From 3a2f056e8adf07f31ea1f6b2856a6d57da1f4777 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:12:38 -0500 Subject: More refactoring --- src/mainwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..9726b27b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -99,7 +99,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -1340,7 +1339,7 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) } m_CurrentSaveView->setSave(save); - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); + QRect screenRect = m_CurrentSaveView->window()->windowHandle()->screen()->geometry(); QPoint pos = QCursor::pos(); if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { @@ -2279,7 +2278,9 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("menubar_visible", m_menuBarVisible); settings.setValue("statusbar_visible", m_statusBarVisible); settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); + QScreen *screen = this->window()->windowHandle()->screen(); + int screenId = QGuiApplication::screens().indexOf(screen); + settings.setValue("window_monitor", screenId); settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); -- cgit v1.3.1 From 7879ea2c623521115eafeef4ad788eef8c18cc2c Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 4 Jul 2019 15:09:49 -0500 Subject: Fix issue if widget isn't on a window --- src/mainwindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9726b27b..6c6eeee7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1339,7 +1339,12 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) } m_CurrentSaveView->setSave(save); - QRect screenRect = m_CurrentSaveView->window()->windowHandle()->screen()->geometry(); + QWindow *window = m_CurrentSaveView->window()->windowHandle(); + QRect screenRect; + if (window == nullptr) + screenRect = QGuiApplication::primaryScreen()->geometry(); + else + screenRect = window->screen()->geometry(); QPoint pos = QCursor::pos(); if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { -- cgit v1.3.1 From 724945ab33864c1fd8d3162bdd85ca66f8bb311d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 16:29:11 -0400 Subject: removed useless validationFailed() callback in MainWindow moved the log that was in it to organizer core --- src/mainwindow.cpp | 6 ------ src/mainwindow.h | 2 -- src/organizercore.cpp | 2 ++ 3 files changed, 2 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 451ec688..9586cb93 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -399,7 +399,6 @@ MainWindow::MainWindow(QSettings &initSettings connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString))); connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), @@ -3095,11 +3094,6 @@ void MainWindow::untrack_clicked() }); } -void MainWindow::validationFailed(const QString &error) -{ - qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); -} - void MainWindow::windowTutorialFinished(const QString &windowName) { m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); diff --git a/src/mainwindow.h b/src/mainwindow.h index eee269cf..80508787 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -510,8 +510,6 @@ private slots: // nexus related void checkModsForUpdates(); - void validationFailed(const QString &message); - void linkClicked(const QString &url); void updateAvailable(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 81ec7f43..65c8eb81 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2484,6 +2484,8 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message) { + qDebug("Nexus API validation failed: %s", qUtf8Printable(message)); + if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == QMessageBox::Yes) { -- cgit v1.3.1 From bc9f286bce224743d244e540d55f26b55affbd4a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 08:30:40 -0400 Subject: moved the log to a dock widget added a menu item in the view menu for it --- src/main.cpp | 4 ++ src/mainwindow.cpp | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 5 ++- src/mainwindow.ui | 69 ++++++++++++++++++++++-------- 4 files changed, 176 insertions(+), 22 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 4359c645..db0c8f93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -706,6 +706,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, SLOT(externalMessage(QString))); mainWindow.processUpdates(); + + // this must be before readSettings(), see DockFixer in mainwindow.cpp + splash.finish(&mainWindow); + mainWindow.readSettings(); qDebug("displaying main window"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9586cb93..4b5ef9ed 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -195,6 +195,102 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock +// sizes are not restored when the main window is maximized; it is used in +// MainWindow::readSettings() and MainWindow::storeSettings() +// +// there's also https://stackoverflow.com/questions/44005852, which has what +// seems to be a popular fix, but it breaks the restored size of the window +// by setting it to the desktop's resolution, so that doesn't work +// +// the only fix I could find is to remember the sizes of the docks and manually +// setting them back; saving is straightforward, but restoring is messy +// +// this also depends on the window being visible before the timer in restore() +// is fired and the timer must be processed by application.exec(); therefore, +// the splash screen _must_ be closed before readSettings() is called, because +// it has its own event loop, which seems to interfere with this +// +// all of this should become unnecessary when QTBUG-46620 is fixed +// +class DockFixer +{ +public: + static void save(MainWindow* mw, QSettings& settings) + { + const auto docks = mw->findChildren(); + + // saves the size of each dock + for (int i=0; isize().width(); + } else { + size = docks[i]->size().height(); + } + + settings.setValue(settingName(docks[i]), size); + } + } + + static void restore(MainWindow* mw, const QSettings& settings) + { + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector dockInfos; + + const auto docks = mw->findChildren(); + + // for each dock + for (int i=0; iresizeDocks({info.d}, {info.size}, info.ori); + } + }); + } + + static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + { + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(d) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } + } + + static QString settingName(QDockWidget* d) + { + return "geometry/" + d->objectName() + "_size"; + } +}; + + MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -422,7 +518,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); + connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -764,7 +861,7 @@ void MainWindow::updatePinnedExecutables() ui->menuRun->menuAction()->setVisible(hasLinks); } -void MainWindow::toolbarMenu_aboutToShow() +void MainWindow::updateToolbarMenu() { // well, this is a bit of a hack to allow the same toolbar menu to be shown // in both the main menu and the context menu @@ -788,6 +885,11 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); } +void MainWindow::updateViewMenu() +{ + ui->actionViewLog->setChecked(ui->logDock->isVisible()); +} + QMenu* MainWindow::createPopupMenu() { return ui->menuToolbars; @@ -838,6 +940,11 @@ void MainWindow::on_actionToolBarIconsAndText_triggered() setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); } +void MainWindow::on_actionViewLog_triggered() +{ + ui->logDock->setVisible(!ui->logDock->isVisible()); +} + void MainWindow::setToolbarSize(const QSize& s) { for (auto* tb : findChildren()) { @@ -1257,7 +1364,7 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().registerAsNXMHandler(false); m_WasVisible = true; - updateProblemsButton(); + updateProblemsButton(); } } @@ -2204,6 +2311,8 @@ void MainWindow::readSettings() if (settings.value("Settings/use_proxy", false).toBool()) { activateProxy(true); } + + DockFixer::restore(this, settings); } void MainWindow::processUpdates() { @@ -2285,10 +2394,13 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + for (const std::pair kv : m_PersistedGeometry) { QString key = QString("geometry/") + kv.first; settings.setValue(key, kv.second->saveState()); } + + DockFixer::save(this, settings); } } @@ -2569,7 +2681,7 @@ void MainWindow::directory_refreshed() if (ui->tabWidget->currentIndex() == 2) { refreshDataTreeKeepExpandedNodes(); } - + } void MainWindow::esplist_changed() diff --git a/src/mainwindow.h b/src/mainwindow.h index 80508787..d7dbfd90 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -219,7 +219,9 @@ private: void updatePinnedExecutables(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); - void toolbarMenu_aboutToShow(); + + void updateToolbarMenu(); + void updateViewMenu(); QMenu* createPopupMenu() override; void activateSelectedProfile(); @@ -654,6 +656,7 @@ private slots: // ui slots void on_actionToolBarIconsOnly_triggered(); void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); + void on_actionViewLog_triggered(); void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 70d1cf39..d83c68ca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1286,23 +1286,6 @@ p, li { white-space: pre-wrap; } - - - Qt::ActionsContextMenu - - - QAbstractItemView::NoSelection - - - true - - - false - - - true - - @@ -1403,6 +1386,7 @@ p, li { white-space: pre-wrap; } + @@ -1417,6 +1401,49 @@ p, li { white-space: pre-wrap; } + + + Log + + + 8 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ActionsContextMenu + + + QAbstractItemView::NoSelection + + + true + + + false + + + true + + + + + + @@ -1736,6 +1763,14 @@ p, li { white-space: pre-wrap; } Status &bar + + + true + + + Log + + -- cgit v1.3.1 From 54d98e291701f2187174a67c186f1ea762c6b959 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:38:32 -0400 Subject: removed unused or redundant stuff in error_report.h renamed log() to vlog() for now extracted console creation to Console class rewrote LogBuffer to work with logging from uibase, renamed to LogModel added fmt dependency --- CMakeLists.txt | 8 +- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 263 +++++++++++++----------------------------- src/logbuffer.h | 70 +++-------- src/main.cpp | 116 ++++++++++++++++--- src/mainwindow.cpp | 35 ++++-- src/mainwindow.h | 1 + src/mainwindow.ui | 15 ++- src/organizercore.cpp | 1 - src/profile.cpp | 5 +- src/shared/directoryentry.cpp | 12 +- src/shared/error_report.cpp | 45 -------- src/shared/error_report.h | 21 +--- 13 files changed, 254 insertions(+), 344 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c76373..ac9d8fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) -ADD_COMPILE_OPTIONS($<$:/MP> $<$:$<$:/O2>> $<$:$<$:/O2>>) +ADD_COMPILE_OPTIONS( + $<$:/MP> + $<$:/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING> + $<$:$<$:/O2>> + $<$:$<$:/O2>>) PROJECT(organizer) @@ -11,9 +15,11 @@ set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) set(CMAKE_INSTALL_MESSAGE NEVER) SET(DEPENDENCIES_DIR CACHE PATH "") + # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) +LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build) ADD_SUBDIRECTORY(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f197211a..a359b8a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -520,6 +520,9 @@ LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) FIND_PACKAGE(zlib REQUIRED) # TODO FindZlib doesn't find the static zlib library +# fmt +find_package(fmt REQUIRED) + INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src @@ -551,10 +554,11 @@ ELSE() ENDIF() ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) + TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets - ${Boost_LIBRARIES} + ${Boost_LIBRARIES} fmt::fmt zlibstatic uibase esptk bsatk githubpp ${usvfs_name} diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index dfe8f943..9e3cd712 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -20,239 +20,140 @@ along with Mod Organizer. If not, see . #include "logbuffer.h" #include #include +#include #include #include #include #include #include -using MOBase::reportError; +using namespace MOBase; -QScopedPointer LogBuffer::s_Instance; -QMutex LogBuffer::s_Mutex; +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) - : QAbstractItemModel(nullptr) - , m_OutFileName(outputFileName) - , m_ShutDown(false) - , m_MinMsgType(minMsgType) - , m_NumMessages(0) +LogModel::LogModel() { - m_Messages.resize(messageCount); + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); } -LogBuffer::~LogBuffer() +void LogModel::create() { - qInstallMessageHandler(0); - write(); + g_instance = new LogModel; } -void LogBuffer::logMessage(QtMsgType type, const QString &message) +LogModel& LogModel::instance() { - if (type >= m_MinMsgType) { - QStringList messagelist = message.split("\n"); - for (auto split_message : messagelist) { - Message msg = {type, QTime::currentTime(), split_message}; - if (m_NumMessages < m_Messages.size()) { - beginInsertRows(QModelIndex(), static_cast(m_NumMessages), - static_cast(m_NumMessages) + 1); - } - m_Messages.at(m_NumMessages % m_Messages.size()) = msg; - if (m_NumMessages < m_Messages.size()) { - endInsertRows(); - } else { - emit dataChanged(createIndex(0, 0), - createIndex(static_cast(m_Messages.size()), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } - } - } -} - -void LogBuffer::write() const -{ - if (m_NumMessages == 0) { - return; - } - - DWORD lastError = ::GetLastError(); - - QFile file(m_OutFileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2") - .arg(m_OutFileName) - .arg(file.errorString())); - return; - } - - unsigned int i - = (m_NumMessages > m_Messages.size()) - ? static_cast(m_NumMessages - m_Messages.size()) - : 0U; - for (; i < m_NumMessages; ++i) { - file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); - file.write("\r\n"); - } - ::SetLastError(lastError); + return *g_instance; } -void LogBuffer::init(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) +void LogModel::add(MOBase::log::Entry e) { - QMutexLocker guard(&s_Mutex); - - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); - qInstallMessageHandler(LogBuffer::log); + emit entryAdded(std::move(e)); } -char LogBuffer::msgTypeID(QtMsgType type) +void LogModel::onEntryAdded(MOBase::log::Entry e) { - switch (type) { - case QtDebugMsg: - return 'D'; - case QtInfoMsg: - return 'I'; - case QtWarningMsg: - return 'W'; - case QtCriticalMsg: - return 'C'; - case QtFatalMsg: - return 'F'; - default: - return '?'; + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; } -} -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, - const QString &message) -{ - // QMutexLocker doesn't support timeout... - if (!s_Mutex.tryLock(100)) { - fprintf(stderr, "failed to log: %s", qUtf8Printable(message)); - return; - } - ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); + const int row = static_cast(m_messages.size()); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); } - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qUtf8Printable(QTime::currentTime().toString()), - msgTypeID(type), qUtf8Printable(message)); + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); } else { - if (context.line != 0) { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qUtf8Printable(message)); - } else { - fprintf(stdout, "%s [%c] %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - qUtf8Printable(message)); - } + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); } - fflush(stdout); } -QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const { return createIndex(row, column, row); } -QModelIndex LogBuffer::parent(const QModelIndex &) const +QModelIndex LogModel::parent(const QModelIndex&) const { return QModelIndex(); } -int LogBuffer::rowCount(const QModelIndex &parent) const +int LogModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; else - return static_cast(std::min(m_NumMessages, m_Messages.size())); + return static_cast(m_messages.size()); } -int LogBuffer::columnCount(const QModelIndex &) const +int LogModel::columnCount(const QModelIndex&) const { - return 2; + return 3; } -QVariant LogBuffer::data(const QModelIndex &index, int role) const +QVariant LogModel::data(const QModelIndex& index, int role) const { - unsigned int offset - = m_NumMessages < m_Messages.size() - ? 0 - : static_cast(m_NumMessages - m_Messages.size()); - unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); - switch (role) { - case Qt::DisplayRole: { - if (index.column() == 0) { - return m_Messages[msgIndex].time.toString("H: mm: ss"); - } else if (index.column() == 1) { - const QString &msg = m_Messages[msgIndex].message; - if (msg.length() < 200) { - return msg; - } else { - return msg.mid(0, 200) + "..."; - } - } - } break; - case Qt::DecorationRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - case QtInfoMsg: - return QIcon(":/MO/gui/information"); - case QtWarningMsg: - return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: - return QIcon(":/MO/gui/important"); - case QtFatalMsg: - return QIcon(":/MO/gui/problem"); - } - } - } break; - case Qt::UserRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - return "D"; - case QtInfoMsg: - return "I"; - case QtWarningMsg: - return "W"; - case QtCriticalMsg: - return "C"; - case QtFatalMsg: - return "F"; - } - } - } break; + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; } - return QVariant(); -} -void LogBuffer::writeNow() -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->write(); + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } } + + return QVariant(); } -void LogBuffer::cleanQuit() +QVariant LogModel::headerData(int, Qt::Orientation, int) const { - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->m_ShutDown = true; - } + return {}; } -void log(const char *format, ...) +void vlog(const char *format, ...) { va_list argList; va_start(argList, format); @@ -268,11 +169,3 @@ void log(const char *format, ...) va_end(argList); } - -QString LogBuffer::Message::toString() const -{ - return QString("%1 [%2] %3") - .arg(time.toString()) - .arg(msgTypeID(type)) - .arg(message); -} diff --git a/src/logbuffer.h b/src/logbuffer.h index 0cfecfa2..1bf8901b 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -26,70 +26,36 @@ along with Mod Organizer. If not, see . #include #include #include +#include - -class LogBuffer : public QAbstractItemModel +class LogModel : public QAbstractItemModel { Q_OBJECT public: + static void create(); + static LogModel& instance(); - static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); - - static void writeNow(); - static void cleanQuit(); - - static LogBuffer *instance() { return s_Instance.data(); } - -public: - - virtual ~LogBuffer(); - - void logMessage(QtMsgType type, const QString &message); + void add(MOBase::log::Entry e); - // QAbstractItemModel interface -public: - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; signals: - -public slots: + void entryAdded(MOBase::log::Entry e); private: + std::deque m_messages; - explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - LogBuffer(const LogBuffer &reference); // not implemented - LogBuffer &operator=(const LogBuffer &reference); // not implemented - - void write() const; - - static char msgTypeID(QtMsgType type); - -private: - - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; - -private: - - static QScopedPointer s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - size_t m_NumMessages; - std::vector m_Messages; - + LogModel(); + void onEntryAdded(MOBase::log::Entry e); }; #endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index db0c8f93..23ea234a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -731,18 +732,46 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } -int doCoreDump(env::CoreDumpTypes type) +class Console { - // open a console - AllocConsole(); +public: + Console() + { + // open a console + AllocConsole(); - // redirect stdin, stdout and stderr to it - FILE* in=nullptr; - FILE* out=nullptr; - FILE* err=nullptr; - freopen_s(&in, "CONIN$", "r", stdin); - freopen_s(&out, "CONOUT$", "w", stdout); - freopen_s(&err, "CONOUT$", "w", stderr); + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); + } + + ~Console() + { + // close redirected handles + std::fclose(m_err); + std::fclose(m_out); + std::fclose(m_in); + + // close console + FreeConsole(); + + // redirect stdin, stdout and stderr to NUL, don't bother closing the + // handles + freopen_s(&m_in, "NUL", "r", stdin); + freopen_s(&m_out, "NUL", "w", stdout); + freopen_s(&m_err, "NUL", "w", stderr); + } + +private: + FILE* m_in = nullptr; + FILE* m_out = nullptr; + FILE* m_err = nullptr; +}; + +int doCoreDump(env::CoreDumpTypes type) +{ + Console c; // dump const auto b = env::coredumpOther(type); @@ -753,15 +782,66 @@ int doCoreDump(env::CoreDumpTypes type) std::wcerr << L"Press enter to continue..."; std::wcin.get(); - // close redirected handles - std::fclose(err); - std::fclose(out); - std::fclose(in); + return (b ? 0 : 1); +} + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) + { + case QtDebugMsg: + return log::Debug; - // close console - FreeConsole(); + case QtWarningMsg: + return log::Warning; - return (b ? 0 : 1); + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } +} + +void qtLogCallback( + QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log( + convertQtLevel(type), "{}", + message.toStdString()); + } else { + log::log( + convertQtLevel(type), "[{}:{}] {}", + file, context.line, message.toStdString()); + } +} + +void initLogging(const QString& logFile) +{ + LogModel::create(); + + log::init( + true, MOBase::log::File::rotating(logFile.toStdWString(), 5*1024*1024, 5), + MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$", + [](log::Entry e){ LogModel::instance().add(e); }); + + qInstallMessageHandler(qtLogCallback); } int main(int argc, char *argv[]) @@ -839,7 +919,7 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(1000000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + initLogging(qApp->property("dataPath").toString() + "/logs/mo_interface.log"); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4b5ef9ed..cd224414 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,17 +357,10 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + setupLogList(); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); updateProblemsButton(); @@ -593,6 +586,30 @@ MainWindow::MainWindow(QSettings &initSettings updateModCount(); } +void MainWindow::setupLogList() +{ + ui->logList->setModel(&LogModel::instance()); + + const int timestampWidth = + QFontMetrics(ui->logList->font()).width("00:00:00.000"); + + ui->logList->header()->setMinimumSectionSize(0); + ui->logList->header()->resizeSection(0, 20); + ui->logList->header()->resizeSection(1, timestampWidth + 8); + + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + + connect( + ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + ui->logList, SLOT(scrollToBottom())); + + connect( + ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + ui->logList, SLOT(scrollToBottom())); +} + void MainWindow::resetActionIcons() { // this is a bit of a hack diff --git a/src/mainwindow.h b/src/mainwindow.h index d7dbfd90..9ca3e5c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,6 +636,7 @@ private slots: void resetActionIcons(); void updateModCount(); void updatePluginCount(); + void setupLogList(); private slots: // ui slots // actions diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d83c68ca..5a45b3b4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1402,6 +1402,9 @@ p, li { white-space: pre-wrap; } + + QDockWidget::AllDockWidgetFeatures + Log @@ -1428,17 +1431,17 @@ p, li { white-space: pre-wrap; } Qt::ActionsContextMenu - QAbstractItemView::NoSelection - - - true + QAbstractItemView::ExtendedSelection - + false - + true + + false + diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eeb69e61..5f5c3afe 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -342,7 +342,6 @@ OrganizerCore::~OrganizerCore() m_CurrentProfile = nullptr; ModInfo::clear(); - LogBuffer::cleanQuit(); m_ModList.setProfile(nullptr); // NexusInterface::instance()->cleanup(); diff --git a/src/profile.cpp b/src/profile.cpp index ef387027..01906903 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -287,8 +287,11 @@ void Profile::createTweakedIniFile() } if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str())); + const auto e = ::GetLastError(); + reportError(tr("failed to create tweaked ini: %1") + .arg(formatSystemMessageQ(e))); } + qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index bde515a9..9d9edd85 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -103,7 +103,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + vlog("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); } } @@ -714,12 +714,12 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { m_Files.erase(iter); } else { - log("file \"%ls\" not in directory \"%ls\"", + vlog("file \"%ls\" not in directory \"%ls\"", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } } else { - log("file \"%ls\" not in directory \"%ls\", directory empty", + vlog("file \"%ls\" not in directory \"%ls\", directory empty", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } @@ -844,7 +844,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - log("unexpected end of path"); + vlog("unexpected end of path"); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -988,7 +988,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - log("invalid file index for remove: %lu", index); + vlog("invalid file index for remove: %lu", index); return false; } } @@ -1002,7 +1002,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - log("invalid file index for remove (for origin): %lu", index); + vlog("invalid file index for remove (for origin): %lu", index); } } diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 6d091630..4185b544 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . namespace MOShared { - void reportError(LPCSTR format, ...) { char buffer[1025]; @@ -52,48 +51,4 @@ void reportError(LPCWSTR format, ...) MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); } - -std::string getCurrentErrorStringA() -{ - LPSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::string(); - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; - - std::string result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} - -std::wstring getCurrentErrorStringW() -{ - LPWSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::wstring(); - } else { - LPWSTR lastChar = buffer + wcslen(buffer) - 2; - *lastChar = '\0'; - - std::wstring result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} } // namespace MOShared diff --git a/src/shared/error_report.h b/src/shared/error_report.h index c09ad75b..a003ee09 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -24,28 +24,11 @@ along with Mod Organizer. If not, see . #include #include -namespace std { -#ifdef UNICODE -typedef wstring tstring; -#else -typedef string tstring; -#endif -} - -extern void log(const char* format, ...); - namespace MOShared { void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); -std::string getCurrentErrorStringA(); -std::wstring getCurrentErrorStringW(); - -#ifdef UNICODE -#define getCurrentErrorString getCurrentErrorStringW -#else -#define getCurrentErrorString getCurrentErrorStringA -#endif - } // namespace MOShared + +void vlog(const char* format, ...); -- cgit v1.3.1 From 6217f910f095adea4b06f370726636b09efea4ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:42:22 -0400 Subject: renamed logbuffer files to loglist --- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 171 -------------------------------------------------- src/logbuffer.h | 61 ------------------ src/loglist.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/loglist.h | 61 ++++++++++++++++++ src/main.cpp | 2 +- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 1 - 8 files changed, 237 insertions(+), 238 deletions(-) delete mode 100644 src/logbuffer.cpp delete mode 100644 src/logbuffer.h create mode 100644 src/loglist.cpp create mode 100644 src/loglist.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a359b8a9..9dbab132 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,7 +73,7 @@ SET(organizer_SRCS mainwindow.cpp main.cpp loghighlighter.cpp - logbuffer.cpp + loglist.cpp lockeddialogbase.cpp lockeddialog.cpp waitingonclosedialog.cpp @@ -181,7 +181,7 @@ SET(organizer_HDRS messagedialog.h mainwindow.h loghighlighter.h - logbuffer.h + loglist.h lockeddialogbase.h lockeddialog.h waitingonclosedialog.h @@ -436,7 +436,7 @@ set(widgets filterwidget icondelegate lcdnumber - logbuffer + loglist loghighlighter modflagicondelegate modidlineedit diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp deleted file mode 100644 index 9e3cd712..00000000 --- a/src/logbuffer.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "logbuffer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -static LogModel* g_instance = nullptr; -const std::size_t MaxLines = 1000; - -LogModel::LogModel() -{ - connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); -} - -void LogModel::create() -{ - g_instance = new LogModel; -} - -LogModel& LogModel::instance() -{ - return *g_instance; -} - -void LogModel::add(MOBase::log::Entry e) -{ - emit entryAdded(std::move(e)); -} - -void LogModel::onEntryAdded(MOBase::log::Entry e) -{ - bool full = false; - if (m_messages.size() > MaxLines) { - m_messages.pop_front(); - full = true; - } - - const int row = static_cast(m_messages.size()); - - if (!full) { - beginInsertRows(QModelIndex(), row, row + 1); - } - - m_messages.emplace_back(std::move(e)); - - if (!full) { - endInsertRows(); - } else { - emit dataChanged( - createIndex(row, 0), - createIndex(row + 1, columnCount({}))); - } -} - -QModelIndex LogModel::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogModel::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - return 0; - else - return static_cast(m_messages.size()); -} - -int LogModel::columnCount(const QModelIndex&) const -{ - return 3; -} - -QVariant LogModel::data(const QModelIndex& index, int role) const -{ - using namespace std::chrono; - - const auto row = static_cast(index.row()); - if (row >= m_messages.size()) { - return {}; - } - - const auto& e = m_messages[row]; - - if (role == Qt::DisplayRole) { - if (index.column() == 1) { - const auto ms = duration_cast(e.time.time_since_epoch()); - const auto s = duration_cast(ms); - - const std::time_t t = s.count(); - const std::size_t frac = ms.count() % 1000; - - auto time = QDateTime::fromTime_t(t).time(); - time = time.addMSecs(frac); - - return time.toString("hh:mm:ss.zzz"); - } else if (index.column() == 2) { - return QString::fromStdString(e.message); - } - } - - if (role == Qt::DecorationRole) { - if (index.column() == 0) { - switch (e.level) { - case log::Warning: - return QIcon(":/MO/gui/warning"); - - case log::Error: - return QIcon(":/MO/gui/problem"); - - case log::Debug: // fall-through - case log::Info: - default: - return {}; - } - } - } - - return QVariant(); -} - -QVariant LogModel::headerData(int, Qt::Orientation, int) const -{ - return {}; -} - -void vlog(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} diff --git a/src/logbuffer.h b/src/logbuffer.h deleted file mode 100644 index 1bf8901b..00000000 --- a/src/logbuffer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef LOGBUFFER_H -#define LOGBUFFER_H - -#include -#include -#include -#include -#include -#include -#include - -class LogModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - static void create(); - static LogModel& instance(); - - void add(MOBase::log::Entry e); - -protected: - QModelIndex index(int row, int column, const QModelIndex& parent) const override; - QModelIndex parent(const QModelIndex &child) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - - QVariant headerData( - int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; - -signals: - void entryAdded(MOBase::log::Entry e); - -private: - std::deque m_messages; - - LogModel(); - void onEntryAdded(MOBase::log::Entry e); -}; - -#endif // LOGBUFFER_H diff --git a/src/loglist.cpp b/src/loglist.cpp new file mode 100644 index 00000000..cb927272 --- /dev/null +++ b/src/loglist.cpp @@ -0,0 +1,171 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "loglist.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; + +LogModel::LogModel() +{ + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); +} + +void LogModel::create() +{ + g_instance = new LogModel; +} + +LogModel& LogModel::instance() +{ + return *g_instance; +} + +void LogModel::add(MOBase::log::Entry e) +{ + emit entryAdded(std::move(e)); +} + +void LogModel::onEntryAdded(MOBase::log::Entry e) +{ + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; + } + + const int row = static_cast(m_messages.size()); + + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); + } + + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); + } else { + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); + } +} + +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex LogModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int LogModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + else + return static_cast(m_messages.size()); +} + +int LogModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +QVariant LogModel::data(const QModelIndex& index, int role) const +{ + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; + } + + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } + } + + return QVariant(); +} + +QVariant LogModel::headerData(int, Qt::Orientation, int) const +{ + return {}; +} + +void vlog(const char *format, ...) +{ + va_list argList; + va_start(argList, format); + + static const int BUFFERSIZE = 1000; + + char buffer[BUFFERSIZE + 1]; + buffer[BUFFERSIZE] = '\0'; + + vsnprintf(buffer, BUFFERSIZE, format, argList); + + qCritical("%s", buffer); + + va_end(argList); +} diff --git a/src/loglist.h b/src/loglist.h new file mode 100644 index 00000000..1bf8901b --- /dev/null +++ b/src/loglist.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef LOGBUFFER_H +#define LOGBUFFER_H + +#include +#include +#include +#include +#include +#include +#include + +class LogModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + static void create(); + static LogModel& instance(); + + void add(MOBase::log::Entry e); + +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + +signals: + void entryAdded(MOBase::log::Entry e); + +private: + std::deque m_messages; + + LogModel(); + void onEntryAdded(MOBase::log::Entry e); +}; + +#endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index 23ea234a..8b5648c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "singleinstance.h" #include "utility.h" #include "helper.h" -#include "logbuffer.h" +#include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" #include "tutorialmanager.h" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cd224414..107f3e09 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,7 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "logbuffer.h" +#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5f5c3afe..d3cd54ee 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -14,7 +14,6 @@ #include "plugincontainer.h" #include "pluginlistsortproxy.h" #include "profile.h" -#include "logbuffer.h" #include "credentialsdialog.h" #include "filedialogmemory.h" #include "modinfodialog.h" -- cgit v1.3.1 From d1b4dec8ad1635738ada3dfbde5907e7f0df3448 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:58:51 -0400 Subject: moved setup to new LogList class --- src/loglist.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++------- src/loglist.h | 14 +++++++++++-- src/mainwindow.cpp | 35 ++----------------------------- src/mainwindow.h | 1 - src/mainwindow.ui | 10 +++++---- 5 files changed, 74 insertions(+), 47 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loglist.cpp b/src/loglist.cpp index cb927272..9e876d37 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -52,21 +52,26 @@ void LogModel::add(MOBase::log::Entry e) emit entryAdded(std::move(e)); } +const std::deque& LogModel::entries() const +{ + return m_entries; +} + void LogModel::onEntryAdded(MOBase::log::Entry e) { bool full = false; - if (m_messages.size() > MaxLines) { - m_messages.pop_front(); + if (m_entries.size() > MaxLines) { + m_entries.pop_front(); full = true; } - const int row = static_cast(m_messages.size()); + const int row = static_cast(m_entries.size()); if (!full) { beginInsertRows(QModelIndex(), row, row + 1); } - m_messages.emplace_back(std::move(e)); + m_entries.emplace_back(std::move(e)); if (!full) { endInsertRows(); @@ -92,7 +97,7 @@ int LogModel::rowCount(const QModelIndex& parent) const if (parent.isValid()) return 0; else - return static_cast(m_messages.size()); + return static_cast(m_entries.size()); } int LogModel::columnCount(const QModelIndex&) const @@ -105,11 +110,11 @@ QVariant LogModel::data(const QModelIndex& index, int role) const using namespace std::chrono; const auto row = static_cast(index.row()); - if (row >= m_messages.size()) { + if (row >= m_entries.size()) { return {}; } - const auto& e = m_messages[row]; + const auto& e = m_entries[row]; if (role == Qt::DisplayRole) { if (index.column() == 1) { @@ -153,6 +158,48 @@ QVariant LogModel::headerData(int, Qt::Orientation, int) const return {}; } + +LogList::LogList(QWidget* parent) + : QTreeView(parent) +{ + setModel(&LogModel::instance()); + + const int timestampWidth = QFontMetrics(font()).width("00:00:00.000"); + + header()->setMinimumSectionSize(0); + header()->resizeSection(0, 20); + header()->resizeSection(1, timestampWidth + 8); + + setAutoScroll(true); + scrollToBottom(); + + connect( + model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(scrollToBottom())); + + connect( + model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(scrollToBottom())); +} + +void LogList::copyToClipboard() +{ + std::string s; + + auto* m = static_cast(model()); + for (const auto& e : m->entries()) { + s += e.formattedMessage + "\n"; + } + + if (!s.empty()) { + // last newline + s.pop_back(); + } + + QApplication::clipboard()->setText(QString::fromStdString(s)); +} + + void vlog(const char *format, ...) { va_list argList; diff --git a/src/loglist.h b/src/loglist.h index 1bf8901b..d1f7a2ad 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -37,6 +37,7 @@ public: static LogModel& instance(); void add(MOBase::log::Entry e); + const std::deque& entries() const; protected: QModelIndex index(int row, int column, const QModelIndex& parent) const override; @@ -44,7 +45,7 @@ protected: int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; - + QVariant headerData( int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; @@ -52,10 +53,19 @@ signals: void entryAdded(MOBase::log::Entry e); private: - std::deque m_messages; + std::deque m_entries; LogModel(); void onEntryAdded(MOBase::log::Entry e); }; + +class LogList : public QTreeView +{ +public: + LogList(QWidget* parent=nullptr); + + void copyToClipboard(); +}; + #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 107f3e09..4e91ef9f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - setupLogList(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -586,30 +586,6 @@ MainWindow::MainWindow(QSettings &initSettings updateModCount(); } -void MainWindow::setupLogList() -{ - ui->logList->setModel(&LogModel::instance()); - - const int timestampWidth = - QFontMetrics(ui->logList->font()).width("00:00:00.000"); - - ui->logList->header()->setMinimumSectionSize(0); - ui->logList->header()->resizeSection(0, 20); - ui->logList->header()->resizeSection(1, timestampWidth + 8); - - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); - - connect( - ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - - connect( - ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); -} - void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -6837,14 +6813,7 @@ void MainWindow::on_restoreModsButton_clicked() void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() { - QStringList lines; - QAbstractItemModel *model = ui->logList->model(); - for (int i = 0; i < model->rowCount(); ++i) { - lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) - .arg(model->index(i, 1).data(Qt::UserRole).toString()) - .arg(model->index(i, 1).data().toString())); - } - QApplication::clipboard()->setText(lines.join("\n")); + ui->logList->copyToClipboard(); } void MainWindow::on_categoriesAndBtn_toggled(bool checked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 9ca3e5c3..d7dbfd90 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,7 +636,6 @@ private slots: void resetActionIcons(); void updateModCount(); void updatePluginCount(); - void setupLogList(); private slots: // ui slots // actions diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5a45b3b4..c694abd4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1426,13 +1426,10 @@ p, li { white-space: pre-wrap; } 0 - + Qt::ActionsContextMenu - - QAbstractItemView::ExtendedSelection - false @@ -1807,6 +1804,11 @@ p, li { white-space: pre-wrap; } QWidget
sortabletreewidget.h
+ + LogList + QTreeView +
loglist.h
+
-- cgit v1.3.1 From bca6283311cf1dea4c96f8ee5bf192bdb1640cb3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 08:56:16 -0400 Subject: use log::Levels instead of ints create log level combobox in code, set selected index based on value instead added log level to context menu in log list --- src/mainwindow.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++--- src/mainwindow.h | 12 +++++++++--- src/mainwindow.ui | 30 +++++++++++++++--------------- src/organizercore.cpp | 4 +++- src/organizercore.h | 5 ++++- src/settings.cpp | 30 ++++++++++++++++++++++++++---- src/settings.h | 10 +++++++++- src/settingsdialog.ui | 20 -------------------- src/usvfsconnector.cpp | 20 +++++++++++--------- src/usvfsconnector.h | 7 ++++++- 10 files changed, 127 insertions(+), 58 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4e91ef9f..f65bf4e1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + setupLogMenu(); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -812,6 +812,36 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } +void MainWindow::setupLogMenu() +{ + connect(ui->logList, &QWidget::customContextMenuRequested, [&](auto&& pos){ + auto* menu = new QMenu(ui->logList); + + menu->addAction(tr("Copy& Log"), [&]{ ui->logList->copyToClipboard(); }); + menu->addSeparator(); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + connect(a, &QAction::triggered, [this, level]{ setLogLevel(level); }); + levels->addAction(a); + }; + + addAction(tr("&Errors"), log::Error); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Info"), log::Info); + addAction(tr("&Debug"), log::Debug); + + menu->popup(ui->logList->viewport()->mapToGlobal(pos)); + }); +} + void MainWindow::updatePinnedExecutables() { for (auto* a : ui->toolBar->actions()) { @@ -5287,12 +5317,23 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist()); + setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } +void MainWindow::setLogLevel(log::Levels level) +{ + auto& s = m_OrganizerCore.settings(); + + s.setLogLevel(level); + + m_OrganizerCore.updateVFSParams( + s.logLevel(), s.crashDumpsType(), s.executablesBlacklist()); + + log::getDefault().setLevel(s.logLevel()); +} void MainWindow::on_actionNexus_triggered() { @@ -6811,7 +6852,7 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() +void MainWindow::on_actionLogCopy_triggered() { ui->logList->copyToClipboard(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index d7dbfd90..74993667 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -30,6 +30,9 @@ along with Mod Organizer. If not, see . #include "modlistsortproxy.h" #include "savegameinfo.h" #include "tutorialcontrol.h" +#include "plugincontainer.h" //class PluginContainer; +#include "iplugingame.h" //namespace MOBase { class IPluginGame; } +#include //Note the commented headers here can be replaced with forward references, //when I get round to cleaning up main.cpp @@ -38,10 +41,10 @@ class CategoryFactory; class LockedDialogBase; class OrganizerCore; class StatusBar; -#include "plugincontainer.h" //class PluginContainer; + class PluginListSortProxy; namespace BSA { class Archive; } -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } + namespace MOBase { class IPluginModPage; } namespace MOBase { class IPluginTool; } namespace MOBase { class ISaveGame; } @@ -633,10 +636,13 @@ private slots: void search_activated(); void searchClear_activated(); + void setupLogMenu(); void resetActionIcons(); void updateModCount(); void updatePluginCount(); + void setLogLevel(MOBase::log::Levels level); + private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); @@ -690,7 +696,7 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); + void on_actionLogCopy_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c694abd4..fc2bcdd3 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1428,7 +1428,7 @@ p, li { white-space: pre-wrap; } - Qt::ActionsContextMenu + Qt::CustomContextMenu false @@ -1645,20 +1645,6 @@ p, li { white-space: pre-wrap; } Endorse Mod Organizer - - - Copy &Log - - - Copy &Log - - - Copy log to clipboard - - - Copy log to clipboard - - @@ -1771,6 +1757,20 @@ p, li { white-space: pre-wrap; } Log + + + Copy &Log + + + Copy &Log + + + Copy log to clipboard + + + Copy log to clipboard + + diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3cd54ee..25fbc7cd 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -726,7 +726,9 @@ void OrganizerCore::prepareVFS() m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); } -void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist) { +void OrganizerCore::updateVFSParams( + log::Levels logLevel, int crashDumpsType, QString executableBlacklist) +{ setGlobalCrashDumpsType(crashDumpsType); m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } diff --git a/src/organizercore.h b/src/organizercore.h index 99b1c5f2..ef1a4133 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -21,6 +21,7 @@ #include #include #include "executableinfo.h" +#include class ModListSortProxy; class PluginListSortProxy; @@ -191,7 +192,9 @@ public: void prepareVFS(); - void updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist); + void updateVFSParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); bool cycleDiagnostics(); diff --git a/src/settings.cpp b/src/settings.cpp index 5cb2524f..e622d632 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -415,9 +415,14 @@ bool Settings::offlineMode() const return m_Settings.value("Settings/offline_mode", false).toBool(); } -int Settings::logLevel() const +log::Levels Settings::logLevel() const { - return m_Settings.value("Settings/log_level", static_cast(LogLevel::Info)).toInt(); + return static_cast(m_Settings.value("Settings/log_level").toInt()); +} + +void Settings::setLogLevel(log::Levels level) +{ + m_Settings.setValue("Settings/log_level", static_cast(level)); } int Settings::crashDumpsType() const @@ -1000,7 +1005,7 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d , m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")) , m_diagnosticsExplainedLabel(m_dialog.findChild("diagnosticsExplainedLabel")) { - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + setLevelsBox(); m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() @@ -1016,11 +1021,28 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d void Settings::DiagnosticsTab::update() { - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/log_level", m_logLevelBox->currentData().toInt()); m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); } +void Settings::DiagnosticsTab::setLevelsBox() +{ + m_logLevelBox->clear(); + + m_logLevelBox->addItem(tr("Debug"), log::Debug); + m_logLevelBox->addItem(tr("Info (recommended)"), log::Info); + m_logLevelBox->addItem(tr("Warning"), log::Warning); + m_logLevelBox->addItem(tr("Error"), log::Error); + + for (int i=0; icount(); ++i) { + if (m_logLevelBox->itemData(i) == m_parent->logLevel()) { + m_logLevelBox->setCurrentIndex(i); + break; + } + } +} + Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) : Settings::SettingsTab(parent, dialog) , m_offlineBox(dialog.findChild("offlineBox")) diff --git a/src/settings.h b/src/settings.h index bccd1e81..c66eb94c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include #include #include @@ -232,7 +233,12 @@ public: /** * @return the configured log level */ - int logLevel() const; + MOBase::log::Levels logLevel() const; + + /** + * sets the log level setting + */ + void setLogLevel(MOBase::log::Levels level); /** * @return the configured crash dumps type @@ -481,6 +487,8 @@ private: QComboBox *m_dumpsTypeBox; QSpinBox *m_dumpsMaxEdit; QLabel *m_diagnosticsExplainedLabel; + + void setLevelsBox(); }; /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index fccc8be0..1e94bcde 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1426,26 +1426,6 @@ programs you are intentionally running. "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. - - - Debug - - - - - Info (recommended) - - - - - Warning - - - - - Error - - diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index b752667d..197955b8 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -32,7 +32,7 @@ along with Mod Organizer. If not, see . #include static const char SHMID[] = "mod_organizer_instance"; - +using namespace MOBase; std::string to_hex(void *bufferIn, size_t bufferSize) { @@ -90,15 +90,16 @@ void LogWorker::exit() m_QuitRequested = true; } -LogLevel logLevel(int level) +LogLevel toUsvfsLogLevel(log::Levels level) { - switch (static_cast(level)) { - case LogLevel::Info: + switch (level) { + case log::Info: return LogLevel::Info; - case LogLevel::Warning: + case log::Warning: return LogLevel::Warning; - case LogLevel::Error: + case log::Error: return LogLevel::Error; + case log::Debug: // fall-through default: return LogLevel::Debug; } @@ -121,7 +122,7 @@ CrashDumpsType crashDumpsType(int type) UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = logLevel(Settings::instance().logLevel()); + LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); @@ -205,9 +206,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) */ } -void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist) +void UsvfsConnector::updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist) { - USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType)); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 8a88bde5..b0bd320c 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include "executableinfo.h" @@ -84,7 +85,11 @@ public: ~UsvfsConnector(); void updateMapping(const MappingType &mapping); - void updateParams(int logLevel, int crashDumpsType, QString executableBlacklist); + + void updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); + void updateForcedLibraries(const QList &forcedLibraries); private: -- cgit v1.3.1 From 28c46eed919cf3044147f642ea0a8d9909fea2ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:53:25 -0400 Subject: reversed log level menu actions to fit settings combobox replaced qWarnings() and qCritical() --- src/main.cpp | 11 ++++------- src/mainwindow.cpp | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index aa842ead..b89e4a80 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,9 +133,9 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except int dumpRes = CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); + log::error("ModOrganizer has crashed, crash dump created."); else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError()); if (prevUnhandledExceptionFilter) return prevUnhandledExceptionFilter(exceptionPtrs); @@ -456,16 +456,13 @@ void preloadDll(const QString& filename) const auto dllPath = appPath + "\\" + filename; if (!QFile::exists(dllPath)) { - qWarning().nospace() << dllPath << "not found"; + log::warn("{} not found", dllPath); return; } if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - - qWarning().nospace() - << "failed to load " << dllPath << ": " - << formatSystemMessage(e); + log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f65bf4e1..6c2ab389 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -833,10 +833,10 @@ void MainWindow::setupLogMenu() levels->addAction(a); }; - addAction(tr("&Errors"), log::Error); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Info"), log::Info); addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); menu->popup(ui->logList->viewport()->mapToGlobal(pos)); }); -- cgit v1.3.1 From b2a1e1391fdd6bdee1c5e8d337b273447c70a506 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:13:57 -0400 Subject: split env --- src/CMakeLists.txt | 25 +- src/env.cpp | 479 ++++++++++++ src/env.h | 117 +++ src/envmetrics.cpp | 224 ++++++ src/envmetrics.h | 28 + src/envmodule.cpp | 390 ++++++++++ src/envmodule.h | 98 +++ src/envsecurity.cpp | 418 ++++++++++ src/envsecurity.h | 49 ++ src/envshortcut.cpp | 376 +++++++++ src/envshortcut.h | 114 +++ src/envwindows.cpp | 236 ++++++ src/envwindows.h | 106 +++ src/main.cpp | 2 + src/mainwindow.cpp | 1 + src/shared/util.cpp | 2123 +-------------------------------------------------- src/shared/util.h | 443 ----------- 17 files changed, 2663 insertions(+), 2566 deletions(-) create mode 100644 src/env.cpp create mode 100644 src/env.h create mode 100644 src/envmetrics.cpp create mode 100644 src/envmetrics.h create mode 100644 src/envmodule.cpp create mode 100644 src/envmodule.h create mode 100644 src/envsecurity.cpp create mode 100644 src/envsecurity.h create mode 100644 src/envshortcut.cpp create mode 100644 src/envshortcut.h create mode 100644 src/envwindows.cpp create mode 100644 src/envwindows.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dbab132..9785dc3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,12 @@ SET(organizer_SRCS filerenamer.cpp texteditor.cpp expanderwidget.cpp + env.cpp + envmetrics.cpp + envmodule.cpp + envsecurity.cpp + envshortcut.cpp + envwindows.cpp shared/windows_error.cpp shared/error_report.cpp @@ -239,6 +245,12 @@ SET(organizer_HDRS filerenamer.h texteditor.h expanderwidget.h + env.h + envmetrics.h + envmodule.h + envsecurity.h + envshortcut.h + envwindows.h shared/windows_error.h shared/error_report.h @@ -350,6 +362,15 @@ set(downloads downloadmanager ) +set(env + env + envmetrics + envmodule + envsecurity + envshortcut + envwindows +) + set(executables executableslist editexecutablesdialog @@ -447,8 +468,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo modinfo\\dialog - modlist plugins previews profiles settings utilities widgets + application core browser dialogs downloads env executables locking modinfo + modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // double the buffer size 10 times + const int MaxTries = 10; + + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process has pid 0 and seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot for system processes, even + // when elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + // going through processes, trying to find one with the same name and a + // different pid than this process has + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i; + + +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +template +using COMPtr = std::unique_ptr; + + +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + +// represents the process's environment +// +class Environment +{ +public: + Environment(); + ~Environment(); + + // list of loaded modules in the current process + // + const std::vector& loadedModules() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; + + // information about the installed security products + // + const std::vector& securityProducts() const; + + // information about displays + // + const Metrics& metrics() const; + + // logs the environment + // + void dump() const; + +private: + std::vector m_modules; + std::unique_ptr m_windows; + std::vector m_security; + std::unique_ptr m_metrics; +}; + + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace env diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp new file mode 100644 index 00000000..a6988909 --- /dev/null +++ b/src/envmetrics.cpp @@ -0,0 +1,224 @@ +#include "envmetrics.h" +#include "env.h" +#include +#include +#include +#include + +namespace env +{ + +using namespace MOBase; + +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + +} // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h new file mode 100644 index 00000000..62fc8c49 --- /dev/null +++ b/src/envmetrics.h @@ -0,0 +1,28 @@ +#include +#include + +namespace env +{ + +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + +} // namespace diff --git a/src/envmodule.cpp b/src/envmodule.cpp new file mode 100644 index 00000000..1717da15 --- /dev/null +++ b/src/envmodule.cpp @@ -0,0 +1,390 @@ +#include "envmodule.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; + m_md5 = getMD5(); +} + +const QString& Module::path() const +{ + return m_path; +} + +QString Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + +std::size_t Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Module::version() const +{ + return m_version; +} + +const QString& Module::versionString() const +{ + return m_versionString; +} + +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + +QString Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Module::toString() const +{ + QStringList sl; + + // file size + sl.push_back(displayPath()); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + // version + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (!m_versionString.isEmpty() && m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + // timestamp + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + // md5 + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + + return sl.join(", "); +} + +Module::FileInfo Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + // getting version info size + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + qCritical().nospace().noquote() + << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // getting version info + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // the version info has two major parts: a fixed version and a localizable + // set of strings + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // the fixed version info is in the root + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = static_cast(valuePointer); + + // signature is always 0xfeef04bd + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // getting list of available languages + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + qCritical().nospace().noquote() + << "VerQueryValueW() for translations failed on '" << m_path << "'"; + + return {}; + } + + // number of languages + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + // using the first language in the list to get FileVersion + const auto* lcp = static_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + static_cast(valuePointer), valueSize - 1); +} + +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file + HandlePtr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessageQ(e); + + return {}; + } + + // getting the file time + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical().nospace().noquote() + << "can't get file time for '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + } else { + // use the time from the file info + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + // converting to SYSTEMTIME + SYSTEMTIME utc = {}; + + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical().nospace().noquote() + << "FileTimeToSystemTime() failed on timestamp " + << "high=0x" << hex << ft.dwHighDateTime << " " + << "low=0x" << hex << ft.dwLowDateTime << " for " + << "'" << m_path << "'"; + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + +QString Module::getMD5() const +{ + if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + return {}; + } + + // opening the file + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + // hashing + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + + +std::vector getLoadedModules() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessageQ(e); + + return {}; + } + + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // first module, this shouldn't fail because there's at least the executable + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "Module32First() failed, " << formatSystemMessageQ(e); + + return {}; + } + + std::vector v; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } + + // next module + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessageQ(e); + } + + break; + } + } + + // sorting by display name + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); + + return v; +} + +} // namespace diff --git a/src/envmodule.h b/src/envmodule.h new file mode 100644 index 00000000..ea1156bd --- /dev/null +++ b/src/envmodule.h @@ -0,0 +1,98 @@ +#include +#include + +namespace env +{ + +// represents one module +// +class Module +{ +public: + explicit Module(QString path, std::size_t fileSize); + + // returns the module's path + // + const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // + QString displayPath() const; + + // returns the size in bytes, may be 0 + // + std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // + const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // + const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // + QString timestampString() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + // contains the information from the version resource + // + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; + + // returns information from the version resource + // + FileInfo getFileInfo() const; + + // uses VS_FIXEDFILEINFO to build the version string + // + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // + QString getMD5() const; + + // gets VS_FIXEDFILEINFO from the file version info buffer + // + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // + QString getFileDescription(std::byte* buffer) const; +}; + + +std::vector getLoadedModules(); + +} // namespace env diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp new file mode 100644 index 00000000..559ce4ad --- /dev/null +++ b/src/envsecurity.cpp @@ -0,0 +1,418 @@ +#include "envsecurity.h" +#include "env.h" +#include + +#include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + +namespace env +{ + +using namespace MOBase; + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } + + for (;;) + { + COMPtr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + +private: + COMPtr m_locator; + COMPtr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret) || !rawLocator) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessageQ(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res) || !rawService) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessageQ(res); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + qCritical() + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); + + throw failed(); + } + } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } +}; + + +SecurityProduct::SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate) : + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{ +} + +const QString& SecurityProduct::name() const +{ + return m_name; +} + +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const +{ + QString s; + + s += m_name + " (" + providerToString() + ")"; + + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} + +QString SecurityProduct::providerToString() const +{ + QStringList ps; + + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + return "doesn't provider anything"; + } + + return ps.join("|"); +} + + +std::vector getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + // display name + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get displayName, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + // product state + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + + VariantClear(&prop); + + // guid + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; + + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); + + map.insert({ + guid, + {guid, QString::fromStdWString(name), provider, active, upToDate}}); + }; + + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; + + for (auto&& p : map) { + v.push_back(p.second); + } + + return v; +} + +std::optional getWindowsFirewall() +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessageQ(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessageQ(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); +} + + +std::vector getSecurityProducts() +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; +} + +} // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h new file mode 100644 index 00000000..200cb531 --- /dev/null +++ b/src/envsecurity.h @@ -0,0 +1,49 @@ +#include +#include + +namespace env +{ + +// represents a security product, such as an antivirus or a firewall +// +class SecurityProduct +{ +public: + SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate); + + // display name of the product + // + const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // + int provider() const; + + // whether the product is active + // + bool active() const; + + // whether its definitions are up-to-date + // + bool upToDate() const; + + // string representation of the above + // + QString toString() const; + +private: + QUuid m_guid; + QString m_name; + int m_provider; + bool m_active; + bool m_upToDate; + + QString providerToString() const; +}; + + +std::vector getSecurityProducts(); + +} // namespace env diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp new file mode 100644 index 00000000..30ef4633 --- /dev/null +++ b/src/envshortcut.cpp @@ -0,0 +1,376 @@ +#include "envshortcut.h" +#include "env.h" +#include "executableslist.h" +#include "instancemanager.h" +#include + +namespace env +{ + +using namespace MOBase; + +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + throw ShellLinkException("path cannot be empty"); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + throw ShellLinkException("creating IShellLink worked, pointer is null"); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + throw ShellLinkException("querying IPersistFile worked, pointer is null"); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; + return false; + } + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "shorcut file will be saved at '" << path << "'"; + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException& e) + { + critical() << e.what() << "\nshortcut file was not saved"; + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "path to shortcut file is '" << path << "'"; + + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; + return false; + } + + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + critical() << "bad location " << loc; + break; + } + } + catch(std::exception&) + { + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + critical() << "name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + +} // namespace diff --git a/src/envshortcut.h b/src/envshortcut.h new file mode 100644 index 00000000..904b3ab7 --- /dev/null +++ b/src/envshortcut.h @@ -0,0 +1,114 @@ +#include + +class Executable; + +namespace env +{ + +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + +} // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp new file mode 100644 index 00000000..718cf2ce --- /dev/null +++ b/src/envwindows.cpp @@ -0,0 +1,236 @@ +#include "envwindows.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +WindowsInfo::WindowsInfo() +{ + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); +} + +bool WindowsInfo::compatibilityMode() const +{ + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode + return false; + } + + return (m_real != m_reported); +} + +const WindowsInfo::Version& WindowsInfo::reportedVersion() const +{ + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} + +QString WindowsInfo::toString() const +{ + QStringList sl; + + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); + + // version + sl.push_back("version " + reported); + + // real version if different + if (compatibilityMode()) { + sl.push_back("real version " + real); + } + + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); + } + + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); + } + + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); + } + + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); + } + + // elevated + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const +{ + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though + + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return {}; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + // this apparently never fails + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; +} + +WindowsInfo::Release WindowsInfo::getRelease() const +{ + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); + } + } + + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; +} + +std::optional WindowsInfo::getElevated() const +{ + HandlePtr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + + return {}; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + + return {}; + } + + return (e.TokenIsElevated != 0); +} + +} // namespace diff --git a/src/envwindows.h b/src/envwindows.h new file mode 100644 index 00000000..c23f99f4 --- /dev/null +++ b/src/envwindows.h @@ -0,0 +1,106 @@ +#include +#include + +namespace env +{ + +// a variety of information on windows +// +class WindowsInfo +{ +public: + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // + bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + Version m_reported, m_real; + Release m_release; + std::optional m_elevated; + + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; +}; + +} // namespace diff --git a/src/main.cpp b/src/main.cpp index 09da9408..5c5ce945 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,8 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "moshortcut.h" #include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6c2ab389..87ee2c8e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,6 +86,7 @@ along with Mod Organizer. If not, see . #include #include "localsavegames.h" #include "listdialog.h" +#include "envshortcut.h" #include #include diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4ee4b766..07983e12 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,35 +19,9 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" -#include "error_report.h" -#include "executableslist.h" -#include "instancemanager.h" -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#pragma comment(lib, "Wbemuuid.lib") - -using namespace MOBase; -namespace fs = std::filesystem; - -namespace MOShared { +namespace MOShared +{ bool FileExists(const std::string &filename) { @@ -269,2097 +243,4 @@ MOBase::VersionInfo createVersionInfo() } } - -namespace env -{ - -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - -struct LibraryFreer -{ - using pointer = HINSTANCE; - - void operator()(HINSTANCE h) - { - if (h != 0) { - ::FreeLibrary(h); - } - } -}; - -struct COMReleaser -{ - void operator()(IUnknown* p) - { - if (p) { - p->Release(); - } - } -}; - - -template -using COMPtr = std::unique_ptr; - - -class ShellLinkException -{ -public: - ShellLinkException(QString s) - : m_what(std::move(s)) - { - } - - const QString& what() const - { - return m_what; - } - -private: - QString m_what; -}; - -// just a wrapper around IShellLink operations that throws ShellLinkException -// on errors -// -class ShellLinkWrapper -{ -public: - ShellLinkWrapper() - { - m_link = createShellLink(); - m_file = createPersistFile(); - } - - void setPath(const QString& s) - { - if (s.isEmpty()) { - throw ShellLinkException("path cannot be empty"); - } - - const auto r = m_link->SetPath(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set target path '%1'").arg(s)); - } - - void setArguments(const QString& s) - { - const auto r = m_link->SetArguments(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); - } - - void setDescription(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetDescription(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set description '%1'").arg(s)); - } - - void setIcon(const QString& file, int i) - { - if (file.isEmpty()) { - return; - } - - const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); - throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); - } - - void setWorkingDirectory(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); - } - - void save(const QString& path) - { - const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); - throwOnFail(r, QString("failed to save link '%1'").arg(path)); - } - -private: - COMPtr m_link; - COMPtr m_file; - - void throwOnFail(HRESULT r, const QString& s) - { - if (FAILED(r)) { - throw ShellLinkException(QString("%1, %2") - .arg(s) - .arg(formatSystemMessageQ(r))); - } - } - - COMPtr createShellLink() - { - void* link = nullptr; - - const auto r = CoCreateInstance( - CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, &link); - - throwOnFail(r, "failed to create IShellLink instance"); - - if (!link) { - throw ShellLinkException("creating IShellLink worked, pointer is null"); - } - - return COMPtr(static_cast(link)); - } - - COMPtr createPersistFile() - { - void* file = nullptr; - - const auto r = m_link->QueryInterface(IID_IPersistFile, &file); - throwOnFail(r, "failed to get IPersistFile interface"); - - if (!file) { - throw ShellLinkException("querying IPersistFile worked, pointer is null"); - } - - return COMPtr(static_cast(file)); - } -}; - - -Console::Console() - : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) -{ - // open a console - if (!AllocConsole()) { - // failed, ignore - } - - m_hasConsole = true; - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); -} - -Console::~Console() -{ - // close redirected handles and redirect standard stream to NUL in case - // they're used after this - - if (m_err) { - std::fclose(m_err); - freopen_s(&m_err, "NUL", "w", stderr); - } - - if (m_out) { - std::fclose(m_out); - freopen_s(&m_out, "NUL", "w", stdout); - } - - if (m_in) { - std::fclose(m_in); - freopen_s(&m_in, "NUL", "r", stdin); - } - - // close console - if (m_hasConsole) { - FreeConsole(); - } -} - - -Shortcut::Shortcut() - : m_iconIndex(0) -{ -} - -Shortcut::Shortcut(const Executable& exe) - : Shortcut() -{ - m_name = exe.title(); - m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); - - m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) - .arg(exe.title()); - - m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); - - if (exe.usesOwnIcon()) { - m_icon = exe.binaryInfo().absoluteFilePath(); - } - - m_workingDirectory = qApp->applicationDirPath(); -} - -Shortcut& Shortcut::name(const QString& s) -{ - m_name = s; - return *this; -} - -Shortcut& Shortcut::target(const QString& s) -{ - m_target = s; - return *this; -} - -Shortcut& Shortcut::arguments(const QString& s) -{ - m_arguments = s; - return *this; -} - -Shortcut& Shortcut::description(const QString& s) -{ - m_description = s; - return *this; -} - -Shortcut& Shortcut::icon(const QString& s, int index) -{ - m_icon = s; - m_iconIndex = index; - return *this; -} - -Shortcut& Shortcut::workingDirectory(const QString& s) -{ - m_workingDirectory = s; - return *this; -} - -bool Shortcut::exists(Locations loc) const -{ - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - return QFileInfo(path).exists(); -} - -bool Shortcut::toggle(Locations loc) -{ - if (exists(loc)) { - return remove(loc); - } else { - return add(loc); - } -} - -bool Shortcut::add(Locations loc) -{ - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; - - if (m_target.isEmpty()) { - critical() << "target is empty"; - return false; - } - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "shorcut file will be saved at '" << path << "'"; - - try - { - ShellLinkWrapper link; - - link.setPath(m_target); - link.setArguments(m_arguments); - link.setDescription(m_description); - link.setIcon(m_icon, m_iconIndex); - link.setWorkingDirectory(m_workingDirectory); - - link.save(path); - - return true; - } - catch(ShellLinkException& e) - { - critical() << e.what() << "\nshortcut file was not saved"; - } - - return false; -} - -bool Shortcut::remove(Locations loc) -{ - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "path to shortcut file is '" << path << "'"; - - if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; - return false; - } - - if (!MOBase::shellDelete({path})) { - const auto e = ::GetLastError(); - - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); - - return false; - } - - return true; -} - -QString Shortcut::shortcutPath(Locations loc) const -{ - const auto dir = shortcutDirectory(loc); - if (dir.isEmpty()) { - return {}; - } - - const auto file = shortcutFilename(); - if (file.isEmpty()) { - return {}; - } - - return dir + QDir::separator() + file; -} - -QString Shortcut::shortcutDirectory(Locations loc) const -{ - QString dir; - - try - { - switch (loc) - { - case Desktop: - dir = MOBase::getDesktopDirectory(); - break; - - case StartMenu: - dir = MOBase::getStartMenuDirectory(); - break; - - case None: - default: - critical() << "bad location " << loc; - break; - } - } - catch(std::exception&) - { - } - - return QDir::toNativeSeparators(dir); -} - -QString Shortcut::shortcutFilename() const -{ - if (m_name.isEmpty()) { - critical() << "name is empty"; - return {}; - } - - return m_name + ".lnk"; -} - -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - - -QString toString(Shortcut::Locations loc) -{ - switch (loc) - { - case Shortcut::None: - return "none"; - - case Shortcut::Desktop: - return "desktop"; - - case Shortcut::StartMenu: - return "start menu"; - - default: - return QString("? (%1)").arg(static_cast(loc)); - } -} - - - -class WMI -{ -public: - class failed {}; - - WMI(const std::string& ns) - { - try - { - createLocator(); - createService(ns); - setSecurity(); - } - catch(failed&) - { - } - } - - template - void query(const std::string& q, F&& f) - { - if (!m_locator || !m_service) { - return; - } - - auto enumerator = getEnumerator(q); - if (!enumerator) { - return; - } - - for (;;) - { - COMPtr object; - - { - IWbemClassObject* rawObject = nullptr; - ULONG count = 0; - auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - - if (count == 0 || !rawObject) { - break; - } - - if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); - break; - } - - object.reset(rawObject); - } - - f(object.get()); - } - } - -private: - COMPtr m_locator; - COMPtr m_service; - - void createLocator() - { - void* rawLocator = nullptr; - - const auto ret = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, - IID_IWbemLocator, &rawLocator); - - if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); - - throw failed(); - } - - m_locator.reset(static_cast(rawLocator)); - } - - void createService(const std::string& ns) - { - IWbemServices* rawService = nullptr; - - const auto res = m_locator->ConnectServer( - _bstr_t(ns.c_str()), - nullptr, nullptr, nullptr, 0, nullptr, nullptr, - &rawService); - - if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); - - throw failed(); - } - - m_service.reset(rawService); - } - - void setSecurity() - { - auto ret = CoSetProxyBlanket( - m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, - RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); - - if (FAILED(ret)) - { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - - throw failed(); - } - } - - COMPtr getEnumerator( - const std::string& query) - { - IEnumWbemClassObject* rawEnumerator = NULL; - - auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret) || !rawEnumerator) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return COMPtr(rawEnumerator); - } -}; - - -class DisplayEnumerator -{ -public: - DisplayEnumerator() - : m_GetDpiForMonitor(nullptr) - { - m_shcore.reset(LoadLibraryW(L"Shcore.dll")); - - if (m_shcore) { - // windows 8.1+ only - m_GetDpiForMonitor = reinterpret_cast( - GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); - } - - // gets all monitors and the device they're running on - getDisplayDevices(); - } - - std::vector&& displays() && - { - return std::move(m_displays); - } - - const std::vector& displays() const & - { - return m_displays; - } - -private: - using GetDpiForMonitorFunction = - HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - std::unique_ptr m_shcore; - GetDpiForMonitorFunction* m_GetDpiForMonitor; - std::vector m_displays; - - void getDisplayDevices() - { - // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { - DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); - - if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { - // no more - break; - } - - // EnumDisplayDevices() seems to be returning a lot of devices that are - // not actually monitors, but those don't have the - // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set - if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { - continue; - } - - m_displays.push_back(createDisplay(device)); - } - } - - Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) - { - Metrics::Display d; - - d.adapter = QString::fromWCharArray(device.DeviceString); - d.monitor = QString::fromWCharArray(device.DeviceName); - d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); - - getDisplaySettings(device.DeviceName, d); - getDpi(d); - - return d; - } - - void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) - { - DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); - - if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { - log::error("EnumDisplaySettings() failed for '{}'", d.monitor); - return; - } - - // all these fields should be available - - if (dm.dmFields & DM_DISPLAYFREQUENCY) { - d.refreshRate = dm.dmDisplayFrequency; - } - - if (dm.dmFields & DM_PELSWIDTH) { - d.resX = dm.dmPelsWidth; - } - - if (dm.dmFields & DM_PELSHEIGHT) { - d.resY = dm.dmPelsHeight; - } - } - - void getDpi(Metrics::Display& d) - { - if (!m_GetDpiForMonitor) { - // this happens on windows 7, get the desktop dpi instead - getDesktopDpi(d); - return; - } - - // there's no way to get an HMONITOR from a device name, so all monitors - // will have to be enumerated and their name checked - HMONITOR hm = findMonitor(d.monitor); - if (!hm) { - log::error("can't get dpi for monitor '{}', not found", d.monitor); - return; - } - - UINT dpiX=0, dpiY=0; - const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - d.monitor, formatSystemMessageQ(r)); - - return; - } - - // dpiX and dpiY are always identical, as per the documentation - d.dpi = dpiX; - } - - void getDesktopDpi(Metrics::Display& d) - { - // desktop dc - HDC dc = GetDC(0); - - if (!dc) { - const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); - return; - } - - d.dpi = GetDeviceCaps(dc, LOGPIXELSX); - - ReleaseDC(0, dc); - } - - HMONITOR findMonitor(const QString& name) - { - // passed to the enumeration callback - struct Data - { - DisplayEnumerator* self; - QString name; - HMONITOR hm; - }; - - Data data = {this, name, 0}; - - // for each monitor - EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { - auto& data = *reinterpret_cast(lp); - - MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); - - // monitor info will include the name - if (!GetMonitorInfoW(hm, &mi)) { - const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); - - // error for this monitor, but continue - return TRUE; - } - - if (QString::fromWCharArray(mi.szDevice) == data.name) { - // found, stop - data.hm = hm; - return FALSE; - } - - // not found, continue to the next monitor - return TRUE; - }, reinterpret_cast(&data)); - - return data.hm; - } -}; - - -Environment::Environment() -{ - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); -} - -const std::vector& Environment::loadedModules() const -{ - return m_modules; -} - -const WindowsInfo& Environment::windowsInfo() const -{ - return m_windows; -} - -const std::vector& Environment::securityProducts() const -{ - return m_security; -} - -const Metrics& Environment::metrics() const -{ - return m_metrics; -} - -void Environment::dump() const -{ - log::debug("windows: {}", windowsInfo().toString()); - - if (windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : loadedModules()) { - log::debug(" . {}", m.toString()); - } - - log::debug("displays:"); - for (const auto& d : m_metrics.displays()) { - log::debug(" . {}", d.toString()); - } -} - -std::vector Environment::getLoadedModules() const -{ - HandlePtr snapshot(CreateToolhelp32Snapshot( - TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); - - if (snapshot.get() == INVALID_HANDLE_VALUE) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - - return {}; - } - - MODULEENTRY32 me = {}; - me.dwSize = sizeof(me); - - // first module, this shouldn't fail because there's at least the executable - if (!Module32First(snapshot.get(), &me)) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - - return {}; - } - - std::vector v; - - for (;;) - { - const auto path = QString::fromWCharArray(me.szExePath); - if (!path.isEmpty()) { - v.push_back(Module(path, me.modBaseSize)); - } - - // next module - if (!Module32Next(snapshot.get(), &me)) { - const auto e = GetLastError(); - - // no more modules is not an error - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); - } - - break; - } - } - - // sorting by display name - std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { - return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); - }); - - return v; -} - -std::vector Environment::getSecurityProducts() const -{ - std::vector v; - - { - auto fromWMI = getSecurityProductsFromWMI(); - v.insert( - v.end(), - std::make_move_iterator(fromWMI.begin()), - std::make_move_iterator(fromWMI.end())); - } - - if (auto p=getWindowsFirewall()) { - v.push_back(std::move(*p)); - } - - return v; -} - -std::vector Environment::getSecurityProductsFromWMI() const -{ - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map map; - - auto handleProduct = [&](auto* o) { - VARIANT prop; - - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; - return; - } - - const std::wstring name = prop.bstrVal; - VariantClear(&prop); - - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; - return; - } - - DWORD state = 0; - if (prop.vt == VT_I4) { - state = prop.lVal; - } else { - state = prop.ulVal; - } - - VariantClear(&prop); - - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; - return; - } - - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); - - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; - - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); - - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); - }; - - { - WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - { - WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - std::vector v; - - for (auto&& p : map) { - v.push_back(p.second); - } - - return v; -} - -std::optional Environment::getWindowsFirewall() const -{ - HRESULT hr = 0; - - COMPtr policy; - - { - void* rawPolicy = nullptr; - - hr = CoCreateInstance( - __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, - __uuidof(INetFwPolicy2), &rawPolicy); - - if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); - - return {}; - } - - policy.reset(static_cast(rawPolicy)); - } - - VARIANT_BOOL enabledVariant; - - if (policy) { - hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); - if (FAILED(hr)) - { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - - return {}; - } - } - - const auto enabled = (enabledVariant != VARIANT_FALSE); - if (!enabled) { - return {}; - } - - return SecurityProduct( - {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); -} - - -Metrics::Metrics() -{ - m_displays = DisplayEnumerator().displays(); -} - -const std::vector& Metrics::displays() const -{ - return m_displays; -} - -QString Metrics::Display::toString() const -{ - return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(resX) - .arg(resY) - .arg(refreshRate) - .arg(dpi) - .arg(adapter) - .arg(primary ? " (primary)" : ""); -} - - -Module::Module(QString path, std::size_t fileSize) - : m_path(std::move(path)), m_fileSize(fileSize) -{ - const auto fi = getFileInfo(); - - m_version = getVersion(fi.ffi); - m_timestamp = getTimestamp(fi.ffi); - m_versionString = fi.fileDescription; - m_md5 = getMD5(); -} - -const QString& Module::path() const -{ - return m_path; -} - -QString Module::displayPath() const -{ - return QDir::fromNativeSeparators(m_path.toLower()); -} - -std::size_t Module::fileSize() const -{ - return m_fileSize; -} - -const QString& Module::version() const -{ - return m_version; -} - -const QString& Module::versionString() const -{ - return m_versionString; -} - -const QDateTime& Module::timestamp() const -{ - return m_timestamp; -} - -const QString& Module::md5() const -{ - return m_md5; -} - -QString Module::timestampString() const -{ - if (!m_timestamp.isValid()) { - return "(no timestamp)"; - } - - return m_timestamp.toString(Qt::DateFormat::ISODate); -} - -QString Module::toString() const -{ - QStringList sl; - - // file size - sl.push_back(displayPath()); - sl.push_back(QString("%1 B").arg(m_fileSize)); - - // version - if (m_version.isEmpty() && m_versionString.isEmpty()) { - sl.push_back("(no version)"); - } else { - if (!m_version.isEmpty()) { - sl.push_back(m_version); - } - - if (!m_versionString.isEmpty() && m_versionString != m_version) { - sl.push_back(versionString()); - } - } - - // timestamp - if (m_timestamp.isValid()) { - sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); - } else { - sl.push_back("(no timestamp)"); - } - - // md5 - if (!m_md5.isEmpty()) { - sl.push_back(m_md5); - } - - return sl.join(", "); -} - -Module::FileInfo Module::getFileInfo() const -{ - const auto wspath = m_path.toStdWString(); - - // getting version info size - DWORD dummy = 0; - const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); - - if (size == 0) { - const auto e = GetLastError(); - - if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { - // not an error, no version information built into that module - return {}; - } - - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // getting version info - auto buffer = std::make_unique(size); - - if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // the version info has two major parts: a fixed version and a localizable - // set of strings - - FileInfo fi; - fi.ffi = getFixedFileInfo(buffer.get()); - fi.fileDescription = getFileDescription(buffer.get()); - - return fi; -} - -VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const -{ - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // the fixed version info is in the root - const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no fixed file info - return {}; - } - - const auto* fi = static_cast(valuePointer); - - // signature is always 0xfeef04bd - if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; - - return {}; - } - - return *fi; -} - -QString Module::getFileDescription(std::byte* buffer) const -{ - struct LANGANDCODEPAGE - { - WORD wLanguage; - WORD wCodePage; - }; - - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // getting list of available languages - auto ret = VerQueryValueW( - buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - - return {}; - } - - // number of languages - const auto count = valueSize / sizeof(LANGANDCODEPAGE); - if (count == 0) { - return {}; - } - - // using the first language in the list to get FileVersion - const auto* lcp = static_cast(valuePointer); - - const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") - .arg(lcp->wLanguage, 4, 16, QChar('0')) - .arg(lcp->wCodePage, 4, 16, QChar('0')); - - ret = VerQueryValueW( - buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no file version - return {}; - } - - // valueSize includes the null terminator - return QString::fromWCharArray( - static_cast(valuePointer), valueSize - 1); -} - -QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const -{ - if (fi.dwSignature == 0) { - return {}; - } - - const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; - const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; - const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; - const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; - - if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { - return {}; - } - - return QString("%1.%2.%3.%4") - .arg(major).arg(minor).arg(maintenance).arg(build); -} - -QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const -{ - FILETIME ft = {}; - - if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { - // if the file info is invalid or doesn't have a date, use the creation - // time on the file - - // opening the file - HandlePtr h(CreateFileW( - m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); - - if (h.get() == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); - - return {}; - } - - // getting the file time - if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { - const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - } else { - // use the time from the file info - ft.dwHighDateTime = fi.dwFileDateMS; - ft.dwLowDateTime = fi.dwFileDateLS; - } - - - // converting to SYSTEMTIME - SYSTEMTIME utc = {}; - - if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; - - return {}; - } - - return QDateTime( - QDate(utc.wYear, utc.wMonth, utc.wDay), - QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); -} - -QString Module::getMD5() const -{ - if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { - // don't calculate md5 for system files, it's not really relevant and - // it takes a while - return {}; - } - - // opening the file - QFile f(m_path); - - if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - - return {}; - } - - // hashing - QCryptographicHash hash(QCryptographicHash::Md5); - if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - - return {}; - } - - return hash.result().toHex(); -} - - -WindowsInfo::WindowsInfo() -{ - // loading ntdll.dll, the functions will be found with GetProcAddress() - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } else { - m_reported = getReportedVersion(ntdll.get()); - m_real = getRealVersion(ntdll.get()); - } - - m_release = getRelease(); - m_elevated = getElevated(); -} - -bool WindowsInfo::compatibilityMode() const -{ - if (m_real == Version()) { - // don't know the real version, can't guess compatibility mode - return false; - } - - return (m_real != m_reported); -} - -const WindowsInfo::Version& WindowsInfo::reportedVersion() const -{ - return m_reported; -} - -const WindowsInfo::Version& WindowsInfo::realVersion() const -{ - return m_real; -} - -const WindowsInfo::Release& WindowsInfo::release() const -{ - return m_release; -} - -std::optional WindowsInfo::isElevated() const -{ - return m_elevated; -} - -QString WindowsInfo::toString() const -{ - QStringList sl; - - const QString reported = m_reported.toString(); - const QString real = m_real.toString(); - - // version - sl.push_back("version " + reported); - - // real version if different - if (compatibilityMode()) { - sl.push_back("real version " + real); - } - - // build.UBR, such as 17763.557 - if (m_release.UBR != 0) { - DWORD build = 0; - - if (compatibilityMode()) { - build = m_real.build; - } else { - build = m_reported.build; - } - - sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); - } - - // release ID - if (!m_release.ID.isEmpty()) { - sl.push_back("release " + m_release.ID); - } - - // buildlab string - if (!m_release.buildLab.isEmpty()) { - sl.push_back(m_release.buildLab); - } - - // product name - if (!m_release.productName.isEmpty()) { - sl.push_back(m_release.productName); - } - - // elevated - QString elevated = "?"; - if (m_elevated.has_value()) { - elevated = (*m_elevated ? "yes" : "no"); - } - - sl.push_back("elevated: " + elevated); - - return sl.join(", "); -} - -WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const -{ - // windows has been deprecating pretty much all the functions having to do - // with getting version information because apparently, people keep misusing - // them for feature detection - // - // there's still RtlGetVersion() though - - using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); - - auto* RtlGetVersion = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); - - if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return {}; - } - - OSVERSIONINFOEX vi = {}; - vi.dwOSVersionInfoSize = sizeof(vi); - - // this apparently never fails - RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - - return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; -} - -WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const -{ - // getting the actual windows version is more difficult because all the - // functions are lying when running in compatibility mode - // - // RtlGetNtVersionNumbers() is an undocumented function that seems to work - // fine, but it might not in the future - - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); - - if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; - return {}; - } - - DWORD major=0, minor=0, build=0; - RtlGetNtVersionNumbers(&major, &minor, &build); - - // for whatever reason, the build number has 0xf0000000 set - build = 0x0fffffff & build; - - return {major, minor, build}; -} - -WindowsInfo::Release WindowsInfo::getRelease() const -{ - // there are several interesting items in the registry, but most of them - // are undocumented, not always available, and localizable - // - // most of them are used to provide as much information as possible in case - // any of the other versions fail to work - - QSettings settings( - R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", - QSettings::NativeFormat); - - Release r; - - // buildlab seems to be an internal name from the build system - r.buildLab = settings.value("BuildLabEx", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildLab", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildBranch", "").toString(); - } - } - - // localized name of windows, such as "Windows 10 Pro" - r.productName = settings.value("ProductName", "").toString(); - - // release ID, such as 1803 - r.ID = settings.value("ReleaseId", "").toString(); - - // some other build number, shown in winver.exe - r.UBR = settings.value("UBR", 0).toUInt(); - - return r; -} - -std::optional WindowsInfo::getElevated() const -{ - HandlePtr token; - - { - HANDLE rawToken = 0; - - if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); - - return {}; - } - - token.reset(rawToken); - } - - TOKEN_ELEVATION e = {}; - DWORD size = sizeof(TOKEN_ELEVATION); - - if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); - - return {}; - } - - return (e.TokenIsElevated != 0); -} - - -SecurityProduct::SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate) : - m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), - m_active(active), m_upToDate(upToDate) -{ -} - -const QString& SecurityProduct::name() const -{ - return m_name; -} - -int SecurityProduct::provider() const -{ - return m_provider; -} - -bool SecurityProduct::active() const -{ - return m_active; -} - -bool SecurityProduct::upToDate() const -{ - return m_upToDate; -} - -QString SecurityProduct::toString() const -{ - QString s; - - s += m_name + " (" + providerToString() + ")"; - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; - } - - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - - return s; -} - -QString SecurityProduct::providerToString() const -{ - QStringList ps; - - if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { - ps.push_back("firewall"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { - ps.push_back("autoupdate"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { - ps.push_back("antivirus"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { - ps.push_back("antispyware"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { - ps.push_back("settings"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { - ps.push_back("uac"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { - ps.push_back("service"); - } - - if (ps.empty()) { - return "doesn't provider anything"; - } - - return ps.join("|"); -} - - -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - -// returns the filename of the given process or the current one -// -std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) -{ - // double the buffer size 10 times - const int MaxTries = 10; - - DWORD bufferSize = MAX_PATH; - - for (int tries=0; tries(bufferSize + 1); - std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); - - DWORD writtenSize = 0; - - if (process == INVALID_HANDLE_VALUE) { - // query this process - writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); - } else { - // query another process - writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); - } - - if (writtenSize == 0) { - // hard failure - const auto e = GetLastError(); - std::wcerr << formatSystemMessage(e) << L"\n"; - break; - } else if (writtenSize >= bufferSize) { - // buffer is too small, try again - bufferSize *= 2; - } else { - // if GetModuleFileName() works, `writtenSize` does not include the null - // terminator - const std::wstring s(buffer.get(), writtenSize); - const fs::path path(s); - - return path.filename().native(); - } - } - - // something failed or the path is way too long to make sense - - std::wstring what; - if (process == INVALID_HANDLE_VALUE) { - what = L"the current process"; - } else { - what = L"pid " + std::to_wstring(reinterpret_cast(process)); - } - - std::wcerr << L"failed to get filename for " << what << L"\n"; - return {}; -} - -std::vector runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - -DWORD findOtherPid() -{ - const std::wstring defaultName = L"ModOrganizer.exe"; - - std::wclog << L"looking for the other process...\n"; - - // used to skip the current process below - const auto thisPid = GetCurrentProcessId(); - std::wclog << L"this process id is " << thisPid << L"\n"; - - // getting the filename for this process, assumes the other process has the - // smae one - auto filename = processFilename(); - if (filename.empty()) { - std::wcerr - << L"can't get current process filename, defaulting to " - << defaultName << L"\n"; - - filename = defaultName; - } else { - std::wclog << L"this process filename is " << filename << L"\n"; - } - - // getting all running processes - const auto processes = runningProcesses(); - std::wclog << L"there are " << processes.size() << L" processes running\n"; - - // going through processes, trying to find one with the same name and a - // different pid than this process has - for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; - } - } - } - - std::wclog - << L"no process with this filename\n" - << L"MO may not be running, or it may be running as administrator\n" - << L"you can try running this again as administrator\n"; - - return 0; -} - -std::wstring tempDir() -{ - const DWORD bufferSize = MAX_PATH + 1; - wchar_t buffer[bufferSize + 1] = {}; - - const auto written = GetTempPathW(bufferSize, buffer); - if (written == 0) { - const auto e = GetLastError(); - - std::wcerr - << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; - - return {}; - } - - // `written` does not include the null terminator - return std::wstring(buffer, buffer + written); -} - -HandlePtr tempFile(const std::wstring dir) -{ - // maximum tries of incrementing the counter - const int MaxTries = 100; - - // UTC time and date will be in the filename - const auto now = std::time(0); - const auto tm = std::gmtime(&now); - - // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where - // i can go until MaxTries - std::wostringstream oss; - oss - << L"ModOrganizer-" - << std::setw(4) << (1900 + tm->tm_year) - << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) - << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" - << std::setw(2) << std::setfill(L'0') << tm->tm_hour - << std::setw(2) << std::setfill(L'0') << tm->tm_min - << std::setw(2) << std::setfill(L'0') << tm->tm_sec; - - const std::wstring prefix = oss.str(); - const std::wstring ext = L".dmp"; - - // first path to try, without counter in it - std::wstring path = dir + L"\\" + prefix + ext; - - for (int i=0; i. #ifndef UTIL_H #define UTIL_H - #include -#include - -#define WIN32_LEAN_AND_MEAN -#include - #include -#include class Executable; @@ -51,442 +44,6 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); - -namespace env -{ - -class Console -{ -public: - Console(); - ~Console(); - -private: - bool m_hasConsole; - FILE* m_in; - FILE* m_out; - FILE* m_err; -}; - - -// an application shortcut that can be either on the desktop or the start menu -// -class Shortcut -{ -public: - // location of a shortcut - // - enum Locations - { - None = 0, - - // on the desktop - Desktop, - - // in the start menu - StartMenu - }; - - - // empty shortcut - // - Shortcut(); - - // shortcut from an executable - // - explicit Shortcut(const Executable& exe); - - // sets the name of the shortcut, shown on icons and start menu entries - // - Shortcut& name(const QString& s); - - // the program to start - // - Shortcut& target(const QString& s); - - // arguments to pass - // - Shortcut& arguments(const QString& s); - - // shows in the status bar of explorer, for example - // - Shortcut& description(const QString& s); - - // path to a binary that contains the icon and its index - // - Shortcut& icon(const QString& s, int index=0); - - // "start in" option for this shortcut - // - Shortcut& workingDirectory(const QString& s); - - - // returns whether this shortcut already exists at the given location; this - // does not check whether the shortcut parameters are different, it merely if - // the .lnk file exists - // - bool exists(Locations loc) const; - - // calls remove() if exists(), or add() - // - bool toggle(Locations loc); - - // adds the shortcut to the given location - // - bool add(Locations loc); - - // removes the shortcut from the given location - // - bool remove(Locations loc); - -private: - QString m_name; - QString m_target; - QString m_arguments; - QString m_description; - QString m_icon; - int m_iconIndex; - QString m_workingDirectory; - - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - - // returns the path where the shortcut file should be saved - // - QString shortcutPath(Locations loc) const; - - // returns the directory where the shortcut file should be saved - // - QString shortcutDirectory(Locations loc) const; - - // returns the filename of the shortcut file that should be used when saving - // - QString shortcutFilename() const; -}; - - -// returns a string representation of the given location -// -QString toString(Shortcut::Locations loc); - - -// represents one module -// -class Module -{ -public: - explicit Module(QString path, std::size_t fileSize); - - // returns the module's path - // - const QString& path() const; - - // returns the module's path in lowercase and using forward slashes - // - QString displayPath() const; - - // returns the size in bytes, may be 0 - // - std::size_t fileSize() const; - - // returns the x.x.x.x version embedded from the version info, may be empty - // - const QString& version() const; - - // returns the FileVersion entry from the resource file, returns - // "(no version)" if not available - // - const QString& versionString() const; - - // returns the build date from the version info, or the creation time of the - // file on the filesystem, may be empty - // - const QDateTime& timestamp() const; - - // returns the md5 of the file, may be empty for system files - // - const QString& md5() const; - - // converts timestamp() to a string for display, returns "(no timestamp)" if - // not available - // - QString timestampString() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - // contains the information from the version resource - // - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; - - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - - // returns information from the version resource - // - FileInfo getFileInfo() const; - - // uses VS_FIXEDFILEINFO to build the version string - // - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - - // uses the file date from VS_FIXEDFILEINFO if available, or gets the - // creation date on the file - // - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - - // returns the md5 hash unless the path contains "\windows\" - // - QString getMD5() const; - - // gets VS_FIXEDFILEINFO from the file version info buffer - // - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - - // gets FileVersion from the file version info buffer - // - QString getFileDescription(std::byte* buffer) const; -}; - - -// a variety of information on windows -// -class WindowsInfo -{ -public: - struct Version - { - DWORD major=0, minor=0, build=0; - - QString toString() const - { - return QString("%1.%2.%3").arg(major).arg(minor).arg(build); - } - - friend bool operator==(const Version& a, const Version& b) - { - return - a.major == b.major && - a.minor == b.minor && - a.build == b.build; - } - - friend bool operator!=(const Version& a, const Version& b) - { - return !(a == b); - } - }; - - struct Release - { - // the BuildLab entry from the registry, may be empty - QString buildLab; - - // product name such as "Windows 10 Pro", may not be in English, may be - // empty - QString productName; - - // release ID such as 1809, may be mepty - QString ID; - - // some sub-build number, undocumented, may be empty - DWORD UBR; - - Release() - : UBR(0) - { - } - }; - - - WindowsInfo(); - - // tries to guess whether this process is running in compatibility mode - // - bool compatibilityMode() const; - - // returns the Windows version, may not correspond to the actual version - // if the process is running in compatibility mode - // - const Version& reportedVersion() const; - - // tries to guess the real Windows version that's running, can be empty - // - const Version& realVersion() const; - - // various information about the current release - // - const Release& release() const; - - // whether this process is running as administrator, may be empty if the - // information is not available - std::optional isElevated() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - Version m_reported, m_real; - Release m_release; - std::optional m_elevated; - - // uses RtlGetVersion() to get the version number as reported by Windows - // - Version getReportedVersion(HINSTANCE ntdll) const; - - // uses RtlGetNtVersionNumbers() to get the real version number - // - Version getRealVersion(HINSTANCE ntdll) const; - - // gets various information from the registry - // - Release getRelease() const; - - // gets whether the process is elevated - // - std::optional getElevated() const; -}; - - -// represents a security product, such as an antivirus or a firewall -// -class SecurityProduct -{ -public: - SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate); - - // display name of the product - // - const QString& name() const; - - // a bunch of _WSC_SECURITY_PROVIDER flags - // - int provider() const; - - // whether the product is active - // - bool active() const; - - // whether its definitions are up-to-date - // - bool upToDate() const; - - // string representation of the above - // - QString toString() const; - -private: - QUuid m_guid; - QString m_name; - int m_provider; - bool m_active; - bool m_upToDate; - - QString providerToString() const; -}; - - -class Metrics -{ -public: - struct Display - { - int resX=0, resY=0, dpi=0; - bool primary=false; - int refreshRate = 0; - QString monitor, adapter; - - QString toString() const; - }; - - Metrics(); - - const std::vector& displays() const; - -private: - std::vector m_displays; -}; - - -// represents the process's environment -// -class Environment -{ -public: - Environment(); - - // list of loaded modules in the current process - // - const std::vector& loadedModules() const; - - // information about the operating system - // - const WindowsInfo& windowsInfo() const; - - // information about the installed security products - // - const std::vector& securityProducts() const; - - // information about displays - // - const Metrics& metrics() const; - - // logs the environment - // - void dump() const; - -private: - std::vector m_modules; - WindowsInfo m_windows; - std::vector m_security; - Metrics m_metrics; - - std::vector getLoadedModules() const; - std::vector getSecurityProducts() const; - - std::vector getSecurityProductsFromWMI() const; - std::optional getWindowsFirewall() const; -}; - - -enum class CoreDumpTypes -{ - Mini = 1, - Data, - Full -}; - -// creates a minidump file for the given process -// -bool coredump(CoreDumpTypes type); - -// finds another process with the same name as this one and creates a minidump -// file for it -// -bool coredumpOther(CoreDumpTypes type); - -} // namespace env - - MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From f13f5e21c42b3b5bdfdc4fcab50e10abd92c8486 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 00:11:43 -0400 Subject: replaced qInfo() with log::info() --- src/mainwindow.cpp | 2 +- src/modinfo.cpp | 10 +++++----- src/modinfodialognexus.cpp | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 87ee2c8e..9dbada1c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5907,7 +5907,7 @@ void MainWindow::finishUpdateInfo() auto finalMods = watcher->result(); if (finalMods.empty()) { - qInfo("None of your mods appear to have had recent file updates."); + log::info("None of your mods appear to have had recent file updates."); } std::set> organizedGames; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 3484b644..92c7366c 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -37,6 +37,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -322,10 +323,9 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); updatesAvailable = false; } else { - qInfo() << tr( + log::info("{}", tr( "You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. " - "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods." - ); + "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods.")); } for (auto game : organizedGames) @@ -412,7 +412,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei }); if (mods.size()) { - qInfo("Checking updates for %d mods...", mods.size()); + log::info("Checking updates for {} mods...", mods.size()); for (auto mod : mods) { organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); @@ -422,7 +422,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } } else { - qInfo("None of the selected mods can be updated."); + log::info("None of the selected mods can be updated."); } } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index e606525c..6d28cbe3 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -6,8 +6,9 @@ #include "bbcode.h" #include #include +#include -namespace shell = MOBase::shell; +using namespace MOBase; bool isValidModID(int id) { @@ -350,7 +351,7 @@ void NexusTab::onRefreshBrowser() mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { - qInfo("Mod has no valid Nexus ID, info can't be updated."); + log::info("Mod has no valid Nexus ID, info can't be updated."); } } -- cgit v1.3.1 From aae6d6a5aa8d6b101fcc38388222a8a6e7ee2ec6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 01:09:19 -0400 Subject: replaced qWarning() --- src/bbcode.cpp | 9 ++++---- src/categories.cpp | 3 ++- src/directoryrefresher.cpp | 4 ++-- src/downloadmanager.cpp | 21 +++++++++--------- src/editexecutablesdialog.cpp | 19 ++++++++-------- src/executableslist.cpp | 6 +++--- src/filerenamer.cpp | 7 ++++-- src/icondelegate.cpp | 4 +++- src/installationmanager.cpp | 2 +- src/instancemanager.cpp | 14 +++++++----- src/mainwindow.cpp | 50 +++++++++++++++++++++++-------------------- src/moapplication.cpp | 5 +++-- src/modflagicondelegate.cpp | 4 +++- src/modinfo.cpp | 2 +- src/modinfodialogesps.cpp | 7 +++--- src/modlist.cpp | 7 +++--- src/modlistsortproxy.cpp | 6 ++++-- src/nexusinterface.cpp | 27 +++++++++++++---------- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 46 +++++++++++++++++++-------------------- src/plugincontainer.cpp | 11 +++++----- src/pluginlist.cpp | 4 ++-- src/problemsdialog.cpp | 2 +- src/profile.cpp | 21 ++++++++++-------- src/profilesdialog.cpp | 4 ++-- src/settings.cpp | 5 +++-- 26 files changed, 161 insertions(+), 131 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 323dd128..d9b7debd 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -18,13 +18,13 @@ along with Mod Organizer. If not, see . */ #include "bbcode.h" - +#include #include #include - namespace BBCode { +namespace log = MOBase::log; class BBCodeMap { @@ -88,7 +88,7 @@ public: return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); } } else { - qWarning("don't know how to deal with tag %s", qUtf8Printable(tagName)); + log::warn("don't know how to deal with tag {}", tagName); } } else { if (tagName == "*") { @@ -99,8 +99,7 @@ public: } else { // expression doesn't match. either the input string is invalid // or the expression is - qWarning("%s doesn't match the expression for %s", - qUtf8Printable(temp), qUtf8Printable(tagName)); + log::warn("{} doesn't match the expression for {}", temp, tagName); length = 0; return QString(); } diff --git a/src/categories.cpp b/src/categories.cpp index 9e5fa9f7..8f9d3ad8 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include #include +#include #include #include @@ -294,7 +295,7 @@ bool CategoryFactory::isDecendantOf(int id, int parentID) const return isDecendantOf(m_Categories[index].m_ParentID, parentID); } } else { - qWarning("%d is no valid category id", id); + log::warn("{} is no valid category id", id); return false; } } diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 3ce4691b..87305599 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -127,7 +127,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); for (const QString &filename : stealFiles) { if (filename.isEmpty()) { - qWarning("Trying to find file with no name"); + log::warn("Trying to find file with no name"); continue; } QFileInfo fileInfo(filename); @@ -143,7 +143,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu QString warnStr = fileInfo.absolutePath(); if (warnStr.isEmpty()) warnStr = filename; - qWarning("file not found: %s", qUtf8Printable(warnStr)); + log::warn("file not found: {}", warnStr); } } } else { diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 648b102a..e3ceb261 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -924,7 +924,7 @@ void DownloadManager::queryInfo(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -976,7 +976,7 @@ void DownloadManager::queryInfoMd5(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -1016,7 +1016,7 @@ void DownloadManager::visitOnNexus(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("Visiting mod page is currently only possible with Nexus"); + log::warn("Visiting mod page is currently only possible with Nexus"); return; } @@ -1614,8 +1614,9 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian } } else { if (info->m_FileInfo->fileID == 0) { - qWarning("could not determine file id for %s (state %d)", - qUtf8Printable(info->m_FileName), info->m_State); + log::warn( + "could not determine file id for {} (state {})", + info->m_FileName, info->m_State); } } @@ -1999,7 +2000,7 @@ void DownloadManager::downloadFinished(int index) resumeDownloadInt(index); } } else { - qWarning("no download index %d", index); + log::warn("no download index {}", index); } } @@ -2008,9 +2009,9 @@ void DownloadManager::downloadError(QNetworkReply::NetworkError error) { if (error != QNetworkReply::OperationCanceledError) { QNetworkReply *reply = qobject_cast(sender()); - qWarning("%s (%d)", reply != nullptr ? qUtf8Printable(reply->errorString()) - : "Download error occured", - error); + log::warn("{} ({})", + reply != nullptr ? reply->errorString() : "Download error occured", + error); } } @@ -2033,7 +2034,7 @@ void DownloadManager::metaDataChanged() } } } else { - qWarning("meta data event for unknown download"); + log::warn("meta data event for unknown download"); } } diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8929d207..3ec3d64f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -101,9 +101,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const auto itor = m_executablesList.find(title); if (itor == m_executablesList.end()) { - qWarning().nospace() - << "getExecutablesList(): executable '" << title << "' not found"; - + log::warn("getExecutablesList(): executable '{}' not found", title); continue; } @@ -293,9 +291,10 @@ void EditExecutablesDialog::setEdits(const Executable& e) modIndex = ui->mods->findText(modName->value); if (modIndex == -1) { - qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << modName->value << "' " - << "as a custom overwrite, but that mod doesn't exist"; + log::warn( + "executable '{}' uses mod '{}' as a custom overwrite, but that mod " + "doesn't exist", + e.title(), modName->value); } } @@ -335,7 +334,7 @@ void EditExecutablesDialog::save() auto* e = selectedExe(); if (!e) { - qWarning("trying to save but nothing is selected"); + log::warn("trying to save but nothing is selected"); return; } @@ -475,13 +474,13 @@ void EditExecutablesDialog::on_remove_clicked() { auto* item = selectedItem(); if (!item) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } auto* exe = selectedExe(); if (!exe) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } @@ -646,7 +645,7 @@ void EditExecutablesDialog::on_configureLibraries_clicked() { auto* e = selectedExe(); if (!e) { - qWarning("trying to configure libraries but nothing is selected"); + log::warn("trying to configure libraries but nothing is selected"); return; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 0ca880cd..fbb96bd4 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -250,9 +250,9 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) return; } - qWarning().nospace() - << "executable '" << itor->title() << "' was in the way and was " - << "renamed to '" << *newTitle << "'"; + log::warn( + "executable '{}' was in the way and was renamed to '{}'", + itor->title(), *newTitle); itor->title(*newTitle); itor = end(); diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index c5c6782b..b516c902 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,7 +1,10 @@ #include "filerenamer.h" +#include #include #include +using namespace MOBase; + FileRenamer::FileRenamer(QWidget* parent, QFlags flags) : m_parent(parent), m_flags(flags) { @@ -34,7 +37,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt qDebug().nospace() << "removing " << newName; // user wants to replace the file, so remove it if (!QFile(newName).remove()) { - qWarning().nospace() << "failed to remove " << newName; + log::warn("failed to remove '{}'", newName); // removal failed, warn the user and allow canceling if (!removeFailed(newName)) { qDebug().nospace() << "canceling " << oldName; @@ -62,7 +65,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt // target either didn't exist or was removed correctly if (!QFile::rename(oldName, newName)) { - qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + log::warn("failed to rename '{}' to '{}'", oldName, newName); // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName)) { diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 249dae6f..39038f3c 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -18,12 +18,14 @@ along with Mod Organizer. If not, see . */ #include "icondelegate.h" +#include #include #include #include #include #include +using namespace MOBase; IconDelegate::IconDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -54,7 +56,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, if (!QPixmapCache::find(fullIconId, &icon)) { icon = QIcon(iconId).pixmap(iconWidth, iconWidth); if (icon.isNull()) { - qWarning("failed to load icon %s", qUtf8Printable(iconId)); + log::warn("failed to load icon {}", iconId); } QPixmapCache::insert(fullIconId, icon); } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index e443a8f2..0e50de52 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -693,7 +693,7 @@ void InstallationManager::postInstallCleanup() QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther); } if (!QFile::remove(fileInfo.absoluteFilePath())) { - qWarning() << "Unable to delete " << fileInfo.absoluteFilePath(); + log::warn("Unable to delete {}", fileInfo.absoluteFilePath()); } } directoriesToRemove.insert(fileInfo.absolutePath()); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index ddc2d067..55ef3fc8 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "selectiondialog.h" #include +#include #include #include #include @@ -29,13 +30,13 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; static const char COMPANY_NAME[] = "Tannin"; static const char APPLICATION_NAME[] = "Mod Organizer"; static const char INSTANCE_KEY[] = "CurrentInstance"; - InstanceManager::InstanceManager() : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) { @@ -86,10 +87,13 @@ bool InstanceManager::deleteLocalInstance(const QString &instanceId) const if (!MOBase::shellDelete(QStringList(instancePath),true)) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(instancePath), ::GetLastError()); + log::warn( + "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", + instancePath, ::GetLastError()); + if (!MOBase::removeDir(instancePath)) { - qWarning("regular delete failed too"); + log::warn("regular delete failed too"); result = false; } } @@ -153,7 +157,7 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons dialogText = dialog.textValue(); instanceId = sanitizeInstanceName(dialogText); if (instanceId != dialogText) { - if (QMessageBox::question( nullptr, + if (QMessageBox::question( nullptr, QObject::tr("Invalid instance name"), QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { @@ -323,7 +327,7 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const // Don't end in spaces and periods new_name = new_name.remove(QRegExp("\\.*$")); new_name = new_name.remove(QRegExp(" *$")); - + // Recurse until stuff stops changing if (new_name != name) { return sanitizeInstanceName(new_name); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9dbada1c..70ace8f1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -800,7 +800,7 @@ void MainWindow::setupToolbar() ui->toolBar->insertWidget(m_linksSeparator, spacer); } else { - qWarning("no separator found on the toolbar, icons won't be right-aligned"); + log::warn("no separator found on the toolbar, icons won't be right-aligned"); } } @@ -1646,9 +1646,7 @@ void MainWindow::startExeAction() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "startExeAction(): executable '" << title << "' not found"; - + log::warn("startExeAction(): executable '{}' not found", title); return; } @@ -1874,17 +1872,18 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { // read the data we need from the sub-item, then dispose of it QTreeWidgetItem *onDemandDataItem = item->child(0); - std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); + const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); + std::wstring wspath = path.toStdWString(); bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); + std::wstring virtualPath = (wspath + L"\\").substr(6) + ToWString(item->text(0)); DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); if (dir != nullptr) { QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - updateTo(item, path, *dir, conflictsOnly, &fileIcon, &folderIcon); + updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); } else { - qWarning("failed to update view of %ls", path.c_str()); + log::warn("failed to update view of {}", path); } m_RemoveWidget.push_back(item); QTimer::singleShot(5, this, SLOT(delayedRemove())); @@ -2380,10 +2379,17 @@ void MainWindow::processUpdates() { if (currentVersion > lastVersion) { //NOP - } else if (currentVersion < lastVersion) - qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). " - "The GUI may not downgrade gracefully, so you may experience oddities. " - "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString(); + } else if (currentVersion < lastVersion) { + const auto text = tr( + "Notice: Your current MO version (%1) is lower than the previously used one (%2). " + "The GUI may not downgrade gracefully, so you may experience oddities. " + "However, there should be no serious issues.") + .arg(currentVersion.toString()) + .arg(lastVersion.toString()); + + log::warn("{}", text); + } + //save version in all case settings.setValue("version", currentVersion.toString()); } @@ -2924,7 +2930,7 @@ void MainWindow::refreshFilters() while (currentID != 0) { categoriesUsed.insert(currentID); if (!cycleTest.insert(currentID).second) { - qWarning("cycle in categories: %s", qUtf8Printable(SetJoin(cycleTest, ", "))); + log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); break; } currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); @@ -4008,7 +4014,7 @@ void MainWindow::moveOverwriteContentToExistingMod() } if (modAbsolutePath.isNull()) { - qWarning("Mod %s has not been found, for some reason", qUtf8Printable(result)); + log::warn("Mod {} has not been found, for some reason", result); return; } @@ -4404,7 +4410,7 @@ void MainWindow::saveArchiveList() qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); } } else { - qWarning("archive list not initialised"); + log::warn("archive list not initialised"); } } @@ -4421,7 +4427,7 @@ void MainWindow::checkModsForUpdates() m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -5802,7 +5808,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -5918,7 +5924,7 @@ void MainWindow::finishUpdateInfo() } if (!finalMods.empty() && organizedGames.empty()) - qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); for (auto game : organizedGames) NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); @@ -6368,9 +6374,7 @@ void MainWindow::removeFromToolbar() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "removeFromToolbar(): executable '" << title << "' not found"; - + log::warn("removeFromToolbar(): executable '{}' not found", title); return; } @@ -6570,7 +6574,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { - qWarning("%s", line.c_str()); + log::warn("{}", line); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); } else { std::smatch match; @@ -6928,7 +6932,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m { QFileInfo file(url.toLocalFile()); if (!file.exists()) { - qWarning("invalid source file: %s", qUtf8Printable(file.absoluteFilePath())); + log::warn("invalid source file: {}", file.absoluteFilePath()); return; } QString target = outputDir + "/" + file.fileName(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 5652833a..3d55b28d 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include #include +#include #include #include #include @@ -36,7 +37,7 @@ along with Mod Organizer. If not, see . #include -using MOBase::reportError; +using namespace MOBase; class ProxyStyle : public QProxyStyle { @@ -137,7 +138,7 @@ void MOApplication::updateStyle(const QString &fileName) if (QFile::exists(fileName)) { setStyleSheet(QString("file:///%1").arg(fileName)); } else { - qWarning("invalid stylesheet: %s", qUtf8Printable(fileName)); + log::warn("invalid stylesheet: {}", fileName); } } } diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index c3142962..7110a590 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,6 +1,8 @@ #include "modflagicondelegate.h" +#include #include +using namespace MOBase; ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED , ModInfo::FLAG_CONFLICT_OVERWRITE @@ -117,7 +119,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_PLUGIN_SELECTED: return QString(); case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked"); default: - qWarning("ModInfo flag %d has no defined icon", flag); + log::warn("ModInfo flag {} has no defined icon", flag); return QString(); } } diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 92c7366c..ca6e8046 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -320,7 +320,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } if (organizedGames.empty()) { - qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); updatesAvailable = false; } else { log::info("{}", tr( diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index fba5d39a..3130b4bd 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -3,8 +3,9 @@ #include "modinfodialog.h" #include "settings.h" #include +#include -using MOBase::reportError; +using namespace MOBase; class ESPItem { @@ -297,7 +298,7 @@ void ESPsTab::onActivate() } if (esp->isActive()) { - qWarning("ESPsTab::onActive(): item is already active"); + log::warn("ESPsTab::onActive(): item is already active"); return; } @@ -348,7 +349,7 @@ void ESPsTab::onDeactivate() } if (!esp->isActive()) { - qWarning("ESPsTab::onDeactivate(): item is already inactive"); + log::warn("ESPsTab::onDeactivate(): item is already inactive"); return; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 7b71355c..df25df0d 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -275,7 +275,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QString(); } } else { - qWarning("category %d doesn't exist (may have been removed)", category); + log::warn("category {} doesn't exist (may have been removed)", category); modInfo->setCategory(category, false); return QString(); } @@ -618,8 +618,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) result = true; } break; default: { - qWarning("edit on column \"%s\" not supported", - getColumnName(index.column()).toUtf8().constData()); + log::warn( + "edit on column \"{}\" not supported", + getColumnName(index.column()).toUtf8().constData()); result = false; } break; } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 2d9ea4a5..1127c7d4 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "profile.h" #include "messagedialog.h" #include "qtgroupingproxy.h" +#include #include #include #include @@ -30,6 +31,7 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) @@ -240,7 +242,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, // nop, already compared by priority } break; default: { - qWarning() << "Sorting is not defined for column " << left.column(); + log::warn("Sorting is not defined for column {}", left.column()); } break; } return lt; @@ -474,7 +476,7 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons } if (row >= static_cast(m_Profile->numMods())) { - qWarning("invalid row index: %d", row); + log::warn("invalid row index: {}", row); return false; } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 2bcd72f3..008f3c0d 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "bbcode.h" #include #include +#include #include #include @@ -695,9 +696,13 @@ void NexusInterface::nextRequest() QTime time = QTime::currentTime(); QTime targetTime; targetTime.setHMS((time.hour() + 1) % 23, 5, 0); - QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. " - "Your next batch of requests will be available in approximately %1 minutes and %2 seconds."); - qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60); + QString warning = tr( + "You've exceeded the Nexus API rate limit and requests are now being throttled. " + "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.") + .arg(time.secsTo(targetTime) / 60) + .arg(time.secsTo(targetTime) % 60); + + log::warn("{}", warning); return; } @@ -747,8 +752,8 @@ void NexusInterface::nextRequest() url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); } else { - qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " - "or the download link was generated by a different account than the one stored in Mod Organizer."); + log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + "or the download link was generated by a different account than the one stored in Mod Organizer.")); return; } } break; @@ -828,16 +833,16 @@ void NexusInterface::requestFinished(std::list::iterator iter) m_User.limits(parseLimits(reply)); if (!m_User.exhausted()) { - qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); + log::warn("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); } else { - qWarning("All API requests have been consumed and are now being denied."); + log::warn("All API requests have been consumed and are now being denied."); } emit requestsChanged(getAPIStats(), m_User); - qWarning("Error: %s", reply->errorString().toUtf8().constData()); + log::warn("Error: {}", reply->errorString()); } else { - qWarning("request failed: %s", reply->errorString().toUtf8().constData()); + log::warn("request failed: {}", reply->errorString()); } emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), reply->errorString()); } else { @@ -940,7 +945,7 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) { QNetworkReply *reply = qobject_cast(sender()); if (reply == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } @@ -955,7 +960,7 @@ void NexusInterface::requestTimeout() { QTimer *timer = qobject_cast(sender()); if (timer == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c413e156..9f40894e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -612,7 +612,7 @@ void NXMAccessManager::clearCookies() if (jar != nullptr) { jar->clear(); } else { - qWarning("failed to clear cookies, invalid cookie jar"); + log::warn("failed to clear cookies, invalid cookie jar"); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 400f5391..dbff1a2a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -200,26 +200,26 @@ bool checkService() try { serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceManagerHandle) { - qWarning("failed to open service manager (query status) (error %d)", GetLastError()); + log::warn("failed to open service manager (query status) (error {})", GetLastError()); throw 1; } serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceHandle) { - qWarning("failed to open EventLog service (query status) (error %d)", GetLastError()); + log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); throw 2; } if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service config (error %d)", GetLastError()); + log::warn("failed to get size of service config (error {})", GetLastError()); throw 3; } DWORD serviceConfigSize = bytesNeeded; serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - qWarning("failed to query service config (error %d)", GetLastError()); + log::warn("failed to query service config (error {})", GetLastError()); throw 4; } @@ -230,14 +230,14 @@ bool checkService() if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service status (error %d)", GetLastError()); + log::warn("failed to get size of service status (error {})", GetLastError()); throw 5; } DWORD serviceStatusSize = bytesNeeded; serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - qWarning("failed to query service status (error %d)", GetLastError()); + log::warn("failed to query service status (error {})", GetLastError()); throw 6; } @@ -402,8 +402,9 @@ void OrganizerCore::storeSettings() if (result == QSettings::NoError) { QString errMsg = commitSettings(iniFile); if (!errMsg.isEmpty()) { - qWarning("settings file not writable, may be locked by another " - "application, trying direct write"); + log::warn( + "settings file not writable, may be locked by another " + "application, trying direct write"); writeTarget = iniFile; result = storeSettings(iniFile); } @@ -1381,9 +1382,9 @@ bool OrganizerCore::previewFileWithAlternatives( // sanity check, this shouldn't happen unless the caller passed an // incorrect id - qWarning().nospace() - << "selected preview origin " << selectedOrigin << " not found in " - << "list of alternatives"; + log::warn( + "selected preview origin {} not found in list of alternatives", + selectedOrigin); } for (int id : origins) { @@ -1798,8 +1799,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { - qWarning("\"%s\" not set up as executable", - qUtf8Printable(executable)); + log::warn("\"{}\" not set up as executable", executable); binary = QFileInfo(executable); } } @@ -1881,7 +1881,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL // Wait for a an event on the handle, a key press, mouse click or timeout res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); if (res == WAIT_FAILED) { - qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError(); + log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError()); break; } @@ -1897,7 +1897,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL if (res == WAIT_OBJECT_0) { // process we were waiting on has completed if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - qWarning() << "Failed getting exit code of complete process :" << GetLastError(); + log::warn("Failed getting exit code of complete process: {}", GetLastError()); CloseHandle(handle); handle = INVALID_HANDLE_VALUE; originalHandle = false; @@ -1962,7 +1962,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde DWORD pids[querySize]; size_t found = querySize; if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!"); return INVALID_HANDLE_VALUE; } @@ -1974,7 +1974,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError()); continue; } @@ -2118,7 +2118,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, dir.entryList(QStringList() << "*.esm", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esm)); + log::warn("failed to activate {}", esm); continue; } @@ -2134,7 +2134,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, dir.entryList(QStringList() << "*.esl", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esl)); + log::warn("failed to activate {}", esl); continue; } @@ -2150,7 +2150,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, for (const QString &esp : esps) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esp)); + log::warn("failed to activate {}", esp); continue; } @@ -2558,7 +2558,7 @@ std::vector OrganizerCore::activeProblems() const // of a "log spam". But since this is a sevre error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. - qWarning("hook.dll found in game folder: %s", qUtf8Printable(hookdll)); + log::warn("hook.dll found in game folder: {}", hookdll); problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); } return problems; @@ -2604,7 +2604,7 @@ void OrganizerCore::startGuidedFix(unsigned int) const bool OrganizerCore::saveCurrentLists() { if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); + log::warn("not saving lists during directory update"); return false; } @@ -2698,7 +2698,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, result.reserve(result.size() + saveMap.size()); result.insert(result.end(), saveMap.begin(), saveMap.end()); } else { - qWarning("local save games not supported by this game plugin"); + log::warn("local save games not supported by this game plugin"); } } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 2126c5ef..d47fa2c6 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -69,7 +69,7 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin) if (plugin == nullptr) { return false; } else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin->name()))) { - qWarning("plugin failed to initialize"); + log::warn("plugin failed to initialize"); return false; } return true; @@ -167,9 +167,10 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); } else { - qWarning("plugin \"%s\" failed to load. If this plugin is for an older version of MO " - "you have to update it or delete it if no update exists.", - qUtf8Printable(pluginName)); + log::warn( + "plugin \"{}\" failed to load. If this plugin is for an older version of MO " + "you have to update it or delete it if no update exists.", + pluginName); } } } @@ -298,7 +299,7 @@ void PluginContainer::loadPlugins() m_PluginLoaders.push_back(pluginLoader.release()); } else { m_FailedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load (may be outdated)", qUtf8Printable(pluginName)); + log::warn("plugin \"{}\" failed to load (may be outdated)", pluginName); } } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 2edb92f5..2fb743d0 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -417,7 +417,7 @@ void PluginList::addInformation(const QString &name, const QString &message) if (iter != m_ESPsByName.end()) { m_AdditionalInfo[name.toLower()].m_Messages.append(message); } else { - qWarning("failed to associate message for \"%s\"", qUtf8Printable(name)); + log::warn("failed to associate message for \"{}\"", name); } } @@ -694,7 +694,7 @@ void PluginList::setState(const QString &name, PluginStates state) { m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) || m_ESPs[iter->second].m_ForceEnabled; } else { - qWarning("Plugin not found: %s", qUtf8Printable(name)); + log::warn("Plugin not found: {}", name); } } diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 1e8e800f..da09935b 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -96,7 +96,7 @@ void ProblemsDialog::startFix() { QObject *fixButton = QObject::sender(); if (fixButton == NULL) { - qWarning("no button"); + log::warn("no button"); return; } IPluginDiagnose *plugin = reinterpret_cast(fixButton ->property("fix").value()); diff --git a/src/profile.cpp b/src/profile.cpp index 01906903..d4778305 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -125,7 +125,7 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) findProfileSettings(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qUtf8Printable(directory.path())); + log::warn("missing modlist.txt in {}", directory.path()); touchFile(m_Directory.filePath("modlist.txt")); } @@ -307,7 +307,7 @@ void Profile::renameModInAllProfiles(const QString& oldName, const QString& newN if (modList.exists()) renameModInList(modList, oldName, newName); else - qWarning("Profile has no modlist.txt : %s", qUtf8Printable(profileIter.filePath())); + log::warn("Profile has no modlist.txt: {}", profileIter.filePath()); } } @@ -328,7 +328,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (line.length() == 0) { // ignore empty lines - qWarning("mod list contained invalid data: empty line"); + log::warn("mod list contained invalid data: empty line"); continue; } @@ -343,7 +343,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (modName.isEmpty()) { // file broken? - qWarning("mod list contained invalid data: missing mod name"); + log::warn("mod list contained invalid data: missing mod name"); continue; } @@ -424,8 +424,9 @@ void Profile::refreshModStatus() m_ModStatus[modIndex].m_Priority = index++; } } else { - qWarning("no mod state for \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::warn( + "no mod state for \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } @@ -495,8 +496,10 @@ void Profile::dumpModStatus() const { for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + log::warn( + "{}: {} - {} ({})", + i, info->name(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); } } @@ -803,7 +806,7 @@ bool Profile::localSettingsEnabled() const QStringList missingFiles; for (QString file : m_GamePlugin->iniFiles()) { if (!QFile::exists(m_Directory.filePath(file))) { - qWarning("missing %s in %s", qUtf8Printable(file), qUtf8Printable(m_Directory.path())); + log::warn("missing {} in {}", file, m_Directory.path()); missingFiles << file; } } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index f9ea655f..d7863fc8 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -214,9 +214,9 @@ void ProfilesDialog::on_removeProfileButton_clicked() delete item; } if (!shellDelete(QStringList(profilePath))) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(profilePath), ::GetLastError()); + log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", profilePath, ::GetLastError()); if (!removeDir(profilePath)) { - qWarning("regular delete failed too"); + log::warn("regular delete failed too"); } } } diff --git a/src/settings.cpp b/src/settings.cpp index e622d632..92ae2251 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -165,8 +165,9 @@ void Settings::registerPlugin(IPlugin *plugin) for (const PluginSetting &setting : plugin->settings()) { QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qUtf8Printable(temp.toString()), qUtf8Printable(setting.key), qUtf8Printable(plugin->name())); + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; } m_PluginSettings[plugin->name()][setting.key] = temp; -- cgit v1.3.1 From e071dfdfaa369a475a2d93df623c1696feee56ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 02:47:13 -0400 Subject: changed qCritical() to log::error() removed now unused vlog() --- src/browserdialog.cpp | 12 ++++---- src/categories.cpp | 10 +++---- src/downloadlist.cpp | 5 ++-- src/downloadmanager.cpp | 10 +++++-- src/envmodule.cpp | 66 +++++++++++++++++------------------------- src/envsecurity.cpp | 58 +++++++++++++------------------------ src/envshortcut.cpp | 56 +++++++++++++++++------------------ src/envshortcut.h | 9 ------ src/envwindows.cpp | 19 ++++++------ src/executableslist.cpp | 10 +++---- src/filerenamer.cpp | 2 +- src/filterwidget.cpp | 5 +++- src/forcedloaddialogwidget.cpp | 9 +++--- src/installationmanager.cpp | 7 ++--- src/loglist.cpp | 18 ------------ src/mainwindow.cpp | 34 +++++++++++----------- src/moapplication.cpp | 10 ++++--- src/modinfo.cpp | 5 +--- src/modinfodialog.cpp | 12 ++++---- src/modinfodialogconflicts.cpp | 6 ++-- src/modinfodialogfiletree.cpp | 9 +++--- src/modinfodialogimages.cpp | 9 +++--- src/modinforegular.cpp | 20 ++++++------- src/modlist.cpp | 12 ++++---- src/modlistsortproxy.cpp | 2 +- src/nexusinterface.cpp | 28 +++++++++--------- src/organizercore.cpp | 22 +++++++------- src/overwriteinfodialog.cpp | 6 ++-- src/persistentcookiejar.cpp | 8 +++-- src/plugincontainer.cpp | 5 ++-- src/pluginlist.cpp | 10 +++---- src/profile.cpp | 4 +-- src/settings.cpp | 14 ++------- src/settingsdialog.cpp | 1 - src/shared/directoryentry.cpp | 23 ++++++++------- src/shared/error_report.h | 2 -- src/syncoverwritedialog.cpp | 3 +- src/texteditor.cpp | 7 +++-- src/transfersavesdialog.cpp | 13 ++++----- 39 files changed, 251 insertions(+), 310 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index e186ad63..1fde7f15 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -24,9 +24,10 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "report.h" #include "persistentcookiejar.h" +#include "settings.h" #include -#include "settings.h" +#include #include #include @@ -38,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; BrowserDialog::BrowserDialog(QWidget *parent) @@ -192,12 +194,12 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) try { QWebEnginePage *page = qobject_cast(sender()); if (page == nullptr) { - qCritical("sender not a page"); + log::error("sender not a page"); return; } BrowserView *view = qobject_cast(page->view()); if (view == nullptr) { - qCritical("no view?"); + log::error("no view?"); return; } @@ -206,14 +208,14 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) if (isVisible()) { MessageDialog::showMessage(tr("failed to start download"), this); } - qCritical("exception downloading unsupported content: %s", e.what()); + log::error("exception downloading unsupported content: {}", e.what()); } } void BrowserDialog::downloadRequested(const QNetworkRequest &request) { - qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); + log::error("download request {} ignored", request.url().toString()); } diff --git a/src/categories.cpp b/src/categories.cpp index 8f9d3ad8..7acf6ff5 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -62,8 +62,9 @@ void CategoryFactory::loadCategories() ++lineNum; QList cells = line.split('|'); if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", - lineNum, line.constData(), cells.count()); + log::error( + "invalid category line {}: {} ({} cells)", + lineNum, line.constData(), cells.count()); } else { std::vector nexusIDs; if (cells[2].length() > 0) { @@ -73,7 +74,7 @@ void CategoryFactory::loadCategories() bool ok = false; int temp = iter->toInt(&ok); if (!ok) { - qCritical("invalid category id %s", iter->constData()); + log::error("invalid category id {}", iter->constData()); } nexusIDs.push_back(temp); } @@ -83,8 +84,7 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok); int parentID = cells[3].trimmed().toInt(&cell3Ok); if (!cell0Ok || !cell3Ok) { - qCritical("invalid category line %d: %s", - lineNum, line.constData()); + log::error("invalid category line {}: {}", lineNum, line.constData()); } addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); } diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 5e698e0e..36bc2b7f 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -19,12 +19,13 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadmanager.h" +#include #include #include #include - #include +using namespace MOBase; DownloadList::DownloadList(DownloadManager *manager, QObject *parent) : QAbstractTableModel(parent), m_Manager(manager) @@ -192,7 +193,7 @@ void DownloadList::update(int row) else if (row < this->rowCount()) emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, this->columnCount(QModelIndex())-1, QModelIndex())); else - qCritical("invalid row %d in download list, update failed", row); + log::error("invalid row {} in download list, update failed", row); } QString DownloadList::sizeFormat(quint64 size) const diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index e3ceb261..348b2108 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -660,7 +660,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if ((download->m_State == STATE_STARTED) || (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible - qCritical("tried to remove active download"); + log::error("tried to remove active download"); endDisableDirWatcher(); return; } @@ -798,7 +798,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit update(-1); endDisableDirWatcher(); } catch (const std::exception &e) { - qCritical("failed to remove download: %s", e.what()); + log::error("failed to remove download: {}", e.what()); } refreshList(); } @@ -2069,7 +2069,11 @@ void DownloadManager::writeData(DownloadInfo *info) if (ret < info->m_Reply->size()) { QString fileName = info->m_FileName; // m_FileName may be destroyed after setState setState(info, DownloadState::STATE_CANCELED); - qCritical(QString("Unable to write download \"%2\" to drive (return %1)").arg(ret).arg(info->m_FileName).toLocal8Bit()); + + log::error( + "Unable to write download \"{}\" to drive (return {})", + info->m_FileName, ret); + reportError(tr("Unable to write download to drive (return %1).\n" "Check the drive's available storage.\n\n" "Canceling download \"%2\"...").arg(ret).arg(fileName)); diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 1717da15..aae4e0b1 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -1,6 +1,7 @@ #include "envmodule.h" #include "env.h" #include +#include namespace env { @@ -114,9 +115,9 @@ Module::FileInfo Module::getFileInfo() const return {}; } - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); + log::error( + "GetFileVersionInfoSizeW() failed on '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -127,9 +128,9 @@ Module::FileInfo Module::getFileInfo() const if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); + log::error( + "GetFileVersionInfoW() failed on '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -161,9 +162,9 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; + log::error( + "bad file info signature {:#x} for '{}'", + fi->dwSignature, m_path); return {}; } @@ -187,9 +188,7 @@ QString Module::getFileDescription(std::byte* buffer) const buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - + log::error("VerQueryValueW() for translations failed on '{}'", m_path); return {}; } @@ -254,9 +253,9 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (h.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); + log::error( + "can't open file '{}' for timestamp, {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -264,9 +263,10 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const // getting the file time if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); + + log::error( + "can't get file time for '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -281,11 +281,9 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const SYSTEMTIME utc = {}; if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; + log::error( + "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'", + ft.dwHighDateTime, ft.dwLowDateTime, m_path); return {}; } @@ -307,18 +305,14 @@ QString Module::getMD5() const QFile f(m_path); if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - + log::error("failed to open file '{}' for md5", m_path); return {}; } // hashing QCryptographicHash hash(QCryptographicHash::Md5); if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - + log::error("failed to calculate md5 for '{}'", m_path); return {}; } @@ -334,11 +328,7 @@ std::vector getLoadedModules() if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessageQ(e)); return {}; } @@ -349,10 +339,7 @@ std::vector getLoadedModules() if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - + log::error("Module32First() failed, {}", formatSystemMessageQ(e)); return {}; } @@ -371,8 +358,7 @@ std::vector getLoadedModules() // no more modules is not an error if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); + log::error("Module32Next() failed, {}", formatSystemMessageQ(e)); } break; diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 559ce4ad..015e4000 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,6 +1,7 @@ #include "envsecurity.h" #include "env.h" #include +#include #include #include @@ -57,8 +58,7 @@ public: } if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); + log::error("enum->next() failed, {}", formatSystemMessageQ(ret)); break; } @@ -82,9 +82,9 @@ private: IID_IWbemLocator, &rawLocator); if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); + log::error( + "CoCreateInstance for WbemLocator failed, {}", + formatSystemMessageQ(ret)); throw failed(); } @@ -102,10 +102,9 @@ private: &rawService); if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); + log::error( + "locator->ConnectServer() failed for namespace '{}', {}", + ns, formatSystemMessageQ(res)); throw failed(); } @@ -121,9 +120,7 @@ private: if (FAILED(ret)) { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessageQ(ret)); throw failed(); } } @@ -142,10 +139,7 @@ private: if (FAILED(ret) || !rawEnumerator) { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - + log::error("query '{}' failed, {}", query, formatSystemMessageQ(ret)); return {}; } @@ -256,15 +250,12 @@ std::vector getSecurityProductsFromWMI() // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - + log::error("failed to get displayName, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + log::error("displayName is a {}, not a bstr", prop.vt); return; } @@ -274,15 +265,12 @@ std::vector getSecurityProductsFromWMI() // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - + log::error("failed to get productState, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + log::error("productState is a {}, is not a VT_UI4", prop.vt); return; } @@ -298,15 +286,12 @@ std::vector getSecurityProductsFromWMI() // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - + log::error("failed to get instanceGuid, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + log::error("instanceGuid is a {}, is not a bstr", prop.vt); return; } @@ -362,9 +347,9 @@ std::optional getWindowsFirewall() __uuidof(INetFwPolicy2), &rawPolicy); if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); + log::error( + "CoCreateInstance for NetFwPolicy2 failed, {}", + formatSystemMessageQ(hr)); return {}; } @@ -378,10 +363,7 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - + log::error("get_FirewallEnabled failed, {}", formatSystemMessageQ(hr)); return {}; } } diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 30ef4633..1deb9dad 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -3,6 +3,7 @@ #include "executableslist.h" #include "instancemanager.h" #include +#include namespace env { @@ -218,17 +219,24 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; + log::debug( + "adding shortcut to {}:\n" + " . name: '{}'\n" + " . target: '{}'\n" + " . arguments: '{}'\n" + " . description: '{}'\n" + " . icon: '{}' @ {}\n" + " . working directory: '{}'", + toString(loc), + m_name, + m_target, + m_arguments, + m_description, + m_icon, m_iconIndex, + m_workingDirectory); if (m_target.isEmpty()) { - critical() << "target is empty"; + log::error("shortcut: target is empty"); return false; } @@ -237,7 +245,7 @@ bool Shortcut::add(Locations loc) return false; } - debug() << "shorcut file will be saved at '" << path << "'"; + log::debug("shorcut file will be saved at '{}'", path); try { @@ -255,7 +263,7 @@ bool Shortcut::add(Locations loc) } catch(ShellLinkException& e) { - critical() << e.what() << "\nshortcut file was not saved"; + log::error("{}\nshortcut file was not saved", e.what()); } return false; @@ -263,26 +271,26 @@ bool Shortcut::add(Locations loc) bool Shortcut::remove(Locations loc) { - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + log::debug("removing shortcut for '{}' from {}", m_name, toString(loc)); const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } - debug() << "path to shortcut file is '" << path << "'"; + log::debug("path to shortcut file is '{}'", path); if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; + log::error("can't remove shortcut '{}', file not found", path); return false; } if (!MOBase::shellDelete({path})) { const auto e = ::GetLastError(); - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); + log::error( + "failed to remove shortcut '{}', {}", + path, formatSystemMessageQ(e)); return false; } @@ -323,7 +331,7 @@ QString Shortcut::shortcutDirectory(Locations loc) const case None: default: - critical() << "bad location " << loc; + log::error("shortcut: bad location {}", loc); break; } } @@ -337,23 +345,13 @@ QString Shortcut::shortcutDirectory(Locations loc) const QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { - critical() << "name is empty"; + log::error("shortcut name is empty"); return {}; } return m_name + ".lnk"; } -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - QString toString(Shortcut::Locations loc) { diff --git a/src/envshortcut.h b/src/envshortcut.h index 904b3ab7..82eea191 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -84,15 +84,6 @@ private: int m_iconIndex; QString m_workingDirectory; - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - // returns the path where the shortcut file should be saved // QString shortcutPath(Locations loc) const; diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 4fbd788a..8a98036a 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,6 +1,7 @@ #include "envwindows.h" #include "env.h" #include +#include namespace env { @@ -13,7 +14,7 @@ WindowsInfo::WindowsInfo() LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; + log::error("failed to load ntdll.dll while getting version"); return; } else { m_reported = getReportedVersion(ntdll.get()); @@ -122,7 +123,7 @@ WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const GetProcAddress(ntdll, "RtlGetVersion")); if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; + log::error("RtlGetVersion() not found in ntdll.dll"); return {}; } @@ -149,7 +150,7 @@ WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + log::error("RtlGetNtVersionNumbers not found in ntdll.dll"); return {}; } @@ -207,9 +208,9 @@ std::optional WindowsInfo::getElevated() const if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { const auto e = GetLastError(); - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + log::error( + "while trying to check if process is elevated, " + "OpenProcessToken() failed: {}", formatSystemMessageQ(e)); return {}; } @@ -223,9 +224,9 @@ std::optional WindowsInfo::getElevated() const if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { const auto e = GetLastError(); - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + log::error( + "while trying to check if process is elevated, " + "GetTokenInformation() failed: {}", formatSystemMessageQ(e)); return {}; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index fbb96bd4..2408e8f3 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -243,9 +243,9 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) if (flags == MoveExisting) { const auto newTitle = makeNonConflictingTitle(exe.title()); if (!newTitle) { - qCritical().nospace() - << "executable '" << exe.title() << "' was in the way but could " - << "not be renamed"; + log::error( + "executable '{}' was in the way but could not be renamed", + exe.title()); return; } @@ -289,9 +289,7 @@ std::optional ExecutablesList::makeNonConflictingTitle( title = prefix + QString(" (%1)").arg(i); } - qCritical().nospace() - << "ran out of executable titles for prefix '" << prefix << "'"; - + log::error("ran out of executable titles for prefix '{}'", prefix); return {}; } diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index b516c902..8835f52f 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -10,7 +10,7 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags flags) { // sanity check for flags if ((m_flags & (HIDE|UNHIDE)) == 0) { - qCritical("renameFile() missing hide flag"); + log::error("renameFile() missing hide flag"); // doesn't really matter, it's just for text m_flags = HIDE; } diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 44cbb274..0638add3 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,5 +1,8 @@ #include "filterwidget.h" #include "eventfilter.h" +#include + +using namespace MOBase; FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) : QSortFilterProxyModel(parent), m_filter(fw) @@ -80,7 +83,7 @@ QModelIndex FilterWidget::map(const QModelIndex& index) if (m_proxy) { return m_proxy->mapToSource(index); } else { - qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + log::error("FilterWidget::map() called, but proxy isn't set up"); return index; } } diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index b92838c3..b84f785f 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -1,9 +1,8 @@ #include "forcedloaddialogwidget.h" #include "ui_forcedloaddialogwidget.h" - -#include - #include "executableinfo.h" +#include +#include using namespace MOBase; @@ -85,7 +84,7 @@ void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() if (fileInfo.exists()) { ui->libraryPathEdit->setText(filePath); } else { - qCritical("%ls does not exist", filePath.toStdWString().c_str()); + log::error("{} does not exist", filePath); } } } @@ -102,7 +101,7 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked() if (fileInfo.exists()) { ui->processEdit->setText(fileName); } else { - qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str()); + log::error("{} does not exist", fileInfo.filePath()); } } } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 0e50de52..fd971f47 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -263,7 +263,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/'); } if (targetFile == nullptr) { - qCritical() << "Failed to find backslash in " << data[i]->getFileName(); + log::error("Failed to find backslash in {}", data[i]->getFileName()); continue; } else { // skip the slash @@ -527,7 +527,7 @@ bool InstallationManager::testOverwrite(GuessedValue &modName, bool *me settingsFile.write(originalSettings); settingsFile.close(); } else { - qCritical("failed to restore original settings: %s", qUtf8Printable(metaFilename)); + log::error("failed to restore original settings: {}", metaFilename); } return true; } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { @@ -856,8 +856,7 @@ bool InstallationManager::install(const QString &fileName, } } } catch (const IncompatibilityException &e) { - qCritical("plugin \"%s\" incompatible: %s", - qUtf8Printable(installer->name()), e.what()); + log::error("plugin \"{}\" incompatible: {}", installer->name(), e.what()); } // act upon the installation result. at this point the files have already been diff --git a/src/loglist.cpp b/src/loglist.cpp index 207f412b..c34ac76e 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -196,21 +196,3 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } - - -void vlog(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 70ace8f1..ad87ba03 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1212,14 +1212,14 @@ void MainWindow::createHelpMenu() QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//TL")) { QStringList params = firstLine.mid(4).trimmed().split('#'); if (params.size() != 2) { - qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; + log::error("invalid header line for tutorial {}, expected 2 parameters", fileName); continue; } QAction *tutAction = new QAction(params.at(0), tutorialMenu); @@ -1323,7 +1323,7 @@ void MainWindow::hookUpWindowTutorials() QString fileName = dirIter.fileName(); QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); @@ -1369,7 +1369,7 @@ void MainWindow::showEvent(QShowEvent *event) TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } } else { - qCritical() << firstStepsTutorial << " missing"; + log::error("{} missing", firstStepsTutorial); QPoint pos = ui->toolBar->mapToGlobal(QPoint()); pos.rx() += ui->toolBar->width() / 2; pos.ry() += ui->toolBar->height(); @@ -1636,7 +1636,7 @@ void MainWindow::startExeAction() QAction *action = qobject_cast(sender()); if (action == nullptr) { - qCritical("not an action?"); + log::error("not an action?"); return; } @@ -3415,7 +3415,7 @@ void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tab { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { - qCritical("failed to resolve mod name %s", qUtf8Printable(modName)); + log::error("failed to resolve mod name {}", modName); return; } @@ -3500,7 +3500,7 @@ void MainWindow::visitOnNexus_clicked() if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } else { - qCritical() << "mod '" << info->name() << "' has no nexus id"; + log::error("mod '{}' has no nexus id", info->name()); } } } @@ -4038,7 +4038,7 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - qCritical("Move operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("Move operation failed: {}", windowsErrorString(::GetLastError())); } m_OrganizerCore.refreshModList(); @@ -4067,7 +4067,7 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - qCritical("Delete operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("Delete operation failed: {}", windowsErrorString(::GetLastError())); } } } @@ -4311,7 +4311,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere void MainWindow::addRemoveCategories_MenuHandler() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4352,7 +4352,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { void MainWindow::replaceCategories_MenuHandler() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4547,7 +4547,7 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, categoryBox->setChecked(categoryID == info->getPrimaryCategory()); action->setDefaultWidget(categoryBox); } catch (const std::exception &e) { - qCritical("failed to create category checkbox: %s", e.what()); + log::error("failed to create category checkbox: {}", e.what()); } action->setData(categoryID); @@ -4559,7 +4559,7 @@ void MainWindow::addPrimaryCategoryCandidates() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } menu->clear(); @@ -6067,7 +6067,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); + log::error("failed to disconnect endorsement slot"); } } @@ -6527,11 +6527,11 @@ void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) secAttributes.lpSecurityDescriptor = nullptr; if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - qCritical("failed to create stdout reroute"); + log::error("failed to create stdout reroute"); } if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - qCritical("failed to correctly set up the stdout reroute"); + log::error("failed to correctly set up the stdout reroute"); *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; } } @@ -6965,7 +6965,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - qCritical("file operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("file operation failed: {}", windowsErrorString(::GetLastError())); } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 3d55b28d..370a23b5 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -115,13 +115,15 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) try { return QApplication::notify(receiver, event); } catch (const std::exception &e) { - qCritical("uncaught exception in handler (object %s, eventtype %d): %s", - receiver->objectName().toUtf8().constData(), event->type(), e.what()); + log::error( + "uncaught exception in handler (object {}, eventtype {}): {}", + receiver->objectName(), event->type(), e.what()); reportError(tr("an error occurred: %1").arg(e.what())); return false; } catch (...) { - qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", - receiver->objectName().toUtf8().constData(), event->type()); + log::error( + "uncaught non-std exception in handler (object {}, eventtype {})", + receiver->objectName(), event->type()); reportError(tr("an error occurred")); return false; } diff --git a/src/modinfo.cpp b/src/modinfo.cpp index ca6e8046..5a05e7ca 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -530,10 +530,7 @@ QUrl ModInfo::parseCustomURL() const const auto url = QUrl::fromUserInput(getCustomURL()); if (!url.isValid()) { - qCritical() - << "mod '" << name() << "' has an invalid custom url " - << "'" << getCustomURL() << "'"; - + log::error("mod '{}' has an invalid custom url '{}'", name(), getCustomURL()); return {}; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 47ac84be..a7a6b0d7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -176,7 +176,7 @@ void ModInfoDialog::createTabs() // check for tabs in the ui not having a corresponding tab in the list int count = ui->tabWidget->count(); if (count < 0 || count > static_cast(m_tabs.size())) { - qCritical() << "mod info dialog has more tabs than expected"; + log::error("mod info dialog has more tabs than expected"); count = static_cast(m_tabs.size()); } @@ -239,13 +239,13 @@ void ModInfoDialog::setMod(const QString& name) { unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { - qCritical() << "failed to resolve mod name " << name; + log::error("failed to resolve mod name {}", name); return; } auto mod = ModInfo::getByIndex(index); if (!mod) { - qCritical() << "mod by index " << index << " is null"; + log::error("mod by index {} is null", index); return; } @@ -307,7 +307,7 @@ void ModInfoDialog::update(bool firstTime) // changed tabInfo->tab->activated(); } else { - qCritical() << "tab index " << oldTab << " not found"; + log::error("tab index {} not found", oldTab); } } } @@ -400,7 +400,7 @@ void ModInfoDialog::reAddTabs( if (itor == orderedNames.end()) { // this shouldn't happen, it means there's a tab in the UI that's no // in the list - qCritical() << "can't sort tabs, '" << objectName << "' not found"; + log::error("can't sort tabs, '{}' not found", objectName); canSort = false; } } @@ -753,7 +753,7 @@ void ModInfoDialog::onTabMoved() } if (!found) { - qCritical() << "unknown tab at index " << i; + log::error("unknown tab at index {}", i); } } } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 511d48ad..d16d548c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -365,7 +365,7 @@ void for_each_in_selection(QTreeView* tree, F&& f) const auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return; } @@ -454,7 +454,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "list doesn't have a ConflictListModel"; + log::error("list doesn't have a ConflictListModel"); return; } @@ -633,7 +633,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) const auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return {}; } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 0b519932..219ddf35 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -5,8 +5,9 @@ #include "filerenamer.h" #include #include +#include -using MOBase::reportError; +using namespace MOBase; namespace shell = MOBase::shell; // if there are more than 50 selected items in the filetree, don't bother @@ -230,19 +231,19 @@ bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) if (m_fs->isDir(index)) { if (!deleteFileRecursive(index)) { - qCritical() << "failed to delete" << m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } else { if (!m_fs->remove(index)) { - qCritical() << "failed to delete", m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } } if (!m_fs->remove(parent)) { - qCritical() << "failed to delete" << m_fs->fileName(parent); + log::error("failed to delete {}", m_fs->fileName(parent)); return false; } diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 69866902..10362058 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -2,7 +2,9 @@ #include "ui_modinfodialog.h" #include "settings.h" #include "utility.h" +#include +using namespace MOBase; using namespace ImagesTabHelpers; QSize resizeWithAspectRatio(const QSize& original, const QSize& available) @@ -896,10 +898,9 @@ void File::ensureOriginalLoaded() QImageReader reader(m_path); if (!reader.read(&m_original)) { - qCritical().noquote().nospace() - << "failed to load '" << m_path << "'\n" - << reader.errorString() << " " - << "(error " << static_cast(reader.error()) << ")"; + log::error( + "failed to load '{}'\n{} (error {})", + m_path, reader.errorString(), static_cast(reader.error())); m_failed = true; } diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 448447e1..074fa9e2 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -68,8 +68,7 @@ ModInfoRegular::~ModInfoRegular() try { saveMeta(); } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - qUtf8Printable(m_Name), e.what()); + log::error("failed to save meta information for \"{}\": {}", m_Name, e.what()); } } @@ -258,14 +257,14 @@ void ModInfoRegular::saveMeta() if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } } @@ -425,14 +424,13 @@ bool ModInfoRegular::setName(const QString &name) return false; } if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); + log::error("rename to final name failed after successful rename to intermediate name"); modDir.rename(tempName, m_Name); return false; } } else { if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qUtf8Printable(name), ::GetLastError()); + log::error("failed to rename mod {} (errorcode {})", name, ::GetLastError()); return false; } } diff --git a/src/modlist.cpp b/src/modlist.cpp index df25df0d..6ebd0e8b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -271,7 +271,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const int categoryIdx = categoryFactory.getCategoryIndex(category); return categoryFactory.getCategoryName(categoryIdx); } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); + log::error("failed to retrieve category name: {}", e.what()); return QString(); } } else { @@ -449,7 +449,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { return modInfo->getDescription(); } catch (const std::exception &e) { - qCritical("invalid mod description: %s", e.what()); + log::error("invalid mod description: {}", e.what()); return QString(); } } else if (column == COL_VERSION) { @@ -488,7 +488,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); + log::error("failed to generate tooltip: {}", e.what()); return QString(); } } @@ -636,9 +636,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) try { m_ModStateChanged(info->name(), newState); } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); + log::error("failed to invoke state changed notification: {}", e.what()); } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); + log::error("failed to invoke state changed notification: unknown exception"); } } @@ -834,7 +834,7 @@ void ModList::modInfoChanged(ModInfo::Ptr info) emit dataChanged(index(row, 0), index(row, columnCount())); emit postDataChanged(); } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); + log::error("modInfoChanged not called after modInfoAboutToChange"); } m_ChangeInfo.name = QString(); } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 1127c7d4..d330e0c2 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -196,7 +196,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); lt = leftCatName < rightCatName; } catch (const std::exception &e) { - qCritical("failed to compare categories: %s", e.what()); + log::error("failed to compare categories: {}", e.what()); } } } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 008f3c0d..c797aed6 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -41,12 +41,11 @@ using namespace MOShared; void throttledWarning(const APIUserAccount& user) { - qCritical() << - QString( - "You have fewer than %1 requests remaining (%2). Only downloads and " - "login validation are being allowed.") - .arg(APIUserAccount::ThrottleThreshold) - .arg(user.remainingRequests()); + log::error( + "You have fewer than {} requests remaining ({}). Only downloads and " + "login validation are being allowed.", + APIUserAccount::ThrottleThreshold, + user.remainingRequests()); } @@ -344,7 +343,7 @@ QString NexusInterface::getGameURL(QString gameName) const if (game != nullptr) { return "https://www.nexusmods.com/" + game->gameNexusName().toLower(); } else { - qCritical("getGameURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getGameURL can't find plugin for {}", gameName); return ""; } } @@ -355,7 +354,7 @@ QString NexusInterface::getOldModsURL(QString gameName) const if (game != nullptr) { return "https://" + game->gameNexusName().toLower() + ".nexusmods.com/mods"; } else { - qCritical("getOldModsURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getOldModsURL can't find plugin for {}", gameName); return ""; } } @@ -464,7 +463,7 @@ int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant IPluginGame *game = getGame(gameName); if (game == nullptr) { - qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestUpdates can't find plugin for {}", gameName); return -1; } @@ -521,7 +520,7 @@ int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, QOb { IPluginGame *gamePlugin = getGame(gameName); if (gamePlugin == nullptr) { - qCritical("requestFileInfo can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestFileInfo can't find plugin for {}", gameName); return -1; } @@ -687,7 +686,7 @@ void NexusInterface::nextRequest() } else if (getAccessManager()->validateWaiting()) { return; } else { - qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API."); + log::error("{}", tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.")); } } @@ -949,10 +948,9 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) return; } - qCritical("request (%s) error: %s (%d)", - qUtf8Printable(reply->url().toString()), - qUtf8Printable(reply->errorString()), - reply->error()); + log::error( + "request ({}) error: {} ({})", + reply->url().toString(), reply->errorString(), reply->error()); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dbff1a2a..725371e9 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -224,7 +224,7 @@ bool checkService() } if (serviceConfig->dwStartType == SERVICE_DISABLED) { - qCritical("Windows Event Log service is disabled!"); + log::error("Windows Event Log service is disabled!"); serviceRunning = false; } @@ -242,7 +242,7 @@ bool checkService() } if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - qCritical("Windows Event Log service is not running"); + log::error("Windows Event Log service is not running"); serviceRunning = false; } } @@ -437,7 +437,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { lastError = GetLastError(); - qCritical("unable to get snapshot of processes (error %d)", lastError); + log::error("unable to get snapshot of processes (error {})", lastError); return false; } @@ -446,7 +446,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) pe32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hProcessSnap, &pe32)) { lastError = GetLastError(); - qCritical("unable to get first process (error %d)", lastError); + log::error("unable to get first process (error {})", lastError); CloseHandle(hProcessSnap); return false; } @@ -486,7 +486,7 @@ return true; void OrganizerCore::updateExecutablesList(QSettings &settings) { if (m_PluginContainer == nullptr) { - qCritical("can't update executables list now"); + log::error("can't update executables list now"); return; } @@ -657,7 +657,7 @@ void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, in } } catch (const std::exception &e) { MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); - qCritical("exception starting download: %s", e.what()); + log::error("exception starting download: {}", e.what()); } } @@ -1552,7 +1552,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, bool steamFound = true; bool steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } if (!steamFound) { @@ -1569,9 +1569,9 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, steamFound = true; steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } else if (!steamFound) { - qCritical("could not find Steam"); + log::error("could not find Steam"); } } else if (result == QDialogButtonBox::Cancel) { @@ -1592,14 +1592,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, if (result == QDialogButtonBox::Yes) { WCHAR cwd[MAX_PATH]; if (!GetCurrentDirectory(MAX_PATH, cwd)) { - qCritical("unable to get current directory (error %d)", GetLastError()); + log::error("unable to get current directory (error {})", GetLastError()); cwd[0] = L'\0'; } if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - qCritical("unable to relaunch MO as admin"); + log::error("unable to relaunch MO as admin"); return INVALID_HANDLE_VALUE; } qApp->exit(0); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 5ee8d76c..cc4ae849 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -121,18 +121,18 @@ bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); if (m_FileSystemModel->isDir(childIndex)) { if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); return false; } } else { if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); return false; } } } if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(index)); return false; } return true; diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 1ed463c6..670bf382 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -1,8 +1,10 @@ #include "persistentcookiejar.h" +#include #include #include #include +using namespace MOBase; PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent) : QNetworkCookieJar(parent), m_FileName(fileName) @@ -24,7 +26,7 @@ void PersistentCookieJar::clear() { void PersistentCookieJar::save() { QTemporaryFile file; if (!file.open()) { - qCritical("failed to save cookies: couldn't create temporary file"); + log::error("failed to save cookies: couldn't create temporary file"); return; } QDataStream data(&file); @@ -40,14 +42,14 @@ void PersistentCookieJar::save() { QFile oldCookies(m_FileName); if (oldCookies.exists()) { if (!oldCookies.remove()) { - qCritical("failed to save cookies: failed to remove %s", qUtf8Printable(m_FileName)); + log::error("failed to save cookies: failed to remove {}", m_FileName); return; } } // if it doesn't exists that's fine } if (!file.copy(m_FileName)) { - qCritical("failed to save cookies: failed to write %s", qUtf8Printable(m_FileName)); + log::error("failed to save cookies: failed to write {}", m_FileName); } } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index d47fa2c6..36daec52 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -291,8 +291,9 @@ void PluginContainer::loadPlugins() std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); if (pluginLoader->instance() == nullptr) { m_FailedPlugins.push_back(pluginName); - qCritical("failed to load plugin %s: %s", - qUtf8Printable(pluginName), qUtf8Printable(pluginLoader->errorString())); + log::error( + "failed to load plugin {}: {}", + pluginName, pluginLoader->errorString()); } else { if (registerPlugin(pluginLoader->instance(), pluginName)) { qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 2fb743d0..e436d7f6 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -309,7 +309,7 @@ int PluginList::findPluginByPriority(int priority) return i; } } - qCritical(QString("No plugin with priority %1").arg(priority).toLocal8Bit()); + log::error("No plugin with priority {}", priority); return -1; } @@ -824,7 +824,7 @@ void PluginList::updateIndices() continue; } if (m_ESPs[i].m_Priority >= static_cast(m_ESPs.size())) { - qCritical("invalid plugin priority: %d", m_ESPs[i].m_Priority); + log::error("invalid plugin priority: {}", m_ESPs[i].m_Priority); continue; } m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; @@ -1067,9 +1067,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int this->index(0, 0), this->index(static_cast(m_ESPs.size()), columnCount())); } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); + log::error("failed to invoke state changed notification: {}", e.what()); } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); + log::error("failed to invoke state changed notification: unknown exception"); } } @@ -1368,7 +1368,7 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, m_Masters.insert(QString(iter->c_str())); } } catch (const std::exception &e) { - qCritical("failed to parse plugin file %s: %s", qUtf8Printable(fullPath), e.what()); + log::error("failed to parse plugin file {}: {}", fullPath, e.what()); m_IsMaster = false; m_IsLight = false; m_IsLightFlagged = false; diff --git a/src/profile.cpp b/src/profile.cpp index d4778305..555de89a 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -572,7 +572,7 @@ void Profile::setModsEnabled(const QList &modsToEnable, const QLis QList dirtyMods; for (auto idx : modsToEnable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (!m_ModStatus[idx].m_Enabled) { @@ -582,7 +582,7 @@ void Profile::setModsEnabled(const QList &modsToEnable, const QLis } for (auto idx : modsToDisable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (ModInfo::getByIndex(idx)->alwaysEnabled()) { diff --git a/src/settings.cpp b/src/settings.cpp index 92ae2251..9c303442 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -221,9 +221,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - qCritical().nospace() - << "Retrieving encrypted data failed: " - << formatSystemMessageQ(e); + log::error("Retrieving encrypted data failed: {}", formatSystemMessageQ(e)); } } delete[] keyData; @@ -368,11 +366,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - - qCritical().nospace() - << "Storing API key failed: " - << formatSystemMessageQ(e); - + log::error("Storing API key failed: {}", formatSystemMessageQ(e)); return false; } @@ -493,9 +487,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - qCritical().nospace() - << "Storing or deleting password failed: " - << formatSystemMessageQ(e); + log::error("Storing or deleting password failed: {}", formatSystemMessageQ(e)); } } diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0dae31ac..99943d04 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -485,7 +485,6 @@ void SettingsDialog::onValidatorStateChanged( for (auto&& line : log.split("\n")) { addNexusLog(line); } - } updateNexusState(); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 9d9edd85..2cdbac74 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "leaktrace.h" #include "error_report.h" +#include #include #include #include @@ -35,6 +36,8 @@ along with Mod Organizer. If not, see . namespace MOShared { +namespace log = MOBase::log; + static const int MAXPATH_UNICODE = 32767; class OriginConnection { @@ -103,7 +106,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - vlog("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + log::error("failed to change name lookup from {} to {}", oldName, newName); } } @@ -714,14 +717,14 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { m_Files.erase(iter); } else { - vlog("file \"%ls\" not in directory \"%ls\"", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); + log::error( + "file \"{}\" not in directory \"{}\"", + m_FileRegister->getFile(index)->getName(), this->getName()); } } else { - vlog("file \"%ls\" not in directory \"%ls\", directory empty", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); + log::error( + "file \"{}\" not in directory \"{}\", directory empty", + m_FileRegister->getFile(index)->getName(), this->getName()); } } @@ -844,7 +847,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - vlog("unexpected end of path"); + log::error("unexpected end of path"); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -988,7 +991,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - vlog("invalid file index for remove: %lu", index); + log::error("invalid file index for remove: {}", index); return false; } } @@ -1002,7 +1005,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - vlog("invalid file index for remove (for origin): %lu", index); + log::error("invalid file index for remove (for origin): {}", index); } } diff --git a/src/shared/error_report.h b/src/shared/error_report.h index a003ee09..17b25645 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -30,5 +30,3 @@ void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); } // namespace MOShared - -void vlog(const char* format, ...); diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 4ee4716e..b1643b2d 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "ui_syncoverwritedialog.h" #include #include +#include #include #include @@ -86,7 +87,7 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director if (subDir != nullptr) { readTree(fileInfo.absoluteFilePath(), subDir, newItem); } else { - qCritical("no directory structure for %s?", qUtf8Printable(file)); + log::error("no directory structure for {}?", file); delete newItem; newItem = nullptr; } diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 130cd76f..0c0eb1cc 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,7 +1,10 @@ #include "texteditor.h" #include "utility.h" +#include #include +using namespace MOBase; + TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), @@ -249,7 +252,7 @@ QWidget* TextEditor::wrapEditWidget() auto index = splitter->indexOf(this); if (index == -1) { - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "parent is a splitter, but widget isn't in it"); @@ -260,7 +263,7 @@ QWidget* TextEditor::wrapEditWidget() } else { // unknown parent - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "no parent or parent has no layout"); diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 130df14f..1b211fd3 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "isavegame.h" #include "savegameinfo.h" #include +#include #include #include @@ -186,7 +187,7 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellMove(source, destination, this); }, - "Failed to move %s to %s")) { + "Failed to move {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); refreshLocalSaves(); @@ -203,7 +204,7 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellCopy(source, destination, this); }, - "Failed to copy %s to %s")) { + "Failed to copy {} to {}")) { refreshLocalSaves(); refreshLocalCharacters(); } @@ -218,7 +219,7 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellMove(source, destination, this); }, - "Failed to move %s to %s")) { + "Failed to move {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); refreshLocalSaves(); @@ -235,7 +236,7 @@ void TransferSavesDialog::on_copyToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellCopy(source, destination, this); }, - "Failed to copy %s to %s")) { + "Failed to copy {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); } @@ -340,9 +341,7 @@ bool TransferSavesDialog::transferCharacters( } if (!method(sourceFile.absoluteFilePath(), destinationFile)) { - qCritical(errmsg, - sourceFile.absoluteFilePath().toUtf8().constData(), - qUtf8Printable(destinationFile)); + log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile); } } } -- cgit v1.3.1 From b3d0ddb0b75da4abd59cae1508d983945c8e235d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 04:21:45 -0400 Subject: changed qDebug() to log::debug() removed some commented out logging --- src/categories.cpp | 4 +- src/downloadlistwidget.cpp | 5 ++- src/downloadmanager.cpp | 35 ++++++++--------- src/executableslist.cpp | 4 +- src/filerenamer.cpp | 43 +++++++++++---------- src/installationmanager.cpp | 16 ++++---- src/instancemanager.cpp | 2 +- src/loadmechanism.cpp | 13 ++++--- src/mainwindow.cpp | 37 +++++++----------- src/messagedialog.cpp | 6 ++- src/modinfodialog.cpp | 4 +- src/modinfodialogconflicts.cpp | 24 ++++++++---- src/modinfodialogfiletree.cpp | 12 +++--- src/modinforegular.cpp | 5 ++- src/modlist.cpp | 7 ++-- src/modlistsortproxy.cpp | 2 +- src/nexusinterface.cpp | 8 ++-- src/nxmaccessmanager.cpp | 4 +- src/organizercore.cpp | 41 +++++++++++--------- src/persistentcookiejar.cpp | 2 +- src/plugincontainer.cpp | 14 +++---- src/pluginlist.cpp | 6 +-- src/profile.cpp | 16 ++++---- src/qtgroupingproxy.cpp | 87 +++++++++++++++++------------------------- src/selfupdater.cpp | 18 ++++----- src/settings.cpp | 7 ++-- src/usvfsconnector.cpp | 19 ++++----- 27 files changed, 213 insertions(+), 228 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 7acf6ff5..12b18998 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -360,10 +360,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const { std::map::const_iterator iter = m_NexusMap.find(nexusID); if (iter != m_NexusMap.end()) { - qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); + log::debug("nexus category id {} maps to internal {}", nexusID, iter->second); return iter->second; } else { - qDebug("nexus category id %d not mapped", nexusID); + log::debug("nexus category id {} not mapped", nexusID); return 0U; } } diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index e2a6f321..85d27831 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadlistwidget.h" +#include #include #include #include @@ -29,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; + void DownloadProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QModelIndex sourceIndex = m_SortProxy->mapToSource(index); @@ -286,7 +289,7 @@ void DownloadListWidget::issueDelete() void DownloadListWidget::issueRemoveFromView() { - qDebug() << "removing from view: " << m_ContextRow; + log::debug("removing from view: {}", m_ContextRow); emit removeDownload(m_ContextRow, false); } diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 348b2108..d2556faa 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -352,7 +352,7 @@ void DownloadManager::refreshList() } } if (orphans.size() > 0) { - qDebug("%d orphaned meta files will be deleted", orphans.size()); + log::debug("{} orphaned meta files will be deleted", orphans.size()); shellDelete(orphans, true); } @@ -379,7 +379,7 @@ void DownloadManager::refreshList() } //if (m_ActiveDownloads.size() != downloadsBefore) { - qDebug("Downloads after refresh: %d", m_ActiveDownloads.size()); + log::debug("Downloads after refresh: {}", m_ActiveDownloads.size()); //} emit update(-1); @@ -401,7 +401,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, } QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); - qDebug("selected download url: %s", qUtf8Printable(preferredUrl.toString())); + log::debug("selected download url: {}", preferredUrl.toString()); QNetworkRequest request(preferredUrl); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo); @@ -562,9 +562,9 @@ void DownloadManager::addNXMDownload(const QString &url) break; } } - qDebug("add nxm download: %s", qUtf8Printable(url)); + log::debug("add nxm download: {}", url); if (foundGame == nullptr) { - qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game())); + log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game()); QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); return; @@ -572,13 +572,14 @@ void DownloadManager::addNXMDownload(const QString &url) for (auto tuple : m_PendingDownloads) { if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { - QString debugStr("download requested is already queued (mod: %1, file: %2)"); - QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2")); + const auto infoStr = + tr("There is already a download queued for this file.\n\nMod %1\nFile %2") + .arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - debugStr = debugStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - infoStr = infoStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); + log::debug( + "download requested is already queued (mod: {}, file: {})", + nxmInfo.modId(), nxmInfo.fileId()); - qDebug(qUtf8Printable(debugStr)); QMessageBox::information(nullptr, tr("Already Queued"), infoStr, QMessageBox::Ok); return; } @@ -622,7 +623,7 @@ void DownloadManager::addNXMDownload(const QString &url) infoStr = infoStr.arg(QStringLiteral("")); } - qDebug(qUtf8Printable(debugStr)); + log::debug("{}", debugStr); QMessageBox::information(nullptr, tr("Already Started"), infoStr, QMessageBox::Ok); return; } @@ -883,7 +884,7 @@ void DownloadManager::resumeDownloadInt(int index) if (info->m_State == STATE_ERROR) { info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); } - qDebug("request resume from url %s", qUtf8Printable(info->currentURL())); + log::debug("request resume from url {}", info->currentURL()); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); if (info->m_State != STATE_ERROR) { @@ -896,7 +897,7 @@ void DownloadManager::resumeDownloadInt(int index) std::get<2>(info->m_SpeedDiff) = 0; std::get<3>(info->m_SpeedDiff) = 0; std::get<4>(info->m_SpeedDiff) = 0; - qDebug("resume at %lld bytes", info->m_ResumePos); + log::debug("resume at {} bytes", info->m_ResumePos); startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); } emit update(index); @@ -993,11 +994,11 @@ void DownloadManager::queryInfoMd5(int index) downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName); } if (!downloadFile.exists()) { - qDebug("Can't find download file %s", info->m_FileName); + log::debug("Can't find download file {}", info->m_FileName); return; } if (!downloadFile.open(QIODevice::ReadOnly)) { - qDebug("Can't open download file %s", info->m_FileName); + log::debug("Can't open download file {}", info->m_FileName); return; } info->m_Hash = QCryptographicHash::hash(downloadFile.readAll(), QCryptographicHash::Md5); @@ -1384,7 +1385,7 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString())); } break; case STATE_FETCHINGMODINFO_MD5: { - qDebug(qUtf8Printable(QString("Searching %1 for MD5 of %2").arg(info->m_GamesToQuery[0]).arg(QString(info->m_Hash.toHex())))); + log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], QString(info->m_Hash.toHex())); m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5(info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString())); } break; case STATE_READY: { @@ -1780,7 +1781,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use if (chosenIdx < 0) { chosenIdx = i; //intentional to not break in order to check other results } else { - qDebug("Multiple active files found during MD5 search. Defaulting to time stamps..."); + log::debug("Multiple active files found during MD5 search. Defaulting to time stamps..."); chosenIdx = -1; break; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 2408e8f3..3f76bb6f 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -166,7 +166,7 @@ std::vector ExecutablesList::getPluginExecutables( void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) { - qDebug("resetting plugin executables"); + log::debug("resetting plugin executables"); Q_ASSERT(game != nullptr); @@ -295,7 +295,7 @@ std::optional ExecutablesList::makeNonConflictingTitle( void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) { - qDebug() << "upgrading executables list"; + log::debug("upgrading executables list"); Q_ASSERT(game != nullptr); diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index 8835f52f..a97d7742 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -18,10 +18,10 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags flags) FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) { - qDebug().nospace() << "renaming " << oldName << " to " << newName; + log::debug("renaming {} to {}", oldName, newName); if (QFileInfo(newName).exists()) { - qDebug().nospace() << newName << " already exists"; + log::debug("{} already exists", newName); // target file already exists, confirm replacement auto answer = confirmReplace(newName); @@ -29,24 +29,25 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt switch (answer) { case DECISION_SKIP: { // user wants to skip this file - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } case DECISION_REPLACE: { - qDebug().nospace() << "removing " << newName; + log::debug("removing {}", newName); + // user wants to replace the file, so remove it if (!QFile(newName).remove()) { log::warn("failed to remove '{}'", newName); // removal failed, warn the user and allow canceling if (!removeFailed(newName)) { - qDebug().nospace() << "canceling " << oldName; + log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } @@ -56,7 +57,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt case DECISION_CANCEL: // fall-through default: { // user wants to stop - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } } @@ -70,17 +71,17 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName)) { // user wants to cancel - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } // everything worked - qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + log::debug("successfully renamed {} to {}", oldName, newName); return RESULT_OK; } @@ -88,12 +89,12 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) { if (m_flags & REPLACE_ALL) { // user wants to silently replace all - qDebug().nospace() << "user has selected replace all"; + log::debug("user has selected replace all"); return DECISION_REPLACE; } else if (m_flags & REPLACE_NONE) { // user wants to silently skip all - qDebug().nospace() << "user has selected replace none"; + log::debug("user has selected replace none"); return DECISION_SKIP; } @@ -117,28 +118,28 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) switch (answer) { case QMessageBox::Yes: - qDebug().nospace() << "user wants to replace"; + log::debug("user wants to replace"); return DECISION_REPLACE; case QMessageBox::No: - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return DECISION_SKIP; case QMessageBox::YesToAll: - qDebug().nospace() << "user wants to replace all"; + log::debug("user wants to replace all"); // remember the answer m_flags |= REPLACE_ALL; return DECISION_REPLACE; case QMessageBox::NoToAll: - qDebug().nospace() << "user wants to replace none"; + log::debug("user wants to replace none"); // remember the answer m_flags |= REPLACE_NONE; return DECISION_SKIP; case QMessageBox::Cancel: // fall-through default: - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return DECISION_CANCEL; } } @@ -158,12 +159,12 @@ bool FileRenamer::removeFailed(const QString& name) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } @@ -182,11 +183,11 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index fd971f47..89d0079f 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -398,7 +398,7 @@ bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *nod for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { - qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); + log::debug("{} on the top level", (*iter)->getData().name.toQString()); return true; } } @@ -424,7 +424,7 @@ DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *da (currentNode->numNodes() == 1)) { currentNode = *currentNode->nodesBegin(); } else { - qDebug("not a simple archive"); + log::debug("not a simple archive"); return nullptr; } } @@ -576,7 +576,7 @@ bool InstallationManager::doInstall(GuessedValue &modName, QString game QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); - qDebug("installing to \"%s\"", qUtf8Printable(targetDirectoryNative)); + log::debug("installing to \"{}\"", targetDirectoryNative); m_InstallationProgress = new QProgressDialog(m_ParentWidget); ON_BLOCK_EXIT([this] () { @@ -764,7 +764,7 @@ bool InstallationManager::install(const QString &fileName, if ((modID == 0) && (guessedModID != -1)) { modID = guessedModID; } else if (modID != guessedModID) { - qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); + log::debug("passed mod id: {}, guessed id: {}", modID, guessedModID); } modName.update(guessedModName, GUESS_GOOD); @@ -774,7 +774,7 @@ bool InstallationManager::install(const QString &fileName, if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { m_CurrentFile = fileInfo.fileName(); } - qDebug("using mod name \"%s\" (id %d) -> %s", qUtf8Printable(modName), modID, qUtf8Printable(m_CurrentFile)); + log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, m_CurrentFile); //If there's an archive already open, close it. This happens with the bundle //installer when it uncompresses a split archive, then finds it has a real archive @@ -785,9 +785,9 @@ bool InstallationManager::install(const QString &fileName, bool archiveOpen = m_ArchiveHandler->open(fileName, new MethodCallback(this, &InstallationManager::queryPassword)); if (!archiveOpen) { - qDebug("integrated archiver can't open %s: %s (%d)", - qUtf8Printable(fileName), - qUtf8Printable(getErrorString(m_ArchiveHandler->getLastError())), + log::debug("integrated archiver can't open {}: {} ({})", + fileName, + getErrorString(m_ArchiveHandler->getLastError()), m_ArchiveHandler->getLastError()); } ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 55ef3fc8..fdc30e22 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -224,7 +224,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); if (selection.exec() == QDialog::Rejected) { - qDebug("rejected"); + log::debug("rejected"); throw MOBase::MyException(QObject::tr("Canceled")); } diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 8f0529ce..4d6cebd4 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -141,7 +142,7 @@ void LoadMechanism::deactivateScriptExtender() { vfsDLLName = ToQString(AppConfig::vfs64DLLName()); } - qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1()); + log::debug("USVFS DLL Name: {}", vfsDLLName); if (vfsDLLName != "") { if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) { // remove dll from SE plugins directory @@ -215,8 +216,8 @@ void LoadMechanism::activateScriptExtender() QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL)); QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL); - qDebug("DLL USVFS Target Path: " + targetPath.toLatin1()); - qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1()); + log::debug("DLL USVFS Target Path: {}", targetPath); + log::debug("DLL USVFS VFS DLL Path: {}", vfsDLLPath); QFile dllFile(targetPath); @@ -297,17 +298,17 @@ void LoadMechanism::activate(EMechanism mechanism) { switch (mechanism) { case LOAD_MODORGANIZER: { - qDebug("Load Mechanism: Mod Organizer"); + log::debug("Load Mechanism: Mod Organizer"); deactivateProxyDLL(); deactivateScriptExtender(); } break; case LOAD_SCRIPTEXTENDER: { - qDebug("Load Mechanism: ScriptExtender"); + log::debug("Load Mechanism: ScriptExtender"); deactivateProxyDLL(); activateScriptExtender(); } break; case LOAD_PROXYDLL: { - qDebug("Load Mechanism: Proxy DLL"); + log::debug("Load Mechanism: Proxy DLL"); deactivateScriptExtender(); activateProxyDLL(); } break; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ad87ba03..e502bdb1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -867,7 +867,7 @@ void MainWindow::updatePinnedExecutables() exeAction->setStatusTip(exe.binaryInfo().filePath()); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); + log::debug("failed to connect trigger?"); } if (m_linksSeparator) { @@ -1711,7 +1711,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) // ensure the new index is valid if (index < 0 || index >= ui->profileBox->count()) { - qDebug("invalid profile index, using last profile"); + log::debug("invalid profile index, using last profile"); ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); } @@ -2060,7 +2060,7 @@ void MainWindow::refreshSaveList() QDir savesDir = currentSavesDir(); savesDir.setNameFilters(filters); - qDebug("reading save games from %s", qUtf8Printable(savesDir.absolutePath())); + log::debug("reading save games from {}", savesDir.absolutePath()); QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); for (const QFileInfo &file : files) { @@ -2261,15 +2261,6 @@ void MainWindow::fixCategories() void MainWindow::setupNetworkProxy(bool activate) { QNetworkProxyFactory::setUseSystemConfiguration(activate); -/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); - query.setProtocolTag("http"); - QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); - if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { - qDebug("Using proxy: %s", qUtf8Printable(proxies.at(0).hostName())); - QNetworkProxy::setApplicationProxy(proxies[0]); - } else { - qDebug("Not using proxy"); - }*/ } @@ -2456,7 +2447,7 @@ void MainWindow::unlock() { //If you come through here with a null lock pointer, it's a bug! if (m_LockDialog == nullptr) { - qDebug("Unlocking main window when already unlocked"); + log::debug("Unlocking main window when already unlocked"); return; } --m_LockCount; @@ -3259,7 +3250,7 @@ void MainWindow::displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { - qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); + log::debug("A different mod information dialog is open. If this is incorrect, please restart MO"); return; } std::vector flags = modInfo->getFlags(); @@ -4325,7 +4316,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { int maxRow = -1; for (const QPersistentModelIndex &idx : selected) { - qDebug("change categories on: %s", qUtf8Printable(idx.data().toString())); + log::debug("change categories on: {}", idx.data().toString()); QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); if (modIdx.row() != m_ContextIdx.row()) { addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); @@ -4407,7 +4398,7 @@ void MainWindow::saveArchiveList() } } if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + log::debug("{} saved", QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName())); } } else { log::warn("archive list not initialised"); @@ -5364,7 +5355,7 @@ void MainWindow::installTranslator(const QString &name) QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { - qDebug("localization file %s not found", qUtf8Printable(fileName)); + log::debug("localization file %s not found", fileName); } // we don't actually expect localization files for English (en, en-us, en-uk, and any variation thereof) } @@ -5389,7 +5380,7 @@ void MainWindow::languageChange(const QString &newLanguage) installTranslator(QFileInfo(fileName).baseName()); } ui->retranslateUi(this); - qDebug("loaded language %s", qUtf8Printable(newLanguage)); + log::debug("loaded language {}", newLanguage); ui->profileBox->setItemText(0, QObject::tr("")); @@ -5634,7 +5625,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); - qDebug().nospace() << "opening in explorer: " << fullPath; + log::debug("opening in explorer: {}", fullPath); shell::ExploreFile(fullPath); } @@ -6120,7 +6111,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString) { if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) { - qDebug(qUtf8Printable(tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID))); + log::debug("{}", tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)); } else { MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); } @@ -6587,7 +6578,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe std::string dependency(match[2].first, match[2].second); m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - qDebug("[loot] %s", line.c_str()); + log::debug("[loot] {}", line); } } } @@ -6632,7 +6623,7 @@ void MainWindow::on_bossButton_clicked() try { m_OrganizerCore.prepareVFS(); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug("{}", e.what()); return; } catch (const std::exception &e) { QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); @@ -6662,7 +6653,7 @@ void MainWindow::on_bossButton_clicked() if (isJobHandle) { if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { - qDebug("no more processes in job"); + log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 6c6de3e7..78a5dd4d 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -19,10 +19,13 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "ui_messagedialog.h" +#include #include #include #include +using namespace MOBase; + MessageDialog::MessageDialog(const QString &text, QWidget *reference) : QDialog(reference), ui(new Ui::MessageDialog) @@ -81,7 +84,8 @@ void MessageDialog::resizeEvent(QResizeEvent *event) void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) { - qDebug("%s", qUtf8Printable(text)); + log::debug("{}", text); + if (reference != nullptr) { if (bringToFront || (qApp->activeWindow() != nullptr)) { MessageDialog *dialog = new MessageDialog(text, reference); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a7a6b0d7..4b1e2f76 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -556,9 +556,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id) } // this could happen if the tab is not visible right now - qDebug() - << "can't switch to tab ID " << static_cast(id) - << ", not available"; + log::debug("can't switch to tab ID {}, not available", static_cast(id)); } MOShared::FilesOrigin* ModInfoDialog::getOrigin() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d16d548c..9a5d9d8d 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -438,10 +438,18 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) const auto n = smallSelectionSize(tree); - qDebug().nospace().noquote() - << (visible ? "unhiding" : "hiding") << " " - << (n > max_small_selection ? "a lot of" : QString("%1").arg(n)) - << " conflict files"; + // logging + { + const QString action = (visible ? "unhiding" : "hiding"); + + QString files; + if (n > max_small_selection) + files = "a lot of"; + else + files = QString("%1").arg(n); + + log::debug("{} {} conflict files", action, files); + } QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -467,7 +475,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) if (visible) { if (!item->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping"; + log::debug("cannot unhide {}, skipping", item->relativeName()); return true; } @@ -475,7 +483,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } else { if (!item->canHide()) { - qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping"; + log::debug("cannot hide {}, skipping", item->relativeName()); return true; } @@ -504,10 +512,10 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) return true; }); - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; + log::debug("{} conflict files done", (visible ? "unhiding" : "hiding")); if (changed) { - qDebug().nospace() << "triggering refresh"; + log::debug("triggering refresh"); if (origin()) { emitOriginModified(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 219ddf35..207c792d 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -257,9 +257,9 @@ void FileTreeTab::changeVisibility(bool visible) bool changed = false; bool stop = false; - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << selection.size() << " filetree files"; + log::debug( + "{} {} filetree files", + (visible ? "unhiding" : "hiding"), selection.size()); QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -280,13 +280,13 @@ void FileTreeTab::changeVisibility(bool visible) if (visible) { if (!canUnhideFile(false, path)) { - qDebug().nospace() << "cannot unhide " << path << ", skipping"; + log::debug("cannot unhide {}, skipping", path); continue; } result = unhideFile(renamer, path); } else { if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; + log::debug("cannot hide {}, skipping", path); continue; } result = hideFile(renamer, path); @@ -312,7 +312,7 @@ void FileTreeTab::changeVisibility(bool visible) } } - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; + log::debug("{} filetree files done", (visible ? "unhiding" : "hiding")); if (changed) { if (origin()) { diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 074fa9e2..6e11befc 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -883,8 +883,9 @@ std::vector ModInfoRegular::getIniTweaks() const int numTweaks = metaFile.beginReadArray("INI Tweaks"); if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + log::debug( + "{} active ini tweaks in {}", + numTweaks, QDir::toNativeSeparators(metaFileName)); } for (int i = 0; i < numTweaks; ++i) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 6ebd0e8b..39f51d72 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1013,9 +1013,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); for (auto url : mimeData->urls()) { - //qDebug("URL drop requested: %s -> %s", qUtf8Printable(url.url()), qUtf8Printable(modDir.canonicalPath())); if (!url.isLocalFile()) { - qDebug("URL drop ignored: \"%s\" is not a local file", qUtf8Printable(url.url())); + log::debug("URL drop ignored: \"{}\" is not a local file", url.url()); continue; } @@ -1035,7 +1034,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa originName = overwriteName; relativePath = overwriteDir.relativeFilePath(sourceFile); } else { - qDebug("URL drop ignored: \"%s\" is not a known file to MO", qUtf8Printable(sourceFile)); + log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile); continue; } @@ -1047,7 +1046,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa if (sourceList.count()) { if (!shellMove(sourceList, targetList)) { - qDebug("Failed to move file (error %d)", ::GetLastError()); + log::debug("Failed to move file (error {})", ::GetLastError()); return false; } } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d330e0c2..77ffad96 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -482,7 +482,7 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons QModelIndex idx = sourceModel()->index(row, 0, parent); if (!idx.isValid()) { - qDebug("invalid mod index"); + log::debug("invalid mod index"); return false; } if (sourceModel()->hasChildren(idx)) { diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index c797aed6..0e2bb45b 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -316,15 +316,15 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo } else { modID = strtol(candidate.c_str(), nullptr, 10); } - qDebug("mod id guessed: %s -> %d", qUtf8Printable(fileName), modID); + log::debug("mod id guessed: {} -> {}", fileName, modID); } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - qDebug("simple expression matched, using name only"); + log::debug("simple expression matched, using name only"); modName = QString::fromUtf8(result[1].str().c_str()); modName = modName.replace('_', ' ').trimmed(); modID = -1; } else { - qDebug("no expression matched!"); + log::debug("no expression matched!"); modName.clear(); modID = -1; } @@ -860,7 +860,7 @@ void NexusInterface::requestFinished(std::list::iterator iter) if (nexusError.length() == 0) { nexusError = tr("empty response"); } - qDebug("nexus error: %s", qUtf8Printable(nexusError)); + log::debug("nexus error: {}", nexusError); emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), nexusError); } else { QJsonDocument responseDoc = QJsonDocument::fromJson(data); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 9f40894e..fd1dc0c1 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -600,9 +600,9 @@ void NXMAccessManager::showCookies() const { QUrl url(NexusBaseUrl + "/"); for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { - qDebug("%s - %s (expires: %s)", + log::debug("{} - {} (expires: {})", cookie.name().constData(), cookie.value().constData(), - qUtf8Printable(cookie.expirationDate().toString())); + cookie.expirationDate().toString()); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 725371e9..f6802673 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -89,9 +89,9 @@ static bool isOnline() if (addresses.count() == 0) { continue; } - qDebug("interface %s seems to be up (address: %s)", - qUtf8Printable(iter->humanReadableName()), - qUtf8Printable(addresses[0].ip().toString())); + log::debug("interface {} seems to be up (address: {})", + iter->humanReadableName(), + addresses[0].ip().toString()); connected = true; } } @@ -543,7 +543,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (isOnline() && !m_Settings.offlineMode()) { m_Updater.testForUpdate(); } else { - qDebug("user doesn't seem to be connected to the internet"); + log::debug("user doesn't seem to be connected to the internet"); } } } @@ -605,7 +605,7 @@ bool OrganizerCore::nexusApi(bool retry) QString apiKey; if (m_Settings.getNexusApiKey(apiKey)) { // credentials stored or user entered them manually - qDebug("attempt to verify nexus api key"); + log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); return true; } else { @@ -627,7 +627,7 @@ void OrganizerCore::startMOUpdate() void OrganizerCore::downloadRequestedNXM(const QString &url) { - qDebug("download requested: %s", qUtf8Printable(url)); + log::debug("download requested: {}", url); if (nexusApi()) { m_PendingDownloads.append(url); } else { @@ -1208,7 +1208,9 @@ QString OrganizerCore::findJavaInstallation(const QString& jarFile) if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { DWORD binaryType = 0UL; if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + log::debug( + "failed to determine binary type of \"{}\": {}", + QString::fromWCharArray(buffer), ::GetLastError()); } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { return QString::fromWCharArray(buffer); } @@ -1459,7 +1461,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, // need to remove our stored load order because it may be outdated if a foreign tool changed the // file time. After removing that file, refreshESPList will use the file time as the order if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - qDebug("removing loadorder.txt"); + log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } refreshDirectoryStructure(); @@ -1627,7 +1629,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, m_USVFS.updateForcedLibraries(forcedLibraries); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug(e.what()); return INVALID_HANDLE_VALUE; } catch (const std::exception &e) { QMessageBox::warning(window, tr("Error"), e.what()); @@ -1694,17 +1696,16 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - qDebug() << "Spawning proxyed process <" << cmdline << ">"; + log::debug("Spawning proxyed process <{}>", cmdline); return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { - qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; + log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); return startBinary(binary, arguments, currentDirectory, true); } } else { - qDebug("start of \"%s\" canceled by plugin", - qUtf8Printable(binary.absoluteFilePath())); + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); return INVALID_HANDLE_VALUE; } } @@ -1872,9 +1873,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL processName += QString(" (%1)").arg(currentPID); if (uilock) uilock->setProcessName(processName); - qDebug() << "Waiting for" - << (originalHandle ? "spawned" : "usvfs") - << "process completion :" << qUtf8Printable(processName); + + log::debug( + "Waiting for {} process completion: {}", + (originalHandle ? "spawned" : "usvfs"), processName); + newHandle = false; } @@ -1943,11 +1946,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL } if (res == WAIT_OBJECT_0) - qDebug() << "Waiting for process completion successfull"; + log::debug("Waiting for process completion successfull"); else if (uiunlocked) - qDebug() << "Waiting for process completion aborted by UI"; + log::debug("Waiting for process completion aborted by UI"); else - qDebug() << "Waiting for process completion not successfull :" << res; + log::debug("Waiting for process completion not successfull: {}", res); if (handle != INVALID_HANDLE_VALUE) ::CloseHandle(handle); diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 670bf382..8657f356 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -13,7 +13,7 @@ PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *paren } PersistentCookieJar::~PersistentCookieJar() { - qDebug("save %s", qUtf8Printable(m_FileName)); + log::debug("save {}", m_FileName); save(); } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 36daec52..62cdff1e 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -91,7 +91,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // generic treatment for all plugins IPlugin *pluginObj = qobject_cast(plugin); if (pluginObj == nullptr) { - qDebug("not an IPlugin"); + log::debug("not an IPlugin"); return false; } plugin->setProperty("filename", fileName); @@ -164,7 +164,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) for (QObject *proxiedPlugin : matchingPlugins) { if (proxiedPlugin != nullptr) { if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); + log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); } else { log::warn( @@ -191,7 +191,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } } - qDebug("no matching plugin interface"); + log::debug("no matching plugin interface"); return false; } @@ -225,7 +225,7 @@ void PluginContainer::unloadPlugins() QPluginLoader *loader = m_PluginLoaders.back(); m_PluginLoaders.pop_back(); if ((loader != nullptr) && !loader->unload()) { - qDebug("failed to unload %s: %s", qUtf8Printable(loader->fileName()), qUtf8Printable(loader->errorString())); + log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); } delete loader; } @@ -274,13 +274,13 @@ void PluginContainer::loadPlugins() loadCheck.open(QIODevice::WriteOnly); QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); + log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); while (iter.hasNext()) { iter.next(); if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qUtf8Printable(iter.fileName())); + log::debug("plugin \"{}\" blacklisted", iter.fileName()); continue; } loadCheck.write(iter.fileName().toUtf8()); @@ -296,7 +296,7 @@ void PluginContainer::loadPlugins() pluginName, pluginLoader->errorString()); } else { if (registerPlugin(pluginLoader->instance(), pluginName)) { - qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); + log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); m_PluginLoaders.push_back(pluginLoader.release()); } else { m_FailedPlugins.push_back(pluginName); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index e436d7f6..6718641f 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -481,7 +481,7 @@ void PluginList::writeLockedOrder(const QString &fileName) const file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); } file.commit(); - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); + log::debug("{} saved", QDir::toNativeSeparators(fileName)); } @@ -506,7 +506,7 @@ void PluginList::saveTo(const QString &lockedOrderFileName } } if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(deleterFileName))); + log::debug("{} saved", QDir::toNativeSeparators(deleterFileName)); } } else if (QFile::exists(deleterFileName)) { shellDelete(QStringList() << deleterFileName); @@ -521,7 +521,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) return true; } - qDebug("setting file times on esps"); + log::debug("setting file times on esps"); for (ESPInfo &esp : m_ESPs) { std::wstring espName = ToWString(esp.m_Name); diff --git a/src/profile.cpp b/src/profile.cpp index 555de89a..27616986 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -40,7 +40,6 @@ along with Mod Organizer. If not, see . #include #include #include // for QStringList -#include // for qDebug, qWarning, etc #include // for qUtf8Printable #include #include @@ -232,7 +231,6 @@ void Profile::doWriteModlist() } for (std::map::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) { - //qDebug(QString("write mod %1 to priority %2").arg(iter->first).arg(iter->second).toLocal8Bit()); // the priority order was inverted on load so it has to be inverted again unsigned int index = iter->second; if (index != UINT_MAX) { @@ -253,7 +251,7 @@ void Profile::doWriteModlist() } if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); + log::debug("{} saved", QDir::toNativeSeparators(fileName)); } } catch (const std::exception &e) { reportError(tr("failed to write mod list: %1").arg(e.what())); @@ -292,7 +290,7 @@ void Profile::createTweakedIniFile() .arg(formatSystemMessageQ(e))); } - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); + log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); } // static @@ -364,8 +362,9 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr } if (renamed) - qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", - renamed, qUtf8Printable(oldName), qUtf8Printable(newName), qUtf8Printable(modList.fileName())); + log::debug( + "Renamed {} \"{}\" mod to \"{}\" in {}", + renamed, oldName, newName, modList.fileName()); } void Profile::refreshModStatus() @@ -431,8 +430,9 @@ void Profile::refreshModStatus() modStatusModified = true; } } else { - qDebug("mod not found: \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::debug( + "mod not found: \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 5fcb84d3..ff9539d7 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -18,11 +18,14 @@ #include "qtgroupingproxy.h" +#include #include #include #include +using namespace MOBase; + /*! \class QtGroupingProxy \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level. @@ -86,7 +89,6 @@ QtGroupingProxy::setGroupedColumn( int groupedColumn ) QList QtGroupingProxy::belongsTo( const QModelIndex &idx ) { - //qDebug() << __FILE__ << __FUNCTION__; QList rowDataList; //get all the data for this index from the model @@ -106,7 +108,7 @@ QtGroupingProxy::belongsTo( const QModelIndex &idx ) i.next(); int role = i.key(); QVariant variant = i.value(); - // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant; + if ( variant.type() == QVariant::List ) { //a list of variants get's expanded to multiple rows @@ -162,7 +164,7 @@ QtGroupingProxy::buildTree() m_parentCreateList.clear(); int max = sourceModel()->rowCount( m_rootNode ); - //qDebug() << QString("building tree with %1 leafs.").arg( max ); + //WARNING: these have to be added in order because the addToGroups function is optimized for //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best. for( int row = 0; row < max; row++ ) @@ -232,9 +234,6 @@ QtGroupingProxy::addSourceRow( const QModelIndex &idx ) int updatedGroup = -1; if( !data.isEmpty() ) { - // qDebug() << QString("index %1 belongs to group %2").arg( row ) - // .arg( data[0][Qt::DisplayRole].toString() ); - foreach( const RowData &cachedData, m_groupMaps ) { //when this matches the index belongs to an existing group @@ -316,21 +315,12 @@ QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const pc.row = parent.row(); m_parentCreateList << pc; - //dumpParentCreateList(); - // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() ); - // for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) - // { - // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex << - // " | " << m_parentCreateList[i].row; - // } - return m_parentCreateList.size() - 1; } QModelIndex QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const { - // qDebug() << "index requested for: (" << row << "," << column << "), " << parent; if( !hasIndex(row, column, parent) ) { return QModelIndex(); } @@ -350,17 +340,15 @@ QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const QModelIndex QtGroupingProxy::parent( const QModelIndex &index ) const { - //qDebug() << "parent: " << index; if( !index.isValid() ) return QModelIndex(); int parentCreateIndex = index.internalId(); - //qDebug() << "parentCreateIndex: " << parentCreateIndex; if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) return QModelIndex(); struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; - //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")"; + //only items at column 0 have children return createIndex( pc.row, 0, pc.parentCreateIndex ); } @@ -368,12 +356,10 @@ QtGroupingProxy::parent( const QModelIndex &index ) const int QtGroupingProxy::rowCount( const QModelIndex &index ) const { - //qDebug() << "rowCount: " << index; if( !index.isValid() ) { //the number of top level groups + the number of non-grouped items int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits::max() ).count(); - //qDebug() << rows << " in root group"; return rows; } @@ -382,12 +368,10 @@ QtGroupingProxy::rowCount( const QModelIndex &index ) const { qint64 groupIndex = index.row(); int rows = m_groupHash.value( groupIndex ).count(); - //qDebug() << rows << " in group " << m_groupMaps[groupIndex]; return rows; } else { QModelIndex originalIndex = mapToSource( index ); int rowCount = sourceModel()->rowCount( originalIndex ); - //qDebug() << "original item: rowCount == " << rowCount; return rowCount; } } @@ -447,7 +431,7 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const { if( !index.isValid() ) return QVariant(); - // qDebug() << __FUNCTION__ << index << " role: " << role; + int row = index.row(); int column = index.column(); if( isGroup( index ) ) @@ -495,11 +479,9 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const } } - //qDebug() << __FUNCTION__ << "is a group"; //use cached or precalculated data if( m_groupMaps[row][column].contains( Qt::DisplayRole ) ) { - // qDebug() << "Using cached data for " << row << "x" << column << ": " << m_groupMaps[row][column].value(Qt::DisplayRole).toString(); if ((m_flags & FLAG_NOGROUPNAME) != 0) { QModelIndex parentIndex = this->index( row, 0, index.parent() ); QModelIndex childIndex = this->index( 0, column, parentIndex ); @@ -526,18 +508,17 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const function = mapToSource(childIndex).data(m_aggregateRole).toInt(); } - //qDebug() << __FUNCTION__ << "childCount: " << childCount; //Need a parentIndex with column == 0 because only those have children. QModelIndex parentIndex = this->index( row, 0, index.parent() ); for( int childRow = 0; childRow < childCount; childRow++ ) { QModelIndex childIndex = this->index( childRow, column, parentIndex ); QVariant data = mapToSource( childIndex ).data( role ); - //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type()); + if( data.isValid() && !variantsOfChildren.contains( data ) ) variantsOfChildren << data; } - //qDebug() << "gathered this data from children: " << variantsOfChildren; + //saving in cache ItemData roleMap = m_groupMaps[row].value( column ); foreach( const QVariant &variant, variantsOfChildren ) @@ -547,8 +528,6 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const } } - //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role]; - if( variantsOfChildren.count() == 0 ) return QVariant(); @@ -621,34 +600,30 @@ QtGroupingProxy::isGroup( const QModelIndex &index ) const QModelIndex QtGroupingProxy::mapToSource( const QModelIndex &index ) const { - //qDebug() << "mapToSource: " << index; if( !index.isValid() ) { return m_rootNode; } if( isGroup( index ) ) { - //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString(); return m_rootNode; } QModelIndex proxyParent = index.parent(); - //qDebug() << "parent: " << proxyParent; QModelIndex originalParent = mapToSource( proxyParent ); - //qDebug() << "originalParent: " << originalParent; + int originalRow = index.row(); if( originalParent == m_rootNode ) { int indexInGroup = index.row(); if( !proxyParent.isValid() ) indexInGroup -= m_groupMaps.count(); - //qDebug() << "indexInGroup" << indexInGroup; + QList childRows = m_groupHash.value( proxyParent.row() ); if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) return QModelIndex(); originalRow = childRows.at( indexInGroup ); - //qDebug() << "originalRow: " << originalRow; } return sourceModel()->index( originalRow, index.column(), originalParent ); } @@ -674,7 +649,7 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const QModelIndex proxyParent; QModelIndex sourceParent = idx.parent(); - //qDebug() << "sourceParent: " << sourceParent; + int proxyRow = idx.row(); int sourceRow = idx.row(); @@ -708,15 +683,12 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const proxyParent = QModelIndex(); // if the proxy item is not in a group it will be below the groups. int groupLength = m_groupMaps.count(); - //qDebug() << "groupNames length: " << groupLength; int i = m_groupHash.value( std::numeric_limits::max() ).indexOf( sourceRow ); - //qDebug() << "index in hash: " << i; + proxyRow = groupLength + i; } } - //qDebug() << "proxyParent: " << proxyParent; - //qDebug() << "proxyRow: " << proxyRow; return this->index( proxyRow, idx.column(), proxyParent ); } @@ -731,9 +703,9 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const return 0; } + //only if the grouped column has the editable flag set allow the //actions leading to setData on the source (edit & drop) - // qDebug() << idx; if( isGroup( idx ) ) { // dumpGroups(); @@ -749,7 +721,7 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const m_rootNode.parent() ); if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) { - qDebug("row %d is not checkable", originalRow); + log::debug("row {} is not checkable", originalRow); checkable = false; } } @@ -892,9 +864,7 @@ QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int star if( parent != m_rootNode ) { //an item will be added to an original index, remap and pass it on - // qDebug() << parent; QModelIndex proxyParent = mapFromSource( parent ); - // qDebug() << proxyParent; beginInsertRows( proxyParent, start, end ); } } @@ -914,7 +884,12 @@ QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int en { //an item was added to an original index, remap and pass it on QModelIndex proxyParent = mapFromSource( parent ); - qDebug() << proxyParent; + + QString s; + QDebug debug(&s); + debug << proxyParent; + log::debug("{}", s); + //beginInsertRows had to be called in modelRowsAboutToBeInserted() endInsertRows(); } @@ -951,9 +926,7 @@ QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start else { //child item(s) of an original item will be removed, remap and pass it on - // qDebug() << parent; QModelIndex proxyParent = mapFromSource( parent ); - // qDebug() << proxyParent; beginRemoveRows( proxyParent, start, end ); } } @@ -1044,16 +1017,24 @@ QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const void QtGroupingProxy::dumpGroups() const { - qDebug() << "m_groupHash: "; + QString s; + QDebug debug(&s); + + debug << "m_groupHash:\n"; for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ ) { - qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex ); + debug << groupIndex << " : " << m_groupHash.value( groupIndex ) << "\n"; } - qDebug() << "m_groupMaps: "; + debug << "m_groupMaps:\n"; for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) - qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ); - qDebug() << m_groupHash.value( std::numeric_limits::max() ); + { + debug << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ) << "\n"; + } + + debug << m_groupHash.value( std::numeric_limits::max() ); + + log::debug("{}", s); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index e967b27c..0ca39b19 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -150,23 +150,23 @@ void SelfUpdater::testForUpdate() VersionInfo newestVer(newest["tag_name"].toString()); if (newestVer > this->m_MOVersion) { m_UpdateCandidate = newest; - qDebug("update available: %s -> %s", - qUtf8Printable(this->m_MOVersion.displayString(3)), - qUtf8Printable(newestVer.displayString(3))); + log::debug("update available: {} -> {}", + this->m_MOVersion.displayString(3), + newestVer.displayString(3)); emit updateAvailable(); } else if (newestVer < this->m_MOVersion) { // this could happen if the user switches from using prereleases to // stable builds. Should we downgrade? - qDebug("This version is newer than the latest released one: %s -> %s", - qUtf8Printable(this->m_MOVersion.displayString(3)), - qUtf8Printable(newestVer.displayString(3))); + log::debug("This version is newer than the latest released one: {} -> {}", + this->m_MOVersion.displayString(3), + newestVer.displayString(3)); } } }); } //Catch all is bad by design, should be improved catch (...) { - qDebug("Unable to connect to github.com to check version"); + log::debug("Unable to connect to github.com to check version"); } } @@ -230,7 +230,7 @@ void SelfUpdater::closeProgress() void SelfUpdater::openOutputFile(const QString &fileName) { QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; - qDebug("downloading to %s", qUtf8Printable(outputPath)); + log::debug("downloading to {}", outputPath); m_UpdateFile.setFileName(outputPath); m_UpdateFile.open(QIODevice::WriteOnly); } @@ -312,7 +312,7 @@ void SelfUpdater::downloadFinished() return; } - qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + log::debug("download: {}", m_UpdateFile.fileName()); try { installUpdate(); diff --git a/src/settings.cpp b/src/settings.cpp index 9c303442..ff5b9976 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -54,7 +54,6 @@ along with Mod Organizer. If not, see . #include #include // for Qt::UserRole, etc -#include // for qDebug, qWarning #include // For ShellExecuteW, HINSTANCE, etc #include // For storage @@ -635,7 +634,7 @@ void Settings::updateServers(const QList &servers) QVariantMap val = m_Settings.value(key).toMap(); QDate lastSeen = val["lastSeen"].toDate(); if (lastSeen.daysTo(now) > 30) { - qDebug("removing server %s since it hasn't been available for downloads in over a month", qUtf8Printable(key)); + log::debug("removing server {} since it hasn't been available for downloads in over a month", key); m_Settings.remove(key); } } @@ -758,10 +757,10 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { if (first_update) { - qDebug("Changed settings:"); + log::debug("Changed settings:"); first_update = false; } - qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + log::debug(" {}={}", k, m_Settings.value(k).toString()); } m_Settings.endGroup(); } diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 197955b8..5918c8a5 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -60,8 +60,7 @@ LogWorker::LogWorker() "yyyy-MM-dd_hh-mm-ss"))) { m_LogFile.open(QIODevice::WriteOnly); - qDebug("usvfs log messages are written to %s", - qUtf8Printable(m_LogFile.fileName())); + log::debug("usvfs log messages are written to {}", m_LogFile.fileName()); } LogWorker::~LogWorker() @@ -129,7 +128,10 @@ UsvfsConnector::UsvfsConnector() USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); InitLogging(false); - qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath); + log::debug( + "Initializing VFS <{}, {}, {}, {}>", + params.instanceName, static_cast(params.logLevel), + static_cast(params.crashDumpsType), params.crashDumpsPath); CreateVFS(¶ms); @@ -168,7 +170,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) int files = 0; int dirs = 0; - qDebug("Updating VFS mappings..."); + log::debug("Updating VFS mappings..."); ClearVirtualMappings(); @@ -196,14 +198,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } } - qDebug("VFS mappings updated ", dirs, files); - /* - size_t dumpSize = 0; - CreateVFSDump(nullptr, &dumpSize); - std::unique_ptr buffer(new char[dumpSize]); - CreateVFSDump(buffer.get(), &dumpSize); - qDebug(buffer.get()); - */ + log::debug("VFS mappings updated ", dirs, files); } void UsvfsConnector::updateParams( -- cgit v1.3.1 From f49efd6d448dccd4100fa46e2ebf1690d97033cc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 04:54:47 -0400 Subject: replaced formatSystemMessageQ() with formatSystemMessage() replaced windowsErrorString() with formatSystemMessage() --- src/envmetrics.cpp | 6 +++--- src/envmodule.cpp | 14 +++++++------- src/envsecurity.cpp | 20 ++++++++++---------- src/envshortcut.cpp | 4 ++-- src/envwindows.cpp | 4 ++-- src/main.cpp | 2 +- src/mainwindow.cpp | 25 ++++++++++++++++++------- src/organizercore.cpp | 6 ++++-- src/profile.cpp | 7 +++++-- src/settings.cpp | 6 +++--- 10 files changed, 55 insertions(+), 39 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index 784e4baf..b1b9bd2e 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -19,7 +19,7 @@ int getDesktopDpi() if (!dc) { const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + log::error("can't get desktop DC, {}", formatSystemMessage(e)); return 0; } @@ -52,7 +52,7 @@ HMONITOR findMonitor(const QString& name) const auto e = GetLastError(); log::error( "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); + data.name, formatSystemMessage(e)); // error for this monitor, but continue return TRUE; @@ -121,7 +121,7 @@ int getDpi(const QString& monitorDevice) if (FAILED(r)) { log::error( "GetDpiForMonitor() failed for '{}', {}", - monitorDevice, formatSystemMessageQ(r)); + monitorDevice, formatSystemMessage(r)); return 0; } diff --git a/src/envmodule.cpp b/src/envmodule.cpp index aae4e0b1..8cea414a 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -117,7 +117,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoSizeW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -130,7 +130,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -255,7 +255,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't open file '{}' for timestamp, {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -266,7 +266,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't get file time for '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -328,7 +328,7 @@ std::vector getLoadedModules() if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessageQ(e)); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); return {}; } @@ -339,7 +339,7 @@ std::vector getLoadedModules() if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); - log::error("Module32First() failed, {}", formatSystemMessageQ(e)); + log::error("Module32First() failed, {}", formatSystemMessage(e)); return {}; } @@ -358,7 +358,7 @@ std::vector getLoadedModules() // no more modules is not an error if (e != ERROR_NO_MORE_FILES) { - log::error("Module32Next() failed, {}", formatSystemMessageQ(e)); + log::error("Module32Next() failed, {}", formatSystemMessage(e)); } break; diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 015e4000..376be4df 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -58,7 +58,7 @@ public: } if (FAILED(ret)) { - log::error("enum->next() failed, {}", formatSystemMessageQ(ret)); + log::error("enum->next() failed, {}", formatSystemMessage(ret)); break; } @@ -84,7 +84,7 @@ private: if (FAILED(ret) || !rawLocator) { log::error( "CoCreateInstance for WbemLocator failed, {}", - formatSystemMessageQ(ret)); + formatSystemMessage(ret)); throw failed(); } @@ -104,7 +104,7 @@ private: if (FAILED(res) || !rawService) { log::error( "locator->ConnectServer() failed for namespace '{}', {}", - ns, formatSystemMessageQ(res)); + ns, formatSystemMessage(res)); throw failed(); } @@ -120,7 +120,7 @@ private: if (FAILED(ret)) { - log::error("CoSetProxyBlanket() failed, {}", formatSystemMessageQ(ret)); + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret)); throw failed(); } } @@ -139,7 +139,7 @@ private: if (FAILED(ret) || !rawEnumerator) { - log::error("query '{}' failed, {}", query, formatSystemMessageQ(ret)); + log::error("query '{}' failed, {}", query, formatSystemMessage(ret)); return {}; } @@ -250,7 +250,7 @@ std::vector getSecurityProductsFromWMI() // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessageQ(ret)); + log::error("failed to get displayName, {}", formatSystemMessage(ret)); return; } @@ -265,7 +265,7 @@ std::vector getSecurityProductsFromWMI() // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessageQ(ret)); + log::error("failed to get productState, {}", formatSystemMessage(ret)); return; } @@ -286,7 +286,7 @@ std::vector getSecurityProductsFromWMI() // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessageQ(ret)); + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); return; } @@ -349,7 +349,7 @@ std::optional getWindowsFirewall() if (FAILED(hr) || !rawPolicy) { log::error( "CoCreateInstance for NetFwPolicy2 failed, {}", - formatSystemMessageQ(hr)); + formatSystemMessage(hr)); return {}; } @@ -363,7 +363,7 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - log::error("get_FirewallEnabled failed, {}", formatSystemMessageQ(hr)); + log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); return {}; } } diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 1deb9dad..99495c39 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -100,7 +100,7 @@ private: if (FAILED(r)) { throw ShellLinkException(QString("%1, %2") .arg(s) - .arg(formatSystemMessageQ(r))); + .arg(formatSystemMessage(r))); } } @@ -290,7 +290,7 @@ bool Shortcut::remove(Locations loc) log::error( "failed to remove shortcut '{}', {}", - path, formatSystemMessageQ(e)); + path, formatSystemMessage(e)); return false; } diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 8a98036a..3932a9b5 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -210,7 +210,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "OpenProcessToken() failed: {}", formatSystemMessageQ(e)); + "OpenProcessToken() failed: {}", formatSystemMessage(e)); return {}; } @@ -226,7 +226,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "GetTokenInformation() failed: {}", formatSystemMessageQ(e)); + "GetTokenInformation() failed: {}", formatSystemMessage(e)); return {}; } diff --git a/src/main.cpp b/src/main.cpp index 5c5ce945..f53a574e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -464,7 +464,7 @@ void preloadDll(const QString& filename) if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); + log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e502bdb1..8a8a99ef 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4029,7 +4029,8 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - log::error("Move operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); } m_OrganizerCore.refreshModList(); @@ -4058,7 +4059,8 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - log::error("Delete operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); } } } @@ -6819,8 +6821,13 @@ void MainWindow::on_restoreButton_clicked() if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + + const auto e = GetLastError(); + + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } m_OrganizerCore.refreshESPList(true); } @@ -6841,8 +6848,11 @@ void MainWindow::on_restoreModsButton_clicked() QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(formatSystemMessage(e))); } m_OrganizerCore.refreshModList(false); } @@ -6956,7 +6966,8 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - log::error("file operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("file operation failed: {}", formatSystemMessage(e)); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index f6802673..b61ebde8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,7 +354,7 @@ QString OrganizerCore::commitSettings(const QString &iniFile) // make a second attempt using qt functions but if that fails print the // error from the first attempt if (!renameFile(iniFile + ".new", iniFile)) { - return windowsErrorString(err); + return QString::fromStdWString(formatSystemMessage(err)); } } return QString(); @@ -387,10 +387,12 @@ void OrganizerCore::storeSettings() + QString::fromStdWString(AppConfig::iniFileName()); if (QFileInfo(iniFile).exists()) { if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { + const auto e = GetLastError(); QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile, windowsErrorString(::GetLastError()))); + .arg(iniFile) + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } } diff --git a/src/profile.cpp b/src/profile.cpp index 27616986..6de1b097 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -265,7 +265,10 @@ void Profile::createTweakedIniFile() QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + reportError( + tr("failed to update tweaked ini file, wrong settings may be used: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } @@ -287,7 +290,7 @@ void Profile::createTweakedIniFile() if (error) { const auto e = ::GetLastError(); reportError(tr("failed to create tweaked ini: %1") - .arg(formatSystemMessageQ(e))); + .arg(QString::fromStdWString(formatSystemMessage(e)))); } log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); diff --git a/src/settings.cpp b/src/settings.cpp index ff5b9976..5ad066b2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -220,7 +220,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessageQ(e)); + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); } } delete[] keyData; @@ -365,7 +365,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessageQ(e)); + log::error("Storing API key failed: {}", formatSystemMessage(e)); return false; } @@ -486,7 +486,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessageQ(e)); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); } } -- cgit v1.3.1 From 3c7712a32dd5079a9543485b6a85d548460faefd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 05:34:31 -0400 Subject: moved setLogLevel() to OrganizerCore moved context menu to LogList --- src/loglist.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++-------- src/loglist.h | 16 +++++++++----- src/mainwindow.cpp | 57 ++--------------------------------------------- src/mainwindow.h | 4 ---- src/mainwindow.ui | 14 ------------ src/organizercore.cpp | 12 ++++++++++ src/organizercore.h | 2 ++ 7 files changed, 78 insertions(+), 88 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loglist.cpp b/src/loglist.cpp index c34ac76e..26aea682 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -18,14 +18,7 @@ along with Mod Organizer. If not, see . */ #include "loglist.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "organizercore.h" using namespace MOBase; @@ -158,7 +151,7 @@ QVariant LogModel::headerData(int, Qt::Orientation, int) const LogList::LogList(QWidget* parent) - : QTreeView(parent) + : QTreeView(parent), m_core(nullptr) { setModel(&LogModel::instance()); @@ -171,6 +164,10 @@ LogList::LogList(QWidget* parent) setAutoScroll(true); scrollToBottom(); + connect( + this, &QWidget::customContextMenuRequested, + [&](auto&& pos){ onContextMenu(pos); }); + connect( model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(scrollToBottom())); @@ -180,6 +177,11 @@ LogList::LogList(QWidget* parent) this, SLOT(scrollToBottom())); } +void LogList::setCore(OrganizerCore& core) +{ + m_core = &core; +} + void LogList::copyToClipboard() { std::string s; @@ -196,3 +198,44 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } + +QMenu* LogList::createMenu(QWidget* parent) +{ + auto* menu = new QMenu(parent); + + menu->addAction(tr("Copy& Log"), [&]{ copyToClipboard(); }); + menu->addSeparator(); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + + connect(a, &QAction::triggered, [this, level]{ + if (m_core) { + m_core->setLogLevel(level); + } + }); + + levels->addAction(a); + }; + + addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); + + return menu; +} + +void LogList::onContextMenu(const QPoint& pos) +{ + auto* menu = createMenu(this); + menu->popup(viewport()->mapToGlobal(pos)); +} diff --git a/src/loglist.h b/src/loglist.h index d1f7a2ad..ae827ca7 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -20,14 +20,11 @@ along with Mod Organizer. If not, see . #ifndef LOGBUFFER_H #define LOGBUFFER_H -#include -#include -#include -#include -#include -#include +#include #include +class OrganizerCore; + class LogModel : public QAbstractItemModel { Q_OBJECT @@ -65,7 +62,14 @@ class LogList : public QTreeView public: LogList(QWidget* parent=nullptr); + void setCore(OrganizerCore& core); + void copyToClipboard(); + QMenu* createMenu(QWidget* parent=nullptr); + +private: + OrganizerCore* m_core; + void onContextMenu(const QPoint& pos); }; #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8a8a99ef..761e9843 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,6 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" @@ -358,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - setupLogMenu(); + ui->logList->setCore(m_OrganizerCore); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -813,36 +812,6 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } -void MainWindow::setupLogMenu() -{ - connect(ui->logList, &QWidget::customContextMenuRequested, [&](auto&& pos){ - auto* menu = new QMenu(ui->logList); - - menu->addAction(tr("Copy& Log"), [&]{ ui->logList->copyToClipboard(); }); - menu->addSeparator(); - - auto* levels = new QMenu(tr("&Level")); - menu->addMenu(levels); - - auto* ag = new QActionGroup(menu); - - auto addAction = [&](auto&& text, auto&& level) { - auto* a = new QAction(text, ag); - a->setCheckable(true); - a->setChecked(log::getDefault().level() == level); - connect(a, &QAction::triggered, [this, level]{ setLogLevel(level); }); - levels->addAction(a); - }; - - addAction(tr("&Debug"), log::Debug); - addAction(tr("&Info"), log::Info); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Errors"), log::Error); - - menu->popup(ui->logList->viewport()->mapToGlobal(pos)); - }); -} - void MainWindow::updatePinnedExecutables() { for (auto* a : ui->toolBar->actions()) { @@ -1432,11 +1401,6 @@ bool MainWindow::confirmExit() void MainWindow::cleanup() { - if (ui->logList->model() != nullptr) { - disconnect(ui->logList->model(), nullptr, nullptr, nullptr); - ui->logList->setModel(nullptr); - } - QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); m_IntegratedBrowser.close(); m_SaveMetaTimer.stop(); @@ -5317,24 +5281,12 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } -void MainWindow::setLogLevel(log::Levels level) -{ - auto& s = m_OrganizerCore.settings(); - - s.setLogLevel(level); - - m_OrganizerCore.updateVFSParams( - s.logLevel(), s.crashDumpsType(), s.executablesBlacklist()); - - log::getDefault().setLevel(s.logLevel()); -} - void MainWindow::on_actionNexus_triggered() { const IPluginGame *game = m_OrganizerCore.managedGame(); @@ -6858,11 +6810,6 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_actionLogCopy_triggered() -{ - ui->logList->copyToClipboard(); -} - void MainWindow::on_categoriesAndBtn_toggled(bool checked) { if (checked) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 74993667..aa49205d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,13 +636,10 @@ private slots: void search_activated(); void searchClear_activated(); - void setupLogMenu(); void resetActionIcons(); void updateModCount(); void updatePluginCount(); - void setLogLevel(MOBase::log::Levels level); - private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); @@ -696,7 +693,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionLogCopy_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index fc2bcdd3..6c6d0bca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1757,20 +1757,6 @@ p, li { white-space: pre-wrap; } Log - - - Copy &Log - - - Copy &Log - - - Copy log to clipboard - - - Copy log to clipboard - - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 92372d82..1e164525 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -731,6 +731,18 @@ void OrganizerCore::updateVFSParams( m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } +void OrganizerCore::setLogLevel(log::Levels level) +{ + m_Settings.setLogLevel(level); + + updateVFSParams( + m_Settings.logLevel(), + m_Settings.crashDumpsType(), + m_Settings.executablesBlacklist()); + + log::getDefault().setLevel(m_Settings.logLevel()); +} + bool OrganizerCore::cycleDiagnostics() { if (int maxDumps = settings().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); diff --git a/src/organizercore.h b/src/organizercore.h index c368d101..2aa7e707 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -196,6 +196,8 @@ public: MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist); + void setLogLevel(MOBase::log::Levels level); + bool cycleDiagnostics(); static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } -- cgit v1.3.1 From 107b396902be52f8ae305f58d4e7d85a86779051 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 11:12:28 -0400 Subject: moved tabs to SettingsDialog removed Settings::query(), main window now deals with SettingsDialog directly --- src/mainwindow.cpp | 5 +- src/settings.cpp | 100 ---------------------------------------- src/settings.h | 55 +--------------------- src/settingsdialog.cpp | 105 +++++++++++++++++++++++++++++++++++++++++- src/settingsdialog.h | 26 +++++++++-- src/settingsdialogplugins.cpp | 2 + 6 files changed, 133 insertions(+), 160 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7c73bc8a..28405819 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -72,6 +72,7 @@ along with Mod Organizer. If not, see . #include "previewdialog.h" #include "browserdialog.h" #include "aboutdialog.h" +#include "settingsdialog.h" #include #include "nxmaccessmanager.h" #include "appconfig.h" @@ -5217,7 +5218,9 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); - settings.query(&m_PluginContainer, this); + + SettingsDialog dialog(&m_PluginContainer, &settings, this); + dialog.exec(); if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { QMessageBox::about(this, tr("Restarting MO"), diff --git a/src/settings.cpp b/src/settings.cpp index 725b7e06..dc07e107 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,14 +21,6 @@ along with Mod Organizer. If not, see . #include "pluginsetting.h" #include "serverinfo.h" -#include "settingsdialog.h" -#include "settingsdialogdiagnostics.h" -#include "settingsdialoggeneral.h" -#include "settingsdialognexus.h" -#include "settingsdialogpaths.h" -#include "settingsdialogplugins.h" -#include "settingsdialogsteam.h" -#include "settingsdialogworkarounds.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -75,23 +67,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : m_parent(m_parent) - , m_Settings(m_parent->settingsRef()) - , m_dialog(m_dialog) - , ui(m_dialog.ui) -{ -} - -SettingsTab::~SettingsTab() -{} - -QWidget* SettingsTab::parentWidget() -{ - return &m_dialog; -} - - Settings *Settings::s_Instance = nullptr; @@ -668,78 +643,3 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } - -void Settings::query(PluginContainer *pluginContainer, QWidget *parent) -{ - SettingsDialog dialog(pluginContainer, this, parent); - - std::vector> tabs; - - tabs.push_back(std::unique_ptr(new GeneralSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PathsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PluginsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(this, dialog))); - - - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (m_Settings.contains(key)) { - dialog.restoreGeometry(m_Settings.value(key).toByteArray()); - } - - if (dialog.exec() == QDialog::Accepted) { - - for (auto&& tab : tabs) { - tab->closing(); - } - - // remember settings before change - QMap before; - m_Settings.beginGroup("Settings"); - for (auto k : m_Settings.allKeys()) - before[k] = m_Settings.value(k).toString(); - m_Settings.endGroup(); - - // transfer modified settings to configuration file - for (std::unique_ptr const &tab: tabs) { - tab->update(); - } - - // print "changed" settings - m_Settings.beginGroup("Settings"); - bool first_update = true; - for (auto k : m_Settings.allKeys()) - if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) - { - if (first_update) { - log::debug("Changed settings:"); - first_update = false; - } - log::debug(" {}={}", k, m_Settings.value(k).toString()); - } - m_Settings.endGroup(); - } - m_Settings.setValue(key, dialog.saveGeometry()); - - // These changes happen regardless of accepted or rejected - bool restartNeeded = false; - if (dialog.getApiKeyChanged()) { - restartNeeded = true; - } - if (dialog.getResetGeometries()) { - restartNeeded = true; - m_Settings.setValue("reset_geometry", true); - } - if (restartNeeded) { - if (QMessageBox::question(nullptr, - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - -} diff --git a/src/settings.h b/src/settings.h index 71fbcbc1..899baaa3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -26,60 +26,21 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include #include #include -#include - -#include //for uint - #include #include -class QCheckBox; -class QComboBox; -class QLineEdit; -class QSpinBox; -class QListWidget; -class QWidget; -class QLabel; -class QPushButton; - -struct ServerInfo; - namespace MOBase { class IPlugin; class IPluginGame; } -namespace Ui { - class SettingsDialog; -} - -class SettingsDialog; class PluginContainer; -class Settings; - -class SettingsTab -{ -public: - SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - virtual ~SettingsTab(); - - virtual void update() = 0; - virtual void closing() {} - -protected: - Settings *m_parent; - QSettings &m_Settings; - SettingsDialog &m_dialog; - Ui::SettingsDialog* ui; - - QWidget* parentWidget(); -}; +struct ServerInfo; /** * manages the settings for Mod Organizer. The settings are not cached @@ -87,17 +48,11 @@ protected: **/ class Settings : public QObject { - Q_OBJECT public: - - /** - * @brief constructor - **/ Settings(const QSettings &settingsSource); - - virtual ~Settings(); + ~Settings(); static Settings &instance(); @@ -113,12 +68,6 @@ public: */ void registerPlugin(MOBase::IPlugin *plugin); - /** - * displays a SettingsDialog that allows the user to change settings. If the - * user accepts the changes, the settings are immediately written - **/ - void query(PluginContainer *pluginContainer, QWidget *parent); - /** * set up the settings for the specified plugins **/ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 76b0a146..8c5b2678 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -29,6 +29,14 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "plugincontainer.h" +#include "settingsdialogdiagnostics.h" +#include "settingsdialoggeneral.h" +#include "settingsdialognexus.h" +#include "settingsdialogpaths.h" +#include "settingsdialogplugins.h" +#include "settingsdialogsteam.h" +#include "settingsdialogworkarounds.h" + #include #include @@ -47,7 +55,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; - SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) @@ -57,7 +64,84 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , m_keyChanged(false) { ui->setupUi(this); - ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); + + m_tabs.push_back(std::unique_ptr(new GeneralSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new SteamSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new PluginsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); + + auto& qsettings = settings->directInterface(); + + QString key = QString("geometry/%1").arg(objectName()); + if (qsettings.contains(key)) { + restoreGeometry(qsettings.value(key).toByteArray()); + } +} + +int SettingsDialog::exec() +{ + auto& qsettings = m_settings->directInterface(); + auto ret = TutorableDialog::exec(); + + if (ret == QDialog::Accepted) { + + for (auto&& tab : m_tabs) { + tab->closing(); + } + + // remember settings before change + QMap before; + qsettings.beginGroup("Settings"); + for (auto k : qsettings.allKeys()) + before[k] = qsettings.value(k).toString(); + qsettings.endGroup(); + + // transfer modified settings to configuration file + for (std::unique_ptr const &tab: m_tabs) { + tab->update(); + } + + // print "changed" settings + qsettings.beginGroup("Settings"); + bool first_update = true; + for (auto k : qsettings.allKeys()) + if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) + { + if (first_update) { + qDebug("Changed settings:"); + first_update = false; + } + qDebug(" %s=%s", k.toUtf8().data(), qsettings.value(k).toString().toUtf8().data()); + } + qsettings.endGroup(); + } + + QString key = QString("geometry/%1").arg(objectName()); + qsettings.setValue(key, saveGeometry()); + + // These changes happen regardless of accepted or rejected + bool restartNeeded = false; + if (getApiKeyChanged()) { + restartNeeded = true; + } + if (getResetGeometries()) { + restartNeeded = true; + qsettings.setValue("reset_geometry", true); + } + if (restartNeeded) { + if (QMessageBox::question(nullptr, + tr("Restart Mod Organizer?"), + tr("In order to finish configuration changes, MO must be restarted.\n" + "Restart it now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + qApp->exit(INT_MAX); + } + } + + return ret; } SettingsDialog::~SettingsDialog() @@ -107,3 +191,20 @@ bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; } + + +SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->settingsRef()) + , m_dialog(m_dialog) + , ui(m_dialog.ui) +{ +} + +SettingsTab::~SettingsTab() +{} + +QWidget* SettingsTab::parentWidget() +{ + return &m_dialog; +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 81c17f44..f2367315 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -27,10 +27,26 @@ along with Mod Organizer. If not, see . class PluginContainer; class Settings; +class SettingsDialog; +namespace Ui { class SettingsDialog; } -namespace Ui { - class SettingsDialog; -} +class SettingsTab +{ +public: + SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + virtual ~SettingsTab(); + + virtual void update() = 0; + virtual void closing() {} + +protected: + Settings *m_parent; + QSettings &m_Settings; + SettingsDialog &m_dialog; + Ui::SettingsDialog* ui; + + QWidget* parentWidget(); +}; /** @@ -60,6 +76,8 @@ public: bool m_GeometriesReset; PluginContainer *m_PluginContainer; + int exec() override; + public slots: virtual void accept(); @@ -69,7 +87,7 @@ public: private: Settings* m_settings; - + std::vector> m_tabs; }; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 33bc1563..53b28fcc 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -8,6 +8,8 @@ using MOBase::IPlugin; PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) { + ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); + // display plugin settings QSet handledNames; for (IPlugin *plugin : m_parent->plugins()) { -- cgit v1.3.1 From b6b01a52db1877b16531137289641fb9be9833aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 23:24:27 -0400 Subject: removed mentions of QSettings from main.cpp added necessary member functions in Settings --- src/filedialogmemory.cpp | 1 + src/filedialogmemory.h | 1 + src/main.cpp | 185 +++++++++++++++++++++-------------------------- src/mainwindow.cpp | 6 +- src/mainwindow.h | 3 +- src/organizercore.cpp | 4 +- src/organizercore.h | 2 +- src/settings.cpp | 77 +++++++++++++++++++- src/settings.h | 22 +++++- 9 files changed, 188 insertions(+), 113 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 0e3e9793..308a175e 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "filedialogmemory.h" +#include "settings.h" #include diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 81d7ba40..1a72b289 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include #include +class Settings; class FileDialogMemory { diff --git a/src/main.cpp b/src/main.cpp index 911f11c3..720ecbf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -245,9 +245,10 @@ static bool HaveWriteAccess(const std::wstring &path) } -QString determineProfile(QStringList &arguments, const QSettings &settings) +QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + QString selectedProfileName = settings.getSelectedProfileName(); + { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { @@ -257,6 +258,7 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } + if (selectedProfileName.isEmpty()) { log::debug("no configured profile"); selectedProfileName = "Default"; @@ -267,46 +269,50 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) return selectedProfileName; } -MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +MOBase::IPluginGame *selectGame( + Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setValue("gameName", game->gameName()); - //Sadly, hookdll needs gamePath in order to run. So following code block is - //commented out - /*if (gamePath == game->gameDirectory()) { - settings.remove("gamePath"); - } else*/ { - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); - } - return game; //Woot + settings.setManagedGameName(game->gameName()); + + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + + settings.setManagedGameDirectory(gameDir); + + return game; } -MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) +MOBase::IPluginGame *determineCurrentGame( + QString const &moPath, Settings &settings, PluginContainer const &plugins) { //Determine what game we are running where. Be very paranoid in case the //user has done something odd. //If the game name has been set up, try to use that. - QString gameName = settings.value("gameName", "").toString(); + const QString gameName = settings.getManagedGameName(); bool gameConfigured = !gameName.isEmpty(); + if (gameConfigured) { MOBase::IPluginGame *game = plugins.managedGame(gameName); if (game == nullptr) { reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); return nullptr; } - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + + QString gamePath = settings.getManagedGameDirectory(); if (gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } + QDir gameDir(gamePath); QFileInfo directoryInfo(gameDir.path()); + if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); } + if (game->looksValid(gameDir)) { return selectGame(settings, gameDir, game); } @@ -315,7 +321,7 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + const QString gamePath = settings.getManagedGameDirectory(); reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). arg(gameName).arg(gamePath)); } @@ -480,27 +486,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpSettings(QSettings& settings) -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" - }); - - log::debug("settings:"); - - settings.beginGroup("Settings"); - - for (auto k : settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } - - log::debug(" . {}={}", k, settings.value(k).toString()); - } - - settings.endGroup(); -} - void checkMissingFiles() { // files that are likely to be eaten @@ -557,7 +542,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::warn("no ssl support"); } - QString dataPath = application.property("dataPath").toString(); + const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); if (!bootstrap()) { @@ -573,11 +558,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { log::info("working directory: {}", QDir::currentPath()); - QSettings initSettings( - dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); - - Settings settings(initSettings); + Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.logLevel()); // global crashDumpType sits in OrganizerCore to make a bit less ugly to @@ -587,7 +568,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, env::Environment env; env.dump(); - dumpSettings(initSettings); + settings.dump(); sanityChecks(env); log::debug("initializing core"); @@ -602,7 +583,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), initSettings, pluginContainer); + application.applicationDirPath(), settings, pluginContainer); + if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -612,6 +594,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } return 1; } + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash @@ -625,7 +608,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!initSettings.contains("game_edition")) { + if (settings.getManagedGameEdition() == "") { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -641,78 +624,76 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - initSettings.setValue("game_edition", selection.getChoiceString()); + settings.setManagedGameEdition(selection.getChoiceString()); } } } - game->setGameVariant(initSettings.value("game_edition").toString()); + + game->setGameVariant(settings.getManagedGameEdition()); log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(initSettings); + organizer.updateExecutablesList(); - QString selectedProfileName = determineProfile(arguments, initSettings); + QString selectedProfileName = determineProfile(arguments, settings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - if (shortcut.hasExecutable()) { - try { - organizer.runShortcut(shortcut); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } - else if (OrganizerCore::isNxmLink(arguments.at(1))) { - log::debug("starting download from command line: {}", arguments.at(1)); - organizer.externalMessage(arguments.at(1)); - } - else { - QString exeName = arguments.at(1); - log::debug("starting {} from command line", exeName); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + if (shortcut.hasExecutable()) { + try { + organizer.runShortcut(shortcut); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } + else if (OrganizerCore::isNxmLink(arguments.at(1))) { + log::debug("starting download from command line: {}", arguments.at(1)); + organizer.externalMessage(arguments.at(1)); + } + else { + QString exeName = arguments.at(1); + log::debug("starting {} from command line", exeName); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + } QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (initSettings.contains("window_monitor")) { - const int monitor = initSettings.value("window_monitor").toInt(); - - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); - splash.move(center - splash.rect().center()); - } else { - const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); - splash.move(center - splash.rect().center()); - } + const int monitor = settings.getMainWindowMonitor(); + if (monitor != -1 && QGuiApplication::screens().size() > monitor) { + QGuiApplication::screens().at(monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + splash.move(center - splash.rect().center()); + } else { + const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); + splash.move(center - splash.rect().center()); } splash.show(); splash.activateWindow(); QString apiKey; - if (organizer.settings().getNexusApiKey(apiKey)) { + if (settings.getNexusApiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -722,15 +703,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(settings.getStyleName())) { // disable invalid stylesheet - initSettings.setValue("Settings/style", ""); + settings.setStyleName(""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(initSettings, organizer, pluginContainer); + MainWindow mainWindow(settings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28405819..7f7ded80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -291,7 +291,7 @@ public: }; -MainWindow::MainWindow(QSettings &initSettings +MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer , QWidget *parent) @@ -540,8 +540,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); + setCategoryListVisible(settings.isCategoryListVisible()); + FileDialogMemory::restore(settings.directInterface()); fixCategories(); diff --git a/src/mainwindow.h b/src/mainwindow.h index aa49205d..7326425a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -103,6 +103,7 @@ namespace Ui { class MainWindow; } +class Settings; class MainWindow : public QMainWindow, public IUserInterface @@ -113,7 +114,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: - explicit MainWindow(QSettings &initSettings, + explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); ~MainWindow(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1e164525..72c8dab5 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -480,14 +480,14 @@ return true; } -void OrganizerCore::updateExecutablesList(QSettings &settings) +void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { log::error("can't update executables list now"); return; } - m_ExecutablesList.load(managedGame(), settings); + m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 2aa7e707..926a21f0 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -107,7 +107,7 @@ public: void setManagedGame(MOBase::IPluginGame *game); - void updateExecutablesList(QSettings &settings); + void updateExecutablesList(); void startMOUpdate(); diff --git a/src/settings.cpp b/src/settings.cpp index 77db6918..5d103267 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,8 +28,8 @@ using namespace MOBase; Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QSettings &settingsSource) - : m_Settings(settingsSource.fileName(), settingsSource.format()) +Settings::Settings(const QString& path) + : m_Settings(path, QSettings::IniFormat) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -280,7 +280,57 @@ QString Settings::getModDirectory(bool resolve) const QString Settings::getManagedGameDirectory() const { - return m_Settings.value("gamePath", "").toString(); + return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); +} + +void Settings::setManagedGameDirectory(const QString& path) +{ + m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); +} + +QString Settings::getManagedGameName() const +{ + return m_Settings.value("gameName", "").toString(); +} + +void Settings::setManagedGameName(const QString& name) +{ + m_Settings.setValue("gameName", name); +} + +QString Settings::getManagedGameEdition() const +{ + return m_Settings.value("game_edition", "").toString(); +} + +void Settings::setManagedGameEdition(const QString& name) +{ + m_Settings.setValue("game_edition", name); +} + +QString Settings::getSelectedProfileName() const +{ + return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); +} + +int Settings::getMainWindowMonitor() const +{ + return m_Settings.value("window_monitor", -1).toInt(); +} + +QString Settings::getStyleName() const +{ + return m_Settings.value("Settings/style", "").toString(); +} + +void Settings::setStyleName(const QString& name) +{ + m_Settings.setValue("Settings/style", name); +} + +bool Settings::isCategoryListVisible() const +{ + return m_Settings.value("categorylist_visible", true).toBool(); } QString Settings::getProfileDirectory(bool resolve) const @@ -608,3 +658,24 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } + +void Settings::dump() const +{ + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); + + log::debug("settings:"); + + m_Settings.beginGroup("Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } + + m_Settings.endGroup(); +} diff --git a/src/settings.h b/src/settings.h index 63718089..f06aece9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,7 +40,7 @@ class Settings : public QObject Q_OBJECT public: - Settings(const QSettings &settingsSource); + Settings(const QString& path); ~Settings(); static Settings &instance(); @@ -123,6 +123,24 @@ public: * retrieve the directory where the managed game is stored (with native separators) **/ QString getManagedGameDirectory() const; + void setManagedGameDirectory(const QString& path); + + QString getManagedGameName() const; + void setManagedGameName(const QString& name); + + QString getManagedGameEdition() const; + void setManagedGameEdition(const QString& name); + + QString getSelectedProfileName() const; + + // returns -1 if not set + // + int getMainWindowMonitor() const; + + QString getStyleName() const; + void setStyleName(const QString& name); + + bool isCategoryListVisible() const; /** * retrieve the directory where profiles stored (with native separators) @@ -370,6 +388,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + void dump() const; + // temp QMap m_PluginSettings; QMap m_PluginDescriptions; -- cgit v1.3.1 From 07f1ac7a96dcf4c91a24bb1d30af92851ecda78f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 01:55:21 -0400 Subject: split into GeometrySettings removed most of storeSettings() from OrganizerCore: QSettings handles saving by itself, no need for that removed topLevelSplitter from ui, unused since the log widget is in a dock removed QSettings from MainWindow::readSettings() replaced return values for some of the new getters in Settings to std::optional --- src/executableslist.cpp | 9 ++- src/executableslist.h | 5 +- src/filedialogmemory.cpp | 8 ++- src/filedialogmemory.h | 5 +- src/iuserinterface.h | 4 +- src/main.cpp | 96 +++++++++++++++---------- src/mainwindow.cpp | 116 ++++++++++++++---------------- src/mainwindow.h | 4 +- src/mainwindow.ui | 5 -- src/organizercore.cpp | 90 +++++------------------- src/organizercore.h | 4 -- src/settings.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++---- src/settings.h | 55 ++++++++++++--- 13 files changed, 359 insertions(+), 222 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 3f76bb6f..2b3219df 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "utility.h" +#include "settings.h" #include #include @@ -64,7 +65,7 @@ bool ExecutablesList::empty() const return m_Executables.empty(); } -void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) +void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) { log::debug("loading executables"); @@ -74,6 +75,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; + auto& settings = const_cast(s.directInterface()); + int numCustomExecutables = settings.beginReadArray("customExecutables"); for (int i = 0; i < numCustomExecutables; ++i) { settings.setArrayIndex(i); @@ -108,8 +111,10 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) dump(); } -void ExecutablesList::store(QSettings& settings) +void ExecutablesList::store(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("customExecutables"); settings.beginWriteArray("customExecutables"); diff --git a/src/executableslist.h b/src/executableslist.h index eda2034e..23cf3cfe 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include namespace MOBase { class IPluginGame; class ExecutableInfo; } +class Settings; /*! * @brief Information about an executable @@ -103,7 +104,7 @@ public: /** * @brief initializes the list from the settings and the given plugin **/ - void load(const MOBase::IPluginGame* game, QSettings& settings); + void load(const MOBase::IPluginGame* game, const Settings& settings); /** * @brief re-adds all the executables from the plugin and renames existing @@ -114,7 +115,7 @@ public: /** * @brief writes the current list to the settings */ - void store(QSettings& settings); + void store(Settings& settings); /** * @brief get an executable by name diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 308a175e..48828563 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -27,8 +27,10 @@ FileDialogMemory::FileDialogMemory() } -void FileDialogMemory::save(QSettings &settings) +void FileDialogMemory::save(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("recentDirectories"); settings.beginWriteArray("recentDirectories"); int index = 0; @@ -42,8 +44,10 @@ void FileDialogMemory::save(QSettings &settings) } -void FileDialogMemory::restore(QSettings &settings) +void FileDialogMemory::restore(const Settings& s) { + auto& settings = const_cast(s.directInterface()); + int size = settings.beginReadArray("recentDirectories"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 1a72b289..d214a8e6 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include #include -#include #include class Settings; @@ -31,8 +30,8 @@ class Settings; class FileDialogMemory { public: - static void save(QSettings &settings); - static void restore(QSettings &settings); + static void save(Settings& settings); + static void restore(const Settings& settings); static QString getOpenFileName( const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), diff --git a/src/iuserinterface.h b/src/iuserinterface.h index bba8de2b..7205f982 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,13 +10,13 @@ #include -class QSettings; +class Settings; class IUserInterface { public: - virtual void storeSettings(QSettings &settings) = 0; + virtual void storeSettings(Settings &settings) = 0; virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; virtual void registerPluginTools(std::vector toolPlugins) = 0; diff --git a/src/main.cpp b/src/main.cpp index 720ecbf9..3e26ea17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,7 +62,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -116,7 +115,7 @@ bool bootstrap() shellDelete(QStringList(backupDirectory)); } - // cycle logfile + // cycle log file removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), "usvfs*.log", 5, QDir::Name); @@ -247,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.getSelectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -259,14 +258,14 @@ QString determineProfile(QStringList &arguments, const Settings &settings) arguments.removeAt(profileIndex); } - if (selectedProfileName.isEmpty()) { + if (!selectedProfileName) { log::debug("no configured profile"); selectedProfileName = "Default"; } else { - log::debug("configured profile: {}", selectedProfileName); + log::debug("configured profile: {}", *selectedProfileName); } - return selectedProfileName; + return *selectedProfileName; } MOBase::IPluginGame *selectGame( @@ -290,27 +289,27 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const QString gameName = settings.getManagedGameName(); - bool gameConfigured = !gameName.isEmpty(); + const auto gameName = settings.getManagedGameName(); + const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(gameName); + MOBase::IPluginGame *game = plugins.managedGame(*gameName); if (game == nullptr) { - reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); + reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(*gameName)); return nullptr; } - QString gamePath = settings.getManagedGameDirectory(); - if (gamePath == "") { + auto gamePath = settings.getManagedGameDirectory(); + if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } - QDir gameDir(gamePath); + QDir gameDir(*gamePath); QFileInfo directoryInfo(gameDir.path()); if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); } if (game->looksValid(gameDir)) { @@ -321,17 +320,20 @@ MOBase::IPluginGame *determineCurrentGame( //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - const QString gamePath = settings.getManagedGameDirectory(); - reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). - arg(gameName).arg(gamePath)); + const auto gamePath = settings.getManagedGameDirectory(); + + reportError( + QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") + .arg(*gameName).arg(gamePath ? *gamePath : "")); } - SelectionDialog selection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + SelectionDialog selection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); for (IPluginGame *game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only add games that are installed @@ -355,9 +357,11 @@ MOBase::IPluginGame *determineCurrentGame( return selectGame(settings, game->gameDirectory(), game); } - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + QString(), QFileDialog::ShowDirsOnly); + if (!gamePath.isEmpty()) { QDir gameDir(gamePath); QFileInfo directoryInfo(gamePath); @@ -368,7 +372,7 @@ MOBase::IPluginGame *determineCurrentGame( QList possibleGames; for (IPluginGame * const game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only try plugins that look valid for this directory @@ -376,24 +380,31 @@ MOBase::IPluginGame *determineCurrentGame( possibleGames.append(game); } } + if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); + SelectionDialog browseSelection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + nullptr, QSize(32, 32)); + for (IPluginGame *game : possibleGames) { browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); } + if (browseSelection.exec() == QDialog::Accepted) { return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); } else { - reportError(gameConfigured ? QObject::tr("Canceled finding %1 in \"%2\".").arg(gameName).arg(gamePath) - : QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); + reportError(gameConfigured ? + QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : + QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); } } else if(possibleGames.count() == 1) { return selectGame(settings, gameDir, possibleGames[0]); } else { if (gameConfigured) { - reportError(QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.").arg(gameName).arg(gamePath)); + reportError( + QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") + .arg(*gameName).arg(gamePath)); } else { QString supportedGames; @@ -608,7 +619,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (settings.getManagedGameEdition() == "") { + QString edition; + + if (auto v=settings.getManagedGameEdition()) { + edition = *v; + } else { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -624,12 +639,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setManagedGameEdition(selection.getChoiceString()); + edition = selection.getChoiceString(); + settings.setManagedGameEdition(edition); } } } - game->setGameVariant(settings.getManagedGameEdition()); + Q_ASSERT(!edition.isEmpty()); + + game->setGameVariant(edition); log::info("managing game at {}", game->gameDirectory().absolutePath()); @@ -679,10 +697,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - const int monitor = settings.getMainWindowMonitor(); - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + const auto monitor = settings.geometry().getMainWindowMonitor(); + if (monitor && QGuiApplication::screens().size() > *monitor) { + QGuiApplication::screens().at(*monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(*monitor)->geometry().center(); splash.move(center - splash.rect().center()); } else { const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); @@ -703,7 +721,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName())) { + if (!application.setStyleFile(settings.getStyleName().value_or(""))) { // disable invalid stylesheet settings.setStyleName(""); } @@ -726,7 +744,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(); + mainWindow.readSettings(settings); log::debug("displaying main window"); mainWindow.show(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7f7ded80..e77d08b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -216,26 +216,24 @@ const QSize LargeToolbarSize(42, 36); class DockFixer { public: - static void save(MainWindow* mw, QSettings& settings) + static void save(MainWindow* mw, Settings& settings) { - const auto docks = mw->findChildren(); - // saves the size of each dock - for (int i=0; ifindChildren()) { int size = 0; // save the width for horizontal docks, or the height for vertical - if (orientation(mw, docks[i]) == Qt::Horizontal) { - size = docks[i]->size().width(); + if (orientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); } else { - size = docks[i]->size().height(); + size = dock->size().height(); } - settings.setValue(settingName(docks[i]), size); + settings.geometry().setDockSize(dock->objectName(), size); } } - static void restore(MainWindow* mw, const QSettings& settings) + static void restore(MainWindow* mw, const Settings& settings) { struct DockInfo { @@ -246,16 +244,11 @@ public: std::vector dockInfos; - const auto docks = mw->findChildren(); - // for each dock - for (int i=0; ifindChildren()) { + if (auto size=settings.geometry().getDockSize(dock->objectName())) { // remember this dock, its size and orientation - const auto size = settings.value(name).toInt(); - dockInfos.push_back({docks[i], size, orientation(mw, docks[i])}); + dockInfos.push_back({dock, *size, orientation(mw, dock)}); } } @@ -264,30 +257,25 @@ public: // // some people said a single processEvents() call is enough, but it doesn't // look like it - QTimer::singleShot(1, [=] { + QTimer::singleShot(5, [=] { for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } - static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + static Qt::Orientation orientation(QMainWindow* mw, const QDockWidget* d) { // docks in these areas are horizontal const auto horizontalAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; - if (mw->dockWidgetArea(d) & horizontalAreas) { + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { return Qt::Horizontal; } else { return Qt::Vertical; } } - - static QString settingName(QDockWidget* d) - { - return "geometry/" + d->objectName() + "_size"; - } }; @@ -359,9 +347,6 @@ MainWindow::MainWindow(Settings &settings ui->logList->setCore(m_OrganizerCore); - int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value - ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - updateProblemsButton(); setupToolbar(); @@ -540,8 +525,7 @@ MainWindow::MainWindow(Settings &settings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(settings.isCategoryListVisible()); - FileDialogMemory::restore(settings.directInterface()); + FileDialogMemory::restore(settings); fixCategories(); @@ -2247,52 +2231,50 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings() +void MainWindow::readSettings(const Settings& settings) { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - - if (settings.contains("window_geometry")) { - restoreGeometry(settings.value("window_geometry").toByteArray()); + if (auto v=settings.geometry().getMainWindow()) { + restoreGeometry(*v); } - if (settings.contains("window_state")) { - restoreState(settings.value("window_state").toByteArray()); + if (auto v=settings.geometry().getMainWindowState()) { + restoreState(*v); } - if (settings.contains("toolbar_size")) { - setToolbarSize(settings.value("toolbar_size").toSize()); + if (auto v=settings.geometry().getToolbarSize()) { + setToolbarSize(*v); } - if (settings.contains("toolbar_button_style")) { - setToolbarButtonStyle(static_cast( - settings.value("toolbar_button_style").toInt())); + if (auto v=settings.geometry().getToolbarButtonStyle()) { + setToolbarButtonStyle(*v); } - if (settings.contains("menubar_visible")) { - showMenuBar(settings.value("menubar_visible").toBool()); + if (auto v=settings.geometry().getMenubarVisible()) { + showMenuBar(*v); } - if (settings.contains("statusbar_visible")) { - showStatusBar(settings.value("statusbar_visible").toBool()); + if (auto v=settings.geometry().getStatusbarVisible()) { + showStatusBar(*v); } - if (settings.contains("window_split")) { - ui->splitter->restoreState(settings.value("window_split").toByteArray()); + if (auto v=settings.geometry().getMainSplitterState()) { + ui->splitter->restoreState(*v); } - if (settings.contains("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); + { + auto v = settings.geometry().getFiltersVisible().value_or(false); + setCategoryListVisible(v); + ui->displayCategoriesBtn->setChecked(v); } - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); + if (auto v=settings.getSelectedExecutable()) { + setExecutableIndex(*v); + } - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); + if (auto v=settings.getUseProxy()) { + if (*v) { + activateProxy(true); + } } DockFixer::restore(this, settings); @@ -2335,6 +2317,12 @@ void MainWindow::processUpdates() { ui->downloadView->header()->hideSection(i); } } + if (lastVersion < QVersionNumber(2, 2, 2)) { + QSettings &instance = Settings::instance().directInterface(); + + // log splitter is gone, it's a dock now + instance.remove("log_split"); + } } if (currentVersion > lastVersion) { @@ -2354,7 +2342,9 @@ void MainWindow::processUpdates() { settings.setValue("version", currentVersion.toString()); } -void MainWindow::storeSettings(QSettings &settings) { +void MainWindow::storeSettings(Settings& s) { + auto& settings = s.directInterface(); + settings.setValue("group_state", ui->groupCombo->currentIndex()); settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); @@ -2367,7 +2357,6 @@ void MainWindow::storeSettings(QSettings &settings) { settings.remove("menubar_visible"); settings.remove("window_split"); settings.remove("window_monitor"); - settings.remove("log_split"); settings.remove("filters_visible"); settings.remove("browser_geometry"); settings.remove("geometry"); @@ -2383,7 +2372,6 @@ void MainWindow::storeSettings(QSettings &settings) { QScreen *screen = this->window()->windowHandle()->screen(); int screenId = QGuiApplication::screens().indexOf(screen); settings.setValue("window_monitor", screenId); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); @@ -2392,7 +2380,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue(key, kv.second->saveState()); } - DockFixer::save(this, settings); + DockFixer::save(this, s); } } @@ -5213,7 +5201,7 @@ void MainWindow::on_actionSettings_triggered() QString oldModDirectory(settings.getModDirectory()); QString oldCacheDirectory(settings.getCacheDirectory()); QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory()); + QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); bool oldDisplayForeign(settings.displayForeign()); bool proxy = settings.useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 7326425a..d4513c0f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -119,8 +119,8 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(QSettings &settings) override; - void readSettings(); + void storeSettings(Settings& settings) override; + void readSettings(const Settings& settings); void processUpdates(); virtual ILockedWaitingForProcess* lock() override; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6c6d0bca..e9910b83 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -47,10 +47,6 @@ 0 - - - Qt::Vertical - @@ -1286,7 +1282,6 @@ p, li { white-space: pre-wrap; } - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 72c8dab5..a64d93b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -94,15 +94,6 @@ static bool isOnline() return false; } -static bool renameFile(const QString &oldName, const QString &newName, - bool overwrite = true) -{ - if (overwrite && QFile::exists(newName)) { - QFile::remove(newName); - } - return QFile::rename(oldName, newName); -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -342,80 +333,37 @@ OrganizerCore::~OrganizerCore() delete m_DirectoryStructure; } -QString OrganizerCore::commitSettings(const QString &iniFile) -{ - if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { - DWORD err = ::GetLastError(); - // make a second attempt using qt functions but if that fails print the - // error from the first attempt - if (!renameFile(iniFile + ".new", iniFile)) { - return QString::fromStdWString(formatSystemMessage(err)); - } - } - return QString(); -} - -QSettings::Status OrganizerCore::storeSettings(const QString &fileName) +void OrganizerCore::storeSettings() { - QSettings settings(fileName, QSettings::IniFormat); - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(settings); + m_UserInterface->storeSettings(m_Settings); } if (m_CurrentProfile != nullptr) { - settings.setValue("selected_profile", - m_CurrentProfile->name().toUtf8().constData()); + m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } - m_ExecutablesList.store(settings); - - FileDialogMemory::save(settings); + m_ExecutablesList.store(m_Settings); - settings.sync(); - return settings.status(); -} - -void OrganizerCore::storeSettings() -{ - QString iniFile = qApp->property("dataPath").toString() + "/" - + QString::fromStdWString(AppConfig::iniFileName()); - if (QFileInfo(iniFile).exists()) { - if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { - const auto e = GetLastError(); - QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile) - .arg(QString::fromStdWString(formatSystemMessage(e)))); - return; - } - } + FileDialogMemory::save(m_Settings); - QString writeTarget = iniFile + ".new"; + const auto result = m_Settings.sync(); - QSettings::Status result = storeSettings(writeTarget); + if (result != QSettings::NoError) { + QString reason; - if (result == QSettings::NoError) { - QString errMsg = commitSettings(iniFile); - if (!errMsg.isEmpty()) { - log::warn( - "settings file not writable, may be locked by another " - "application, trying direct write"); - writeTarget = iniFile; - result = storeSettings(iniFile); + if (result == QSettings::AccessError) { + reason = tr("File is write protected"); + } else if (result == QSettings::FormatError) { + reason = tr("Invalid file format (probably a bug)"); + } else { + reason = tr("Unknown error %1").arg(result); } - } - if (result != QSettings::NoError) { - QString reason = result == QSettings::AccessError - ? tr("File is write protected") - : result == QSettings::FormatError - ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); + QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to write back MO settings to %1: %2") - .arg(writeTarget, reason)); + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occurred trying to write back MO settings to %1: %2") + .arg(m_Settings.getFilename(), reason)); } } @@ -487,7 +435,7 @@ void OrganizerCore::updateExecutablesList() return; } - m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); + m_ExecutablesList.load(managedGame(), m_Settings); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 926a21f0..4bcfe745 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -288,10 +288,6 @@ private: void storeSettings(); - QSettings::Status storeSettings(const QString &fileName); - - QString commitSettings(const QString &iniFile); - bool queryApi(QString &apiKey); void updateModActiveState(int index, bool active); diff --git a/src/settings.cpp b/src/settings.cpp index 5d103267..d843a0db 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,10 +26,56 @@ along with Mod Organizer. If not, see . using namespace MOBase; +template +T convertVariant(const QVariant& v); + +template <> +QByteArray convertVariant(const QVariant& v) +{ + return v.toByteArray(); +} + +template <> +QString convertVariant(const QVariant& v) +{ + return v.toString(); +} + +template <> +int convertVariant(const QVariant& v) +{ + return v.toInt(); +} + +template <> +bool convertVariant(const QVariant& v) +{ + return v.toBool(); +} + +template <> +QSize convertVariant(const QVariant& v) +{ + return v.toSize(); +} + + + +template +std::optional getOptional(const QSettings& s, const QString& name) +{ + if (s.contains(name)) { + return convertVariant(s.value(name)); + } + + return {}; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) - : m_Settings(path, QSettings::IniFormat) + : m_Settings(path, QSettings::IniFormat), m_Geometry(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -51,6 +97,11 @@ Settings &Settings::instance() return *s_Instance; } +QString Settings::getFilename() const +{ + return m_Settings.fileName(); +} + void Settings::clearPlugins() { m_Plugins.clear(); @@ -278,9 +329,13 @@ QString Settings::getModDirectory(bool resolve) const return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); } -QString Settings::getManagedGameDirectory() const +std::optional Settings::getManagedGameDirectory() const { - return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); + if (auto v=getOptional(m_Settings, "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } void Settings::setManagedGameDirectory(const QString& path) @@ -288,9 +343,9 @@ void Settings::setManagedGameDirectory(const QString& path) m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); } -QString Settings::getManagedGameName() const +std::optional Settings::getManagedGameName() const { - return m_Settings.value("gameName", "").toString(); + return getOptional(m_Settings, "gameName"); } void Settings::setManagedGameName(const QString& name) @@ -298,9 +353,9 @@ void Settings::setManagedGameName(const QString& name) m_Settings.setValue("gameName", name); } -QString Settings::getManagedGameEdition() const +std::optional Settings::getManagedGameEdition() const { - return m_Settings.value("game_edition", "").toString(); + return getOptional(m_Settings, "game_edition"); } void Settings::setManagedGameEdition(const QString& name) @@ -308,19 +363,23 @@ void Settings::setManagedGameEdition(const QString& name) m_Settings.setValue("game_edition", name); } -QString Settings::getSelectedProfileName() const +std::optional Settings::getSelectedProfileName() const { - return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); + if (auto v=getOptional(m_Settings, "selected_profile")) { + return QString::fromUtf8(*v); + } + + return {}; } -int Settings::getMainWindowMonitor() const +void Settings::setSelectedProfileName(const QString& name) { - return m_Settings.value("window_monitor", -1).toInt(); + m_Settings.setValue("selected_profile", name.toUtf8()); } -QString Settings::getStyleName() const +std::optional Settings::getStyleName() const { - return m_Settings.value("Settings/style", "").toString(); + return getOptional(m_Settings, "Settings/style"); } void Settings::setStyleName(const QString& name) @@ -328,9 +387,14 @@ void Settings::setStyleName(const QString& name) m_Settings.setValue("Settings/style", name); } -bool Settings::isCategoryListVisible() const +std::optional Settings::getSelectedExecutable() const { - return m_Settings.value("categorylist_visible", true).toBool(); + return getOptional(m_Settings, "selected_executable"); +} + +std::optional Settings::getUseProxy() const +{ + return getOptional(m_Settings, "Settings/use_proxy"); } QString Settings::getProfileDirectory(bool resolve) const @@ -659,6 +723,22 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } +GeometrySettings& Settings::geometry() +{ + return m_Geometry; +} + +const GeometrySettings& Settings::geometry() const +{ + return m_Geometry; +} + +QSettings::Status Settings::sync() const +{ + m_Settings.sync(); + return m_Settings.status(); +} + void Settings::dump() const { static const QStringList ignore({ @@ -679,3 +759,73 @@ void Settings::dump() const m_Settings.endGroup(); } + + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s) +{ +} + +std::optional GeometrySettings::getMainWindow() const +{ + return getOptional(m_Settings, "window_geometry"); +} + +std::optional GeometrySettings::getMainWindowState() const +{ + return getOptional(m_Settings, "window_state"); +} + +std::optional GeometrySettings::getToolbarSize() const +{ + return getOptional(m_Settings, "toolbar_size"); +} + +std::optional GeometrySettings::getToolbarButtonStyle() const +{ + if (auto v=getOptional(m_Settings, "toolbar_button_style")) { + return static_cast(*v); + } + + return {}; +} + +std::optional GeometrySettings::getMenubarVisible() const +{ + return getOptional(m_Settings, "menubar_visible"); +} + +std::optional GeometrySettings::getStatusbarVisible() const +{ + return getOptional(m_Settings, "statusbar_visible"); +} + +std::optional GeometrySettings::getMainSplitterState() const +{ + return getOptional(m_Settings, "window_split"); +} + +std::optional GeometrySettings::getFiltersVisible() const +{ + return getOptional(m_Settings, "filters_visible"); +} + +std::optional GeometrySettings::getMainWindowMonitor() const +{ + return getOptional(m_Settings, "window_monitor"); +} + +void GeometrySettings::setDockSize(const QString& name, int size) +{ + m_Settings.setValue("geometry/" + name + "_size", size); +} + +std::optional GeometrySettings::getDockSize(const QString& name) const +{ + return getOptional(m_Settings, "geometry/" + name + "_size"); +} + +std::optional GeometrySettings::isCategoryListVisible() const +{ + return getOptional(m_Settings, "categorylist_visible"); +} diff --git a/src/settings.h b/src/settings.h index f06aece9..066843c2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,13 +31,40 @@ namespace MOBase { class PluginContainer; struct ServerInfo; + +class GeometrySettings +{ +public: + GeometrySettings(QSettings& s); + + std::optional getMainWindow() const; + std::optional getMainWindowState() const; + std::optional getToolbarSize() const; + std::optional getToolbarButtonStyle() const; + std::optional getMenubarVisible() const; + std::optional getStatusbarVisible() const; + std::optional getMainSplitterState() const; + std::optional getFiltersVisible() const; + + std::optional getMainWindowMonitor() const; + void setDockSize(const QString& name, int size); + + std::optional getDockSize(const QString& name) const; + + std::optional isCategoryListVisible() const; + +private: + QSettings& m_Settings; +}; + + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc **/ class Settings : public QObject { - Q_OBJECT + Q_OBJECT; public: Settings(const QString& path); @@ -45,6 +72,8 @@ public: static Settings &instance(); + QString getFilename() const; + /** * unregister all plugins from settings */ @@ -122,25 +151,26 @@ public: /** * retrieve the directory where the managed game is stored (with native separators) **/ - QString getManagedGameDirectory() const; + std::optional getManagedGameDirectory() const; void setManagedGameDirectory(const QString& path); - QString getManagedGameName() const; + std::optional getManagedGameName() const; void setManagedGameName(const QString& name); - QString getManagedGameEdition() const; + std::optional getManagedGameEdition() const; void setManagedGameEdition(const QString& name); - QString getSelectedProfileName() const; - - // returns -1 if not set - // - int getMainWindowMonitor() const; + std::optional getSelectedProfileName() const; + void setSelectedProfileName(const QString& name); - QString getStyleName() const; + std::optional getStyleName() const; void setStyleName(const QString& name); - bool isCategoryListVisible() const; + std::optional getSelectedExecutable() const; + std::optional getUseProxy() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; /** * retrieve the directory where profiles stored (with native separators) @@ -388,6 +418,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + QSettings::Status sync() const; + void dump() const; // temp @@ -407,6 +439,7 @@ private: static Settings *s_Instance; MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + GeometrySettings m_Geometry; LoadMechanism m_LoadMechanism; std::vector m_Plugins; -- cgit v1.3.1 From e4418b95fa24f9caea32adfe9d957ce37e46f127 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:30:20 -0400 Subject: moved settings updates to Settings::processUpdates() --- src/main.cpp | 2 +- src/mainwindow.cpp | 44 ++++++++++++-------------------------------- src/mainwindow.h | 2 +- src/settings.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 7 +++++++ 5 files changed, 67 insertions(+), 34 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 3e26ea17..506c6270 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -739,7 +739,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(); + mainWindow.processUpdates(settings); // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e77d08b1..0618f949 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2280,11 +2280,15 @@ void MainWindow::readSettings(const Settings& settings) DockFixer::restore(this, settings); } -void MainWindow::processUpdates() { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - QVersionNumber lastVersion = QVersionNumber::fromString(settings.value("version", "2.1.2").toString()).normalized(); - QVersionNumber currentVersion = QVersionNumber::fromString(m_OrganizerCore.getVersion().displayString()).normalized(); - if (!m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { +void MainWindow::processUpdates(Settings& settings) { + const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); + + const auto lastVersion = settings.getVersion().value_or(earliest); + const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); + + settings.processUpdates(currentVersion, lastVersion); + + if (!settings.getFirstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2293,41 +2297,20 @@ void MainWindow::processUpdates() { lastHidden = hidden; } } + if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } - if (lastVersion < QVersionNumber(2, 2, 0)) { - QSettings &instance = Settings::instance().directInterface(); - instance.beginGroup("Settings"); - instance.remove("steam_password"); - instance.remove("nexus_username"); - instance.remove("nexus_password"); - instance.remove("nexus_login"); - instance.remove("nexus_api_key"); - instance.remove("ask_for_nexuspw"); - instance.remove("nmm_version"); - instance.endGroup(); - instance.beginGroup("Servers"); - instance.remove(""); - instance.endGroup(); - } + if (lastVersion < QVersionNumber(2, 2, 1)) { // hide new columns by default for (int i=DownloadList::COL_MODNAME; idownloadView->header()->hideSection(i); } } - if (lastVersion < QVersionNumber(2, 2, 2)) { - QSettings &instance = Settings::instance().directInterface(); - - // log splitter is gone, it's a dock now - instance.remove("log_split"); - } } - if (currentVersion > lastVersion) { - //NOP - } else if (currentVersion < lastVersion) { + if (currentVersion < lastVersion) { const auto text = tr( "Notice: Your current MO version (%1) is lower than the previously used one (%2). " "The GUI may not downgrade gracefully, so you may experience oddities. " @@ -2337,9 +2320,6 @@ void MainWindow::processUpdates() { log::warn("{}", text); } - - //save version in all case - settings.setValue("version", currentVersion.toString()); } void MainWindow::storeSettings(Settings& s) { diff --git a/src/mainwindow.h b/src/mainwindow.h index d4513c0f..e8f60211 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -121,7 +121,7 @@ public: void storeSettings(Settings& settings) override; void readSettings(const Settings& settings); - void processUpdates(); + void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; virtual void unlock() override; diff --git a/src/settings.cpp b/src/settings.cpp index d843a0db..35be1298 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -97,6 +97,38 @@ Settings &Settings::instance() return *s_Instance; } +void Settings::processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) +{ + if (getFirstStart()) { + return; + } + + if (lastVersion < QVersionNumber(2, 2, 0)) { + m_Settings.beginGroup("Settings"); + m_Settings.remove("steam_password"); + m_Settings.remove("nexus_username"); + m_Settings.remove("nexus_password"); + m_Settings.remove("nexus_login"); + m_Settings.remove("nexus_api_key"); + m_Settings.remove("ask_for_nexuspw"); + m_Settings.remove("nmm_version"); + m_Settings.endGroup(); + + m_Settings.beginGroup("Servers"); + m_Settings.remove(""); + m_Settings.endGroup(); + } + + if (lastVersion < QVersionNumber(2, 2, 2)) { + // log splitter is gone, it's a dock now + m_Settings.remove("log_split"); + } + + //save version in all case + m_Settings.setValue("version", currentVersion.toString()); +} + QString Settings::getFilename() const { return m_Settings.fileName(); @@ -397,6 +429,20 @@ std::optional Settings::getUseProxy() const return getOptional(m_Settings, "Settings/use_proxy"); } +std::optional Settings::getVersion() const +{ + if (auto v=getOptional(m_Settings, "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; +} + +bool Settings::getFirstStart() const +{ + return getOptional(m_Settings, "first_start").value_or(true); +} + QString Settings::getProfileDirectory(bool resolve) const { return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); diff --git a/src/settings.h b/src/settings.h index 066843c2..bf66c0dd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -72,6 +72,9 @@ public: static Settings &instance(); + void processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + QString getFilename() const; /** @@ -169,9 +172,13 @@ public: std::optional getSelectedExecutable() const; std::optional getUseProxy() const; + std::optional getVersion() const; + bool getFirstStart() const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; + /** * retrieve the directory where profiles stored (with native separators) **/ -- cgit v1.3.1 From e40245abf46f133292636909fbacf10fc0712932 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:42:16 -0400 Subject: moved geometry handling to EditExecutablesDialog itself --- src/editexecutablesdialog.cpp | 14 ++++++++++++++ src/editexecutablesdialog.h | 4 ++++ src/mainwindow.cpp | 9 +-------- src/settings.cpp | 10 ++++++++++ src/settings.h | 2 ++ 5 files changed, 31 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 3ec3d64f..9c5ae44a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -65,6 +65,20 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) EditExecutablesDialog::~EditExecutablesDialog() = default; +int EditExecutablesDialog::exec() +{ + auto& settings = m_organizerCore.settings(); + + if (auto v=settings.geometry().getExecutablesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setExecutablesDialog(saveGeometry()); + + return r; +} void EditExecutablesDialog::loadCustomOverwrites() { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 9715489e..494f0651 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -151,6 +151,10 @@ public: ~EditExecutablesDialog(); + // also saves and restores geometry + // + int exec() override; + ExecutablesList getExecutablesList() const; const CustomOverwrites& getCustomOverwrites() const; const ForcedLibraries& getForcedLibraries() const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0618f949..7ef0c9b9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2480,21 +2480,14 @@ bool MainWindow::modifyExecutablesDialog() EditExecutablesDialog dialog(m_OrganizerCore, this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } - result = (dialog.exec() == QDialog::Accepted); - settings.setValue(key, dialog.saveGeometry()); refreshExecutablesList(); updatePinnedExecutables(); } catch (const std::exception &e) { reportError(e.what()); } + return result; } diff --git a/src/settings.cpp b/src/settings.cpp index 35be1298..834bd1d8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -856,6 +856,16 @@ std::optional GeometrySettings::getFiltersVisible() const return getOptional(m_Settings, "filters_visible"); } +std::optional GeometrySettings::getExecutablesDialog() const +{ + return getOptional(m_Settings, "geometry/EditExecutablesDialog"); +} + +void GeometrySettings::setExecutablesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/EditExecutablesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index bf66c0dd..0cdccd87 100644 --- a/src/settings.h +++ b/src/settings.h @@ -45,6 +45,8 @@ public: std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; + std::optional getExecutablesDialog() const; + void setExecutablesDialog(const QByteArray& v); std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 7eed0450e84cc465b0d163a64ebb4d410db688c4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:52:55 -0400 Subject: moved geometry handling to ProfilesDialog --- src/mainwindow.cpp | 8 ++------ src/profilesdialog.cpp | 15 +++++++++++++++ src/profilesdialog.h | 4 ++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 4 ++++ 5 files changed, 35 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7ef0c9b9..ac86d9d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2554,17 +2554,13 @@ void MainWindow::on_actionAdd_Profile_triggered() ProfilesDialog profilesDialog(m_OrganizerCore.currentProfile()->name(), m_OrganizerCore.managedGame(), this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(profilesDialog.objectName()); - if (settings.contains(key)) { - profilesDialog.restoreGeometry(settings.value(key).toByteArray()); - } + // workaround: need to disable monitoring of the saves directory, otherwise the active // profile directory is locked stopMonitorSaves(); profilesDialog.exec(); - settings.setValue(key, profilesDialog.saveGeometry()); refreshSaveList(); // since the save list may now be outdated we have to refresh it completely + if (refreshProfiles() && !profilesDialog.failed()) { break; } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index d7863fc8..25fff2b2 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -84,6 +84,21 @@ ProfilesDialog::~ProfilesDialog() delete ui; } +int ProfilesDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getProfilesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setProfilesDialog(saveGeometry()); + + return r; +} + void ProfilesDialog::showEvent(QShowEvent *event) { TutorableDialog::showEvent(event); diff --git a/src/profilesdialog.h b/src/profilesdialog.h index a328ce40..a47367be 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -51,6 +51,10 @@ public: explicit ProfilesDialog(const QString &profileName, MOBase::IPluginGame const *game, QWidget *parent = 0); ~ProfilesDialog(); + // also saves and restores geometry + // + int exec() override; + /** * @return true if creation of a new profile failed * @todo the notion of a fail state makes little sense in the current dialog diff --git a/src/settings.cpp b/src/settings.cpp index 834bd1d8..1f5abb2a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -866,6 +866,16 @@ void GeometrySettings::setExecutablesDialog(const QByteArray& v) m_Settings.setValue("geometry/EditExecutablesDialog", v); } +std::optional GeometrySettings::getProfilesDialog() const +{ + return getOptional(m_Settings, "geometry/ProfilesDialog"); +} + +void GeometrySettings::setProfilesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ProfilesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 0cdccd87..6d51c610 100644 --- a/src/settings.h +++ b/src/settings.h @@ -45,9 +45,13 @@ public: std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; + std::optional getExecutablesDialog() const; void setExecutablesDialog(const QByteArray& v); + std::optional getProfilesDialog() const; + void setProfilesDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From cc3a16c6e9d58ed68a31be52f9fe2ef1d514ff5f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:28:33 -0400 Subject: moved geometry handling to mod info and overwrite dialogs --- src/mainwindow.cpp | 18 +----------- src/modinfodialog.cpp | 72 +++++++++++++-------------------------------- src/modinfodialog.h | 26 ++++++++-------- src/overwriteinfodialog.cpp | 19 ++++++++++++ src/overwriteinfodialog.h | 11 ++++++- src/settings.cpp | 66 +++++++++++++++++++++++++++++++++++++++++ src/settings.h | 9 ++++++ 7 files changed, 137 insertions(+), 84 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac86d9d8..32f728d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3165,9 +3165,6 @@ void MainWindow::overwriteClosed(int) OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); if (dialog != nullptr) { m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog->objectName()); - settings.setValue(key, dialog->saveGeometry()); dialog->deleteLater(); } m_OrganizerCore.refreshDirectoryStructure(); @@ -3191,11 +3188,7 @@ void MainWindow::displayModInformation( } else { qobject_cast(dialog)->setModInfo(modInfo); } - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog->objectName()); - if (settings.contains(key)) { - dialog->restoreGeometry(settings.value(key).toByteArray()); - } + dialog->show(); dialog->raise(); dialog->activateWindow(); @@ -3214,16 +3207,7 @@ void MainWindow::displayModInformation( dialog.selectTab(tabID); } - dialog.restoreState(m_OrganizerCore.settings()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } - dialog.exec(); - dialog.saveState(m_OrganizerCore.settings()); - settings.setValue(key, dialog.saveGeometry()); modInfo->saveMeta(); emit modInfoDisplayed(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4b1e2f76..5e614358 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -210,6 +210,11 @@ void ModInfoDialog::createTabs() int ModInfoDialog::exec() { + restoreState(); + if (auto v=m_core->settings().geometry().getModInfoDialog()) { + restoreGeometry(*v); + } + // whether to select the first tab; if the main window requested a specific // tab, it is selected when encountered in update() const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); @@ -220,7 +225,12 @@ int ModInfoDialog::exec() ui->tabWidget->setCurrentIndex(0); } - return TutorableDialog::exec(); + const int r = TutorableDialog::exec(); + + saveState(); + m_core->settings().geometry().setModInfoDialog(saveGeometry()); + + return r; } void ModInfoDialog::setMod(ModInfo::Ptr mod) @@ -356,7 +366,7 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) if (!firstTime) { // but don't do it the first time visibility is set because the tabs are // in the default order, which will clobber the current settings - saveTabOrder(Settings::instance()); + saveTabOrder(); } // remember selection, if any @@ -375,7 +385,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = getOrderedTabNames(); + const auto orderedNames = m_core->settings().geometry().getModInfoTabOrder(); // whether the tabs can be sorted; if the object name of a tab widget is not // found in orderedNames, the list cannot be sorted safely @@ -575,37 +585,28 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() return origin; } -void ModInfoDialog::saveState(Settings& s) const +void ModInfoDialog::saveState() const { - saveTabOrder(s); - - // remove 2.2.0 settings - s.directInterface().remove("mod_info_tabs"); - s.directInterface().remove("mod_info_conflict_expanders"); - s.directInterface().remove("mod_info_conflicts"); - s.directInterface().remove("mod_info_advanced_conflicts"); - s.directInterface().remove("mod_info_conflicts_overwrite"); - s.directInterface().remove("mod_info_conflicts_noconflict"); - s.directInterface().remove("mod_info_conflicts_overwritten"); + saveTabOrder(); // save state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->saveState(s); + tabInfo.tab->saveState(m_core->settings()); } } -void ModInfoDialog::restoreState(const Settings& s) +void ModInfoDialog::restoreState() { // tab order is not restored here, it will be picked up if tabs have to be // removed and re-added // restore state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->restoreState(s); + tabInfo.tab->restoreState(m_core->settings()); } } -void ModInfoDialog::saveTabOrder(Settings& s) const +void ModInfoDialog::saveTabOrder() const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible @@ -629,40 +630,7 @@ void ModInfoDialog::saveTabOrder(Settings& s) const names += ui->tabWidget->widget(i)->objectName(); } - s.directInterface().setValue("mod_info_tab_order", names); -} - -std::vector ModInfoDialog::getOrderedTabNames() const -{ - const auto& settings = Settings::instance().directInterface(); - - std::vector v; - - if (settings.contains("mod_info_tabs")) { - // old byte array from 2.2.0 - QDataStream stream(settings.value("mod_info_tabs").toByteArray()); - - int count = 0; - stream >> count; - - for (int i=0; i> s; - v.emplace_back(std::move(s)); - } - } else { - // string list - QString string = settings.value("mod_info_tab_order").toString(); - QTextStream stream(&string); - - while (!stream.atEnd()) { - QString s; - stream >> s; - v.emplace_back(std::move(s)); - } - } - - return v; + m_core->settings().geometry().setModInfoTabOrder(names); } void ModInfoDialog::onOriginModified(int originID) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 34555b0c..48680ca4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -61,18 +61,11 @@ public: // void selectTab(ModInfoTabIDs id); - // updates all tabs, selects the initial tab and opens the dialog + // updates all tabs, selects the initial tab, opens the dialog and + // saves/restores geometry // int exec() override; - // saves the dialog state and calls saveState() on all tabs - // - void saveState(Settings& s) const; - - // restores the dialog state and calls restoreState() on all tabs - // - void restoreState(const Settings& s); - signals: // emitted when a tab changes the origin // @@ -146,6 +139,15 @@ private: void createTabs(); + // saves the dialog state and calls saveState() on all tabs + // + void saveState() const; + + // restores the dialog state and calls restoreState() on all tabs + // + void restoreState(); + + // sets the currently selected mod; resets first activation, but doesn't // update anything // @@ -213,11 +215,7 @@ private: // setTabsVisibility() to make sure any changes to order are saved before // re-adding tabs // - void saveTabOrder(Settings& s) const; - - // returns a list of tab names in the order they should appear on the widget - // - std::vector getOrderedTabNames() const; + void saveTabOrder() const; // asks all the tabs if they accept closing the dialog, returns false if one // objected diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 715e11e3..f3ae0ff5 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -104,6 +104,25 @@ OverwriteInfoDialog::~OverwriteInfoDialog() delete ui; } +void OverwriteInfoDialog::showEvent(QShowEvent* e) +{ + const auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getOverwriteDialog()) { + restoreGeometry(*v); + } + + QDialog::showEvent(e); +} + +void OverwriteInfoDialog::done(int r) +{ + auto& settings = Settings::instance(); + settings.geometry().setOverwriteDialog(saveGeometry()); + + QDialog::done(r); +} + void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) { m_ModInfo = modInfo; diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 4b731736..bedb779a 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -31,7 +31,7 @@ class OverwriteInfoDialog; class OverwriteInfoDialog : public QDialog { Q_OBJECT - + public: explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); @@ -39,8 +39,17 @@ public: ModInfo::Ptr modInfo() const { return m_ModInfo; } + // saves geometry + // + void done(int r) override; + void setModInfo(ModInfo::Ptr modInfo); +protected: + // restores geometry + // + void showEvent(QShowEvent* e) override; + private: void openFile(const QModelIndex &index); diff --git a/src/settings.cpp b/src/settings.cpp index 1f5abb2a..73595ac9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -120,6 +120,16 @@ void Settings::processUpdates( m_Settings.endGroup(); } + if (lastVersion < QVersionNumber(2, 2, 1)) { + m_Settings.remove("mod_info_tabs"); + m_Settings.remove("mod_info_conflict_expanders"); + m_Settings.remove("mod_info_conflicts"); + m_Settings.remove("mod_info_advanced_conflicts"); + m_Settings.remove("mod_info_conflicts_overwrite"); + m_Settings.remove("mod_info_conflicts_noconflict"); + m_Settings.remove("mod_info_conflicts_overwritten"); + } + if (lastVersion < QVersionNumber(2, 2, 2)) { // log splitter is gone, it's a dock now m_Settings.remove("log_split"); @@ -876,6 +886,62 @@ void GeometrySettings::setProfilesDialog(const QByteArray& v) m_Settings.setValue("geometry/ProfilesDialog", v); } +std::optional GeometrySettings::getOverwriteDialog() const +{ + return getOptional(m_Settings, "geometry/__overwriteDialog"); +} + +void GeometrySettings::setOverwriteDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/__overwriteDialog", v); +} + +std::optional GeometrySettings::getModInfoDialog() const +{ + return getOptional(m_Settings, "geometry/ModInfoDialog"); +} + +void GeometrySettings::setModInfoDialog(const QByteArray& v) const +{ + m_Settings.setValue("geometry/ModInfoDialog", v); +} + +QStringList GeometrySettings::getModInfoTabOrder() const +{ + QStringList v; + + if (m_Settings.contains("mod_info_tabs")) { + // old byte array from 2.2.0 + QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.push_back(s); + } + } else { + // string list since 2.2.1 + QString string = m_Settings.value("mod_info_tab_order").toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.push_back(s); + } + } + + return v; +} + +void GeometrySettings::setModInfoTabOrder(const QString& names) +{ + m_Settings.setValue("mod_info_tab_order", names); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 6d51c610..f4e36b2a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -52,6 +52,15 @@ public: std::optional getProfilesDialog() const; void setProfilesDialog(const QByteArray& v); + std::optional getOverwriteDialog() const; + void setOverwriteDialog(const QByteArray& v); + + std::optional getModInfoDialog() const; + void setModInfoDialog(const QByteArray& v) const; + + QStringList getModInfoTabOrder() const; + void setModInfoTabOrder(const QString& names); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 37502f388422b2fdb60c2564d733ec015f579831 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:39:22 -0400 Subject: removed convertVariant(), turns out value() does it separator colors to settings --- src/mainwindow.cpp | 30 ++++++++++++++++------------ src/settings.cpp | 57 ++++++++++++++++++++---------------------------------- src/settings.h | 4 ++++ 3 files changed, 43 insertions(+), 48 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 32f728d8..f98da391 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3786,32 +3786,37 @@ void MainWindow::createSeparator_clicked() { m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QColor previousColor = settings.value("previousSeparatorColor", QColor()).value(); - if (previousColor.isValid()) { - ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(previousColor); - } + if (auto c=m_OrganizerCore.settings().getPreviousSeparatorColor()) { + ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); + } } void MainWindow::setColor_clicked() { - QSettings &settings = m_OrganizerCore.settings().directInterface(); + auto& settings = m_OrganizerCore.settings(); ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + QColorDialog dialog(this); dialog.setOption(QColorDialog::ShowAlphaChannel); + QColor currentColor = modInfo->getColor(); - QColor previousColor = settings.value("previousSeparatorColor", QColor()).value(); - if (currentColor.isValid()) + if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); - else - dialog.setCurrentColor(previousColor); + } + else if (auto c=settings.getPreviousSeparatorColor()) { + dialog.setCurrentColor(*c); + } + if (!dialog.exec()) return; + currentColor = dialog.currentColor(); if (!currentColor.isValid()) return; - settings.setValue("previousSeparatorColor", currentColor); + + settings.setPreviousSeparatorColor(currentColor); + QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { @@ -3846,7 +3851,8 @@ void MainWindow::resetColor_clicked() else { modInfo->setColor(color); } - Settings::instance().directInterface().remove("previousSeparatorColor"); + + m_OrganizerCore.settings().removePreviousSeparatorColor(); } void MainWindow::createModFromOverwrite() diff --git a/src/settings.cpp b/src/settings.cpp index 73595ac9..f980e0be 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,46 +26,11 @@ along with Mod Organizer. If not, see . using namespace MOBase; -template -T convertVariant(const QVariant& v); - -template <> -QByteArray convertVariant(const QVariant& v) -{ - return v.toByteArray(); -} - -template <> -QString convertVariant(const QVariant& v) -{ - return v.toString(); -} - -template <> -int convertVariant(const QVariant& v) -{ - return v.toInt(); -} - -template <> -bool convertVariant(const QVariant& v) -{ - return v.toBool(); -} - -template <> -QSize convertVariant(const QVariant& v) -{ - return v.toSize(); -} - - - template std::optional getOptional(const QSettings& s, const QString& name) { if (s.contains(name)) { - return convertVariant(s.value(name)); + return s.value(name).value(); } return {}; @@ -453,6 +418,26 @@ bool Settings::getFirstStart() const return getOptional(m_Settings, "first_start").value_or(true); } +std::optional Settings::getPreviousSeparatorColor() const +{ + const auto c = getOptional(m_Settings, "previousSeparatorColor"); + if (c && c->isValid()) { + return c; + } + + return {}; +} + +void Settings::setPreviousSeparatorColor(const QColor& c) const +{ + m_Settings.setValue("previousSeparatorColor", c); +} + +void Settings::removePreviousSeparatorColor() +{ + m_Settings.remove("previousSeparatorColor"); +} + QString Settings::getProfileDirectory(bool resolve) const { return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); diff --git a/src/settings.h b/src/settings.h index f4e36b2a..fff684b8 100644 --- a/src/settings.h +++ b/src/settings.h @@ -190,6 +190,10 @@ public: std::optional getVersion() const; bool getFirstStart() const; + std::optional getPreviousSeparatorColor() const; + void setPreviousSeparatorColor(const QColor& c) const; + void removePreviousSeparatorColor(); + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From 61ad96cb54a20ce9f8e5380d67ba4bb26e19cc8e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:47:35 -0400 Subject: moved geometry handling to ListDialog --- src/listdialog.cpp | 16 ++++++++++++++++ src/listdialog.h | 4 ++++ src/mainwindow.cpp | 8 -------- src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 5 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/listdialog.cpp b/src/listdialog.cpp index b9857070..0fdcdb5f 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -17,6 +17,7 @@ along with Mod Organizer. If not, see . #include "listdialog.h" #include "ui_listdialog.h" +#include "settings.h" ListDialog::ListDialog(QWidget *parent) : QDialog(parent) @@ -32,6 +33,21 @@ ListDialog::~ListDialog() delete ui; } +int ListDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getListDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setListDialog(saveGeometry()); + + return r; +} + void ListDialog::setChoices(QStringList choices) { m_Choices = choices; diff --git a/src/listdialog.h b/src/listdialog.h index 7b5a5461..d0594bd7 100644 --- a/src/listdialog.h +++ b/src/listdialog.h @@ -15,6 +15,10 @@ public: explicit ListDialog(QWidget *parent = nullptr); ~ListDialog(); + // also saves and restores geometry + // + int exec() override; + void setChoices(QStringList choices); QString getChoice() const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f98da391..f41bde17 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3898,17 +3898,10 @@ void MainWindow::moveOverwriteContentToExistingMod() } ListDialog dialog(this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - dialog.setWindowTitle("Select a mod..."); dialog.setChoices(mods); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } if (dialog.exec() == QDialog::Accepted) { - QString result = dialog.getChoice(); if (!result.isEmpty()) { @@ -3930,7 +3923,6 @@ void MainWindow::moveOverwriteContentToExistingMod() doMoveOverwriteContentToMod(modAbsolutePath); } } - settings.setValue(key, dialog.saveGeometry()); } void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) diff --git a/src/settings.cpp b/src/settings.cpp index f980e0be..c36585b3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -927,6 +927,16 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } +std::optional GeometrySettings::getListDialog() const +{ + return getOptional(m_Settings, "geometry/ListDialog"); +} + +void GeometrySettings::setListDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ListDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index fff684b8..989ea1c6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -61,6 +61,9 @@ public: QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); + std::optional getListDialog() const; + void setListDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 3d86f150ca3a0992ddaca5055a270b7204c0682a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 06:30:37 -0400 Subject: moved geometry handling to ProblemsDialog and CategoriesDialog --- src/categoriesdialog.cpp | 16 ++++++++++++++++ src/categoriesdialog.h | 6 +++++- src/mainwindow.cpp | 26 ++++++++------------------ src/problemsdialog.cpp | 15 +++++++++++++++ src/problemsdialog.h | 4 ++++ src/settings.cpp | 20 ++++++++++++++++++++ src/settings.h | 6 ++++++ 7 files changed, 74 insertions(+), 19 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 881179a4..91df5cae 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_categoriesdialog.h" #include "categories.h" #include "utility.h" +#include "settings.h" #include #include #include @@ -109,6 +110,21 @@ CategoriesDialog::~CategoriesDialog() delete ui; } +int CategoriesDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getCategoriesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setCategoriesDialog(saveGeometry()); + + return r; +} + void CategoriesDialog::cellChanged(int row, int) { diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 72d2154d..c743c157 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -33,12 +33,16 @@ class CategoriesDialog; class CategoriesDialog : public MOBase::TutorableDialog { Q_OBJECT - + public: explicit CategoriesDialog(QWidget *parent = 0); ~CategoriesDialog(); + // also saves and restores geometry + // + int exec() override; + /** * @brief store changes here to the global categories store (categories.h) * diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f41bde17..26398630 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6164,24 +6164,20 @@ void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) void MainWindow::on_actionNotifications_triggered() { updateProblemsButton(); - ProblemsDialog problems(m_PluginContainer.plugins(), this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(problems.objectName()); - if (settings.contains(key)) { - problems.restoreGeometry(settings.value(key).toByteArray()); - } + ProblemsDialog problems(m_PluginContainer.plugins(), this); problems.exec(); - settings.setValue(key, problems.saveGeometry()); + updateProblemsButton(); } void MainWindow::on_actionChange_Game_triggered() { - if (QMessageBox::question(this, tr("Are you sure?"), - tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel) - == QMessageBox::Yes) { + const auto r = QMessageBox::question( + this, tr("Are you sure?"), tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel); + + if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); qApp->exit(INT_MAX); } @@ -6206,16 +6202,10 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) void MainWindow::editCategories() { CategoriesDialog dialog(this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } + if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); } - settings.setValue(key, dialog.saveGeometry()); - } void MainWindow::deselectFilters() diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index da09935b..99cc9833 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -29,6 +29,21 @@ ProblemsDialog::~ProblemsDialog() delete ui; } +int ProblemsDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getProblemsDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setProblemsDialog(saveGeometry()); + + return r; +} + void ProblemsDialog::runDiagnosis() { m_hasProblems = false; diff --git a/src/problemsdialog.h b/src/problemsdialog.h index c211e4f5..a30c8d48 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -20,6 +20,10 @@ public: explicit ProblemsDialog(std::vector pluginObjects, QWidget *parent = 0); ~ProblemsDialog(); + // also saves and restores geometry + // + int exec() override; + bool hasProblems() const; private: diff --git a/src/settings.cpp b/src/settings.cpp index c36585b3..da3b42a0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -937,6 +937,26 @@ void GeometrySettings::setListDialog(const QByteArray& v) m_Settings.setValue("geometry/ListDialog", v); } +std::optional GeometrySettings::getProblemsDialog() const +{ + return getOptional(m_Settings, "geometry/ProblemsDialog"); +} + +void GeometrySettings::setProblemsDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ProblemsDialog", v); +} + +std::optional GeometrySettings::getCategoriesDialog() const +{ + return getOptional(m_Settings, "geometry/CategoriesDialog"); +} + +void GeometrySettings::setCategoriesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/CategoriesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 989ea1c6..217c8db6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -64,6 +64,12 @@ public: std::optional getListDialog() const; void setListDialog(const QByteArray& v); + std::optional getProblemsDialog() const; + void setProblemsDialog(const QByteArray& v); + + std::optional getCategoriesDialog() const; + void setCategoriesDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From f387a670d119e501c5750b7efa1d3c11832ccf8c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 06:58:15 -0400 Subject: moved mod list stuff to setupModList(), no changes --- src/mainwindow.cpp | 120 ++++++++++++++++++++++++++++++++++------------------- src/mainwindow.h | 1 + 2 files changed, 79 insertions(+), 42 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 26398630..d4673701 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -354,41 +354,7 @@ MainWindow::MainWindow(Settings &settings TaskProgressManager::instance().tryCreateTaskbar(); - // set up mod list - m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); - - ui->modList->setModel(m_ModListSortProxy); - - GenericIconDelegate *contentDelegate = new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); - connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), contentDelegate, SLOT(columnResized(int,int,int))); - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate(ui->modList, ModList::COL_FLAGS, 120); - connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), flagDelegate, SLOT(columnResized(int,int,int))); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); - ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); - ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - connect(ui->modList->header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(modListSectionResized(int, int, int))); - - bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state"); - - if (modListAdjusted) { - // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { - int sectionSize = ui->modList->header()->sectionSize(column); - ui->modList->header()->resizeSection(column, sectionSize + 1); - ui->modList->header()->resizeSection(column, sectionSize); - } - } else { - // hide these columns by default - ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); - ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); - ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); - ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); - } - - ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden - ui->modList->installEventFilter(m_OrganizerCore.modList()); + setupModList(); // set up plugin list m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); @@ -401,10 +367,14 @@ MainWindow::MainWindow(Settings &settings ui->bsaList->setLocalMoveOnly(true); initDownloadView(); - bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + + bool pluginListAdjusted = registerWidgetState( + ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); - registerWidgetState(ui->downloadView->objectName(), - ui->downloadView->header()); + + registerWidgetState( + ui->downloadView->objectName(), ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -446,8 +416,6 @@ MainWindow::MainWindow(Settings &settings connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); - connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount())); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); @@ -494,7 +462,6 @@ MainWindow::MainWindow(Settings &settings connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); - connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); @@ -508,7 +475,6 @@ MainWindow::MainWindow(Settings &settings connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); - connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection))); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); @@ -570,6 +536,76 @@ MainWindow::MainWindow(Settings &settings updateModCount(); } +void MainWindow::setupModList() +{ + m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); + ui->modList->setModel(m_ModListSortProxy); + ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + + + connect( + ui->modList, SIGNAL(dropModeUpdate(bool)), + m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); + + connect( + ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); + + connect( + ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(modlistSelectionsChanged(QItemSelection))); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int, int, int)), + this, SLOT(modListSectionResized(int, int, int))); + + + GenericIconDelegate *contentDelegate = new GenericIconDelegate( + ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int,int,int)), + contentDelegate, SLOT(columnResized(int,int,int))); + + + ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate( + ui->modList, ModList::COL_FLAGS, 120); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int,int,int)), + flagDelegate, SLOT(columnResized(int,int,int))); + + + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); + ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); + ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); + + + const bool modListAdjusted = registerWidgetState( + ui->modList->objectName(), ui->modList->header(), "mod_list_state"); + + if (modListAdjusted) { + // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + int sectionSize = ui->modList->header()->sectionSize(column); + ui->modList->header()->resizeSection(column, sectionSize + 1); + ui->modList->header()->resizeSection(column, sectionSize); + } + } else { + // hide these columns by default + ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); + ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); + ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); + ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); + } + + // prevent the name-column from being hidden + ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); + + ui->modList->installEventFilter(m_OrganizerCore.modList()); +} + void MainWindow::resetActionIcons() { // this is a bit of a hack diff --git a/src/mainwindow.h b/src/mainwindow.h index e8f60211..5ddb9bef 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -698,6 +698,7 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + void setupModList(); void showMenuBar(bool b); void showStatusBar(bool b); }; -- cgit v1.3.1 From ea3840a39deacf269c1859389c3b1847bcbdb93b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:14:06 -0400 Subject: removed registerWidgetState(), was used just for header list headers, now saved and restored directly --- src/mainwindow.cpp | 73 +++++++++++++++++++----------------------------------- src/mainwindow.h | 9 +------ src/settings.cpp | 40 ++++++++++++++++++++++++++++++ src/settings.h | 12 +++++++++ 4 files changed, 79 insertions(+), 55 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d4673701..95aa0b38 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -368,18 +368,24 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - bool pluginListAdjusted = registerWidgetState( - ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + bool pluginListAdjusted = false; + if (auto v=m_OrganizerCore.settings().geometry().getPluginListHeader()) { + ui->espList->header()->restoreState(*v); + pluginListAdjusted = true; + } - registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); + if (auto v=m_OrganizerCore.settings().geometry().getDataTreeHeader()) { + ui->dataTree->header()->restoreState(*v); + } - registerWidgetState( - ui->downloadView->objectName(), ui->downloadView->header()); + if (auto v=m_OrganizerCore.settings().geometry().getDownloadViewHeader()) { + ui->downloadView->header()->restoreState(*v); + } ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); - resizeLists(modListAdjusted, pluginListAdjusted); + resizeLists(pluginListAdjusted); QMenu *linkMenu = new QMenu(this); m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); @@ -581,10 +587,9 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - const bool modListAdjusted = registerWidgetState( - ui->modList->objectName(), ui->modList->header(), "mod_list_state"); + if (auto v=m_OrganizerCore.settings().geometry().getModListHeader()) { + ui->modList->header()->restoreState(*v); - if (modListAdjusted) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -598,6 +603,13 @@ void MainWindow::setupModList() ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); + + // resize mod list to fit content + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + + ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); } // prevent the name-column from being hidden @@ -720,16 +732,8 @@ void MainWindow::disconnectPlugins() } -void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) +void MainWindow::resizeLists(bool pluginListCustom) { - if (!modListCustom) { - // resize mod list to fit content - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - // ensure the columns aren't so small you can't see them any more for (int i = 0; i < ui->modList->header()->count(); ++i) { if (ui->modList->header()->sectionSize(i) < 10) { @@ -2391,10 +2395,10 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - for (const std::pair kv : m_PersistedGeometry) { - QString key = QString("geometry/") + kv.first; - settings.setValue(key, kv.second->saveState()); - } + s.geometry().setPluginListHeader(ui->espList->header()->saveState()); + s.geometry().setDataTreeHeader(ui->dataTree->header()->saveState()); + s.geometry().setDownloadViewHeader(ui->downloadView->header()->saveState()); + s.geometry().setModListHeader(ui->modList->header()->saveState()); DockFixer::save(this, s); } @@ -6892,31 +6896,6 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m } } -bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) { - // register the view so it's geometry gets saved at exit - m_PersistedGeometry.push_back(std::make_pair(name, view)); - - // also, restore the geometry if it was saved before - QSettings &settings = m_OrganizerCore.settings().directInterface(); - - QString key = QString("geometry/%1").arg(name); - QByteArray data; - - if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) { - data = settings.value(oldSettingName).toByteArray(); - settings.remove(oldSettingName); - } else if (settings.contains(key)) { - data = settings.value(key).toByteArray(); - } - - if (!data.isEmpty()) { - view->restoreState(data); - return true; - } else { - return false; - } -} - void MainWindow::dropEvent(QDropEvent *event) { Qt::DropAction action = event->proposedAction(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 5ddb9bef..46f04784 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -316,8 +316,6 @@ private: void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); - bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr); - void sendSelectedModsToPriority(int newPriority); void sendSelectedPluginsToPriority(int newPriority); @@ -405,8 +403,6 @@ private: bool m_showArchiveData{ true }; - std::vector> m_PersistedGeometry; - MOBase::DelayedFileWriter m_ArchiveListWriter; QAction* m_LinkToolbar; @@ -604,10 +600,7 @@ private slots: void expandModList(const QModelIndex &index); - /** - * @brief resize columns in mod list and plugin list to content - */ - void resizeLists(bool modListCustom, bool pluginListCustom); + void resizeLists(bool pluginListCustom); /** * @brief allow columns in mod list and plugin list to be resized diff --git a/src/settings.cpp b/src/settings.cpp index da3b42a0..d9440aa4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -957,6 +957,46 @@ void GeometrySettings::setCategoriesDialog(const QByteArray& v) m_Settings.setValue("geometry/CategoriesDialog", v); } +std::optional GeometrySettings::getPluginListHeader() const +{ + return getOptional(m_Settings, "geometry/espList"); +} + +void GeometrySettings::setPluginListHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/espList", v); +} + +std::optional GeometrySettings::getDataTreeHeader() const +{ + return getOptional(m_Settings, "geometry/dataTree"); +} + +void GeometrySettings::setDataTreeHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/dataTree", v); +} + +std::optional GeometrySettings::getDownloadViewHeader() const +{ + return getOptional(m_Settings, "geometry/downloadView"); +} + +void GeometrySettings::setDownloadViewHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/downloadView", v); +} + +std::optional GeometrySettings::getModListHeader() const +{ + return getOptional(m_Settings, "geometry/modList"); +} + +void GeometrySettings::setModListHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/modList", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 217c8db6..110cfa76 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,6 +70,18 @@ public: std::optional getCategoriesDialog() const; void setCategoriesDialog(const QByteArray& v); + std::optional getPluginListHeader() const; + void setPluginListHeader(const QByteArray& v) const; + + std::optional getDataTreeHeader() const; + void setDataTreeHeader(const QByteArray& v) const; + + std::optional getDownloadViewHeader() const; + void setDownloadViewHeader(const QByteArray& v) const; + + std::optional getModListHeader() const; + void setModListHeader(const QByteArray& v) const; + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 4c6ccb38459152089d2d964f842c349b19fdb56a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:20:30 -0400 Subject: geo already saved by ListDialog --- src/mainwindow.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 95aa0b38..532914a5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -138,7 +138,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -6986,12 +6985,9 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } ListDialog dialog(this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - dialog.setWindowTitle("Select a separator..."); dialog.setChoices(separators); - dialog.restoreGeometry(settings.value(key).toByteArray()); + if (dialog.exec() == QDialog::Accepted) { QString result = dialog.getChoice(); if (!result.isEmpty()) { @@ -7025,7 +7021,6 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } } } - settings.setValue(key, dialog.saveGeometry()); } void MainWindow::on_showArchiveDataCheckBox_toggled(const bool checked) -- cgit v1.3.1 From 799ddb1b2477434252d06975fd4c68106dc3826f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 06:03:05 -0400 Subject: added GeometrySaver removed widget-specific functions in GeometrySettings, now using generic functions in Settings removed some unused member variables in MainWindow --- src/categoriesdialog.cpp | 13 +-- src/editexecutablesdialog.cpp | 13 +-- src/listdialog.cpp | 13 +-- src/mainwindow.cpp | 49 ++++------ src/mainwindow.h | 4 - src/modinfodialog.cpp | 6 +- src/overwriteinfodialog.cpp | 11 +-- src/previewdialog.cpp | 13 +-- src/problemsdialog.cpp | 13 +-- src/profilesdialog.cpp | 13 +-- src/settings.cpp | 218 +++++++++++++++++++----------------------- src/settings.h | 64 +++++-------- 12 files changed, 158 insertions(+), 272 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 91df5cae..b5194bf0 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -112,17 +112,8 @@ CategoriesDialog::~CategoriesDialog() int CategoriesDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getCategoriesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setCategoriesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 9c5ae44a..7823fadc 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -67,17 +67,8 @@ EditExecutablesDialog::~EditExecutablesDialog() = default; int EditExecutablesDialog::exec() { - auto& settings = m_organizerCore.settings(); - - if (auto v=settings.geometry().getExecutablesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setExecutablesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void EditExecutablesDialog::loadCustomOverwrites() diff --git a/src/listdialog.cpp b/src/listdialog.cpp index 0fdcdb5f..2ad88408 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -35,17 +35,8 @@ ListDialog::~ListDialog() int ListDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getListDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setListDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ListDialog::setChoices(QStringList choices) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 532914a5..85be8563 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,7 +290,6 @@ MainWindow::MainWindow(Settings &settings , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) - , m_ModListGroupingProxy(nullptr) , m_ModListSortProxy(nullptr) , m_OldExecutableIndex(-1) , m_CategoryFactory(CategoryFactory::instance()) @@ -309,8 +308,8 @@ MainWindow::MainWindow(Settings &settings { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setCachePath(settings.getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); ui->setupUi(this); m_statusBar.reset(new StatusBar(statusBar(), ui)); @@ -340,7 +339,7 @@ MainWindow::MainWindow(Settings &settings m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(m_OrganizerCore.settings().language()); + languageChange(settings.language()); m_CategoryFactory.loadCategories(); @@ -367,19 +366,9 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - bool pluginListAdjusted = false; - if (auto v=m_OrganizerCore.settings().geometry().getPluginListHeader()) { - ui->espList->header()->restoreState(*v); - pluginListAdjusted = true; - } - - if (auto v=m_OrganizerCore.settings().geometry().getDataTreeHeader()) { - ui->dataTree->header()->restoreState(*v); - } - - if (auto v=m_OrganizerCore.settings().geometry().getDownloadViewHeader()) { - ui->downloadView->header()->restoreState(*v); - } + const bool pluginListAdjusted = settings.restoreState(ui->espList->header()); + settings.restoreState(ui->dataTree->header()); + settings.restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -586,9 +575,7 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - if (auto v=m_OrganizerCore.settings().geometry().getModListHeader()) { - ui->modList->header()->restoreState(*v); - + if (m_OrganizerCore.settings().restoreState(ui->modList->header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -2272,13 +2259,8 @@ void MainWindow::activateProxy(bool activate) void MainWindow::readSettings(const Settings& settings) { - if (auto v=settings.geometry().getMainWindow()) { - restoreGeometry(*v); - } - - if (auto v=settings.geometry().getMainWindowState()) { - restoreState(*v); - } + settings.restoreGeometry(this); + settings.restoreState(this); if (auto v=settings.geometry().getToolbarSize()) { setToolbarSize(*v); @@ -2381,8 +2363,9 @@ void MainWindow::storeSettings(Settings& s) { settings.remove("geometry"); settings.remove("reset_geometry"); } else { - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_state", saveState()); + s.saveState(this); + s.saveGeometry(this); + settings.setValue("toolbar_size", ui->toolBar->iconSize()); settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); settings.setValue("menubar_visible", m_menuBarVisible); @@ -2394,10 +2377,10 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - s.geometry().setPluginListHeader(ui->espList->header()->saveState()); - s.geometry().setDataTreeHeader(ui->dataTree->header()->saveState()); - s.geometry().setDownloadViewHeader(ui->downloadView->header()->saveState()); - s.geometry().setModListHeader(ui->modList->header()->saveState()); + s.saveState(ui->espList->header()); + s.saveState(ui->dataTree->header()); + s.saveState(ui->downloadView->header()); + s.saveState(ui->modList->header()); DockFixer::save(this, s); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 7460019d..946a341b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -347,11 +347,9 @@ private: int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure - bool m_Refreshing; QStringList m_DefaultArchives; - QAbstractItemModel *m_ModListGroupingProxy; ModListSortProxy *m_ModListSortProxy; PluginListSortProxy *m_PluginListSortProxy; @@ -367,8 +365,6 @@ private: CategoryFactory &m_CategoryFactory; - bool m_LoginAttempted; - QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 5e614358..f3840230 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -210,10 +210,8 @@ void ModInfoDialog::createTabs() int ModInfoDialog::exec() { + GeometrySaver gs(Settings::instance(), this); restoreState(); - if (auto v=m_core->settings().geometry().getModInfoDialog()) { - restoreGeometry(*v); - } // whether to select the first tab; if the main window requested a specific // tab, it is selected when encountered in update() @@ -226,9 +224,7 @@ int ModInfoDialog::exec() } const int r = TutorableDialog::exec(); - saveState(); - m_core->settings().geometry().setModInfoDialog(saveGeometry()); return r; } diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index f3ae0ff5..47416311 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -106,20 +106,13 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - const auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getOverwriteDialog()) { - restoreGeometry(*v); - } - + Settings::instance().restoreGeometry(this); QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - auto& settings = Settings::instance(); - settings.geometry().setOverwriteDialog(saveGeometry()); - + Settings::instance().saveGeometry(this); QDialog::done(r); } diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index 06dcd674..91a5f13e 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -20,17 +20,8 @@ PreviewDialog::~PreviewDialog() int PreviewDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getPreviewDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setPreviewDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void PreviewDialog::addVariant(const QString &modName, QWidget *widget) diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 99cc9833..63d58295 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -31,17 +31,8 @@ ProblemsDialog::~ProblemsDialog() int ProblemsDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getProblemsDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setProblemsDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ProblemsDialog::runDiagnosis() diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 25fff2b2..2f1bd059 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -86,17 +86,8 @@ ProfilesDialog::~ProfilesDialog() int ProfilesDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getProfilesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setProfilesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ProfilesDialog::showEvent(QShowEvent *event) diff --git a/src/settings.cpp b/src/settings.cpp index a8dcfa39..91e667d5 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,94 +884,146 @@ void Settings::dump() const m_Settings.endGroup(); } +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s) + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) { + return w->objectName(); } -std::optional GeometrySettings::getMainWindow() const +QString widgetName(const QHeaderView* w) { - return getOptional(m_Settings, "window_geometry"); + return widgetNameWithTopLevel(w->parentWidget()); } -std::optional GeometrySettings::getMainWindowState() const +QString widgetName(const QWidget* w) { - return getOptional(m_Settings, "window_state"); + return widgetNameWithTopLevel(w); } -std::optional GeometrySettings::getToolbarSize() const +template +QString geoSettingName(const Widget* widget) { - return getOptional(m_Settings, "toolbar_size"); + return "geometry/" + widgetName(widget) + "_geometry"; } -std::optional GeometrySettings::getToolbarButtonStyle() const +template +QString stateSettingName(const Widget* widget) { - if (auto v=getOptional(m_Settings, "toolbar_button_style")) { - return static_cast(*v); - } + return "geometry/" + widgetName(widget) + "_state"; +} - return {}; +void Settings::saveGeometry(const QWidget* w) +{ + m_Settings.setValue(geoSettingName(w), w->saveGeometry()); } -std::optional GeometrySettings::getMenubarVisible() const +bool Settings::restoreGeometry(QWidget* w) const { - return getOptional(m_Settings, "menubar_visible"); + if (auto v=getOptional(m_Settings, geoSettingName(w))) { + w->restoreGeometry(*v); + return true; + } + + return false; } -std::optional GeometrySettings::getStatusbarVisible() const +void Settings::saveState(const QMainWindow* w) { - return getOptional(m_Settings, "statusbar_visible"); + m_Settings.setValue(stateSettingName(w), w->saveGeometry()); } -std::optional GeometrySettings::getMainSplitterState() const +bool Settings::restoreState(QMainWindow* w) const { - return getOptional(m_Settings, "window_split"); + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -std::optional GeometrySettings::getFiltersVisible() const +void Settings::saveState(const QHeaderView* w) { - return getOptional(m_Settings, "filters_visible"); + m_Settings.setValue(stateSettingName(w), w->saveState()); } -std::optional GeometrySettings::getExecutablesDialog() const +bool Settings::restoreState(QHeaderView* w) const { - return getOptional(m_Settings, "geometry/EditExecutablesDialog"); + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -void GeometrySettings::setExecutablesDialog(const QByteArray& v) + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s) { - m_Settings.setValue("geometry/EditExecutablesDialog", v); } -std::optional GeometrySettings::getProfilesDialog() const +std::optional GeometrySettings::getToolbarSize() const { - return getOptional(m_Settings, "geometry/ProfilesDialog"); + return getOptional(m_Settings, "toolbar_size"); } -void GeometrySettings::setProfilesDialog(const QByteArray& v) +std::optional GeometrySettings::getToolbarButtonStyle() const { - m_Settings.setValue("geometry/ProfilesDialog", v); + if (auto v=getOptional(m_Settings, "toolbar_button_style")) { + return static_cast(*v); + } + + return {}; } -std::optional GeometrySettings::getOverwriteDialog() const +std::optional GeometrySettings::getMenubarVisible() const { - return getOptional(m_Settings, "geometry/__overwriteDialog"); + return getOptional(m_Settings, "menubar_visible"); } -void GeometrySettings::setOverwriteDialog(const QByteArray& v) +std::optional GeometrySettings::getStatusbarVisible() const { - m_Settings.setValue("geometry/__overwriteDialog", v); + return getOptional(m_Settings, "statusbar_visible"); } -std::optional GeometrySettings::getModInfoDialog() const +std::optional GeometrySettings::getMainSplitterState() const { - return getOptional(m_Settings, "geometry/ModInfoDialog"); + return getOptional(m_Settings, "window_split"); } -void GeometrySettings::setModInfoDialog(const QByteArray& v) const +std::optional GeometrySettings::getFiltersVisible() const { - m_Settings.setValue("geometry/ModInfoDialog", v); + return getOptional(m_Settings, "filters_visible"); } QStringList GeometrySettings::getModInfoTabOrder() const @@ -1010,86 +1062,6 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } -std::optional GeometrySettings::getListDialog() const -{ - return getOptional(m_Settings, "geometry/ListDialog"); -} - -void GeometrySettings::setListDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/ListDialog", v); -} - -std::optional GeometrySettings::getProblemsDialog() const -{ - return getOptional(m_Settings, "geometry/ProblemsDialog"); -} - -void GeometrySettings::setProblemsDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/ProblemsDialog", v); -} - -std::optional GeometrySettings::getCategoriesDialog() const -{ - return getOptional(m_Settings, "geometry/CategoriesDialog"); -} - -void GeometrySettings::setCategoriesDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/CategoriesDialog", v); -} - -std::optional GeometrySettings::getPreviewDialog() const -{ - return getOptional(m_Settings, "geometry/PreviewDialog"); -} - -void GeometrySettings::setPreviewDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/PreviewDialog", v); -} - -std::optional GeometrySettings::getPluginListHeader() const -{ - return getOptional(m_Settings, "geometry/espList"); -} - -void GeometrySettings::setPluginListHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/espList", v); -} - -std::optional GeometrySettings::getDataTreeHeader() const -{ - return getOptional(m_Settings, "geometry/dataTree"); -} - -void GeometrySettings::setDataTreeHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/dataTree", v); -} - -std::optional GeometrySettings::getDownloadViewHeader() const -{ - return getOptional(m_Settings, "geometry/downloadView"); -} - -void GeometrySettings::setDownloadViewHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/downloadView", v); -} - -std::optional GeometrySettings::getModListHeader() const -{ - return getOptional(m_Settings, "geometry/modList"); -} - -void GeometrySettings::setModListHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/modList", v); -} - std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); @@ -1109,3 +1081,15 @@ std::optional GeometrySettings::isCategoryListVisible() const { return getOptional(m_Settings, "categorylist_visible"); } + + +GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) + : m_settings(s), m_dialog(dialog) +{ + m_settings.restoreGeometry(m_dialog); +} + +GeometrySaver::~GeometrySaver() +{ + m_settings.saveGeometry(m_dialog); +} diff --git a/src/settings.h b/src/settings.h index 2c4c7ca6..1575b3cd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -30,6 +30,18 @@ namespace MOBase { class PluginContainer; struct ServerInfo; +class Settings; + +class GeometrySaver +{ +public: + GeometrySaver(Settings& s, QDialog* dialog); + ~GeometrySaver(); + +private: + Settings& m_settings; + QDialog* m_dialog; +}; class GeometrySettings @@ -37,54 +49,17 @@ class GeometrySettings public: GeometrySettings(QSettings& s); - std::optional getMainWindow() const; - std::optional getMainWindowState() const; std::optional getToolbarSize() const; std::optional getToolbarButtonStyle() const; + std::optional getMenubarVisible() const; std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; - std::optional getExecutablesDialog() const; - void setExecutablesDialog(const QByteArray& v); - - std::optional getProfilesDialog() const; - void setProfilesDialog(const QByteArray& v); - - std::optional getOverwriteDialog() const; - void setOverwriteDialog(const QByteArray& v); - - std::optional getModInfoDialog() const; - void setModInfoDialog(const QByteArray& v) const; - QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); - std::optional getListDialog() const; - void setListDialog(const QByteArray& v); - - std::optional getProblemsDialog() const; - void setProblemsDialog(const QByteArray& v); - - std::optional getCategoriesDialog() const; - void setCategoriesDialog(const QByteArray& v); - - std::optional getPreviewDialog() const; - void setPreviewDialog(const QByteArray& v); - - std::optional getPluginListHeader() const; - void setPluginListHeader(const QByteArray& v) const; - - std::optional getDataTreeHeader() const; - void setDataTreeHeader(const QByteArray& v) const; - - std::optional getDownloadViewHeader() const; - void setDownloadViewHeader(const QByteArray& v) const; - - std::optional getModListHeader() const; - void setModListHeader(const QByteArray& v) const; - std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); @@ -228,6 +203,19 @@ public: const GeometrySettings& geometry() const; + void saveGeometry(const QWidget* w); + bool restoreGeometry(QWidget* w) const; + + void saveState(const QMainWindow* window); + bool restoreState(QMainWindow* window) const; + + void saveState(const QHeaderView* header); + bool restoreState(QHeaderView* header) const; + + void saveState(const QToolBar* toolbar); + bool restoreState(QToolBar* toolbar) const; + + /** * retrieve the directory where profiles stored (with native separators) **/ -- cgit v1.3.1 From 3f487a5a6c9c23824298fdde3d76dc82edf3ca46 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 07:03:52 -0400 Subject: merged toolbars into restoreToolbars() and saveToolbars() added centerOnMainWindowMonitor(), now also used by validation dialog added overloads for splitter, used by main splitter fixed saveState() for QMainWindow calling the wrong function --- src/main.cpp | 11 +------ src/mainwindow.cpp | 28 +++++----------- src/nxmaccessmanager.cpp | 13 ++++++-- src/nxmaccessmanager.h | 2 ++ src/pch.h | 1 + src/settings.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++------ src/settings.h | 19 ++++++++--- 7 files changed, 114 insertions(+), 46 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 506c6270..8eee41e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -697,16 +697,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - const auto monitor = settings.geometry().getMainWindowMonitor(); - if (monitor && QGuiApplication::screens().size() > *monitor) { - QGuiApplication::screens().at(*monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(*monitor)->geometry().center(); - splash.move(center - splash.rect().center()); - } else { - const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); - splash.move(center - splash.rect().center()); - } - + settings.geometry().centerOnMainWindowMonitor(&splash); splash.show(); splash.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85be8563..6e6e3d22 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2261,14 +2261,8 @@ void MainWindow::readSettings(const Settings& settings) { settings.restoreGeometry(this); settings.restoreState(this); - - if (auto v=settings.geometry().getToolbarSize()) { - setToolbarSize(*v); - } - - if (auto v=settings.geometry().getToolbarButtonStyle()) { - setToolbarButtonStyle(*v); - } + settings.geometry().restoreToolbars(this); + settings.restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2278,10 +2272,6 @@ void MainWindow::readSettings(const Settings& settings) showStatusBar(*v); } - if (auto v=settings.geometry().getMainSplitterState()) { - ui->splitter->restoreState(*v); - } - { auto v = settings.geometry().getFiltersVisible().value_or(false); setCategoryListVisible(v); @@ -2366,14 +2356,12 @@ void MainWindow::storeSettings(Settings& s) { s.saveState(this); s.saveGeometry(this); - settings.setValue("toolbar_size", ui->toolBar->iconSize()); - settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); - settings.setValue("menubar_visible", m_menuBarVisible); - settings.setValue("statusbar_visible", m_statusBarVisible); - settings.setValue("window_split", ui->splitter->saveState()); - QScreen *screen = this->window()->windowHandle()->screen(); - int screenId = QGuiApplication::screens().indexOf(screen); - settings.setValue("window_monitor", screenId); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index fd1dc0c1..16190ca4 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -48,8 +48,9 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) - : m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : + m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), + m_first(true) { m_bar = new QProgressBar; m_bar->setTextVisible(false); @@ -103,6 +104,14 @@ void ValidationProgressDialog::stop() hide(); } +void ValidationProgressDialog::showEvent(QShowEvent* e) +{ + if (m_first) { + Settings::instance().geometry().centerOnMainWindowMonitor(this); + m_first = false; + } +} + void ValidationProgressDialog::closeEvent(QCloseEvent* e) { hide(); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index eed7c1c9..0c85153b 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -48,6 +48,7 @@ public: using QDialog::show; protected: + void showEvent(QShowEvent* e) override; void closeEvent(QCloseEvent* e) override; private: @@ -56,6 +57,7 @@ private: QDialogButtonBox* m_buttons; QTimer* m_timer; QElapsedTimer m_elapsed; + bool m_first; void onButton(QAbstractButton* b); void onTimer(); diff --git a/src/pch.h b/src/pch.h index 504ef8f1..dd65efbe 100644 --- a/src/pch.h +++ b/src/pch.h @@ -189,6 +189,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index 91e667d5..a3d12070 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -958,7 +958,7 @@ bool Settings::restoreGeometry(QWidget* w) const void Settings::saveState(const QMainWindow* w) { - m_Settings.setValue(stateSettingName(w), w->saveGeometry()); + m_Settings.setValue(stateSettingName(w), w->saveState()); } bool Settings::restoreState(QMainWindow* w) const @@ -986,24 +986,61 @@ bool Settings::restoreState(QHeaderView* w) const return false; } +void Settings::saveState(const QSplitter* w) +{ + m_Settings.setValue(stateSettingName(w), w->saveState()); +} + +bool Settings::restoreState(QSplitter* w) const +{ + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s) { } -std::optional GeometrySettings::getToolbarSize() const +bool GeometrySettings::restoreToolbars(QMainWindow* w) const { - return getOptional(m_Settings, "toolbar_size"); + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + + if (!size && !style) { + return false; + } + + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + } + + return true; } -std::optional GeometrySettings::getToolbarButtonStyle() const +void GeometrySettings::saveToolbars(const QMainWindow* w) { - if (auto v=getOptional(m_Settings, "toolbar_button_style")) { - return static_cast(*v); + // all toolbars are identical, just save the first one + const auto tbs = w->findChildren(); + if (tbs.isEmpty()) { + return; } - return {}; + const auto* tb = tbs[0]; + + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); } std::optional GeometrySettings::getMenubarVisible() const @@ -1011,14 +1048,19 @@ std::optional GeometrySettings::getMenubarVisible() const return getOptional(m_Settings, "menubar_visible"); } +void GeometrySettings::setMenubarVisible(bool b) +{ + m_Settings.setValue("menubar_visible", b); +} + std::optional GeometrySettings::getStatusbarVisible() const { return getOptional(m_Settings, "statusbar_visible"); } -std::optional GeometrySettings::getMainSplitterState() const +void GeometrySettings::setStatusbarVisible(bool b) { - return getOptional(m_Settings, "window_split"); + m_Settings.setValue("statusbar_visible", b); } std::optional GeometrySettings::getFiltersVisible() const @@ -1064,7 +1106,31 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "window_monitor"); + return getOptional(m_Settings, "geometry/window_monitor"); +} + +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) +{ + const auto monitor = getMainWindowMonitor(); + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + m_Settings.setValue("geometry/window_monitor", screenId); + } + } } void GeometrySettings::setDockSize(const QString& name, int size) diff --git a/src/settings.h b/src/settings.h index 1575b3cd..bbf008f0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -28,6 +28,8 @@ namespace MOBase { class IPluginGame; } +class QSplitter; + class PluginContainer; struct ServerInfo; class Settings; @@ -49,18 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); - std::optional getToolbarSize() const; - std::optional getToolbarButtonStyle() const; - std::optional getMenubarVisible() const; + void setMenubarVisible(bool b); + + bool restoreToolbars(QMainWindow* w) const; + void saveToolbars(const QMainWindow* w); + std::optional getStatusbarVisible() const; - std::optional getMainSplitterState() const; + void setStatusbarVisible(bool b); + std::optional getFiltersVisible() const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); std::optional getMainWindowMonitor() const; + void centerOnMainWindowMonitor(QWidget* w); + void saveMainWindowMonitor(const QMainWindow* w); + void setDockSize(const QString& name, int size); std::optional getDockSize(const QString& name) const; @@ -215,6 +223,9 @@ public: void saveState(const QToolBar* toolbar); bool restoreState(QToolBar* toolbar) const; + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + /** * retrieve the directory where profiles stored (with native separators) -- cgit v1.3.1 From a5cb39aaf44b1f84003fb2ec2d36f07bf28916e4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 08:02:14 -0400 Subject: moved all geometry save, restore and reset to GeometrySettings changed reset button in settings to restart immediately --- src/browserdialog.cpp | 3 +- src/main.cpp | 12 ++++-- src/mainwindow.cpp | 75 +++++++++++-------------------------- src/mainwindow.h | 1 - src/overwriteinfodialog.cpp | 4 +- src/settings.cpp | 79 ++++++++++++++++++++++++++------------- src/settings.h | 39 +++++++++---------- src/settingsdialog.cpp | 11 +----- src/settingsdialog.h | 2 - src/settingsdialog.ui | 3 -- src/settingsdialogworkarounds.cpp | 16 ++++++-- 11 files changed, 121 insertions(+), 124 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 73a6a2d0..70da0b9c 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -72,7 +72,7 @@ BrowserDialog::~BrowserDialog() void BrowserDialog::closeEvent(QCloseEvent *event) { -// m_AccessManager->showCookies(); + Settings::instance().geometry().saveGeometry(this); QDialog::closeEvent(event); } @@ -126,6 +126,7 @@ void BrowserDialog::urlChanged(const QUrl &url) void BrowserDialog::openUrl(const QUrl &url) { if (isHidden()) { + Settings::instance().geometry().restoreGeometry(this); show(); } openInNewTab(url); diff --git a/src/main.cpp b/src/main.cpp index 8eee41e4..6d4108fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -718,6 +718,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } int res = 1; + { // scope to control lifetime of mainwindow // set up main window and its data structures MainWindow mainWindow(settings, organizer, pluginContainer); @@ -743,17 +744,20 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); - const auto ret = application.exec(); + res = application.exec(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); - - return ret; } + + settings.geometry().resetIfNeeded(); + return res; + } catch (const std::exception &e) { reportError(e.what()); - return 1; } + + return 1; } int doCoreDump(env::CoreDumpTypes type) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e6e3d22..28e1de2e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -366,9 +366,11 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - const bool pluginListAdjusted = settings.restoreState(ui->espList->header()); - settings.restoreState(ui->dataTree->header()); - settings.restoreState(ui->downloadView->header()); + const bool pluginListAdjusted = + settings.geometry().restoreState(ui->espList->header()); + + settings.geometry().restoreState(ui->dataTree->header()); + settings.geometry().restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -575,7 +577,7 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - if (m_OrganizerCore.settings().restoreState(ui->modList->header())) { + if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -1417,12 +1419,6 @@ void MainWindow::cleanup() m_MetaSave.waitForFinished(); } - -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_IntegratedBrowser.restoreGeometry(geometry); -} - void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) { // don't display the widget if the main window doesn't have focus @@ -2259,10 +2255,10 @@ void MainWindow::activateProxy(bool activate) void MainWindow::readSettings(const Settings& settings) { - settings.restoreGeometry(this); - settings.restoreState(this); + settings.geometry().restoreGeometry(this); + settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); - settings.restoreState(ui->splitter); + settings.geometry().restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2340,38 +2336,22 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); - if (settings.value("reset_geometry", false).toBool()) { - settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry"); - } else { - s.saveState(this); - s.saveGeometry(this); - - s.geometry().setMenubarVisible(m_menuBarVisible); - s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); - s.saveState(ui->splitter); - s.geometry().saveMainWindowMonitor(this); + s.geometry().saveState(this); + s.geometry().saveGeometry(this); - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.geometry().saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); - s.saveState(ui->espList->header()); - s.saveState(ui->dataTree->header()); - s.saveState(ui->downloadView->header()); - s.saveState(ui->modList->header()); + s.geometry().saveState(ui->espList->header()); + s.geometry().saveState(ui->dataTree->header()); + s.geometry().saveState(ui->downloadView->header()); + s.geometry().saveState(ui->modList->header()); - DockFixer::save(this, s); - } + DockFixer::save(this, s); } ILockedWaitingForProcess* MainWindow::lock() @@ -6489,7 +6469,6 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe void MainWindow::on_bossButton_clicked() { - std::string reportURL; std::string errorMessages; //m_OrganizerCore.currentProfile()->writeModlistNow(); @@ -6637,16 +6616,6 @@ void MainWindow::on_bossButton_clicked() if (success) { m_DidUpdateMasterList = true; - if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - QStringList temp = report.split("?"); - QUrl url = QUrl::fromLocalFile(temp.at(0)); - if (temp.size() > 1) { - url.setQuery(temp.at(1).toUtf8()); - } - m_IntegratedBrowser.openUrl(url); - } m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 946a341b..8542dc8a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -293,7 +293,6 @@ private: static void setupNetworkProxy(bool activate); void activateProxy(bool activate); - void setBrowserGeometry(const QByteArray &geometry); bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 47416311..fe1d8825 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -106,13 +106,13 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - Settings::instance().restoreGeometry(this); + Settings::instance().geometry().restoreGeometry(this); QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - Settings::instance().saveGeometry(this); + Settings::instance().geometry().saveGeometry(this); QDialog::done(r); } diff --git a/src/settings.cpp b/src/settings.cpp index a3d12070..db6cecdf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,6 +884,7 @@ void Settings::dump() const m_Settings.endGroup(); } + QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -941,12 +942,46 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } -void Settings::saveGeometry(const QWidget* w) + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) +{ +} + +void GeometrySettings::requestReset() +{ + m_Reset = true; +} + +void GeometrySettings::resetIfNeeded() +{ + if (!m_Reset) { + return; + } + + m_Settings.beginGroup("geometry"); + m_Settings.remove(""); + m_Settings.endGroup(); + + /*settings.remove("window_geometry"); + settings.remove("window_state"); + settings.remove("toolbar_size"); + settings.remove("toolbar_button_style"); + settings.remove("menubar_visible"); + settings.remove("window_split"); + settings.remove("window_monitor"); + settings.remove("filters_visible"); + settings.remove("browser_geometry"); + settings.remove("geometry"); + settings.remove("reset_geometry");*/ +} + +void GeometrySettings::saveGeometry(const QWidget* w) { m_Settings.setValue(geoSettingName(w), w->saveGeometry()); } -bool Settings::restoreGeometry(QWidget* w) const +bool GeometrySettings::restoreGeometry(QWidget* w) const { if (auto v=getOptional(m_Settings, geoSettingName(w))) { w->restoreGeometry(*v); @@ -956,12 +991,12 @@ bool Settings::restoreGeometry(QWidget* w) const return false; } -void Settings::saveState(const QMainWindow* w) +void GeometrySettings::saveState(const QMainWindow* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QMainWindow* w) const +bool GeometrySettings::restoreState(QMainWindow* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -971,12 +1006,12 @@ bool Settings::restoreState(QMainWindow* w) const return false; } -void Settings::saveState(const QHeaderView* w) +void GeometrySettings::saveState(const QHeaderView* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QHeaderView* w) const +bool GeometrySettings::restoreState(QHeaderView* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -986,12 +1021,12 @@ bool Settings::restoreState(QHeaderView* w) const return false; } -void Settings::saveState(const QSplitter* w) +void GeometrySettings::saveState(const QSplitter* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QSplitter* w) const +bool GeometrySettings::restoreState(QSplitter* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -1001,12 +1036,6 @@ bool Settings::restoreState(QSplitter* w) const return false; } - -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s) -{ -} - bool GeometrySettings::restoreToolbars(QMainWindow* w) const { const auto size = getOptional(m_Settings, "toolbar_size"); @@ -1068,6 +1097,11 @@ std::optional GeometrySettings::getFiltersVisible() const return getOptional(m_Settings, "filters_visible"); } +void GeometrySettings::setFiltersVisible(bool b) +{ + m_Settings.setValue("filters_visible", b); +} + QStringList GeometrySettings::getModInfoTabOrder() const { QStringList v; @@ -1106,7 +1140,7 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "geometry/window_monitor"); + return getOptional(m_Settings, "geometry/MainWindow_monitor"); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) @@ -1128,34 +1162,29 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) if (auto* handle=w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); - m_Settings.setValue("geometry/window_monitor", screenId); + m_Settings.setValue("geometry/MainWindow_monitor", screenId); } } } void GeometrySettings::setDockSize(const QString& name, int size) { - m_Settings.setValue("geometry/" + name + "_size", size); + m_Settings.setValue("geometry/MainWindow_docks_" + name + "_size", size); } std::optional GeometrySettings::getDockSize(const QString& name) const { - return getOptional(m_Settings, "geometry/" + name + "_size"); -} - -std::optional GeometrySettings::isCategoryListVisible() const -{ - return getOptional(m_Settings, "categorylist_visible"); + return getOptional(m_Settings, "geometry/MainWindow_docks_" + name + "_size"); } GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { - m_settings.restoreGeometry(m_dialog); + m_settings.geometry().restoreGeometry(m_dialog); } GeometrySaver::~GeometrySaver() { - m_settings.saveGeometry(m_dialog); + m_settings.geometry().saveGeometry(m_dialog); } diff --git a/src/settings.h b/src/settings.h index bbf008f0..9ae58803 100644 --- a/src/settings.h +++ b/src/settings.h @@ -51,6 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); + void requestReset(); + void resetIfNeeded(); + + void saveGeometry(const QWidget* w); + bool restoreGeometry(QWidget* w) const; + + void saveState(const QMainWindow* window); + bool restoreState(QMainWindow* window) const; + + void saveState(const QHeaderView* header); + bool restoreState(QHeaderView* header) const; + + void saveState(const QToolBar* toolbar); + bool restoreState(QToolBar* toolbar) const; + + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + std::optional getMenubarVisible() const; void setMenubarVisible(bool b); @@ -61,6 +79,7 @@ public: void setStatusbarVisible(bool b); std::optional getFiltersVisible() const; + void setFiltersVisible(bool b); QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); @@ -73,10 +92,9 @@ public: std::optional getDockSize(const QString& name) const; - std::optional isCategoryListVisible() const; - private: QSettings& m_Settings; + bool m_Reset; }; @@ -210,23 +228,6 @@ public: GeometrySettings& geometry(); const GeometrySettings& geometry() const; - - void saveGeometry(const QWidget* w); - bool restoreGeometry(QWidget* w) const; - - void saveState(const QMainWindow* window); - bool restoreState(QMainWindow* window) const; - - void saveState(const QHeaderView* header); - bool restoreState(QHeaderView* header) const; - - void saveState(const QToolBar* toolbar); - bool restoreState(QToolBar* toolbar) const; - - void saveState(const QSplitter* splitter); - bool restoreState(QSplitter* splitter) const; - - /** * retrieve the directory where profiles stored (with native separators) **/ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index fbd9ecd1..d74507c9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -34,7 +34,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , ui(new Ui::SettingsDialog) , m_settings(settings) , m_PluginContainer(pluginContainer) - , m_GeometriesReset(false) , m_keyChanged(false) { ui->setupUi(this); @@ -101,10 +100,7 @@ int SettingsDialog::exec() if (getApiKeyChanged()) { restartNeeded = true; } - if (getResetGeometries()) { - restartNeeded = true; - qsettings.setValue("reset_geometry", true); - } + if (restartNeeded) { if (QMessageBox::question(nullptr, tr("Restart Mod Organizer?"), @@ -156,11 +152,6 @@ void SettingsDialog::accept() TutorableDialog::accept(); } -bool SettingsDialog::getResetGeometries() -{ - return ui->resetGeometryBtn->isChecked(); -} - bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 03bba7cf..efc4a095 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -71,7 +71,6 @@ public: // temp Ui::SettingsDialog *ui; bool m_keyChanged; - bool m_GeometriesReset; PluginContainer *m_PluginContainer; int exec() override; @@ -81,7 +80,6 @@ public slots: public: bool getApiKeyChanged(); - bool getResetGeometries(); private: Settings* m_settings; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e011542e..e7676387 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1233,9 +1233,6 @@ programs you are intentionally running. Reset Window Geometries - - true - diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 9ac46ac1..fc859289 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -26,8 +26,6 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo ui->lockGUIBox->setChecked(m_parent->lockGUI()); ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); - ui->resetGeometryBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool()); - setExecutableBlacklist(m_parent->executablesBlacklist()); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); @@ -89,6 +87,16 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - m_dialog.m_GeometriesReset = true; - ui->resetGeometryBtn->setChecked(true); + const auto caption = QObject::tr("Restart Mod Organizer?"); + const auto text = QObject::tr( + "In order to reset the geometry, Mod Organizer must be restarted.\n" + "Restart now?"); + + const auto res = QMessageBox::question( + nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); + + if (res == QMessageBox::Yes) { + m_parent->geometry().requestReset(); + qApp->exit(INT_MAX); + } } -- cgit v1.3.1 From 0374291a3451c464fb27e53077da42ad21c27cd6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 09:00:31 -0400 Subject: StatusBar now inherits from QStatusBar to handle hide/show events merged settings into saveVisibility() and restoreVisibility() call MainWindow::storeSettings() earlier so widget visibility is still valid --- src/iuserinterface.h | 5 - src/main.cpp | 2 - src/mainwindow.cpp | 78 +- src/mainwindow.h | 15 +- src/mainwindow.ui | 2129 +++++++++++++++++++++++++------------------------ src/organizercore.cpp | 4 - src/settings.cpp | 96 +-- src/settings.h | 13 +- src/statusbar.cpp | 65 +- src/statusbar.h | 14 +- 10 files changed, 1200 insertions(+), 1221 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 7205f982..a309ed9b 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,14 +10,9 @@ #include -class Settings; - class IUserInterface { public: - - virtual void storeSettings(Settings &settings) = 0; - virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; virtual void registerPluginTools(std::vector toolPlugins) = 0; virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; diff --git a/src/main.cpp b/src/main.cpp index 6d4108fa..aa781c19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -736,8 +736,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(settings); - log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28e1de2e..7e471d24 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -285,8 +285,6 @@ MainWindow::MainWindow(Settings &settings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) - , m_menuBarVisible(true) - , m_statusBarVisible(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -312,7 +310,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); ui->setupUi(this); - m_statusBar.reset(new StatusBar(statusBar(), ui)); + ui->statusBar->setup(ui); { auto* ni = NexusInterface::instance(&m_PluginContainer); @@ -336,7 +334,7 @@ MainWindow::MainWindow(Settings &settings // in the rare case where the user restarts MO through the settings, this // will correctly pick up the previous values updateWindowTitle(ni->getAPIUserAccount()); - m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); + ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } languageChange(settings.language()); @@ -708,7 +706,7 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user) void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) { - m_statusBar->setAPI(stats, user); + ui->statusBar->setAPI(stats, user); } @@ -902,7 +900,7 @@ QMenu* MainWindow::createPopupMenu() void MainWindow::on_actionMainMenuToggle_triggered() { - showMenuBar(!ui->menuBar->isVisible()); + ui->menuBar->setVisible(!ui->menuBar->isVisible()); } void MainWindow::on_actionToolBarMainToggle_triggered() @@ -912,7 +910,7 @@ void MainWindow::on_actionToolBarMainToggle_triggered() void MainWindow::on_actionStatusBarToggle_triggered() { - showStatusBar(!ui->statusBar->isVisible()); + ui->statusBar->setVisible(!ui->statusBar->isVisible()); } void MainWindow::on_actionToolBarSmallIcons_triggered() @@ -964,36 +962,6 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) } } -void MainWindow::showMenuBar(bool b) -{ - ui->menuBar->setVisible(b); - m_menuBarVisible = b; -} - -void MainWindow::showStatusBar(bool b) -{ - ui->statusBar->setVisible(b); - m_statusBarVisible = b; - - // the central widget typically has no bottom padding because the status bar - // is more than enough, but when it's hidden, the bottom widget (currently - // the log) touches the bottom border of the window, which looks ugly - // - // when hiding the statusbar, the central widget is given the same border - // margin as it has on the top (which is typically 6, as it's the default from - // the qt designer) - - auto m = ui->centralWidget->layout()->contentsMargins(); - - if (b) { - m.setBottom(0); - } else { - m.setBottom(m.top()); - } - - ui->centralWidget->layout()->setContentsMargins(m); -} - void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) { // this allows for getting the context menu even if both the menubar and all @@ -1075,8 +1043,8 @@ void MainWindow::updateProblemsButton() } // updating the status bar, may be null very early when MO is starting - if (m_statusBar) { - m_statusBar->setNotifications(numProblems > 0); + if (ui->statusBar) { + ui->statusBar->setNotifications(numProblems > 0); } } @@ -1319,6 +1287,8 @@ void MainWindow::hookUpWindowTutorials() void MainWindow::showEvent(QShowEvent *event) { + readSettings(m_OrganizerCore.settings()); + refreshFilters(); QMainWindow::showEvent(event); @@ -1378,7 +1348,10 @@ void MainWindow::closeEvent(QCloseEvent* event) { if (!confirmExit()) { event->ignore(); + return; } + + storeSettings(m_OrganizerCore.settings()); } bool MainWindow::confirmExit() @@ -2259,17 +2232,12 @@ void MainWindow::readSettings(const Settings& settings) settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); - - if (auto v=settings.geometry().getMenubarVisible()) { - showMenuBar(*v); - } - - if (auto v=settings.geometry().getStatusbarVisible()) { - showStatusBar(*v); - } + settings.geometry().restoreVisibility(ui->menuBar); + settings.geometry().restoreVisibility(ui->statusBar); { - auto v = settings.geometry().getFiltersVisible().value_or(false); + settings.geometry().restoreVisibility(ui->categoriesGroup, false); + const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } @@ -2339,12 +2307,12 @@ void MainWindow::storeSettings(Settings& s) { s.geometry().saveState(this); s.geometry().saveGeometry(this); - s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveVisibility(ui->menuBar); + s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); s.geometry().saveState(ui->splitter); s.geometry().saveMainWindowMonitor(this); - s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); + s.geometry().saveVisibility(ui->categoriesGroup); s.geometry().saveState(ui->espList->header()); s.geometry().saveState(ui->dataTree->header()); @@ -2606,7 +2574,7 @@ void MainWindow::setESPListSorting(int index) void MainWindow::refresher_progress(int percent) { setEnabled(percent == 100); - m_statusBar->setProgress(percent); + ui->statusBar->setProgress(percent); } void MainWindow::directory_refreshed() @@ -5216,7 +5184,7 @@ void MainWindow::on_actionSettings_triggered() activateProxy(settings.useProxy()); } - m_statusBar->checkSettings(m_OrganizerCore.settings()); + ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); m_OrganizerCore.setLogLevel(settings.logLevel()); @@ -5525,7 +5493,7 @@ void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); ui->actionUpdate->setToolTip(tr("Update available")); - m_statusBar->setUpdateAvailable(true); + ui->statusBar->setUpdateAvailable(true); } @@ -6858,7 +6826,7 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) // if the menubar is hidden, pressing Alt will make it visible if (event->key() == Qt::Key_Alt) { if (!ui->menuBar->isVisible()) { - showMenuBar(true); + ui->menuBar->show(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8542dc8a..a905a163 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -40,7 +40,6 @@ class Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; -class StatusBar; class PluginListSortProxy; namespace BSA { class Archive; } @@ -118,8 +117,6 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(Settings& settings) override; - void readSettings(const Settings& settings); void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; @@ -331,12 +328,6 @@ private: bool m_WasVisible; - // this has to be remembered because by the time storeSettings() is called, - // the window is closed and the all bars are hidden - bool m_menuBarVisible, m_statusBarVisible; - - std::unique_ptr m_statusBar; - // last separator on the toolbar, used to add spacer for right-alignment and // as an insert point for executables QAction* m_linksSeparator; @@ -685,11 +676,9 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + void storeSettings(Settings& settings); + void readSettings(const Settings& settings); void setupModList(); - void showMenuBar(bool b); - void showStatusBar(bool b); }; - - #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e9910b83..02c6dec0 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -48,1239 +48,1239 @@ - + + + - - - - - Categories - - - + + + Categories + + + + 0 + + + 3 + + + 7 + + + 3 + + + 1 + + + + + + 120 + 0 + + + + + 214 + 16777215 + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + 0 - - 3 + + true - - 7 + + false + + + + 1 + + + + + + + + false - - 3 + + + 0 + 0 + - - 1 + + + 0 + 25 + - - - - - 120 - 0 - - - - - 214 - 16777215 - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - 0 - - - true - - - false - - - - 1 - - - - - - - - false - - - - 0 - 0 - - - - - 0 - 25 - - - - Clear - - - true - - - - - - - - 0 - 0 - - - - - - - If checked, only mods that match all selected categories are displayed. - - - And - - - true - - - - - - - If checked, all mods that match at least one of the selected categories are displayed. - - - Or - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - 2 - - - - - - - - 0 - 0 - - - - Profile - - - profileBox - - - - - - - Pick a module collection - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16777215 - 16777215 - - - - Open list options... - - - Refresh list. This is usually not necessary unless you modified data outside the program. - - - - - - - :/MO/gui/settings:/MO/gui/settings - - - - 16 - 16 - - - - - - - - Show Open Folders menu... - - - - - - - :/MO/gui/open_folder:/MO/gui/open_folder - - - + + Clear + + + true + + + + + + + + 0 + 0 + + + - + - Restore Backup... + If checked, only mods that match all selected categories are displayed. - + And - - - :/MO/gui/restore:/MO/gui/restore + + true - + - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup + If checked, all mods that match at least one of the selected categories are displayed. - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 5 - - - QLCDNumber::Flat + Or + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 2 + + + + + + + + 0 + 0 + + + + Profile + + + profileBox + + - - + + + Pick a module collection + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + + + + + + Qt::Horizontal + + - 330 - 400 + 40 + 20 - - Qt::CustomContextMenu + + + + + + + 16777215 + 16777215 + - List of available mods. + Open list options... - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Refresh list. This is usually not necessary unless you modified data outside the program. - + + + + + + :/MO/gui/settings:/MO/gui/settings + + + + 16 + 16 + + + + + + + + Show Open Folders menu... + + + + + + + :/MO/gui/open_folder:/MO/gui/open_folder + + + + + + + Restore Backup... + + - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + :/MO/gui/restore:/MO/gui/restore - - true + + + + + + Create Backup - - true + + - - QAbstractItemView::DragDrop + + + :/MO/gui/backup:/MO/gui/backup - - Qt::MoveAction + + + + + + Active: - - true + + + + + + + 0 + 26 + - - QAbstractItemView::ExtendedSelection + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - QAbstractItemView::SelectRows + + QFrame::Sunken - - 20 + + 5 - - true + + QLCDNumber::Flat - - true + + + + + + + + + 330 + 400 + + + + Qt::CustomContextMenu + + + List of available mods. + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 20 + + + true + + + true + + + true + + + false + + + 35 + + + true + + + false + + + + + + + + + + 20 + 16777215 + - - true + + x - - false + + + 20 + 20 + - - 35 - - + true - - + + + + + + + + 0 + 0 + + + + Filter + + + + + + + + 8 + true + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 95 + 0 + + + false - + + + Qt::RightToLeft + + + border:1px solid #ff0000; + + + Clear all Filters + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + 12 + 12 + + - - - - - - 20 - 16777215 - - - - x - - - - 20 - 20 - - - - true - - - - - - - - 0 - 0 - - - - Filter - - - - - - - - 8 - true - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - - 95 - 0 - - - - false - - - Qt::RightToLeft - - - border:1px solid #ff0000; - - - Clear all Filters - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - 12 - 12 - - - + + + + 220 + 0 + + + + Qt::ClickFocus + + + + No groups + - - - - 220 - 0 - - - - Qt::ClickFocus - - - - No groups - - - - - Categories - - - - - Nexus IDs - - - + + Categories + - - - - 220 - 0 - - - - Filter - - + + Nexus IDs + - + - - - - - - + + + + 220 + 0 + + + + Filter + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 9 + 75 + true + + + + Pick a program to run. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + + + + 32 + 32 + + + + false + + + + + - + - + 0 0 - 0 - 40 + 120 + 0 + + + + + 16777215 + 16777215 - 9 + 10 75 true - Pick a program to run. + Run program <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + + Run + + + + :/MO/gui/run:/MO/gui/run - 32 - 32 + 36 + 36 - - false - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - Run program - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - - - - - - Run - - - - :/MO/gui/run:/MO/gui/run - - - - 36 - 36 - - - - - - - - - 0 - 0 - - - - - 140 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - Create a shortcut in your start menu or on the desktop to the specified program - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 0 + 0 + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - - - Shortcut - - - - :/MO/gui/link:/MO/gui/link - - - - + + + Shortcut + + + + :/MO/gui/link:/MO/gui/link + + - - - - - - - 340 - 250 - - - - - 16777215 - 16777215 - + + + + + + + + + 340 + 250 + + + + + 16777215 + 16777215 + + + + Qt::NoContextMenu + + + QTabWidget::Rounded + + + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Plugins + + + + 6 - - Qt::NoContextMenu + + 6 - - QTabWidget::Rounded + + 6 - + 0 - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Plugins - - - - 6 + + + + + + true + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 4 + + + QLCDNumber::Flat + + + + + + + + + + 250 + 250 + + + + Qt::CustomContextMenu + + + List of available esp/esm files + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 0 + + + true - - 6 + + false - - 6 + + true - - 0 + + false + + false + + + + + - - - - - true - - - Sort - - - - :/MO/gui/sort:/MO/gui/sort - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - 16 - 16 - - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 4 - - - QLCDNumber::Flat - - - - - - - - - - 250 - 250 - + + + - - Qt::CustomContextMenu + + Filter + + + + + + + + + false + + + Archives + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + - List of available esp/esm files - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 0 - - - true + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - false + + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + true - - false - - - false - - - - - - - - - - Filter - - - - - - - - - false - - - Archives - - - - 6 + + + + + Qt::CustomContextMenu - - 6 + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - 6 + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - 6 + + false + + false + + + false + + + 20 + + + true + + + 1 + + + + + + + + Data + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + refresh data-directory overview + + + Refresh the overview. This may take a moment. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + - - - - - <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - - true - - - - - - - + Qt::CustomContextMenu - - List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. - By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! - - BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - - false - - - false - - - false + This is an overview of your data directory as visible to the game (and tools). - - 20 - - + true - - 1 + + true + + 400 + + + + File + + + + + Mod + + - - - - Data - - - - 6 - - - 6 - - - 6 - - - 6 - + + + - + - refresh data-directory overview + Filters the above list so that only conflicts are displayed. - Refresh the overview. This may take a moment. + Filters the above list so that only conflicts are displayed. - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show only conflicts - - - - - Qt::CustomContextMenu - - - This is an overview of your data directory as visible to the game (and tools). - - - true - - - true - - - 400 - - - - File - - - - - Mod - - - - - - - - - - - - Filters the above list so that only conflicts are displayed. - - - Filters the above list so that only conflicts are displayed. - - - Show only conflicts - - - - - - - Filters the above list so that files from archives are not shown - - - - - - Filters the above list so that files from archives are not shown - - - Show files from Archives - - - - + + + Filters the above list so that files from archives are not shown + + + + + + Filters the above list so that files from archives are not shown + + + Show files from Archives + + - - - - Saves - - - - 6 + + + + + + Saves + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::CustomContextMenu + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows - - 6 + + + + + + + Downloads + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Refresh downloads view - - 6 + + Refresh - - 6 + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + - + + + + 320 + 0 + + Qt::CustomContextMenu + + true + - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. + + + Qt::ScrollBarAlwaysOn + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ScrollPerPixel + + + 0 - - QAbstractItemView::ExtendedSelection + + false - - QAbstractItemView::SelectRows + + true - - - - Downloads - - - - 2 - - - 2 - - - 2 - - - 2 - + + + - - - Refresh downloads view - + - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show Hidden - - - - - - 320 - 0 - - - - Qt::CustomContextMenu - - - true - - - - - - This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - - Qt::ScrollBarAlwaysOn - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ScrollPerPixel - - - 0 - - - false - - - true - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - + + + Filter + + - - - - - - - - + + + + + + + + + + @@ -1320,7 +1320,7 @@ p, li { white-space: pre-wrap; } - + @@ -1790,6 +1790,11 @@ p, li { white-space: pre-wrap; } QTreeView
loglist.h
+ + StatusBar + QStatusBar +
statusbar.h
+
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2d11dafd..a2b0fd69 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -335,10 +335,6 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(m_Settings); - } - if (m_CurrentProfile != nullptr) { m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } diff --git a/src/settings.cpp b/src/settings.cpp index db6cecdf..06b4446a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -942,6 +942,12 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } +template +QString visibilitySettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_visibility"; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) @@ -962,18 +968,6 @@ void GeometrySettings::resetIfNeeded() m_Settings.beginGroup("geometry"); m_Settings.remove(""); m_Settings.endGroup(); - - /*settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry");*/ } void GeometrySettings::saveGeometry(const QWidget* w) @@ -1036,15 +1030,32 @@ bool GeometrySettings::restoreState(QSplitter* w) const return false; } -bool GeometrySettings::restoreToolbars(QMainWindow* w) const +void GeometrySettings::saveVisibility(const QWidget* w) { - const auto size = getOptional(m_Settings, "toolbar_size"); - const auto style = getOptional(m_Settings, "toolbar_button_style"); + m_Settings.setValue(visibilitySettingName(w), w->isVisible()); +} - if (!size && !style) { - return false; +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +{ + auto v = getOptional(m_Settings, visibilitySettingName(w)); + if (!v) { + v = def; + } + + if (v) { + w->setVisible(*v); + return true; } + return false; +} + +void GeometrySettings::restoreToolbars(QMainWindow* w) const +{ + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + for (auto* tb : w->findChildren()) { if (size) { tb->setIconSize(*size); @@ -1053,53 +1064,28 @@ bool GeometrySettings::restoreToolbars(QMainWindow* w) const if (style) { tb->setToolButtonStyle(static_cast(*style)); } - } - return true; + restoreVisibility(tb); + } } void GeometrySettings::saveToolbars(const QMainWindow* w) { - // all toolbars are identical, just save the first one const auto tbs = w->findChildren(); - if (tbs.isEmpty()) { - return; - } - - const auto* tb = tbs[0]; - - m_Settings.setValue("toolbar_size", tb->iconSize()); - m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); -} - -std::optional GeometrySettings::getMenubarVisible() const -{ - return getOptional(m_Settings, "menubar_visible"); -} - -void GeometrySettings::setMenubarVisible(bool b) -{ - m_Settings.setValue("menubar_visible", b); -} - -std::optional GeometrySettings::getStatusbarVisible() const -{ - return getOptional(m_Settings, "statusbar_visible"); -} -void GeometrySettings::setStatusbarVisible(bool b) -{ - m_Settings.setValue("statusbar_visible", b); -} + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } -std::optional GeometrySettings::getFiltersVisible() const -{ - return getOptional(m_Settings, "filters_visible"); -} + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; -void GeometrySettings::setFiltersVisible(bool b) -{ - m_Settings.setValue("filters_visible", b); + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); + } } QStringList GeometrySettings::getModInfoTabOrder() const diff --git a/src/settings.h b/src/settings.h index 9ae58803..072b4066 100644 --- a/src/settings.h +++ b/src/settings.h @@ -54,6 +54,7 @@ public: void requestReset(); void resetIfNeeded(); + void saveGeometry(const QWidget* w); bool restoreGeometry(QWidget* w) const; @@ -69,17 +70,13 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - std::optional getMenubarVisible() const; - void setMenubarVisible(bool b); - bool restoreToolbars(QMainWindow* w) const; - void saveToolbars(const QMainWindow* w); + void saveVisibility(const QWidget* w); + bool restoreVisibility(QWidget* w, std::optional defaultValue={}) const; - std::optional getStatusbarVisible() const; - void setStatusbarVisible(bool b); - std::optional getFiltersVisible() const; - void setFiltersVisible(bool b); + void saveToolbars(const QMainWindow* w); + void restoreToolbars(QMainWindow* w) const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index e9a6e658..d22010a5 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -3,26 +3,32 @@ #include "settings.h" #include "ui_mainwindow.h" -StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : - m_bar(bar), m_progress(new QProgressBar), - m_notifications(new StatusBarAction(ui->actionNotifications)), - m_update(new StatusBarAction(ui->actionUpdate)), - m_api(new QLabel) +StatusBar::StatusBar(QWidget* parent) : + QStatusBar(parent), ui(nullptr), m_progress(new QProgressBar), + m_notifications(nullptr), m_update(nullptr), m_api(new QLabel) { +} + +void StatusBar::setup(Ui::MainWindow* mainWindowUI) +{ + ui = mainWindowUI; + m_notifications = new StatusBarAction(ui->actionNotifications); + m_update = new StatusBarAction(ui->actionUpdate); + QWidget* spacer1 = new QWidget; spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer1->setHidden(true); spacer1->setVisible(true); - m_bar->addPermanentWidget(spacer1, 0); - m_bar->addPermanentWidget(m_progress); + addPermanentWidget(spacer1, 0); + addPermanentWidget(m_progress); QWidget* spacer2 = new QWidget; spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer2->setHidden(true); spacer2->setVisible(true); - m_bar->addPermanentWidget(spacer2,0); - m_bar->addPermanentWidget(m_notifications); - m_bar->addPermanentWidget(m_update); - m_bar->addPermanentWidget(m_api); + addPermanentWidget(spacer2,0); + addPermanentWidget(m_notifications); + addPermanentWidget(m_update); + addPermanentWidget(m_api); m_progress->setTextVisible(true); @@ -42,7 +48,7 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : "be unable to queue downloads, check updates, parse mod info, or even log " "in. Both pools must be consumed before this happens.")); - m_bar->clearMessage(); + clearMessage(); setProgress(-1); setAPI({}, {}); } @@ -50,10 +56,10 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : void StatusBar::setProgress(int percent) { if (percent < 0 || percent >= 100) { - m_bar->clearMessage(); + clearMessage(); m_progress->setVisible(false); } else { - m_bar->showMessage(QObject::tr("Loading...")); + showMessage(QObject::tr("Loading...")); m_progress->setVisible(true); m_progress->setValue(percent); } @@ -126,6 +132,37 @@ void StatusBar::checkSettings(const Settings& settings) m_api->setVisible(!settings.hideAPICounter()); } +void StatusBar::showEvent(QShowEvent*) +{ + visibilityChanged(true); +} + +void StatusBar::hideEvent(QHideEvent*) +{ + visibilityChanged(false); +} + +void StatusBar::visibilityChanged(bool visible) +{ + // the central widget typically has no bottom padding because the status bar + // is more than enough, but when it's hidden, the bottom widget (currently + // the log) touches the bottom border of the window, which looks ugly + // + // when hiding the statusbar, the central widget is given the same border + // margin as it has on the top (which is typically 6, as it's the default from + // the qt designer) + + auto m = ui->centralWidget->layout()->contentsMargins(); + + if (visible) { + m.setBottom(0); + } else { + m.setBottom(m.top()); + } + + ui->centralWidget->layout()->setContentsMargins(m); +} + StatusBarAction::StatusBarAction(QAction* action) : m_action(action), m_icon(new QLabel), m_text(new QLabel) diff --git a/src/statusbar.h b/src/statusbar.h index 2baf12ee..442b9acf 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -29,10 +29,12 @@ private: }; -class StatusBar +class StatusBar : public QStatusBar { public: - StatusBar(QStatusBar* bar, Ui::MainWindow* ui); + StatusBar(QWidget* parent=nullptr); + + void setup(Ui::MainWindow* ui); void setProgress(int percent); void setNotifications(bool hasNotifications); @@ -40,12 +42,18 @@ public: void setUpdateAvailable(bool b); void checkSettings(const Settings& settings); +protected: + void showEvent(QShowEvent* e); + void hideEvent(QHideEvent* e); + private: - QStatusBar* m_bar; + Ui::MainWindow* ui; QProgressBar* m_progress; StatusBarAction* m_notifications; StatusBarAction* m_update; QLabel* m_api; + + void visibilityChanged(bool visible); }; #endif // MO_STATUSBAR_H -- cgit v1.3.1 From 965eccb328a0a2b0cb4d1945a0382df9f0f91147 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 10:29:42 -0400 Subject: merged DockFixer into GeometrySettings added combobox index to settings --- src/mainwindow.cpp | 156 ++++++-------------------------- src/mainwindow.h | 2 - src/pch.h | 1 + src/settings.cpp | 258 +++++++++++++++++++++++++++++++++++------------------ src/settings.h | 18 ++-- 5 files changed, 210 insertions(+), 225 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7e471d24..bce92e48 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -194,90 +194,6 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); -// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock -// sizes are not restored when the main window is maximized; it is used in -// MainWindow::readSettings() and MainWindow::storeSettings() -// -// there's also https://stackoverflow.com/questions/44005852, which has what -// seems to be a popular fix, but it breaks the restored size of the window -// by setting it to the desktop's resolution, so that doesn't work -// -// the only fix I could find is to remember the sizes of the docks and manually -// setting them back; saving is straightforward, but restoring is messy -// -// this also depends on the window being visible before the timer in restore() -// is fired and the timer must be processed by application.exec(); therefore, -// the splash screen _must_ be closed before readSettings() is called, because -// it has its own event loop, which seems to interfere with this -// -// all of this should become unnecessary when QTBUG-46620 is fixed -// -class DockFixer -{ -public: - static void save(MainWindow* mw, Settings& settings) - { - // saves the size of each dock - for (const auto* dock : mw->findChildren()) { - int size = 0; - - // save the width for horizontal docks, or the height for vertical - if (orientation(mw, dock) == Qt::Horizontal) { - size = dock->size().width(); - } else { - size = dock->size().height(); - } - - settings.geometry().setDockSize(dock->objectName(), size); - } - } - - static void restore(MainWindow* mw, const Settings& settings) - { - struct DockInfo - { - QDockWidget* d; - int size = 0; - Qt::Orientation ori; - }; - - std::vector dockInfos; - - // for each dock - for (auto* dock : mw->findChildren()) { - if (auto size=settings.geometry().getDockSize(dock->objectName())) { - // remember this dock, its size and orientation - dockInfos.push_back({dock, *size, orientation(mw, dock)}); - } - } - - // the main window must have had time to process the settings from - // readSettings() or it seems to override whatever is set here - // - // some people said a single processEvents() call is enough, but it doesn't - // look like it - QTimer::singleShot(5, [=] { - for (const auto& info : dockInfos) { - mw->resizeDocks({info.d}, {info.size}, info.ori); - } - }); - } - - static Qt::Orientation orientation(QMainWindow* mw, const QDockWidget* d) - { - // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; - - if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { - return Qt::Horizontal; - } else { - return Qt::Vertical; - } - } -}; - - MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -1328,12 +1244,10 @@ void MainWindow::showEvent(QShowEvent *event) QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); } - m_OrganizerCore.settings().directInterface().setValue("first_start", false); + m_OrganizerCore.settings().directInterface().setValue("first_start", false); } - // this has no visible impact when called before the ui is visible - int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt(); - ui->groupCombo->setCurrentIndex(grouping); + m_OrganizerCore.settings().restoreIndex(ui->groupCombo); allowListResize(); @@ -1621,18 +1535,6 @@ void MainWindow::startExeAction() } - -void MainWindow::setExecutableIndex(int index) -{ - QComboBox *executableBox = findChild("executablesListBox"); - - if ((index != 0) && (executableBox->count() > index)) { - executableBox->setCurrentIndex(index); - } else { - executableBox->setCurrentIndex(1); - } -} - void MainWindow::activateSelectedProfile() { m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); @@ -1895,7 +1797,7 @@ void MainWindow::refreshExecutablesList() ++i; } - setExecutableIndex(1); + ui->executablesListBox->setCurrentIndex(1); executablesList->setEnabled(true); } @@ -2230,11 +2132,24 @@ void MainWindow::readSettings(const Settings& settings) { settings.geometry().restoreGeometry(this); settings.geometry().restoreState(this); + settings.geometry().restoreDocks(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); settings.geometry().restoreVisibility(ui->menuBar); settings.geometry().restoreVisibility(ui->statusBar); + { + // special case in case someone puts 0 in the INI + auto v = settings.getIndex(ui->executablesListBox); + if (!v || v == 0) { + v = 1; + } + + ui->executablesListBox->setCurrentIndex(*v); + } + + settings.restoreIndex(ui->groupCombo); + { settings.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); @@ -2242,17 +2157,11 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (auto v=settings.getSelectedExecutable()) { - setExecutableIndex(*v); - } - if (auto v=settings.getUseProxy()) { if (*v) { activateProxy(true); } } - - DockFixer::restore(this, settings); } void MainWindow::processUpdates(Settings& settings) { @@ -2297,15 +2206,11 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) { - auto& settings = s.directInterface(); - - settings.setValue("group_state", ui->groupCombo->currentIndex()); - settings.setValue("selected_executable", - ui->executablesListBox->currentIndex()); - +void MainWindow::storeSettings(Settings& s) +{ s.geometry().saveState(this); s.geometry().saveGeometry(this); + s.geometry().saveDocks(this); s.geometry().saveVisibility(ui->menuBar); s.geometry().saveVisibility(ui->statusBar); @@ -2319,7 +2224,8 @@ void MainWindow::storeSettings(Settings& s) { s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); - DockFixer::save(this, s); + s.saveIndex(ui->groupCombo); + s.saveIndex(ui->executablesListBox); } ILockedWaitingForProcess* MainWindow::lock() @@ -2451,20 +2357,16 @@ bool MainWindow::modifyExecutablesDialog() void MainWindow::on_executablesListBox_currentIndexChanged(int index) { - QComboBox* executablesList = findChild("executablesListBox"); + if (!ui->executablesListBox->isEnabled()) { + return; + } - int previousIndex = m_OldExecutableIndex; + const int previousIndex = m_OldExecutableIndex; m_OldExecutableIndex = index; - if (executablesList->isEnabled()) { - //I think the 2nd test is impossible - if ((index == 0) || (index > static_cast(m_OrganizerCore.executablesList()->size()))) { - if (modifyExecutablesDialog()) { - setExecutableIndex(previousIndex); - } - } else { - setExecutableIndex(index); - } + if (index == 0) { + modifyExecutablesDialog(); + ui->executablesListBox->setCurrentIndex(previousIndex); } } @@ -2540,7 +2442,7 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { if (modifyExecutablesDialog()) { - setExecutableIndex(m_OldExecutableIndex); + ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index a905a163..6f06b9d5 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -226,8 +226,6 @@ private: QMenu* createPopupMenu() override; void activateSelectedProfile(); - void setExecutableIndex(int index); - void startSteam(); void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon); diff --git a/src/pch.h b/src/pch.h index dd65efbe..af1a4ade 100644 --- a/src/pch.h +++ b/src/pch.h @@ -95,6 +95,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index 06b4446a..40f4dd95 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,13 +28,88 @@ along with Mod Organizer. If not, see . using namespace MOBase; template -std::optional getOptional(const QSettings& s, const QString& name) +std::optional getOptional( + const QSettings& s, const QString& name, std::optional def={}) { if (s.contains(name)) { return s.value(name).value(); } - return {}; + return def; +} + + +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; + + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) +{ + return w->objectName(); +} + +QString widgetName(const QHeaderView* w) +{ + return widgetNameWithTopLevel(w->parentWidget()); +} + +QString widgetName(const QWidget* w) +{ + return widgetNameWithTopLevel(w); +} + +template +QString geoSettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_geometry"; +} + +template +QString stateSettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_state"; +} + +template +QString visibilitySettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_visibility"; +} + +QString dockSettingName(const QDockWidget* dock) +{ + return "geometry/MainWindow_docks_" + dock->objectName() + "_size"; +} + +QString indexSettingName(const QWidget* widget) +{ + return widgetNameWithTopLevel(widget) + "_index"; } @@ -395,11 +470,6 @@ void Settings::setStyleName(const QString& name) m_Settings.setValue("Settings/style", name); } -std::optional Settings::getSelectedExecutable() const -{ - return getOptional(m_Settings, "selected_executable"); -} - std::optional Settings::getUseProxy() const { return getOptional(m_Settings, "Settings/use_proxy"); @@ -847,6 +917,23 @@ void Settings::setExecutables(const std::vector>& v) m_Settings.endArray(); } +std::optional Settings::getIndex(QComboBox* cb) const +{ + return getOptional(m_Settings, indexSettingName(cb)); +} + +void Settings::saveIndex(const QComboBox* cb) +{ + m_Settings.setValue(indexSettingName(cb), cb->currentIndex()); +} + +void Settings::restoreIndex(QComboBox* cb, std::optional def) const +{ + if (auto v=getOptional(m_Settings, indexSettingName(cb), def)) { + cb->setCurrentIndex(*v); + } +} + GeometrySettings& Settings::geometry() { return m_Geometry; @@ -885,70 +972,6 @@ void Settings::dump() const } -QString widgetNameWithTopLevel(const QWidget* widget) -{ - QStringList components; - - auto* tl = widget->window(); - - if (tl == widget) { - // this is a top level widget, such as a dialog - components.push_back(widget->objectName()); - } else { - // this is a widget - const auto toplevelName = tl->objectName(); - if (!toplevelName.isEmpty()) { - components.push_back(toplevelName); - } - - const auto widgetName = widget->objectName(); - if (!widgetName.isEmpty()) { - components.push_back(widgetName); - } - } - - if (components.isEmpty()) { - // can't do much - return "unknown_widget"; - } - - return components.join("_"); -} - -QString widgetName(const QMainWindow* w) -{ - return w->objectName(); -} - -QString widgetName(const QHeaderView* w) -{ - return widgetNameWithTopLevel(w->parentWidget()); -} - -QString widgetName(const QWidget* w) -{ - return widgetNameWithTopLevel(w); -} - -template -QString geoSettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_geometry"; -} - -template -QString stateSettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_state"; -} - -template -QString visibilitySettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_visibility"; -} - - GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) { @@ -1037,12 +1060,7 @@ void GeometrySettings::saveVisibility(const QWidget* w) bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - auto v = getOptional(m_Settings, visibilitySettingName(w)); - if (!v) { - v = def; - } - - if (v) { + if (auto v=getOptional(m_Settings, visibilitySettingName(w), def)) { w->setVisible(*v); return true; } @@ -1124,14 +1142,10 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } -std::optional GeometrySettings::getMainWindowMonitor() const -{ - return getOptional(m_Settings, "geometry/MainWindow_monitor"); -} - void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - const auto monitor = getMainWindowMonitor(); + const auto monitor = getOptional(m_Settings, "geometry/MainWindow_monitor"); + QPoint center; if (monitor && QGuiApplication::screens().size() > *monitor) { @@ -1153,14 +1167,84 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) } } -void GeometrySettings::setDockSize(const QString& name, int size) +Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) { - m_Settings.setValue("geometry/MainWindow_docks_" + name + "_size", size); + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } } -std::optional GeometrySettings::getDockSize(const QString& name) const +void GeometrySettings::saveDocks(const QMainWindow* mw) +{ + // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock + // sizes are not restored when the main window is maximized; it is used in + // MainWindow::readSettings() and MainWindow::storeSettings() + // + // there's also https://stackoverflow.com/questions/44005852, which has what + // seems to be a popular fix, but it breaks the restored size of the window + // by setting it to the desktop's resolution, so that doesn't work + // + // the only fix I could find is to remember the sizes of the docks and manually + // setting them back; saving is straightforward, but restoring is messy + // + // this also depends on the window being visible before the timer in restore() + // is fired and the timer must be processed by application.exec(); therefore, + // the splash screen _must_ be closed before readSettings() is called, because + // it has its own event loop, which seems to interfere with this + // + // all of this should become unnecessary when QTBUG-46620 is fixed + // + + // saves the size of each dock + for (const auto* dock : mw->findChildren()) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (dockOrientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); + } else { + size = dock->size().height(); + } + + m_Settings.setValue(dockSettingName(dock), size); + } +} + +void GeometrySettings::restoreDocks(QMainWindow* mw) const { - return getOptional(m_Settings, "geometry/MainWindow_docks_" + name + "_size"); + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector dockInfos; + + // for each dock + for (auto* dock : mw->findChildren()) { + if (auto size=getOptional(m_Settings, dockSettingName(dock))) { + // remember this dock, its size and orientation + dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + } + } + + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(5, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); } diff --git a/src/settings.h b/src/settings.h index 072b4066..1b6616a0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,25 +70,21 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - void saveVisibility(const QWidget* w); - bool restoreVisibility(QWidget* w, std::optional defaultValue={}) const; - + bool restoreVisibility(QWidget* w, std::optional def={}) const; void saveToolbars(const QMainWindow* w); void restoreToolbars(QMainWindow* w) const; + void saveDocks(const QMainWindow* w); + void restoreDocks(QMainWindow* w) const; + QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); - std::optional getMainWindowMonitor() const; void centerOnMainWindowMonitor(QWidget* w); void saveMainWindowMonitor(const QMainWindow* w); - void setDockSize(const QString& name, int size); - - std::optional getDockSize(const QString& name) const; - private: QSettings& m_Settings; bool m_Reset; @@ -206,7 +202,6 @@ public: std::optional getStyleName() const; void setStyleName(const QString& name); - std::optional getSelectedExecutable() const; std::optional getUseProxy() const; std::optional getVersion() const; @@ -222,6 +217,11 @@ public: std::vector> getExecutables() const; void setExecutables(const std::vector>& v); + + std::optional getIndex(QComboBox* cb) const; + void saveIndex(const QComboBox* cb); + void restoreIndex(QComboBox* cb, std::optional def={}) const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From d9cb15f1d117b91f0d75c1b7702696f7da93d3d2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 07:42:37 -0400 Subject: put endorsement state and first start in settings --- src/mainwindow.cpp | 54 +++++++++++++++++++++++++++++++++++++----------------- src/settings.cpp | 18 ++++++++++++++++++ src/settings.h | 11 +++++++++++ 3 files changed, 66 insertions(+), 17 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bce92e48..f0e2fe56 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1225,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { + if (m_OrganizerCore.settings().getFirstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -1244,7 +1244,7 @@ void MainWindow::showEvent(QShowEvent *event) QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); } - m_OrganizerCore.settings().directInterface().setValue("first_start", false); + m_OrganizerCore.settings().setFirstStart(false); } m_OrganizerCore.settings().restoreIndex(ui->groupCombo); @@ -5567,22 +5567,42 @@ void MainWindow::modUpdateCheck(std::multimap IDs) void MainWindow::toggleMO2EndorseState() { - if (Settings::instance().endorsementIntegration()) { - ui->actionEndorseMO->setVisible(true); - if (Settings::instance().directInterface().contains("endorse_state")) { - ui->actionEndorseMO->menu()->setEnabled(false); - if (Settings::instance().directInterface().value("endorse_state").toString() == "Endorsed") { - ui->actionEndorseMO->setToolTip(tr("Thank you for endorsing MO2! :)")); - ui->actionEndorseMO->setStatusTip(tr("Thank you for endorsing MO2! :)")); - } else if (Settings::instance().directInterface().value("endorse_state").toString() == "Abstained") { - ui->actionEndorseMO->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!")); - ui->actionEndorseMO->setStatusTip(tr("Please reconsider endorsing MO2 on Nexus!")); - } - } else { - ui->actionEndorseMO->menu()->setEnabled(true); - } - } else + const auto& s = m_OrganizerCore.settings(); + + if (!s.endorsementIntegration()) { ui->actionEndorseMO->setVisible(false); + return; + } + + ui->actionEndorseMO->setVisible(true); + + bool enabled = false; + QString text; + + switch (s.endorsementState()) + { + case EndorsementState::Accepted: + { + text = tr("Thank you for endorsing MO2! :)"); + break; + } + + case EndorsementState::Refused: + { + text = tr("Please reconsider endorsing MO2 on Nexus!"); + break; + } + + case EndorsementState::NoDecision: + { + enabled = true; + break; + } + } + + ui->actionEndorseMO->menu()->setEnabled(enabled); + ui->actionEndorseMO->setToolTip(text); + ui->actionEndorseMO->setStatusTip(text); } void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) diff --git a/src/settings.cpp b/src/settings.cpp index 40f4dd95..882984f3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -489,6 +489,11 @@ bool Settings::getFirstStart() const return getOptional(m_Settings, "first_start").value_or(true); } +void Settings::setFirstStart(bool b) +{ + m_Settings.setValue("first_start", b); +} + std::optional Settings::getPreviousSeparatorColor() const { const auto c = getOptional(m_Settings, "previousSeparatorColor"); @@ -689,6 +694,19 @@ bool Settings::endorsementIntegration() const return m_Settings.value("Settings/endorsement_integration", true).toBool(); } +EndorsementState Settings::endorsementState() const +{ + const auto v = getOptional(m_Settings, "endorse_state"); + + if (!v) { + return EndorsementState::NoDecision; + } else if (*v == "Abstained") { + return EndorsementState::Refused; + } else { + return EndorsementState::Accepted; + } +} + bool Settings::hideAPICounter() const { return m_Settings.value("Settings/hide_api_counter", false).toBool(); diff --git a/src/settings.h b/src/settings.h index 1b6616a0..167c74fc 100644 --- a/src/settings.h +++ b/src/settings.h @@ -91,6 +91,13 @@ private: }; +enum class EndorsementState +{ + Accepted = 1, + Refused, + NoDecision +}; + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc @@ -205,7 +212,9 @@ public: std::optional getUseProxy() const; std::optional getVersion() const; + bool getFirstStart() const; + void setFirstStart(bool b); std::optional getPreviousSeparatorColor() const; void setPreviousSeparatorColor(const QColor& c) const; @@ -354,6 +363,8 @@ public: */ bool endorsementIntegration() const; + EndorsementState endorsementState() const; + /** * @return true if the API counter should be hidden */ -- cgit v1.3.1 From 7cc5f220520ab19940462fb6d2f660d8b7e2d600 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 08:27:13 -0400 Subject: put tutorials in the settings finished moving endorsement to settings --- src/mainwindow.cpp | 52 ++++++++++++++++++++++++++++++++++++++++----------- src/settings.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++----- src/settings.h | 8 ++++++++ 3 files changed, 99 insertions(+), 16 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f0e2fe56..6e77f507 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1194,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings().directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { + if (!m_OrganizerCore.settings().isTutorialCompleted(windowName)) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -3017,7 +3017,7 @@ void MainWindow::untrack_clicked() void MainWindow::windowTutorialFinished(const QString &windowName) { - m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); + m_OrganizerCore.settings().setTutorialCompleted(windowName); } void MainWindow::overwriteClosed(int) @@ -5636,7 +5636,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData if (Settings::instance().endorsementIntegration()) { if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { - Settings::instance().directInterface().setValue("endorse_state", result->second.second); + m_OrganizerCore.settings().setEndorsementState( + endorsementStateFromString(result->second.second)); + toggleMO2EndorseState(); } } @@ -5649,7 +5651,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { - Settings::instance().directInterface().setValue("endorse_state", result->second.second); + m_OrganizerCore.settings().setEndorsementState( + endorsementStateFromString(result->second.second)); + toggleMO2EndorseState(); break; } @@ -5829,15 +5833,41 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int) { - QMap results = resultData.toMap(); - if (results["status"].toString().compare("Endorsed") == 0) { - QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - Settings::instance().directInterface().setValue("endorse_state", "Endorsed"); - } else if (results["status"].toString().compare("Abstained") == 0) { - QMessageBox::information(this, tr("Okay."), tr("This mod will not be endorsed and will no longer ask you to endorse.")); - Settings::instance().directInterface().setValue("endorse_state", "Abstained"); + const QMap results = resultData.toMap(); + + auto itor = results.find("status"); + if (itor == results.end()) { + log::error("endorsement response has no status"); + return; + } + + const auto s = endorsementStateFromString(itor->toString()); + + switch (s) + { + case EndorsementState::Accepted: + { + QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); + break; + } + + case EndorsementState::Refused: + { + // don't spam message boxes if the user doesn't want to endorse + log::info("Mod Organizer will not be endorsed and will no longer ask you to endorse."); + break; + } + + case EndorsementState::NoDecision: + { + log::error("bad status '{}' in endorsement response", itor->toString()); + return; + } } + + m_OrganizerCore.settings().setEndorsementState(s); toggleMO2EndorseState(); + if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { log::error("failed to disconnect endorsement slot"); diff --git a/src/settings.cpp b/src/settings.cpp index 882984f3..af32a082 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -39,6 +39,34 @@ std::optional getOptional( } +EndorsementState endorsementStateFromString(const QString& s) +{ + if (s == "Endorsed") { + return EndorsementState::Accepted; + } else if (s == "Abstained") { + return EndorsementState::Refused; + } else { + return EndorsementState::NoDecision; + } +} + +QString toString(EndorsementState s) +{ + switch (s) + { + case EndorsementState::Accepted: + return "Endorsed"; + + case EndorsementState::Refused: + return "Abstained"; + + case EndorsementState::NoDecision: // fall-through + default: + return {}; + } +} + + QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -697,13 +725,17 @@ bool Settings::endorsementIntegration() const EndorsementState Settings::endorsementState() const { const auto v = getOptional(m_Settings, "endorse_state"); + return endorsementStateFromString(v.value_or("")); +} - if (!v) { - return EndorsementState::NoDecision; - } else if (*v == "Abstained") { - return EndorsementState::Refused; +void Settings::setEndorsementState(EndorsementState s) +{ + const auto v = toString(s); + + if (v.isEmpty()) { + m_Settings.remove("endorse_state"); } else { - return EndorsementState::Accepted; + m_Settings.setValue("endorse_state", v); } } @@ -935,6 +967,19 @@ void Settings::setExecutables(const std::vector>& v) m_Settings.endArray(); } +bool Settings::isTutorialCompleted(const QString& windowName) const +{ + const auto v = getOptional( + m_Settings, "CompletedWindowTutorials/" + windowName); + + return v.value_or(false); +} + +void Settings::setTutorialCompleted(const QString& windowName, bool b) +{ + m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); +} + std::optional Settings::getIndex(QComboBox* cb) const { return getOptional(m_Settings, indexSettingName(cb)); diff --git a/src/settings.h b/src/settings.h index 167c74fc..5044af98 100644 --- a/src/settings.h +++ b/src/settings.h @@ -98,6 +98,10 @@ enum class EndorsementState NoDecision }; +EndorsementState endorsementStateFromString(const QString& s); +QString toString(EndorsementState s); + + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc @@ -226,6 +230,8 @@ public: std::vector> getExecutables() const; void setExecutables(const std::vector>& v); + bool isTutorialCompleted(const QString& windowName) const; + void setTutorialCompleted(const QString& windowName, bool b=true); std::optional getIndex(QComboBox* cb) const; void saveIndex(const QComboBox* cb); @@ -364,6 +370,8 @@ public: bool endorsementIntegration() const; EndorsementState endorsementState() const; + void setEndorsementState(EndorsementState s); + void setEndorsementState(const QString& s); /** * @return true if the API counter should be hidden -- cgit v1.3.1 From dfa15218f33ad06a6e868e8e5f1022026b6530a9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 12:42:27 -0400 Subject: passes callbacks to QuestionBoxMemory so it doesn't access the ini directly fixed selected executable being empty after closing the edit dialog put backup_install inside Settings --- src/installationmanager.cpp | 13 ++++++--- src/mainwindow.cpp | 6 ++-- src/organizercore.cpp | 2 -- src/settings.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 16 ++++++++++ src/settingsdialoggeneral.cpp | 2 +- 6 files changed, 97 insertions(+), 10 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 89d0079f..522489e4 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -476,13 +476,18 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const { QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + while (QDir(targetDirectory).exists()) { Settings &settings(Settings::instance()); - bool backup = settings.directInterface().value("backup_install", false).toBool(); - QueryOverwriteDialog overwriteDialog(m_ParentWidget, - backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + + const bool backup = settings.keepBackupOnInstall(); + QueryOverwriteDialog overwriteDialog( + m_ParentWidget, + backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + if (overwriteDialog.exec()) { - settings.directInterface().setValue("backup_install", overwriteDialog.backup()); + settings.setKeepBackupOnInstall(overwriteDialog.backup()); + if (overwriteDialog.backup()) { QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e77f507..2ce6f9d9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2340,8 +2340,6 @@ bool MainWindow::modifyExecutablesDialog() bool result = false; try { - const auto oldExecutables = *m_OrganizerCore.executablesList(); - EditExecutablesDialog dialog(m_OrganizerCore, this); result = (dialog.exec() == QDialog::Accepted); @@ -2361,7 +2359,9 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) return; } - const int previousIndex = m_OldExecutableIndex; + const int previousIndex = + (m_OldExecutableIndex > 0 ? m_OldExecutableIndex : 1); + m_OldExecutableIndex = index; if (index == 0) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a2b0fd69..233a631e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -280,8 +280,6 @@ OrganizerCore::OrganizerCore(Settings &settings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); diff --git a/src/settings.cpp b/src/settings.cpp index af32a082..9001ac65 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -151,10 +151,16 @@ Settings::Settings(const QString& path) } else { s_Instance = this; } + + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return getQuestionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); } Settings::~Settings() { + MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); s_Instance = nullptr; } @@ -980,6 +986,68 @@ void Settings::setTutorialCompleted(const QString& windowName, bool b) m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); } +bool Settings::keepBackupOnInstall() const +{ + return getOptional(m_Settings, "backup_install").value_or(false); +} + +void Settings::setKeepBackupOnInstall(bool b) +{ + m_Settings.setValue("backup_install", b); +} + +QuestionBoxMemory::Button Settings::getQuestionButton( + const QString& windowName, const QString& filename) const +{ + const QString windowSetting("DialogChoices/" + windowName); + + if (!filename.isEmpty()) { + const auto fileSetting = windowSetting + "/" + filename; + + if (auto v=getOptional(m_Settings, fileSetting)) { + return static_cast(*v); + } + } + + if (auto v=getOptional(m_Settings, windowSetting)) { + return static_cast(*v); + } + + return QuestionBoxMemory::NoButton; +} + +void Settings::setQuestionWindowButton( + const QString& windowName, QuestionBoxMemory::Button button) +{ + const QString settingName("DialogChoices/" + windowName); + + if (button == QuestionBoxMemory::NoButton) { + m_Settings.remove(settingName); + } else { + m_Settings.setValue(settingName, static_cast(button)); + } +} + +void Settings::setQuestionFileButton( + const QString& windowName, const QString& filename, + QuestionBoxMemory::Button button) +{ + const QString settingName("DialogChoices/" + windowName + "/" + filename); + + if (button == QuestionBoxMemory::NoButton) { + m_Settings.remove(settingName); + } else { + m_Settings.setValue(settingName, static_cast(button)); + } +} + +void Settings::resetQuestionButtons() +{ + m_Settings.beginGroup("DialogChoices"); + m_Settings.remove(""); + m_Settings.endGroup(); +} + std::optional Settings::getIndex(QComboBox* cb) const { return getOptional(m_Settings, indexSettingName(cb)); diff --git a/src/settings.h b/src/settings.h index 5044af98..d46c358c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include #include namespace MOBase { @@ -233,6 +234,21 @@ public: bool isTutorialCompleted(const QString& windowName) const; void setTutorialCompleted(const QString& windowName, bool b=true); + bool keepBackupOnInstall() const; + void setKeepBackupOnInstall(bool b); + + MOBase::QuestionBoxMemory::Button getQuestionButton( + const QString& windowName, const QString& filename) const; + + void setQuestionWindowButton( + const QString& windowName, MOBase::QuestionBoxMemory::Button button); + + void setQuestionFileButton( + const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); + + void resetQuestionButtons(); + std::optional getIndex(QComboBox* cb) const; void saveIndex(const QComboBox* cb); void restoreIndex(QComboBox* cb, std::optional def={}) const; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 324dc4f4..fda50220 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -141,7 +141,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - QuestionBoxMemory::resetDialogs(); + m_parent->resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) -- cgit v1.3.1 From 2ffad7edf2946e66585a67c4ab58c0522cd8e412 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 13:20:53 -0400 Subject: made member variables in SettingsTab private, added accessors SettingsDialog now uses GeometrySaver --- src/loglist.h | 2 + src/mainwindow.cpp | 2 +- src/organizercore.cpp | 9 +--- src/settingsdialog.cpp | 40 ++++++++-------- src/settingsdialog.h | 18 ++++--- src/settingsdialogdiagnostics.cpp | 16 +++---- src/settingsdialogdiagnostics.h | 2 +- src/settingsdialoggeneral.cpp | 98 +++++++++++++++++++-------------------- src/settingsdialoggeneral.h | 2 +- src/settingsdialognexus.cpp | 66 +++++++++++++------------- src/settingsdialognexus.h | 2 +- src/settingsdialogpaths.cpp | 48 +++++++++---------- src/settingsdialogpaths.h | 3 +- src/settingsdialogplugins.cpp | 26 +++++------ src/settingsdialogplugins.h | 2 +- src/settingsdialogsteam.cpp | 8 ++-- src/settingsdialogsteam.h | 5 +- src/settingsdialogworkarounds.cpp | 46 +++++++++--------- src/settingsdialogworkarounds.h | 3 +- 19 files changed, 196 insertions(+), 202 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loglist.h b/src/loglist.h index 0b25dfd1..36671be4 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -61,6 +61,8 @@ private: class LogList : public QTreeView { + Q_OBJECT; + public: LogList(QWidget* parent=nullptr); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2ce6f9d9..f1a2047f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5018,7 +5018,7 @@ void MainWindow::on_actionSettings_triggered() DownloadManager *dlManager = m_OrganizerCore.downloadManager(); - SettingsDialog dialog(&m_PluginContainer, &settings, this); + SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 233a631e..73d0abac 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -745,16 +745,11 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } + m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); refreshDirectoryStructure(); - - //This line is not actually needed and was only added to allow some - //outside detection of Mo2 profile change. (like BaobobMiller utility) - if (m_CurrentProfile != nullptr) { - settings().directInterface().setValue("selected_profile", - m_CurrentProfile->name().toUtf8().constData()); - } } MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index d74507c9..097dafc8 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -29,7 +29,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) +SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) @@ -45,18 +45,13 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti m_tabs.push_back(std::unique_ptr(new SteamSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PluginsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); - - auto& qsettings = settings->directInterface(); - - QString key = QString("geometry/%1").arg(objectName()); - if (qsettings.contains(key)) { - restoreGeometry(qsettings.value(key).toByteArray()); - } } int SettingsDialog::exec() { - auto& qsettings = m_settings->directInterface(); + GeometrySaver gs(m_settings, this); + + auto& qsettings = m_settings.directInterface(); auto ret = TutorableDialog::exec(); if (ret == QDialog::Accepted) { @@ -92,9 +87,6 @@ int SettingsDialog::exec() qsettings.endGroup(); } - QString key = QString("geometry/%1").arg(objectName()); - qsettings.setValue(key, saveGeometry()); - // These changes happen regardless of accepted or rejected bool restartNeeded = false; if (getApiKeyChanged()) { @@ -158,18 +150,24 @@ bool SettingsDialog::getApiKeyChanged() } -SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : m_parent(m_parent) - , m_Settings(m_parent->directInterface()) - , m_dialog(m_dialog) - , ui(m_dialog.ui) +SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) + : ui(d.ui), m_settings(s), m_qsettings(s.directInterface()), m_dialog(d) { } -SettingsTab::~SettingsTab() -{} +SettingsTab::~SettingsTab() = default; + +Settings& SettingsTab::settings() +{ + return m_settings; +} + +QSettings& SettingsTab::qsettings() +{ + return m_qsettings; +} -QWidget* SettingsTab::parentWidget() +SettingsDialog& SettingsTab::dialog() { - return &m_dialog; + return m_dialog; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index efc4a095..0aad8863 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -31,19 +31,23 @@ namespace Ui { class SettingsDialog; } class SettingsTab { public: - SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + SettingsTab(Settings& settings, SettingsDialog& m_dialog); virtual ~SettingsTab(); virtual void update() = 0; virtual void closing() {} protected: - Settings *m_parent; - QSettings &m_Settings; - SettingsDialog &m_dialog; Ui::SettingsDialog* ui; - QWidget* parentWidget(); + Settings& settings(); + QSettings& qsettings(); + SettingsDialog& dialog(); + +private: + Settings& m_settings; + QSettings& m_qsettings; + SettingsDialog& m_dialog; }; @@ -58,7 +62,7 @@ class SettingsDialog : public MOBase::TutorableDialog public: explicit SettingsDialog( - PluginContainer *pluginContainer, Settings* settings, QWidget *parent = 0); + PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0); ~SettingsDialog(); @@ -82,7 +86,7 @@ public: bool getApiKeyChanged(); private: - Settings* m_settings; + Settings& m_settings; std::vector> m_tabs; }; diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index daf81d5c..227d1dfa 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -6,12 +6,12 @@ using namespace MOBase; -DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { setLevelsBox(); - ui->dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); - ui->dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); + ui->dumpsTypeBox->setCurrentIndex(settings().crashDumpsType()); + ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); ui->diagnosticsExplainedLabel->setText( @@ -33,7 +33,7 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == m_parent->logLevel()) { + if (ui->logLevelBox->itemData(i) == settings().logLevel()) { ui->logLevelBox->setCurrentIndex(i); break; } @@ -42,7 +42,7 @@ void DiagnosticsSettingsTab::setLevelsBox() void DiagnosticsSettingsTab::update() { - m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); - m_Settings.setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); - m_Settings.setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); + qsettings().setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); + qsettings().setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); + qsettings().setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index 4c1805e2..f20413f8 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -7,7 +7,7 @@ class DiagnosticsSettingsTab : public SettingsTab { public: - DiagnosticsSettingsTab(Settings *parent, SettingsDialog &dialog); + DiagnosticsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index fda50220..35012db7 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -6,12 +6,12 @@ using MOBase::QuestionBoxMemory; -GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { addLanguages(); { - QString languageCode = m_parent->language(); + QString languageCode = settings().language(); int currentID = ui->languageBox->findData(languageCode); // I made a mess. :( Most languages are stored with only the iso country // code (2 characters like "de") but chinese @@ -28,31 +28,31 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia addStyles(); { int currentID = ui->styleBox->findData( - m_Settings.value("Settings/style", "").toString()); + qsettings().value("Settings/style", "").toString()); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } //version with stylesheet - setButtonColor(ui->overwritingBtn, m_parent->modlistOverwritingLooseColor()); - setButtonColor(ui->overwrittenBtn, m_parent->modlistOverwrittenLooseColor()); - setButtonColor(ui->overwritingArchiveBtn, m_parent->modlistOverwritingArchiveColor()); - setButtonColor(ui->overwrittenArchiveBtn, m_parent->modlistOverwrittenArchiveColor()); - setButtonColor(ui->containsBtn, m_parent->modlistContainsPluginColor()); - setButtonColor(ui->containedBtn, m_parent->pluginListContainedColor()); - - setOverwritingColor(m_parent->modlistOverwritingLooseColor()); - setOverwrittenColor(m_parent->modlistOverwrittenLooseColor()); - setOverwritingArchiveColor(m_parent->modlistOverwritingArchiveColor()); - setOverwrittenArchiveColor(m_parent->modlistOverwrittenArchiveColor()); - setContainsColor(m_parent->modlistContainsPluginColor()); - setContainedColor(m_parent->pluginListContainedColor()); - - ui->compactBox->setChecked(m_parent->compactDownloads()); - ui->showMetaBox->setChecked(m_parent->metaDownloads()); - ui->usePrereleaseBox->setChecked(m_parent->usePrereleases()); - ui->colorSeparatorsBox->setChecked(m_parent->colorSeparatorScrollbar()); + setButtonColor(ui->overwritingBtn, settings().modlistOverwritingLooseColor()); + setButtonColor(ui->overwrittenBtn, settings().modlistOverwrittenLooseColor()); + setButtonColor(ui->overwritingArchiveBtn, settings().modlistOverwritingArchiveColor()); + setButtonColor(ui->overwrittenArchiveBtn, settings().modlistOverwrittenArchiveColor()); + setButtonColor(ui->containsBtn, settings().modlistContainsPluginColor()); + setButtonColor(ui->containedBtn, settings().pluginListContainedColor()); + + setOverwritingColor(settings().modlistOverwritingLooseColor()); + setOverwrittenColor(settings().modlistOverwrittenLooseColor()); + setOverwritingArchiveColor(settings().modlistOverwritingArchiveColor()); + setOverwrittenArchiveColor(settings().modlistOverwrittenArchiveColor()); + setContainsColor(settings().modlistContainsPluginColor()); + setContainedColor(settings().pluginListContainedColor()); + + ui->compactBox->setChecked(settings().compactDownloads()); + ui->showMetaBox->setChecked(settings().metaDownloads()); + ui->usePrereleaseBox->setChecked(settings().usePrereleases()); + ui->colorSeparatorsBox->setChecked(settings().colorSeparatorScrollbar()); QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); @@ -67,30 +67,30 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia void GeneralSettingsTab::update() { - QString oldLanguage = m_parent->language(); + QString oldLanguage = settings().language(); QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit m_parent->languageChanged(newLanguage); + qsettings().setValue("Settings/language", newLanguage); + emit settings().languageChanged(newLanguage); } - QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString oldStyle = qsettings().value("Settings/style", "").toString(); QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit m_parent->styleChanged(newStyle); - } - - m_Settings.setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); - m_Settings.setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); - m_Settings.setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); - m_Settings.setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); - m_Settings.setValue("Settings/containsPluginColor", getContainsColor()); - m_Settings.setValue("Settings/containedColor", getContainedColor()); - m_Settings.setValue("Settings/compact_downloads", ui->compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); - m_Settings.setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); - m_Settings.setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); + qsettings().setValue("Settings/style", newStyle); + emit settings().styleChanged(newStyle); + } + + qsettings().setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); + qsettings().setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); + qsettings().setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); + qsettings().setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); + qsettings().setValue("Settings/containsPluginColor", getContainsColor()); + qsettings().setValue("Settings/containedColor", getContainedColor()); + qsettings().setValue("Settings/compact_downloads", ui->compactBox->isChecked()); + qsettings().setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); + qsettings().setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); + qsettings().setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() @@ -141,7 +141,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - m_parent->resetQuestionButtons(); + settings().resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) @@ -163,7 +163,7 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color void GeneralSettingsTab::on_containsBtn_clicked() { - QColor result = QColorDialog::getColor(m_ContainsColor, parentWidget(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_ContainsColor, &dialog(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_ContainsColor = result; setButtonColor(ui->containsBtn, result); @@ -172,7 +172,7 @@ void GeneralSettingsTab::on_containsBtn_clicked() void GeneralSettingsTab::on_containedBtn_clicked() { - QColor result = QColorDialog::getColor(m_ContainedColor, parentWidget(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_ContainedColor, &dialog(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_ContainedColor = result; setButtonColor(ui->containedBtn, result); @@ -181,7 +181,7 @@ void GeneralSettingsTab::on_containedBtn_clicked() void GeneralSettingsTab::on_overwrittenBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwrittenColor, parentWidget(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwrittenColor, &dialog(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwrittenColor = result; setButtonColor(ui->overwrittenBtn, result); @@ -190,7 +190,7 @@ void GeneralSettingsTab::on_overwrittenBtn_clicked() void GeneralSettingsTab::on_overwritingBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwritingColor, parentWidget(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwritingColor, &dialog(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwritingColor = result; setButtonColor(ui->overwritingBtn, result); @@ -199,7 +199,7 @@ void GeneralSettingsTab::on_overwritingBtn_clicked() void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, parentWidget(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, &dialog(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwrittenArchiveColor = result; setButtonColor(ui->overwrittenArchiveBtn, result); @@ -208,7 +208,7 @@ void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() void GeneralSettingsTab::on_overwritingArchiveBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, parentWidget(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, &dialog(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwritingArchiveColor = result; setButtonColor(ui->overwritingArchiveBtn, result); @@ -234,7 +234,7 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked() void GeneralSettingsTab::on_resetDialogsButton_clicked() { - if (QMessageBox::question(parentWidget(), QObject::tr("Confirm?"), + if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"), QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { resetDialogs(); @@ -243,7 +243,7 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked() void GeneralSettingsTab::on_categoriesBtn_clicked() { - CategoriesDialog dialog(parentWidget()); + CategoriesDialog dialog(&dialog()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); } diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index c7fcae36..2038ba31 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -7,7 +7,7 @@ class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 575f54d0..b1964069 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -69,18 +69,18 @@ private: }; -NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) +NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->offlineBox->setChecked(parent->offlineMode()); - ui->proxyBox->setChecked(parent->useProxy()); - ui->endorsementBox->setChecked(parent->endorsementIntegration()); - ui->hideAPICounterBox->setChecked(parent->hideAPICounter()); + ui->offlineBox->setChecked(settings().offlineMode()); + ui->proxyBox->setChecked(settings().useProxy()); + ui->endorsementBox->setChecked(settings().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); // display server preferences - m_Settings.beginGroup("Servers"); - for (const QString &key : m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); + qsettings().beginGroup("Servers"); + for (const QString &key : qsettings().childKeys()) { + QVariantMap val = qsettings().value(key).toMap(); QString descriptor = key; if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { descriptor += QStringLiteral(" (automatic)"); @@ -101,7 +101,7 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) } ui->preferredServersList->sortItems(Qt::DescendingOrder); } - m_Settings.endGroup(); + qsettings().endGroup(); QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); @@ -114,27 +114,27 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) void NexusSettingsTab::update() { - m_Settings.setValue("Settings/offline_mode", ui->offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", ui->proxyBox->isChecked()); - m_Settings.setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); - m_Settings.setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); + qsettings().setValue("Settings/offline_mode", ui->offlineBox->isChecked()); + qsettings().setValue("Settings/use_proxy", ui->proxyBox->isChecked()); + qsettings().setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); + qsettings().setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); // store server preference - m_Settings.beginGroup("Servers"); + qsettings().beginGroup("Servers"); for (int i = 0; i < ui->knownServersList->count(); ++i) { QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); + QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = 0; - m_Settings.setValue(key, val); + qsettings().setValue(key, val); } int count = ui->preferredServersList->count(); for (int i = 0; i < count; ++i) { QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); + QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = count - i; - m_Settings.setValue(key, val); + qsettings().setValue(key, val); } - m_Settings.endGroup(); + qsettings().endGroup(); } void NexusSettingsTab::on_nexusConnect_clicked() @@ -168,12 +168,12 @@ void NexusSettingsTab::on_nexusManualKey_clicked() return; } - NexusManualKeyDialog dialog(parentWidget()); - if (dialog.exec() != QDialog::Accepted) { + NexusManualKeyDialog d(&dialog()); + if (d.exec() != QDialog::Accepted) { return; } - const auto key = dialog.key(); + const auto key = d.key(); if (key.isEmpty()) { clearKey(); return; @@ -193,7 +193,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { QDir(Settings::instance().getCacheDirectory()).removeRecursively(); - NexusInterface::instance(m_dialog.m_PluginContainer)->clearCache(); + NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() @@ -205,7 +205,7 @@ void NexusSettingsTab::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager())); + *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager())); m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ onValidatorStateChanged(s, e); @@ -261,7 +261,7 @@ void NexusSettingsTab::onValidatorStateChanged( void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) { - NexusInterface::instance(m_dialog.m_PluginContainer)->setUserAccount(user); + NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user); if (!user.apiKey().isEmpty()) { if (setKey(user.apiKey())) { @@ -278,18 +278,18 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - m_dialog.m_keyChanged = true; - const bool ret = m_parent->setNexusApiKey(key); + dialog().m_keyChanged = true; + const bool ret = settings().setNexusApiKey(key); updateNexusState(); return ret; } bool NexusSettingsTab::clearKey() { - m_dialog.m_keyChanged = true; - const auto ret = m_parent->clearNexusApiKey(); + dialog().m_keyChanged = true; + const auto ret = settings().clearNexusApiKey(); - NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager()->clearApiKey(); + NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); updateNexusState(); return ret; @@ -319,7 +319,7 @@ void NexusSettingsTab::updateNexusButtons() ui->nexusManualKey->setText(QObject::tr("Cancel")); ui->nexusManualKey->setEnabled(true); } - else if (m_parent->hasNexusApiKey()) { + else if (settings().hasNexusApiKey()) { // api key is present ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); @@ -338,7 +338,7 @@ void NexusSettingsTab::updateNexusButtons() void NexusSettingsTab::updateNexusData() { - const auto user = NexusInterface::instance(m_dialog.m_PluginContainer) + const auto user = NexusInterface::instance(dialog().m_PluginContainer) ->getAPIUserAccount(); if (user.isValid()) { diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index cca2e1b5..89a6618f 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -8,7 +8,7 @@ class NexusSettingsTab : public SettingsTab { public: - NexusSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + NexusSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 6e8fe994..290ceeb3 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -3,19 +3,19 @@ #include "appconfig.h" #include -PathsSettingsTab::PathsSettingsTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) +PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->baseDirEdit->setText(m_parent->getBaseDirectory()); - ui->managedGameDirEdit->setText(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); - QString basePath = parent->getBaseDirectory(); + ui->baseDirEdit->setText(settings().getBaseDirectory()); + ui->managedGameDirEdit->setText(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); + QString basePath = settings().getBaseDirectory(); QDir baseDir(basePath); for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, m_parent->getDownloadDirectory(false)), - std::make_pair(ui->modDirEdit, m_parent->getModDirectory(false)), - std::make_pair(ui->cacheDirEdit, m_parent->getCacheDirectory(false)), - std::make_pair(ui->profilesDirEdit, m_parent->getProfileDirectory(false)), - std::make_pair(ui->overwriteDirEdit, m_parent->getOverwriteDirectory(false)) + std::make_pair(ui->downloadDirEdit, settings().getDownloadDirectory(false)), + std::make_pair(ui->modDirEdit, settings().getModDirectory(false)), + std::make_pair(ui->cacheDirEdit, settings().getCacheDirectory(false)), + std::make_pair(ui->profilesDirEdit, settings().getProfileDirectory(false)), + std::make_pair(ui->overwriteDirEdit, settings().getOverwriteDirectory(false)) }) { QString storePath = baseDir.relativeFilePath(dir.second); storePath = dir.second; @@ -42,7 +42,7 @@ void PathsSettingsTab::update() { typedef std::tuple Directory; - QString basePath = m_parent->getBaseDirectory(); + QString basePath = settings().getBaseDirectory(); for (const Directory &dir :{ Directory{ui->downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, @@ -71,30 +71,30 @@ void PathsSettingsTab::update() if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - m_Settings.setValue(settingsKey, path); + qsettings().setValue(settingsKey, path); } else { - m_Settings.remove(settingsKey); + qsettings().remove(settingsKey); } } if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - m_Settings.setValue("Settings/base_directory", ui->baseDirEdit->text()); + qsettings().setValue("Settings/base_directory", ui->baseDirEdit->text()); } else { - m_Settings.remove("Settings/base_directory"); + qsettings().remove("Settings/base_directory"); } - QFileInfo oldGameExe(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); + QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); QFileInfo newGameExe(ui->managedGameDirEdit->text()); if (oldGameExe != newGameExe) { - m_Settings.setValue("gamePath", newGameExe.absolutePath()); + qsettings().setValue("gamePath", newGameExe.absolutePath()); } } void PathsSettingsTab::on_browseBaseDirBtn_clicked() { QString temp = QFileDialog::getExistingDirectory( - parentWidget(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); + &dialog(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); if (!temp.isEmpty()) { ui->baseDirEdit->setText(temp); } @@ -105,7 +105,7 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked() QString searchPath = ui->downloadDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select download directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath); if (!temp.isEmpty()) { ui->downloadDirEdit->setText(temp); } @@ -116,7 +116,7 @@ void PathsSettingsTab::on_browseModDirBtn_clicked() QString searchPath = ui->modDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select mod directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { ui->modDirEdit->setText(temp); } @@ -127,7 +127,7 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked() QString searchPath = ui->cacheDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select cache directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { ui->cacheDirEdit->setText(temp); } @@ -138,7 +138,7 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked() QString searchPath = ui->profilesDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select profiles directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath); if (!temp.isEmpty()) { ui->profilesDirEdit->setText(temp); } @@ -149,7 +149,7 @@ void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() QString searchPath = ui->overwriteDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select overwrite directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath); if (!temp.isEmpty()) { ui->overwriteDirEdit->setText(temp); } @@ -159,7 +159,7 @@ void PathsSettingsTab::on_browseGameDirBtn_clicked() { QFileInfo oldGameExe(ui->managedGameDirEdit->text()); - QString temp = QFileDialog::getOpenFileName(parentWidget(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); + QString temp = QFileDialog::getOpenFileName(&dialog(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); if (!temp.isEmpty()) { ui->managedGameDirEdit->setText(temp); } diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h index f661b624..a2073188 100644 --- a/src/settingsdialogpaths.h +++ b/src/settingsdialogpaths.h @@ -7,8 +7,7 @@ class PathsSettingsTab : public SettingsTab { public: - PathsSettingsTab(Settings *parent, SettingsDialog &dialog); - + PathsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 53b28fcc..329ba301 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -5,26 +5,26 @@ using MOBase::IPlugin; -PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +PluginsSettingsTab::PluginsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); // display plugin settings QSet handledNames; - for (IPlugin *plugin : m_parent->plugins()) { + for (IPlugin *plugin : settings().plugins()) { if (handledNames.contains(plugin->name())) continue; QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList); listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); - listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); + listItem->setData(Qt::UserRole + 1, settings().m_PluginSettings[plugin->name()]); + listItem->setData(Qt::UserRole + 2, settings().m_PluginDescriptions[plugin->name()]); ui->pluginsList->addItem(listItem); handledNames.insert(plugin->name()); } // display plugin blacklist - for (const QString &pluginName : m_parent->m_PluginBlacklist) { + for (const QString &pluginName : settings().m_PluginBlacklist) { ui->pluginBlacklist->addItem(pluginName); } @@ -34,7 +34,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dia QShortcut *delShortcut = new QShortcut( QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - QObject::connect(delShortcut, &QShortcut::activated, parentWidget(), [&]{ deleteBlacklistItem(); }); + QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&]{ deleteBlacklistItem(); }); } void PluginsSettingsTab::update() @@ -42,21 +42,21 @@ void PluginsSettingsTab::update() // transfer plugin settings to in-memory structure for (int i = 0; i < ui->pluginsList->count(); ++i) { QListWidgetItem *item = ui->pluginsList->item(i); - m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + settings().m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); } // store plugin settings on disc - for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) { + for (auto iterPlugins = settings().m_PluginSettings.begin(); iterPlugins != settings().m_PluginSettings.end(); ++iterPlugins) { for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + qsettings().setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); } } // store plugin blacklist - m_parent->m_PluginBlacklist.clear(); + settings().m_PluginBlacklist.clear(); for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { - m_parent->m_PluginBlacklist.insert(item->text()); + settings().m_PluginBlacklist.insert(item->text()); } - m_parent->writePluginBlacklist(); + settings().writePluginBlacklist(); } void PluginsSettingsTab::closing() diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 9d21daa6..8e2dae2a 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -7,7 +7,7 @@ class PluginsSettingsTab : public SettingsTab { public: - PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + PluginsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); void closing() override; diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp index 34c2d76b..9ed93e47 100644 --- a/src/settingsdialogsteam.cpp +++ b/src/settingsdialogsteam.cpp @@ -1,11 +1,11 @@ #include "settingsdialogsteam.h" #include "ui_settingsdialog.h" -SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { QString username, password; - m_parent->getSteamLogin(username, password); + settings().getSteamLogin(username, password); ui->steamUserEdit->setText(username); ui->steamPassEdit->setText(password); @@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) void SteamSettingsTab::update() { - m_parent->setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); + settings().setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); } diff --git a/src/settingsdialogsteam.h b/src/settingsdialogsteam.h index dbd85151..6a3d75f4 100644 --- a/src/settingsdialogsteam.h +++ b/src/settingsdialogsteam.h @@ -7,11 +7,8 @@ class SteamSettingsTab : public SettingsTab { public: - SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - + SteamSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); - -private: }; #endif // SETTINGSDIALOGSTEAM_H diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index fc859289..443ba54e 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -3,15 +3,15 @@ #include "helper.h" #include -WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->appIDEdit->setText(m_parent->getSteamAppID()); + ui->appIDEdit->setText(settings().getSteamAppID()); - LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); + LoadMechanism::EMechanism mechanismID = settings().getLoadMechanism(); int index = 0; - if (m_parent->loadMechanism().isDirectLoadingSupported()) { + if (settings().loadMechanism().isDirectLoadingSupported()) { ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { index = ui->mechanismBox->count() - 1; @@ -20,13 +20,13 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo ui->mechanismBox->setCurrentIndex(index); - ui->hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); - ui->forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); - ui->displayForeignBox->setChecked(m_parent->displayForeign()); - ui->lockGUIBox->setChecked(m_parent->lockGUI()); - ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); + ui->hideUncheckedBox->setChecked(settings().hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(settings().forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(settings().displayForeign()); + ui->lockGUIBox->setChecked(settings().lockGUI()); + ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); - setExecutableBlacklist(m_parent->executablesBlacklist()); + setExecutableBlacklist(settings().executablesBlacklist()); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); @@ -35,26 +35,26 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo void WorkaroundsSettingsTab::update() { - if (ui->appIDEdit->text() != m_parent->gamePlugin()->steamAPPId()) { - m_Settings.setValue("Settings/app_id", ui->appIDEdit->text()); + if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { + qsettings().setValue("Settings/app_id", ui->appIDEdit->text()); } else { - m_Settings.remove("Settings/app_id"); + qsettings().remove("Settings/app_id"); } - m_Settings.setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); - m_Settings.setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); - m_Settings.setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); - m_Settings.setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); - m_Settings.setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); + qsettings().setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); + qsettings().setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); + qsettings().setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); + qsettings().setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); + qsettings().setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); + qsettings().setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); - m_Settings.setValue("Settings/executable_blacklist", getExecutableBlacklist()); + qsettings().setValue("Settings/executable_blacklist", getExecutableBlacklist()); } void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() { bool ok = false; QString result = QInputDialog::getMultiLineText( - parentWidget(), + &dialog(), QObject::tr("Executables Blacklist"), QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" "Mods and other virtualized files will not be visible to these executables and\n" @@ -96,7 +96,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); if (res == QMessageBox::Yes) { - m_parent->geometry().requestReset(); + settings().geometry().requestReset(); qApp->exit(INT_MAX); } } diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h index 1687624b..d5d6815f 100644 --- a/src/settingsdialogworkarounds.h +++ b/src/settingsdialogworkarounds.h @@ -7,8 +7,7 @@ class WorkaroundsSettingsTab : public SettingsTab { public: - WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - + WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: -- cgit v1.3.1 From 7395fbb7544740a136884e103cb0829bd10b5655 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 00:21:41 -0400 Subject: made ServerInfo a class moved server functions together in Settings --- src/mainwindow.cpp | 10 +++---- src/serverinfo.cpp | 31 ++++++++++++++++++++ src/serverinfo.h | 20 +++++++++---- src/settings.cpp | 86 +++++++++++++++++++++++++++--------------------------- src/settings.h | 26 ++++++++--------- 5 files changed, 107 insertions(+), 66 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f1a2047f..b6ae59a1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5909,11 +5909,11 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat QList servers; for (const QVariant &server : serverList) { QVariantMap serverInfo = server.toMap(); - ServerInfo info; - info.name = serverInfo["short_name"].toString(); - info.premium = serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive); - info.lastSeen = QDate::currentDate(); - info.preferred = serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive); + ServerInfo info( + serverInfo["short_name"].toString(), + serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive), + QDate::currentDate(), + serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive)); servers.append(info); } m_OrganizerCore.settings().updateServers(servers); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index e96b69d2..5912c226 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1 +1,32 @@ #include "serverinfo.h" + +ServerInfo::ServerInfo() + : ServerInfo({}, false, {}, false) +{ +} + +ServerInfo::ServerInfo(QString n, bool premium, QDate last, bool preferred) : + m_name(std::move(n)), m_premium(premium), m_lastSeen(std::move(last)), + m_preferred(preferred) +{ +} + +const QString& ServerInfo::name() const +{ + return m_name; +} + +bool ServerInfo::isPremium() const +{ + return m_premium; +} + +const QDate& ServerInfo::lastSeen() const +{ + return m_lastSeen; +} + +bool ServerInfo::isPreferred() const +{ + return m_preferred; +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 8e5e935a..79b90e77 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -5,12 +5,22 @@ #include #include -struct ServerInfo +class ServerInfo { - QString name; - bool premium; - QDate lastSeen; - bool preferred; +public: + ServerInfo(); + ServerInfo(QString name, bool premium, QDate lastSeen, bool preferred); + + const QString& name() const; + bool isPremium() const; + const QDate& lastSeen() const; + bool isPreferred() const; + +private: + QString m_name; + bool m_premium; + QDate m_lastSeen; + bool m_preferred; }; Q_DECLARE_METATYPE(ServerInfo) diff --git a/src/settings.cpp b/src/settings.cpp index 9c9e4c4d..9975642b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -414,40 +414,6 @@ void Settings::setUsePrereleases(bool b) m_Settings.setValue("Settings/use_prereleases", b); } -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - QString Settings::getConfigurablePath(const QString &key, const QString &def, bool resolve) const @@ -884,28 +850,62 @@ void Settings::setLanguage(const QString& name) m_Settings.setValue("Settings/language", name); } +void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + if (serverKey == serverName) { + data["downloadCount"] = data["downloadCount"].toInt() + 1; + data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); + m_Settings.setValue(serverKey, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +std::map Settings::getPreferredServers() +{ + std::map result; + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + int preference = data["preferred"].toInt(); + if (preference > 0) { + result[serverKey] = preference; + } + } + m_Settings.endGroup(); + + return result; +} + void Settings::updateServers(const QList &servers) { m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); for (const ServerInfo &server : servers) { - if (!oldServerKeys.contains(server.name)) { + if (!oldServerKeys.contains(server.name())) { // not yet known server QVariantMap newVal; - newVal["premium"] = server.premium; - newVal["preferred"] = server.preferred ? 1 : 0; - newVal["lastSeen"] = server.lastSeen; + newVal["premium"] = server.isPremium(); + newVal["preferred"] = server.isPreferred() ? 1 : 0; + newVal["lastSeen"] = server.lastSeen(); newVal["downloadCount"] = 0; newVal["downloadSpeed"] = 0.0; - m_Settings.setValue(server.name, newVal); + m_Settings.setValue(server.name(), newVal); } else { - QVariantMap data = m_Settings.value(server.name).toMap(); - data["lastSeen"] = server.lastSeen; - data["premium"] = server.premium; + QVariantMap data = m_Settings.value(server.name()).toMap(); + data["lastSeen"] = server.lastSeen(); + data["premium"] = server.isPremium(); - m_Settings.setValue(server.name, data); + m_Settings.setValue(server.name(), data); } } diff --git a/src/settings.h b/src/settings.h index 21776169..4662ed19 100644 --- a/src/settings.h +++ b/src/settings.h @@ -33,7 +33,7 @@ namespace MOBase { class QSplitter; class PluginContainer; -struct ServerInfo; +class ServerInfo; class Settings; class ExpanderWidget; @@ -190,13 +190,6 @@ public: */ bool lockGUI() const; - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - /** * the steam appid is assigned by the steam platform to each product sold there. * The appid may differ between different versions of a game so it may be impossible @@ -216,11 +209,6 @@ public: **/ QString getDownloadDirectory(bool resolve = true) const; - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - /** * retrieve the directory where mods are stored (with native separators) **/ @@ -493,6 +481,18 @@ public: QString language(); void setLanguage(const QString& name); + /** + * @brief register download speed + * @param url complete download url + * @param bytesPerSecond download size in bytes per second + */ + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * retrieve a sorted list of preferred servers + */ + std::map getPreferredServers(); + /** * @brief updates the list of known servers * @param list of servers from a recent query -- cgit v1.3.1 From 36dbb4bad74b097d44b843a2e934aa4a58ef6492 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 00:48:25 -0400 Subject: ServerList instead of a QList of ServerInfo changed preferred to an int moved all server settings to Settings --- src/mainwindow.cpp | 24 ++++++++------ src/serverinfo.cpp | 63 ++++++++++++++++++++++++++++++++++--- src/serverinfo.h | 35 +++++++++++++++++++-- src/settings.cpp | 36 ++++++++++++++++++--- src/settings.h | 5 ++- src/settingsdialognexus.cpp | 77 ++++++++++++++++++++++++++++++++------------- 6 files changed, 194 insertions(+), 46 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b6ae59a1..9921ad82 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5904,18 +5904,22 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - QVariantList serverList = resultData.toList(); - - QList servers; - for (const QVariant &server : serverList) { - QVariantMap serverInfo = server.toMap(); - ServerInfo info( - serverInfo["short_name"].toString(), - serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive), + ServerList servers; + + for (const QVariant &var : resultData.toList()) { + const QVariantMap map = var.toMap(); + + ServerInfo server( + map["short_name"].toString(), + map["name"].toString().contains("Premium", Qt::CaseInsensitive), QDate::currentDate(), - serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive)); - servers.append(info); + map["short_name"].toString().contains("CDN", Qt::CaseInsensitive) ? 1 : 0, + map["downloadCount"].toInt(), + map["downloadSpeed"].toDouble()); + + servers.add(std::move(server)); } + m_OrganizerCore.settings().updateServers(servers); } diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 5912c226..67a80b9e 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1,13 +1,15 @@ #include "serverinfo.h" ServerInfo::ServerInfo() - : ServerInfo({}, false, {}, false) + : ServerInfo({}, false, {}, 0, 0, 0.0) { } -ServerInfo::ServerInfo(QString n, bool premium, QDate last, bool preferred) : - m_name(std::move(n)), m_premium(premium), m_lastSeen(std::move(last)), - m_preferred(preferred) +ServerInfo::ServerInfo( + QString name, bool premium, QDate last, int preferred, + int count, double speed) : + m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), + m_preferred(preferred), m_downloadCount(count), m_downloadSpeed(speed) { } @@ -26,7 +28,58 @@ const QDate& ServerInfo::lastSeen() const return m_lastSeen; } -bool ServerInfo::isPreferred() const +int ServerInfo::preferred() const { return m_preferred; } + +int ServerInfo::downloadCount() const +{ + return m_downloadCount; +} + +double ServerInfo::downloadSpeed() const +{ + return m_downloadSpeed; +} + +void ServerInfo::setPreferred(int i) +{ + m_preferred = i; +} + + +void ServerList::add(ServerInfo s) +{ + m_servers.push_back(std::move(s)); +} + +ServerList::iterator ServerList::begin() +{ + return m_servers.begin(); +} + +ServerList::const_iterator ServerList::begin() const +{ + return m_servers.begin(); +} + +ServerList::iterator ServerList::end() +{ + return m_servers.end(); +} + +ServerList::const_iterator ServerList::end() const +{ + return m_servers.end(); +} + +std::size_t ServerList::size() const +{ + return m_servers.size(); +} + +bool ServerList::empty() const +{ + return m_servers.empty(); +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 79b90e77..0a8a5028 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -9,20 +9,49 @@ class ServerInfo { public: ServerInfo(); - ServerInfo(QString name, bool premium, QDate lastSeen, bool preferred); + ServerInfo( + QString name, bool premium, QDate lastSeen, int preferred, + int downloadCount, double downloadSpeed); const QString& name() const; bool isPremium() const; const QDate& lastSeen() const; - bool isPreferred() const; + int preferred() const; + int downloadCount() const; + double downloadSpeed() const; + + void setPreferred(int i); private: QString m_name; bool m_premium; QDate m_lastSeen; - bool m_preferred; + int m_preferred; + int m_downloadCount; + double m_downloadSpeed; }; Q_DECLARE_METATYPE(ServerInfo) + +class ServerList +{ +public: + using container = QList; + using iterator = container::iterator; + using const_iterator = container::const_iterator; + + void add(ServerInfo s); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + std::size_t size() const; + bool empty() const; + +private: + container m_servers; +}; + #endif // SERVERINFO_H diff --git a/src/settings.cpp b/src/settings.cpp index 9975642b..5ddffd8f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,17 +884,42 @@ std::map Settings::getPreferredServers() return result; } -void Settings::updateServers(const QList &servers) +ServerList Settings::getServers() const +{ + ServerList list; + + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + data["downloadCount"].toInt(), + data["downloadSpeed"].toDouble()); + + list.add(std::move(server)); + } + + m_Settings.endGroup(); + + return list; +} + +void Settings::updateServers(const ServerList& servers) { m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); - for (const ServerInfo &server : servers) { + for (const auto& server : servers) { if (!oldServerKeys.contains(server.name())) { // not yet known server QVariantMap newVal; newVal["premium"] = server.isPremium(); - newVal["preferred"] = server.isPreferred() ? 1 : 0; + newVal["preferred"] = server.preferred(); newVal["lastSeen"] = server.lastSeen(); newVal["downloadCount"] = 0; newVal["downloadSpeed"] = 0.0; @@ -902,12 +927,13 @@ void Settings::updateServers(const QList &servers) m_Settings.setValue(server.name(), newVal); } else { QVariantMap data = m_Settings.value(server.name()).toMap(); - data["lastSeen"] = server.lastSeen(); data["premium"] = server.isPremium(); + data["lastSeen"] = server.lastSeen(); + data["preferred"] = server.preferred(); m_Settings.setValue(server.name(), data); - } } + } // clean up unavailable servers QDate now = QDate::currentDate(); diff --git a/src/settings.h b/src/settings.h index 4662ed19..31dbf85c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -34,6 +34,7 @@ class QSplitter; class PluginContainer; class ServerInfo; +class ServerList; class Settings; class ExpanderWidget; @@ -493,11 +494,13 @@ public: */ std::map getPreferredServers(); + ServerList getServers() const; + /** * @brief updates the list of known servers * @param list of servers from a recent query */ - void updateServers(const QList &servers); + void updateServers(const ServerList& servers); /** * @brief add a plugin that is to be blacklisted diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 4d9a18a2..926ea9a6 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -2,9 +2,11 @@ #include "ui_settingsdialog.h" #include "ui_nexusmanualkey.h" #include "nexusinterface.h" +#include "serverinfo.h" +#include "log.h" #include -namespace shell = MOBase::shell; +using namespace MOBase; template class ServerItem : public QListWidgetItem { @@ -78,30 +80,31 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); // display server preferences - qsettings().beginGroup("Servers"); - for (const QString &key : qsettings().childKeys()) { - QVariantMap val = qsettings().value(key).toMap(); - QString descriptor = key; + for (const auto& server : s.getServers()) { + QString descriptor = server.name(); + if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { descriptor += QStringLiteral(" (automatic)"); } - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + + if (server.downloadSpeed() > 0 && server.downloadCount() > 0) { + const int bps = static_cast(server.downloadSpeed() / server.downloadCount()); descriptor += QString(" (%1 kbps)").arg(bps / 1024); } QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { + newItem->setData(Qt::UserRole, server.name()); + newItem->setData(Qt::UserRole + 1, server.preferred()); + + if (server.preferred() > 0) { ui->preferredServersList->addItem(newItem); } else { ui->knownServersList->addItem(newItem); } + ui->preferredServersList->sortItems(Qt::DescendingOrder); } - qsettings().endGroup(); QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); @@ -119,22 +122,52 @@ void NexusSettingsTab::update() settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + auto servers = settings().getServers(); + // store server preference - qsettings().beginGroup("Servers"); for (int i = 0; i < ui->knownServersList->count(); ++i) { - QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = qsettings().value(key).toMap(); - val["preferred"] = 0; - qsettings().setValue(key, val); + const QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); + + bool found = false; + + for (auto& server : servers) { + if (server.name() == key) { + server.setPreferred(0); + found = true; + break; + } } - int count = ui->preferredServersList->count(); + + if (!found) { + log::error("while setting preferred to 0, server '{}' not found", key); + } + } + + const int count = ui->preferredServersList->count(); + for (int i = 0; i < count; ++i) { - QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = qsettings().value(key).toMap(); - val["preferred"] = count - i; - qsettings().setValue(key, val); + const QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); + const int newPreferred = count - i; + + bool found = false; + + for (auto& server : servers) { + + if (server.name() == key) { + server.setPreferred(newPreferred); + found = true; + break; } - qsettings().endGroup(); + } + + if (!found) { + log::error( + "while setting preference to {}, server '{}' not found", + newPreferred, key); + } + } + + settings().updateServers(servers); } void NexusSettingsTab::on_nexusConnect_clicked() -- cgit v1.3.1 From aff3ee8fcf427c9ff8c554a179222eabec3a95e2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 01:07:03 -0400 Subject: moved preferred servers into ServerList --- src/downloadmanager.cpp | 40 +++++++++++++++++++++++++++++----------- src/downloadmanager.h | 11 +++++++---- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 2 +- src/serverinfo.cpp | 17 +++++++++++++++++ src/serverinfo.h | 2 ++ src/settings.cpp | 17 ----------------- src/settings.h | 16 ---------------- 8 files changed, 57 insertions(+), 50 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3b084b83..45cfdeed 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -288,9 +288,9 @@ void DownloadManager::setOutputDirectory(const QString &outputDirectory) } -void DownloadManager::setPreferredServers(const std::map &preferredServers) +void DownloadManager::setServers(const ServerList& servers) { - m_PreferredServers = preferredServers; + m_Servers = servers; } @@ -1667,23 +1667,38 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(info->gameName, info->modID, info->fileID, this, qVariantFromValue(test), QString())); } -static int evaluateFileInfoMap(const QVariantMap &map, const std::map &preferredServers) +static int evaluateFileInfoMap( + const QVariantMap &map, + const QList& preferredServers) { - int result = 0; + int preference = 0; + bool found = false; + const auto name = map["short_name"].toString(); - auto preference = preferredServers.find(map["short_name"].toString()); + for (const auto& server : preferredServers) { + if (server.name() == name) { + preference = server.preferred(); + found = true; + break; + } + } - if (preference != preferredServers.end()) { - result += 100 + preference->second * 20; + if (!found) { + log::error("server '{}' not found while sorting by preference", name); + return 0; } - return result; + return 100 + preference * 20; } // sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +bool DownloadManager::ServerByPreference( + const QList& preferredServers, + const QVariant &LHS, const QVariant &RHS) { - return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers); + const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers); + const auto b = evaluateFileInfoMap(RHS.toMap(), preferredServers); + return (a > b); } int DownloadManager::startDownloadURLs(const QStringList &urls) @@ -1732,7 +1747,10 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } - std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); + std::sort( + resultList.begin(), + resultList.end(), + boost::bind(&DownloadManager::ServerByPreference, m_Servers.getPreferred(), _1, _2)); info->userData["downloadMap"] = resultList; diff --git a/src/downloadmanager.h b/src/downloadmanager.h index feef0eaa..f739f4f0 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H +#include "serverinfo.h" #include #include #include @@ -174,9 +175,9 @@ public: QString getOutputDirectory() const { return m_OutputDirectory; } /** - * @brief setPreferredServers set the list of preferred servers + * @brief sets the list of servers */ - void setPreferredServers(const std::map &preferredServers); + void setServers(const ServerList& servers); /** * @brief set the list of supported extensions @@ -366,7 +367,9 @@ public: * @param RHS * @return */ - static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); + static bool ServerByPreference( + const QList& preferredServers, + const QVariant &LHS, const QVariant &RHS); virtual int startDownloadURLs(const QStringList &urls); @@ -548,7 +551,7 @@ private: QVector m_ActiveDownloads; QString m_OutputDirectory; - std::map m_PreferredServers; + ServerList m_Servers; QStringList m_SupportedExtensions; std::set m_RequestIDs; QVector m_AlphabeticalTranslation; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9921ad82..79203d29 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5050,7 +5050,7 @@ void MainWindow::on_actionSettings_triggered() dlManager->setOutputDirectory(settings.getDownloadDirectory()); } } - dlManager->setPreferredServers(settings.getPreferredServers()); + dlManager->setServers(settings.getServers()); if ((settings.getModDirectory() != oldModDirectory) || (settings.displayForeign() != oldDisplayForeign)) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5a8ee4c2..522d28be 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -276,7 +276,7 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + m_DownloadManager.setServers(m_Settings.getServers()); NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 67a80b9e..70cdec6d 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -52,6 +52,10 @@ void ServerInfo::setPreferred(int i) void ServerList::add(ServerInfo s) { m_servers.push_back(std::move(s)); + + std::sort(m_servers.begin(), m_servers.end(), [](auto&& a, auto&& b){ + return (a.preferred() < b.preferred()); + }); } ServerList::iterator ServerList::begin() @@ -83,3 +87,16 @@ bool ServerList::empty() const { return m_servers.empty(); } + +ServerList::container ServerList::getPreferred() const +{ + container v; + + for (const auto& server : m_servers) { + if (server.preferred() > 0) { + v.push_back(server); + } + } + + return v; +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 0a8a5028..2e5682fc 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -50,6 +50,8 @@ public: std::size_t size() const; bool empty() const; + container getPreferred() const; + private: container m_servers; }; diff --git a/src/settings.cpp b/src/settings.cpp index 5ddffd8f..c57530e4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -867,23 +867,6 @@ void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) m_Settings.sync(); } -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - ServerList Settings::getServers() const { ServerList list; diff --git a/src/settings.h b/src/settings.h index 31dbf85c..e7337301 100644 --- a/src/settings.h +++ b/src/settings.h @@ -482,24 +482,8 @@ public: QString language(); void setLanguage(const QString& name); - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - ServerList getServers() const; - - /** - * @brief updates the list of known servers - * @param list of servers from a recent query - */ void updateServers(const ServerList& servers); /** -- cgit v1.3.1 From c42e5fb2fec9b20fe6956d8798b85874b2eff73e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 03:01:55 -0400 Subject: changed total speed and count to a list of the last 5 downloads existing servers now merged when retrieving the download links download manager doesn't store the servers any more, queries the settings every time --- src/downloadmanager.cpp | 17 +++++------ src/downloadmanager.h | 17 ----------- src/mainwindow.cpp | 31 ++++++++++++------- src/organizercore.cpp | 1 - src/serverinfo.cpp | 72 +++++++++++++++++++++++++++++++++++++++------ src/serverinfo.h | 21 ++++++++----- src/settings.cpp | 51 ++++++++++++++++++++++---------- src/settingsdialognexus.cpp | 6 ++-- 8 files changed, 143 insertions(+), 73 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 45cfdeed..93ca1608 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -288,12 +288,6 @@ void DownloadManager::setOutputDirectory(const QString &outputDirectory) } -void DownloadManager::setServers(const ServerList& servers) -{ - m_Servers = servers; -} - - void DownloadManager::setSupportedExtensions(const QStringList &extensions) { m_SupportedExtensions = extensions; @@ -1669,7 +1663,7 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file static int evaluateFileInfoMap( const QVariantMap &map, - const QList& preferredServers) + const ServerList::container& preferredServers) { int preference = 0; bool found = false; @@ -1692,8 +1686,9 @@ static int evaluateFileInfoMap( } // sort function to sort by best download server -bool DownloadManager::ServerByPreference( - const QList& preferredServers, +// +bool ServerByPreference( + const ServerList::container& preferredServers, const QVariant &LHS, const QVariant &RHS) { const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers); @@ -1747,10 +1742,12 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } + const auto servers = m_OrganizerCore->settings().getServers(); + std::sort( resultList.begin(), resultList.end(), - boost::bind(&DownloadManager::ServerByPreference, m_Servers.getPreferred(), _1, _2)); + boost::bind(&ServerByPreference, servers.getPreferred(), _1, _2)); info->userData["downloadMap"] = resultList; diff --git a/src/downloadmanager.h b/src/downloadmanager.h index f739f4f0..bed1b3cc 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -174,11 +174,6 @@ public: **/ QString getOutputDirectory() const { return m_OutputDirectory; } - /** - * @brief sets the list of servers - */ - void setServers(const ServerList& servers); - /** * @brief set the list of supported extensions * @param extensions list of supported extensions @@ -361,17 +356,6 @@ public: */ void refreshList(); - /** - * @brief Sort function for download servers - * @param LHS - * @param RHS - * @return - */ - static bool ServerByPreference( - const QList& preferredServers, - const QVariant &LHS, const QVariant &RHS); - - virtual int startDownloadURLs(const QStringList &urls); virtual int startDownloadNexusFile(int modID, int fileID); @@ -551,7 +535,6 @@ private: QVector m_ActiveDownloads; QString m_OutputDirectory; - ServerList m_Servers; QStringList m_SupportedExtensions; std::set m_RequestIDs; QVector m_AlphabeticalTranslation; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 79203d29..4c2594b8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5050,7 +5050,6 @@ void MainWindow::on_actionSettings_triggered() dlManager->setOutputDirectory(settings.getDownloadDirectory()); } } - dlManager->setServers(settings.getServers()); if ((settings.getModDirectory() != oldModDirectory) || (settings.displayForeign() != oldDisplayForeign)) { @@ -5904,20 +5903,32 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - ServerList servers; + auto servers = m_OrganizerCore.settings().getServers(); for (const QVariant &var : resultData.toList()) { const QVariantMap map = var.toMap(); - ServerInfo server( - map["short_name"].toString(), - map["name"].toString().contains("Premium", Qt::CaseInsensitive), - QDate::currentDate(), - map["short_name"].toString().contains("CDN", Qt::CaseInsensitive) ? 1 : 0, - map["downloadCount"].toInt(), - map["downloadSpeed"].toDouble()); + const auto name = map["short_name"].toString(); + const auto isPremium = map["name"].toString().contains("Premium", Qt::CaseInsensitive); + const auto isCDN = map["short_name"].toString().contains("CDN", Qt::CaseInsensitive); - servers.add(std::move(server)); + bool found = false; + + for (auto& server : servers) { + if (server.name() == name) { + // already exists, update + server.setPremium(isPremium); + server.updateLastSeen(); + found = true; + break; + } + } + + if (!found) { + // new server + ServerInfo server(name, isPremium, QDate::currentDate(), isCDN ? 1 : 0, {}); + servers.add(std::move(server)); + } } m_OrganizerCore.settings().updateServers(servers); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 522d28be..ec13ca9c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -276,7 +276,6 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setServers(m_Settings.getServers()); NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 16e65f52..aece61da 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -3,17 +3,23 @@ using namespace MOBase; +const std::size_t MaxDownloadCount = 5; + + ServerInfo::ServerInfo() - : ServerInfo({}, false, {}, 0, 0, 0.0) + : ServerInfo({}, false, {}, 0, {}) { } ServerInfo::ServerInfo( QString name, bool premium, QDate last, int preferred, - int count, double speed) : + SpeedList lastDownloads) : m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), - m_preferred(preferred), m_downloadCount(count), m_downloadSpeed(speed) + m_preferred(preferred), m_lastDownloads(std::move(lastDownloads)) { + if (m_lastDownloads.size() > MaxDownloadCount) { + m_lastDownloads.resize(MaxDownloadCount); + } } const QString& ServerInfo::name() const @@ -26,29 +32,77 @@ bool ServerInfo::isPremium() const return m_premium; } +void ServerInfo::setPremium(bool b) +{ + m_premium = b; +} + const QDate& ServerInfo::lastSeen() const { return m_lastSeen; } +void ServerInfo::updateLastSeen() +{ + m_lastSeen = QDate::currentDate(); +} + int ServerInfo::preferred() const { return m_preferred; } -int ServerInfo::downloadCount() const +void ServerInfo::setPreferred(int i) { - return m_downloadCount; + m_preferred = i; } -double ServerInfo::downloadSpeed() const +const ServerInfo::SpeedList& ServerInfo::lastDownloads() const { - return m_downloadSpeed; + return m_lastDownloads; } -void ServerInfo::setPreferred(int i) +int ServerInfo::averageSpeed() const { - m_preferred = i; + int count = 0; + int total = 0; + + for (const auto& s : m_lastDownloads) { + if (s > 0) { + ++count; + total += s; + } + } + + if (count > 0) { + return static_cast(total) / count; + } + + return 0; +} + +void ServerInfo::addDownload(int bytesPerSecond) +{ + if (bytesPerSecond <= 0) { + log::error( + "trying to add download with {} B/s to server '{}'; ignoring", + bytesPerSecond, m_name); + + return; + } + + if (m_lastDownloads.size() == MaxDownloadCount) { + std::rotate( + m_lastDownloads.begin(), + m_lastDownloads.begin() + 1, + m_lastDownloads.end()); + + m_lastDownloads.back() = bytesPerSecond; + } else { + m_lastDownloads.push_back(bytesPerSecond); + } + + log::debug("added download at {} B/s to server '{}'", bytesPerSecond, m_name); } diff --git a/src/serverinfo.h b/src/serverinfo.h index c6e3b640..af8f77c8 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -8,27 +8,34 @@ class ServerInfo { public: + using SpeedList = std::vector; + ServerInfo(); ServerInfo( QString name, bool premium, QDate lastSeen, int preferred, - int downloadCount, double downloadSpeed); + SpeedList lastDownloads); const QString& name() const; + bool isPremium() const; + void setPremium(bool b); + const QDate& lastSeen() const; - int preferred() const; - int downloadCount() const; - double downloadSpeed() const; + void updateLastSeen(); + int preferred() const; void setPreferred(int i); + const SpeedList& lastDownloads() const; + int averageSpeed() const; + void addDownload(int bytesPerSecond); + private: QString m_name; bool m_premium; QDate m_lastSeen; int m_preferred; - int m_downloadCount; - double m_downloadSpeed; + SpeedList m_lastDownloads; }; Q_DECLARE_METATYPE(ServerInfo) @@ -37,7 +44,7 @@ Q_DECLARE_METATYPE(ServerInfo) class ServerList { public: - using container = QList; + using container = std::vector; using iterator = container::iterator; using const_iterator = container::const_iterator; diff --git a/src/settings.cpp b/src/settings.cpp index 3a7bda75..b11bc61c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -850,21 +850,21 @@ void Settings::setLanguage(const QString& name) m_Settings.setValue("Settings/language", name); } -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) { - m_Settings.beginGroup("Servers"); + auto servers = getServers(); - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); + for (auto& server : servers) { + if (server.name() == name) { + server.addDownload(bytesPerSecond); + updateServers(servers); + return; } } - m_Settings.endGroup(); - m_Settings.sync(); + log::error( + "server '{}' not found while trying to add a download with bps {}", + name, bytesPerSecond); } ServerList Settings::getServers() const @@ -885,6 +885,7 @@ ServerList Settings::getServers() const return getServersFromOldMap(); } + // post 2.2.1 format, array of values ServerList list; @@ -893,13 +894,22 @@ ServerList Settings::getServers() const for (int i=0; i 0) { + lastDownloads.push_back(bytesPerSecond); + } + } + ServerInfo server( m_Settings.value("name").toString(), m_Settings.value("premium").toBool(), QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), m_Settings.value("preferred").toInt(), - m_Settings.value("downloadCount").toInt(), - m_Settings.value("downloadSpeed").toDouble()); + lastDownloads); list.add(std::move(server)); } @@ -925,8 +935,10 @@ ServerList Settings::getServersFromOldMap() const data["premium"].toBool(), data["lastSeen"].toDate(), data["preferred"].toInt(), - data["downloadCount"].toInt(), - data["downloadSpeed"].toDouble()); + {}); + + // ignoring download count and speed, it's now a list of values instead of + // a total list.add(std::move(server)); } @@ -955,8 +967,15 @@ void Settings::updateServers(ServerList servers) m_Settings.setValue("premium", server.isPremium()); m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); m_Settings.setValue("preferred", server.preferred()); - m_Settings.setValue("downloadCount", server.downloadCount()); - m_Settings.setValue("downloadSpeed", server.downloadSpeed()); + + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } + } + + m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); ++i; } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 926ea9a6..f2bd3ab5 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -87,9 +87,9 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) descriptor += QStringLiteral(" (automatic)"); } - if (server.downloadSpeed() > 0 && server.downloadCount() > 0) { - const int bps = static_cast(server.downloadSpeed() / server.downloadCount()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); + const auto averageSpeed = server.averageSpeed(); + if (averageSpeed > 0) { + descriptor += QString(" (%1 kbps)").arg(averageSpeed / 1024); } QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); -- cgit v1.3.1 From ec3fb7b3509fb10a8a1392740e209509ae6c092c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 12:56:50 -0400 Subject: removed duplicate useProxy() use dedicated functions to set, get or remove settings, allows for logging --- src/loadmechanism.cpp | 13 + src/loadmechanism.h | 2 + src/mainwindow.cpp | 12 +- src/settings.cpp | 852 +++++++++++++++++++++++++++----------------- src/settings.h | 16 +- src/settingsdialognexus.cpp | 2 +- 6 files changed, 556 insertions(+), 341 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 06e9f201..0c81b7b2 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -49,3 +49,16 @@ void LoadMechanism::activate(EMechanism) { // no-op } + + +QString toString(LoadMechanism::EMechanism e) +{ + switch (e) + { + case LoadMechanism::LOAD_MODORGANIZER: + return "ModOrganizer"; + + default: + return QString("unknown (%1)").arg(static_cast(e)); + } +} diff --git a/src/loadmechanism.h b/src/loadmechanism.h index 49eb0c52..151e804f 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -56,4 +56,6 @@ private: }; +QString toString(LoadMechanism::EMechanism e); + #endif // LOADMECHANISM_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4c2594b8..42b19cb7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2157,10 +2157,8 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (auto v=settings.getUseProxy()) { - if (*v) { - activateProxy(true); - } + if (settings.getUseProxy()) { + activateProxy(true); } } @@ -5014,7 +5012,7 @@ void MainWindow::on_actionSettings_triggered() QString oldProfilesDirectory(settings.getProfileDirectory()); QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.useProxy(); + bool proxy = settings.getUseProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); @@ -5081,8 +5079,8 @@ void MainWindow::on_actionSettings_triggered() NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory()); } - if (proxy != settings.useProxy()) { - activateProxy(settings.useProxy()); + if (proxy != settings.getUseProxy()) { + activateProxy(settings.getUseProxy()); } ui->statusBar->checkSettings(m_OrganizerCore.settings()); diff --git a/src/settings.cpp b/src/settings.cpp index f6be8ba0..71288950 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,13 +27,164 @@ along with Mod Organizer. If not, see . using namespace MOBase; +template +struct ValueConverter +{ + static const T& convert(const T& t) + { + return t; + } +}; + +template +struct ValueConverter>> +{ + static QString convert(const T& t) + { + return QString("%1").arg(static_cast>(t)); + } +}; + + +template +void logChange( + const QString& displayName, std::optional oldValue, const T& newValue) +{ + using VC = ValueConverter; + + if (oldValue) { + log::debug( + "setting '{}' changed from '{}' to '{}'", + displayName, VC::convert(*oldValue), VC::convert(newValue)); + } else { + log::debug( + "setting '{}' set to '{}'", + displayName, VC::convert(newValue)); + } +} + +void logRemoval(const QString& name) +{ + log::debug("setting '{}' removed", name); +} + + +QString settingName(const QString& section, const QString& key) +{ + if (section.isEmpty()) { + return key; + } else if (key.isEmpty()) { + return section; + } else { + if (section.compare("General", Qt::CaseInsensitive) == 0) { + return key; + } else { + return section + "/" + key; + } + } +} + +template +void setImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key, const T& value) +{ + const auto current = getOptional(settings, section, key); + + if (current && *current == value) { + // no change + return; + } + + const auto name = settingName(section, key); + + logChange(displayName, current, value); + + if constexpr (std::is_enum_v) { + settings.setValue( + name, static_cast>(value)); + } else { + settings.setValue(name, value); + } +} + +void removeImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key) +{ + if (key.isEmpty()) { + if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { + // not there + return; + } + } else { + if (!settings.contains(settingName(section, key))) { + // not there + return; + } + } + + logRemoval(displayName); + settings.remove(settingName(section, key)); +} + + +template +std::optional getOptional( + const QSettings& settings, + const QString& section, const QString& key, std::optional def={}) +{ + if (settings.contains(settingName(section, key))) { + const auto v = settings.value(settingName(section, key)); + + if constexpr (std::is_enum_v) { + return static_cast(v.value>()); + } else { + return v.value(); + } + } + + return def; +} + +template +T get( + const QSettings& settings, + const QString& section, const QString& key, T def={}) +{ + if (auto v=getOptional(settings, section, key)) { + return *v; + } else { + return def; + } +} + +template +void set( + QSettings& settings, + const QString& section, const QString& key, const T& value) +{ + setImpl(settings, settingName(section, key), section, key, value); +} + +void remove(QSettings& settings, const QString& section, const QString& key) +{ + removeImpl(settings, settingName(section, key), section, key); +} + +void removeSection(QSettings& settings, const QString& section) +{ + removeImpl(settings, section, section, ""); +} + + class ScopedGroup { public: ScopedGroup(QSettings& s, const QString& name) - : m_settings(s) + : m_settings(s), m_name(name) { - m_settings.beginGroup(name); + m_settings.beginGroup(m_name); } ~ScopedGroup() @@ -44,18 +195,55 @@ public: ScopedGroup(const ScopedGroup&) = delete; ScopedGroup& operator=(const ScopedGroup&) = delete; + template + void set(const QString& key, const T& value) + { + setImpl(m_settings, settingName(m_name, key), "", key, value); + } + + void remove(const QString& key) + { + removeImpl(m_settings, settingName(m_name, key), "", key); + } + + QStringList keys() const + { + return m_settings.childKeys(); + } + + template + void for_each(F&& f) const + { + for (const QString& key : keys()) { + f(key); + } + } + + template + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + private: QSettings& m_settings; + QString m_name; }; class ScopedReadArray { public: - ScopedReadArray(QSettings& s, const QString& name) + ScopedReadArray(QSettings& s, const QString& section) : m_settings(s), m_count(0) { - m_count = m_settings.beginReadArray(name); + m_count = m_settings.beginReadArray(section); } ~ScopedReadArray() @@ -66,11 +254,37 @@ public: ScopedReadArray(const ScopedReadArray&) = delete; ScopedReadArray& operator=(const ScopedReadArray&) = delete; + template + void for_each(F&& f) const + { + for (int i=0; i + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + int count() const { return m_count; } + QStringList keys() const + { + return m_settings.childKeys(); + } + private: QSettings& m_settings; int m_count; @@ -80,10 +294,10 @@ private: class ScopedWriteArray { public: - ScopedWriteArray(QSettings& s, const QString& name) - : m_settings(s) + ScopedWriteArray(QSettings& s, const QString& section) + : m_settings(s), m_section(section), m_i(0) { - m_settings.beginWriteArray(name); + m_settings.beginWriteArray(section); } ~ScopedWriteArray() @@ -94,27 +308,28 @@ public: ScopedWriteArray(const ScopedWriteArray&) = delete; ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; -private: - QSettings& m_settings; -}; - + void next() + { + m_settings.setArrayIndex(m_i); + ++m_i; + } -template -std::optional getOptional( - const QSettings& s, const QString& name, std::optional def={}) -{ - if (s.contains(name)) { - const auto v = s.value(name); + template + void set(const QString& key, const T& value) + { + const auto displayName = QString("%1/%2\\%3") + .arg(m_section) + .arg(m_i) + .arg(key); - if constexpr (std::is_enum_v) { - return static_cast(v.value>()); - } else { - return v.value(); - } + setImpl(m_settings, displayName, "", key, value); } - return def; -} +private: + QSettings& m_settings; + QString m_section; + int m_i; +}; EndorsementState endorsementStateFromString(const QString& s) @@ -132,15 +347,15 @@ QString toString(EndorsementState s) { switch (s) { - case EndorsementState::Accepted: - return "Endorsed"; + case EndorsementState::Accepted: + return "Endorsed"; - case EndorsementState::Refused: - return "Abstained"; + case EndorsementState::Refused: + return "Abstained"; - case EndorsementState::NoDecision: // fall-through - default: - return {}; + case EndorsementState::NoDecision: // fall-through + default: + return {}; } } @@ -198,24 +413,24 @@ QString widgetName(const QWidget* w) template QString geoSettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_geometry"; + return widgetName(widget) + "_geometry"; } template QString stateSettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_state"; + return widgetName(widget) + "_state"; } template QString visibilitySettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_visibility"; + return widgetName(widget) + "_visibility"; } QString dockSettingName(const QDockWidget* dock) { - return "geometry/MainWindow_docks_" + dock->objectName() + "_size"; + return "MainWindow_docks_" + dock->objectName() + "_size"; } QString indexSettingName(const QWidget* widget) @@ -278,40 +493,34 @@ void Settings::processUpdates( } if (lastVersion < QVersionNumber(2, 2, 0)) { - { - ScopedGroup sg(m_Settings, "Settings"); - m_Settings.remove("steam_password"); - m_Settings.remove("nexus_username"); - m_Settings.remove("nexus_password"); - m_Settings.remove("nexus_login"); - m_Settings.remove("nexus_api_key"); - m_Settings.remove("ask_for_nexuspw"); - m_Settings.remove("nmm_version"); - } + remove(m_Settings, "Settings", "steam_password"); + remove(m_Settings, "Settings", "nexus_username"); + remove(m_Settings, "Settings", "nexus_password"); + remove(m_Settings, "Settings", "nexus_login"); + remove(m_Settings, "Settings", "nexus_api_key"); + remove(m_Settings, "Settings", "ask_for_nexuspw"); + remove(m_Settings, "Settings", "nmm_version"); - { - ScopedGroup sg(m_Settings, "Servers"); - m_Settings.remove(""); - } + removeSection(m_Settings, "Servers"); } if (lastVersion < QVersionNumber(2, 2, 1)) { - m_Settings.remove("mod_info_tabs"); - m_Settings.remove("mod_info_conflict_expanders"); - m_Settings.remove("mod_info_conflicts"); - m_Settings.remove("mod_info_advanced_conflicts"); - m_Settings.remove("mod_info_conflicts_overwrite"); - m_Settings.remove("mod_info_conflicts_noconflict"); - m_Settings.remove("mod_info_conflicts_overwritten"); + remove(m_Settings, "General", "mod_info_tabs"); + remove(m_Settings, "General", "mod_info_conflict_expanders"); + remove(m_Settings, "General", "mod_info_conflicts"); + remove(m_Settings, "General", "mod_info_advanced_conflicts"); + remove(m_Settings, "General", "mod_info_conflicts_overwrite"); + remove(m_Settings, "General", "mod_info_conflicts_noconflict"); + remove(m_Settings, "General", "mod_info_conflicts_overwritten"); } if (lastVersion < QVersionNumber(2, 2, 2)) { // log splitter is gone, it's a dock now - m_Settings.remove("log_split"); + remove(m_Settings, "General", "log_split"); } //save version in all case - m_Settings.setValue("version", currentVersion.toString()); + set(m_Settings, "General", "version", currentVersion.toString()); } QString Settings::getFilename() const @@ -339,12 +548,12 @@ void Settings::registerAsNXMHandler(bool force) bool Settings::colorSeparatorScrollbar() const { - return m_Settings.value("Settings/colorSeparatorScrollbars", true).toBool(); + return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); } void Settings::setColorSeparatorScrollbar(bool b) { - m_Settings.setValue("Settings/colorSeparatorScrollbars", b); + set(m_Settings, "Settings", "colorSeparatorScrollbars", b); } void Settings::managedGameChanged(IPluginGame const *gamePlugin) @@ -418,71 +627,69 @@ QColor Settings::getIdealTextColor(const QColor& rBackgroundColor) bool Settings::hideUncheckedPlugins() const { - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); + return get(m_Settings, "Settings", "hide_unchecked_plugins", false); } void Settings::setHideUncheckedPlugins(bool b) { - m_Settings.setValue("Settings/hide_unchecked_plugins", b); + set(m_Settings, "Settings", "hide_unchecked_plugins", b); } bool Settings::forceEnableCoreFiles() const { - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); + return get(m_Settings, "Settings", "force_enable_core_files", true); } void Settings::setForceEnableCoreFiles(bool b) { - m_Settings.setValue("Settings/force_enable_core_files", b); + set(m_Settings, "Settings", "force_enable_core_files", b); } bool Settings::lockGUI() const { - return m_Settings.value("Settings/lock_gui", true).toBool(); + return get(m_Settings, "Settings", "lock_gui", true); } void Settings::setLockGUI(bool b) { - m_Settings.setValue("Settings/lock_gui", b); + set(m_Settings, "Settings", "lock_gui", b); } bool Settings::automaticLoginEnabled() const { - return m_Settings.value("Settings/nexus_login", false).toBool(); + return get(m_Settings, "Settings", "nexus_login", false); } QString Settings::getSteamAppID() const { - return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); + return get(m_Settings, "Settings", "app_id", m_GamePlugin->steamAPPId()); } void Settings::setSteamAppID(const QString& id) { if (id.isEmpty()) { - m_Settings.remove("Settings/app_id"); + remove(m_Settings, "Settings", "app_id"); } else { - m_Settings.setValue("Settings/app_id", id); + set(m_Settings, "Settings", "app_id", id); } } bool Settings::usePrereleases() const { - return m_Settings.value("Settings/use_prereleases", false).toBool(); + return get(m_Settings, "Settings", "use_prereleases", false); } void Settings::setUsePrereleases(bool b) { - m_Settings.setValue("Settings/use_prereleases", b); + set(m_Settings, "Settings", "use_prereleases", b); } QString Settings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const + const QString &def, + bool resolve) const { - const QString settingName = "Settings/" + key; - QString result = QDir::fromNativeSeparators( - m_Settings.value(settingName, QString("%BASE_DIR%/") + def).toString()); + get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); if (resolve) { result.replace("%BASE_DIR%", getBaseDirectory()); @@ -493,20 +700,17 @@ QString Settings::getConfigurablePath(const QString &key, void Settings::setConfigurablePath(const QString &key, const QString& path) { - const QString settingName = "Settings/" + key; - if (path.isEmpty()) { - m_Settings.remove(settingName); + remove(m_Settings, "Settings", key); } else { - m_Settings.setValue(settingName, path); + set(m_Settings, "Settings", key, path); } } QString Settings::getBaseDirectory() const { - return QDir::fromNativeSeparators(m_Settings.value( - "settings/base_directory", - qApp->property("dataPath").toString()).toString()); + return QDir::fromNativeSeparators(get(m_Settings, + "Settings", "base_directory", qApp->property("dataPath").toString())); } QString Settings::getDownloadDirectory(bool resolve) const @@ -552,9 +756,9 @@ QString Settings::getOverwriteDirectory(bool resolve) const void Settings::setBaseDirectory(const QString& path) { if (path.isEmpty()) { - m_Settings.remove("Settings/base_directory"); + remove(m_Settings, "Settings", "base_directory"); } else { - m_Settings.setValue("Settings/base_directory", path); + set(m_Settings, "Settings", "base_directory", path); } } @@ -585,7 +789,7 @@ void Settings::setOverwriteDirectory(const QString& path) std::optional Settings::getManagedGameDirectory() const { - if (auto v=getOptional(m_Settings, "gamePath")) { + if (auto v=getOptional(m_Settings, "General", "gamePath")) { return QString::fromUtf8(*v); } @@ -594,32 +798,32 @@ std::optional Settings::getManagedGameDirectory() const void Settings::setManagedGameDirectory(const QString& path) { - m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); + set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); } std::optional Settings::getManagedGameName() const { - return getOptional(m_Settings, "gameName"); + return getOptional(m_Settings, "General", "gameName"); } void Settings::setManagedGameName(const QString& name) { - m_Settings.setValue("gameName", name); + set(m_Settings, "General", "gameName", name); } std::optional Settings::getManagedGameEdition() const { - return getOptional(m_Settings, "game_edition"); + return getOptional(m_Settings, "General", "game_edition"); } void Settings::setManagedGameEdition(const QString& name) { - m_Settings.setValue("game_edition", name); + set(m_Settings, "General", "game_edition", name); } std::optional Settings::getSelectedProfileName() const { - if (auto v=getOptional(m_Settings, "selected_profile")) { + if (auto v=getOptional(m_Settings, "General", "selected_profile")) { return QString::fromUtf8(*v); } @@ -628,32 +832,32 @@ std::optional Settings::getSelectedProfileName() const void Settings::setSelectedProfileName(const QString& name) { - m_Settings.setValue("selected_profile", name.toUtf8()); + set(m_Settings, "General", "selected_profile", name.toUtf8()); } std::optional Settings::getStyleName() const { - return getOptional(m_Settings, "Settings/style"); + return getOptional(m_Settings, "Settings", "style"); } void Settings::setStyleName(const QString& name) { - m_Settings.setValue("Settings/style", name); + set(m_Settings, "Settings", "style", name); } -std::optional Settings::getUseProxy() const +bool Settings::getUseProxy() const { - return getOptional(m_Settings, "Settings/use_proxy"); + return get(m_Settings, "Settings", "use_proxy", false); } void Settings::setUseProxy(bool b) { - m_Settings.setValue("Settings/use_proxy", b); + set(m_Settings, "Settings", "use_proxy", b); } std::optional Settings::getVersion() const { - if (auto v=getOptional(m_Settings, "version")) { + if (auto v=getOptional(m_Settings, "General", "version")) { return QVersionNumber::fromString(*v).normalized(); } @@ -662,17 +866,17 @@ std::optional Settings::getVersion() const bool Settings::getFirstStart() const { - return getOptional(m_Settings, "first_start").value_or(true); + return get(m_Settings, "General", "first_start", true); } void Settings::setFirstStart(bool b) { - m_Settings.setValue("first_start", b); + set(m_Settings, "General", "first_start", b); } std::optional Settings::getPreviousSeparatorColor() const { - const auto c = getOptional(m_Settings, "previousSeparatorColor"); + const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); if (c && c->isValid()) { return c; } @@ -682,12 +886,12 @@ std::optional Settings::getPreviousSeparatorColor() const void Settings::setPreviousSeparatorColor(const QColor& c) const { - m_Settings.setValue("previousSeparatorColor", c); + set(m_Settings, "General", "previousSeparatorColor", c); } void Settings::removePreviousSeparatorColor() { - m_Settings.remove("previousSeparatorColor"); + remove(m_Settings, "General", "previousSeparatorColor"); } bool Settings::getNexusApiKey(QString &apiKey) const @@ -695,6 +899,7 @@ bool Settings::getNexusApiKey(QString &apiKey) const QString tempKey = deObfuscate("APIKEY"); if (tempKey.isEmpty()) return false; + apiKey = tempKey; return true; } @@ -722,7 +927,7 @@ bool Settings::hasNexusApiKey() const bool Settings::getSteamLogin(QString &username, QString &password) const { - username = m_Settings.value("Settings/steam_username", "").toString(); + username = get(m_Settings, "Settings", "steam_username", ""); password = deObfuscate("steam_password"); return !username.isEmpty() && !password.isEmpty(); @@ -730,95 +935,96 @@ bool Settings::getSteamLogin(QString &username, QString &password) const bool Settings::compactDownloads() const { - return m_Settings.value("Settings/compact_downloads", false).toBool(); + return get(m_Settings, "Settings", "compact_downloads", false); } void Settings::setCompactDownloads(bool b) { - m_Settings.setValue("Settings/compact_downloads", b); + set(m_Settings, "Settings", "compact_downloads", b); } bool Settings::metaDownloads() const { - return m_Settings.value("Settings/meta_downloads", false).toBool(); + return get(m_Settings, "Settings", "meta_downloads", false); } void Settings::setMetaDownloads(bool b) { - m_Settings.setValue("Settings/meta_downloads", b); + set(m_Settings, "Settings", "meta_downloads", b); } bool Settings::offlineMode() const { - return m_Settings.value("Settings/offline_mode", false).toBool(); + return get(m_Settings, "Settings/offline_mode", false); } void Settings::setOfflineMode(bool b) { - m_Settings.setValue("Settings/offline_mode", b); + set(m_Settings, "Settings", "offline_mode", b); } log::Levels Settings::logLevel() const { - return static_cast(m_Settings.value("Settings/log_level").toInt()); + return get(m_Settings, "Settings", "log_level", log::Levels::Info); } void Settings::setLogLevel(log::Levels level) { - m_Settings.setValue("Settings/log_level", static_cast(level)); + set(m_Settings, "Settings", "log_level", level); } CrashDumpsType Settings::crashDumpsType() const { - const auto v = getOptional(m_Settings, "Settings/crash_dumps_type"); - return v.value_or(CrashDumpsType::Mini); + return get(m_Settings, + "Settings", "crash_dumps_type", CrashDumpsType::Mini); } void Settings::setCrashDumpsType(CrashDumpsType type) { - m_Settings.setValue("Settings/crash_dumps_type", static_cast(type)); + set(m_Settings, "Settings", "crash_dumps_type", type); } int Settings::crashDumpsMax() const { - return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); + return get(m_Settings, "Settings", "crash_dumps_max", 5); } void Settings::setCrashDumpsMax(int n) { - return m_Settings.setValue("Settings/crash_dumps_max", n); + set(m_Settings, "Settings", "crash_dumps_max", n); } QString Settings::executablesBlacklist() const { - return m_Settings.value("Settings/executable_blacklist", ( - QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - ).join(";") - ).toString(); + static const QString def = (QStringList() + << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + ).join(";"); + + return get(m_Settings, "Settings", "executable_blacklist", def); } void Settings::setExecutablesBlacklist(const QString& s) { - m_Settings.setValue("Settings/executable_blacklist", s); + set(m_Settings, "Settings", "executable_blacklist", s); } void Settings::setSteamLogin(QString username, QString password) { if (username == "") { - m_Settings.remove("Settings/steam_username"); + remove(m_Settings, "Settings", "steam_username"); password = ""; } else { - m_Settings.setValue("Settings/steam_username", username); + set(m_Settings, "Settings", "steam_username", username); } + if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); @@ -827,26 +1033,37 @@ void Settings::setSteamLogin(QString username, QString password) LoadMechanism::EMechanism Settings::getLoadMechanism() const { - const auto i = m_Settings.value("Settings/load_mechanism").toInt(); + const auto def = LoadMechanism::LOAD_MODORGANIZER; + + const auto i = get(m_Settings, + "Settings", "load_mechanism", def); switch (i) { - case LoadMechanism::LOAD_MODORGANIZER: - return LoadMechanism::LOAD_MODORGANIZER; + // ok + case LoadMechanism::LOAD_MODORGANIZER: // fall-through + { + break; + } - default: - qCritical().nospace().noquote() - << "invalid load mechanism " << i << ", reverting to modorganizer"; + default: + { + log::error( + "invalid load mechanism {}, reverting to {}", + static_cast(i), toString(def)); - m_Settings.setValue("Settings/load_mechanism", LoadMechanism::LOAD_MODORGANIZER); + set(m_Settings, "Settings", "load_mechanism", def); - return LoadMechanism::LOAD_MODORGANIZER; + return def; } + } + + return i; } void Settings::setLoadMechanism(LoadMechanism::EMechanism m) { - m_Settings.setValue("Settings/load_mechanism", static_cast(m)); + set(m_Settings, "Settings", "load_mechanism", m); } void Settings::setupLoadMechanism() @@ -854,26 +1071,20 @@ void Settings::setupLoadMechanism() m_LoadMechanism.activate(getLoadMechanism()); } - -bool Settings::useProxy() const -{ - return m_Settings.value("Settings/use_proxy", false).toBool(); -} - bool Settings::endorsementIntegration() const { - return m_Settings.value("Settings/endorsement_integration", true).toBool(); + return get(m_Settings, "Settings", "endorsement_integration", true); } void Settings::setEndorsementIntegration(bool b) const { - m_Settings.setValue("Settings/endorsement_integration", b); + set(m_Settings, "Settings", "endorsement_integration", b); } EndorsementState Settings::endorsementState() const { - const auto v = getOptional(m_Settings, "endorse_state"); - return endorsementStateFromString(v.value_or("")); + return endorsementStateFromString( + get(m_Settings, "General", "endorse_state", "")); } void Settings::setEndorsementState(EndorsementState s) @@ -881,57 +1092,59 @@ void Settings::setEndorsementState(EndorsementState s) const auto v = toString(s); if (v.isEmpty()) { - m_Settings.remove("endorse_state"); + remove(m_Settings, "General", "endorse_state"); } else { - m_Settings.setValue("endorse_state", v); + set(m_Settings, "General", "endorse_state", v); } } bool Settings::hideAPICounter() const { - return m_Settings.value("Settings/hide_api_counter", false).toBool(); + return get(m_Settings, "Settings", "hide_api_counter", false); } void Settings::setHideAPICounter(bool b) { - m_Settings.setValue("Settings/hide_api_counter", b); + set(m_Settings, "Settings", "hide_api_counter", b); } bool Settings::displayForeign() const { - return m_Settings.value("Settings/display_foreign", true).toBool(); + return get(m_Settings, "Settings", "display_foreign", true); } void Settings::setDisplayForeign(bool b) { - m_Settings.setValue("Settings/display_foreign", b); + set(m_Settings, "Settings", "display_foreign", b); } void Settings::setMotDHash(uint hash) { - m_Settings.setValue("motd_hash", hash); + set(m_Settings, "General", "motd_hash", hash); } -uint Settings::getMotDHash() const +unsigned int Settings::getMotDHash() const { - return m_Settings.value("motd_hash", 0).toUInt(); + return get(m_Settings, "motd_hash", 0); } bool Settings::archiveParsing() const { - return m_Settings.value("Settings/archive_parsing_experimental", false).toBool(); + return get(m_Settings, "Settings", "archive_parsing_experimental", false); } void Settings::setArchiveParsing(bool b) { - m_Settings.setValue("Settings/archive_parsing_experimental", b); + set(m_Settings, "Settings", "archive_parsing_experimental", b); } QString Settings::language() { - QString result = m_Settings.value("Settings/language", "").toString(); + QString result = get(m_Settings, "Settings", "language", ""); + if (result.isEmpty()) { QStringList languagePreferences = QLocale::system().uiLanguages(); + if (languagePreferences.length() > 0) { // the users most favoritest language result = languagePreferences.at(0); @@ -940,12 +1153,13 @@ QString Settings::language() result = QLocale::system().name(); } } + return result; } void Settings::setLanguage(const QString& name) { - m_Settings.setValue("Settings/language", name); + set(m_Settings, "Settings", "language", name); } void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) @@ -972,18 +1186,13 @@ ServerList Settings::getServers() const // // so post 2.2.1, only one key is returned: "size", the size of the arrays; // in 2.2.1, one key per server is returned - - // getting the keys - QStringList keys; - { - ScopedGroup sg(m_Settings, "Servers"); - keys = m_Settings.childKeys(); - } + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - if (!keys.empty() && keys[0] != "size") { - // old format - return getServersFromOldMap(); + if (!keys.empty() && keys[0] != "size") { + // old format + return getServersFromOldMap(); + } } @@ -994,12 +1203,11 @@ ServerList Settings::getServers() const { ScopedReadArray sra(m_Settings, "Servers"); - for (int i=0; i("lastDownloads", ""); + for (const auto& s : lastDownloadsString.split(" ")) { const auto bytesPerSecond = s.toInt(); if (bytesPerSecond > 0) { @@ -1008,14 +1216,14 @@ ServerList Settings::getServers() const } ServerInfo server( - m_Settings.value("name").toString(), - m_Settings.value("premium").toBool(), - QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), - m_Settings.value("preferred").toInt(), + sra.get("name", ""), + sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), lastDownloads); list.add(std::move(server)); - } + }); } return list; @@ -1026,10 +1234,10 @@ ServerList Settings::getServersFromOldMap() const // for 2.2.1 and before ServerList list; - ScopedGroup sg(m_Settings, "Servers"); + const ScopedGroup sg(m_Settings, "Servers"); - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); ServerInfo server( serverKey, @@ -1042,7 +1250,7 @@ ServerList Settings::getServersFromOldMap() const // a total list.add(std::move(server)); - } + }); return list; } @@ -1052,22 +1260,18 @@ void Settings::updateServers(ServerList servers) // clean up unavailable servers servers.cleanup(); - { - ScopedGroup sg(m_Settings, "Servers"); - m_Settings.remove(""); - } + removeSection(m_Settings, "Servers"); { ScopedWriteArray swa(m_Settings, "Servers"); - int i=0; for (const auto& server : servers) { - m_Settings.setArrayIndex(i); + swa.next(); - m_Settings.setValue("name", server.name()); - m_Settings.setValue("premium", server.isPremium()); - m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); - m_Settings.setValue("preferred", server.preferred()); + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); QString lastDownloads; for (const auto& speed : server.lastDownloads()) { @@ -1076,9 +1280,7 @@ void Settings::updateServers(ServerList servers) } } - m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); - - ++i; + swa.set("lastDownloads", lastDownloads.trimmed()); } } } @@ -1087,35 +1289,31 @@ std::map Settings::getRecentDirectories() const { std::map map; - ScopedReadArray sra(m_Settings, "recentDirectories"); - - for (int i=0; i("name"); + const QVariant dir = sra.get("directory"); if (name.isValid() && dir.isValid()) { map.emplace(name.toString(), dir.toString()); } - } + }); return map; } void Settings::setRecentDirectories(const std::map& map) { - m_Settings.remove("recentDirectories"); + removeSection(m_Settings, "RecentDirectories"); ScopedWriteArray swa(m_Settings, "recentDirectories"); - int index = 0; for (auto&& p : map) { - m_Settings.setArrayIndex(index); - m_Settings.setValue("name", p.first); - m_Settings.setValue("directory", p.second); + swa.next(); - ++index; + swa.set("name", p.first); + swa.set("directory", p.second); } } @@ -1124,78 +1322,67 @@ std::vector> Settings::getExecutables() const ScopedReadArray sra(m_Settings, "customExecutables"); std::vector> v; - for (int i=0; i map; - const auto keys = m_Settings.childKeys(); - for (auto&& key : keys) { + for (auto&& key : sra.keys()) { map[key] = m_Settings.value(key); } v.push_back(map); - } + }); return v; } void Settings::setExecutables(const std::vector>& v) { - m_Settings.remove("customExecutables"); + removeSection(m_Settings, "customExecutables"); ScopedWriteArray swa(m_Settings, "customExecutables"); - int i = 0; - for (const auto& map : v) { - m_Settings.setArrayIndex(i); + swa.next(); for (auto&& p : map) { - m_Settings.setValue(p.first, p.second); + swa.set(p.first, p.second); } - - ++i; } } bool Settings::isTutorialCompleted(const QString& windowName) const { - const auto v = getOptional( - m_Settings, "CompletedWindowTutorials/" + windowName); - - return v.value_or(false); + return get(m_Settings, "CompletedWindowTutorials", windowName, false); } void Settings::setTutorialCompleted(const QString& windowName, bool b) { - m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); + set(m_Settings, "CompletedWindowTutorials", windowName, b); } bool Settings::keepBackupOnInstall() const { - return getOptional(m_Settings, "backup_install").value_or(false); + return get(m_Settings, "backup_install", false); } void Settings::setKeepBackupOnInstall(bool b) { - m_Settings.setValue("backup_install", b); + set(m_Settings, "General", "backup_install", b); } QuestionBoxMemory::Button Settings::getQuestionButton( const QString& windowName, const QString& filename) const { - const QString windowSetting("DialogChoices/" + windowName); + const QString sectionName("DialogChoices"); if (!filename.isEmpty()) { - const auto fileSetting = windowSetting + "/" + filename; - - if (auto v=getOptional(m_Settings, fileSetting)) { + const auto fileSetting = windowName + "/" + filename; + if (auto v=getOptional(m_Settings, sectionName, filename)) { return static_cast(*v); } } - if (auto v=getOptional(m_Settings, windowSetting)) { + if (auto v=getOptional(m_Settings, sectionName, windowName)) { return static_cast(*v); } @@ -1205,12 +1392,12 @@ QuestionBoxMemory::Button Settings::getQuestionButton( void Settings::setQuestionWindowButton( const QString& windowName, QuestionBoxMemory::Button button) { - const QString settingName("DialogChoices/" + windowName); + const QString sectionName("DialogChoices/"); if (button == QuestionBoxMemory::NoButton) { - m_Settings.remove(settingName); + remove(m_Settings, sectionName, windowName); } else { - m_Settings.setValue(settingName, static_cast(button)); + set(m_Settings, sectionName, windowName, button); } } @@ -1218,51 +1405,51 @@ void Settings::setQuestionFileButton( const QString& windowName, const QString& filename, QuestionBoxMemory::Button button) { - const QString settingName("DialogChoices/" + windowName + "/" + filename); + const QString sectionName("DialogChoices"); + const QString settingName(windowName + "/" + filename); if (button == QuestionBoxMemory::NoButton) { - m_Settings.remove(settingName); + remove(m_Settings, sectionName, settingName); } else { - m_Settings.setValue(settingName, static_cast(button)); + set(m_Settings, sectionName, settingName, button); } } void Settings::resetQuestionButtons() { - ScopedGroup sg(m_Settings, "DialogChoices"); - m_Settings.remove(""); + removeSection(m_Settings, "DialogChoices"); } std::optional Settings::getIndex(const QComboBox* cb) const { - return getOptional(m_Settings, indexSettingName(cb)); + return getOptional(m_Settings, "Widgets", indexSettingName(cb)); } void Settings::saveIndex(const QComboBox* cb) { - m_Settings.setValue(indexSettingName(cb), cb->currentIndex()); + set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); } void Settings::restoreIndex(QComboBox* cb, std::optional def) const { - if (auto v=getOptional(m_Settings, indexSettingName(cb), def)) { + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { cb->setCurrentIndex(*v); } } std::optional Settings::getIndex(const QTabWidget* w) const { - return getOptional(m_Settings, indexSettingName(w)); + return getOptional(m_Settings, "Widgets", indexSettingName(w)); } void Settings::saveIndex(const QTabWidget* w) { - m_Settings.setValue(indexSettingName(w), w->currentIndex()); + set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); } void Settings::restoreIndex(QTabWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, indexSettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { w->setCurrentIndex(*v); } } @@ -1270,20 +1457,20 @@ void Settings::restoreIndex(QTabWidget* w, std::optional def) const std::optional Settings::getChecked(const QAbstractButton* w) const { warnIfNotCheckable(w); - return getOptional(m_Settings, checkedSettingName(w)); + return getOptional(m_Settings, "Widgets", checkedSettingName(w)); } void Settings::saveChecked(const QAbstractButton* w) { warnIfNotCheckable(w); - m_Settings.setValue(checkedSettingName(w), w->isChecked()); + set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); } void Settings::restoreChecked(QAbstractButton* w, std::optional def) const { warnIfNotCheckable(w); - if (auto v=getOptional(m_Settings, checkedSettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { w->setChecked(*v); } } @@ -1328,7 +1515,7 @@ void Settings::dump() const { static const QStringList ignore({ "username", "password", "nexus_api_key" - }); + }); log::debug("settings:"); @@ -1379,18 +1566,17 @@ void GeometrySettings::resetIfNeeded() return; } - ScopedGroup sg(m_Settings, "geometry"); - m_Settings.remove(""); + removeSection(m_Settings, "Geometry"); } void GeometrySettings::saveGeometry(const QWidget* w) { - m_Settings.setValue(geoSettingName(w), w->saveGeometry()); + set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } bool GeometrySettings::restoreGeometry(QWidget* w) const { - if (auto v=getOptional(m_Settings, geoSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { w->restoreGeometry(*v); return true; } @@ -1400,12 +1586,12 @@ bool GeometrySettings::restoreGeometry(QWidget* w) const void GeometrySettings::saveState(const QMainWindow* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QMainWindow* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1415,12 +1601,12 @@ bool GeometrySettings::restoreState(QMainWindow* w) const void GeometrySettings::saveState(const QHeaderView* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QHeaderView* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1430,12 +1616,12 @@ bool GeometrySettings::restoreState(QHeaderView* w) const void GeometrySettings::saveState(const QSplitter* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QSplitter* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1445,12 +1631,12 @@ bool GeometrySettings::restoreState(QSplitter* w) const void GeometrySettings::saveState(const ExpanderWidget* expander) { - m_Settings.setValue(stateSettingName(expander), expander->saveState()); + set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); } bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - if (auto v=getOptional(m_Settings, stateSettingName(expander))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { expander->restoreState(*v); return true; } @@ -1460,12 +1646,12 @@ bool GeometrySettings::restoreState(ExpanderWidget* expander) const void GeometrySettings::saveVisibility(const QWidget* w) { - m_Settings.setValue(visibilitySettingName(w), w->isVisible()); + set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); } bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, visibilitySettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { w->setVisible(*v); return true; } @@ -1476,8 +1662,8 @@ bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) co void GeometrySettings::restoreToolbars(QMainWindow* w) const { // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "toolbar_size"); - const auto style = getOptional(m_Settings, "toolbar_button_style"); + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); for (auto* tb : w->findChildren()) { if (size) { @@ -1506,8 +1692,8 @@ void GeometrySettings::saveToolbars(const QMainWindow* w) if (!tbs.isEmpty()) { const auto* tb = tbs[0]; - m_Settings.setValue("toolbar_size", tb->iconSize()); - m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); + set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); + set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); } } @@ -1544,12 +1730,13 @@ QStringList GeometrySettings::getModInfoTabOrder() const void GeometrySettings::setModInfoTabOrder(const QString& names) { - m_Settings.setValue("mod_info_tab_order", names); + set(m_Settings, "Geometry", "mod_info_tab_order", names); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - const auto monitor = getOptional(m_Settings, "geometry/MainWindow_monitor"); + const auto monitor = getOptional( + m_Settings, "Geometry", "MainWindow_monitor"); QPoint center; @@ -1567,7 +1754,7 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) if (auto* handle=w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); - m_Settings.setValue("geometry/MainWindow_monitor", screenId); + set(m_Settings, "Geometry", "MainWindow_monitor", screenId); } } } @@ -1617,7 +1804,7 @@ void GeometrySettings::saveDocks(const QMainWindow* mw) size = dock->size().height(); } - m_Settings.setValue(dockSettingName(dock), size); + set(m_Settings, "Geometry", dockSettingName(dock), size); } } @@ -1634,7 +1821,7 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const // for each dock for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, dockSettingName(dock))) { + if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { // remember this dock, its size and orientation dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); } @@ -1649,7 +1836,7 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } @@ -1660,68 +1847,74 @@ ColorSettings::ColorSettings(QSettings& s) QColor ColorSettings::modlistOverwrittenLoose() const { - return getOptional(m_Settings, "Settings/overwrittenLooseFilesColor") - .value_or(QColor(0, 255, 0, 64)); + return get( + m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); } void ColorSettings::setModlistOverwrittenLoose(const QColor& c) { - m_Settings.setValue("Settings/overwrittenLooseFilesColor", c); + set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); } QColor ColorSettings::modlistOverwritingLoose() const { - return getOptional(m_Settings, "Settings/overwritingLooseFilesColor") - .value_or(QColor(255, 0, 0, 64)); + return get( + m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); } void ColorSettings::setModlistOverwritingLoose(const QColor& c) { - m_Settings.setValue("Settings/overwritingLooseFilesColor", c); + set(m_Settings, "Settings", "overwritingLooseFilesColor", c); } QColor ColorSettings::modlistOverwrittenArchive() const { - return getOptional(m_Settings, "Settings/overwrittenArchiveFilesColor") - .value_or(QColor(0, 255, 255, 64)); + return get( + m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); } void ColorSettings::setModlistOverwrittenArchive(const QColor& c) { - m_Settings.setValue("Settings/overwrittenArchiveFilesColor", c); + set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); } QColor ColorSettings::modlistOverwritingArchive() const { - return getOptional(m_Settings, "Settings/overwritingArchiveFilesColor") - .value_or(QColor(255, 0, 255, 64)); + return get( + m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); } void ColorSettings::setModlistOverwritingArchive(const QColor& c) { - m_Settings.setValue("Settings/overwritingArchiveFilesColor", c); + set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); } QColor ColorSettings::modlistContainsPlugin() const { - return getOptional(m_Settings, "Settings/containsPluginColor") - .value_or(QColor(0, 0, 255, 64)); + return get( + m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } void ColorSettings::setModlistContainsPlugin(const QColor& c) { - m_Settings.setValue("Settings/containsPluginColor", c); + set(m_Settings, "Settings", "containsPluginColor", c); } QColor ColorSettings::pluginListContained() const { - return getOptional(m_Settings, "Settings/containedColor") - .value_or(QColor(0, 0, 255, 64)); + return get( + m_Settings, "Settings", "containedColor", + QColor(0, 0, 255, 64)); } void ColorSettings::setPluginListContained(const QColor& c) { - m_Settings.setValue("Settings/containedColor", c); + set(m_Settings, "Settings", "containedColor", c); } @@ -1738,10 +1931,9 @@ void PluginSettings::clearPlugins() m_PluginBlacklist.clear(); ScopedReadArray sra(m_Settings, "pluginBlacklist"); - for (int i = 0; i < sra.count(); ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } + sra.for_each([&]{ + m_PluginBlacklist.insert(sra.get("name")); + }); } void PluginSettings::registerPlugin(IPlugin *plugin) @@ -1749,16 +1941,26 @@ void PluginSettings::registerPlugin(IPlugin *plugin) m_Plugins.push_back(plugin); m_PluginSettings.insert(plugin->name(), QVariantMap()); m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + for (const PluginSetting &setting : plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + const QString settingName = plugin->name() + "/" + setting.key; + + QVariant temp = get( + m_Settings, "Plugins", settingName, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { log::warn( "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", temp.toString(), setting.key, plugin->name()); + temp = setting.defaultValue; } + m_PluginSettings[plugin->name()][setting.key] = temp; - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); + + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } } @@ -1773,6 +1975,7 @@ QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString if (iterPlugin == m_PluginSettings.end()) { return QVariant(); } + auto iterSetting = iterPlugin->find(key); if (iterSetting == iterPlugin->end()) { return QVariant(); @@ -1784,13 +1987,16 @@ QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } // store the new setting both in memory and in the ini m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); + set(m_Settings, "Plugins", pluginName + "/" + key, value); } QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const @@ -1798,15 +2004,21 @@ QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QStri if (!m_PluginSettings.contains(pluginName)) { return def; } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); + + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -void PluginSettings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +void PluginSettings::setPluginPersistent( + const QString &pluginName, const QString &key, const QVariant &value, bool sync) { if (!m_PluginSettings.contains(pluginName)) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + + set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + if (sync) { m_Settings.sync(); } @@ -1820,13 +2032,13 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - m_Settings.remove("pluginBlacklist"); + removeSection(m_Settings, "PluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "PluginBlacklist"); - ScopedWriteArray swa(m_Settings, "pluginBlacklist"); - int idx = 0; for (const QString &plugin : m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); + swa.next(); + swa.set("name", plugin); } } @@ -1868,8 +2080,8 @@ void PluginSettings::save() { for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = "Plugins/" + iterPlugins.key() + "/" + iterSettings.key(); - m_Settings.setValue(key, iterSettings.value()); + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); } } diff --git a/src/settings.h b/src/settings.h index ae29d788..403c2d71 100644 --- a/src/settings.h +++ b/src/settings.h @@ -68,9 +68,6 @@ public: void saveState(const QHeaderView* header); bool restoreState(QHeaderView* header) const; - void saveState(const QToolBar* toolbar); - bool restoreState(QToolBar* toolbar) const; - void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; @@ -258,8 +255,6 @@ public: std::optional getStyleName() const; void setStyleName(const QString& name); - std::optional getUseProxy() const; - std::optional getVersion() const; bool getFirstStart() const; @@ -408,7 +403,7 @@ public: /** * @return true if the user configured the use of a network proxy */ - bool useProxy() const; + bool getUseProxy() const; void setUseProxy(bool b); /** @@ -419,7 +414,6 @@ public: EndorsementState endorsementState() const; void setEndorsementState(EndorsementState s); - void setEndorsementState(const QString& s); /** * @return true if the API counter should be hidden @@ -436,7 +430,8 @@ public: /** * @brief sets the new motd hash **/ - void setMotDHash(uint hash); + unsigned int getMotDHash() const; + void setMotDHash(unsigned int hash); /** * @return true if the user wants to have archives being parsed to show conflicts and contents @@ -444,11 +439,6 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); - /** - * @return hash of the last displayed message of the day - **/ - uint getMotDHash() const; - /** * @return short code of the configured language (corresponding to the translation files) */ diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 3de1a6ba..8822200e 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -75,7 +75,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { ui->offlineBox->setChecked(settings().offlineMode()); - ui->proxyBox->setChecked(settings().useProxy()); + ui->proxyBox->setChecked(settings().getUseProxy()); ui->endorsementBox->setChecked(settings().endorsementIntegration()); ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); -- cgit v1.3.1 From e9dba260cb9548dd5863ac66da18c295f6499b92 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 14:52:02 -0400 Subject: split settings into a bunch of classes removed "get" from the getters that had it --- src/browserdialog.cpp | 2 +- src/downloadlistsortproxy.cpp | 2 +- src/downloadmanager.cpp | 2 +- src/executableslist.cpp | 2 +- src/filedialogmemory.cpp | 4 +- src/main.cpp | 26 +- src/mainwindow.cpp | 121 +-- src/modinfodialog.cpp | 2 +- src/modinfodialogconflicts.cpp | 16 +- src/modinfodialogimages.cpp | 4 +- src/modinfodialognexus.cpp | 2 +- src/modinfooverwrite.cpp | 2 +- src/modinforegular.cpp | 2 +- src/modlist.cpp | 8 +- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 112 +- src/pluginlist.cpp | 2 +- src/profile.cpp | 8 +- src/profilesdialog.cpp | 4 +- src/settings.cpp | 2075 ++++++++++++++++++++----------------- src/settings.h | 569 ++++++---- src/settingsdialog.cpp | 6 +- src/settingsdialogdiagnostics.cpp | 13 +- src/settingsdialoggeneral.cpp | 28 +- src/settingsdialognexus.cpp | 32 +- src/settingsdialogpaths.cpp | 52 +- src/settingsdialogsteam.cpp | 4 +- src/settingsdialogworkarounds.cpp | 30 +- src/statusbar.cpp | 2 +- src/usvfsconnector.cpp | 4 +- 30 files changed, 1712 insertions(+), 1426 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 70da0b9c..72cb8862 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(QWidget *parent) ui->setupUi(this); m_AccessManager->setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/cookies.dat"))); + QDir::fromNativeSeparators(Settings::instance().paths().cache() + "/cookies.dat"))); Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 7bda139b..a69993c0 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -110,7 +110,7 @@ bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) if (m_CurrentFilter.length() == 0) { return true; } else if (sourceRow < m_Manager->numTotalDownloads()) { - QString displayedName = Settings::instance().metaDownloads() + QString displayedName = Settings::instance().interface().metaDownloads() ? m_Manager->getDisplayName(sourceRow) : m_Manager->getFileName(sourceRow); return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index a5dc164c..56238ef3 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1731,7 +1731,7 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } - const auto servers = m_OrganizerCore->settings().getServers(); + const auto servers = m_OrganizerCore->settings().network().servers(); std::sort( resultList.begin(), diff --git a/src/executableslist.cpp b/src/executableslist.cpp index f2df2d6d..dce9181b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -75,7 +75,7 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; - for (auto& map : s.getExecutables()) { + for (auto& map : s.executables()) { Executable::Flags flags; if (map["toolbar"].toBool()) diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 96587ac7..8cfeb6b5 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -25,12 +25,12 @@ static std::map g_Cache; void FileDialogMemory::save(Settings& s) { - s.setRecentDirectories(g_Cache); + s.paths().setRecent(g_Cache); } void FileDialogMemory::restore(const Settings& s) { - g_Cache = s.getRecentDirectories(); + g_Cache = s.paths().recent(); } QString FileDialogMemory::getOpenFileName( diff --git a/src/main.cpp b/src/main.cpp index aa781c19..b5568fec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - auto selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.game().selectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -271,12 +271,12 @@ QString determineProfile(QStringList &arguments, const Settings &settings) MOBase::IPluginGame *selectGame( Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setManagedGameName(game->gameName()); + settings.game().setName(game->gameName()); QString gameDir = gamePath.absolutePath(); game->setGamePath(gameDir); - settings.setManagedGameDirectory(gameDir); + settings.game().setDirectory(gameDir); return game; } @@ -289,7 +289,7 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const auto gameName = settings.getManagedGameName(); + const auto gameName = settings.game().name(); const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { @@ -299,7 +299,7 @@ MOBase::IPluginGame *determineCurrentGame( return nullptr; } - auto gamePath = settings.getManagedGameDirectory(); + auto gamePath = settings.game().directory(); if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } @@ -320,7 +320,7 @@ MOBase::IPluginGame *determineCurrentGame( //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - const auto gamePath = settings.getManagedGameDirectory(); + const auto gamePath = settings.game().directory(); reportError( QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") @@ -570,11 +570,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::info("working directory: {}", QDir::currentPath()); Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); - log::getDefault().setLevel(settings.logLevel()); + log::getDefault().setLevel(settings.diagnostics().logLevel()); // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); + OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; @@ -621,7 +621,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QString edition; - if (auto v=settings.getManagedGameEdition()) { + if (auto v=settings.game().edition()) { edition = *v; } else { QStringList editions = game->gameVariants(); @@ -640,7 +640,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } else { edition = selection.getChoiceString(); - settings.setManagedGameEdition(edition); + settings.game().setEdition(edition); } } } @@ -702,7 +702,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.activateWindow(); QString apiKey; - if (settings.getNexusApiKey(apiKey)) { + if (settings.nexus().apiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -712,9 +712,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName().value_or(""))) { + if (!application.setStyleFile(settings.interface().styleName().value_or(""))) { // disable invalid stylesheet - settings.setStyleName(""); + settings.interface().setStyleName(""); } int res = 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42b19cb7..657c1a27 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -222,8 +222,8 @@ MainWindow::MainWindow(Settings &settings { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(settings.getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); ui->statusBar->setup(ui); @@ -253,7 +253,7 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(settings.language()); + languageChange(settings.interface().language()); m_CategoryFactory.loadCategories(); @@ -1194,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings().isTutorialCompleted(windowName)) { + if (!m_OrganizerCore.settings().interface().isTutorialCompleted(windowName)) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -1225,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_OrganizerCore.settings().getFirstStart()) { + if (m_OrganizerCore.settings().firstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -1247,11 +1247,11 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().setFirstStart(false); } - m_OrganizerCore.settings().restoreIndex(ui->groupCombo); + m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); allowListResize(); - m_OrganizerCore.settings().registerAsNXMHandler(false); + m_OrganizerCore.settings().nexus().registerAsNXMHandler(false); m_WasVisible = true; updateProblemsButton(); } @@ -1751,7 +1751,7 @@ bool MainWindow::refreshProfiles(bool selectProfile) profileBox->clear(); profileBox->addItem(QObject::tr("")); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -1990,7 +1990,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); - if (m_OrganizerCore.settings().forceEnableCoreFiles() + if (m_OrganizerCore.settings().game().forceEnableCoreFiles() && defaultArchives.contains(fileInfo.fileName())) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); @@ -2140,7 +2140,7 @@ void MainWindow::readSettings(const Settings& settings) { // special case in case someone puts 0 in the INI - auto v = settings.getIndex(ui->executablesListBox); + auto v = settings.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2148,7 +2148,7 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.restoreIndex(ui->groupCombo); + settings.widgets().restoreIndex(ui->groupCombo); { settings.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2157,7 +2157,7 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (settings.getUseProxy()) { + if (settings.network().useProxy()) { activateProxy(true); } } @@ -2165,12 +2165,12 @@ void MainWindow::readSettings(const Settings& settings) void MainWindow::processUpdates(Settings& settings) { const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); - const auto lastVersion = settings.getVersion().value_or(earliest); + const auto lastVersion = settings.version().value_or(earliest); const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); settings.processUpdates(currentVersion, lastVersion); - if (!settings.getFirstStart()) { + if (!settings.firstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2222,8 +2222,8 @@ void MainWindow::storeSettings(Settings& s) s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); - s.saveIndex(ui->groupCombo); - s.saveIndex(ui->executablesListBox); + s.widgets().saveIndex(ui->groupCombo); + s.widgets().saveIndex(ui->executablesListBox); } ILockedWaitingForProcess* MainWindow::lock() @@ -2751,7 +2751,7 @@ void MainWindow::restoreBackup_clicked() ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); if (backupRegEx.indexIn(modInfo->name()) != -1) { QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); + QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods())); if (!modDir.exists(regName) || (QMessageBox::question(this, tr("Overwrite?"), tr("This will replace the existing mod \"%1\". Continue?").arg(regName), @@ -2759,7 +2759,7 @@ void MainWindow::restoreBackup_clicked() if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { reportError(tr("failed to remove mod \"%1\"").arg(regName)); } else { - QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()) + "/" + regName; + QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName; if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } @@ -3015,7 +3015,7 @@ void MainWindow::untrack_clicked() void MainWindow::windowTutorialFinished(const QString &windowName) { - m_OrganizerCore.settings().setTutorialCompleted(windowName); + m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); } void MainWindow::overwriteClosed(int) @@ -3645,7 +3645,7 @@ void MainWindow::createSeparator_clicked() m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } - if (auto c=m_OrganizerCore.settings().getPreviousSeparatorColor()) { + if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); } } @@ -3662,7 +3662,7 @@ void MainWindow::setColor_clicked() if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); } - else if (auto c=settings.getPreviousSeparatorColor()) { + else if (auto c=settings.colors().previousSeparatorColor()) { dialog.setCurrentColor(*c); } @@ -3673,7 +3673,7 @@ void MainWindow::setColor_clicked() if (!currentColor.isValid()) return; - settings.setPreviousSeparatorColor(currentColor); + settings.colors().setPreviousSeparatorColor(currentColor); QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3710,7 +3710,7 @@ void MainWindow::resetColor_clicked() modInfo->setColor(color); } - m_OrganizerCore.settings().removePreviousSeparatorColor(); + m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); } void MainWindow::createModFromOverwrite() @@ -4184,7 +4184,7 @@ void MainWindow::checkModsForUpdates() NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -4387,12 +4387,12 @@ void MainWindow::openIniFolder() void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getModDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() @@ -4758,7 +4758,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); - if (info->getNexusID() > 0 && Settings::instance().endorsementIntegration()) { + if (info->getNexusID() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (info->endorsedState()) { case ModInfo::ENDORSED_TRUE: { menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); @@ -5007,19 +5007,19 @@ void MainWindow::on_actionSettings_triggered() { Settings &settings = m_OrganizerCore.settings(); - QString oldModDirectory(settings.getModDirectory()); - QString oldCacheDirectory(settings.getCacheDirectory()); - QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); - bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.getUseProxy(); + QString oldModDirectory(settings.paths().mods()); + QString oldCacheDirectory(settings.paths().cache()); + QString oldProfilesDirectory(settings.paths().profiles()); + QString oldManagedGameDirectory(settings.game().directory().value_or("")); + bool oldDisplayForeign(settings.interface().displayForeign()); + bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); - if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { + if (oldManagedGameDirectory != settings.game().directory()) { QMessageBox::about(this, tr("Restarting MO"), tr("Changing the managed game directory requires restarting MO.\n" "Any pending downloads will be paused.\n\n" @@ -5029,28 +5029,28 @@ void MainWindow::on_actionSettings_triggered() } InstallationManager *instManager = m_OrganizerCore.installationManager(); - instManager->setModsDirectory(settings.getModDirectory()); - instManager->setDownloadDirectory(settings.getDownloadDirectory()); + instManager->setModsDirectory(settings.paths().mods()); + instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); refreshFilters(); - if (settings.getProfileDirectory() != oldProfilesDirectory) { + if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); } - if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) { + if (dlManager->getOutputDirectory() != settings.paths().downloads()) { if (dlManager->downloadsInProgress()) { MessageDialog::showMessage(tr("Can't change download directory while " "downloads are in progress!"), this); } else { - dlManager->setOutputDirectory(settings.getDownloadDirectory()); + dlManager->setOutputDirectory(settings.paths().downloads()); } } - if ((settings.getModDirectory() != oldModDirectory) - || (settings.displayForeign() != oldDisplayForeign)) { + if ((settings.paths().mods() != oldModDirectory) + || (settings.interface().displayForeign() != oldDisplayForeign)) { m_OrganizerCore.profileRefresh(); } @@ -5075,18 +5075,19 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.refreshLists(); } - if (settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory()); + if (settings.paths().cache() != oldCacheDirectory) { + NexusInterface::instance(&m_PluginContainer)->setCacheDirectory( + settings.paths().cache()); } - if (proxy != settings.getUseProxy()) { - activateProxy(settings.getUseProxy()); + if (proxy != settings.network().useProxy()) { + activateProxy(settings.network().useProxy()); } ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); @@ -5402,10 +5403,10 @@ void MainWindow::motdReceived(const QString &motd) // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { uint hash = qHash(motd); - if (hash != m_OrganizerCore.settings().getMotDHash()) { + if (hash != m_OrganizerCore.settings().motdHash()) { MotDDialog dialog(motd); dialog.exec(); - m_OrganizerCore.settings().setMotDHash(hash); + m_OrganizerCore.settings().setMotdHash(hash); } } } @@ -5528,7 +5529,7 @@ void MainWindow::initDownloadView() void MainWindow::updateDownloadView() { // set the view attribute and default row sizes - if (m_OrganizerCore.settings().compactDownloads()) { + if (m_OrganizerCore.settings().interface().compactDownloads()) { ui->downloadView->setProperty("downloadView", "compact"); setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }"); } else { @@ -5541,7 +5542,7 @@ void MainWindow::updateDownloadView() // reapply global stylesheet on the widget level (!) to override the defaults //ui->downloadView->setStyleSheet(styleSheet()); - ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().metaDownloads()); + ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().interface().metaDownloads()); ui->downloadView->style()->unpolish(ui->downloadView); ui->downloadView->style()->polish(ui->downloadView); qobject_cast(ui->downloadView->header())->customResizeSections(); @@ -5554,7 +5555,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else @@ -5566,7 +5567,7 @@ void MainWindow::toggleMO2EndorseState() { const auto& s = m_OrganizerCore.settings(); - if (!s.endorsementIntegration()) { + if (!s.nexus().endorsementIntegration()) { ui->actionEndorseMO->setVisible(false); return; } @@ -5576,7 +5577,7 @@ void MainWindow::toggleMO2EndorseState() bool enabled = false; QString text; - switch (s.endorsementState()) + switch (s.nexus().endorsementState()) { case EndorsementState::Accepted: { @@ -5631,9 +5632,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData mod->setIsEndorsed(false); } - if (Settings::instance().endorsementIntegration()) { + if (Settings::instance().nexus().endorsementIntegration()) { if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5642,13 +5643,13 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData } } - if (!searchedMO2NexusGame && Settings::instance().endorsementIntegration()) { + if (!searchedMO2NexusGame && Settings::instance().nexus().endorsementIntegration()) { auto gamePlugin = m_OrganizerCore.getGame("SkyrimSE"); if (gamePlugin) { auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5862,7 +5863,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa } } - m_OrganizerCore.settings().setEndorsementState(s); + m_OrganizerCore.settings().nexus().setEndorsementState(s); toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), @@ -5901,7 +5902,7 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - auto servers = m_OrganizerCore.settings().getServers(); + auto servers = m_OrganizerCore.settings().network().servers(); for (const QVariant &var : resultData.toList()) { const QVariantMap map = var.toMap(); @@ -5929,7 +5930,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat } } - m_OrganizerCore.settings().updateServers(servers); + m_OrganizerCore.settings().network().updateServers(servers); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f3840230..2178ef34 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -381,7 +381,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = m_core->settings().geometry().getModInfoTabOrder(); + const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); // whether the tabs can be sorted; if the object name of a tab widget is not // found in orderedNames, the list cannot be sorted safely diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 7840269d..3a71b405 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -409,7 +409,7 @@ void ConflictsTab::clear() void ConflictsTab::saveState(Settings& s) { - s.saveIndex(ui->tabConflictsTabs); + s.widgets().saveIndex(ui->tabConflictsTabs); m_general.saveState(s); m_advanced.saveState(s); @@ -417,7 +417,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - s.restoreIndex(ui->tabConflictsTabs, 0); + s.widgets().restoreIndex(ui->tabConflictsTabs, 0); m_general.restoreState(s); m_advanced.restoreState(s); @@ -1014,17 +1014,17 @@ void AdvancedConflictsTab::clear() void AdvancedConflictsTab::saveState(Settings& s) { s.geometry().saveState(ui->conflictsAdvancedList->header()); - s.saveChecked(ui->conflictsAdvancedShowNoConflict); - s.saveChecked(ui->conflictsAdvancedShowAll); - s.saveChecked(ui->conflictsAdvancedShowNearest); + s.widgets().saveChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().saveChecked(ui->conflictsAdvancedShowAll); + s.widgets().saveChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::restoreState(const Settings& s) { s.geometry().restoreState(ui->conflictsAdvancedList->header()); - s.restoreChecked(ui->conflictsAdvancedShowNoConflict); - s.restoreChecked(ui->conflictsAdvancedShowAll); - s.restoreChecked(ui->conflictsAdvancedShowNearest); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().restoreChecked(ui->conflictsAdvancedShowAll); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 38c12d8a..9d347f57 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -130,13 +130,13 @@ void ImagesTab::update() void ImagesTab::saveState(Settings& s) { - s.saveChecked(ui->imagesShowDDS); + s.widgets().saveChecked(ui->imagesShowDDS); s.geometry().saveState(ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { - s.restoreChecked(ui->imagesShowDDS); + s.widgets().restoreChecked(ui->imagesShowDDS); s.geometry().restoreState(ui->tabImagesSplitter); } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 6d28cbe3..95e62328 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -19,7 +19,7 @@ NexusTab::NexusTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); - ui->endorse->setVisible(core().settings().endorsementIntegration()); + ui->endorse->setVisible(core().settings().nexus().endorsementIntegration()); connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); connect( diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 37c8c650..fb110abb 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -23,7 +23,7 @@ bool ModInfoOverwrite::isEmpty() const QString ModInfoOverwrite::absolutePath() const { - return Settings::instance().getOverwriteDirectory(); + return Settings::instance().paths().overwrite(); } std::vector ModInfoOverwrite::getFlags() const diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index ce29e11e..3cff914a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -631,7 +631,7 @@ std::vector ModInfoRegular::getFlags() const std::vector result = ModInfoWithConflictInfo::getFlags(); if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE) && - Settings::instance().endorsementIntegration()) { + Settings::instance().nexus().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } if ((m_NexusID > 0) && diff --git a/src/modlist.cpp b/src/modlist.cpp index 94b4a387..6018d3d4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -390,7 +390,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } else if (role == Qt::ForegroundRole) { if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) { - return Settings::getIdealTextColor(modInfo->getColor()); + return ColorSettings::idealTextColor(modInfo->getColor()); } else if (column == COL_NAME) { int highlight = modInfo->getHighlight(); if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) @@ -428,7 +428,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) - || Settings::instance().colorSeparatorScrollbar())) { + || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->getColor(); } else { return QVariant(); @@ -999,8 +999,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa ModInfo::Ptr modInfo = ModInfo::getByIndex(row); QDir modDir = QDir(modInfo->absolutePath()); - QDir allModsDir(Settings::instance().getModDirectory()); - QDir overwriteDir(Settings::instance().getOverwriteDirectory()); + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); QStringList sourceList; QStringList targetList; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 16190ca4..c6ef7bc7 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -572,7 +572,7 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); + Settings::instance().paths().cache() + "/nexus_cookies.dat"))); if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? diff --git a/src/organizercore.cpp b/src/organizercore.cpp index af0cf969..1a89641d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -146,7 +146,7 @@ static void startSteam(QWidget *widget) QStringList args; QString username; QString password; - if (Settings::instance().getSteamLogin(username, password)) { + if (Settings::instance().steam().login(username, password)) { args << "-login"; args << username; if (password != "") { @@ -275,12 +275,13 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_ArchivesInit(false) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads()); - NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance(m_PluginContainer)->setCacheDirectory( + m_Settings.paths().cache()); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); + m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads()); connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, SLOT(downloadSpeed(QString, int))); @@ -333,7 +334,7 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { if (m_CurrentProfile != nullptr) { - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); } m_ExecutablesList.store(m_Settings); @@ -356,7 +357,7 @@ void OrganizerCore::storeSettings() QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), tr("An error occurred trying to write back MO settings to %1: %2") - .arg(m_Settings.getFilename(), reason)); + .arg(m_Settings.filename(), reason)); } } @@ -432,8 +433,9 @@ void OrganizerCore::updateExecutablesList() // TODO this has nothing to do with executables list move to an appropriate // function! - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } void OrganizerCore::setUserInterface(IUserInterface *userInterface, @@ -478,7 +480,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (userInterface != nullptr) { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (isOnline() && !m_Settings.offlineMode()) { + if (isOnline() && !m_Settings.network().offlineMode()) { m_Updater.testForUpdate(); } else { log::debug("user doesn't seem to be connected to the internet"); @@ -541,7 +543,7 @@ bool OrganizerCore::nexusApi(bool retry) return false; } else { QString apiKey; - if (m_Settings.getNexusApiKey(apiKey)) { + if (m_Settings.nexus().apiKey(apiKey)) { // credentials stored or user entered them manually log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); @@ -608,7 +610,7 @@ void OrganizerCore::removeOrigin(const QString &name) void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond) { - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); + m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond); } InstallationManager *OrganizerCore::installationManager() @@ -629,9 +631,9 @@ bool OrganizerCore::createDirectory(const QString &path) { } bool OrganizerCore::checkPathSymlinks() { - bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() || - QFileInfo(m_Settings.getModDirectory()).isSymLink() || - QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink()); + bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + QFileInfo(m_Settings.paths().mods()).isSymLink() || + QFileInfo(m_Settings.paths().overwrite()).isSymLink()); if (hasSymlink) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " @@ -643,17 +645,17 @@ bool OrganizerCore::checkPathSymlinks() { } bool OrganizerCore::bootstrap() { - return createDirectory(m_Settings.getProfileDirectory()) && - createDirectory(m_Settings.getModDirectory()) && - createDirectory(m_Settings.getDownloadDirectory()) && - createDirectory(m_Settings.getOverwriteDirectory()) && + return createDirectory(m_Settings.paths().profiles()) && + createDirectory(m_Settings.paths().mods()) && + createDirectory(m_Settings.paths().downloads()) && + createDirectory(m_Settings.paths().overwrite()) && createDirectory(QString::fromStdWString(crashDumpsPath())) && checkPathSymlinks() && cycleDiagnostics(); } void OrganizerCore::createDefaultProfile() { - QString profilesPath = settings().getProfileDirectory(); + QString profilesPath = settings().paths().profiles(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { Profile newProf("Default", managedGame(), false); @@ -674,18 +676,18 @@ void OrganizerCore::updateVFSParams( void OrganizerCore::setLogLevel(log::Levels level) { - m_Settings.setLogLevel(level); + m_Settings.diagnostics().setLogLevel(level); updateVFSParams( - m_Settings.logLevel(), - m_Settings.crashDumpsType(), + m_Settings.diagnostics().logLevel(), + m_Settings.diagnostics().crashDumpsType(), m_Settings.executablesBlacklist()); - log::getDefault().setLevel(m_Settings.logLevel()); + log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().crashDumpsMax()) + if (int maxDumps = settings().diagnostics().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); return true; } @@ -720,7 +722,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) return; } - QDir profileBaseDir(settings().getProfileDirectory()); + QDir profileBaseDir(settings().paths().profiles()); QString profileDir = profileBaseDir.absoluteFilePath(profileName); if (!QDir(profileDir).exists()) { @@ -744,7 +746,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); @@ -776,22 +778,22 @@ QString OrganizerCore::profilePath() const QString OrganizerCore::downloadsPath() const { - return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().downloads()); } QString OrganizerCore::overwritePath() const { - return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().overwrite()); } QString OrganizerCore::basePath() const { - return QDir::fromNativeSeparators(m_Settings.getBaseDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().base()); } QString OrganizerCore::modsPath() const { - return QDir::fromNativeSeparators(m_Settings.getModDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().mods()); } MOBase::VersionInfo OrganizerCore::appVersion() const @@ -821,10 +823,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) return nullptr; } - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); QString targetDirectory - = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + = QDir::fromNativeSeparators(m_Settings.paths().mods()) .append("/") .append(name); @@ -912,7 +914,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, modName.update(initModName, GUESS_USER); } m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -977,7 +979,7 @@ void OrganizerCore::installDownload(int index) m_CurrentProfile->writeModlistNow(); bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -1270,7 +1272,7 @@ bool OrganizerCore::previewFileWithAlternatives( else { // crude: we search for the next slash after the base mod directory to skip // everything up to the data-relative directory - int offset = settings().getModDirectory().size() + 1; + int offset = settings().paths().mods().size() + 1; offset = fileName.indexOf("/", offset); fileName = fileName.mid(offset + 1); } @@ -1412,7 +1414,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, LPDWORD exitCode) { HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); - if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { + if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; @@ -1461,7 +1463,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); } else { ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.getSteamAppID()).c_str()); + ToWString(m_Settings.steam().appID()).c_str()); } QWidget *window = qApp->activeWindow(); @@ -1477,7 +1479,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( "steam_api64.dll")) .exists()) - && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { bool steamFound = true; bool steamAccess = true; @@ -1592,7 +1594,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } } - QString modsPath = settings().getModDirectory(); + QString modsPath = settings().paths().mods(); // Check if this a request with either an executable or a working directory under our mods folder // then will start the process in a virtualized "environment" with the appropriate paths fixed: @@ -1749,7 +1751,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { - if (!Settings::instance().lockGUI()) + if (!Settings::instance().interface().lockGUI()) return true; ILockedWaitingForProcess* uilock = nullptr; @@ -1960,8 +1962,10 @@ void OrganizerCore::refreshModList(bool saveChanges) if (saveChanges) { m_CurrentProfile->writeModlistNow(true); } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); m_CurrentProfile->refreshModStatus(); @@ -2130,7 +2134,7 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); @@ -2156,7 +2160,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) f(); } else { QString apiKey; - if (settings().getNexusApiKey(apiKey)) { + if (settings().nexus().apiKey(apiKey)) { doAfterLogin([f]{ f(); }); NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -2295,8 +2299,10 @@ void OrganizerCore::profileRefresh() { // have to refresh mods twice (again in refreshModList), otherwise the refresh // isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); + m_CurrentProfile->refreshModStatus(); refreshModList(); @@ -2463,7 +2469,7 @@ void OrganizerCore::syncOverwrite() SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow()); if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + syncDialog.apply(QDir::fromNativeSeparators(m_Settings.paths().mods())); modInfo->testValid(); refreshDirectoryStructure(); } @@ -2486,7 +2492,7 @@ std::vector OrganizerCore::activeProblems() const const auto& hookdll = oldMO1HookDll(); if (!hookdll.isEmpty()) { // This warning will now be shown every time the problems are checked, which is a bit - // of a "log spam". But since this is a sevre error which will most likely make the + // of a "log spam". But since this is a sever error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. log::warn("hook.dll found in game folder: {}", hookdll); @@ -2562,7 +2568,7 @@ void OrganizerCore::savePluginList() } m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(), m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); + m_Settings.game().hideUncheckedPlugins()); m_PluginList.saveLoadOrder(*m_DirectoryStructure); } @@ -2574,7 +2580,7 @@ void OrganizerCore::prepareStart() m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); - m_Settings.setupLoadMechanism(); + m_Settings.game().setupLoadMechanism(); storeSettings(); } @@ -2588,7 +2594,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } IPluginGame *game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName), + Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game); MappingType result; @@ -2634,7 +2640,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } result.insert(result.end(), { - QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()), + QDir::toNativeSeparators(m_Settings.paths().overwrite()), dataPath, true, customOverwrite.isEmpty() diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ddfe492e..33423225 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -191,7 +191,7 @@ void PluginList::refresh(const QString &profileName continue; } - bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() && primaryPlugins.contains(filename, Qt::CaseInsensitive); //(std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end()); diff --git a/src/profile.cpp b/src/profile.cpp index 7f4ebcaa..e76060b9 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -73,7 +73,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(gamePlugin) { - QString profilesDir = Settings::instance().getProfileDirectory(); + QString profilesDir = Settings::instance().paths().profiles(); QDir profileBase(profilesDir); QString fixedName = name; if (!fixDirectoryName(fixedName)) { @@ -299,7 +299,7 @@ void Profile::createTweakedIniFile() // static void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) { - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); while (profileIter.hasNext()) { @@ -655,7 +655,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { - QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; + QString profileDirectory = Settings::instance().paths().profiles() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } @@ -906,7 +906,7 @@ QString Profile::savePath() const void Profile::rename(const QString &newName) { - QDir profileDir(Settings::instance().getProfileDirectory()); + QDir profileDir(Settings::instance().paths().profiles()); profileDir.rename(name(), newName); m_Directory.setPath(profileDir.absoluteFilePath(newName)); } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 2f1bd059..c91f48f4 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -57,7 +57,7 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c { ui->setupUi(this); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -203,7 +203,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() if (confirmBox.exec() == QMessageBox::Yes) { QString profilePath; if (profileToDelete.get() == nullptr) { - profilePath = Settings::instance().getProfileDirectory() + profilePath = Settings::instance().paths().profiles() + "/" + ui->profilesList->currentItem()->text(); if (QMessageBox::question(this, tr("Profile broken"), tr("This profile you're about to delete seems to be broken or the path is invalid. " diff --git a/src/settings.cpp b/src/settings.cpp index 71288950..8b063efb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -150,7 +150,7 @@ std::optional getOptional( template T get( const QSettings& settings, - const QString& section, const QString& key, T def={}) + const QString& section, const QString& key, T def) { if (auto v=getOptional(settings, section, key)) { return *v; @@ -453,22 +453,74 @@ void warnIfNotCheckable(const QAbstractButton* b) } +bool setWindowsCredential(const QString key, const QString data) +{ + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + bool result = false; + if (data.isEmpty()) { + result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); + if (!result) + if (GetLastError() == ERROR_NOT_FOUND) + result = true; + } else { + wchar_t* charData = new wchar_t[data.size()]; + data.toWCharArray(charData); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = keyData; + cred.CredentialBlob = (LPBYTE)charData; + cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + result = CredWriteW(&cred, 0); + delete[] charData; + } + delete[] keyData; + return result; +} + +QString getWindowsCredential(const QString key) +{ + QString result; + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + PCREDENTIALW creds; + if (CredReadW(keyData, 1, 0, &creds)) { + wchar_t *charData = (wchar_t *)creds->CredentialBlob; + result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); + CredFree(creds); + } else { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + } + } + delete[] keyData; + return result; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) : m_Settings(path, QSettings::IniFormat), - m_Geometry(m_Settings), m_Colors(m_Settings), m_Plugins(m_Settings) + m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings), + m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), + m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), + m_Interface(m_Settings), m_Diagnostics(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); } else { s_Instance = this; } - - MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return getQuestionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); } Settings::~Settings() @@ -488,7 +540,7 @@ Settings &Settings::instance() void Settings::processUpdates( const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) { - if (getFirstStart()) { + if (firstStart()) { return; } @@ -523,1569 +575,1670 @@ void Settings::processUpdates( set(m_Settings, "General", "version", currentVersion.toString()); } -QString Settings::getFilename() const +QString Settings::filename() const { return m_Settings.fileName(); } -void Settings::registerAsNXMHandler(bool force) +bool Settings::usePrereleases() const { - const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; - const auto executable = QCoreApplication::applicationFilePath(); - - QString mode = force ? "forcereg" : "reg"; - QString parameters = mode + " " + m_GamePlugin->gameShortName(); - for (const QString& altGame : m_GamePlugin->validShortNames()) { - parameters += "," + altGame; - } - parameters += " \"" + executable + "\""; - - if (!shell::Execute(nxmPath, parameters)) { - QMessageBox::critical( - nullptr, tr("Failed"), tr("Failed to start the helper application")); - } + return get(m_Settings, "Settings", "use_prereleases", false); } -bool Settings::colorSeparatorScrollbar() const +void Settings::setUsePrereleases(bool b) { - return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); + set(m_Settings, "Settings", "use_prereleases", b); } -void Settings::setColorSeparatorScrollbar(bool b) +std::optional Settings::version() const { - set(m_Settings, "Settings", "colorSeparatorScrollbars", b); + if (auto v=getOptional(m_Settings, "General", "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; } -void Settings::managedGameChanged(IPluginGame const *gamePlugin) +bool Settings::firstStart() const { - m_GamePlugin = gamePlugin; + return get(m_Settings, "General", "first_start", true); } -bool Settings::obfuscate(const QString key, const QString data) +void Settings::setFirstStart(bool b) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); + set(m_Settings, "General", "first_start", b); +} - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; +QString Settings::executablesBlacklist() const +{ + static const QString def = (QStringList() + << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + ).join(";"); - result = CredWriteW(&cred, 0); - delete[] charData; - } - delete[] keyData; - return result; + return get(m_Settings, "Settings", "executable_blacklist", def); } -QString Settings::deObfuscate(const QString key) +void Settings::setExecutablesBlacklist(const QString& s) { - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { - const auto e = GetLastError(); - if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); - } - } - delete[] keyData; - return result; + set(m_Settings, "Settings", "executable_blacklist", s); } -QColor Settings::getIdealTextColor(const QColor& rBackgroundColor) +void Settings::setMotdHash(uint hash) { - if (rBackgroundColor.alpha() == 0) - return QColor(Qt::black); - - const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); - int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); - return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); + set(m_Settings, "General", "motd_hash", hash); } - -bool Settings::hideUncheckedPlugins() const +unsigned int Settings::motdHash() const { - return get(m_Settings, "Settings", "hide_unchecked_plugins", false); + return get(m_Settings, "General", "motd_hash", 0); } -void Settings::setHideUncheckedPlugins(bool b) +bool Settings::archiveParsing() const { - set(m_Settings, "Settings", "hide_unchecked_plugins", b); + return get(m_Settings, "Settings", "archive_parsing_experimental", false); } -bool Settings::forceEnableCoreFiles() const +void Settings::setArchiveParsing(bool b) { - return get(m_Settings, "Settings", "force_enable_core_files", true); + set(m_Settings, "Settings", "archive_parsing_experimental", b); } -void Settings::setForceEnableCoreFiles(bool b) +std::vector> Settings::executables() const { - set(m_Settings, "Settings", "force_enable_core_files", b); + ScopedReadArray sra(m_Settings, "customExecutables"); + std::vector> v; + + sra.for_each([&]{ + std::map map; + + for (auto&& key : sra.keys()) { + map[key] = m_Settings.value(key); + } + + v.push_back(map); + }); + + return v; } -bool Settings::lockGUI() const +void Settings::setExecutables(const std::vector>& v) { - return get(m_Settings, "Settings", "lock_gui", true); + removeSection(m_Settings, "customExecutables"); + + ScopedWriteArray swa(m_Settings, "customExecutables"); + + for (const auto& map : v) { + swa.next(); + + for (auto&& p : map) { + swa.set(p.first, p.second); + } + } } -void Settings::setLockGUI(bool b) +bool Settings::keepBackupOnInstall() const { - set(m_Settings, "Settings", "lock_gui", b); + return get(m_Settings, "General", "backup_install", false); } -bool Settings::automaticLoginEnabled() const +void Settings::setKeepBackupOnInstall(bool b) { - return get(m_Settings, "Settings", "nexus_login", false); + set(m_Settings, "General", "backup_install", b); } -QString Settings::getSteamAppID() const +GameSettings& Settings::game() { - return get(m_Settings, "Settings", "app_id", m_GamePlugin->steamAPPId()); + return m_Game; } -void Settings::setSteamAppID(const QString& id) +const GameSettings& Settings::game() const { - if (id.isEmpty()) { - remove(m_Settings, "Settings", "app_id"); - } else { - set(m_Settings, "Settings", "app_id", id); - } + return m_Game; } -bool Settings::usePrereleases() const +GeometrySettings& Settings::geometry() { - return get(m_Settings, "Settings", "use_prereleases", false); + return m_Geometry; } -void Settings::setUsePrereleases(bool b) +const GeometrySettings& Settings::geometry() const { - set(m_Settings, "Settings", "use_prereleases", b); + return m_Geometry; } -QString Settings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const +WidgetSettings& Settings::widgets() { - QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - - if (resolve) { - result.replace("%BASE_DIR%", getBaseDirectory()); - } - - return result; + return m_Widgets; } -void Settings::setConfigurablePath(const QString &key, const QString& path) +const WidgetSettings& Settings::widgets() const { - if (path.isEmpty()) { - remove(m_Settings, "Settings", key); - } else { - set(m_Settings, "Settings", key, path); - } + return m_Widgets; } -QString Settings::getBaseDirectory() const +ColorSettings& Settings::colors() { - return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", qApp->property("dataPath").toString())); + return m_Colors; } -QString Settings::getDownloadDirectory(bool resolve) const +const ColorSettings& Settings::colors() const { - return getConfigurablePath( - "download_directory", - ToQString(AppConfig::downloadPath()), - resolve); + return m_Colors; } -QString Settings::getCacheDirectory(bool resolve) const +PluginSettings& Settings::plugins() { - return getConfigurablePath( - "cache_directory", - ToQString(AppConfig::cachePath()), - resolve); + return m_Plugins; } -QString Settings::getModDirectory(bool resolve) const +const PluginSettings& Settings::plugins() const { - return getConfigurablePath( - "mod_directory", - ToQString(AppConfig::modsPath()), - resolve); + return m_Plugins; } -QString Settings::getProfileDirectory(bool resolve) const +PathSettings& Settings::paths() { - return getConfigurablePath( - "profiles_directory", - ToQString(AppConfig::profilesPath()), - resolve); + return m_Paths; } -QString Settings::getOverwriteDirectory(bool resolve) const +const PathSettings& Settings::paths() const { - return getConfigurablePath( - "overwrite_directory", - ToQString(AppConfig::overwritePath()), - resolve); + return m_Paths; } -void Settings::setBaseDirectory(const QString& path) +NetworkSettings& Settings::network() { - if (path.isEmpty()) { - remove(m_Settings, "Settings", "base_directory"); - } else { - set(m_Settings, "Settings", "base_directory", path); - } + return m_Network; } -void Settings::setDownloadDirectory(const QString& path) +const NetworkSettings& Settings::network() const { - setConfigurablePath("download_directory", path); + return m_Network; } -void Settings::setModDirectory(const QString& path) +NexusSettings& Settings::nexus() { - setConfigurablePath("mod_directory", path); + return m_Nexus; } -void Settings::setCacheDirectory(const QString& path) +const NexusSettings& Settings::nexus() const { - setConfigurablePath("cache_directory", path); + return m_Nexus; } -void Settings::setProfileDirectory(const QString& path) +SteamSettings& Settings::steam() { - setConfigurablePath("profiles_directory", path); + return m_Steam; } -void Settings::setOverwriteDirectory(const QString& path) +const SteamSettings& Settings::steam() const { - setConfigurablePath("overwrite_directory", path); + return m_Steam; } -std::optional Settings::getManagedGameDirectory() const +InterfaceSettings& Settings::interface() { - if (auto v=getOptional(m_Settings, "General", "gamePath")) { - return QString::fromUtf8(*v); - } - - return {}; + return m_Interface; } -void Settings::setManagedGameDirectory(const QString& path) +const InterfaceSettings& Settings::interface() const { - set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); + return m_Interface; } -std::optional Settings::getManagedGameName() const +DiagnosticsSettings& Settings::diagnostics() { - return getOptional(m_Settings, "General", "gameName"); + return m_Diagnostics; } -void Settings::setManagedGameName(const QString& name) +const DiagnosticsSettings& Settings::diagnostics() const { - set(m_Settings, "General", "gameName", name); + return m_Diagnostics; } -std::optional Settings::getManagedGameEdition() const +QSettings::Status Settings::sync() const { - return getOptional(m_Settings, "General", "game_edition"); + m_Settings.sync(); + return m_Settings.status(); } -void Settings::setManagedGameEdition(const QString& name) +void Settings::dump() const { - set(m_Settings, "General", "game_edition", name); -} + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); -std::optional Settings::getSelectedProfileName() const -{ - if (auto v=getOptional(m_Settings, "General", "selected_profile")) { - return QString::fromUtf8(*v); + log::debug("settings:"); + + { + ScopedGroup sg(m_Settings, "Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } } - return {}; + m_Network.dump(); } -void Settings::setSelectedProfileName(const QString& name) +void Settings::managedGameChanged(IPluginGame const *gamePlugin) { - set(m_Settings, "General", "selected_profile", name.toUtf8()); + m_Game.setPlugin(gamePlugin); } -std::optional Settings::getStyleName() const + +GameSettings::GameSettings(QSettings& settings) + : m_Settings(settings), m_GamePlugin(nullptr) { - return getOptional(m_Settings, "Settings", "style"); } -void Settings::setStyleName(const QString& name) +const MOBase::IPluginGame* GameSettings::plugin() { - set(m_Settings, "Settings", "style", name); + return m_GamePlugin; } -bool Settings::getUseProxy() const +void GameSettings::setPlugin(const MOBase::IPluginGame* gamePlugin) { - return get(m_Settings, "Settings", "use_proxy", false); + m_GamePlugin = gamePlugin; } -void Settings::setUseProxy(bool b) +bool GameSettings::forceEnableCoreFiles() const { - set(m_Settings, "Settings", "use_proxy", b); + return get(m_Settings, "Settings", "force_enable_core_files", true); } -std::optional Settings::getVersion() const +void GameSettings::setForceEnableCoreFiles(bool b) { - if (auto v=getOptional(m_Settings, "General", "version")) { - return QVersionNumber::fromString(*v).normalized(); - } - - return {}; + set(m_Settings, "Settings", "force_enable_core_files", b); } -bool Settings::getFirstStart() const +std::optional GameSettings::directory() const { - return get(m_Settings, "General", "first_start", true); + if (auto v=getOptional(m_Settings, "General", "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } -void Settings::setFirstStart(bool b) +void GameSettings::setDirectory(const QString& path) { - set(m_Settings, "General", "first_start", b); + set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); } -std::optional Settings::getPreviousSeparatorColor() const +std::optional GameSettings::name() const { - const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); - if (c && c->isValid()) { - return c; - } - - return {}; + return getOptional(m_Settings, "General", "gameName"); } -void Settings::setPreviousSeparatorColor(const QColor& c) const +void GameSettings::setName(const QString& name) { - set(m_Settings, "General", "previousSeparatorColor", c); + set(m_Settings, "General", "gameName", name); } -void Settings::removePreviousSeparatorColor() +std::optional GameSettings::edition() const { - remove(m_Settings, "General", "previousSeparatorColor"); + return getOptional(m_Settings, "General", "game_edition"); } -bool Settings::getNexusApiKey(QString &apiKey) const +void GameSettings::setEdition(const QString& name) { - QString tempKey = deObfuscate("APIKEY"); - if (tempKey.isEmpty()) - return false; - - apiKey = tempKey; - return true; + set(m_Settings, "General", "game_edition", name); } -bool Settings::setNexusApiKey(const QString& apiKey) +std::optional GameSettings::selectedProfileName() const { - if (!obfuscate("APIKEY", apiKey)) { - const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessage(e)); - return false; + if (auto v=getOptional(m_Settings, "General", "selected_profile")) { + return QString::fromUtf8(*v); } - return true; + return {}; } -bool Settings::clearNexusApiKey() +void GameSettings::setSelectedProfileName(const QString& name) { - return setNexusApiKey(""); + set(m_Settings, "General", "selected_profile", name.toUtf8()); } -bool Settings::hasNexusApiKey() const +LoadMechanism::EMechanism GameSettings::loadMechanismType() const { - return !deObfuscate("APIKEY").isEmpty(); -} + const auto def = LoadMechanism::LOAD_MODORGANIZER; -bool Settings::getSteamLogin(QString &username, QString &password) const -{ - username = get(m_Settings, "Settings", "steam_username", ""); - password = deObfuscate("steam_password"); + const auto i = get(m_Settings, + "Settings", "load_mechanism", def); - return !username.isEmpty() && !password.isEmpty(); -} + switch (i) + { + // ok + case LoadMechanism::LOAD_MODORGANIZER: // fall-through + { + break; + } -bool Settings::compactDownloads() const -{ - return get(m_Settings, "Settings", "compact_downloads", false); -} + default: + { + log::error( + "invalid load mechanism {}, reverting to {}", + static_cast(i), toString(def)); -void Settings::setCompactDownloads(bool b) -{ - set(m_Settings, "Settings", "compact_downloads", b); -} + set(m_Settings, "Settings", "load_mechanism", def); -bool Settings::metaDownloads() const -{ - return get(m_Settings, "Settings", "meta_downloads", false); -} + return def; + } + } -void Settings::setMetaDownloads(bool b) -{ - set(m_Settings, "Settings", "meta_downloads", b); + return i; } -bool Settings::offlineMode() const +void GameSettings::setLoadMechanism(LoadMechanism::EMechanism m) { - return get(m_Settings, "Settings/offline_mode", false); + set(m_Settings, "Settings", "load_mechanism", m); } -void Settings::setOfflineMode(bool b) +const LoadMechanism& GameSettings::loadMechanism() const { - set(m_Settings, "Settings", "offline_mode", b); + return m_LoadMechanism; } -log::Levels Settings::logLevel() const +void GameSettings::setupLoadMechanism() { - return get(m_Settings, "Settings", "log_level", log::Levels::Info); + m_LoadMechanism.activate(loadMechanismType()); } -void Settings::setLogLevel(log::Levels level) +bool GameSettings::hideUncheckedPlugins() const { - set(m_Settings, "Settings", "log_level", level); + return get(m_Settings, "Settings", "hide_unchecked_plugins", false); } -CrashDumpsType Settings::crashDumpsType() const +void GameSettings::setHideUncheckedPlugins(bool b) { - return get(m_Settings, - "Settings", "crash_dumps_type", CrashDumpsType::Mini); + set(m_Settings, "Settings", "hide_unchecked_plugins", b); } -void Settings::setCrashDumpsType(CrashDumpsType type) -{ - set(m_Settings, "Settings", "crash_dumps_type", type); -} -int Settings::crashDumpsMax() const +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) { - return get(m_Settings, "Settings", "crash_dumps_max", 5); } -void Settings::setCrashDumpsMax(int n) +void GeometrySettings::requestReset() { - set(m_Settings, "Settings", "crash_dumps_max", n); + m_Reset = true; } -QString Settings::executablesBlacklist() const +void GeometrySettings::resetIfNeeded() { - static const QString def = (QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - ).join(";"); + if (!m_Reset) { + return; + } - return get(m_Settings, "Settings", "executable_blacklist", def); + removeSection(m_Settings, "Geometry"); } -void Settings::setExecutablesBlacklist(const QString& s) +void GeometrySettings::saveGeometry(const QWidget* w) { - set(m_Settings, "Settings", "executable_blacklist", s); + set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } -void Settings::setSteamLogin(QString username, QString password) +bool GeometrySettings::restoreGeometry(QWidget* w) const { - if (username == "") { - remove(m_Settings, "Settings", "steam_username"); - password = ""; - } else { - set(m_Settings, "Settings", "steam_username", username); + if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { + w->restoreGeometry(*v); + return true; } - if (!obfuscate("steam_password", password)) { - const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); - } + return false; } -LoadMechanism::EMechanism Settings::getLoadMechanism() const +void GeometrySettings::saveState(const QMainWindow* w) { - const auto def = LoadMechanism::LOAD_MODORGANIZER; - - const auto i = get(m_Settings, - "Settings", "load_mechanism", def); - - switch (i) - { - // ok - case LoadMechanism::LOAD_MODORGANIZER: // fall-through - { - break; - } - - default: - { - log::error( - "invalid load mechanism {}, reverting to {}", - static_cast(i), toString(def)); - - set(m_Settings, "Settings", "load_mechanism", def); - - return def; - } - } - - return i; + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setLoadMechanism(LoadMechanism::EMechanism m) +bool GeometrySettings::restoreState(QMainWindow* w) const { - set(m_Settings, "Settings", "load_mechanism", m); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); + return false; } -bool Settings::endorsementIntegration() const +void GeometrySettings::saveState(const QHeaderView* w) { - return get(m_Settings, "Settings", "endorsement_integration", true); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementIntegration(bool b) const +bool GeometrySettings::restoreState(QHeaderView* w) const { - set(m_Settings, "Settings", "endorsement_integration", b); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -EndorsementState Settings::endorsementState() const +void GeometrySettings::saveState(const QSplitter* w) { - return endorsementStateFromString( - get(m_Settings, "General", "endorse_state", "")); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementState(EndorsementState s) +bool GeometrySettings::restoreState(QSplitter* w) const { - const auto v = toString(s); - - if (v.isEmpty()) { - remove(m_Settings, "General", "endorse_state"); - } else { - set(m_Settings, "General", "endorse_state", v); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; } -} -bool Settings::hideAPICounter() const -{ - return get(m_Settings, "Settings", "hide_api_counter", false); + return false; } -void Settings::setHideAPICounter(bool b) +void GeometrySettings::saveState(const ExpanderWidget* expander) { - set(m_Settings, "Settings", "hide_api_counter", b); + set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); } -bool Settings::displayForeign() const +bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - return get(m_Settings, "Settings", "display_foreign", true); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { + expander->restoreState(*v); + return true; + } -void Settings::setDisplayForeign(bool b) -{ - set(m_Settings, "Settings", "display_foreign", b); + return false; } -void Settings::setMotDHash(uint hash) +void GeometrySettings::saveVisibility(const QWidget* w) { - set(m_Settings, "General", "motd_hash", hash); + set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); } -unsigned int Settings::getMotDHash() const +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - return get(m_Settings, "motd_hash", 0); -} + if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { + w->setVisible(*v); + return true; + } -bool Settings::archiveParsing() const -{ - return get(m_Settings, "Settings", "archive_parsing_experimental", false); + return false; } -void Settings::setArchiveParsing(bool b) +void GeometrySettings::restoreToolbars(QMainWindow* w) const { - set(m_Settings, "Settings", "archive_parsing_experimental", b); -} + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); -QString Settings::language() + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + + restoreVisibility(tb); + } +} + +void GeometrySettings::saveToolbars(const QMainWindow* w) { - QString result = get(m_Settings, "Settings", "language", ""); + const auto tbs = w->findChildren(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; + + set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); + set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); + } +} + +QStringList GeometrySettings::modInfoTabOrder() const +{ + QStringList v; + + if (m_Settings.contains("mod_info_tabs")) { + // old byte array from 2.2.0 + QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.push_back(s); + } + } else { + // string list since 2.2.1 + QString string = m_Settings.value("mod_info_tab_order").toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.push_back(s); } } - return result; + return v; } -void Settings::setLanguage(const QString& name) +void GeometrySettings::setModInfoTabOrder(const QString& names) { - set(m_Settings, "Settings", "language", name); + set(m_Settings, "Geometry", "mod_info_tab_order", names); } -void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - auto servers = getServers(); + const auto monitor = getOptional( + m_Settings, "Geometry", "MainWindow_monitor"); - for (auto& server : servers) { - if (server.name() == name) { - server.addDownload(bytesPerSecond); - updateServers(servers); - return; + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + set(m_Settings, "Geometry", "MainWindow_monitor", screenId); } } +} - log::error( - "server '{}' not found while trying to add a download with bps {}", - name, bytesPerSecond); +Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +{ + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } } -ServerList Settings::getServers() const +void GeometrySettings::saveDocks(const QMainWindow* mw) { - // servers used to be a map of byte arrays until 2.2.1, it's now an array of - // individual values instead + // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock + // sizes are not restored when the main window is maximized; it is used in + // MainWindow::readSettings() and MainWindow::storeSettings() + // + // there's also https://stackoverflow.com/questions/44005852, which has what + // seems to be a popular fix, but it breaks the restored size of the window + // by setting it to the desktop's resolution, so that doesn't work + // + // the only fix I could find is to remember the sizes of the docks and manually + // setting them back; saving is straightforward, but restoring is messy + // + // this also depends on the window being visible before the timer in restore() + // is fired and the timer must be processed by application.exec(); therefore, + // the splash screen _must_ be closed before readSettings() is called, because + // it has its own event loop, which seems to interfere with this + // + // all of this should become unnecessary when QTBUG-46620 is fixed // - // so post 2.2.1, only one key is returned: "size", the size of the arrays; - // in 2.2.1, one key per server is returned - { - const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - if (!keys.empty() && keys[0] != "size") { - // old format - return getServersFromOldMap(); + // saves the size of each dock + for (const auto* dock : mw->findChildren()) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (dockOrientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); + } else { + size = dock->size().height(); } + + set(m_Settings, "Geometry", dockSettingName(dock), size); } +} +void GeometrySettings::restoreDocks(QMainWindow* mw) const +{ + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; - // post 2.2.1 format, array of values + std::vector dockInfos; - ServerList list; + // for each dock + for (auto* dock : mw->findChildren()) { + if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { + // remember this dock, its size and orientation + dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + } + } - { - ScopedReadArray sra(m_Settings, "Servers"); + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(5, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); +} - sra.for_each([&] { - ServerInfo::SpeedList lastDownloads; - const auto lastDownloadsString = sra.get("lastDownloads", ""); +WidgetSettings::WidgetSettings(QSettings& s) + : m_Settings(s) +{ + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return questionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); +} - for (const auto& s : lastDownloadsString.split(" ")) { - const auto bytesPerSecond = s.toInt(); - if (bytesPerSecond > 0) { - lastDownloads.push_back(bytesPerSecond); - } - } +std::optional WidgetSettings::index(const QComboBox* cb) const +{ + return getOptional(m_Settings, "Widgets", indexSettingName(cb)); +} - ServerInfo server( - sra.get("name", ""), - sra.get("premium", false), - QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), - sra.get("preferred", 0), - lastDownloads); +void WidgetSettings::saveIndex(const QComboBox* cb) +{ + set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreIndex(QComboBox* cb, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { + cb->setCurrentIndex(*v); } - - return list; } -ServerList Settings::getServersFromOldMap() const +std::optional WidgetSettings::index(const QTabWidget* w) const { - // for 2.2.1 and before + return getOptional(m_Settings, "Widgets", indexSettingName(w)); +} - ServerList list; - const ScopedGroup sg(m_Settings, "Servers"); +void WidgetSettings::saveIndex(const QTabWidget* w) +{ + set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); +} - sg.for_each([&](auto&& serverKey) { - QVariantMap data = sg.get(serverKey); +void WidgetSettings::restoreIndex(QTabWidget* w, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { + w->setCurrentIndex(*v); + } +} - ServerInfo server( - serverKey, - data["premium"].toBool(), - data["lastSeen"].toDate(), - data["preferred"].toInt(), - {}); +std::optional WidgetSettings::checked(const QAbstractButton* w) const +{ + warnIfNotCheckable(w); + return getOptional(m_Settings, "Widgets", checkedSettingName(w)); +} - // ignoring download count and speed, it's now a list of values instead of - // a total +void WidgetSettings::saveChecked(const QAbstractButton* w) +{ + warnIfNotCheckable(w); + set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreChecked(QAbstractButton* w, std::optional def) const +{ + warnIfNotCheckable(w); - return list; + if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { + w->setChecked(*v); + } } -void Settings::updateServers(ServerList servers) +QuestionBoxMemory::Button WidgetSettings::questionButton( + const QString& windowName, const QString& filename) const { - // clean up unavailable servers - servers.cleanup(); - - removeSection(m_Settings, "Servers"); + const QString sectionName("DialogChoices"); - { - ScopedWriteArray swa(m_Settings, "Servers"); + if (!filename.isEmpty()) { + const auto fileSetting = windowName + "/" + filename; + if (auto v=getOptional(m_Settings, sectionName, filename)) { + return static_cast(*v); + } + } - for (const auto& server : servers) { - swa.next(); + if (auto v=getOptional(m_Settings, sectionName, windowName)) { + return static_cast(*v); + } - swa.set("name", server.name()); - swa.set("premium", server.isPremium()); - swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); - swa.set("preferred", server.preferred()); + return QuestionBoxMemory::NoButton; +} - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); - } - } +void WidgetSettings::setQuestionWindowButton( + const QString& windowName, QuestionBoxMemory::Button button) +{ + const QString sectionName("DialogChoices"); - swa.set("lastDownloads", lastDownloads.trimmed()); - } + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, windowName); + } else { + set(m_Settings, sectionName, windowName, button); } } -std::map Settings::getRecentDirectories() const +void WidgetSettings::setQuestionFileButton( + const QString& windowName, const QString& filename, + QuestionBoxMemory::Button button) { - std::map map; + const QString sectionName("DialogChoices"); + const QString settingName(windowName + "/" + filename); - ScopedReadArray sra(m_Settings, "RecentDirectories"); + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, settingName); + } else { + set(m_Settings, sectionName, settingName, button); + } +} - sra.for_each([&] { - const QVariant name = sra.get("name"); - const QVariant dir = sra.get("directory"); +void WidgetSettings::resetQuestionButtons() +{ + removeSection(m_Settings, "DialogChoices"); +} - if (name.isValid() && dir.isValid()) { - map.emplace(name.toString(), dir.toString()); - } - }); - return map; +ColorSettings::ColorSettings(QSettings& s) + : m_Settings(s) +{ } -void Settings::setRecentDirectories(const std::map& map) +QColor ColorSettings::modlistOverwrittenLoose() const { - removeSection(m_Settings, "RecentDirectories"); + return get( + m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); +} - ScopedWriteArray swa(m_Settings, "recentDirectories"); +void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); +} - for (auto&& p : map) { - swa.next(); +QColor ColorSettings::modlistOverwritingLoose() const +{ + return get( + m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); +} - swa.set("name", p.first); - swa.set("directory", p.second); - } +void ColorSettings::setModlistOverwritingLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingLooseFilesColor", c); } -std::vector> Settings::getExecutables() const +QColor ColorSettings::modlistOverwrittenArchive() const { - ScopedReadArray sra(m_Settings, "customExecutables"); - std::vector> v; + return get( + m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); +} - sra.for_each([&]{ - std::map map; +void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); +} - for (auto&& key : sra.keys()) { - map[key] = m_Settings.value(key); - } +QColor ColorSettings::modlistOverwritingArchive() const +{ + return get( + m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); +} - v.push_back(map); - }); +void ColorSettings::setModlistOverwritingArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); +} - return v; +QColor ColorSettings::modlistContainsPlugin() const +{ + return get( + m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } -void Settings::setExecutables(const std::vector>& v) +void ColorSettings::setModlistContainsPlugin(const QColor& c) { - removeSection(m_Settings, "customExecutables"); + set(m_Settings, "Settings", "containsPluginColor", c); +} - ScopedWriteArray swa(m_Settings, "customExecutables"); +QColor ColorSettings::pluginListContained() const +{ + return get( + m_Settings, "Settings", "containedColor", + QColor(0, 0, 255, 64)); +} - for (const auto& map : v) { - swa.next(); +void ColorSettings::setPluginListContained(const QColor& c) +{ + set(m_Settings, "Settings", "containedColor", c); +} - for (auto&& p : map) { - swa.set(p.first, p.second); - } +std::optional ColorSettings::previousSeparatorColor() const +{ + const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); + if (c && c->isValid()) { + return c; } + + return {}; } -bool Settings::isTutorialCompleted(const QString& windowName) const +void ColorSettings::setPreviousSeparatorColor(const QColor& c) const { - return get(m_Settings, "CompletedWindowTutorials", windowName, false); + set(m_Settings, "General", "previousSeparatorColor", c); } -void Settings::setTutorialCompleted(const QString& windowName, bool b) +void ColorSettings::removePreviousSeparatorColor() { - set(m_Settings, "CompletedWindowTutorials", windowName, b); + remove(m_Settings, "General", "previousSeparatorColor"); } -bool Settings::keepBackupOnInstall() const +bool ColorSettings::colorSeparatorScrollbar() const { - return get(m_Settings, "backup_install", false); + return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); } -void Settings::setKeepBackupOnInstall(bool b) +void ColorSettings::setColorSeparatorScrollbar(bool b) { - set(m_Settings, "General", "backup_install", b); + set(m_Settings, "Settings", "colorSeparatorScrollbars", b); } -QuestionBoxMemory::Button Settings::getQuestionButton( - const QString& windowName, const QString& filename) const +QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) { - const QString sectionName("DialogChoices"); + if (rBackgroundColor.alpha() == 0) + return QColor(Qt::black); - if (!filename.isEmpty()) { - const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional(m_Settings, sectionName, filename)) { - return static_cast(*v); + const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); + int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); + return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); +} + + + +PluginSettings::PluginSettings(QSettings& settings) + : m_Settings(settings) +{ +} + +void PluginSettings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + sra.for_each([&]{ + m_PluginBlacklist.insert(sra.get("name")); + }); +} + +void PluginSettings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QVariantMap()); + m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + + for (const PluginSetting &setting : plugin->settings()) { + const QString settingName = plugin->name() + "/" + setting.key; + + QVariant temp = get( + m_Settings, "Plugins", settingName, setting.defaultValue); + + if (!temp.convert(setting.defaultValue.type())) { + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); + + temp = setting.defaultValue; } - } - if (auto v=getOptional(m_Settings, sectionName, windowName)) { - return static_cast(*v); + m_PluginSettings[plugin->name()][setting.key] = temp; + + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } +} - return QuestionBoxMemory::NoButton; +bool PluginSettings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); } -void Settings::setQuestionWindowButton( - const QString& windowName, QuestionBoxMemory::Button button) +QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const { - const QString sectionName("DialogChoices/"); + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, windowName); - } else { - set(m_Settings, sectionName, windowName, button); + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); } + + return *iterSetting; } -void Settings::setQuestionFileButton( - const QString& windowName, const QString& filename, - QuestionBoxMemory::Button button) +void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - const QString sectionName("DialogChoices"); - const QString settingName(windowName + "/" + filename); + auto iterPlugin = m_PluginSettings.find(pluginName); - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, settingName); - } else { - set(m_Settings, sectionName, settingName, button); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + set(m_Settings, "Plugins", pluginName + "/" + key, value); } -void Settings::resetQuestionButtons() +QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const { - removeSection(m_Settings, "DialogChoices"); + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -std::optional Settings::getIndex(const QComboBox* cb) const +void PluginSettings::setPluginPersistent( + const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - return getOptional(m_Settings, "Widgets", indexSettingName(cb)); + if (!m_PluginSettings.contains(pluginName)) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); + } + + set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + + if (sync) { + m_Settings.sync(); + } } -void Settings::saveIndex(const QComboBox* cb) +void PluginSettings::addBlacklistPlugin(const QString &fileName) { - set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); } -void Settings::restoreIndex(QComboBox* cb, std::optional def) const +void PluginSettings::writePluginBlacklist() { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { - cb->setCurrentIndex(*v); + removeSection(m_Settings, "PluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "PluginBlacklist"); + + for (const QString &plugin : m_PluginBlacklist) { + swa.next(); + swa.set("name", plugin); } } -std::optional Settings::getIndex(const QTabWidget* w) const +QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const { - return getOptional(m_Settings, "Widgets", indexSettingName(w)); + return m_PluginSettings[pluginName]; } -void Settings::saveIndex(const QTabWidget* w) +void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) { - set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); + m_PluginSettings[pluginName] = map; } -void Settings::restoreIndex(QTabWidget* w, std::optional def) const +QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { - w->setCurrentIndex(*v); - } + return m_PluginDescriptions[pluginName]; } -std::optional Settings::getChecked(const QAbstractButton* w) const +void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) { - warnIfNotCheckable(w); - return getOptional(m_Settings, "Widgets", checkedSettingName(w)); + m_PluginDescriptions[pluginName] = map; } -void Settings::saveChecked(const QAbstractButton* w) +const QSet& PluginSettings::pluginBlacklist() const { - warnIfNotCheckable(w); - set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); + return m_PluginBlacklist; } -void Settings::restoreChecked(QAbstractButton* w, std::optional def) const +void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) { - warnIfNotCheckable(w); + m_PluginBlacklist.clear(); - if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { - w->setChecked(*v); + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); } } -GeometrySettings& Settings::geometry() +void PluginSettings::save() { - return m_Geometry; -} + for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); + } + } -const GeometrySettings& Settings::geometry() const -{ - return m_Geometry; + writePluginBlacklist(); } -ColorSettings& Settings::colors() -{ - return m_Colors; -} -const ColorSettings& Settings::colors() const +PathSettings::PathSettings(QSettings& settings) + : m_Settings(settings) { - return m_Colors; } -PluginSettings& Settings::plugins() +std::map PathSettings::recent() const { - return m_Plugins; -} + std::map map; -const PluginSettings& Settings::plugins() const -{ - return m_Plugins; -} + ScopedReadArray sra(m_Settings, "RecentDirectories"); -QSettings::Status Settings::sync() const -{ - m_Settings.sync(); - return m_Settings.status(); -} + sra.for_each([&] { + const QVariant name = sra.get("name"); + const QVariant dir = sra.get("directory"); -void Settings::dump() const -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" + if (name.isValid() && dir.isValid()) { + map.emplace(name.toString(), dir.toString()); + } }); - log::debug("settings:"); + return map; +} - { - ScopedGroup sg(m_Settings, "Settings"); +void PathSettings::setRecent(const std::map& map) +{ + removeSection(m_Settings, "RecentDirectories"); - for (auto k : m_Settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } + ScopedWriteArray swa(m_Settings, "recentDirectories"); - log::debug(" . {}={}", k, m_Settings.value(k).toString()); - } - } + for (auto&& p : map) { + swa.next(); - log::debug("servers:"); + swa.set("name", p.first); + swa.set("directory", p.second); + } +} - for (const auto& server : getServers()) { - QString lastDownloads; - for (auto speed : server.lastDownloads()) { - lastDownloads += QString("%1 ").arg(speed); - } +QString PathSettings::getConfigurablePath(const QString &key, + const QString &def, + bool resolve) const +{ + QString result = QDir::fromNativeSeparators( + get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - log::debug( - " . {} premium={} lastSeen={} preferred={} lastDownloads={}", - server.name(), - server.isPremium() ? "yes" : "no", - server.lastSeen().toString(Qt::ISODate), - server.preferred(), - lastDownloads.trimmed()); + if (resolve) { + result.replace("%BASE_DIR%", base()); } -} + return result; +} -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s), m_Reset(false) +void PathSettings::setConfigurablePath(const QString &key, const QString& path) { + if (path.isEmpty()) { + remove(m_Settings, "Settings", key); + } else { + set(m_Settings, "Settings", key, path); + } } -void GeometrySettings::requestReset() +QString PathSettings::base() const { - m_Reset = true; + return QDir::fromNativeSeparators(get(m_Settings, + "Settings", "base_directory", qApp->property("dataPath").toString())); } -void GeometrySettings::resetIfNeeded() +QString PathSettings::downloads(bool resolve) const { - if (!m_Reset) { - return; - } - - removeSection(m_Settings, "Geometry"); + return getConfigurablePath( + "download_directory", + ToQString(AppConfig::downloadPath()), + resolve); } -void GeometrySettings::saveGeometry(const QWidget* w) +QString PathSettings::cache(bool resolve) const { - set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); + return getConfigurablePath( + "cache_directory", + ToQString(AppConfig::cachePath()), + resolve); } -bool GeometrySettings::restoreGeometry(QWidget* w) const +QString PathSettings::mods(bool resolve) const { - if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { - w->restoreGeometry(*v); - return true; - } + return getConfigurablePath( + "mod_directory", + ToQString(AppConfig::modsPath()), + resolve); +} - return false; +QString PathSettings::profiles(bool resolve) const +{ + return getConfigurablePath( + "profiles_directory", + ToQString(AppConfig::profilesPath()), + resolve); } -void GeometrySettings::saveState(const QMainWindow* w) +QString PathSettings::overwrite(bool resolve) const { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + return getConfigurablePath( + "overwrite_directory", + ToQString(AppConfig::overwritePath()), + resolve); } -bool GeometrySettings::restoreState(QMainWindow* w) const +void PathSettings::setBase(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; + if (path.isEmpty()) { + remove(m_Settings, "Settings", "base_directory"); + } else { + set(m_Settings, "Settings", "base_directory", path); } - - return false; } -void GeometrySettings::saveState(const QHeaderView* w) +void PathSettings::setDownloads(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("download_directory", path); } -bool GeometrySettings::restoreState(QHeaderView* w) const +void PathSettings::setMods(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("mod_directory", path); } -void GeometrySettings::saveState(const QSplitter* w) +void PathSettings::setCache(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("cache_directory", path); } -bool GeometrySettings::restoreState(QSplitter* w) const +void PathSettings::setProfiles(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("profiles_directory", path); } -void GeometrySettings::saveState(const ExpanderWidget* expander) +void PathSettings::setOverwrite(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); + setConfigurablePath("overwrite_directory", path); } -bool GeometrySettings::restoreState(ExpanderWidget* expander) const -{ - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { - expander->restoreState(*v); - return true; - } - return false; +NetworkSettings::NetworkSettings(QSettings& settings) + : m_Settings(settings) +{ } -void GeometrySettings::saveVisibility(const QWidget* w) +bool NetworkSettings::offlineMode() const { - set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); + return get(m_Settings, "Settings", "offline_mode", false); } -bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +void NetworkSettings::setOfflineMode(bool b) { - if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { - w->setVisible(*v); - return true; - } + set(m_Settings, "Settings", "offline_mode", b); +} - return false; +bool NetworkSettings::useProxy() const +{ + return get(m_Settings, "Settings", "use_proxy", false); } -void GeometrySettings::restoreToolbars(QMainWindow* w) const +void NetworkSettings::setUseProxy(bool b) { - // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); - const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); + set(m_Settings, "Settings", "use_proxy", b); +} - for (auto* tb : w->findChildren()) { - if (size) { - tb->setIconSize(*size); - } +void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond) +{ + auto current = servers(); - if (style) { - tb->setToolButtonStyle(static_cast(*style)); + for (auto& server : current) { + if (server.name() == name) { + server.addDownload(bytesPerSecond); + updateServers(current); + return; } - - restoreVisibility(tb); } + + log::error( + "server '{}' not found while trying to add a download with bps {}", + name, bytesPerSecond); } -void GeometrySettings::saveToolbars(const QMainWindow* w) +ServerList NetworkSettings::servers() const { - const auto tbs = w->findChildren(); + // servers used to be a map of byte arrays until 2.2.1, it's now an array of + // individual values instead + // + // so post 2.2.1, only one key is returned: "size", the size of the arrays; + // in 2.2.1, one key per server is returned + { + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - // save visibility for all - for (auto* tb : tbs) { - saveVisibility(tb); + if (!keys.empty() && keys[0] != "size") { + // old format + return serversFromOldMap(); + } } - // all toolbars have the same size and button style settings, just save the - // first one - if (!tbs.isEmpty()) { - const auto* tb = tbs[0]; - set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); - set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); - } -} + // post 2.2.1 format, array of values -QStringList GeometrySettings::getModInfoTabOrder() const -{ - QStringList v; + ServerList list; - if (m_Settings.contains("mod_info_tabs")) { - // old byte array from 2.2.0 - QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + { + ScopedReadArray sra(m_Settings, "Servers"); - int count = 0; - stream >> count; + sra.for_each([&] { + ServerInfo::SpeedList lastDownloads; - for (int i=0; i> s; - v.push_back(s); - } - } else { - // string list since 2.2.1 - QString string = m_Settings.value("mod_info_tab_order").toString(); - QTextStream stream(&string); + const auto lastDownloadsString = sra.get("lastDownloads", ""); - while (!stream.atEnd()) { - QString s; - stream >> s; - v.push_back(s); - } + for (const auto& s : lastDownloadsString.split(" ")) { + const auto bytesPerSecond = s.toInt(); + if (bytesPerSecond > 0) { + lastDownloads.push_back(bytesPerSecond); + } + } + + ServerInfo server( + sra.get("name", ""), + sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), + lastDownloads); + + list.add(std::move(server)); + }); } - return v; + return list; } -void GeometrySettings::setModInfoTabOrder(const QString& names) +ServerList NetworkSettings::serversFromOldMap() const { - set(m_Settings, "Geometry", "mod_info_tab_order", names); -} + // for 2.2.1 and before -void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) -{ - const auto monitor = getOptional( - m_Settings, "Geometry", "MainWindow_monitor"); + ServerList list; + const ScopedGroup sg(m_Settings, "Servers"); - QPoint center; + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); - if (monitor && QGuiApplication::screens().size() > *monitor) { - center = QGuiApplication::screens().at(*monitor)->geometry().center(); - } else { - center = QGuiApplication::primaryScreen()->geometry().center(); - } + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + {}); - w->move(center - w->rect().center()); -} + // ignoring download count and speed, it's now a list of values instead of + // a total -void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) -{ - if (auto* handle=w->windowHandle()) { - if (auto* screen = handle->screen()) { - const int screenId = QGuiApplication::screens().indexOf(screen); - set(m_Settings, "Geometry", "MainWindow_monitor", screenId); - } - } + list.add(std::move(server)); + }); + + return list; } -Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +void NetworkSettings::updateServers(ServerList servers) { - // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + // clean up unavailable servers + servers.cleanup(); - if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { - return Qt::Horizontal; - } else { - return Qt::Vertical; - } -} + removeSection(m_Settings, "Servers"); -void GeometrySettings::saveDocks(const QMainWindow* mw) -{ - // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock - // sizes are not restored when the main window is maximized; it is used in - // MainWindow::readSettings() and MainWindow::storeSettings() - // - // there's also https://stackoverflow.com/questions/44005852, which has what - // seems to be a popular fix, but it breaks the restored size of the window - // by setting it to the desktop's resolution, so that doesn't work - // - // the only fix I could find is to remember the sizes of the docks and manually - // setting them back; saving is straightforward, but restoring is messy - // - // this also depends on the window being visible before the timer in restore() - // is fired and the timer must be processed by application.exec(); therefore, - // the splash screen _must_ be closed before readSettings() is called, because - // it has its own event loop, which seems to interfere with this - // - // all of this should become unnecessary when QTBUG-46620 is fixed - // + { + ScopedWriteArray swa(m_Settings, "Servers"); - // saves the size of each dock - for (const auto* dock : mw->findChildren()) { - int size = 0; + for (const auto& server : servers) { + swa.next(); - // save the width for horizontal docks, or the height for vertical - if (dockOrientation(mw, dock) == Qt::Horizontal) { - size = dock->size().width(); - } else { - size = dock->size().height(); - } + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); - set(m_Settings, "Geometry", dockSettingName(dock), size); + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } + } + + swa.set("lastDownloads", lastDownloads.trimmed()); + } } } -void GeometrySettings::restoreDocks(QMainWindow* mw) const +void NetworkSettings::dump() const { - struct DockInfo - { - QDockWidget* d; - int size = 0; - Qt::Orientation ori; - }; - - std::vector dockInfos; + log::debug("servers:"); - // for each dock - for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { - // remember this dock, its size and orientation - dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + for (const auto& server : servers()) { + QString lastDownloads; + for (auto speed : server.lastDownloads()) { + lastDownloads += QString("%1 ").arg(speed); } - } - // the main window must have had time to process the settings from - // readSettings() or it seems to override whatever is set here - // - // some people said a single processEvents() call is enough, but it doesn't - // look like it - QTimer::singleShot(5, [=] { - for (const auto& info : dockInfos) { - mw->resizeDocks({info.d}, {info.size}, info.ori); - } - }); + log::debug( + " . {} premium={} lastSeen={} preferred={} lastDownloads={}", + server.name(), + server.isPremium() ? "yes" : "no", + server.lastSeen().toString(Qt::ISODate), + server.preferred(), + lastDownloads.trimmed()); + } } -ColorSettings::ColorSettings(QSettings& s) - : m_Settings(s) +NexusSettings::NexusSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { } -QColor ColorSettings::modlistOverwrittenLoose() const +bool NexusSettings::automaticLoginEnabled() const { - return get( - m_Settings, "Settings", "overwrittenLooseFilesColor", - QColor(0, 255, 0, 64)); + return get(m_Settings, "Settings", "nexus_login", false); } -void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +bool NexusSettings::apiKey(QString &apiKey) const { - set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); + QString tempKey = getWindowsCredential("APIKEY"); + if (tempKey.isEmpty()) + return false; + + apiKey = tempKey; + return true; } -QColor ColorSettings::modlistOverwritingLoose() const +bool NexusSettings::setApiKey(const QString& apiKey) { - return get( - m_Settings, "Settings", "overwritingLooseFilesColor", - QColor(255, 0, 0, 64)); + if (!setWindowsCredential("APIKEY", apiKey)) { + const auto e = GetLastError(); + log::error("Storing API key failed: {}", formatSystemMessage(e)); + return false; + } + + return true; } -void ColorSettings::setModlistOverwritingLoose(const QColor& c) +bool NexusSettings::clearApiKey() { - set(m_Settings, "Settings", "overwritingLooseFilesColor", c); + return setApiKey(""); } -QColor ColorSettings::modlistOverwrittenArchive() const +bool NexusSettings::hasApiKey() const { - return get( - m_Settings, "Settings", "overwrittenArchiveFilesColor", - QColor(0, 255, 255, 64)); + return !getWindowsCredential("APIKEY").isEmpty(); } -void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +bool NexusSettings::endorsementIntegration() const { - set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); + return get(m_Settings, "Settings", "endorsement_integration", true); } -QColor ColorSettings::modlistOverwritingArchive() const +void NexusSettings::setEndorsementIntegration(bool b) const { - return get( - m_Settings, "Settings", "overwritingArchiveFilesColor", - QColor(255, 0, 255, 64)); + set(m_Settings, "Settings", "endorsement_integration", b); } -void ColorSettings::setModlistOverwritingArchive(const QColor& c) +EndorsementState NexusSettings::endorsementState() const { - set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); + return endorsementStateFromString( + get(m_Settings, "General", "endorse_state", "")); } -QColor ColorSettings::modlistContainsPlugin() const +void NexusSettings::setEndorsementState(EndorsementState s) { - return get( - m_Settings, "Settings", "containsPluginColor", - QColor(0, 0, 255, 64)); + const auto v = toString(s); + + if (v.isEmpty()) { + remove(m_Settings, "General", "endorse_state"); + } else { + set(m_Settings, "General", "endorse_state", v); + } } -void ColorSettings::setModlistContainsPlugin(const QColor& c) +void NexusSettings::registerAsNXMHandler(bool force) { - set(m_Settings, "Settings", "containsPluginColor", c); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; + const auto executable = QCoreApplication::applicationFilePath(); + + QString mode = force ? "forcereg" : "reg"; + QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName(); + for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) { + parameters += "," + altGame; + } + parameters += " \"" + executable + "\""; + + if (!shell::Execute(nxmPath, parameters)) { + QMessageBox::critical( + nullptr, QObject::tr("Failed"), + QObject::tr("Failed to start the helper application")); + } } -QColor ColorSettings::pluginListContained() const + +SteamSettings::SteamSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { - return get( - m_Settings, "Settings", "containedColor", - QColor(0, 0, 255, 64)); } -void ColorSettings::setPluginListContained(const QColor& c) +QString SteamSettings::appID() const { - set(m_Settings, "Settings", "containedColor", c); + return get( + m_Settings, "Settings", "app_id", m_Parent.game().plugin()->steamAPPId()); } - -PluginSettings::PluginSettings(QSettings& settings) - : m_Settings(settings) +void SteamSettings::setAppID(const QString& id) { + if (id.isEmpty()) { + remove(m_Settings, "Settings", "app_id"); + } else { + set(m_Settings, "Settings", "app_id", id); + } } -void PluginSettings::clearPlugins() +bool SteamSettings::login(QString &username, QString &password) const { - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); + username = get(m_Settings, "Settings", "steam_username", ""); + password = getWindowsCredential("steam_password"); - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&]{ - m_PluginBlacklist.insert(sra.get("name")); - }); + return !username.isEmpty() && !password.isEmpty(); } -void PluginSettings::registerPlugin(IPlugin *plugin) +void SteamSettings::setLogin(QString username, QString password) { - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QVariantMap()); - m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + if (username == "") { + remove(m_Settings, "Settings", "steam_username"); + password = ""; + } else { + set(m_Settings, "Settings", "steam_username", username); + } - for (const PluginSetting &setting : plugin->settings()) { - const QString settingName = plugin->name() + "/" + setting.key; + if (!setWindowsCredential("steam_password", password)) { + const auto e = GetLastError(); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); + } +} - QVariant temp = get( - m_Settings, "Plugins", settingName, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - log::warn( - "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", - temp.toString(), setting.key, plugin->name()); +InterfaceSettings::InterfaceSettings(QSettings& settings) + : m_Settings(settings) +{ +} - temp = setting.defaultValue; - } +bool InterfaceSettings::lockGUI() const +{ + return get(m_Settings, "Settings", "lock_gui", true); +} - m_PluginSettings[plugin->name()][setting.key] = temp; +void InterfaceSettings::setLockGUI(bool b) +{ + set(m_Settings, "Settings", "lock_gui", b); +} - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") - .arg(setting.description) - .arg(setting.defaultValue.toString()); - } +std::optional InterfaceSettings::styleName() const +{ + return getOptional(m_Settings, "Settings", "style"); } -bool PluginSettings::pluginBlacklisted(const QString &fileName) const +void InterfaceSettings::setStyleName(const QString& name) { - return m_PluginBlacklist.contains(fileName); + set(m_Settings, "Settings", "style", name); } -QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const +bool InterfaceSettings::compactDownloads() const { - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } + return get(m_Settings, "Settings", "compact_downloads", false); +} - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } +void InterfaceSettings::setCompactDownloads(bool b) +{ + set(m_Settings, "Settings", "compact_downloads", b); +} - return *iterSetting; +bool InterfaceSettings::metaDownloads() const +{ + return get(m_Settings, "Settings", "meta_downloads", false); } -void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +void InterfaceSettings::setMetaDownloads(bool b) { - auto iterPlugin = m_PluginSettings.find(pluginName); + set(m_Settings, "Settings", "meta_downloads", b); +} - if (iterPlugin == m_PluginSettings.end()) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } +bool InterfaceSettings::hideAPICounter() const +{ + return get(m_Settings, "Settings", "hide_api_counter", false); +} - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - set(m_Settings, "Plugins", pluginName + "/" + key, value); +void InterfaceSettings::setHideAPICounter(bool b) +{ + set(m_Settings, "Settings", "hide_api_counter", b); } -QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +bool InterfaceSettings::displayForeign() const { - if (!m_PluginSettings.contains(pluginName)) { - return def; - } + return get(m_Settings, "Settings", "display_foreign", true); +} - return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); +void InterfaceSettings::setDisplayForeign(bool b) +{ + set(m_Settings, "Settings", "display_foreign", b); } -void PluginSettings::setPluginPersistent( - const QString &pluginName, const QString &key, const QVariant &value, bool sync) +QString InterfaceSettings::language() { - if (!m_PluginSettings.contains(pluginName)) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } + QString result = get(m_Settings, "Settings", "language", ""); - set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); - if (sync) { - m_Settings.sync(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); + } else { + // fallback system locale + result = QLocale::system().name(); + } } + + return result; } -void PluginSettings::addBlacklistPlugin(const QString &fileName) +void InterfaceSettings::setLanguage(const QString& name) { - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); + set(m_Settings, "Settings", "language", name); } -void PluginSettings::writePluginBlacklist() +bool InterfaceSettings::isTutorialCompleted(const QString& windowName) const { - removeSection(m_Settings, "PluginBlacklist"); - - ScopedWriteArray swa(m_Settings, "PluginBlacklist"); - - for (const QString &plugin : m_PluginBlacklist) { - swa.next(); - swa.set("name", plugin); - } + return get(m_Settings, "CompletedWindowTutorials", windowName, false); } -QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const +void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b) { - return m_PluginSettings[pluginName]; + set(m_Settings, "CompletedWindowTutorials", windowName, b); } -void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) + +DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) + : m_Settings(settings) { - m_PluginSettings[pluginName] = map; } -QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const +log::Levels DiagnosticsSettings::logLevel() const { - return m_PluginDescriptions[pluginName]; + return get(m_Settings, "Settings", "log_level", log::Levels::Info); } -void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) +void DiagnosticsSettings::setLogLevel(log::Levels level) { - m_PluginDescriptions[pluginName] = map; + set(m_Settings, "Settings", "log_level", level); } -const QSet& PluginSettings::pluginBlacklist() const +CrashDumpsType DiagnosticsSettings::crashDumpsType() const { - return m_PluginBlacklist; + return get(m_Settings, + "Settings", "crash_dumps_type", CrashDumpsType::Mini); } -void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) +void DiagnosticsSettings::setCrashDumpsType(CrashDumpsType type) { - m_PluginBlacklist.clear(); - - for (const auto& name : pluginNames) { - m_PluginBlacklist.insert(name); - } + set(m_Settings, "Settings", "crash_dumps_type", type); } -void PluginSettings::save() +int DiagnosticsSettings::crashDumpsMax() const { - for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = iterPlugins.key() + "/" + iterSettings.key(); - set(m_Settings, "Plugins", key, iterSettings.value()); - } - } + return get(m_Settings, "Settings", "crash_dumps_max", 5); +} - writePluginBlacklist(); +void DiagnosticsSettings::setCrashDumpsMax(int n) +{ + set(m_Settings, "Settings", "crash_dumps_max", n); } diff --git a/src/settings.h b/src/settings.h index 403c2d71..5c0a2542 100644 --- a/src/settings.h +++ b/src/settings.h @@ -25,6 +25,10 @@ along with Mod Organizer. If not, see . #include #include +#ifdef interface + #undef interface +#endif + namespace MOBase { class IPlugin; class IPluginGame; @@ -50,6 +54,58 @@ private: }; +class GameSettings +{ +public: + GameSettings(QSettings& setting); + + const MOBase::IPluginGame* plugin(); + void setPlugin(const MOBase::IPluginGame* gamePlugin); + + /** + * whether files of the core game are forced-enabled so the user can't + * accidentally disable them + */ + bool forceEnableCoreFiles() const; + void setForceEnableCoreFiles(bool b); + + /** + * the directory where the managed game is stored (with native separators) + **/ + std::optional directory() const; + void setDirectory(const QString& path); + + std::optional name() const; + void setName(const QString& name); + + std::optional edition() const; + void setEdition(const QString& name); + + std::optional selectedProfileName() const; + void setSelectedProfileName(const QString& name); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism loadMechanismType() const; + void setLoadMechanism(LoadMechanism::EMechanism m); + const LoadMechanism& loadMechanism() const; + void setupLoadMechanism(); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual data directory + **/ + bool hideUncheckedPlugins() const; + void setHideUncheckedPlugins(bool b); + +private: + QSettings& m_Settings; + const MOBase::IPluginGame* m_GamePlugin; + LoadMechanism m_LoadMechanism; +}; + + class GeometrySettings { public: @@ -83,7 +139,7 @@ public: void saveDocks(const QMainWindow* w); void restoreDocks(QMainWindow* w) const; - QStringList getModInfoTabOrder() const; + QStringList modInfoTabOrder() const; void setModInfoTabOrder(const QString& names); void centerOnMainWindowMonitor(QWidget* w); @@ -95,6 +151,40 @@ private: }; +class WidgetSettings +{ +public: + WidgetSettings(QSettings& s); + + std::optional index(const QComboBox* cb) const; + void saveIndex(const QComboBox* cb); + void restoreIndex(QComboBox* cb, std::optional def={}) const; + + std::optional index(const QTabWidget* w) const; + void saveIndex(const QTabWidget* w); + void restoreIndex(QTabWidget* w, std::optional def={}) const; + + std::optional checked(const QAbstractButton* w) const; + void saveChecked(const QAbstractButton* w); + void restoreChecked(QAbstractButton* w, std::optional def={}) const; + + MOBase::QuestionBoxMemory::Button questionButton( + const QString& windowName, const QString& filename) const; + + void setQuestionWindowButton( + const QString& windowName, MOBase::QuestionBoxMemory::Button button); + + void setQuestionFileButton( + const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); + + void resetQuestionButtons(); + +private: + QSettings& m_Settings; +}; + + class ColorSettings { public: @@ -120,6 +210,19 @@ public: QColor pluginListContained() const; void setPluginListContained(const QColor& c) ; + std::optional previousSeparatorColor() const; + void setPreviousSeparatorColor(const QColor& c) const; + void removePreviousSeparatorColor(); + + /** + * @brief color the scrollbar of the mod list for custom separator colors? + * @return the state of the setting + */ + bool colorSeparatorScrollbar() const; + void setColorSeparatorScrollbar(bool b); + + static QColor idealTextColor(const QColor& rBackgroundColor); + private: QSettings& m_Settings; }; @@ -165,210 +268,229 @@ private: }; -enum class EndorsementState -{ - Accepted = 1, - Refused, - NoDecision -}; - -EndorsementState endorsementStateFromString(const QString& s); -QString toString(EndorsementState s); - - -/** - * manages the settings for Mod Organizer. The settings are not cached - * inside the class but read/written directly from/to disc - **/ -class Settings : public QObject +class PathSettings { - Q_OBJECT; - public: - Settings(const QString& path); - ~Settings(); + PathSettings(QSettings& settings); - static Settings &instance(); + QString base() const; + QString downloads(bool resolve = true) const; + QString mods(bool resolve = true) const; + QString cache(bool resolve = true) const; + QString profiles(bool resolve = true) const; + QString overwrite(bool resolve = true) const; - void processUpdates( - const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + void setBase(const QString& path); + void setDownloads(const QString& path); + void setMods(const QString& path); + void setCache(const QString& path); + void setProfiles(const QString& path); + void setOverwrite(const QString& path); - QString getFilename() const; + std::map recent() const; + void setRecent(const std::map& map); - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - void setHideUncheckedPlugins(bool b); +private: + QSettings& m_Settings; - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - void setForceEnableCoreFiles(bool b); + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; + void setConfigurablePath(const QString &key, const QString& path); +}; - /** - * @return true if the GUI should be locked when running executables - */ - bool lockGUI() const; - void setLockGUI(bool b); - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - void setSteamAppID(const QString& id); - - QString getBaseDirectory() const; - QString getDownloadDirectory(bool resolve = true) const; - QString getModDirectory(bool resolve = true) const; - QString getCacheDirectory(bool resolve = true) const; - QString getProfileDirectory(bool resolve = true) const; - QString getOverwriteDirectory(bool resolve = true) const; - - void setBaseDirectory(const QString& path); - void setDownloadDirectory(const QString& path); - void setModDirectory(const QString& path); - void setCacheDirectory(const QString& path); - void setProfileDirectory(const QString& path); - void setOverwriteDirectory(const QString& path); +class NetworkSettings +{ +public: + NetworkSettings(QSettings& settings); /** - * retrieve the directory where the managed game is stored (with native separators) - **/ - std::optional getManagedGameDirectory() const; - void setManagedGameDirectory(const QString& path); - - std::optional getManagedGameName() const; - void setManagedGameName(const QString& name); - - std::optional getManagedGameEdition() const; - void setManagedGameEdition(const QString& name); + * @return true if the user disabled internet features + */ + bool offlineMode() const; + void setOfflineMode(bool b); - std::optional getSelectedProfileName() const; - void setSelectedProfileName(const QString& name); + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy() const; + void setUseProxy(bool b); - std::optional getStyleName() const; - void setStyleName(const QString& name); + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + ServerList servers() const; + void updateServers(ServerList servers); - std::optional getVersion() const; + void dump() const; - bool getFirstStart() const; - void setFirstStart(bool b); +private: + QSettings& m_Settings; - std::optional getPreviousSeparatorColor() const; - void setPreviousSeparatorColor(const QColor& c) const; - void removePreviousSeparatorColor(); + ServerList serversFromOldMap() const; +}; - std::map getRecentDirectories() const; - void setRecentDirectories(const std::map& map); - std::vector> getExecutables() const; - void setExecutables(const std::vector>& v); +enum class EndorsementState +{ + Accepted = 1, + Refused, + NoDecision +}; - bool isTutorialCompleted(const QString& windowName) const; - void setTutorialCompleted(const QString& windowName, bool b=true); +EndorsementState endorsementStateFromString(const QString& s); +QString toString(EndorsementState s); - bool keepBackupOnInstall() const; - void setKeepBackupOnInstall(bool b); +class NexusSettings +{ +public: + NexusSettings(Settings& parent, QSettings& settings); - MOBase::QuestionBoxMemory::Button getQuestionButton( - const QString& windowName, const QString& filename) const; + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; - void setQuestionWindowButton( - const QString& windowName, MOBase::QuestionBoxMemory::Button button); + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool apiKey(QString &apiKey) const; - void setQuestionFileButton( - const QString& windowName, const QString& filename, - MOBase::QuestionBoxMemory::Button choice); + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + bool setApiKey(const QString& apiKey); - void resetQuestionButtons(); + /** + * @brief clears the nexus login information + */ + bool clearApiKey(); - std::optional getIndex(const QComboBox* cb) const; - void saveIndex(const QComboBox* cb); - void restoreIndex(QComboBox* cb, std::optional def={}) const; + /** + * @brief returns whether an API key is currently stored + */ + bool hasApiKey() const; - std::optional getIndex(const QTabWidget* w) const; - void saveIndex(const QTabWidget* w); - void restoreIndex(QTabWidget* w, std::optional def={}) const; + /** + * @return true if endorsement integration is enabled + */ + bool endorsementIntegration() const; + void setEndorsementIntegration(bool b) const; - std::optional getChecked(const QAbstractButton* w) const; - void saveChecked(const QAbstractButton* w); - void restoreChecked(QAbstractButton* w, std::optional def={}) const; + EndorsementState endorsementState() const; + void setEndorsementState(EndorsementState s); - GeometrySettings& geometry(); - const GeometrySettings& geometry() const; + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); - ColorSettings& colors(); - const ColorSettings& colors() const; +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - PluginSettings& plugins(); - const PluginSettings& plugins() const; +class SteamSettings +{ +public: + SteamSettings(Settings& parent, QSettings& settings); /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString appID() const; + void setAppID(const QString& id); /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusApiKey(QString &apiKey) const; + * @brief retrieve the login information for steam + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if a username has been specified, false otherwise + **/ + bool login(QString &username, QString &password) const; /** - * @brief set the nexus login information + * @brief set the steam login information * * @param username username * @param password password */ - bool setNexusApiKey(const QString& apiKey); + void setLogin(QString username, QString password); - /** - * @brief clears the nexus login information - */ - bool clearNexusApiKey(); +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - /** - * @brief returns whether an API key is currently stored - */ - bool hasNexusApiKey() const; - /** - * @brief retrieve the login information for steam - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if a username has been specified, false otherwise - **/ - bool getSteamLogin(QString &username, QString &password) const; +class InterfaceSettings +{ +public: + InterfaceSettings(QSettings& settings); /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - void setOfflineMode(bool b); + * @return true if the GUI should be locked when running executables + */ + bool lockGUI() const; + void setLockGUI(bool b); + + std::optional styleName() const; + void setStyleName(const QString& name); /** - * @return true if the user chose compact downloads - */ + * @return true if the user chose compact downloads + */ bool compactDownloads() const; void setCompactDownloads(bool b); /** - * @return true if the user chose meta downloads - */ + * @return true if the user chose meta downloads + */ bool metaDownloads() const; void setMetaDownloads(bool b); + /** + * @return true if the API counter should be hidden + */ + bool hideAPICounter() const; + void setHideAPICounter(bool b); + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign() const; + void setDisplayForeign(bool b); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + void setLanguage(const QString& name); + + bool isTutorialCompleted(const QString& windowName) const; + void setTutorialCompleted(const QString& windowName, bool b=true); + +private: + QSettings& m_Settings; +}; + + +class DiagnosticsSettings +{ +public: + DiagnosticsSettings(QSettings& settings); + MOBase::log::Levels logLevel() const; void setLogLevel(MOBase::log::Levels level); @@ -378,60 +500,48 @@ public: int crashDumpsMax() const; void setCrashDumpsMax(int n); - QString executablesBlacklist() const; - void setExecutablesBlacklist(const QString& s); +private: + QSettings& m_Settings; +}; - /** - * @brief set the steam login information - * - * @param username username - * @param password password - */ - void setSteamLogin(QString username, QString password); - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - void setLoadMechanism(LoadMechanism::EMechanism m); - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); +/** + * manages the settings for Mod Organizer. The settings are not cached + * inside the class but read/written directly from/to disc + **/ +class Settings : public QObject +{ + Q_OBJECT; - /** - * @return true if the user configured the use of a network proxy - */ - bool getUseProxy() const; - void setUseProxy(bool b); +public: + Settings(const QString& path); + ~Settings(); - /** - * @return true if endorsement integration is enabled - */ - bool endorsementIntegration() const; - void setEndorsementIntegration(bool b) const; + static Settings &instance(); - EndorsementState endorsementState() const; - void setEndorsementState(EndorsementState s); + QString filename() const; - /** - * @return true if the API counter should be hidden - */ - bool hideAPICounter() const; - void setHideAPICounter(bool b); + std::optional version() const; + void processUpdates(const QVersionNumber& current, const QVersionNumber& last); - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign() const; - void setDisplayForeign(bool b); + bool firstStart() const; + void setFirstStart(bool b); + + std::vector> executables() const; + void setExecutables(const std::vector>& v); + + bool keepBackupOnInstall() const; + void setKeepBackupOnInstall(bool b); + + QString executablesBlacklist() const; + void setExecutablesBlacklist(const QString& s); /** * @brief sets the new motd hash **/ - unsigned int getMotDHash() const; - void setMotDHash(unsigned int hash); + unsigned int motdHash() const; + void setMotdHash(unsigned int hash); /** * @return true if the user wants to have archives being parsed to show conflicts and contents @@ -439,41 +549,45 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - void setLanguage(const QString& name); - - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - ServerList getServers() const; - ServerList getServersFromOldMap() const; - void updateServers(ServerList servers); - bool usePrereleases() const; void setUsePrereleases(bool b); - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); - /** - * @brief color the scrollbar of the mod list for custom separator colors? - * @return the state of the setting - */ - bool colorSeparatorScrollbar() const; - void setColorSeparatorScrollbar(bool b); + GameSettings& game(); + const GameSettings& game() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; - static QColor getIdealTextColor(const QColor& rBackgroundColor); + WidgetSettings& widgets(); + const WidgetSettings& widgets() const; - MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } - const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + ColorSettings& colors(); + const ColorSettings& colors() const; - QSettings::Status sync() const; + PluginSettings& plugins(); + const PluginSettings& plugins() const; + + PathSettings& paths(); + const PathSettings& paths() const; + + NetworkSettings& network(); + const NetworkSettings& network() const; + + NexusSettings& nexus(); + const NexusSettings& nexus() const; + + SteamSettings& steam(); + const SteamSettings& steam() const; + InterfaceSettings& interface(); + const InterfaceSettings& interface() const; + + DiagnosticsSettings& diagnostics(); + const DiagnosticsSettings& diagnostics() const; + + + QSettings::Status sync() const; void dump() const; public slots: @@ -485,18 +599,19 @@ signals: private: static Settings *s_Instance; - MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + + GameSettings m_Game; GeometrySettings m_Geometry; + WidgetSettings m_Widgets; ColorSettings m_Colors; PluginSettings m_Plugins; - LoadMechanism m_LoadMechanism; - - static bool obfuscate(const QString key, const QString data); - static QString deObfuscate(const QString key); - - QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - void setConfigurablePath(const QString &key, const QString& path); + PathSettings m_Paths; + NetworkSettings m_Network; + NexusSettings m_Nexus; + SteamSettings m_Steam; + InterfaceSettings m_Interface; + DiagnosticsSettings m_Diagnostics; }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 35d14644..1d3d4a39 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,11 +51,11 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); - m_settings.restoreIndex(ui->tabWidget); + m_settings.widgets().restoreIndex(ui->tabWidget); auto ret = TutorableDialog::exec(); - m_settings.saveIndex(ui->tabWidget); + m_settings.widgets().saveIndex(ui->tabWidget); if (ret == QDialog::Accepted) { for (auto&& tab : m_tabs) { @@ -109,7 +109,7 @@ void SettingsDialog::accept() if ((QDir::fromNativeSeparators(newModPath) != QDir::fromNativeSeparators( - Settings::instance().getModDirectory(true))) && + Settings::instance().paths().mods(true))) && (QMessageBox::question( nullptr, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 278da0bf..386c7425 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -12,7 +12,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) setLevelsBox(); setCrashDumpTypesBox(); - ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); + ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); @@ -36,7 +36,7 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == settings().logLevel()) { + if (ui->logLevelBox->itemData(i) == settings().diagnostics().logLevel()) { ui->logLevelBox->setCurrentIndex(i); break; } @@ -56,7 +56,8 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() add(QObject::tr("Data"), CrashDumpsType::Data); add(QObject::tr("Full"), CrashDumpsType::Full); - const auto current = static_cast(settings().crashDumpsType()); + const auto current = static_cast( + settings().diagnostics().crashDumpsType()); for (int i=0; idumpsTypeBox->count(); ++i) { if (ui->dumpsTypeBox->itemData(i) == current) { @@ -68,11 +69,11 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() void DiagnosticsSettingsTab::update() { - settings().setLogLevel( + settings().diagnostics().setLogLevel( static_cast(ui->logLevelBox->currentData().toInt())); - settings().setCrashDumpsType( + settings().diagnostics().setCrashDumpsType( static_cast(ui->dumpsTypeBox->currentData().toInt())); - settings().setCrashDumpsMax(ui->dumpsMaxEdit->value()); + settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index e3d73037..3f7ece38 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -11,7 +11,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { addLanguages(); { - QString languageCode = settings().language(); + QString languageCode = settings().interface().language(); int currentID = ui->languageBox->findData(languageCode); // I made a mess. :( Most languages are stored with only the iso country // code (2 characters like "de") but chinese @@ -29,7 +29,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { const int currentID = ui->styleBox->findData( - settings().getStyleName().value_or("")); + settings().interface().styleName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); @@ -51,10 +51,10 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) setContainsColor(settings().colors().modlistContainsPlugin()); setContainedColor(settings().colors().pluginListContained()); - ui->compactBox->setChecked(settings().compactDownloads()); - ui->showMetaBox->setChecked(settings().metaDownloads()); + ui->compactBox->setChecked(settings().interface().compactDownloads()); + ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); - ui->colorSeparatorsBox->setChecked(settings().colorSeparatorScrollbar()); + ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); @@ -69,18 +69,18 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) void GeneralSettingsTab::update() { - const QString oldLanguage = settings().language(); + const QString oldLanguage = settings().interface().language(); const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { - settings().setLanguage(newLanguage); + settings().interface().setLanguage(newLanguage); emit settings().languageChanged(newLanguage); } - const QString oldStyle = settings().getStyleName().value_or(""); + const QString oldStyle = settings().interface().styleName().value_or(""); const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - settings().setStyleName(newStyle); + settings().interface().setStyleName(newStyle); emit settings().styleChanged(newStyle); } @@ -91,10 +91,10 @@ void GeneralSettingsTab::update() settings().colors().setModlistContainsPlugin(getContainsColor()); settings().colors().setPluginListContained(getContainedColor()); - settings().setCompactDownloads(ui->compactBox->isChecked()); - settings().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().interface().setCompactDownloads(ui->compactBox->isChecked()); + settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); - settings().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); + settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() @@ -145,7 +145,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - settings().resetQuestionButtons(); + settings().widgets().resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) @@ -161,7 +161,7 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color .arg(color.green()) .arg(color.blue()) .arg(color.alpha()) - .arg(Settings::getIdealTextColor(color).name()) + .arg(ColorSettings::idealTextColor(color).name()) ); }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 8822200e..0b08f13f 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -74,13 +74,13 @@ private: NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->offlineBox->setChecked(settings().offlineMode()); - ui->proxyBox->setChecked(settings().getUseProxy()); - ui->endorsementBox->setChecked(settings().endorsementIntegration()); - ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); + ui->offlineBox->setChecked(settings().network().offlineMode()); + ui->proxyBox->setChecked(settings().network().useProxy()); + ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); // display server preferences - for (const auto& server : s.getServers()) { + for (const auto& server : s.network().servers()) { QString descriptor = server.name(); if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { @@ -117,12 +117,12 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) void NexusSettingsTab::update() { - settings().setOfflineMode(ui->offlineBox->isChecked()); - settings().setUseProxy(ui->proxyBox->isChecked()); - settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); - settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + settings().network().setOfflineMode(ui->offlineBox->isChecked()); + settings().network().setUseProxy(ui->proxyBox->isChecked()); + settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); + settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); - auto servers = settings().getServers(); + auto servers = settings().network().servers(); // store server preference for (int i = 0; i < ui->knownServersList->count(); ++i) { @@ -167,7 +167,7 @@ void NexusSettingsTab::update() } } - settings().updateServers(servers); + settings().network().updateServers(servers); } void NexusSettingsTab::on_nexusConnect_clicked() @@ -225,13 +225,13 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { - QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + QDir(Settings::instance().paths().cache()).removeRecursively(); NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() { - Settings::instance().registerAsNXMHandler(true); + Settings::instance().nexus().registerAsNXMHandler(true); } void NexusSettingsTab::validateKey(const QString& key) @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { dialog().m_keyChanged = true; - const bool ret = settings().setNexusApiKey(key); + const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; } @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { dialog().m_keyChanged = true; - const auto ret = settings().clearNexusApiKey(); + const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); updateNexusState(); @@ -352,7 +352,7 @@ void NexusSettingsTab::updateNexusButtons() ui->nexusManualKey->setText(QObject::tr("Cancel")); ui->nexusManualKey->setEnabled(true); } - else if (settings().hasNexusApiKey()) { + else if (settings().nexus().hasApiKey()) { // api key is present ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 32aaf4bf..aeb4dd5d 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -6,17 +6,23 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->baseDirEdit->setText(settings().getBaseDirectory()); - ui->managedGameDirEdit->setText(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); - QString basePath = settings().getBaseDirectory(); + ui->baseDirEdit->setText(settings().paths().base()); + + ui->managedGameDirEdit->setText( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + + QString basePath = settings().paths().base(); QDir baseDir(basePath); + for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, settings().getDownloadDirectory(false)), - std::make_pair(ui->modDirEdit, settings().getModDirectory(false)), - std::make_pair(ui->cacheDirEdit, settings().getCacheDirectory(false)), - std::make_pair(ui->profilesDirEdit, settings().getProfileDirectory(false)), - std::make_pair(ui->overwriteDirEdit, settings().getOverwriteDirectory(false)) + std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)), + std::make_pair(ui->modDirEdit, settings().paths().mods(false)), + std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)), + std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)), + std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false)) }) { + QString storePath = baseDir.relativeFilePath(dir.second); storePath = dir.second; dir.first->setText(storePath); @@ -40,17 +46,17 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) void PathsSettingsTab::update() { - using Setter = void (Settings::*)(const QString&); + using Setter = void (PathSettings::*)(const QString&); using Directory = std::tuple; - QString basePath = settings().getBaseDirectory(); + QString basePath = settings().paths().base(); for (const Directory &dir :{ - Directory{ui->downloadDirEdit->text(), &Settings::setDownloadDirectory, AppConfig::downloadPath()}, - Directory{ui->cacheDirEdit->text(), &Settings::setCacheDirectory, AppConfig::cachePath()}, - Directory{ui->modDirEdit->text(), &Settings::setModDirectory, AppConfig::modsPath()}, - Directory{ui->overwriteDirEdit->text(), &Settings::setOverwriteDirectory, AppConfig::overwritePath()}, - Directory{ui->profilesDirEdit->text(), &Settings::setProfileDirectory, AppConfig::profilesPath()} + Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), &PathSettings::setMods, AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, AppConfig::profilesPath()} }) { QString path; Setter setter; @@ -70,22 +76,26 @@ void PathsSettingsTab::update() } if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - (settings().*setter)(path); + (settings().paths().*setter)(path); } else { - (settings().*setter)(""); + (settings().paths().*setter)(""); } } if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - settings().setBaseDirectory(ui->baseDirEdit->text()); + settings().paths().setBase(ui->baseDirEdit->text()); } else { - settings().setBaseDirectory(""); + settings().paths().setBase(""); } - QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); + QFileInfo oldGameExe( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + QFileInfo newGameExe(ui->managedGameDirEdit->text()); + if (oldGameExe != newGameExe) { - settings().setManagedGameDirectory(newGameExe.absolutePath()); + settings().game().setDirectory(newGameExe.absolutePath()); } } diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp index 9ed93e47..3c4c5de6 100644 --- a/src/settingsdialogsteam.cpp +++ b/src/settingsdialogsteam.cpp @@ -5,7 +5,7 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { QString username, password; - settings().getSteamLogin(username, password); + settings().steam().login(username, password); ui->steamUserEdit->setText(username); ui->steamPassEdit->setText(password); @@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) void SteamSettingsTab::update() { - settings().setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); + settings().steam().setLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); } diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index b06bd77c..4d811e40 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -6,12 +6,12 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->appIDEdit->setText(settings().getSteamAppID()); + ui->appIDEdit->setText(settings().steam().appID()); - LoadMechanism::EMechanism mechanismID = settings().getLoadMechanism(); + LoadMechanism::EMechanism mechanismID = settings().game().loadMechanismType(); int index = 0; - if (settings().loadMechanism().isDirectLoadingSupported()) { + if (settings().game().loadMechanism().isDirectLoadingSupported()) { ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { index = ui->mechanismBox->count() - 1; @@ -20,10 +20,10 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) ui->mechanismBox->setCurrentIndex(index); - ui->hideUncheckedBox->setChecked(settings().hideUncheckedPlugins()); - ui->forceEnableBox->setChecked(settings().forceEnableCoreFiles()); - ui->displayForeignBox->setChecked(settings().displayForeign()); - ui->lockGUIBox->setChecked(settings().lockGUI()); + ui->hideUncheckedBox->setChecked(settings().game().hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(settings().game().forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(settings().interface().displayForeign()); + ui->lockGUIBox->setChecked(settings().interface().lockGUI()); ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); setExecutableBlacklist(settings().executablesBlacklist()); @@ -35,19 +35,19 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) void WorkaroundsSettingsTab::update() { - if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { - settings().setSteamAppID(ui->appIDEdit->text()); + if (ui->appIDEdit->text() != settings().game().plugin()->steamAPPId()) { + settings().steam().setAppID(ui->appIDEdit->text()); } else { - settings().setSteamAppID(""); + settings().steam().setAppID(""); } - settings().setLoadMechanism(static_cast( + settings().game().setLoadMechanism(static_cast( ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt())); - settings().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); - settings().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); - settings().setDisplayForeign(ui->displayForeignBox->isChecked()); - settings().setLockGUI(ui->lockGUIBox->isChecked()); + settings().game().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); + settings().game().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); + settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); + settings().interface().setLockGUI(ui->lockGUIBox->isChecked()); settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); settings().setExecutablesBlacklist(getExecutableBlacklist()); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index d22010a5..3734aa87 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -129,7 +129,7 @@ void StatusBar::setUpdateAvailable(bool b) void StatusBar::checkSettings(const Settings& settings) { - m_api->setVisible(!settings.hideAPICounter()); + m_api->setVisible(!settings.interface().hideAPICounter()); } void StatusBar::showEvent(QShowEvent*) diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 41f58308..4315ed92 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -163,8 +163,8 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = Settings::instance().crashDumpsType(); + LogLevel level = toUsvfsLogLevel(Settings::instance().diagnostics().logLevel()); + CrashDumpsType dumpType = Settings::instance().diagnostics().crashDumpsType(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); -- cgit v1.3.1 From 4d9c1db885bd3ab230440b25e70dcd8049cf4650 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 11 Sep 2019 11:45:32 -0500 Subject: Add portable lock feature If the file "portable.txt" is present in the application directory, MO will force itself to be launched as a portable instance. The change game button and menu item are hidden to prevent the user from changing out of the portable instance. --- src/instancemanager.cpp | 21 +++++++++++++++++++++ src/instancemanager.h | 3 +++ src/mainwindow.cpp | 4 ++++ src/shared/appconfig.inc | 1 + 4 files changed, 29 insertions(+) (limited to 'src/mainwindow.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index fdc30e22..c0d343de 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -186,6 +186,10 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons QString InstanceManager::chooseInstance(const QStringList &instanceList) const { + if (portableInstallIsLocked()) { + return QString(); + } + enum class Special : uint8_t { NewInstance, Portable, @@ -266,6 +270,19 @@ bool InstanceManager::portableInstall() const } +bool InstanceManager::portableInstallIsLocked() const +{ + return QFile::exists(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::portableLockFileName())); +} + + +bool InstanceManager::allowedToChangeInstance() const +{ + return !portableInstallIsLocked(); +} + + void InstanceManager::createDataPath(const QString &dataPath) const { if (!QDir(dataPath).exists()) { @@ -286,6 +303,10 @@ void InstanceManager::createDataPath(const QString &dataPath) const QString InstanceManager::determineDataPath() { QString instanceId = currentInstance(); + if (portableInstallIsLocked()) + { + instanceId.clear(); + } if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) { // startup, apparently using portable mode before diff --git a/src/instancemanager.h b/src/instancemanager.h index 4efa6f03..0e31fb08 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -38,6 +38,8 @@ public: QString currentInstance() const; + bool allowedToChangeInstance() const; + private: InstanceManager(); @@ -58,6 +60,7 @@ private: void createDataPath(const QString &dataPath) const; bool portableInstall() const; + bool portableInstallIsLocked() const; private: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 657c1a27..bd9f1486 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -727,6 +727,10 @@ void MainWindow::setupToolbar() } else { log::warn("no separator found on the toolbar, icons won't be right-aligned"); } + + if (!InstanceManager::instance().allowedToChangeInstance()) { + ui->actionChange_Game->setVisible(false); + } } void MainWindow::setupActionMenu(QAction* a) diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index e572a32b..709c845d 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -17,6 +17,7 @@ APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be ident APPPARAM(std::wstring, proxyDLLSource, L"proxy.dll") APPPARAM(std::wstring, vfs32DLLName, L"usvfs_x86.dll") APPPARAM(std::wstring, vfs64DLLName, L"usvfs_x64.dll") +APPPARAM(std::wstring, portableLockFileName, L"portable.txt") APPPARAM(const wchar_t*, localSavePlaceholder, L"__MOProfileSave__\\") APPPARAM(std::wstring, firstStepsTutorial, L"tutorial_firststeps_main.js") -- cgit v1.3.1 From 06218502ed5379555eda1504e6b05f2ef6dfb292 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:34:54 -0400 Subject: fixes for ExpanderWidget removed dead code in MainWindow --- src/CMakeLists.txt | 3 --- src/mainwindow.cpp | 11 ----------- src/mainwindow.h | 3 --- src/modinfodialogconflicts.h | 2 +- src/settings.h | 6 +++--- src/settingsutilities.h | 6 ++++-- 6 files changed, 8 insertions(+), 23 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea27184b..60822834 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -136,7 +136,6 @@ SET(organizer_SRCS apiuseraccount.cpp filerenamer.cpp texteditor.cpp - expanderwidget.cpp env.cpp envmetrics.cpp envmodule.cpp @@ -260,7 +259,6 @@ SET(organizer_HDRS apiuseraccount.h filerenamer.h texteditor.h - expanderwidget.h env.h envmetrics.h envmodule.h @@ -478,7 +476,6 @@ set(utilities ) set(widgets - expanderwidget genericicondelegate filerenamer filterwidget diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 657c1a27..054262e9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2561,17 +2561,6 @@ void MainWindow::modInstalled(const QString &modName) modUpdateCheck(IDs); } -void MainWindow::procError(QProcess::ProcessError error) -{ - reportError(tr("failed to spawn notepad.exe: %1").arg(error)); - this->sender()->deleteLater(); -} - -void MainWindow::procFinished(int, QProcess::ExitStatus) -{ - this->sender()->deleteLater(); -} - void MainWindow::showMessage(const QString &message) { MessageDialog::showMessage(message, this); diff --git a/src/mainwindow.h b/src/mainwindow.h index 6f06b9d5..1f997ab1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -489,9 +489,6 @@ private slots: void doMoveOverwriteContentToMod(const QString &modAbsolutePath); void clearOverwrite(); - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - // nexus related void checkModsForUpdates(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a77c2ac9..ad305dfc 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -33,7 +33,7 @@ signals: private: struct Expanders { - ExpanderWidget overwrite, overwritten, nonconflict; + MOBase::ExpanderWidget overwrite, overwritten, nonconflict; }; ConflictsTab* m_tab; diff --git a/src/settings.h b/src/settings.h index 91b87e29..815ed160 100644 --- a/src/settings.h +++ b/src/settings.h @@ -32,13 +32,13 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPlugin; class IPluginGame; + class ExpanderWidget; } class QSplitter; class ServerList; class Settings; -class ExpanderWidget; // helper class that calls restoreGeometry() in the constructor and @@ -153,8 +153,8 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - void saveState(const ExpanderWidget* expander); - bool restoreState(ExpanderWidget* expander) const; + void saveState(const MOBase::ExpanderWidget* expander); + bool restoreState(MOBase::ExpanderWidget* expander) const; void saveVisibility(const QWidget* w); bool restoreVisibility(QWidget* w, std::optional def={}) const; diff --git a/src/settingsutilities.h b/src/settingsutilities.h index d99abb06..c3eef12f 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -3,7 +3,9 @@ #include -class ExpanderWidget; +namespace MOBase { + class ExpanderWidget; +} template struct ValueConverter @@ -241,7 +243,7 @@ private: QString widgetNameWithTopLevel(const QWidget* widget); QString widgetName(const QMainWindow* w); QString widgetName(const QHeaderView* w); -QString widgetName(const ExpanderWidget* w); +QString widgetName(const MOBase::ExpanderWidget* w); QString widgetName(const QWidget* w); template -- cgit v1.3.1 From 971cecf343777894cec5144da11d16ef97d0db31 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 01:09:40 -0400 Subject: split spawnBinaryProcess() into spawn, no changes --- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 286 ++-------------------------------------------- src/spawn.cpp | 310 +++++++++++++++++++++++++++++++++++++++++++++++++- src/spawn.h | 18 +++ 4 files changed, 338 insertions(+), 278 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 054262e9..6737e330 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6434,7 +6434,7 @@ void MainWindow::on_bossButton_clicked() return; } - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), qApp->applicationDirPath() + "/loot", true, diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 91e16716..9403b47d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -135,34 +135,6 @@ static DWORD getProcessParentID(DWORD pid) return res; } -static void startSteam(QWidget *widget) -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - exe = QString("\"%1\"").arg(exe); - // See if username and password supplied. If so, pass them into steam. - QStringList args; - QString username; - QString password; - if (Settings::instance().steam().login(username, password)) { - args << "-login"; - args << username; - if (password != "") { - args << password; - } - } - if (!QProcess::startDetached(exe, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); - } else { - QMessageBox::information( - widget, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); - } - } -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -173,86 +145,6 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -bool checkService() -{ - SC_HANDLE serviceManagerHandle = NULL; - SC_HANDLE serviceHandle = NULL; - LPSERVICE_STATUS_PROCESS serviceStatus = NULL; - LPQUERY_SERVICE_CONFIG serviceConfig = NULL; - bool serviceRunning = true; - - DWORD bytesNeeded; - - try { - serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceManagerHandle) { - log::warn("failed to open service manager (query status) (error {})", GetLastError()); - throw 1; - } - - serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceHandle) { - log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); - throw 2; - } - - if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service config (error {})", GetLastError()); - throw 3; - } - - DWORD serviceConfigSize = bytesNeeded; - serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); - if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - log::warn("failed to query service config (error {})", GetLastError()); - throw 4; - } - - if (serviceConfig->dwStartType == SERVICE_DISABLED) { - log::error("Windows Event Log service is disabled!"); - serviceRunning = false; - } - - if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service status (error {})", GetLastError()); - throw 5; - } - - DWORD serviceStatusSize = bytesNeeded; - serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); - if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - log::warn("failed to query service status (error {})", GetLastError()); - throw 6; - } - - if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - log::error("Windows Event Log service is not running"); - serviceRunning = false; - } - } - catch (int e) { - UNUSED_VAR(e); - serviceRunning = false; - } - - if (serviceStatus) { - LocalFree(serviceStatus); - } - if (serviceConfig) { - LocalFree(serviceConfig); - } - if (serviceHandle) { - CloseServiceHandle(serviceHandle); - } - if (serviceManagerHandle) { - CloseServiceHandle(serviceManagerHandle); - } - - return serviceRunning; -} - OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) @@ -361,67 +253,6 @@ void OrganizerCore::storeSettings() } } -bool OrganizerCore::testForSteam(bool *found, bool *access) -{ - HANDLE hProcessSnap; - HANDLE hProcess; - PROCESSENTRY32 pe32; - DWORD lastError; - - if (found == nullptr || access == nullptr) { - return false; - } - - // Take a snapshot of all processes in the system. - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - lastError = GetLastError(); - log::error("unable to get snapshot of processes (error {})", lastError); - return false; - } - - // Retrieve information about the first process, - // and exit if unsuccessful - pe32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(hProcessSnap, &pe32)) { - lastError = GetLastError(); - log::error("unable to get first process (error {})", lastError); - CloseHandle(hProcessSnap); - return false; - } - - *found = false; - *access = true; - - // Now walk the snapshot of processes, and - // display information about each process in turn - do { - if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || - (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { - - *found = true; - - // Try to open the process to determine if MO has the proper access - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pe32.th32ProcessID); - if (hProcess == NULL) { - lastError = GetLastError(); - if (lastError == ERROR_ACCESS_DENIED) { - *access = false; - } - } else { - CloseHandle(hProcess); - } - break; - } - -} while(Process32Next(hProcessSnap, &pe32)); - -CloseHandle(hProcessSnap); -return true; - -} - void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { @@ -1453,93 +1284,18 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, { prepareStart(); - if (!binary.exists()) { - reportError( - tr("Executable not found: %1").arg(qUtf8Printable(binary.absoluteFilePath()))); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.steam().appID()).c_str()); - } - QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { window = nullptr; } - // This could possibly be extracted somewhere else but it's probably for when - // we have more than one provider of game registration. - if ((QFileInfo( - managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")) - .exists() - || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( - "steam_api64.dll")) - .exists()) - && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { - - bool steamFound = true; - bool steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } - - if (!steamFound) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(window, "steamQuery", binary.fileName(), - tr("Start Steam?"), - tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - startSteam(window); - - // double-check that Steam is started and MO has access - steamFound = true; - steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } else if (!steamFound) { - log::error("could not find Steam"); - } - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBinary(binary)) { + return INVALID_HANDLE_VALUE; + } - if (!steamAccess) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(window, "steamAdminQuery", binary.fileName(), - tr("Steam: Access Denied"), - tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - log::error("unable to get current directory (error {})", GetLastError()); - cwd[0] = L'\0'; - } - if (!Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { - log::error("unable to relaunch MO as admin"); - return INVALID_HANDLE_VALUE; - } - qApp->exit(0); - return INVALID_HANDLE_VALUE; - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) { + return INVALID_HANDLE_VALUE; } while (m_DirectoryUpdate) { @@ -1566,32 +1322,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, return INVALID_HANDLE_VALUE; } - // Check if the Windows Event Logging service is running. For some reason, this seems to be - // critical to the successful running of usvfs. - if (!checkService()) { - if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(), - tr("Windows Event Log Error"), - tr("The Windows Event Log service is disabled and/or not running. This prevents" - " USVFS from running properly. Your mods may not be working in the executable" - " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } + if (!spawn::checkEnvironment(window, binary)) { + return INVALID_HANDLE_VALUE; } - for (auto exec : settings().executablesBlacklist().split(";")) { - if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(window, QString("blacklistedExecutable"), binary.fileName(), - tr("Blacklisted Executable"), - tr("The executable you are attempted to launch is blacklisted in the virtual file" - " system. This will likely prevent the executable, and any executables that are" - " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBlacklist(window, m_Settings, binary)) { + return INVALID_HANDLE_VALUE; } QString modsPath = settings().paths().mods(); @@ -1628,11 +1364,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, log::debug("Spawning proxyed process <{}>", cmdline); - return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), + return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return startBinary(binary, arguments, currentDirectory, true); + return spawn::startBinary(binary, arguments, currentDirectory, true); } } else { log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); diff --git a/src/spawn.cpp b/src/spawn.cpp index 614bc92f..4376e5bf 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envwindows.h" #include "envsecurity.h" +#include "settings.h" #include #include #include @@ -41,6 +42,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +namespace spawn +{ + +// details namespace { @@ -320,8 +325,6 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) return (r == QMessageBox::Yes); } -} // namespace - void startBinaryAdmin(const SpawnParameters& sp) { if (!confirmRestartAsAdmin(sp)) { @@ -344,6 +347,307 @@ void startBinaryAdmin(const SpawnParameters& sp) } } +} // namespace + + +bool checkBinary(const QFileInfo& binary) +{ + if (!binary.exists()) { + reportError( + QObject::tr("Executable not found: %1") + .arg(qUtf8Printable(binary.absoluteFilePath()))); + + return false; + } + + return true; +} + +bool testForSteam(bool *found, bool *access) +{ + HANDLE hProcessSnap; + HANDLE hProcess; + PROCESSENTRY32 pe32; + DWORD lastError; + + if (found == nullptr || access == nullptr) { + return false; + } + + // Take a snapshot of all processes in the system. + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + lastError = GetLastError(); + log::error("unable to get snapshot of processes (error {})", lastError); + return false; + } + + // Retrieve information about the first process, + // and exit if unsuccessful + pe32.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(hProcessSnap, &pe32)) { + lastError = GetLastError(); + log::error("unable to get first process (error {})", lastError); + CloseHandle(hProcessSnap); + return false; + } + + *found = false; + *access = true; + + // Now walk the snapshot of processes, and + // display information about each process in turn + do { + if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || + (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { + + *found = true; + + // Try to open the process to determine if MO has the proper access + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, pe32.th32ProcessID); + if (hProcess == NULL) { + lastError = GetLastError(); + if (lastError == ERROR_ACCESS_DENIED) { + *access = false; + } + } else { + CloseHandle(hProcess); + } + break; + } + + } while(Process32Next(hProcessSnap, &pe32)); + + CloseHandle(hProcessSnap); + return true; +} + +void startSteam(QWidget *widget) +{ + QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", + QSettings::NativeFormat); + QString exe = steamSettings.value("SteamExe", "").toString(); + if (!exe.isEmpty()) { + exe = QString("\"%1\"").arg(exe); + // See if username and password supplied. If so, pass them into steam. + QStringList args; + QString username; + QString password; + if (Settings::instance().steam().login(username, password)) { + args << "-login"; + args << username; + if (password != "") { + args << password; + } + } + if (!QProcess::startDetached(exe, args)) { + reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); + } else { + QMessageBox::information( + widget, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); + } + } +} + +bool checkSteam( + QWidget* parent, const QDir& gameDirectory, + const QFileInfo &binary, const QString &steamAppID, const Settings& settings) +{ + if (!steamAppID.isEmpty()) { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + } else { + ::SetEnvironmentVariableW(L"SteamAPPId", + ToWString(settings.steam().appID()).c_str()); + } + + if ((QFileInfo(gameDirectory.absoluteFilePath("steam_api.dll")).exists() || + QFileInfo(gameDirectory.absoluteFilePath("steam_api64.dll")).exists()) + && (settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { + + bool steamFound = true; + bool steamAccess = true; + if (!testForSteam(&steamFound, &steamAccess)) { + log::error("unable to determine state of Steam"); + } + + if (!steamFound) { + QDialogButtonBox::StandardButton result; + result = QuestionBoxMemory::query(parent, "steamQuery", binary.fileName(), + QObject::tr("Start Steam?"), + QObject::tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + if (result == QDialogButtonBox::Yes) { + startSteam(parent); + + // double-check that Steam is started and MO has access + steamFound = true; + steamAccess = true; + if (!testForSteam(&steamFound, &steamAccess)) { + log::error("unable to determine state of Steam"); + } else if (!steamFound) { + log::error("could not find Steam"); + } + + } else if (result == QDialogButtonBox::Cancel) { + return false; + } + } + + if (!steamAccess) { + QDialogButtonBox::StandardButton result; + result = QuestionBoxMemory::query(parent, "steamAdminQuery", binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::tr("MO was denied access to the Steam process. This normally indicates that " + "Steam is being run as administrator while MO is not. This can cause issues " + "launching the game. It is recommended to not run Steam as administrator unless " + "absolutely necessary.\n\n" + "Restart MO as administrator?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + if (result == QDialogButtonBox::Yes) { + WCHAR cwd[MAX_PATH]; + if (!GetCurrentDirectory(MAX_PATH, cwd)) { + log::error("unable to get current directory (error {})", GetLastError()); + cwd[0] = L'\0'; + } + if (!Helper::adminLaunch( + qApp->applicationDirPath().toStdWString(), + qApp->applicationFilePath().toStdWString(), + std::wstring(cwd))) { + log::error("unable to relaunch MO as admin"); + return false; + } + qApp->exit(0); + return false; + } else if (result == QDialogButtonBox::Cancel) { + return false; + } + } + } + + return true; +} + +bool checkService() +{ + SC_HANDLE serviceManagerHandle = NULL; + SC_HANDLE serviceHandle = NULL; + LPSERVICE_STATUS_PROCESS serviceStatus = NULL; + LPQUERY_SERVICE_CONFIG serviceConfig = NULL; + bool serviceRunning = true; + + DWORD bytesNeeded; + + try { + serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceManagerHandle) { + log::warn("failed to open service manager (query status) (error {})", GetLastError()); + throw 1; + } + + serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceHandle) { + log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); + throw 2; + } + + if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + log::warn("failed to get size of service config (error {})", GetLastError()); + throw 3; + } + + DWORD serviceConfigSize = bytesNeeded; + serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); + if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { + log::warn("failed to query service config (error {})", GetLastError()); + throw 4; + } + + if (serviceConfig->dwStartType == SERVICE_DISABLED) { + log::error("Windows Event Log service is disabled!"); + serviceRunning = false; + } + + if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + log::warn("failed to get size of service status (error {})", GetLastError()); + throw 5; + } + + DWORD serviceStatusSize = bytesNeeded; + serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); + if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { + log::warn("failed to query service status (error {})", GetLastError()); + throw 6; + } + + if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { + log::error("Windows Event Log service is not running"); + serviceRunning = false; + } + } + catch (int) { + serviceRunning = false; + } + + if (serviceStatus) { + LocalFree(serviceStatus); + } + if (serviceConfig) { + LocalFree(serviceConfig); + } + if (serviceHandle) { + CloseServiceHandle(serviceHandle); + } + if (serviceManagerHandle) { + CloseServiceHandle(serviceManagerHandle); + } + + return serviceRunning; +} + +bool checkEnvironment(QWidget* parent, const QFileInfo& binary) +{ + // Check if the Windows Event Logging service is running. For some reason, this seems to be + // critical to the successful running of usvfs. + if (!checkService()) { + if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(), + QObject::tr("Windows Event Log Error"), + QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" + " USVFS from running properly. Your mods may not be working in the executable" + " that you are launching. Note that you may have to restart MO and/or your PC" + " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { + return false; + } + } + + return true; +} + +bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary) +{ + for (auto exec : settings.executablesBlacklist().split(";")) { + if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { + if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(), + QObject::tr("Blacklisted Executable"), + QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" + " system. This will likely prevent the executable, and any executables that are" + " launched by this one, from seeing any mods. This could extend to INI files, save" + " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { + return false; + } + } + } + + return true; +} + + HANDLE startBinary( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool hooked, HANDLE stdOut, HANDLE stdErr) @@ -382,3 +686,5 @@ HANDLE startBinary( } } } + +} // namespace \ No newline at end of file diff --git a/src/spawn.h b/src/spawn.h index 9a2dbfbd..9a5afb4a 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -26,6 +26,22 @@ along with Mod Organizer. If not, see . #include #include +class Settings; + +namespace spawn +{ + +bool checkBinary(const QFileInfo& binary); + +bool checkSteam( + QWidget* parent, const QDir& gameDirectory, + const QFileInfo &binary, const QString &steamAppID, const Settings& settings); + +bool checkEnvironment(QWidget* parent, const QFileInfo& binary); + +bool checkBlacklist( + QWidget* parent, const Settings& settings, const QFileInfo& binary); + /** * @brief spawn a binary with Mod Organizer injected * @@ -45,5 +61,7 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool hooked, HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); +} // namespace + #endif // SPAWN_H -- cgit v1.3.1 From bf3d7527801bcba16ba01735efd3d5acc052f485 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 01:48:48 -0400 Subject: made SpawnParameters public, changed to Qt types removed 'suspended', not used --- src/mainwindow.cpp | 12 ++++--- src/organizercore.cpp | 24 ++++++++++---- src/spawn.cpp | 92 ++++++++++++++++++--------------------------------- src/spawn.h | 46 +++++++++++++++----------- 4 files changed, 83 insertions(+), 91 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6737e330..7774d87d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6434,11 +6434,13 @@ void MainWindow::on_bossButton_clicked() return; } - HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), - parameters.join(" "), - qApp->applicationDirPath() + "/loot", - true, - stdOutWrite); + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.stdOut = stdOutWrite; + + HANDLE loot = spawn::startBinary(this, sp); // we don't use the write end ::CloseHandle(stdOutWrite); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 9403b47d..dd4edbce 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1282,6 +1282,13 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, const QString &customOverwrite, const QList &forcedLibraries) { + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.hooked = true; + + prepareStart(); QWidget *window = qApp->activeWindow(); @@ -1290,11 +1297,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } - if (!spawn::checkBinary(binary)) { + if (!spawn::checkBinary(window, sp)) { return INVALID_HANDLE_VALUE; } - if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) { + if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { return INVALID_HANDLE_VALUE; } @@ -1322,11 +1329,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, return INVALID_HANDLE_VALUE; } - if (!spawn::checkEnvironment(window, binary)) { + if (!spawn::checkEnvironment(window, sp)) { return INVALID_HANDLE_VALUE; } - if (!spawn::checkBlacklist(window, m_Settings, binary)) { + if (!spawn::checkBlacklist(window, sp, m_Settings)) { return INVALID_HANDLE_VALUE; } @@ -1364,11 +1371,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, log::debug("Spawning proxyed process <{}>", cmdline); - return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()), - cmdline, QCoreApplication::applicationDirPath(), true); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + + return spawn::startBinary(window, sp); } else { log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return spawn::startBinary(binary, arguments, currentDirectory, true); + return spawn::startBinary(window, sp); } } else { log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); diff --git a/src/spawn.cpp b/src/spawn.cpp index 4376e5bf..a56df23a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -49,17 +49,6 @@ namespace spawn namespace { -struct SpawnParameters -{ - std::wstring binary; - std::wstring arguments; - std::wstring currentDirectory; - bool suspended = false; - bool hooked = false; - HANDLE stdOut = INVALID_HANDLE_VALUE; - HANDLE stdErr = INVALID_HANDLE_VALUE; -}; - std::wstring pathEnv() { @@ -103,12 +92,11 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand si.dwFlags |= STARTF_USESTDHANDLES; } - std::wstring commandLine; + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); + std::wstring commandLine = L"\"" + bin + L"\""; if (sp.arguments[0] != L'\0') { - commandLine = L"\"" + sp.binary + L"\" " + sp.arguments; - } else { - commandLine = L"\"" + sp.binary + L"\""; + commandLine += L" " + sp.arguments.toStdWString(); } QString moPath = QCoreApplication::applicationDirPath(); @@ -119,16 +107,18 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand PROCESS_INFORMATION pi; BOOL success = FALSE; + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - sp.currentDirectory.c_str(), &si, &pi); + cwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - sp.currentDirectory.c_str(), &si, &pi); + cwd.c_str(), &si, &pi); } const auto e = GetLastError(); @@ -164,11 +154,10 @@ std::wstring makeRightsDetails(const env::FileSecurity& fs) std::wstring makeDetails(const SpawnParameters& sp, DWORD code) { - const QFileInfo bin(QString::fromStdWString(sp.binary)); std::wstring owner, rights; - if (bin.isFile()) { - const auto fs = env::getFileSecurity(bin.absoluteFilePath()); + if (sp.binary.isFile()) { + const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath()); if (fs.error.isEmpty()) { owner = fs.owner.toStdWString(); @@ -182,8 +171,8 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) rights = L"(file not found)"; } - const bool cwdExists = (sp.currentDirectory.empty() ? - true : QFileInfo(QString::fromStdWString(sp.currentDirectory)).isDir()); + const bool cwdExists = (sp.currentDirectory.isEmpty() ? + true : sp.currentDirectory.exists()); const auto appDir = QCoreApplication::applicationDirPath(); const auto sep = QDir::separator(); @@ -214,21 +203,20 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) L" . rights: {rights}\n" L" . arguments: '{args}'\n" L" . cwd: '{cwd}'{cwdexists}\n" - L" . stdout: {stdout}, stderr: {stderr}, suspended: {susp}, hooked: {hooked}\n" + L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" L" . MO elevated: {elevated}", fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), - fmt::arg(L"bin", sp.binary), + fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), fmt::arg(L"owner", owner), fmt::arg(L"rights", rights), fmt::arg(L"error", formatSystemMessage(code)), - fmt::arg(L"args", sp.arguments), - fmt::arg(L"cwd", sp.currentDirectory), + fmt::arg(L"args", sp.arguments.toStdWString()), + fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()), fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")), fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")), fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")), - fmt::arg(L"susp", (sp.suspended ? L"yes" : L"no")), fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")), fmt::arg(L"x86_dll", usvfs_x86_dll), fmt::arg(L"x64_dll", usvfs_x64_dll), @@ -242,12 +230,10 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) const auto details = QString::fromStdWString(makeDetails(sp, code)); log::error("{}", details); - const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); - const auto title = QObject::tr("Cannot launch program"); const auto mainText = QObject::tr("Cannot start %1") - .arg(binary.fileName()); + .arg(sp.binary.fileName()); QString content; @@ -286,12 +272,10 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) log::error("{}", details); - const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); - const auto title = QObject::tr("Elevation required"); const auto mainText = QObject::tr("Cannot start %1") - .arg(binary.fileName()); + .arg(sp.binary.fileName()); const auto content = QObject::tr( "This program is requesting to run as administrator but Mod Organizer " @@ -350,12 +334,12 @@ void startBinaryAdmin(const SpawnParameters& sp) } // namespace -bool checkBinary(const QFileInfo& binary) +bool checkBinary(QWidget* parent, const SpawnParameters& sp) { - if (!binary.exists()) { + if (!sp.binary.exists()) { reportError( QObject::tr("Executable not found: %1") - .arg(qUtf8Printable(binary.absoluteFilePath()))); + .arg(sp.binary.absoluteFilePath())); return false; } @@ -452,8 +436,8 @@ void startSteam(QWidget *widget) } bool checkSteam( - QWidget* parent, const QDir& gameDirectory, - const QFileInfo &binary, const QString &steamAppID, const Settings& settings) + QWidget* parent, const SpawnParameters& sp, + const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) { if (!steamAppID.isEmpty()) { ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); @@ -474,7 +458,7 @@ bool checkSteam( if (!steamFound) { QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamQuery", binary.fileName(), + result = QuestionBoxMemory::query(parent, "steamQuery", sp.binary.fileName(), QObject::tr("Start Steam?"), QObject::tr("Steam is required to be running already to correctly start the game. " "Should MO try to start steam now?"), @@ -498,7 +482,7 @@ bool checkSteam( if (!steamAccess) { QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamAdminQuery", binary.fileName(), + result = QuestionBoxMemory::query(parent, "steamAdminQuery", sp.binary.fileName(), QObject::tr("Steam: Access Denied"), QObject::tr("MO was denied access to the Steam process. This normally indicates that " "Steam is being run as administrator while MO is not. This can cause issues " @@ -609,17 +593,17 @@ bool checkService() return serviceRunning; } -bool checkEnvironment(QWidget* parent, const QFileInfo& binary) +bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) { // Check if the Windows Event Logging service is running. For some reason, this seems to be // critical to the successful running of usvfs. if (!checkService()) { - if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(), + if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(), QObject::tr("Windows Event Log Error"), QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" " USVFS from running properly. Your mods may not be working in the executable" " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), + " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()), QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { return false; } @@ -628,16 +612,16 @@ bool checkEnvironment(QWidget* parent, const QFileInfo& binary) return true; } -bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary) +bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) { for (auto exec : settings.executablesBlacklist().split(";")) { - if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(), + if (exec.compare(sp.binary.fileName(), Qt::CaseInsensitive) == 0) { + if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), sp.binary.fileName(), QObject::tr("Blacklisted Executable"), QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" " system. This will likely prevent the executable, and any executables that are" " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), + " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()), QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { return false; } @@ -648,20 +632,8 @@ bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& } -HANDLE startBinary( - const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, - bool hooked, HANDLE stdOut, HANDLE stdErr) +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { - SpawnParameters sp; - - sp.binary = QDir::toNativeSeparators(binary.absoluteFilePath()).toStdWString(); - sp.arguments = arguments.toStdWString(); - sp.currentDirectory = QDir::toNativeSeparators(currentDirectory.absolutePath()).toStdWString(); - sp.suspended = true; - sp.hooked = hooked; - sp.stdOut = stdOut; - sp.stdErr = stdErr; - HANDLE processHandle, threadHandle; const auto e = spawn(sp, processHandle, threadHandle); diff --git a/src/spawn.h b/src/spawn.h index 9a5afb4a..9398b6cc 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -31,20 +31,7 @@ class Settings; namespace spawn { -bool checkBinary(const QFileInfo& binary); - -bool checkSteam( - QWidget* parent, const QDir& gameDirectory, - const QFileInfo &binary, const QString &steamAppID, const Settings& settings); - -bool checkEnvironment(QWidget* parent, const QFileInfo& binary); - -bool checkBlacklist( - QWidget* parent, const Settings& settings, const QFileInfo& binary); - -/** - * @brief spawn a binary with Mod Organizer injected - * +/* * @param binary the binary to spawn * @param arguments arguments to pass to the binary * @param profileName name of the active profile @@ -53,13 +40,34 @@ bool checkBlacklist( * @param hooked if set, the binary is started with mo injected * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process +*/ +struct SpawnParameters +{ + QFileInfo binary; + QString arguments; + QDir currentDirectory; + bool hooked = false; + HANDLE stdOut = INVALID_HANDLE_VALUE; + HANDLE stdErr = INVALID_HANDLE_VALUE; +}; + + +bool checkBinary(QWidget* parent, const SpawnParameters& sp); + +bool checkSteam( + QWidget* parent, const SpawnParameters& sp, + const QDir& gameDirectory, const QString &steamAppID, const Settings& settings); + +bool checkEnvironment(QWidget* parent, const SpawnParameters& sp); + +bool checkBlacklist( + QWidget* parent, const SpawnParameters& sp, const Settings& settings); + +/** + * @brief spawn a binary with Mod Organizer injected * @return the process handle - * @todo is the profile name even used any more? - * @todo is the hooked parameter used? **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, bool hooked, - HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); } // namespace -- cgit v1.3.1 From 088f27fe48cd8f218052090a97e8187eedf0c06e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 23 Sep 2019 19:10:19 -0400 Subject: changed the layout of the general settings tab added option to disable checking for updates removed online check, just try it and see --- src/mainwindow.cpp | 16 ++++ src/mainwindow.h | 1 + src/organizercore.cpp | 36 ++------ src/organizercore.h | 1 + src/selfupdater.cpp | 12 ++- src/selfupdater.h | 14 ++- src/settings.cpp | 10 +++ src/settings.h | 5 ++ src/settingsdialog.ui | 203 ++++++++++++++++++++++++++---------------- src/settingsdialoggeneral.cpp | 2 + 10 files changed, 187 insertions(+), 113 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42cbe919..cd650d2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -263,6 +263,7 @@ MainWindow::MainWindow(Settings &settings setupToolbar(); toggleMO2EndorseState(); + toggleUpdateAction(); TaskProgressManager::instance().tryCreateTaskbar(); @@ -5007,6 +5008,7 @@ void MainWindow::on_actionSettings_triggered() bool oldDisplayForeign(settings.interface().displayForeign()); bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); + const bool oldCheckForUpdates = settings.checkForUpdates(); SettingsDialog dialog(&m_PluginContainer, settings, this); @@ -5084,6 +5086,14 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); + + if (oldCheckForUpdates != settings.checkForUpdates()) { + toggleUpdateAction(); + + if (settings.checkForUpdates()) { + m_OrganizerCore.checkForUpdates(); + } + } } void MainWindow::on_actionNexus_triggered() @@ -5596,6 +5606,12 @@ void MainWindow::toggleMO2EndorseState() ui->actionEndorseMO->setStatusTip(text); } +void MainWindow::toggleUpdateAction() +{ + const auto& s = m_OrganizerCore.settings(); + ui->actionUpdate->setVisible(s.checkForUpdates()); +} + void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) { QVariantList data = resultData.toList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f997ab1..524e2b6e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -313,6 +313,7 @@ private: void sendSelectedPluginsToPriority(int newPriority); void toggleMO2EndorseState(); + void toggleUpdateAction(); private: diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0da5b604..a4a89c99 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -74,25 +74,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static bool isOnline() -{ - const auto runningFlags = - QNetworkInterface::IsUp | QNetworkInterface::IsRunning; - - for (auto&& i : QNetworkInterface::allInterfaces()) { - if (!(i.flags() & QNetworkInterface::IsLoopBack)) { - if (i.flags() & runningFlags) { - auto addresses = i.addressEntries(); - if (!addresses.empty()) { - return true; - } - } - } - } - - return false; -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -307,14 +288,15 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, m_InstallationManager.setParentWidget(widget); m_Updater.setUserInterface(widget); - if (userInterface != nullptr) { - // this currently wouldn't work reliably if the ui isn't initialized yet to - // display the result - if (isOnline() && !m_Settings.network().offlineMode()) { - m_Updater.testForUpdate(); - } else { - log::debug("user doesn't seem to be connected to the internet"); - } + checkForUpdates(); +} + +void OrganizerCore::checkForUpdates() +{ + // this currently wouldn't work reliably if the ui isn't initialized yet to + // display the result + if (m_UserInterface != nullptr) { + m_Updater.testForUpdate(m_Settings); } } diff --git a/src/organizercore.h b/src/organizercore.h index a14d79a9..5de550df 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -109,6 +109,7 @@ public: void updateExecutablesList(); + void checkForUpdates(); void startMOUpdate(); Settings &settings(); diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 0ca39b19..8887927a 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -122,8 +122,18 @@ void SelfUpdater::setPluginContainer(PluginContainer *pluginContainer) m_Interface->setPluginContainer(pluginContainer); } -void SelfUpdater::testForUpdate() +void SelfUpdater::testForUpdate(const Settings& settings) { + if (settings.network().offlineMode()) { + log::debug("not checking for updates, in offline mode"); + return; + } + + if (!settings.checkForUpdates()) { + log::debug("not checking for updates, disabled"); + return; + } + // TODO: if prereleases are disabled we could just request the latest release // directly try { diff --git a/src/selfupdater.h b/src/selfupdater.h index bce49495..0c81efc5 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -37,7 +37,7 @@ namespace MOBase { class IPluginGame; } class QNetworkReply; class QProgressDialog; - +class Settings; /** * @brief manages updates for Mod Organizer itself @@ -80,6 +80,11 @@ public: void setPluginContainer(PluginContainer *pluginContainer); + /** + * @brief request information about the current version + **/ + void testForUpdate(const Settings& settings); + /** * @brief start the update process * @note this should not be called if there is no update available @@ -91,13 +96,6 @@ public: **/ MOBase::VersionInfo getVersion() const { return m_MOVersion; } -public slots: - - /** - * @brief request information about the current version - **/ - void testForUpdate(); - signals: /** diff --git a/src/settings.cpp b/src/settings.cpp index c3e8781e..462cd92a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -178,6 +178,16 @@ QString Settings::filename() const return m_Settings.fileName(); } +bool Settings::checkForUpdates() const +{ + return get(m_Settings, "Settings", "check_for_updates", true); +} + +void Settings::setCheckForUpdates(bool b) +{ + set(m_Settings, "Settings", "check_for_updates", b); +} + bool Settings::usePrereleases() const { return get(m_Settings, "Settings", "use_prereleases", false); diff --git a/src/settings.h b/src/settings.h index ee6ff3fe..1556ba1e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -692,6 +692,11 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); + // whether the user wants to check for updates + // + bool checkForUpdates() const; + void setCheckForUpdates(bool b); + // whether the user wants to upgrade to pre-releases // bool usePrereleases() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 9d1e4da1..78bae6d7 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 586 - 486 + 491
@@ -23,75 +23,67 @@ General - - - - - - - Language - - - - - - - The display language - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - - - - - - - - - - - Style - - - - - - - graphical style - - - graphical style of the MO user interface - - - - - - - - - Update to non-stable releases. - - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + + + Qt::Vertical - - Install Pre-releases (Betas) + + + 0 + 0 + - + - + User Interface - - + + + + + Style + + + + + + + Language + + + + + + + The display language + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + + + + + graphical style + + + graphical style of the MO user interface + + + + - + Dialogs will always be centered on the main window, but will remember their size. @@ -102,7 +94,7 @@ p, li { white-space: pre-wrap; } - + @@ -121,7 +113,7 @@ p, li { white-space: pre-wrap; } - + Modify the categories available to arrange your mods. @@ -137,36 +129,49 @@ p, li { white-space: pre-wrap; } - + Download List - + - + - If checked, the download interface will be more compact. + If checked, the download list will display meta information instead of file names. - Compact List + Show Meta Information - + - If checked, the download list will display meta information instead of file names. + If checked, the download interface will be more compact. - Show Meta Information + Compact List + + + + Qt::Vertical + + + + 0 + 0 + + + + - + Colors @@ -240,6 +245,54 @@ p, li { white-space: pre-wrap; }
+ + + + Updates + + + + + + Mod Organizer checks for updates on Github on startup. + + + Mod Organizer checks for updates on Github on startup. + + + Check for updates + + + + + + + Update to non-stable releases. + + + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + Install Pre-releases (Betas) + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + +
@@ -1416,11 +1469,7 @@ programs you are intentionally running. - languageBox - styleBox logLevelBox - usePrereleaseBox - categoriesBtn baseDirEdit browseBaseDirBtn downloadDirEdit diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index ae924393..07aff4a1 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -54,6 +54,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); + ui->checkForUpdates->setChecked(settings().checkForUpdates()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); @@ -95,6 +96,7 @@ void GeneralSettingsTab::update() settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } -- cgit v1.3.1 From b389161077675752b6f551e7a6563c4eb0e0ca80 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 2 Oct 2019 23:06:35 +0200 Subject: Made categories section (filters) resizable via a splitter. Correctly handled hiding and unhiding as well as saving the state between sessions. --- src/mainwindow.cpp | 2 + src/mainwindow.ui | 2215 +++++++++++++++++++++++++-------------------------- src/organizer_en.ts | 857 ++++++++++---------- 3 files changed, 1535 insertions(+), 1539 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42cbe919..91da786c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2139,6 +2139,7 @@ void MainWindow::readSettings(const Settings& settings) settings.geometry().restoreDocks(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); + settings.geometry().restoreState(ui->categoriesSplitter); settings.geometry().restoreVisibility(ui->menuBar); settings.geometry().restoreVisibility(ui->statusBar); @@ -2218,6 +2219,7 @@ void MainWindow::storeSettings(Settings& s) s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); s.geometry().saveState(ui->splitter); + s.geometry().saveState(ui->categoriesSplitter); s.geometry().saveMainWindowMonitor(this); s.geometry().saveVisibility(ui->categoriesGroup); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 02c6dec0..6d36e035 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -33,88 +33,427 @@ Qt::CustomContextMenu - - - 6 - - - 6 - - - 6 - - - 0 - + - - - - + + + Qt::Horizontal + + + + + 500 + 16777215 + + + + Categories + + + + 0 + + + 3 + + + 7 + + + 3 + + + 1 + + + + + + 120 + 0 + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + 0 + + + true + + + false + + + + 1 + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 25 + + + + Clear + + + true + + + - - - Categories + + + + 0 + 0 + - - - 0 + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + true + + + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + Or + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 2 + + + + + + + + 0 + 0 + + + + Profile + + + profileBox + + + + + + + Pick a module collection + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16777215 + 16777215 + + + + Open list options... + + + Refresh list. This is usually not necessary unless you modified data outside the program. + + + + + + + :/MO/gui/settings:/MO/gui/settings + + + + 16 + 16 + + + + + + + + Show Open Folders menu... + + + + + + + :/MO/gui/open_folder:/MO/gui/open_folder + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 5 + + + QLCDNumber::Flat + + + + + + + + + + 330 + 400 + + + + Qt::CustomContextMenu + + + List of available mods. + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows - - 3 + + 20 - - 7 + + true - - 3 + + true - - 1 + + true + + false + + + 35 + + + true + + + false + + + + + - - + + - 120 - 0 + 20 + 16777215 - + + x + + - 214 - 16777215 + 20 + 20 - - Qt::CustomContextMenu + + true - - QAbstractItemView::ExtendedSelection + + + + + + + 0 + 0 + - - 0 + + Filter - - true + + + + + + + 8 + true + - - false - - - - 1 - - - - - false + + + Qt::Horizontal + + + + 40 + 20 + + + + + - + 0 0 @@ -122,587 +461,795 @@ 0 - 25 + 22 + + + + + 95 + 0 + + false + + + Qt::RightToLeft + + + border:1px solid #ff0000; + - Clear + Clear all Filters - - true + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + 12 + 12 + - - - - 0 - 0 - + + + + 220 + 0 + + + + Qt::ClickFocus + + + + No groups + + + + + Categories + + + + + Nexus IDs + + + + + + + + + 220 + 0 + + + + Filter - - - - - If checked, only mods that match all selected categories are displayed. - - - And - - - true - - - - - - - If checked, all mods that match at least one of the selected categories are displayed. - - - Or - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - 2 - - - + + + + + + + + - + - + 0 0 - - Profile + + + 0 + 40 + - - profileBox + + + 9 + 75 + true + - - - - - Pick a module collection + Pick a program to run. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16777215 - 16777215 - - - - Open list options... - - - Refresh list. This is usually not necessary unless you modified data outside the program. - - - - - - - :/MO/gui/settings:/MO/gui/settings - - - - 16 - 16 - - - - - - - - Show Open Folders menu... - - - - - - - :/MO/gui/open_folder:/MO/gui/open_folder - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 5 - - - QLCDNumber::Flat - - - - - - - - - - 330 - 400 - - - - Qt::CustomContextMenu - - - List of available mods. - - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 20 - - - true - - - true - - - true - - - false - - - 35 - - - true - - - false - - - - - - - - - - 20 - 16777215 - - - - x +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - 20 - 20 + 32 + 32 - - true - - - - - - - - 0 - 0 - - - - Filter - - - - - - - - 8 - true - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - - 95 - 0 - - - + false - - Qt::RightToLeft - - - border:1px solid #ff0000; - - - Clear all Filters - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - 12 - 12 - - - - - - 220 - 0 - - - - Qt::ClickFocus - + - - No groups - - - - - Categories - + + + + 0 + 0 + + + + + 120 + 0 + + + + + 16777215 + 16777215 + + + + + 10 + 75 + true + + + + Run program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + + Run + + + + :/MO/gui/run:/MO/gui/run + + + + 36 + 36 + + + - - Nexus IDs - + + + + 0 + 0 + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + + + Shortcut + + + + :/MO/gui/link:/MO/gui/link + + - - - - - - - 220 - 0 - - - - Filter - - + - - -
- - - - - + + + + + + + 340 + 250 + + + + + 16777215 + 16777215 + + + + Qt::NoContextMenu + + + QTabWidget::Rounded + + + 4 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Plugins + + + + 6 + + + 6 + + + 6 + + + 0 + - - - - 0 - 0 - - + + + + + false + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 4 + + + QLCDNumber::Flat + + + + + + + - 0 - 40 + 250 + 250 - - - 9 - 75 - true - + + Qt::CustomContextMenu - Pick a program to run. + List of available esp/esm files <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - 32 - 32 - + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 0 + + + true + + + false + + + true - + false + + false + - + - - - - 0 - 0 - + + + - - - 120 - 0 - + + Filter - - - 16777215 - 16777215 - + + + + + + + + + false + + + Archives + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - 10 - 75 - true - + + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - Run program + + true + + + + + + + + + Qt::CustomContextMenu + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. + + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. + + + false + + + false + + + false + + + 20 + + + true + + + 1 + + + + + + + + Data + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + refresh data-directory overview + + + Refresh the overview. This may take a moment. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + + + + + Qt::CustomContextMenu - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + This is an overview of your data directory as visible to the game (and tools). - - + + true + + + true + + + 400 + + + + File + + + + + Mod + + + + + + + + + + + + Filters the above list so that only conflicts are displayed. + + + Filters the above list so that only conflicts are displayed. - Run + Show only conflicts - - - :/MO/gui/run:/MO/gui/run + + + + + + Filters the above list so that files from archives are not shown - - - 36 - 36 - + + + + + Filters the above list so that files from archives are not shown + + + Show files from Archives + + + + + + + Saves + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::CustomContextMenu + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + Downloads + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Refresh downloads view + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + - - - - 0 - 0 - - + - 140 + 320 0 - - - 16777215 - 16777215 - + + Qt::CustomContextMenu - - - 0 - 0 - + + true - Create a shortcut in your start menu or on the desktop to the specified program + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. + + + Qt::ScrollBarAlwaysOn + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + true + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + false + + + true + + + + + + + + + - Shortcut + Show Hidden - - - :/MO/gui/link:/MO/gui/link + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter @@ -710,578 +1257,12 @@ p, li { white-space: pre-wrap; } - - - - - - 340 - 250 - - - - - 16777215 - 16777215 - - - - Qt::NoContextMenu - - - QTabWidget::Rounded - - - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Plugins - - - - 6 - - - 6 - - - 6 - - - 0 - - - - - - - true - - - Sort - - - - :/MO/gui/sort:/MO/gui/sort - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - 16 - 16 - - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 4 - - - QLCDNumber::Flat - - - - - - - - - - 250 - 250 - - - - Qt::CustomContextMenu - - - List of available esp/esm files - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 0 - - - true - - - false - - - true - - - false - - - false - - - - - - - - - - - - Filter - - - - - - - - - - false - - - Archives - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - - <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - - true - - - - - - - - - Qt::CustomContextMenu - - - List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. - By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! - - BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - - false - - - false - - - false - - - 20 - - - true - - - 1 - - - - - - - - Data - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - refresh data-directory overview - - - Refresh the overview. This may take a moment. - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - - - - - - - - Qt::CustomContextMenu - - - This is an overview of your data directory as visible to the game (and tools). - - - true - - - true - - - 400 - - - - File - - - - - Mod - - - - - - - - - - - - Filters the above list so that only conflicts are displayed. - - - Filters the above list so that only conflicts are displayed. - - - Show only conflicts - - - - - - - Filters the above list so that files from archives are not shown - - - - - - Filters the above list so that files from archives are not shown - - - Show files from Archives - - - - - - - - - - Saves - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - Qt::CustomContextMenu - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - - - - - - Downloads - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Refresh downloads view - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - - - - - - - - - 320 - 0 - - - - Qt::CustomContextMenu - - - true - - - - - - This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - - Qt::ScrollBarAlwaysOn - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ScrollPerPixel - - - 0 - - - false - - - true - - - - - - - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - - - - - - - + + + - - + diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 465b0c87..e3531cb2 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -768,194 +768,204 @@ File %3: %4 - + + Hashing download file '%1' + + + + + Cancel + + + + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1622,48 +1632,48 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - + + Categories - + Clear - + If checked, only mods that match all selected categories are displayed. - + And - + If checked, all mods that match at least one of the selected categories are displayed. - + Or - + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1673,84 +1683,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1760,12 +1770,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1774,17 +1784,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1793,32 +1803,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1827,27 +1837,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1855,72 +1865,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1931,307 +1941,307 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar St&atus bar @@ -2280,8 +2290,8 @@ Error: %1 - - + + Endorse @@ -2401,700 +2411,700 @@ Error: %1 - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3102,12 +3112,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3115,12 +3125,12 @@ You can also use online editors and converters instead. - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3128,336 +3138,336 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3662,17 +3672,20 @@ Most mods do not have optional esps, so chances are good you are looking at an e - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of MO. + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Make the selected mod in the lower list unavailable. + Make the selected mod in the right list unavailable. + Make the selected mod in the lower list unavailable. - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + The selected esp (in the right list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. @@ -5767,7 +5780,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From c4dd23abb7a37531040d6348c491dc868919013c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 09:40:37 -0400 Subject: added error messages to FileRenamer and a few more fixes for shell functions changing names --- src/downloadmanager.cpp | 10 +++++----- src/filerenamer.cpp | 42 +++++++++++++++++++++++++++++------------- src/filerenamer.h | 8 ++++++-- src/mainwindow.cpp | 36 ++++++++++++++++++------------------ src/modinfodialogconflicts.cpp | 2 +- src/modinfodialogfiletree.cpp | 8 ++++---- src/modinfodialogimages.cpp | 2 +- src/modinfodialognexus.cpp | 6 +++--- src/motddialog.cpp | 2 +- src/nxmaccessmanager.cpp | 2 +- src/overwriteinfodialog.cpp | 4 ++-- src/problemsdialog.cpp | 2 +- src/selfupdater.cpp | 7 +++++-- src/settings.cpp | 6 ++++-- src/settingsdialognexus.cpp | 2 +- src/texteditor.cpp | 2 +- 16 files changed, 83 insertions(+), 58 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 35f60d7a..b4a7b57d 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1059,11 +1059,11 @@ void DownloadManager::openFile(int index) QDir path = QDir(m_OutputDirectory); if (path.exists(getFileName(index))) { - shell::OpenFile(getFilePath(index)); + shell::Open(getFilePath(index)); return; } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); return; } @@ -1077,18 +1077,18 @@ void DownloadManager::openInDownloadsFolder(int index) const auto path = getFilePath(index); if (QFile::exists(path)) { - shell::ExploreFile(path); + shell::Explore(path); return; } else { const auto unfinished = path + ".unfinished"; if (QFile::exists(unfinished)) { - shell::ExploreFile(unfinished); + shell::Explore(unfinished); return; } } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); } diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index a97d7742..7fc90eb2 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,4 +1,5 @@ #include "filerenamer.h" +#include #include #include #include @@ -37,10 +38,13 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt log::debug("removing {}", newName); // user wants to replace the file, so remove it - if (!QFile(newName).remove()) { - log::warn("failed to remove '{}'", newName); + const auto r = shell::Delete(newName); + + if (!r.success()) { + log::error("failed to remove '{}': {}", newName, r.toString()); + // removal failed, warn the user and allow canceling - if (!removeFailed(newName)) { + if (!removeFailed(newName, r)) { log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; @@ -64,12 +68,15 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt } // target either didn't exist or was removed correctly + const auto r = shell::Rename(oldName, newName); - if (!QFile::rename(oldName, newName)) { - log::warn("failed to rename '{}' to '{}'", oldName, newName); + if (!r.success()) { + log::error( + "failed to rename '{}' to '{}': {}", + oldName, newName, r.toString()); // renaming failed, warn the user and allow canceling - if (!renameFailed(oldName, newName)) { + if (!renameFailed(oldName, newName, r)) { // user wants to cancel log::debug("canceling"); return RESULT_CANCEL; @@ -144,7 +151,7 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) } } -bool FileRenamer::removeFailed(const QString& name) +bool FileRenamer::removeFailed(const QString& name, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -153,8 +160,9 @@ bool FileRenamer::removeFailed(const QString& name) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + m_parent, + QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\": %2").arg(name).arg(r.toString()), buttons); if (answer == QMessageBox::Cancel) { @@ -168,7 +176,8 @@ bool FileRenamer::removeFailed(const QString& name) return true; } -bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +bool FileRenamer::renameFailed( + const QString& oldName, const QString& newName, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -177,9 +186,16 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), - buttons); + m_parent, + QObject::tr("File operation failed"), + QObject::tr( + "Failed to rename file: %1.\r\n\r\n" + "Source:\r\n\"%2\"\r\n\r\n" + "Destination:\r\n\"%3\"") + .arg(r.toString()) + .arg(QDir::toNativeSeparators(oldName)) + .arg(QDir::toNativeSeparators(newName)), + buttons); if (answer == QMessageBox::Cancel) { // user wants to stop diff --git a/src/filerenamer.h b/src/filerenamer.h index cd57244c..5583ecbd 100644 --- a/src/filerenamer.h +++ b/src/filerenamer.h @@ -3,6 +3,8 @@ #include +namespace MOBase::shell { class Result; } + /** * Renames individual files and handles dialog boxes to confirm replacements and * failures with the user @@ -126,7 +128,7 @@ private: * @param name The name of the file that failed to be removed * @return true to continue, false to stop **/ - bool removeFailed(const QString& name); + bool removeFailed(const QString& name, const MOBase::shell::Result& r); /** * renaming a file failed, ask the user to continue or cancel @@ -134,7 +136,9 @@ private: * @param newName new filename * @return true to continue, false to stop **/ - bool renameFailed(const QString& oldName, const QString& newName); + bool renameFailed( + const QString& oldName, const QString& newName, + const MOBase::shell::Result& r); }; #endif // FILERENAMER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..b29ae11c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3313,12 +3313,12 @@ void MainWindow::openExplorer_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - shell::ExploreFile(info->absolutePath()); + shell::Explore(info->absolutePath()); } } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3333,14 +3333,14 @@ void MainWindow::openPluginOriginExplorer_clicked() continue; } ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } else { QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3355,7 +3355,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3376,7 +3376,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } } @@ -4344,61 +4344,61 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); - shell::ExploreFile(dataPath); + shell::Explore(dataPath); } void MainWindow::openLogsFolder() { QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - shell::ExploreFile(logsPath); + shell::Explore(logsPath); } void MainWindow::openInstallFolder() { - shell::ExploreFile(qApp->applicationDirPath()); + shell::Explore(qApp->applicationDirPath()); } void MainWindow::openPluginsFolder() { QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - shell::ExploreFile(pluginsPath); + shell::Explore(pluginsPath); } void MainWindow::openProfileFolder() { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } void MainWindow::openIniFolder() { if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } else { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } } void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); + shell::Explore(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); + shell::Explore(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->gameDirectory()); } void MainWindow::openMyGamesFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } @@ -5391,7 +5391,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); log::debug("opening in explorer: {}", fullPath); - shell::ExploreFile(fullPath); + shell::Explore(fullPath); } void MainWindow::updateAvailable() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 3a71b405..36559a75 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -547,7 +547,7 @@ void ConflictsTab::exploreItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - shell::ExploreFile(item->fileName()); + shell::Explore(item->fileName()); return true; }); } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 207c792d..71ea9210 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -128,7 +128,7 @@ void FileTreeTab::onOpen() return; } - shell::OpenFile(m_fs->filePath(selection)); + shell::Open(m_fs->filePath(selection)); } void FileTreeTab::onPreview() @@ -146,9 +146,9 @@ void FileTreeTab::onExplore() auto selection = singleSelection(); if (selection.isValid()) { - shell::ExploreFile(m_fs->filePath(selection)); + shell::Explore(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } } @@ -204,7 +204,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9d347f57..c5b04538 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -547,7 +547,7 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { if (auto* f=m_files.selectedFile()) { - MOBase::shell::ExploreFile(f->path()); + shell::Explore(f->path()); } } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 95e62328..59bfe930 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -95,7 +95,7 @@ void NexusTab::update() connect( page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ shell::OpenLink(url); }); + [&](const QUrl& url){ shell::Open(url); }); ui->endorse->setEnabled( (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || @@ -363,7 +363,7 @@ void NexusTab::onVisitNexus() const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod().getGameName()); - shell::OpenLink(QUrl(nexusLink)); + shell::Open(QUrl(nexusLink)); } } @@ -412,6 +412,6 @@ void NexusTab::onVisitCustomURL() { const auto url = mod().parseCustomURL(); if (url.isValid()) { - shell::OpenLink(url); + shell::Open(url); } } diff --git a/src/motddialog.cpp b/src/motddialog.cpp index ca1e60ad..eee80205 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -47,5 +47,5 @@ void MotDDialog::on_okButton_clicked() void MotDDialog::linkClicked(const QUrl &url) { - shell::OpenLink(url); + shell::Open(url); } diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c6ef7bc7..3cc1b7d9 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -286,7 +286,7 @@ void NexusSSOLogin::onMessage(const QString& s) // open browser const auto url = NexusSSOPage.arg(m_guid); - shell::OpenLink(url); + shell::Open(url); m_timeout.stop(); setState(WaitingForBrowser); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index fe1d8825..078bcfc9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -229,7 +229,7 @@ void OverwriteInfoDialog::renameTriggered() void OverwriteInfoDialog::openFile(const QModelIndex &index) { - shell::OpenFile(m_FileSystemModel->filePath(index)); + shell::Open(m_FileSystemModel->filePath(index)); } @@ -270,7 +270,7 @@ void OverwriteInfoDialog::createDirectoryTriggered() void OverwriteInfoDialog::on_explorerButton_clicked() { - shell::ExploreFile(m_ModInfo->absolutePath()); + shell::Explore(m_ModInfo->absolutePath()); } void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 63d58295..ea23beec 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -112,5 +112,5 @@ void ProblemsDialog::startFix() void ProblemsDialog::urlClicked(const QUrl &url) { - shell::OpenLink(url); + shell::Open(url); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 8887927a..5a70568e 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -341,11 +341,14 @@ void SelfUpdater::downloadCancel() void SelfUpdater::installUpdate() { const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; + const auto r = shell::Execute(m_UpdateFile.fileName(), parameters); - if (shell::Execute(m_UpdateFile.fileName(), parameters)) { + if (r.success()) { QCoreApplication::quit(); } else { - reportError(tr("Failed to start %1").arg(m_UpdateFile.fileName())); + reportError(tr("Failed to start %1: %2") + .arg(m_UpdateFile.fileName()) + .arg(r.toString())); } m_UpdateFile.remove(); diff --git a/src/settings.cpp b/src/settings.cpp index 462cd92a..15bc801a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1808,10 +1808,12 @@ void NexusSettings::registerAsNXMHandler(bool force) } parameters += " \"" + executable + "\""; - if (!shell::Execute(nxmPath, parameters)) { + const auto r = shell::Execute(nxmPath, parameters); + + if (!r.success()) { QMessageBox::critical( nullptr, QObject::tr("Failed"), - QObject::tr("Failed to start the helper application")); + QObject::tr("Failed to start the helper application: %1").arg(r.toString())); } } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 826075c0..2021bdc1 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -49,7 +49,7 @@ public: void openBrowser() { - shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + shell::Open(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); } void paste() diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 0c0eb1cc..4a8080f4 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -199,7 +199,7 @@ void TextEditor::explore() return; } - MOBase::shell::ExploreFile(m_filename); + shell::Explore(m_filename); } void TextEditor::onModified(bool b) -- cgit v1.3.1 From 49e19c8185eb890b6dc6788bf95dd65455e72538 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 4 Oct 2019 17:18:06 +0200 Subject: Added "No valid game data" and "No Nexus ID" filters as per #295 --- src/categories.h | 2 ++ src/mainwindow.cpp | 2 ++ src/modlistsortproxy.cpp | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.h b/src/categories.h index 48e0b44b..67fee3e7 100644 --- a/src/categories.h +++ b/src/categories.h @@ -50,6 +50,8 @@ public: static const int CATEGORY_SPECIAL_BACKUP = 10006; static const int CATEGORY_SPECIAL_MANAGED = 10007; static const int CATEGORY_SPECIAL_UNMANAGED = 10008; + static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009; + static const int CATEGORY_SPECIAL_NONEXUSID = 10010; public: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..a53c081b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2691,6 +2691,8 @@ void MainWindow::refreshFilters() addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); addContentFilters(); std::set categoriesUsed; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 77ffad96..a9ff6463 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -308,6 +308,15 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; } break; + case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { + if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { + if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && + !info->hasFlag(ModInfo::FLAG_SEPARATOR) && + !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false; + } break; default: { if (!info->categorySet(*iter)) return false; } break; @@ -353,6 +362,15 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; } break; + case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { + if (info->hasFlag(ModInfo::FLAG_INVALID)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { + if ((info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && + !info->hasFlag(ModInfo::FLAG_SEPARATOR) && + !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return true; + } break; default: { if (info->categorySet(*iter)) return true; } break; -- cgit v1.3.1 From c4d96ed8cfee5c66089f9bdf7c0ce8567aaf46fe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:30:58 -0400 Subject: check the api setting on startup to hide it if needed --- src/mainwindow.cpp | 2 +- src/statusbar.cpp | 4 +++- src/statusbar.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..0a82fcaf 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -226,7 +226,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); - ui->statusBar->setup(ui); + ui->statusBar->setup(ui, settings); { auto* ni = NexusInterface::instance(&m_PluginContainer); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 3734aa87..c2c54862 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -9,7 +9,7 @@ StatusBar::StatusBar(QWidget* parent) : { } -void StatusBar::setup(Ui::MainWindow* mainWindowUI) +void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) { ui = mainWindowUI; m_notifications = new StatusBarAction(ui->actionNotifications); @@ -51,6 +51,8 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI) clearMessage(); setProgress(-1); setAPI({}, {}); + + checkSettings(settings); } void StatusBar::setProgress(int percent) diff --git a/src/statusbar.h b/src/statusbar.h index 442b9acf..3da45d41 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -34,7 +34,7 @@ class StatusBar : public QStatusBar public: StatusBar(QWidget* parent=nullptr); - void setup(Ui::MainWindow* ui); + void setup(Ui::MainWindow* ui, const Settings& settings); void setProgress(int percent); void setNotifications(bool hasNotifications); -- cgit v1.3.1 From c28d435ed41f9e693a0d7f09482a86b963fae6ff Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 14:17:04 -0400 Subject: showEvent() is called for stuff like minimizing and restoring the window, don't re-read settings for that --- src/mainwindow.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b977dd6..76109e00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1208,13 +1208,12 @@ void MainWindow::hookUpWindowTutorials() void MainWindow::showEvent(QShowEvent *event) { - readSettings(m_OrganizerCore.settings()); - - refreshFilters(); - QMainWindow::showEvent(event); if (!m_WasVisible) { + readSettings(m_OrganizerCore.settings()); + refreshFilters(); + // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which // connects its signal in runApplication() (in main.cpp), and that happens -- cgit v1.3.1 From ef993a693c9ca5acf60daac3506411ef6b81bb89 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 14:36:07 -0400 Subject: stop reusing the view menu, just create a new context menu with the right stuff in it --- src/mainwindow.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b977dd6..c6f2ee5d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -787,15 +787,6 @@ void MainWindow::updatePinnedExecutables() void MainWindow::updateToolbarMenu() { - // well, this is a bit of a hack to allow the same toolbar menu to be shown - // in both the main menu and the context menu - // - // the toolbar menu is returned by createPopupMenu(), but Qt takes ownership - // of it and deletes it by setting the WA_DeleteOnClose attribute on it - // - // to avoid deleting the menu, the attribute is removed here - ui->menuToolbars->setAttribute(Qt::WA_DeleteOnClose, false); - ui->actionMainMenuToggle->setChecked(ui->menuBar->isVisible()); ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); ui->actionStatusBarToggle->setChecked(ui->statusBar->isVisible()); @@ -816,7 +807,23 @@ void MainWindow::updateViewMenu() QMenu* MainWindow::createPopupMenu() { - return ui->menuToolbars; + auto* m = new QMenu; + + // add all the actions from the toolbars menu + for (auto* a : ui->menuToolbars->actions()) { + m->addAction(a); + } + + m->addSeparator(); + + // other actions + m->addAction(ui->actionViewLog); + + // make sure the actions are updated + updateToolbarMenu(); + updateViewMenu(); + + return m; } void MainWindow::on_actionMainMenuToggle_triggered() @@ -900,8 +907,7 @@ void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) return; } - auto* m = createPopupMenu(); - m->exec(ui->centralWidget->mapToGlobal(pos)); + createPopupMenu()->exec(ui->centralWidget->mapToGlobal(pos)); } void MainWindow::scheduleUpdateButton() -- cgit v1.3.1 From 3d97b0efd04326c0252411fc6639c4c6df2f2160 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 04:27:53 -0400 Subject: added ExitModOrganizer(), used instead of qApp->exit() reset geometry uses TaskDialog moved restart code for the settings dialog to MainWindow fixed settings sometimes not being saved when restarting don't log "crash dumps present" every time the settings dialog is closed --- src/main.cpp | 12 +++- src/mainwindow.cpp | 113 +++++++++++++++++++++++++------------- src/mainwindow.h | 11 ++-- src/settingsdialog.cpp | 21 +++---- src/settingsdialog.h | 9 ++- src/settingsdialognexus.cpp | 4 +- src/settingsdialogworkarounds.cpp | 23 ++++---- src/shared/util.cpp | 47 ++++++++++++++++ src/shared/util.h | 18 ++++++ src/spawn.cpp | 12 ++-- 10 files changed, 189 insertions(+), 81 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index a6918d99..f08ba066 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -563,10 +563,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); + if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { instance.clearCurrentInstance(); - return INT_MAX; + return RestartExitCode; } + return 1; } @@ -710,6 +712,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); + mainWindow.onBeforeClose(); + mainWindow.close(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); @@ -867,6 +871,7 @@ int main(int argc, char *argv[]) do { LogModel::instance().clear(); + ResetExitFlag(); // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted @@ -904,10 +909,11 @@ int main(int argc, char *argv[]) splash = ":/MO/gui/splash"; } - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { + const int result = runApplication(application, instance, splash); + if (result != RestartExitCode) { return result; } + argc = 1; moshortcut = MOShortcut(""); } while (true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..23733cac 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,7 +1217,7 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { - readSettings(m_OrganizerCore.settings()); + readSettings(); refreshFilters(); // this needs to be connected here instead of in the constructor because the @@ -1267,26 +1267,44 @@ void MainWindow::showEvent(QShowEvent *event) } } +void MainWindow::onBeforeClose() +{ + storeSettings(); +} void MainWindow::closeEvent(QCloseEvent* event) { - if (!confirmExit()) { + // this happens for two reasons: + // 1) the user requested to close the window, such as clicking the X + // 2) close() is called in runApplication() after application.exec() + // returns, which happens when qApp->exit() is called + // + // the window must never actually close for 1), because settings haven't been + // saved yet: the state of many widgets is saved to the ini, which relies on + // the window still being onscreen (or else everything is considered hidden) + // + // for 2), the settings have been saved and the window can just close + + if (ModOrganizerExiting()) { + // the user has confirmed if necessary and all settings have been saved, + // just close it + QMainWindow::closeEvent(event); + } else { + // never close the window because settings might need to be changed event->ignore(); - return; - } - storeSettings(m_OrganizerCore.settings()); + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); + } } -bool MainWindow::confirmExit() +bool MainWindow::canExit() { - m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { - m_closing = false; return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); @@ -1298,8 +1316,9 @@ bool MainWindow::confirmExit() HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); if (injected_process_still_running != INVALID_HANDLE_VALUE) { + m_exitAfterWait = true; m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_closing) { // if operation cancelled + if (!m_exitAfterWait) { // if operation cancelled return false; } } @@ -2138,20 +2157,22 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings(const Settings& settings) +void MainWindow::readSettings() { - settings.geometry().restoreGeometry(this); - settings.geometry().restoreState(this); - settings.geometry().restoreDocks(this); - settings.geometry().restoreToolbars(this); - settings.geometry().restoreState(ui->splitter); - settings.geometry().restoreState(ui->categoriesSplitter); - settings.geometry().restoreVisibility(ui->menuBar); - settings.geometry().restoreVisibility(ui->statusBar); + const auto& s = m_OrganizerCore.settings(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(this); + s.geometry().restoreDocks(this); + s.geometry().restoreToolbars(this); + s.geometry().restoreState(ui->splitter); + s.geometry().restoreState(ui->categoriesSplitter); + s.geometry().restoreVisibility(ui->menuBar); + s.geometry().restoreVisibility(ui->statusBar); { // special case in case someone puts 0 in the INI - auto v = settings.widgets().index(ui->executablesListBox); + auto v = s.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2159,16 +2180,16 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.widgets().restoreIndex(ui->groupCombo); + s.widgets().restoreIndex(ui->groupCombo); { - settings.geometry().restoreVisibility(ui->categoriesGroup, false); + s.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } - if (settings.network().useProxy()) { + if (s.network().useProxy()) { activateProxy(true); } } @@ -2215,8 +2236,10 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) +void MainWindow::storeSettings() { + auto& s = m_OrganizerCore.settings(); + s.geometry().saveState(this); s.geometry().saveGeometry(this); s.geometry().saveDocks(this); @@ -2244,7 +2267,7 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - if (m_closing) + if (m_exitAfterWait) m_LockDialog = new WaitingOnCloseDialog(this); else m_LockDialog = new LockedDialog(this, true); @@ -2265,8 +2288,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { - if (m_closing && m_LockDialog->canceled()) - m_closing = false; + if (m_exitAfterWait && m_LockDialog->canceled()) + m_exitAfterWait = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; @@ -5018,18 +5041,31 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); + const int oldMaxDumps = settings.diagnostics().crashDumpsMax(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); + auto e = dialog.exitNeeded(); + if (oldManagedGameDirectory != settings.game().directory()) { - QMessageBox::about(this, tr("Restarting MO"), - tr("Changing the managed game directory requires restarting MO.\n" - "Any pending downloads will be paused.\n\n" - "Click OK to restart MO now.")); - dlManager->pauseAll(); - qApp->exit(INT_MAX); + e |= Exit::Restart; + } + + if (e.testFlag(Exit::Restart)) { + const auto r = MOBase::TaskDialog(this) + .title(tr("Restart Mod Organizer")) + .main("Restart Mod Organizer") + .content(tr("Mod Organizer must restart to finish configuration changes")) + .icon(QMessageBox::Question) + .button({tr("Restart"), QMessageBox::Yes}) + .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) + .exec(); + + if (r == QMessageBox::Yes) { + ExitModOrganizer(e); + } } InstallationManager *instManager = m_OrganizerCore.installationManager(); @@ -5092,7 +5128,10 @@ void MainWindow::on_actionSettings_triggered() updateDownloadView(); m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); - m_OrganizerCore.cycleDiagnostics(); + + if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) { + m_OrganizerCore.cycleDiagnostics(); + } toggleMO2EndorseState(); @@ -5475,9 +5514,7 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionExit_triggered() { - if (confirmExit()) { - qApp->exit(); - } + ExitModOrganizer(); } void MainWindow::actionEndorseMO() @@ -6121,7 +6158,7 @@ void MainWindow::on_actionChange_Game_triggered() if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..b04096be 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -111,7 +111,6 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy; public: - explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); @@ -154,7 +153,8 @@ public: void displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; - bool confirmExit(); + bool canExit(); + void onBeforeClose(); virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); @@ -383,9 +383,8 @@ private: LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; - bool m_closing{ false }; - bool m_showArchiveData{ true }; + bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; @@ -672,8 +671,8 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); - void storeSettings(Settings& settings); - void readSettings(const Settings& settings); + void storeSettings(); + void readSettings(); void setupModList(); }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8fb25b1c..daccfba9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) + , m_exit(Exit::None) , m_pluginContainer(pluginContainer) - , m_restartNeeded(false) { ui->setupUi(this); @@ -61,9 +61,14 @@ QWidget* SettingsDialog::parentWidgetForDialogs() } } -void SettingsDialog::setRestartNeeded() +void SettingsDialog::setExitNeeded(ExitFlags e) { - m_restartNeeded = true; + m_exit = e; +} + +ExitFlags SettingsDialog::exitNeeded() const +{ + return m_exit; } int SettingsDialog::exec() @@ -87,16 +92,6 @@ int SettingsDialog::exec() } } - if (m_restartNeeded) { - if (QMessageBox::question(parentWidgetForDialogs(), - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - return ret; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e89da665..bae4a469 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" +#include "util.h" class PluginContainer; class Settings; @@ -74,7 +75,9 @@ public: PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); - void setRestartNeeded(); + + void setExitNeeded(ExitFlags e); + ExitFlags exitNeeded() const; int exec() override; @@ -82,10 +85,10 @@ public slots: virtual void accept(); private: + Ui::SettingsDialog* ui; Settings& m_settings; std::vector> m_tabs; - Ui::SettingsDialog* ui; - bool m_restartNeeded; + ExitFlags m_exit; PluginContainer* m_pluginContainer; }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2dd8b998..d49e0a33 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 0e31fc4b..1c5fbe26 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -2,6 +2,7 @@ #include "ui_settingsdialog.h" #include "spawn.h" #include "settings.h" +#include #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -118,16 +119,18 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - const auto caption = QObject::tr("Restart Mod Organizer?"); - const auto text = QObject::tr( - "In order to reset the geometry, Mod Organizer must be restarted.\n" - "Restart now?"); - - const auto res = QMessageBox::question( - parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); - - if (res == QMessageBox::Yes) { + const auto r = MOBase::TaskDialog(parentWidget()) + .title(QObject::tr("Restart Mod Organizer")) + .main(QObject::tr("Restart Mod Organizer")) + .content(QObject::tr("Geometries will be reset to their default values.")) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Ok) { settings().geometry().requestReset(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); + dialog().close(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 07983e12..58f4eb2b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" +#include "mainwindow.h" namespace MOShared { @@ -244,3 +245,49 @@ MOBase::VersionInfo createVersionInfo() } } // namespace MOShared + + +static bool g_exiting = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw=dynamic_cast(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + if (!e.testFlag(Exit::Force)) { + if (auto* mw=findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_exiting = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} diff --git a/src/shared/util.h b/src/shared/util.h index 7bae96f2..aea6f200 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -48,4 +48,22 @@ MOBase::VersionInfo createVersionInfo(); } // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; + +using ExitFlags = QFlags; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e=Exit::Normal); +bool ModOrganizerExiting(); +void ResetExitFlag(); + #endif // UTIL_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 079677f4..a34230b2 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -186,12 +186,12 @@ QMessageBox::StandardButton badSteamReg( .details(details) .icon(QMessageBox::Critical) .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); } @@ -517,7 +517,7 @@ bool restartAsAdmin(QWidget* parent) } log::debug("exiting MO"); - qApp->exit(0); + ExitModOrganizer(Exit::Force); return true; } -- cgit v1.3.1 From 52db7e8c7ddd39609a8eecbf44fa15ec6369f843 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:20:08 -0400 Subject: initial selection in the edit executables dialog from main window --- src/editexecutablesdialog.cpp | 6 +++++- src/editexecutablesdialog.h | 3 ++- src/mainwindow.cpp | 9 +++++---- src/mainwindow.h | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 41392712..19b57e9a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -55,7 +55,7 @@ private: }; -EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) +EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget* parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) , m_organizerCore(oc) @@ -75,6 +75,10 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) fillList(); setDirty(false); + if (sel >= 0 && sel < ui->list->count()) { + ui->list->item(sel)->setSelected(true); + } + auto* m = new QMenu; m->addAction(tr("Add from file..."), [&]{ addFromFile(); }); m->addAction(tr("Add empty"), [&]{ addEmpty(); }); diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 2e52c722..949aff66 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -148,7 +148,8 @@ public: using CustomOverwrites = ToggableMap; using ForcedLibraries = ToggableMap>; - explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr); + explicit EditExecutablesDialog( + OrganizerCore& oc, int selection=-1, QWidget* parent=nullptr); ~EditExecutablesDialog(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..ba02267a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2345,12 +2345,12 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(true); } -bool MainWindow::modifyExecutablesDialog() +bool MainWindow::modifyExecutablesDialog(int selection) { bool result = false; try { - EditExecutablesDialog dialog(m_OrganizerCore, this); + EditExecutablesDialog dialog(m_OrganizerCore, selection, this); result = (dialog.exec() == QDialog::Accepted); @@ -2375,7 +2375,7 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) m_OldExecutableIndex = index; if (index == 0) { - modifyExecutablesDialog(); + modifyExecutablesDialog(previousIndex - 1); ui->executablesListBox->setCurrentIndex(previousIndex); } } @@ -2451,7 +2451,8 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { - if (modifyExecutablesDialog()) { + const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); + if (modifyExecutablesDialog(sel)) { ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..fa6a81cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -235,7 +235,7 @@ private: QList findFileInfos(const QString &path, const std::function &filter) const; - bool modifyExecutablesDialog(); + bool modifyExecutablesDialog(int selection); void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); void testExtractBSA(int modIndex); -- cgit v1.3.1 From 21f55da7d3688eafc9e2dbae4535cf0c554be121 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 23:03:45 -0400 Subject: added hide flag to executables --- src/editexecutablesdialog.cpp | 11 ++++ src/editexecutablesdialog.ui | 21 ++++-- src/executableslist.cpp | 16 ++++- src/executableslist.h | 6 +- src/mainwindow.cpp | 145 ++++++++++++++++++++++++++++++------------ src/mainwindow.h | 3 +- 6 files changed, 149 insertions(+), 53 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index b58531e5..3efadc4c 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -97,6 +97,7 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); + connect(ui->hide, &QCheckBox::toggled, [&]{ save(); }); connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&]{ saveOrder(); }); } @@ -316,6 +317,8 @@ void EditExecutablesDialog::clearEdits() ui->configureLibraries->setEnabled(false); ui->useApplicationIcon->setEnabled(false); ui->useApplicationIcon->setChecked(false); + ui->hide->setEnabled(false); + ui->hide->setChecked(false); m_lastGoodTitle = ""; } @@ -330,6 +333,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->steamAppID->setEnabled(!e.steamAppID().isEmpty()); ui->steamAppID->setText(e.steamAppID()); ui->useApplicationIcon->setChecked(e.usesOwnIcon()); + ui->hide->setChecked(e.hide()); m_lastGoodTitle = e.title(); @@ -375,6 +379,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->useApplicationIcon->setEnabled(true); ui->createFilesInMod->setEnabled(true); ui->forceLoadLibraries->setEnabled(true); + ui->hide->setEnabled(true); } void EditExecutablesDialog::save() @@ -434,6 +439,12 @@ void EditExecutablesDialog::save() e->flags(e->flags() & (~Executable::UseApplicationIcon)); } + if (ui->hide->isChecked()) { + e->flags(e->flags() | Executable::Hide); + } else { + e->flags(e->flags() & (~Executable::Hide)); + } + setDirty(true); } diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 5f7fee09..6f50b3b3 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -378,7 +378,7 @@ Right now the only case I know of where this needs to be overwritten is for the If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - Create Files in Mod instead of Overwrite (*) + Create files in mod instead of overwrite (*) @@ -399,7 +399,7 @@ Right now the only case I know of where this needs to be overwritten is for the If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - Force Load Libraries (*) + Force load libraries (*) @@ -431,14 +431,27 @@ Right now the only case I know of where this needs to be overwritten is for the - Use Application's Icon for desktop shortcuts + Use application's icon for desktop shortcuts + + + + + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + Hide in user interface - (*) Profile Specific + (*) Profile specific 5 diff --git a/src/executableslist.cpp b/src/executableslist.cpp index dce9181b..1be34b53 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -84,6 +84,9 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) if (map["ownicon"].toBool()) flags |= Executable::UseApplicationIcon; + if (map["hide"].toBool()) + flags |= Executable::Hide; + if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; @@ -116,6 +119,7 @@ void ExecutablesList::store(Settings& s) map["title"] = item.title(); map["toolbar"] = item.isShownOnToolbar(); map["ownicon"] = item.usesOwnIcon(); + map["hide"] = item.hide(); map["binary"] = item.binaryInfo().absoluteFilePath(); map["arguments"] = item.arguments(); map["workingDirectory"] = item.workingDirectory(); @@ -343,6 +347,10 @@ void ExecutablesList::dump() const flags.push_back("icon"); } + if (e.flags() & Executable::Hide) { + flags.push_back("hide"); + } + log::debug( " . executable '{}'\n" " binary: {}\n" @@ -456,11 +464,13 @@ bool Executable::usesOwnIcon() const return m_flags.testFlag(UseApplicationIcon); } -void Executable::mergeFrom(const Executable& other) +bool Executable::hide() const { - // flags on plugin executables that the user is allowed to change - const auto allow = ShowInToolbar; + return m_flags.testFlag(Hide); +} +void Executable::mergeFrom(const Executable& other) +{ // this happens after executables are loaded from settings and plugin // executables are being added, or when users are modifying executables diff --git a/src/executableslist.h b/src/executableslist.h index 23cf3cfe..a18042db 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -39,8 +39,9 @@ class Executable public: enum Flag { - ShowInToolbar = 0x02, - UseApplicationIcon = 0x04 + ShowInToolbar = 0x02, + UseApplicationIcon = 0x04, + Hide = 0x08 }; Q_DECLARE_FLAGS(Flags, Flag); @@ -69,6 +70,7 @@ public: bool isShownOnToolbar() const; void setShownOnToolbar(bool state); bool usesOwnIcon() const; + bool hide() const; void mergeFrom(const Executable& other); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ba02267a..946caf08 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -757,7 +757,7 @@ void MainWindow::updatePinnedExecutables() bool hasLinks = false; for (const auto& exe : *m_OrganizerCore.executablesList()) { - if (exe.isShownOnToolbar()) { + if (!exe.hide() && exe.isShownOnToolbar()) { hasLinks = true; QAction *exeAction = new QAction( @@ -1792,23 +1792,44 @@ bool MainWindow::refreshProfiles(bool selectProfile) void MainWindow::refreshExecutablesList() { - QComboBox* executablesList = findChild("executablesListBox"); - executablesList->setEnabled(false); - executablesList->clear(); - executablesList->addItem(tr("")); + QAbstractItemModel *model = ui->executablesListBox->model(); + + auto add = [&](const QString& title, const QFileInfo& binary) { + QIcon icon; + if (!binary.fileName().isEmpty()) { + icon = iconForExecutable(binary.filePath()); + } + + ui->executablesListBox->addItem(icon, title); + + const auto i = ui->executablesListBox->count() - 1; + + model->setData( + model->index(i, 0), + QSize(0, ui->executablesListBox->iconSize().height() + 4), + Qt::SizeHintRole); + }; - QAbstractItemModel *model = executablesList->model(); - int i = 0; + ui->executablesListBox->setEnabled(false); + ui->executablesListBox->clear(); + + add(tr(""), {}); + for (const auto& exe : *m_OrganizerCore.executablesList()) { - QIcon icon = iconForExecutable(exe.binaryInfo().filePath()); - executablesList->addItem(icon, exe.title()); - model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); - ++i; + if (!exe.hide()) { + add(exe.title(), exe.binaryInfo()); + } + } + + if (ui->executablesListBox->count() == 1) { + // all executables are hidden, add an empty one to at least be able to + // switch to edit + add(tr("(no executables)"), QFileInfo(":badfile")); } ui->executablesListBox->setCurrentIndex(1); - executablesList->setEnabled(true); + ui->executablesListBox->setEnabled(true); } @@ -2321,27 +2342,42 @@ void MainWindow::installMod(QString fileName) } } -void MainWindow::on_startButton_clicked() { - ui->startButton->setEnabled(false); +void MainWindow::on_startButton_clicked() +{ try { - const Executable &selectedExecutable(getSelectedExecutable()); - QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.title()).toString(); - auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.title()); - if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.title())) { + const Executable* selectedExecutable = getSelectedExecutable(); + if (!selectedExecutable) { + return; + } + + ui->startButton->setEnabled(false); + + auto* profile = m_OrganizerCore.currentProfile(); + + const QString customOverwrite = profile->setting( + "custom_overwrites", selectedExecutable->title()).toString(); + + auto forcedLibraries = profile->determineForcedLibraries( + selectedExecutable->title()); + + if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) { forcedLibraries.clear(); } + m_OrganizerCore.spawnBinary( - selectedExecutable.binaryInfo(), selectedExecutable.arguments(), - selectedExecutable.workingDirectory().length() != 0 - ? selectedExecutable.workingDirectory() - : selectedExecutable.binaryInfo().absolutePath(), - selectedExecutable.steamAppID(), - customOverwrite, - forcedLibraries); + selectedExecutable->binaryInfo(), + selectedExecutable->arguments(), + selectedExecutable->workingDirectory().length() != 0 ? + selectedExecutable->workingDirectory() : + selectedExecutable->binaryInfo().absolutePath(), + selectedExecutable->steamAppID(), + customOverwrite, + forcedLibraries); } catch (...) { ui->startButton->setEnabled(true); throw; } + ui->startButton->setEnabled(true); } @@ -2376,7 +2412,13 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) if (index == 0) { modifyExecutablesDialog(previousIndex - 1); - ui->executablesListBox->setCurrentIndex(previousIndex); + const auto newCount = ui->executablesListBox->count(); + + if (previousIndex >= 0 && previousIndex < newCount) { + ui->executablesListBox->setCurrentIndex(previousIndex); + } else { + ui->executablesListBox->setCurrentIndex(newCount - 1); + } } } @@ -2452,8 +2494,14 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); + if (modifyExecutablesDialog(sel)) { - ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); + const auto newCount = ui->executablesListBox->count(); + if (m_OldExecutableIndex >= 0 && m_OldExecutableIndex < newCount) { + ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); + } else { + ui->executablesListBox->setCurrentIndex(newCount - 1); + } } } @@ -4972,33 +5020,43 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { - Executable& exe = getSelectedExecutable(); + Executable* exe = getSelectedExecutable(); + if (!exe) { + return; + } - exe.setShownOnToolbar(!exe.isShownOnToolbar()); + exe->setShownOnToolbar(!exe->isShownOnToolbar()); updatePinnedExecutables(); } void MainWindow::linkDesktop() { - env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop); + if (auto* exe=getSelectedExecutable()) { + env::Shortcut(*exe).toggle(env::Shortcut::Desktop); + } } void MainWindow::linkMenu() { - env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu); + if (auto* exe=getSelectedExecutable()) { + env::Shortcut(*exe).toggle(env::Shortcut::StartMenu); + } } void MainWindow::on_linkButton_pressed() { - const Executable& exe = getSelectedExecutable(); + const Executable* exe = getSelectedExecutable(); + if (!exe) { + return; + } const QIcon addIcon(":/MO/gui/link"); const QIcon removeIcon(":/MO/gui/remove"); - env::Shortcut shortcut(exe); + env::Shortcut shortcut(*exe); m_LinkToolbar->setIcon( - exe.isShownOnToolbar() ? removeIcon : addIcon); + exe->isShownOnToolbar() ? removeIcon : addIcon); m_LinkDesktop->setIcon( shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); @@ -6327,16 +6385,19 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) modFilterActive(m_ModListSortProxy->isFilterActive()); } -const Executable &MainWindow::getSelectedExecutable() const +Executable* MainWindow::getSelectedExecutable() { - const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->get(name); -} + const QString name = ui->executablesListBox->itemText( + ui->executablesListBox->currentIndex()); -Executable &MainWindow::getSelectedExecutable() -{ - const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->get(name); + try + { + return &m_OrganizerCore.executablesList()->get(name); + } + catch(std::runtime_error&) + { + return nullptr; + } } void MainWindow::on_showHiddenBox_toggled(bool checked) diff --git a/src/mainwindow.h b/src/mainwindow.h index fa6a81cf..dbb1a0d5 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -397,8 +397,7 @@ private: // when painting the count QIcon m_originalNotificationIcon; - Executable const &getSelectedExecutable() const; - Executable &getSelectedExecutable(); + Executable* getSelectedExecutable(); private slots: -- cgit v1.3.1 From 0b480089c327e4037ada6f4bb649b180660f2923 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 12 Oct 2019 19:11:46 -0400 Subject: fixed loot not being hooked bumped to alpha 4.1 --- src/mainwindow.cpp | 1 + src/version.rc | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8d42d020..177c2a23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6566,6 +6566,7 @@ void MainWindow::on_bossButton_clicked() sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; sp.stdOut = stdOutWrite; HANDLE loot = spawn::startBinary(this, sp); diff --git a/src/version.rc b/src/version.rc index 433bcb40..5a893759 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2alpha3\0" +#define VER_FILEVERSION 2,2,2,4 +#define VER_FILEVERSION_STR "2.2.2alpha4.1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 70b966dc0e0dd65f574538b8f09e0e36894fda7d Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 22 Oct 2019 23:27:39 -0500 Subject: Improve automatic naming of new executables When using the "Add from file..." option, the extension will be removed. E.g., "zEdit 1.2.3.exe" will be shortened to "zEdit 1.2.3". When using the "Add empty" option and subsequently browsing for a binary, the complete base name will be used instead of just the base name. E.g., "zEdit 1.2.3.exe" will be shorted to "zEdit 1.2.3" instead of "zEdit 1". When using the "Add as Executable" option of the data tab, the complete base name will be used instead of just the base name. E.g., "zEdit 1.2.3.exe" will be shorted to "zEdit 1.2.3" instead of "zEdit 1". --- src/editexecutablesdialog.cpp | 4 ++-- src/mainwindow.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index c6c90c3f..8535b7a7 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -677,7 +677,7 @@ void EditExecutablesDialog::addFromFile() return; } - addNew(Executable(binary.fileName())); + addNew(Executable(binary.completeBaseName())); setBinary(binary); } @@ -726,7 +726,7 @@ void EditExecutablesDialog::setBinary(const QFileInfo& binary) // setting title if some variation of "New Executable" if (ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { - const auto prefix = binary.baseName(); + const auto prefix = binary.completeBaseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); if (newTitle) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 177c2a23..12ed40b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5329,7 +5329,7 @@ void MainWindow::addAsExecutable() case FileExecutionTypes::Executable: { QString name = QInputDialog::getText(this, tr("Enter Name"), tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.baseName()); + targetInfo.completeBaseName()); if (!name.isEmpty()) { //Note: If this already exists, you'll lose custom settings -- cgit v1.3.1 From d91580cc2669e2ab018c2aaab472cd763e5441d8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 15:39:07 -0400 Subject: initial Spawner and SpawnedProcess added steam app id to spawn parameters removed threadHandle, unused moved most of the stuff from OrganizerCore::spawnBinaryProcess() to Spawner replaced m_UserInterface by m_MainWindow --- src/mainwindow.cpp | 4 +- src/organizercore.cpp | 192 ++++++++++++++++-------------------------------- src/organizercore.h | 9 ++- src/plugincontainer.cpp | 1 + src/spawn.cpp | 131 +++++++++++++++++++++++++++++++-- src/spawn.h | 34 +++++++++ 6 files changed, 230 insertions(+), 141 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 12ed40b3..b5b9ab0d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -411,7 +411,7 @@ MainWindow::MainWindow(Settings &settings m_Tutorial.expose("modList", m_OrganizerCore.modList()); m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); - m_OrganizerCore.setUserInterface(this, this); + m_OrganizerCore.setUserInterface(this); for (const QString &fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } @@ -595,7 +595,7 @@ MainWindow::~MainWindow() cleanup(); m_PluginContainer.setUserInterface(nullptr, nullptr); - m_OrganizerCore.setUserInterface(nullptr, nullptr); + m_OrganizerCore.setUserInterface(nullptr); m_IntegratedBrowser.close(); delete ui; } catch (std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5613e8ce..3c986004 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,5 @@ #include "organizercore.h" - +#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -127,7 +127,7 @@ QStringList toStringList(InputIterator current, InputIterator end) OrganizerCore::OrganizerCore(Settings &settings) - : m_UserInterface(nullptr) + : m_MainWindow(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) @@ -249,44 +249,43 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(IUserInterface *userInterface, - QWidget *widget) +void OrganizerCore::setUserInterface(MainWindow* mainWindow) { storeSettings(); - m_UserInterface = userInterface; + m_MainWindow = mainWindow; - if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget, + if (m_MainWindow != nullptr) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), widget, + connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, + connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), widget, + connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), widget, + connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), widget, + connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), widget, + connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(widget); - m_Updater.setUserInterface(widget); + m_InstallationManager.setParentWidget(m_MainWindow); + m_Updater.setUserInterface(m_MainWindow); checkForUpdates(); } @@ -295,7 +294,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_UserInterface != nullptr) { + if (m_MainWindow != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -755,13 +754,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_UserInterface != nullptr) + if (hasIniTweaks && (m_MainWindow != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -822,13 +821,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_UserInterface != nullptr + if (hasIniTweaks && m_MainWindow != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1250,11 +1249,11 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } else { - // i.e. when running command line shortcuts there is no m_UserInterface + // i.e. when running command line shortcuts there is no user interface dlg.reset(new LockedDialog); dlg->show(); dlg->setEnabled(true); @@ -1262,8 +1261,8 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } ON_BLOCK_EXIT([&]() { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); DWORD ignoreExitCode; @@ -1275,35 +1274,21 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } -HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, - const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries) +HANDLE OrganizerCore::spawnBinaryProcess( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { spawn::SpawnParameters sp; sp.binary = binary; sp.arguments = arguments; sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; sp.hooked = true; prepareStart(); - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - - if (!spawn::checkBinary(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - while (m_DirectoryUpdate) { ::Sleep(100); QCoreApplication::processEvents(); @@ -1315,72 +1300,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(window, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkEnvironment(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkBlacklist(window, sp, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - - QString modsPath = settings().paths().mods(); - - // Check if this a request with either an executable or a working directory under our mods folder - // then will start the process in a virtualized "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = m_GamePlugin->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = m_GamePlugin->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), arguments); + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + return INVALID_HANDLE_VALUE; + } - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + try { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); - return spawn::startBinary(window, sp); - } else { - log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return spawn::startBinary(window, sp); - } - } else { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + } catch (const UsvfsConnectorException &e) { + log::debug(e.what()); + return INVALID_HANDLE_VALUE; + } catch (const std::exception &e) { + QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); return INVALID_HANDLE_VALUE; } + + auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings); + return process.releaseHandle(); } HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) @@ -1498,13 +1436,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) return true; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } ON_BLOCK_EXIT([&] () { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); return waitForProcessCompletion(handle, exitCode, uilock); } @@ -1762,8 +1700,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_UserInterface != nullptr) { - m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_MainWindow != nullptr) { + m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1879,8 +1817,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -2064,8 +2002,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } modInfo->clearCaches(); @@ -2114,8 +2052,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } @@ -2290,8 +2228,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); diff --git a/src/organizercore.h b/src/organizercore.h index 0d0a092c..04c96ba6 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,7 @@ #include "selfupdater.h" -#include "iuserinterface.h" //should be class IUserInterface; +#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -26,6 +26,8 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; +class MainWindow; + namespace MOBase { template class GuessedValue; class IModInterface; @@ -101,7 +103,7 @@ public: ~OrganizerCore(); - void setUserInterface(IUserInterface *userInterface, QWidget *widget); + void setUserInterface(MainWindow* mainWindow); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -328,8 +330,7 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - - IUserInterface *m_UserInterface; + MainWindow* m_MainWindow; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index c0706ba8..767d3eb8 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -3,6 +3,7 @@ #include "organizerproxy.h" #include "report.h" #include +#include #include #include #include diff --git a/src/spawn.cpp b/src/spawn.cpp index a34230b2..3c7d64ce 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" +#include #include #include #include @@ -440,7 +441,7 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -494,7 +495,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand } processHandle = pi.hProcess; - threadHandle = pi.hThread; + ::CloseHandle(pi.hThread); return ERROR_SUCCESS; } @@ -618,8 +619,7 @@ bool startSteam(QWidget* parent) (password.isEmpty() ? "no" : "yes")); HANDLE ph = INVALID_HANDLE_VALUE; - HANDLE th = INVALID_HANDLE_VALUE; - const auto e = spawn(sp, ph, th); + const auto e = spawn(sp, ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown @@ -772,17 +772,59 @@ bool checkBlacklist( } +void adjustForVirtualized( + const IPluginGame* game, SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + + HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { - HANDLE processHandle, threadHandle; - const auto e = spawn(sp, processHandle, threadHandle); + HANDLE handle = INVALID_HANDLE_VALUE; + const auto e = spawn::spawn(sp, handle); switch (e) { case ERROR_SUCCESS: { - ::CloseHandle(threadHandle); - return processHandle; + return handle; } case ERROR_ELEVATION_REQUIRED: @@ -799,6 +841,79 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } + + +SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +SpawnedProcess Spawner::spawn( + QWidget* parent, const IPluginGame* game, + SpawnParameters sp, Settings& settings) +{ + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!spawn::checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!spawn::checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 31b44739..d2853cd5 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include class Settings; +namespace MOBase { class IPluginGame; } namespace spawn { @@ -46,6 +47,7 @@ struct SpawnParameters QFileInfo binary; QString arguments; QDir currentDirectory; + QString steamAppID; bool hooked = false; HANDLE stdOut = INVALID_HANDLE_VALUE; HANDLE stdErr = INVALID_HANDLE_VALUE; @@ -69,6 +71,38 @@ bool checkBlacklist( **/ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + +private: + HANDLE m_handle; + SpawnParameters m_parameters; + + void destroy(); +}; + + +class Spawner +{ +public: + SpawnedProcess spawn( + QWidget* parent, const MOBase::IPluginGame* game, + SpawnParameters sp, Settings& settings); + +private: +}; + } // namespace -- cgit v1.3.1 From e43500eaf5d40bcd28ba5a820cdbc754cfb47e40 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 02:28:20 -0400 Subject: renamed runExecutable() to runExecutablefile() added runExecutable() for Executable class, also used by runShortcut() --- src/mainwindow.cpp | 24 ++----------- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 79 +++++++++++++++++++++++++----------------- src/organizercore.h | 10 ++++-- 4 files changed, 59 insertions(+), 56 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5b9ab0d..2474fdc0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1542,26 +1542,8 @@ void MainWindow::startExeAction() } action->setEnabled(false); - const Executable& exe = *itor; - auto& profile = *m_OrganizerCore.currentProfile(); - - QString customOverwrite = profile.setting("custom_overwrites", exe.title()).toString(); - auto forcedLibraries = profile.determineForcedLibraries(exe.title()); - - if (!profile.forcedLibrariesEnabled(exe.title())) { - forcedLibraries.clear(); - } - - m_OrganizerCore.spawnBinary( - exe.binaryInfo(), exe.arguments(), - exe.workingDirectory().length() != 0 - ? exe.workingDirectory() - : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries); + m_OrganizerCore.runExecutable(*itor); action->setEnabled(true); - } void MainWindow::activateSelectedProfile() @@ -2387,7 +2369,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.spawnBinary( + m_OrganizerCore.runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5477,7 +5459,7 @@ void MainWindow::openDataFile() } QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - m_OrganizerCore.executeFileVirtualized(this, targetInfo); + m_OrganizerCore.runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 36559a75..68b1be6b 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().executeFileVirtualized(parentWidget(), item->fileName()); + core().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4270f3b4..6f36ee3e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1188,7 +1188,7 @@ bool OrganizerCore::runFile( switch (type) { case FileExecutionTypes::Executable: { - runExecutable( + runExecutableFile( binaryInfo, arguments, currentProfile()->name(), targetInfo.absolutePath()); @@ -1208,25 +1208,33 @@ bool OrganizerCore::runFile( return false; } -void OrganizerCore::runExecutable( +bool OrganizerCore::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, - const QList &forcedLibraries) + const QList &forcedLibraries, + bool refresh) { DWORD processExitCode = 0; HANDLE processHandle = spawnAndWait( binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); - if (processHandle != INVALID_HANDLE_VALUE) { + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { refreshDirectoryStructure(); + // need to remove our stored load order because it may be outdated if a foreign tool changed the // file time. After removing that file, refreshESPList will use the file time as the order if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } + refreshDirectoryStructure(); refreshESPList(true); @@ -1235,6 +1243,42 @@ void OrganizerCore::runExecutable( //These callbacks should not fiddle with directoy structure and ESPs. m_FinishedRun(binary.absoluteFilePath(), processExitCode); } + + return true; +} + +bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) +{ + const QString customOverwrite = m_CurrentProfile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool OrganizerCore::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_ExecutablesList.get(shortcut.executable()); + return runExecutable(exe, false); } HANDLE OrganizerCore::spawnAndWait( @@ -1318,33 +1362,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } - -HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - - auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable()); - if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) { - forcedLibaries.clear(); - } - - return spawnAndWait( - exe.binaryInfo(), exe.arguments(), - m_CurrentProfile->name(), - exe.workingDirectory().length() != 0 - ? exe.workingDirectory() - : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - "", - forcedLibaries); -} - HANDLE OrganizerCore::startApplication(const QString &executable, const QStringList &args, const QString &cwd, diff --git a/src/organizercore.h b/src/organizercore.h index 6628452c..ca87a05c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -163,11 +163,16 @@ public: bool runFile(QWidget* parent, const QFileInfo& targetInfo); - void runExecutable( + bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, const QString &customOverwrite={}, - const QList &forcedLibraries={}); + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -222,7 +227,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - HANDLE runShortcut(const MOShortcut& shortcut); HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); -- cgit v1.3.1 From 4e8dcc5157706e1478396179f5dc11305532b159 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 04:27:22 -0400 Subject: moved findJavaInstallation() and getFileExecutionContext() to spawn fixed env::get() returning garbage after value --- src/editexecutablesdialog.cpp | 3 +- src/env.cpp | 31 +++-- src/mainwindow.cpp | 68 +++++------ src/organizercore.cpp | 264 +++++++++++++++--------------------------- src/organizercore.h | 12 -- src/spawn.cpp | 183 ++++++++++++++++++++++++++++- src/spawn.h | 19 +++ 7 files changed, 351 insertions(+), 229 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8535b7a7..32b31357 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modlist.h" #include "forcedloaddialog.h" #include "organizercore.h" +#include "spawn.h" #include #include @@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) { - auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath()); + auto java = spawn::findJavaInstallation(binary.absoluteFilePath()); if (java.isEmpty()) { QMessageBox::information( diff --git a/src/env.cpp b/src/env.cpp index 78b5dc96..507607d1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -190,19 +190,36 @@ QString setPath(const QString& s) QString get(const QString& name) { - std::wstring s(4000, L' '); + std::size_t bufferSize = 4000; + auto buffer = std::make_unique(bufferSize); DWORD realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); - if (realSize > s.size()) { - s.resize(realSize); + if (realSize > bufferSize) { + bufferSize = realSize; + buffer = std::make_unique(bufferSize); - ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); } - return QString::fromStdWString(s); + if (realSize == 0) { + const auto e = ::GetLastError(); + + // don't log if not found + if (e != ERROR_ENVVAR_NOT_FOUND) { + log::error( + "failed to get environment variable '{}', {}", + name, formatSystemMessage(e)); + } + + return {}; + } + + return QString::fromWCharArray(buffer.get(), realSize); } QString set(const QString& n, const QString& v) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2474fdc0..0181a335 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5295,43 +5295,42 @@ void MainWindow::addAsExecutable() return; } - using FileExecutionTypes = OrganizerCore::FileExecutionTypes; + const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString()); + const auto fec = spawn::getFileExecutionContext(this, target); - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { - return; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - QString name = QInputDialog::getText(this, tr("Enter Name"), - tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(binaryInfo) - .arguments(arguments) - .workingDirectory(targetInfo.absolutePath())); - - refreshExecutablesList(); - } - - break; + case spawn::FileExecutionTypes::Executable: + { + const QString name = QInputDialog::getText( + this, tr("Enter Name"), + tr("Enter a name for the executable"), + QLineEdit::Normal, + target.completeBaseName()); + + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_OrganizerCore.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + refreshExecutablesList(); } - case FileExecutionTypes::Other: // fall-through - default: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - break; - } + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + QMessageBox::information( + this, tr("Not an executable"), + tr("This is not a recognized executable.")); + + break; + } } } @@ -5458,7 +5457,8 @@ void MainWindow::openDataFile() return; } - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); m_OrganizerCore.runFile(this, targetInfo); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6bf2c290..78f9517c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -976,76 +976,6 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } -QString OrganizerCore::findJavaInstallation(const QString& jarFile) -{ - if (!jarFile.isEmpty()) { - // try to find java automatically based on the given jar file - std::wstring jarFileW = jarFile.toStdWString(); - - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - log::debug( - "failed to determine binary type of \"{}\": {}", - QString::fromWCharArray(buffer), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { - return QString::fromWCharArray(buffer); - } - } - } - - // second attempt: look to the registry - QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (reg.contains("CurrentVersion")) { - QString currentVersion = reg.value("CurrentVersion").toString(); - return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - - // not found - return {}; -} - -bool OrganizerCore::getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type) -{ - QString extension = targetInfo.suffix(); - if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || - (extension.compare("com", Qt::CaseInsensitive) == 0) || - (extension.compare("bat", Qt::CaseInsensitive) == 0)) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - auto java = findJavaInstallation(targetInfo.absoluteFilePath()); - - if (java.isEmpty()) { - java = QFileDialog::getOpenFileName( - parent, QObject::tr("Select binary"), - QString(), QObject::tr("Binary") + " (*.exe)"); - } - - if (java.isEmpty()) { - return false; - } - - binaryInfo = QFileInfo(java); - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - - return true; - } else { - type = FileExecutionTypes::Other; - return true; - } -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1177,35 +1107,23 @@ bool OrganizerCore::previewFile( bool OrganizerCore::runFile( QWidget* parent, const QFileInfo& targetInfo) { - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - runExecutableFile( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath()); - + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); return true; } - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + const auto r = shell::Open(targetInfo.absoluteFilePath()); + return r.success(); } } - - // nop - return false; } bool OrganizerCore::runExecutableFile( @@ -1281,6 +1199,87 @@ bool OrganizerCore::runShortcut(const MOShortcut& shortcut) return runExecutable(exe, false); } +HANDLE OrganizerCore::runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + QString profileName = profile; + if (profile == "") { + if (m_CurrentProfile != nullptr) { + profileName = m_CurrentProfile->name(); + } else { + throw MyException(tr("No profile set")); + } + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_ExecutablesList.getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait(binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + HANDLE OrganizerCore::spawnAndWait( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, @@ -1362,87 +1361,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) diff --git a/src/organizercore.h b/src/organizercore.h index fa05a20f..f802c8cb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -91,12 +91,6 @@ private: typedef boost::signals2::signal SignalModInstalled; public: - enum class FileExecutionTypes - { - Executable = 1, - Other = 2 - }; - static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } OrganizerCore(Settings &settings); @@ -152,12 +146,6 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } void loggedInAction(QWidget* parent, std::function f); - static QString findJavaInstallation(const QString& jarFile={}); - - static bool getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); diff --git a/src/spawn.cpp b/src/spawn.cpp index 3c7d64ce..dd93bfaa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -901,11 +901,11 @@ SpawnedProcess Spawner::spawn( return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkEnvironment(parent, sp)) { + if (!checkEnvironment(parent, sp)) { return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkBlacklist(parent, sp, settings)) { + if (!checkBlacklist(parent, sp, settings)) { return {INVALID_HANDLE_VALUE, sp}; } @@ -914,6 +914,185 @@ SpawnedProcess Spawner::spawn( return {startBinary(parent, sp), sp}; } + +QString getExecutableForJarFile(const QString& jarFile) +{ + const std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + + const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); + const auto r = static_cast(reinterpret_cast(hinst)); + + // anything <= 32 signals failure + if (r <= 32) { + log::warn( + "failed to find executable associated with file '{}', {}", + jarFile, shell::formatError(r)); + + return {}; + } + + DWORD binaryType = 0; + + if (!::GetBinaryTypeW(buffer, &binaryType)) { + const auto e = ::GetLastError(); + + log::warn( + "failed to determine binary type of '{}', {}", + QString::fromWCharArray(buffer), formatSystemMessage(e)); + + return {}; + } + + if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { + log::warn( + "unexpected binary type {} for file '{}'", + binaryType, QString::fromWCharArray(buffer)); + + return {}; + } + + return QString::fromWCharArray(buffer); +} + +QString getJavaHome() +{ + const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; + const QString value = "CurrentVersion"; + + QSettings reg(key, QSettings::NativeFormat); + + if (!reg.contains(value)) { + log::warn("key '{}\\{}' doesn't exist", key, value); + return {}; + } + + const QString currentVersion = reg.value("CurrentVersion").toString(); + const QString javaHome = QString("%1/JavaHome").arg(currentVersion); + + if (!reg.contains(javaHome)) { + log::warn( + "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", + currentVersion, key, value, key, javaHome); + + return {}; + } + + const auto path = reg.value(javaHome).toString(); + return path + "\\bin\\javaw.exe"; +} + +QString findJavaInstallation(const QString& jarFile) +{ + // try to find java automatically based on the given jar file + if (!jarFile.isEmpty()) { + const auto s = getExecutableForJarFile(jarFile); + if (!s.isEmpty()) { + return s; + } + } + + // second attempt: look to the registry + const auto s = getJavaHome(); + if (!s.isEmpty()) { + return s; + } + + // not found + return {}; +} + +bool isBatchFile(const QFileInfo& target) +{ + const auto batchExtensions = {"cmd", "bat"}; + + const QString extension = target.suffix(); + for (auto&& e : batchExtensions) { + if (extension.compare(e, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + +bool isExeFile(const QFileInfo& target) +{ + return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); +} + +bool isJavaFile(const QFileInfo& target) +{ + return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); +} + +QFileInfo getCmdPath() +{ + const auto p = env::get("COMSPEC2"); + if (!p.isEmpty()) { + return p; + } + + QString systemDirectory; + + const std::size_t buffer_size = 1000; + wchar_t buffer[buffer_size + 1] = {}; + + const auto length = ::GetSystemDirectoryW(buffer, buffer_size); + if (length != 0) { + systemDirectory = QString::fromWCharArray(buffer, length); + + if (!systemDirectory.endsWith("\\")) { + systemDirectory += "\\"; + } + } else { + systemDirectory = "C:\\Windows\\System32\\"; + } + + return systemDirectory + "cmd.exe"; +} + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target) +{ + if (isExeFile(target)) { + return { + target, + "", + FileExecutionTypes::Executable + }; + } + + if (isBatchFile(target)) { + return { + getCmdPath(), + QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + + if (isJavaFile(target)) { + auto java = findJavaInstallation(target.absoluteFilePath()); + + if (java.isEmpty()) { + java = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), + QString(), QObject::tr("Binary") + " (*.exe)"); + } + + if (!java.isEmpty()) { + return { + QFileInfo(java), + QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + } + + return {{}, {}, FileExecutionTypes::Other}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index d2853cd5..866e1795 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -103,6 +103,25 @@ public: private: }; + +enum class FileExecutionTypes +{ + Executable = 1, + Other +}; + +struct FileExecutionContext +{ + QFileInfo binary; + QString arguments; + FileExecutionTypes type; +}; + +QString findJavaInstallation(const QString& jarFile); + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target); + } // namespace -- cgit v1.3.1 From bff5a22f48b933fe9eba3a15497882ddf2a03990 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 07:11:08 -0400 Subject: spawning an executable now only waits for that particular process added waitForAllUSVFSProcesses() to OrganizerCore, used when closing MO --- src/envmodule.cpp | 73 ++++++++++++++++++++++--- src/envmodule.h | 3 ++ src/mainwindow.cpp | 14 ++--- src/organizercore.cpp | 145 ++++++++++++++++++++++++++------------------------ src/organizercore.h | 11 +++- src/spawn.cpp | 63 ++++++++++++++++++++++ src/spawn.h | 14 +++++ 7 files changed, 233 insertions(+), 90 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 3f1f8912..abbe02e5 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -408,7 +408,8 @@ std::vector getLoadedModules() } -std::vector getRunningProcesses() +template +void forEachRunningProcess(F&& f) { HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); @@ -416,7 +417,7 @@ std::vector getRunningProcesses() { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); - return {}; + return; } PROCESSENTRY32 entry = {}; @@ -427,16 +428,14 @@ std::vector getRunningProcesses() if (!Process32First(snapshot.get(), &entry)) { const auto e = GetLastError(); log::error("Process32First() failed, {}", formatSystemMessage(e)); - return {}; + return; } - std::vector v; - for (;;) { - v.push_back(Process( - entry.th32ProcessID, - QString::fromStdWString(entry.szExeFile))); + if (!f(entry)) { + break; + } // next process if (!Process32Next(snapshot.get(), &entry)) @@ -450,8 +449,66 @@ std::vector getRunningProcesses() break; } } +} + +std::vector getRunningProcesses() +{ + std::vector v; + + forEachRunningProcess([&](auto&& entry) { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + return true; + }); return v; } +QString getProcessName(HANDLE process) +{ + const QString badName = "unknown"; + + if (process == 0 || process == INVALID_HANDLE_VALUE) { + return badName; + } + + const DWORD bufferSize = MAX_PATH; + wchar_t buffer[bufferSize + 1] = {}; + + const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize); + + if (realSize == 0) { + const auto e = ::GetLastError(); + log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e)); + return badName; + } + + auto s = QString::fromWCharArray(buffer, realSize); + + const auto lastSlash = s.lastIndexOf("\\"); + if (lastSlash != -1) { + s = s.mid(lastSlash + 1); + } + + return s; +} + +DWORD getProcessParentID(DWORD pid) +{ + DWORD ppid = 0; + + forEachRunningProcess([&](auto&& entry) { + if (entry.th32ProcessID == pid) { + ppid = entry.th32ParentProcessID; + return false; + } + + return true; + }); + + return ppid; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index deb7520f..212f6f7b 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -120,6 +120,9 @@ private: std::vector getRunningProcesses(); std::vector getLoadedModules(); +QString getProcessName(HANDLE process); +DWORD getProcessParentID(DWORD pid); + } // namespace env #endif // ENV_MODULE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0181a335..bbb63333 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1311,16 +1311,10 @@ bool MainWindow::canExit() } } - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - if (injected_process_still_running != INVALID_HANDLE_VALUE) - { - m_exitAfterWait = true; - m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_exitAfterWait) { // if operation cancelled - return false; - } + m_exitAfterWait = true; + m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + if (!m_exitAfterWait) { // if operation cancelled + return false; } setCursor(Qt::WaitCursor); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3f4a83c..2a228fda 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "envmodule.h" #include #include @@ -74,47 +75,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static std::wstring getProcessName(HANDLE process) -{ - wchar_t buffer[MAX_PATH]; - const wchar_t *fileName = L"unknown"; - - if (process == nullptr) return fileName; - - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } - else { - fileName += 1; - } - } - - return fileName; -} - -// Get parent PID for the given process, return 0 on failure -static DWORD getProcessParentID(DWORD pid) -{ - HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe = { 0 }; - pe.dwSize = sizeof(PROCESSENTRY32); - - DWORD res = 0; - if (Process32First(th, &pe)) - do { - if (pe.th32ProcessID == pid) { - res = pe.th32ParentProcessID; - break; - } - } while (Process32Next(th, &pe)); - - CloseHandle(th); - - return res; -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -1348,35 +1308,46 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +void OrganizerCore::withLock(std::function f) { - if (Settings::instance().interface().lockGUI()) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + ON_BLOCK_EXIT([&]() { if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } + m_MainWindow->unlock(); + } }); - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); + f(uilock); +} + +bool OrganizerCore::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + withLock([&](auto* uilock) { DWORD ignoreExitCode; - waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); cycleDiagnostics(); - } + }); - return handle; + return r; } bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) @@ -1393,30 +1364,64 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (m_MainWindow != nullptr) { m_MainWindow->unlock(); } }); + return waitForProcessCompletion(handle, exitCode, uilock); } -bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +bool OrganizerCore::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto r = spawn::waitForProcess(handle, exitCode, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: // fall-through + case spawn::WaitResults::Unlocked: + return true; + + case spawn::WaitResults::Error: // fall-through + default: + return false; + } +} + +bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + // Certain process names we wish to "hide" for aesthetic reason: + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + bool originalHandle = true; bool newHandle = true; bool uiunlocked = false; + HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); + DWORD* exitCode = nullptr; DWORD currentPID = 0; QString processName; + auto waitForChildUntil = GetTickCount64(); if (handle != INVALID_HANDLE_VALUE) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); } - // Certain process names we wish to "hide" for aesthetic reason: bool waitingOnHidden = false; - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" @@ -1489,7 +1494,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL newHandle = handle != INVALID_HANDLE_VALUE; if (newHandle) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; @@ -1541,13 +1546,13 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde continue; } - QString pname = QString::fromStdWString(getProcessName(handle)); + QString pname = env::getProcessName(handle); bool phidden = false; for (auto hide : hiddenList) if (pname.contains(hide, Qt::CaseInsensitive)) phidden = true; - bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { if (best_match != INVALID_HANDLE_VALUE) diff --git a/src/organizercore.h b/src/organizercore.h index 3d3c7325..ffdb6830 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -167,6 +167,8 @@ public: const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + bool waitForAllUSVFSProcessesWithLock(); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -221,8 +223,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); @@ -311,6 +311,13 @@ private: bool waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + + void withLock(std::function f); + + HANDLE findAndOpenAUSVFSProcess( + const std::vector& hiddenList, DWORD preferedParentPid); + private slots: void directory_refreshed(); diff --git a/src/spawn.cpp b/src/spawn.cpp index fe1e9e3e..f0b3b2c7 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "settingsdialogworkarounds.h" #include +#include #include #include #include @@ -1093,6 +1094,68 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } +WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + const DWORD pid = ::GetProcessId(handle); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(handle)) + .arg(pid); + + if (uilock) + uilock->setProcessName(processName); + + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; + DWORD res = WAIT_TIMEOUT; + + log::debug( + "waiting for process completion '{}' ({})", + processName, pid); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion '{}' ({}), {}", + processName, pid, formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == WAIT_OBJECT_0) { + // completed + log::debug("process '{}' ({}) completed", processName, pid); + + if (exitCode) { + if (!::GetExitCodeProcess(handle, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process '{}' ({}): {}", + processName, pid, formatSystemMessage(e)); + } + } + + return WaitResults::Completed; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); + return WaitResults::Unlocked; + } + } +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 866e1795..441cad2c 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #include class Settings; +class ILockedWaitingForProcess; + namespace MOBase { class IPluginGame; } namespace spawn @@ -84,6 +86,7 @@ public: ~SpawnedProcess(); HANDLE releaseHandle(); + void wait(); private: HANDLE m_handle; @@ -122,6 +125,17 @@ QString findJavaInstallation(const QString& jarFile); FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target); + +enum class WaitResults +{ + Completed = 1, + Error, + Unlocked +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + } // namespace -- cgit v1.3.1 From 4d269c2e1a625e6d50b7e6272b4f474a921c6bfa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:34:06 -0400 Subject: split to processrunner added IUserInterface::qtWidget() put back IUserInterface in OrganizerCore now that there's a way to get the widget --- src/CMakeLists.txt | 3 + src/iuserinterface.h | 2 + src/main.cpp | 4 +- src/mainwindow.cpp | 13 +- src/mainwindow.h | 5 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 601 ++++++++------------------------------ src/organizercore.h | 60 +--- src/organizerproxy.cpp | 4 +- src/processrunner.cpp | 634 +++++++++++++++++++++++++++++++++++++++++ src/processrunner.h | 104 +++++++ src/spawn.cpp | 198 ------------- src/spawn.h | 48 ---- 13 files changed, 892 insertions(+), 786 deletions(-) create mode 100644 src/processrunner.cpp create mode 100644 src/processrunner.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 180422ef..5e909760 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ SET(organizer_SRCS envwindows.cpp colortable.cpp sanitychecks.cpp + processrunner.cpp shared/windows_error.cpp shared/error_report.cpp @@ -266,6 +267,7 @@ SET(organizer_HDRS envshortcut.h envwindows.h colortable.h + processrunner.h shared/windows_error.h shared/error_report.h @@ -348,6 +350,7 @@ set(core organizercore organizerproxy apiuseraccount + processrunner ) set(dialogs diff --git a/src/iuserinterface.h b/src/iuserinterface.h index a309ed9b..91487aee 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -33,6 +33,8 @@ public: virtual ILockedWaitingForProcess* lock() = 0; virtual void unlock() = 0; + + virtual QWidget* qtWidget() = 0; }; #endif // IUSERINTERFACE_H diff --git a/src/main.cpp b/src/main.cpp index 49ecc084..5ed7da5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.runShortcut(shortcut); + organizer.processRunner().runShortcut(shortcut); return 0; } catch (const std::exception &e) { @@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.runExecutableOrExecutableFile( + organizer.processRunner().runExecutableOrExecutableFile( exeName, arguments, QString(), QString()); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bbb63333..ce5280a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1312,7 +1312,7 @@ bool MainWindow::canExit() } m_exitAfterWait = true; - m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); if (!m_exitAfterWait) { // if operation cancelled return false; } @@ -1536,7 +1536,7 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.runExecutable(*itor); + m_OrganizerCore.processRunner().runExecutable(*itor); action->setEnabled(true); } @@ -2294,6 +2294,11 @@ void MainWindow::unlock() } } +QWidget* MainWindow::qtWidget() +{ + return this; +} + void MainWindow::on_btnRefreshData_clicked() { m_OrganizerCore.refreshDirectoryStructure(); @@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.runExecutableFile( + m_OrganizerCore.processRunner().runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5453,7 +5458,7 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.runFile(this, targetInfo); + m_OrganizerCore.processRunner().runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/mainwindow.h b/src/mainwindow.h index dbbd0bd9..19723480 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -118,8 +118,9 @@ public: void processUpdates(Settings& settings); - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; + ILockedWaitingForProcess* lock() override; + void unlock() override; + QWidget* qtWidget() override; bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 68b1be6b..758112cc 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().runFile(parentWidget(), item->fileName()); + core().processRunner().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eda64c1d..0f767f46 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,4 @@ #include "organizercore.h" -#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -env::Process* getInterestingProcess(std::vector& processes) -{ - if (processes.empty()) { - return nullptr; - } - - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName() - }; - - auto isHidden = [&](auto&& p) { - for (auto h : hiddenList) { - if (p.name().contains(h, Qt::CaseInsensitive)) { - return true; - } - } - - return false; - }; - - - for (auto&& root : processes) { - if (!isHidden(root)) { - return &root; - } - - for (auto&& child : root.children()) { - if (!isHidden(child)) { - return &child; - } - } - } - - - // everything is hidden, just pick the first one - return &processes[0]; -} - OrganizerCore::OrganizerCore(Settings &settings) - : m_MainWindow(nullptr) + : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) + , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore() m_RefresherThread.exit(); m_RefresherThread.wait(); - prepareStart(); + saveCurrentProfile(); // profile has to be cleaned up before the modinfo-buffer is cleared delete m_CurrentProfile; @@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(MainWindow* mainWindow) +void OrganizerCore::setUserInterface(IUserInterface* ui) { storeSettings(); - m_MainWindow = mainWindow; + m_UserInterface = ui; + + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } - if (m_MainWindow != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, + if (w) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), w, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, + connect(&m_ModList, SIGNAL(removeSelectedMods()), w, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, + connect(&m_ModList, SIGNAL(clearOverwrite()), w, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, + connect(&m_ModList, SIGNAL(modorder_changed()), w, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, + connect(&m_PluginList, SIGNAL(writePluginsList()), w, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, + connect(&m_PluginList, SIGNAL(esplist_changed()), w, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(m_MainWindow); - m_Updater.setUserInterface(m_MainWindow); + m_InstallationManager.setParentWidget(w); + m_Updater.setUserInterface(w); + m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_MainWindow != nullptr) { + if (m_UserInterface != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - runShortcut(moshortcut); + m_Runner.runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_MainWindow != nullptr) + if (hasIniTweaks && (m_UserInterface != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_MainWindow != nullptr + if (hasIniTweaks && m_UserInterface != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile( return true; } -bool OrganizerCore::runFile( - QWidget* parent, const QFileInfo& targetInfo) -{ - const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; - } - } -} - -bool OrganizerCore::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, - customOverwrite, forcedLibraries, &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - refreshDirectoryStructure(); - - // need to remove our stored load order because it may be outdated if a foreign tool changed the - // file time. After removing that file, refreshESPList will use the file time as the order - if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - log::debug("removing loadorder.txt"); - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - - refreshDirectoryStructure(); - - refreshESPList(true); - savePluginList(); - - //These callbacks should not fiddle with directoy structure and ESPs. - m_FinishedRun(binary.absoluteFilePath(), processExitCode); - } - - return true; -} - -bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) -{ - const QString customOverwrite = m_CurrentProfile->setting( - "custom_overwrites", exe.title()).toString(); - - QList forcedLibraries; - - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); -} - -bool OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - } - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - return runExecutable(exe, false); -} - -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - -HANDLE OrganizerCore::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; - - prepareStart(); - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (!m_AboutToRun(binary.absoluteFilePath())) { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); - return INVALID_HANDLE_VALUE; - } - - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - HANDLE handle = spawn::Spawner() - .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) - .releaseHandle(); - - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } - - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} - -void OrganizerCore::withLock(std::function f) -{ - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); - - f(uilock); -} - -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) { - return true; - } - - bool r = false; - - withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - }); - - return r; -} - -bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); - }); - - return r; -} - -bool OrganizerCore::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return true; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return true; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), exitCode, progress); - - switch (r) - { - case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Cancelled: - return true; - - case spawn::WaitResults::Error: // fall-through - default: - return false; - } -} - -bool OrganizerCore::waitForAllUSVFSProcessesWithLock() -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); - }); - - return r; -} - -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) -{ - for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { - break; - } - - std::vector processes; - for (auto&& h : handles) { - auto p = env::getProcessTree(h); - if (p.isValid()) { - processes.emplace_back(std::move(p)); - } - } - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - break; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - break; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), nullptr, progress); - - switch (r) - { - case spawn::WaitResults::Completed: - // this process is completed, check for others - break; - - case spawn::WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case spawn::WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; - } - } - - log::debug("waiting for process completion successful"); - return true; -} - bool OrganizerCore::onAboutToRun( const std::function &func) { @@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_MainWindow != nullptr) { - m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -1896,8 +1457,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } modInfo->clearCaches(); @@ -1946,8 +1507,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } @@ -2122,8 +1683,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); @@ -2147,11 +1708,12 @@ void OrganizerCore::savePluginList() m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void OrganizerCore::prepareStart() +void OrganizerCore::saveCurrentProfile() { if (m_CurrentProfile == nullptr) { return; } + m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); @@ -2159,6 +1721,79 @@ void OrganizerCore::prepareStart() storeSettings(); } +ProcessRunner& OrganizerCore::processRunner() +{ + return m_Runner; +} + +bool OrganizerCore::beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries) +{ + saveCurrentProfile(); + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); + return false; + } + + try + { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); + } + catch (const UsvfsConnectorException &e) + { + log::debug(e.what()); + return false; + } + catch (const std::exception &e) + { + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } + QMessageBox::warning(w, tr("Error"), e.what()); + return false; + } + + return true; +} + +void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) +{ + refreshDirectoryStructure(); + + // need to remove our stored load order because it may be outdated if a + // foreign tool changed the file time. After removing that file, + // refreshESPList will use the file time as the order + if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + log::debug("removing loadorder.txt"); + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + + refreshDirectoryStructure(); + + refreshESPList(true); + savePluginList(); + cycleDiagnostics(); + + //These callbacks should not fiddle with directoy structure and ESPs. + m_FinishedRun(binary.absoluteFilePath(), exitCode); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index ffdb6830..2252c118 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,6 @@ #include "selfupdater.h" -#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -14,6 +13,7 @@ #include "executableslist.h" #include "usvfsconnector.h" #include "moshortcut.h" +#include "processrunner.h" #include #include #include @@ -26,7 +26,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; -class MainWindow; +class IUserInterface; namespace MOBase { template class GuessedValue; @@ -97,7 +97,7 @@ public: ~OrganizerCore(); - void setUserInterface(MainWindow* mainWindow); + void setUserInterface(IUserInterface* ui); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -134,7 +134,14 @@ public: bool saveCurrentLists(); - void prepareStart(); + ProcessRunner& processRunner(); + + bool beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries); + + void afterRun(const QFileInfo& binary, DWORD exitCode); void refreshESPList(bool force = false); void refreshBSAList(); @@ -149,27 +156,6 @@ public: bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - - bool runExecutable(const Executable& exe, bool refresh=true); - - bool runShortcut(const MOShortcut& shortcut); - - HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -273,6 +259,7 @@ signals: private: + void saveCurrentProfile(); void storeSettings(); bool queryApi(QString &apiKey); @@ -298,26 +285,6 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - LPDWORD exitCode = nullptr); - - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); - - void withLock(std::function f); - - HANDLE findAndOpenAUSVFSProcess( - const std::vector& hiddenList, DWORD preferedParentPid); - private slots: void directory_refreshed(); @@ -331,13 +298,14 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - MainWindow* m_MainWindow; + IUserInterface* m_UserInterface; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; Profile *m_CurrentProfile; + ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 2ea1761a..75b3ea41 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -112,14 +112,14 @@ HANDLE OrganizerProxy::startApplication( const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { - return m_Proxied->runExecutableOrExecutableFile( + return m_Proxied->processRunner().runExecutableOrExecutableFile( executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForApplication(handle, exitCode); + return m_Proxied->processRunner().waitForApplication(handle, exitCode); } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/processrunner.cpp b/src/processrunner.cpp new file mode 100644 index 00000000..81c4b99e --- /dev/null +++ b/src/processrunner.cpp @@ -0,0 +1,634 @@ +#include "processrunner.h" +#include "organizercore.h" +#include "instancemanager.h" +#include "lockeddialog.h" +#include "iuserinterface.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + +env::Process* getInterestingProcess(std::vector& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + + +SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +ProcessRunner::ProcessRunner(OrganizerCore& core) + : m_core(core), m_ui(nullptr) +{ +} + +void ProcessRunner::setUserInterface(IUserInterface* ui) +{ + m_ui = ui; +} + +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + if (!parent && m_ui) { + parent = m_ui->qtWidget(); + } + + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); + + switch (fec.type) + { + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); + return true; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + auto r = shell::Open(targetInfo.absoluteFilePath()); + if (!r.success()) { + return false; + } + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + if (r.processHandle() != INVALID_HANDLE_VALUE) { + // steal because it gets closed after the wait + return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); + } + + return true; + } + } +} + +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + DWORD processExitCode = 0; + HANDLE processHandle = spawnAndWait( + binary, arguments, m_core.currentProfile()->name(), + currentDirectory, steamAppID, customOverwrite, forcedLibraries, + &processExitCode); + + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { + m_core.afterRun(binary, processExitCode); + } + + return true; +} + +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + const QString customOverwrite = profile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_core.executablesList()->get(shortcut.executable()); + return runExecutable(exe, false); +} + +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + QString profileName = profileOverride; + if (profileName == "") { + profileName = profile->name(); + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_core.executablesList()->getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_core.executablesList()->get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait( + binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + +HANDLE ProcessRunner::spawnAndWait( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + LPDWORD exitCode) +{ + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; + sp.hooked = true; + + if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { + return INVALID_HANDLE_VALUE; + } + + HANDLE handle = spawn(sp).releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +{ + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } + + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + +void ProcessRunner::withLock(std::function f) +{ + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_ui != nullptr) { + uilock = m_ui->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + + Guard g([&]() { + if (m_ui != nullptr) { + m_ui->unlock(); + } + }); + + f(uilock); +} + +bool ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + + withLock([&](auto* uilock) { + DWORD ignoreExitCode; + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto tree = env::getProcessTree(handle); + std::vector processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), exitCode, progress); + + switch (r) + { + case WaitResults::Completed: // fall-through + case WaitResults::Cancelled: + return true; + + case WaitResults::Error: // fall-through + default: + return false; + } +} + +bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; + } + + std::vector processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), nullptr, progress); + + switch (r) + { + case WaitResults::Completed: + // this process is completed, check for others + break; + + case WaitResults::Cancelled: + // force unlocked + log::debug("waiting for process completion aborted by UI"); + return true; + + case WaitResults::Error: // fall-through + default: + log::debug("waiting for process completion not successful"); + return false; + } + } + + log::debug("waiting for process completion successful"); + return true; +} + + + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + static_cast(handles.size()), &handles[0], + TRUE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { + // completed + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; i + +class OrganizerCore; +class ILockedWaitingForProcess; +class IUserInterface; +class Executable; +class MOShortcut; + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + void wait(); + +private: + HANDLE m_handle; + spawn::SpawnParameters m_parameters; + + void destroy(); +}; + + +class ProcessRunner +{ +public: + ProcessRunner(OrganizerCore& core); + + void setUserInterface(IUserInterface* ui); + + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + bool runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); + + HANDLE runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + + bool waitForAllUSVFSProcessesWithLock(); + +private: + OrganizerCore& m_core; + IUserInterface* m_ui; + + HANDLE spawnAndWait( + const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries={}, + LPDWORD exitCode = nullptr); + + SpawnedProcess spawn(spawn::SpawnParameters sp); + + void withLock(std::function f); + + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); +}; + + +enum class WaitResults +{ + Completed = 1, + Error, + Cancelled +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress); + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress); + +#endif // PROCESSRUNNER_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 0ea60641..c8d7c76a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -772,50 +772,6 @@ bool checkBlacklist( } } - -void adjustForVirtualized( - const IPluginGame* game, SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); - - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); - - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); - } -} - - HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { HANDLE handle = INVALID_HANDLE_VALUE; @@ -842,80 +798,6 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } - - -SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) - : m_handle(handle), m_parameters(std::move(sp)) -{ -} - -SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) - : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) -{ - other.m_handle = INVALID_HANDLE_VALUE; -} - -SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) -{ - if (this != &other) { - destroy(); - - m_handle = other.m_handle; - other.m_handle = INVALID_HANDLE_VALUE; - - m_parameters = std::move(other.m_parameters); - } - - return *this; -} - -SpawnedProcess::~SpawnedProcess() -{ - destroy(); -} - -HANDLE SpawnedProcess::releaseHandle() -{ - const auto h = m_handle; - m_handle = INVALID_HANDLE_VALUE; - return h; -} - -void SpawnedProcess::destroy() -{ - if (m_handle != INVALID_HANDLE_VALUE) { - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - } -} - - -SpawnedProcess Spawner::spawn( - QWidget* parent, const IPluginGame* game, - SpawnParameters sp, Settings& settings) -{ - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - adjustForVirtualized(game, sp, settings); - - return {startBinary(parent, sp), sp}; -} - - QString getExecutableForJarFile(const QString& jarFile) { const std::wstring jarFileW = jarFile.toStdWString(); @@ -1094,86 +976,6 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector exitCodes; - - const auto r = waitForProcesses(handles, exitCodes, progress); - - if (r == WaitResults::Completed) { - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; - } - } - - return r; -} - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - static_cast(handles.size()), &handles[0], - TRUE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { - // completed - exitCodes.resize(handles.size()); - std::fill(exitCodes.begin(), exitCodes.end(), 0); - - for (std::size_t i=0; i progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - } // namespace -- cgit v1.3.1 From db0b92776b5c9a34ebb1a5ce5c3f0b105844ee16 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 30 Oct 2019 23:42:59 -0400 Subject: added lockwidget to replace all the other dialogs rewrote ProcessRunner to have a bunch of setters and then a run() fixed bad exit code when waiting on a process that's already completed removed lock()/unlock() from main window, ProcessRunner is in charge of that now --- src/CMakeLists.txt | 3 + src/envmodule.cpp | 1 - src/iuserinterface.h | 6 +- src/lockwidget.cpp | 224 ++++++++++++++++ src/lockwidget.h | 68 +++++ src/mainwindow.cpp | 44 +--- src/mainwindow.h | 7 - src/organizercore.cpp | 8 +- src/organizercore.h | 3 +- src/organizerproxy.cpp | 19 +- src/pch.h | 47 ++-- src/processrunner.cpp | 687 ++++++++++++++++++++++++++++--------------------- src/processrunner.h | 70 ++++- 13 files changed, 806 insertions(+), 381 deletions(-) create mode 100644 src/lockwidget.cpp create mode 100644 src/lockwidget.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e909760..168b79dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ SET(organizer_SRCS colortable.cpp sanitychecks.cpp processrunner.cpp + lockwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -268,6 +269,7 @@ SET(organizer_HDRS envwindows.h colortable.h processrunner.h + lockwidget.h shared/windows_error.h shared/error_report.h @@ -486,6 +488,7 @@ set(widgets filterwidget icondelegate lcdnumber + lockwidget loglist loghighlighter modflagicondelegate diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 0e2e8ec7..09593e61 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -545,7 +545,6 @@ Process getProcessTree(HANDLE parent) } if (root.pid() == 0) { - log::error("process {} is not running", parentPID); return {}; } diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 91487aee..e5755f03 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -4,12 +4,13 @@ #include "modinfodialogfwd.h" #include "ilockedwaitingforprocess.h" +#include "lockwidget.h" #include #include #include - #include + class IUserInterface { public: @@ -31,9 +32,6 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; - virtual ILockedWaitingForProcess* lock() = 0; - virtual void unlock() = 0; - virtual QWidget* qtWidget() = 0; }; diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp new file mode 100644 index 00000000..720cac36 --- /dev/null +++ b/src/lockwidget.cpp @@ -0,0 +1,224 @@ +#include "lockwidget.h" +#include "mainwindow.h" +#include +#include +#include + +QWidget* createTransparentWidget(QWidget* parent=nullptr) +{ + auto* w = new QWidget(parent); + + w->setWindowOpacity(0); + w->setAttribute(Qt::WA_NoSystemBackground); + w->setAttribute(Qt::WA_TranslucentBackground); + + return w; +} + + +LockWidget::LockWidget(QWidget* parent, Reasons reason) : + m_parent(parent), m_overlay(nullptr), m_info(nullptr), m_result(NoResult), + m_filter(nullptr) +{ + if (reason != NoReason) { + lock(reason); + } +} + +LockWidget::~LockWidget() +{ + unlock(); +} + +void LockWidget::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); +} + +void LockWidget::unlock() +{ + m_overlay.reset(); + + if (m_filter && m_parent) { + m_parent->removeEventFilter(m_filter.get()); + } + + enableAll(); +} + +void LockWidget::setInfo(DWORD pid, const QString& name) +{ + m_info->setText(QString("%1 (%2)").arg(name).arg(pid)); +} + +LockWidget::Results LockWidget::result() const +{ + return m_result; +} + +void LockWidget::createUi(Reasons reason) +{ + if (m_parent) { + m_overlay.reset(createTransparentWidget(m_parent)); + m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); + m_overlay->setGeometry(m_parent->rect()); + } else { + m_overlay.reset(new QDialog); + } + + auto* center = new QFrame; + + if (m_parent) { + center->setFrameStyle(QFrame::StyledPanel); + center->setLineWidth(1); + center->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + center->setGraphicsEffect(shadow); + } + + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + + auto* ly = new QVBoxLayout(center); + + if (!m_parent) { + ly->setContentsMargins(0, 0, 0, 0); + } + + auto* message = new QLabel; + ly->addWidget(message); + ly->addWidget(m_info); + + auto* buttons = new QWidget; + auto* buttonsLayout = new QHBoxLayout(buttons); + ly->addWidget(buttons); + + switch (reason) + { + case LockUI: + { + message->setText(QObject::tr( + "Mod Organizer is locked while the executable is running.")); + + auto* unlockButton = new QPushButton(QObject::tr("Unlock")); + QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(unlockButton); + + break; + } + + case OutputRequired: + { + message->setText(QObject::tr( + "The executable must run to completion because a its output is " + "required.")); + + auto* unlockButton = new QPushButton(QObject::tr("Unlock")); + QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(unlockButton); + + break; + } + + case PreventExit: + { + message->setText(QObject::tr( + "Mod Organizer is waiting on processes to finish before exiting.")); + + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ onCancel(); }); + buttonsLayout->addWidget(cancel); + + break; + } + } + + auto* grid = new QGridLayout(m_overlay.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(center, 1, 1); + + if (!m_parent) { + grid->setContentsMargins(0, 0, 0, 0); + } + + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); + + disableAll(); + + if (m_parent) { + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); }; + m_parent->installEventFilter(m_filter.get()); + } + + m_overlay->setFocusPolicy(Qt::TabFocus); + m_overlay->setFocus(); + m_overlay->show(); + m_overlay->setEnabled(true); +} + +void LockWidget::onForceUnlock() +{ + m_result = ForceUnlocked; + unlock(); +} + +void LockWidget::onCancel() +{ + m_result = Cancelled; + unlock(); +} + +void LockWidget::disableAll() +{ + if (!m_parent) { + // nothing to disable without a main window + return; + } + + if (auto* mw=dynamic_cast(m_parent)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); + } + + for (auto* tb : m_parent->findChildren()) { + disable(tb); + } + + for (auto* d : m_parent->findChildren()) { + disable(d); + } +} + +void LockWidget::enableAll() +{ + for (auto* w : m_disabled) { + w->setEnabled(true); + } + + m_disabled.clear(); +} + +void LockWidget::disable(QWidget* w) +{ + if (w->isEnabled()) { + w->setEnabled(false); + m_disabled.push_back(w); + } +} diff --git a/src/lockwidget.h b/src/lockwidget.h new file mode 100644 index 00000000..bfb0b30f --- /dev/null +++ b/src/lockwidget.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +class LockWidget +{ +public: + enum Reasons + { + NoReason = 0, + LockUI, + OutputRequired, + PreventExit + }; + + enum Results + { + NoResult = 0, + StillLocked, + ForceUnlocked, + Cancelled + }; + + LockWidget(QWidget* parent, Reasons reason=NoReason); + ~LockWidget(); + + void lock(Reasons reason); + void unlock(); + + void setInfo(DWORD pid, const QString& name); + Results result() const; + +private: + class Filter : public QObject + { + public: + std::function resized; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + QWidget* m_parent; + std::unique_ptr m_overlay; + QLabel* m_info; + Results m_result; + std::unique_ptr m_filter; + std::vector m_disabled; + + void createUi(Reasons reason); + + void onForceUnlock(); + void onCancel(); + + void disableAll(); + void enableAll(); + void disable(QWidget* w); +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ce5280a6..7d3d20b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,7 +57,6 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "messagedialog.h" #include "installationmanager.h" -#include "lockeddialog.h" #include "waitingonclosedialog.h" #include "downloadlistsortproxy.h" #include "motddialog.h" @@ -1311,9 +1310,10 @@ bool MainWindow::canExit() } } - m_exitAfterWait = true; - m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); - if (!m_exitAfterWait) { // if operation cancelled + const auto r = m_OrganizerCore.processRunner() + .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit); + + if (r == ProcessRunner::Cancelled) { return false; } @@ -2258,42 +2258,6 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->executablesListBox); } -ILockedWaitingForProcess* MainWindow::lock() -{ - if (m_LockDialog != nullptr) { - ++m_LockCount; - return m_LockDialog; - } - if (m_exitAfterWait) - m_LockDialog = new WaitingOnCloseDialog(this); - else - m_LockDialog = new LockedDialog(this, true); - m_LockDialog->setModal(true); - m_LockDialog->show(); - setEnabled(false); - m_LockDialog->setEnabled(true); //What's the point otherwise? - ++m_LockCount; - return m_LockDialog; -} - -void MainWindow::unlock() -{ - //If you come through here with a null lock pointer, it's a bug! - if (m_LockDialog == nullptr) { - log::debug("Unlocking main window when already unlocked"); - return; - } - --m_LockCount; - if (m_LockCount == 0) { - if (m_exitAfterWait && m_LockDialog->canceled()) - m_exitAfterWait = false; - m_LockDialog->hide(); - m_LockDialog->deleteLater(); - m_LockDialog = nullptr; - setEnabled(true); - } -} - QWidget* MainWindow::qtWidget() { return this; diff --git a/src/mainwindow.h b/src/mainwindow.h index 19723480..c80287b2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -38,7 +38,6 @@ along with Mod Organizer. If not, see . //when I get round to cleaning up main.cpp class Executable; class CategoryFactory; -class LockedDialogBase; class OrganizerCore; class PluginListSortProxy; @@ -118,8 +117,6 @@ public: void processUpdates(Settings& settings); - ILockedWaitingForProcess* lock() override; - void unlock() override; QWidget* qtWidget() override; bool addProfile(); @@ -381,11 +378,7 @@ private: bool m_DidUpdateMasterList; - LockedDialogBase *m_LockDialog { nullptr }; - uint64_t m_LockCount { 0 }; - bool m_showArchiveData{ true }; - bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0f767f46..89e8bd9e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -91,7 +91,6 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -252,7 +251,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); - m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -357,7 +355,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - m_Runner.runShortcut(moshortcut); + processRunner().runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -1721,9 +1719,9 @@ void OrganizerCore::saveCurrentProfile() storeSettings(); } -ProcessRunner& OrganizerCore::processRunner() +ProcessRunner OrganizerCore::processRunner() { - return m_Runner; + return ProcessRunner(*this, m_UserInterface); } bool OrganizerCore::beforeRun( diff --git a/src/organizercore.h b/src/organizercore.h index 2252c118..d4882b92 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -134,7 +134,7 @@ public: bool saveCurrentLists(); - ProcessRunner& processRunner(); + ProcessRunner processRunner(); bool beforeRun( const QFileInfo& binary, const QString& profileName, @@ -305,7 +305,6 @@ private: Profile *m_CurrentProfile; - ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 75b3ea41..3ee35fe2 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -119,7 +119,24 @@ HANDLE OrganizerProxy::startApplication( bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->processRunner().waitForApplication(handle, exitCode); + const auto r = m_Proxied->processRunner().waitForApplication( + handle, exitCode, LockWidget::OutputRequired); + + switch (r) + { + case ProcessRunner::Completed: + return true; + + case ProcessRunner::Cancelled: // fall-through + case ProcessRunner::ForceUnlocked: + // this is always an error because the application should have run to + // completion + return false; + + case ProcessRunner::Error: // fall-through + default: + return false; + } } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/pch.h b/src/pch.h index 01a97357..b7c5d695 100644 --- a/src/pch.h +++ b/src/pch.h @@ -100,9 +100,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -111,13 +111,14 @@ #include #include #include -#include +#include #include +#include #include +#include #include #include #include -#include #include #include #include @@ -126,20 +127,21 @@ #include #include #include -#include #include +#include #include #include #include #include #include #include -#include #include #include +#include #include #include #include +#include #include #include #include @@ -190,53 +192,42 @@ #include #include #include -#include #include #include +#include #include #include -#include #include +#include +#include #include #include #include #include -#include #include #include #include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -255,4 +246,16 @@ #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 5d4a9fde..62c77efc 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -1,26 +1,58 @@ #include "processrunner.h" #include "organizercore.h" #include "instancemanager.h" -#include "lockeddialog.h" #include "iuserinterface.h" #include "envmodule.h" #include using namespace MOBase; -enum class WaitResults +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) { - Completed = 1, - Error, - Cancelled, - StillRunning -}; + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} -WaitResults singleWait(HANDLE handle, DWORD* exitCode) +std::optional singleWait(HANDLE handle, DWORD pid) { if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; + return ProcessRunner::Error; } const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; @@ -33,23 +65,15 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode) { case WAIT_OBJECT_0: { - // completed - if (exitCode) { - if (!::GetExitCodeProcess(handle, exitCode)) { - const auto e = ::GetLastError(); - log::warn( - "failed to get exit code of process, {}", - formatSystemMessage(e)); - } - } - - return WaitResults::Completed; + log::debug("process {} completed", pid); + return ProcessRunner::Completed; } case WAIT_TIMEOUT: case WAIT_EVENT: { - return WaitResults::StillRunning; + // still running + return {}; } case WAIT_FAILED: // fall-through @@ -57,11 +81,8 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode) { // error const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; + log::error("failed waiting for {}, {}", pid, formatSystemMessage(e)); + return ProcessRunner::Error; } } } @@ -132,6 +153,11 @@ std::pair findInterestingProcessInTrees( std::pair getInterestingProcess( const std::vector& initialProcesses) { + if (initialProcesses.empty()) { + log::debug("nothing to wait for"); + return {{}, Interest::None}; + } + std::vector processes; log::debug("getting process tree for {} processes", initialProcesses.size()); @@ -143,7 +169,7 @@ std::pair getInterestingProcess( } if (processes.empty()) { - log::debug("nothing to wait for"); + log::debug("processes are already completed"); return {{}, Interest::None}; } @@ -158,9 +184,8 @@ std::pair getInterestingProcess( const std::chrono::milliseconds Infinite(-1); -WaitResults timedWait( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock, - std::chrono::milliseconds wait) +std::optional timedWait( + HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) { using namespace std::chrono; @@ -170,37 +195,66 @@ WaitResults timedWait( } for (;;) { - const auto r = singleWait(handle, exitCode); + const auto r = singleWait(handle, pid); - if (r != WaitResults::StillRunning) { - return r; + if (r) { + return *r; } + // still running + // keep processing events so the app doesn't appear dead QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); - if (uilock && uilock->unlockForced()) { - return WaitResults::Cancelled; + switch (lock.result()) + { + case LockWidget::StillLocked: + { + break; + } + + case LockWidget::ForceUnlocked: + { + log::debug("waiting for {} force unlocked by user", pid); + return ProcessRunner::ForceUnlocked; + } + + case LockWidget::Cancelled: + { + log::debug("waiting for {} cancelled by user", pid); + return ProcessRunner::Cancelled; + } + + case LockWidget::NoResult: // fall-through + default: + { + // shouldn't happen + log::debug( + "unexpected result {} while waiting for {}", + static_cast(lock.result()), pid); + + return ProcessRunner::Error; + } } if (wait != Infinite) { const auto now = high_resolution_clock::now(); if (duration_cast(now - start) >= wait) { - return WaitResults::StillRunning; + return {}; } } } } -WaitResults waitForProcesses( - const std::vector& initialProcesses, - LPDWORD exitCode, ILockedWaitingForProcess* uilock) +ProcessRunner::Results waitForProcesses( + const std::vector& initialProcesses, LockWidget& lock) { using namespace std::chrono; if (initialProcesses.empty()) { - return WaitResults::Completed; + // shouldn't happen + return ProcessRunner::Completed; } DWORD currentPID = 0; @@ -208,14 +262,16 @@ WaitResults waitForProcesses( for (;;) { auto [p, interest] = getInterestingProcess(initialProcesses); - - if (uilock) { - uilock->setProcessInformation(p.pid(), p.name()); + if (!p.isValid()) { + // nothing to wait on + return ProcessRunner::Completed; } + lock.setInfo(p.pid(), p.name()); + auto interestingHandle = p.openHandleForWait(); if (!interestingHandle) { - return WaitResults::Error; + return ProcessRunner::Error; } if (p.pid() != currentPID) { @@ -230,9 +286,9 @@ WaitResults waitForProcesses( wait = Infinite; } - const auto r = timedWait(interestingHandle.get(), exitCode, uilock, wait); - if (r != WaitResults::StillRunning) { - return r; + const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); + if (r) { + return *r; } wait = std::min(wait * 2, milliseconds(2000)); @@ -243,11 +299,24 @@ WaitResults waitForProcesses( } } -WaitResults waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +ProcessRunner::Results waitForProcess( + HANDLE initialProcess, LPDWORD exitCode, LockWidget& lock) { std::vector processes = {initialProcess}; - return waitForProcesses(processes, exitCode, uilock); + + const auto r = waitForProcesses(processes, lock); + + // as long as it's not running anymore, try to get the exit code + if (exitCode && r != ProcessRunner::Running) { + if (!::GetExitCodeProcess(initialProcess, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process, {}", + formatSystemMessage(e)); + } + } + + return r; } @@ -298,17 +367,64 @@ void SpawnedProcess::destroy() } -ProcessRunner::ProcessRunner(OrganizerCore& core) - : m_core(core), m_ui(nullptr) +ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : + m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false), + m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { + m_sp.hooked = true; } -void ProcessRunner::setUserInterface(IUserInterface* ui) +ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) { - m_ui = ui; + m_sp.binary = binary; + return *this; } -bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setArguments(const QString& arguments) +{ + m_sp.arguments = arguments; + return *this; +} + +ProcessRunner& ProcessRunner::setCurrentDirectory(const QDir& directory) +{ + m_sp.currentDirectory = directory; + return *this; +} + +ProcessRunner& ProcessRunner::setSteamID(const QString& steamID) +{ + m_sp.steamAppID = steamID; + return *this; +} + +ProcessRunner& ProcessRunner::setCustomOverwrite(const QString& customOverwrite) +{ + m_customOverwrite = customOverwrite; + return *this; +} + +ProcessRunner& ProcessRunner::setForcedLibraries(const ForcedLibraries& forcedLibraries) +{ + m_forcedLibraries = forcedLibraries; + return *this; +} + +ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) +{ + m_profileName = profileName; + return *this; +} + +ProcessRunner& ProcessRunner::setWaitForCompletion( + LockWidget::Reasons reason, bool refresh) +{ + m_lock = reason; + m_refresh = refresh; + return *this; +} + +ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->qtWidget(); @@ -320,56 +436,24 @@ bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) { case spawn::FileExecutionTypes::Executable: { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; + setBinary(fec.binary); + setArguments(fec.arguments); + setCurrentDirectory(targetInfo.absoluteDir()); + break; } case spawn::FileExecutionTypes::Other: // fall-through default: { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; + m_shellOpen = targetInfo.absoluteFilePath(); + break; } } -} -bool ProcessRunner::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_core.currentProfile()->name(), - currentDirectory, steamAppID, customOverwrite, forcedLibraries, - &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - m_core.afterRun(binary, processExitCode); - } - - return true; + return *this; } -bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) { const auto* profile = m_core.currentProfile(); if (!profile) { @@ -379,25 +463,31 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) const QString customOverwrite = profile->setting( "custom_overwrites", exe.title()).toString(); - QList forcedLibraries; - + ForcedLibraries forcedLibraries; if (profile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = profile->determineForcedLibraries(exe.title()); } - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); + QDir currentDirectory = exe.workingDirectory(); + if (currentDirectory.isEmpty()) { + currentDirectory.setPath(exe.binaryInfo().absolutePath()); + } + + setBinary(exe.binaryInfo()); + setArguments(exe.arguments()); + setCurrentDirectory(currentDirectory); + setSteamID(exe.steamAppID()); + setCustomOverwrite(customOverwrite); + setForcedLibraries(forcedLibraries); + + return *this; } -bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + const auto currentInstance = InstanceManager::instance().currentInstance(); + + if (shortcut.hasInstance() && shortcut.instance() != currentInstance) { throw std::runtime_error( QString("Refusing to run executable from different instance %1:%2") .arg(shortcut.instance(),shortcut.executable()) @@ -405,12 +495,17 @@ bool ProcessRunner::runShortcut(const MOShortcut& shortcut) } const Executable& exe = m_core.executablesList()->get(shortcut.executable()); - return runExecutable(exe, false); + setFromExecutable(exe); + + return *this; } -HANDLE ProcessRunner::runExecutableOrExecutableFile( - const QString& executable, const QStringList &args, const QString &cwd, - const QString& profileOverride, const QString &forcedCustomOverwrite, +ProcessRunner& ProcessRunner::setFromFileOrExecutable( + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profileOverride, + const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { const auto* profile = m_core.currentProfile(); @@ -418,37 +513,41 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( throw MyException(QObject::tr("No profile set")); } - QString profileName = profileOverride; - if (profileName == "") { - profileName = profile->name(); - } + setProfileName(profileOverride); - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; + //QFileInfo binary; + //QString arguments = args.join(" "); + //QString currentDirectory = cwd; + //QString steamAppID; + //QString customOverwrite; + //QList forcedLibraries; if (executable.contains('\\') || executable.contains('/')) { // file path - binary = QFileInfo(executable); + auto binary = QFileInfo(executable); + if (binary.isRelative()) { // relative path, should be relative to game directory binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); } - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); + setBinary(binary); + + if (cwd == "") { + setCurrentDirectory(binary.absolutePath()); + } else { + setCurrentDirectory(cwd); } try { const Executable& exe = m_core.executablesList()->getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + + setSteamID(exe.steamAppID()); + setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); + if (profile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = profile->determineForcedLibraries(exe.title()); + setForcedLibraries(profile->determineForcedLibraries(exe.title())); } } catch (const std::runtime_error &) { // nop @@ -457,223 +556,244 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( // only a file name, search executables list try { const Executable &exe = m_core.executablesList()->get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + + setSteamID(exe.steamAppID()); + setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); + if (profile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = profile->determineForcedLibraries(exe.title()); + setForcedLibraries(profile->determineForcedLibraries(exe.title())); } - if (arguments == "") { - arguments = exe.arguments(); + + if (args.isEmpty()) { + setArguments(exe.arguments()); + } else { + setArguments(args.join(" ")); } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); + + setBinary(exe.binaryInfo()); + + if (cwd == "") { + setCurrentDirectory(exe.workingDirectory()); + } else { + setCurrentDirectory(cwd); } } catch (const std::runtime_error &) { log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); + setBinary(QFileInfo(executable)); } } - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); + if (ignoreCustomOverwrite) { + setCustomOverwrite(""); + } else if (!forcedCustomOverwrite.isEmpty()) { + setCustomOverwrite(forcedCustomOverwrite); + } - return spawnAndWait( - binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); + return *this; } -HANDLE ProcessRunner::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::run() { - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; + if (!m_shellOpen.isEmpty()) { + auto r = shell::Open(m_shellOpen); + if (!r.success()) { + return Error; + } - if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { - return INVALID_HANDLE_VALUE; - } + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + m_handle = r.stealProcessHandle(); + } else { + if (m_profileName.isEmpty()) { + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } - HANDLE handle = spawn(sp).releaseHandle(); + m_profileName = profile->name(); + } - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } + if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { + return Error; + } - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } -void adjustForVirtualized( - const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); + if (!checkBinary(parent, m_sp)) { + return Error; + } - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { + return Error; } - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; + if (!checkEnvironment(parent, m_sp)) { + return Error; } - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); + if (!checkBlacklist(parent, m_sp, settings)) { + return Error; + } - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + adjustForVirtualized(game, m_sp, settings); + + m_handle = startBinary(parent, m_sp); + if (m_handle == INVALID_HANDLE_VALUE) { + return Error; + } + } + + if (m_handle == INVALID_HANDLE_VALUE || m_lock == LockWidget::NoReason) { + return Running; + } else { + const auto r = waitForProcessCompletionWithLock( + m_handle, &m_exitCode, m_lock); + + if (r == Completed && m_refresh) { + m_core.afterRun(m_sp.binary, m_exitCode); + } + + return r; } } -SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +DWORD ProcessRunner::exitCode() { - QWidget* parent = nullptr; - if (m_ui) { - parent = m_ui->qtWidget(); - } + return m_exitCode; +} - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - const auto* game = m_core.managedGame(); - auto& settings = m_core.settings(); +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + setFromFile(parent, targetInfo); + setWaitForCompletion(LockWidget::LockUI, true); - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } + const auto r = run(); + return (r != Error); +} - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + setBinary(binary); + setArguments(arguments); + setCurrentDirectory(currentDirectory); + setSteamID(steamAppID); + setCustomOverwrite(customOverwrite); + setForcedLibraries(forcedLibraries); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + setFromExecutable(exe); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - adjustForVirtualized(game, sp, settings); +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + setFromShortcut(shortcut); + setWaitForCompletion(LockWidget::LockUI, false); - return {startBinary(parent, sp), sp}; + const auto r = run(); + return (r != Error); } -void ProcessRunner::withLock(std::function f) +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + setFromFileOrExecutable( + executable, args, cwd, profileOverride, forcedCustomOverwrite, + ignoreCustomOverwrite); - if (m_ui != nullptr) { - uilock = m_ui->lock(); - } else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } + setWaitForCompletion(LockWidget::LockUI, true); - Guard g([&]() { - if (m_ui != nullptr) { - m_ui->unlock(); - } - }); + run(); + return m_handle; +} - f(uilock); +void ProcessRunner::withLock( + LockWidget::Reasons reason, std::function f) +{ + auto lock = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, reason); + + f(*lock); } -bool ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) { if (!Settings::instance().interface().lockGUI()) { log::debug("not waiting for process because user has disabled locking"); - return true; + return ForceUnlocked; } - auto r = WaitResults::Error; - - withLock([&](auto* uilock) { - r = waitForProcess(handle, exitCode, uilock); - }); - - // completed/unlocked is fine - return (r != WaitResults::Error); + return waitForApplication(handle, exitCode, reason); } -bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::waitForApplication( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) { // don't check for lockGUI() setting; this _always_ locks the ui and waits // for completion // - // this is typically called only from OrganizerProxy, which allows plugins - // to wait on applications until they're finished + // this is typically called only from: + // 1) OrganizerProxy, which allows plugins to wait on applications until + // they're finished + // + // the check_fnis plugin for example will start FNIS, wait for it to + // complete, and then check the exit code; this has to work regardless of + // the locking setting; // - // the check_fnis plugin for example will start FNIS, wait for it to complete, - // and then check the exit code; this has to work regardless of the locking - // setting + // 2) waitForProcessCompletionWithLock() above, which has already checked the + // lock setting - auto r = WaitResults::Error; + auto r = Error; - withLock([&](auto* uilock) { - r = waitForProcess(handle, exitCode, uilock); + withLock(reason, [&](auto& lock) { + r = waitForProcess(handle, exitCode, lock); }); - // treat unlocked as an error since this should always wait for completion - return (r == WaitResults::Completed); + return r; } -bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { if (!Settings::instance().interface().lockGUI()) { log::debug("not waiting for usvfs processes because user has disabled locking"); - return true; + return ForceUnlocked; } - bool r = false; + auto r = Error; - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); + withLock(reason, [&](auto& lock) { + r = waitForAllUSVFSProcesses(lock); }); return r; } -bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) { for (;;) { const auto processes = getRunningUSVFSProcesses(); @@ -681,26 +801,15 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) break; } - const auto r = waitForProcesses(processes, nullptr, uilock); + const auto r = waitForProcesses(processes, lock); - switch (r) - { - case WaitResults::Completed: - // this process is completed, check for others - break; - - case WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; + if (r != Completed) { + // error, cancelled, or unlocked + return r; } + + // this process is completed, check for others } - log::debug("waiting for process completion successful"); - return true; + return Completed; } diff --git a/src/processrunner.h b/src/processrunner.h index 28f4da75..b7895903 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -2,10 +2,10 @@ #define PROCESSRUNNER_H #include "spawn.h" +#include "lockwidget.h" #include class OrganizerCore; -class ILockedWaitingForProcess; class IUserInterface; class Executable; class MOShortcut; @@ -35,9 +35,43 @@ private: class ProcessRunner { public: - ProcessRunner(OrganizerCore& core); + enum Results + { + Running = 1, + Completed, + Error, + Cancelled, + ForceUnlocked + }; + + using ForcedLibraries = QList; + + ProcessRunner(OrganizerCore& core, IUserInterface* ui); + + ProcessRunner& setBinary(const QFileInfo &binary); + ProcessRunner& setArguments(const QString& arguments); + ProcessRunner& setCurrentDirectory(const QDir& directory); + ProcessRunner& setSteamID(const QString& steamID); + ProcessRunner& setCustomOverwrite(const QString& customOverwrite); + ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); + ProcessRunner& setProfileName(const QString& profileName); + ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh); + + ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + ProcessRunner& setFromExecutable(const Executable& exe); + ProcessRunner& setFromShortcut(const MOShortcut& shortcut); + + ProcessRunner& setFromFileOrExecutable( + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profile, + const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + Results run(); + DWORD exitCode(); - void setUserInterface(IUserInterface* ui); bool runFile(QWidget* parent, const QFileInfo& targetInfo); @@ -53,17 +87,31 @@ public: bool runShortcut(const MOShortcut& shortcut); HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profile, + const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); + Results waitForApplication( + HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); + + Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: OrganizerCore& m_core; IUserInterface* m_ui; + spawn::SpawnParameters m_sp; + QString m_customOverwrite; + ForcedLibraries m_forcedLibraries; + QString m_profileName; + LockWidget::Reasons m_lock; + bool m_refresh; + QString m_shellOpen; + HANDLE m_handle; + DWORD m_exitCode; HANDLE spawnAndWait( const QFileInfo &binary, const QString &arguments, @@ -76,11 +124,13 @@ private: SpawnedProcess spawn(spawn::SpawnParameters sp); - void withLock(std::function f); + void withLock( + LockWidget::Reasons reason, std::function f); - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + Results waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + Results waitForAllUSVFSProcesses(LockWidget& lock); }; #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 0cea4833eb48400feb652e883c70d8a2907701c3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:47:08 -0400 Subject: explicit refresh parameter for setWaitForCompletion(), some parts of the ui will crash if things refresh unexpectedly removed runFile() fixed crash when unlocking if some widgets were destroyed in the meantime lock widget will now pick the active window and disable all top levels --- src/lockwidget.cpp | 73 +++++++++++++++++++++++++++--------------- src/lockwidget.h | 2 +- src/mainwindow.cpp | 6 +++- src/modinfodialogconflicts.cpp | 6 +++- src/processrunner.cpp | 25 +++++---------- src/processrunner.h | 13 +++++--- 6 files changed, 75 insertions(+), 50 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 720cac36..ad9aefe3 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -59,17 +59,22 @@ LockWidget::Results LockWidget::result() const void LockWidget::createUi(Reasons reason) { - if (m_parent) { - m_overlay.reset(createTransparentWidget(m_parent)); + QWidget* overlayTarget = m_parent; + if (auto* w = qApp->activeWindow()) { + overlayTarget = w; + } + + if (overlayTarget) { + m_overlay.reset(createTransparentWidget(overlayTarget)); m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); - m_overlay->setGeometry(m_parent->rect()); + m_overlay->setGeometry(overlayTarget->rect()); } else { m_overlay.reset(new QDialog); } auto* center = new QFrame; - if (m_parent) { + if (overlayTarget) { center->setFrameStyle(QFrame::StyledPanel); center->setLineWidth(1); center->setAutoFillBackground(true); @@ -86,7 +91,7 @@ void LockWidget::createUi(Reasons reason) auto* ly = new QVBoxLayout(center); - if (!m_parent) { + if (!overlayTarget) { ly->setContentsMargins(0, 0, 0, 0); } @@ -115,7 +120,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because a its output is " + "The executable must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -149,7 +154,7 @@ void LockWidget::createUi(Reasons reason) grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(center, 1, 1); - if (!m_parent) { + if (!overlayTarget) { grid->setContentsMargins(0, 0, 0, 0); } @@ -160,10 +165,10 @@ void LockWidget::createUi(Reasons reason) disableAll(); - if (m_parent) { + if (overlayTarget) { m_filter.reset(new Filter); - m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); }; - m_parent->installEventFilter(m_filter.get()); + m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; + overlayTarget->installEventFilter(m_filter.get()); } m_overlay->setFocusPolicy(Qt::TabFocus); @@ -184,32 +189,48 @@ void LockWidget::onCancel() unlock(); } +template +QList findChildrenImmediate(QWidget* parent) +{ + return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); +} + void LockWidget::disableAll() { - if (!m_parent) { - // nothing to disable without a main window - return; - } + const auto topLevels = QApplication::topLevelWidgets(); - if (auto* mw=dynamic_cast(m_parent)) { - disable(mw->centralWidget()); - disable(mw->menuBar()); - disable(mw->statusBar()); - } + for (auto* w : topLevels) { + if (auto* mw=dynamic_cast(w)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); - for (auto* tb : m_parent->findChildren()) { - disable(tb); - } + for (auto* tb : findChildrenImmediate(w)) { + disable(tb); + } + + for (auto* d : findChildrenImmediate(w)) { + disable(d); + } + } - for (auto* d : m_parent->findChildren()) { - disable(d); + if (auto* d=dynamic_cast(w)) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_overlay.get()) { + disable(child); + } + } + } } } void LockWidget::enableAll() { - for (auto* w : m_disabled) { - w->setEnabled(true); + for (auto w : m_disabled) { + if (w) { + w->setEnabled(true); + } } m_disabled.clear(); diff --git a/src/lockwidget.h b/src/lockwidget.h index bfb0b30f..9c555ac9 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -55,7 +55,7 @@ private: QLabel* m_info; Results m_result; std::unique_ptr m_filter; - std::vector m_disabled; + std::vector> m_disabled; void createUi(Reasons reason); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7d3d20b3..63f6c680 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5422,7 +5422,11 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.processRunner().runFile(this, targetInfo); + + m_OrganizerCore.processRunner() + .setFromFile(this, targetInfo) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 758112cc..8aefd4c6 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,11 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner().runFile(parentWidget(), item->fileName()); + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return true; }); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 62c77efc..1d9da96b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,7 +368,7 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false), + m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - LockWidget::Reasons reason, bool refresh) + RefreshModes refresh, LockWidget::Reasons reason) { - m_lock = reason; m_refresh = refresh; + m_lock = reason; return *this; } @@ -655,7 +655,7 @@ ProcessRunner::Results ProcessRunner::run() const auto r = waitForProcessCompletionWithLock( m_handle, &m_exitCode, m_lock); - if (r == Completed && m_refresh) { + if (r == Completed && m_refresh == Refresh) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,15 +669,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) -{ - setFromFile(parent, targetInfo); - setWaitForCompletion(LockWidget::LockUI, true); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, @@ -691,7 +682,7 @@ bool ProcessRunner::runExecutableFile( setSteamID(steamAppID); setCustomOverwrite(customOverwrite); setForcedLibraries(forcedLibraries); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -700,7 +691,7 @@ bool ProcessRunner::runExecutableFile( bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -709,7 +700,7 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); - setWaitForCompletion(LockWidget::LockUI, false); + setWaitForCompletion(NoRefresh); const auto r = run(); return (r != Error); @@ -724,7 +715,7 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( executable, args, cwd, profileOverride, forcedCustomOverwrite, ignoreCustomOverwrite); - setWaitForCompletion(LockWidget::LockUI, true); + setWaitForCompletion(Refresh); run(); return m_handle; diff --git a/src/processrunner.h b/src/processrunner.h index b7895903..21840bfe 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -44,6 +44,12 @@ public: ForceUnlocked }; + enum RefreshModes + { + NoRefresh = 1, + Refresh + }; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); @@ -55,7 +61,8 @@ public: ProcessRunner& setCustomOverwrite(const QString& customOverwrite); ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); - ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh); + ProcessRunner& setWaitForCompletion( + RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -73,8 +80,6 @@ public: DWORD exitCode(); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, @@ -108,7 +113,7 @@ private: ForcedLibraries m_forcedLibraries; QString m_profileName; LockWidget::Reasons m_lock; - bool m_refresh; + RefreshModes m_refresh; QString m_shellOpen; HANDLE m_handle; DWORD m_exitCode; -- cgit v1.3.1 From c8e101e19eed4417d42bef678cd60d3efb414eb7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:54:49 -0400 Subject: removed runExecutableFile() turns out on_startButton_clicked() had redundant code --- src/mainwindow.cpp | 43 ++++++++++--------------------------------- src/processrunner.cpp | 26 -------------------------- src/processrunner.h | 7 ------- 3 files changed, 10 insertions(+), 66 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 63f6c680..c1789b0a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2312,41 +2312,18 @@ void MainWindow::installMod(QString fileName) void MainWindow::on_startButton_clicked() { - try { - const Executable* selectedExecutable = getSelectedExecutable(); - if (!selectedExecutable) { - return; - } - - ui->startButton->setEnabled(false); - - auto* profile = m_OrganizerCore.currentProfile(); - - const QString customOverwrite = profile->setting( - "custom_overwrites", selectedExecutable->title()).toString(); - - auto forcedLibraries = profile->determineForcedLibraries( - selectedExecutable->title()); - - if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) { - forcedLibraries.clear(); - } - - m_OrganizerCore.processRunner().runExecutableFile( - selectedExecutable->binaryInfo(), - selectedExecutable->arguments(), - selectedExecutable->workingDirectory().length() != 0 ? - selectedExecutable->workingDirectory() : - selectedExecutable->binaryInfo().absolutePath(), - selectedExecutable->steamAppID(), - customOverwrite, - forcedLibraries); - } catch (...) { - ui->startButton->setEnabled(true); - throw; + const Executable* selectedExecutable = getSelectedExecutable(); + if (!selectedExecutable) { + return; } - ui->startButton->setEnabled(true); + ui->startButton->setEnabled(false); + Guard g([&]{ ui->startButton->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*selectedExecutable) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } bool MainWindow::modifyExecutablesDialog(int selection) diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1d9da96b..dba29bd2 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -515,13 +515,6 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( setProfileName(profileOverride); - //QFileInfo binary; - //QString arguments = args.join(" "); - //QString currentDirectory = cwd; - //QString steamAppID; - //QString customOverwrite; - //QList forcedLibraries; - if (executable.contains('\\') || executable.contains('/')) { // file path @@ -669,25 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - setBinary(binary); - setArguments(arguments); - setCurrentDirectory(currentDirectory); - setSteamID(steamAppID); - setCustomOverwrite(customOverwrite); - setForcedLibraries(forcedLibraries); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); diff --git a/src/processrunner.h b/src/processrunner.h index 21840bfe..2e9550e0 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,13 +80,6 @@ public: DWORD exitCode(); - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - bool runExecutable(const Executable& exe, bool refresh=true); bool runShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 2c3079c1dc2aaeb84cbe7e4a021f6b3f22c785a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:00:58 -0400 Subject: removed runExecutable() --- src/mainwindow.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 3 files changed, 6 insertions(+), 13 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c1789b0a..3d32aa16 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1536,8 +1536,12 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.processRunner().runExecutable(*itor); - action->setEnabled(true); + Guard g([&]{ action->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*itor) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } void MainWindow::activateSelectedProfile() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index dba29bd2..1c8c923b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) -{ - setFromExecutable(exe); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); diff --git a/src/processrunner.h b/src/processrunner.h index 2e9550e0..d3437412 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runExecutable(const Executable& exe, bool refresh=true); - bool runShortcut(const MOShortcut& shortcut); HANDLE runExecutableOrExecutableFile( -- cgit v1.3.1 From 9ff8a471aaf9a696610a055c091de39c41f985b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 04:03:16 -0400 Subject: added waitForAllUSVFSProcesses() wrapper in OrganizerCore, the fact that it's in ProcessRunner is a bit of a hack --- src/mainwindow.cpp | 4 +--- src/organizercore.cpp | 6 ++++++ src/organizercore.h | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3d32aa16..d304f8b7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1310,9 +1310,7 @@ bool MainWindow::canExit() } } - const auto r = m_OrganizerCore.processRunner() - .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit); - + const auto r = m_OrganizerCore.waitForAllUSVFSProcesses(); if (r == ProcessRunner::Cancelled) { return false; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 57e24f3b..943750ed 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1796,6 +1796,12 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) m_FinishedRun(binary.absoluteFilePath(), exitCode); } +ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( + LockWidget::Reasons reason) +{ + return processRunner().waitForAllUSVFSProcessesWithLock(reason); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index d4882b92..0ba52284 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -143,6 +143,9 @@ public: void afterRun(const QFileInfo& binary, DWORD exitCode); + ProcessRunner::Results waitForAllUSVFSProcesses( + LockWidget::Reasons reason=LockWidget::PreventExit); + void refreshESPList(bool force = false); void refreshBSAList(); -- cgit v1.3.1 From 7ebd4debeef2cfdf268679e7b680021d3dc20687 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:19:14 -0400 Subject: added a ForceWait flag to bypass disabled locking merged a bunch of unnecessary functions in ProcessRunner --- src/main.cpp | 4 +- src/mainwindow.cpp | 2 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 2 +- src/organizerproxy.cpp | 6 +- src/processrunner.cpp | 155 ++++++++++++++++++++--------------------- src/processrunner.h | 43 ++++++------ 7 files changed, 107 insertions(+), 107 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 2b9a2f4d..02347ee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -634,7 +634,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { organizer.processRunner() .setFromShortcut(shortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; @@ -662,7 +662,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // pass the remaining parameters to the binary organizer.processRunner() .setFromFileOrExecutable(exeName, arguments) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d304f8b7..c2d91bcc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 8aefd4c6..d37f068c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -529,7 +529,7 @@ void ConflictsTab::openItems(QTreeView* tree) for_each_in_selection(tree, [&](const ConflictItem* item) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return true; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 943750ed..96ae84a8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } } diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 0b6f8df1..420e2d82 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -128,7 +128,9 @@ HANDLE OrganizerProxy::startApplication( .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) .run(); - return runner.processHandle(); + // the plugin is in charge of closing the handle, unless waitForApplication() + // is called on it + return runner.stealProcessHandle().release(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const @@ -142,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b732204c..9cc2ce52 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,8 +368,8 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), - m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) + m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; } @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason) + WaitFlags flags, LockWidget::Reasons reason) { - m_refresh = refresh; - m_lock = reason; + m_waitFlags = flags; + m_lockReason = reason; return *this; } @@ -593,13 +593,13 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - m_handle = r.stealProcessHandle(); + m_handle.reset(r.stealProcessHandle()); // not all files will return a valid handle even if opening them was // successful, such as inproc handlers (like the photo viewer); in this // case it's impossible to determine the status, so just say it's still // running - if (m_handle == INVALID_HANDLE_VALUE) { + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Running; } } else { @@ -642,8 +642,8 @@ ProcessRunner::Results ProcessRunner::run() adjustForVirtualized(game, m_sp, settings); - m_handle = startBinary(parent, m_sp); - if (m_handle == INVALID_HANDLE_VALUE) { + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Error; } } @@ -653,14 +653,46 @@ ProcessRunner::Results ProcessRunner::run() ProcessRunner::Results ProcessRunner::postRun() { - if (m_lock == LockWidget::NoReason) { - return Running; + const bool mustWait = (m_waitFlags & ForceWait); + + if (mustWait && m_lockReason == LockWidget::NoReason) { + // never lock the ui without an escape hatch for the user + log::debug( + "the ForceWait flag is set but the lock reason wasn't, " + "defaulting to LockUI"); + + m_lockReason = LockWidget::LockUI; + } + + if (mustWait) { + if (!Settings::instance().interface().lockGUI()) { + // at least tell the user what's going on + log::debug( + "locking is disabled, but the output of the application is required; " + "overriding this setting and locking the ui"); + } + } else { + // no force wait + + if (m_lockReason == LockWidget::NoReason) { + // no locking requested + return Running; + } + + if (!Settings::instance().interface().lockGUI()) { + // disabling locking is like clicking on unlock immediately + log::debug("not waiting for process because locking is disabled"); + return ForceUnlocked; + } } - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + auto r = Error; + + withLock([&](auto& lock) { + r = waitForProcess(m_handle.get(), &m_exitCode, lock); + }); - if (r == Completed && m_refresh == Refresh) { + if (r == Completed && (m_waitFlags & Refresh)) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,101 +701,64 @@ ProcessRunner::Results ProcessRunner::postRun() ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) { - m_handle = h; + m_handle.reset(h); return postRun(); } -DWORD ProcessRunner::exitCode() +DWORD ProcessRunner::exitCode() const { return m_exitCode; } -HANDLE ProcessRunner::processHandle() +HANDLE ProcessRunner::getProcessHandle() const { - return m_handle; + return m_handle.get(); } - -void ProcessRunner::withLock( - LockWidget::Reasons reason, std::function f) +env::HandlePtr ProcessRunner::stealProcessHandle() { - auto lock = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, reason); - - f(*lock); + return std::move(m_handle); } -ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { + m_lockReason = reason; + if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for process because user has disabled locking"); + // disabling locking is like clicking on unlock immediately return ForceUnlocked; } - return waitForApplication(handle, exitCode, reason); -} - -ProcessRunner::Results ProcessRunner::waitForApplication( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) -{ - // don't check for lockGUI() setting; this _always_ locks the ui and waits - // for completion - // - // this is typically called only from: - // 1) OrganizerProxy, which allows plugins to wait on applications until - // they're finished - // - // the check_fnis plugin for example will start FNIS, wait for it to - // complete, and then check the exit code; this has to work regardless of - // the locking setting; - // - // 2) waitForProcessCompletionWithLock() above, which has already checked the - // lock setting - auto r = Error; - withLock(reason, [&](auto& lock) { - r = waitForProcess(handle, exitCode, lock); - }); + withLock([&](auto& lock) { + for (;;) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.empty()) { + break; + } - return r; -} + r = waitForProcesses(processes, lock); -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - LockWidget::Reasons reason) -{ - if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for usvfs processes because user has disabled locking"); - return ForceUnlocked; - } + if (r != Completed) { + // error, cancelled, or unlocked + return; + } - auto r = Error; + // this process is completed, check for others + } - withLock(reason, [&](auto& lock) { - r = waitForAllUSVFSProcesses(lock); + r = Completed; }); return r; } -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) +void ProcessRunner::withLock(std::function f) { - for (;;) { - const auto processes = getRunningUSVFSProcesses(); - if (processes.empty()) { - break; - } - - const auto r = waitForProcesses(processes, lock); - - if (r != Completed) { - // error, cancelled, or unlocked - return r; - } - - // this process is completed, check for others - } + auto lk = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - return Completed; + f(*lk); } diff --git a/src/processrunner.h b/src/processrunner.h index 62ec7a9e..41c0f12c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -3,6 +3,7 @@ #include "spawn.h" #include "lockwidget.h" +#include "envmodule.h" #include class OrganizerCore; @@ -44,16 +45,25 @@ public: ForceUnlocked }; - enum RefreshModes + enum WaitFlag { - NoRefresh = 1, - Refresh + NoFlags = 0x00, + Refresh = 0x01, + ForceWait = 0x02 }; + using WaitFlags = QFlags; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); + // move only + ProcessRunner(ProcessRunner&&) = default; + ProcessRunner& operator=(const ProcessRunner&) = delete; + ProcessRunner(const ProcessRunner&) = delete; + ProcessRunner& operator=(ProcessRunner&&) = delete; + ProcessRunner& setBinary(const QFileInfo &binary); ProcessRunner& setArguments(const QString& arguments); ProcessRunner& setCurrentDirectory(const QDir& directory); @@ -62,7 +72,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -79,8 +89,9 @@ public: Results run(); Results attachToProcess(HANDLE h); - DWORD exitCode(); - HANDLE processHandle(); + DWORD exitCode() const; + HANDLE getProcessHandle() const; + env::HandlePtr stealProcessHandle(); Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); @@ -91,24 +102,16 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lock; - RefreshModes m_refresh; + LockWidget::Reasons m_lockReason; + WaitFlags m_waitFlags; QString m_shellOpen; - HANDLE m_handle; + env::HandlePtr m_handle; DWORD m_exitCode; Results postRun(); - - void withLock( - LockWidget::Reasons reason, std::function f); - - Results waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForAllUSVFSProcesses(LockWidget& lock); + void withLock(std::function f); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); + #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 719a2f20b4bb6b722097b63df8468d073d67e65f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:40:46 -0400 Subject: refresh ui after running an exe from 1) an external message, and 2) the data tab --- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2d91bcc..631a31d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 96ae84a8..3c535868 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } } -- cgit v1.3.1 From 72dd230cdc60e74446caceb5cfb4c6d32e4f6f68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:58:41 -0400 Subject: removed unused files fixed handle leak when starting steam --- src/CMakeLists.txt | 17 +----- src/ilockedwaitingforprocess.h | 14 ----- src/iuserinterface.h | 1 - src/lockeddialog.cpp | 71 ------------------------ src/lockeddialog.h | 57 -------------------- src/lockeddialog.ui | 98 --------------------------------- src/lockeddialogbase.cpp | 78 --------------------------- src/lockeddialogbase.h | 64 ---------------------- src/mainwindow.cpp | 1 - src/organizercore.cpp | 1 - src/spawn.cpp | 3 +- src/spawn.h | 2 - src/waitingonclosedialog.cpp | 71 ------------------------ src/waitingonclosedialog.h | 56 ------------------- src/waitingonclosedialog.ui | 119 ----------------------------------------- 15 files changed, 2 insertions(+), 651 deletions(-) delete mode 100644 src/ilockedwaitingforprocess.h delete mode 100644 src/lockeddialog.cpp delete mode 100644 src/lockeddialog.h delete mode 100644 src/lockeddialog.ui delete mode 100644 src/lockeddialogbase.cpp delete mode 100644 src/lockeddialogbase.h delete mode 100644 src/waitingonclosedialog.cpp delete mode 100644 src/waitingonclosedialog.h delete mode 100644 src/waitingonclosedialog.ui (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 168b79dc..06f2cd1e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,9 +82,6 @@ SET(organizer_SRCS main.cpp loghighlighter.cpp loglist.cpp - lockeddialogbase.cpp - lockeddialog.cpp - waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp filedialogmemory.cpp @@ -206,9 +203,6 @@ SET(organizer_HDRS mainwindow.h loghighlighter.h loglist.h - lockeddialogbase.h - lockeddialog.h - waitingonclosedialog.h loadmechanism.h installationmanager.h filedialogmemory.h @@ -246,7 +240,6 @@ SET(organizer_HDRS viewmarkingscrollbar.h plugincontainer.h organizercore.h - ilockedwaitingforprocess.h iuserinterface.h instancemanager.h usvfsconnector.h @@ -293,8 +286,6 @@ SET(organizer_UIS modinfodialog.ui messagedialog.ui mainwindow.ui - lockeddialog.ui - waitingonclosedialog.ui editexecutablesdialog.ui credentialsdialog.ui categoriesdialog.ui @@ -397,12 +388,6 @@ set(executables editexecutablesdialog ) -set(locking - ilockedwaitingforprocess - lockeddialog - lockeddialogbase -) - set(modinfo modinfo modinfobackup @@ -500,7 +485,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads env executables locking modinfo + application core browser dialogs downloads env executables modinfo modinfo\\dialog modlist plugins previews profiles settings settingsdialog utilities widgets ) diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h deleted file mode 100644 index 4d1e786f..00000000 --- a/src/ilockedwaitingforprocess.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ILOCKEDWAITINGFORPROCESS_H -#define ILOCKEDWAITINGFORPROCESS_H - -class QString; - -class ILockedWaitingForProcess -{ -public: - virtual bool unlockForced() const = 0; - virtual void setProcessName(QString const &) = 0; - virtual void setProcessInformation(DWORD pid, const QString& name) = 0; -}; - -#endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/iuserinterface.h b/src/iuserinterface.h index e5755f03..99caceb1 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,7 +3,6 @@ #include "modinfodialogfwd.h" -#include "ilockedwaitingforprocess.h" #include "lockwidget.h" #include #include diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp deleted file mode 100644 index 143d5838..00000000 --- a/src/lockeddialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialog.h" -#include "ui_lockeddialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) - : LockedDialogBase(parent, !unlockByButton) - , ui(new Ui::LockedDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); - - if (!unlockByButton) - { - ui->unlockButton->hide(); - ui->verticalLayout->addItem( - new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void LockedDialog::on_unlockButton_clicked() -{ - unlock(); -} - -void LockedDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->unlockButton->setEnabled(false); -} diff --git a/src/lockeddialog.h b/src/lockeddialog.h deleted file mode 100644 index 36c16429..00000000 --- a/src/lockeddialog.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); - ~LockedDialog(); - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_unlockButton_clicked(); - -private: - - Ui::LockedDialog *ui; -}; diff --git a/src/lockeddialog.ui b/src/lockeddialog.ui deleted file mode 100644 index 0ec2e467..00000000 --- a/src/lockeddialog.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - LockedDialog - - - - 0 - 0 - 317 - 151 - - - - Running virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - - - MO is locked while the executable is running. - - - Qt::AlignCenter - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - Unlock - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp deleted file mode 100644 index 0876a511..00000000 --- a/src/lockeddialogbase.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialogbase.h" -#include "envmodule.h" -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) - : QDialog(parent) - , m_Unlocked(false) - , m_Canceled(false) - , m_allowClose(allowClose) -{ - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } -} - -void LockedDialogBase::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialogBase::reject() -{ - if (m_allowClose) - unlock(); -} - -bool LockedDialogBase::unlockForced() const { - return m_Unlocked; -} - -bool LockedDialogBase::canceled() const { - return m_Canceled; -} - -void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name) -{ - setProcessName(QString("%1 (%2)").arg(name).arg(pid)); -} - -void LockedDialogBase::unlock() { - m_Unlocked = true; -} - -void LockedDialogBase::cancel() { - m_Canceled = true; -} - diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h deleted file mode 100644 index 4ebad4c7..00000000 --- a/src/lockeddialogbase.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString - -class QResizeEvent; -class QWidget; - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialogBase : public QDialog, public ILockedWaitingForProcess -{ - Q_OBJECT - -public: - explicit LockedDialogBase(QWidget *parent, bool allowClose); - - bool unlockForced() const override; - - virtual bool canceled() const; - - void setProcessInformation(DWORD pid, const QString& name) override; - -protected: - - virtual void resizeEvent(QResizeEvent *event); - - virtual void reject(); - - virtual void unlock(); - - virtual void cancel(); - - bool m_Unlocked; - bool m_Canceled; - bool m_allowClose; -}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 631a31d8..9453f07c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,7 +57,6 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "messagedialog.h" #include "installationmanager.h" -#include "waitingonclosedialog.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3c535868..dda60f76 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -29,7 +29,6 @@ #include "appconfig.h" #include #include -#include "lockeddialog.h" #include "instancemanager.h" #include #include "previewdialog.h" diff --git a/src/spawn.cpp b/src/spawn.cpp index b331db01..62745542 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,8 +27,6 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" -#include -#include #include #include #include @@ -645,6 +643,7 @@ bool startSteam(QWidget* parent) HANDLE ph = INVALID_HANDLE_VALUE; const auto e = spawn(sp, ph); + ::CloseHandle(ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown diff --git a/src/spawn.h b/src/spawn.h index 0464ffd6..9fb346b0 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -28,8 +28,6 @@ along with Mod Organizer. If not, see . class Settings; -namespace MOBase { class IPluginGame; } - namespace spawn { diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp deleted file mode 100644 index 565d0a36..00000000 --- a/src/waitingonclosedialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "waitingonclosedialog.h" -#include "ui_waitingonclosedialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) - : LockedDialogBase(parent,true) - , ui(new Ui::WaitingOnCloseDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); -} - -WaitingOnCloseDialog::~WaitingOnCloseDialog() -{ - delete ui; -} - - -void WaitingOnCloseDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void WaitingOnCloseDialog::on_closeButton_clicked() -{ - unlock(); -} - -void WaitingOnCloseDialog::on_cancelButton_clicked() -{ - cancel(); - unlock(); -} - -void WaitingOnCloseDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->closeButton->setEnabled(false); - ui->cancelButton->setEnabled(false); -} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h deleted file mode 100644 index 6650c390..00000000 --- a/src/waitingonclosedialog.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class WaitingOnCloseDialog; -} - -/** - * Similar to the LockedDialog but used for waiting on running process during - * a process close request which requries a slightly different dialog. - **/ -class WaitingOnCloseDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit WaitingOnCloseDialog(QWidget *parent = 0); - ~WaitingOnCloseDialog(); - - bool canceled() const { return m_Canceled; } - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_closeButton_clicked(); - void on_cancelButton_clicked(); - -private: - - Ui::WaitingOnCloseDialog *ui; -}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui deleted file mode 100644 index 9c7818e0..00000000 --- a/src/waitingonclosedialog.ui +++ /dev/null @@ -1,119 +0,0 @@ - - - WaitingOnCloseDialog - - - - 0 - 0 - 317 - 151 - - - - Waiting for virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. - - - Virtualized processes are still running, it is prefered to keep MO running until they are finished. - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - - Close Now - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Cancel - - - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - -- cgit v1.3.1 From 838446e262ac628c748b2e2b2f2ec0cfaff01508 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 19:41:17 -0500 Subject: split loot stuff to loot.h/cpp, no changes in functionality --- src/CMakeLists.txt | 3 + src/loot.cpp | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/loot.h | 10 +++ src/mainwindow.cpp | 227 +------------------------------------------------ src/mainwindow.h | 4 - 5 files changed, 261 insertions(+), 227 deletions(-) create mode 100644 src/loot.cpp create mode 100644 src/loot.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d935981d..70c501ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ SET(organizer_SRCS sanitychecks.cpp processrunner.cpp uilocker.cpp + loot.cpp shared/windows_error.cpp shared/error_report.cpp @@ -263,6 +264,7 @@ SET(organizer_HDRS colortable.h processrunner.h uilocker.h + loot.h shared/windows_error.h shared/error_report.h @@ -465,6 +467,7 @@ set(utilities shared/util usvfsconnector shared/windows_error + loot ) set(widgets diff --git a/src/loot.cpp b/src/loot.cpp new file mode 100644 index 00000000..b19c59ed --- /dev/null +++ b/src/loot.cpp @@ -0,0 +1,244 @@ +#include "spawn.h" +#include "organizercore.h" +#include +#include + +using namespace MOBase; + +void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = nullptr; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + log::error("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + log::error("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + +void processLOOTOut( + OrganizerCore& core, + const std::string &lootOut, std::string &errorMessages, + QProgressDialog &dialog) +{ + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + + std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); + + for (const std::string &line : lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t erroridx = line.find("[error]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (erroridx != std::string::npos) { + log::warn("{}", line); + errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + } else { + std::smatch match; + if (std::regex_match(line, match, exRequires)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + core.pluginList()->addInformation(modName.c_str(), QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else if (std::regex_match(line, match, exIncompatible)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + core.pluginList()->addInformation(modName.c_str(), QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + } else { + log::debug("[loot] {}", line); + } + } + } + } +} + +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +{ + std::string errorMessages; + + //m_OrganizerCore.currentProfile()->writeModlistNow(); + core.savePluginList(); + + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. + + bool success = false; + + try { + QProgressDialog dialog(parent); + + dialog.setLabelText(QObject::tr("Please wait while LOOT is running")); + dialog.setMaximum(0); + dialog.show(); + + QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters + << "--game" << core.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--out" << QString("\"%1\"").arg(outPath); + + if (didUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + + try { + core.prepareVFS(); + } catch (const UsvfsConnectorException &e) { + log::debug("{}", e.what()); + return false; + } catch (const std::exception &e) { + QMessageBox::warning(parent, QObject::tr("Error"), e.what()); + return false; + } + + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; + sp.stdOut = stdOutWrite; + + HANDLE loot = spawn::startBinary(parent, sp); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + core.pluginList()->clearAdditionalInformation(); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + HANDLE processHandle = loot; + + if (loot != INVALID_HANDLE_VALUE) { + bool isJobHandle = true; + ULONG lastProcessID; + DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (isJobHandle) { + if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + log::debug("no more processes in job"); + break; + } else { + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != loot) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + if (dialog.wasCanceled()) { + if (isJobHandle) { + ::TerminateJobObject(loot, 1); + } else { + ::TerminateProcess(loot, 1); + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + std::string lootOut = readFromPipe(stdOutRead); + processLOOTOut(core, lootOut, errorMessages, dialog); + + res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + } + + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + processLOOTOut(core, remainder, errorMessages, dialog); + } + + DWORD exitCode = 0UL; + ::GetExitCodeProcess(processHandle, &exitCode); + ::CloseHandle(processHandle); + if (exitCode != 0UL) { + reportError(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); + return false; + } else { + success = true; + QFile outFile(outPath); + outFile.open(QIODevice::ReadOnly); + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + core.pluginList()->addInformation(pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + } + if (pluginObj["dirty"].toString() == "yes") + core.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); + } + + } + } else { + reportError(QObject::tr("failed to start loot")); + } + } catch (const std::exception &e) { + reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + } + + if (errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox( + QMessageBox::Warning, QObject::tr("Errors occurred"), + errorMessages.c_str(), QMessageBox::Ok, parent); + + warn->setModal(false); + warn->show(); + } + + return success; +} diff --git a/src/loot.h b/src/loot.h new file mode 100644 index 00000000..2314e5b3 --- /dev/null +++ b/src/loot.h @@ -0,0 +1,10 @@ +#ifndef MODORGANIZER_LOOT_H +#define MODORGANIZER_LOOT_H + +#include + +class OrganizerCore; + +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + +#endif // MODORGANIZER_LOOT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9453f07c..11edfc81 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" - +#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -6369,233 +6369,14 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_OrganizerCore.downloadManager()->setShowHidden(checked); } - -void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; - - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - log::error("failed to create stdout reroute"); - } - - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - log::error("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; - } -} - -std::string MainWindow::readFromPipe(HANDLE stdOutRead) -{ - static const int chunkSize = 128; - std::string result; - - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; - - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; -} - -void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog) -{ - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); - - for (const std::string &line : lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (erroridx != std::string::npos) { - log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); - } else { - std::smatch match; - if (std::regex_match(line, match, exRequires)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::regex_match(line, match, exIncompatible)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); - } else { - log::debug("[loot] {}", line); - } - } - } - } -} - void MainWindow::on_bossButton_clicked() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); m_OrganizerCore.savePluginList(); - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - bool success = false; - - try { - setEnabled(false); - ON_BLOCK_EXIT([&] () { setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("Please wait while LOOT is running")); - dialog.setMaximum(0); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (m_DidUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - try { - m_OrganizerCore.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return; - } catch (const std::exception &e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); - return; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(this, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_OrganizerCore.pluginList()->clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; - - if (loot != INVALID_HANDLE_VALUE) { - bool isJobHandle = true; - ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - log::debug("no more processes in job"); - break; - } else { - if (lastProcessID != info.ProcessIdList[0]) { - lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { - ::CloseHandle(processHandle); - } - processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - if (dialog.wasCanceled()) { - if (isJobHandle) { - ::TerminateJobObject(loot, 1); - } else { - ::TerminateProcess(loot, 1); - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, errorMessages, dialog); - - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(processHandle, &exitCode); - ::CloseHandle(processHandle); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - QFile outFile(outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); - } - - } - } else { - reportError(tr("failed to start loot")); - } - } catch (const std::exception &e) { - reportError(tr("failed to run loot: %1").arg(e.what())); - } - - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occurred"), errorMessages.c_str(), QMessageBox::Ok, this); - warn->setModal(false); - warn->show(); - } + setEnabled(false); + ON_BLOCK_EXIT([&] () { setEnabled(true); }); - if (success) { + if (runLoot(this, m_OrganizerCore, m_DidUpdateMasterList)) { m_DidUpdateMasterList = true; m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index c80287b2..7c2ee1eb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -136,10 +136,6 @@ public: void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); QString getOriginDisplayName(int originID); -- cgit v1.3.1 From b8eab5d248272cab886bc800f2d619a49d8af54a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 22:25:55 -0500 Subject: threaded loot --- src/loot.cpp | 317 +++++++++++++++++++++++++++++++++++------------------ src/loot.h | 37 +++++++ src/mainwindow.cpp | 3 +- 3 files changed, 249 insertions(+), 108 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loot.cpp b/src/loot.cpp index a9e716ae..ab152b80 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,3 +1,4 @@ +#include "loot.h" #include "spawn.h" #include "organizercore.h" #include @@ -9,11 +10,35 @@ using namespace MOBase; class LootDialog : public QDialog { public: - LootDialog(QWidget* parent) : - QDialog(parent), m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), - m_cancelled(false) + LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : + QDialog(parent), m_core(core), m_loot(loot), + m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), m_finished(false) { createUI(); + + QObject::connect( + &m_loot, &Loot::output, this, + [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::progress, + this, [&](auto&& s){ setText(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::information, this, + [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::errorMessage, this, + [&](auto&& s){ onErrorMessage(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::error, this, + [&](auto&& s){ onError(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::finished, this, + [&]{ onFinished(); }, Qt::QueuedConnection); } void setText(const QString& s) @@ -39,18 +64,52 @@ public: } } - bool cancelled() const + void setInfo(const QString& mod, const QString& info) { - return m_cancelled; + m_core.pluginList()->addInformation(mod.toStdString().c_str(), info); + } + + bool result() const + { + return m_loot.result(); + } + + void cancel() + { + m_loot.cancel(); + } + + int exec() override + { + m_loot.start(); + QDialog::exec(); + + if (m_errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox( + QMessageBox::Warning, QObject::tr("Errors occurred"), + m_errorMessages, QMessageBox::Ok, parentWidget()); + + warn->exec(); + } + + return 0; + } + + void onError(const QString& s) + { + reportError(s); } private: + OrganizerCore& m_core; + Loot& m_loot; QLabel* m_label; QProgressBar* m_progress; QDialogButtonBox* m_buttons; QPlainTextEdit* m_output; QString m_lastLine; - bool m_cancelled; + QString m_errorMessages; + bool m_finished; void createUI() { @@ -77,13 +136,18 @@ private: void closeEvent(QCloseEvent* e) override { - m_cancelled = true; + if (m_finished) { + QDialog::closeEvent(e); + } else { + cancel(); + e->ignore(); + } } void onButton(QAbstractButton* b) { if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - m_cancelled = true; + cancel(); } } @@ -96,10 +160,83 @@ private: m_output->appendPlainText(line); m_lastLine = line; } + + void onFinished() + { + m_finished = true; + close(); + } + + void onErrorMessage(const QString& s) + { + m_errorMessages += s; + } }; -void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : + m_thread(nullptr), m_cancel(false), m_result(false), + m_lootProcess(INVALID_HANDLE_VALUE), m_stdOutRead(INVALID_HANDLE_VALUE) +{ + m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters + << "--game" << core.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--out" << QString("\"%1\"").arg(m_outPath); + + if (didUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + createStdoutPipe(&m_stdOutRead, &stdOutWrite); + + core.prepareVFS(); + + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; + sp.stdOut = stdOutWrite; + + m_lootProcess = spawn::startBinary(parent, sp); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + core.pluginList()->clearAdditionalInformation(); + + m_thread.reset(QThread::create([&]{ + lootThread(); + emit finished(); + })); +} + +Loot::~Loot() +{ + m_thread->wait(); +} + +void Loot::start() +{ + m_thread->start(); +} + +void Loot::cancel() +{ + m_cancel = true; +} + +bool Loot::result() const +{ + return m_result; +} + +void Loot::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) { SECURITY_ATTRIBUTES secAttributes; secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); @@ -116,7 +253,7 @@ void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) } } -std::string readFromPipe(HANDLE stdOutRead) +std::string Loot::readFromPipe(HANDLE stdOutRead) { static const int chunkSize = 128; std::string result; @@ -139,11 +276,9 @@ std::string readFromPipe(HANDLE stdOutRead) return result; } -void processLOOTOut( - OrganizerCore& core, - const std::string &lootOut, std::string &errorMessages, LootDialog& dialog) +void Loot::processLOOTOut(const std::string &lootOut) { - dialog.addOutput(QString::fromStdString(lootOut)); + emit output(QString::fromStdString(lootOut)); std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); @@ -156,20 +291,25 @@ void processLOOTOut( size_t progidx = line.find("[progress]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { - dialog.setText(line.substr(progidx + 11).c_str()); + emit progress(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + emit errorMessage(QString::fromStdString( + boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n")); } else { std::smatch match; if (std::regex_match(line, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); } else if (std::regex_match(line, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { log::debug("[loot] {}", line); } @@ -178,85 +318,29 @@ void processLOOTOut( } } -bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +void Loot::lootThread() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); - core.savePluginList(); - - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - - bool success = false; - try { - LootDialog dialog(parent); - - dialog.setText(QObject::tr("Please wait while LOOT is running")); - dialog.setIndeterminate(); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters - << "--game" << core.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (didUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - - try { - core.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return false; - } catch (const std::exception &e) { - QMessageBox::warning(parent, QObject::tr("Error"), e.what()); - return false; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(parent, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - core.pluginList()->clearAdditionalInformation(); + m_result = false; DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; + HANDLE processHandle = m_lootProcess; - if (loot != INVALID_HANDLE_VALUE) { + if (m_lootProcess != INVALID_HANDLE_VALUE) { bool isJobHandle = true; ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + DWORD res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (::QueryInformationJobObject(m_lootProcess, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { + if (processHandle != m_lootProcess) { ::CloseHandle(processHandle); } processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); @@ -273,36 +357,37 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } } - if (dialog.cancelled()) { + if (m_cancel) { if (isJobHandle) { - ::TerminateJobObject(loot, 1); + ::TerminateJobObject(m_lootProcess, 1); } else { - ::TerminateProcess(loot, 1); + ::TerminateProcess(m_lootProcess, 1); } } // keep processing events so the app doesn't appear dead QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(core, lootOut, errorMessages, dialog); + std::string lootOut = readFromPipe(m_stdOutRead); + processLOOTOut(lootOut); - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); } - std::string remainder = readFromPipe(stdOutRead).c_str(); + std::string remainder = readFromPipe(m_stdOutRead).c_str(); if (remainder.length() > 0) { - processLOOTOut(core, remainder, errorMessages, dialog); + processLOOTOut(remainder); } DWORD exitCode = 0UL; ::GetExitCodeProcess(processHandle, &exitCode); ::CloseHandle(processHandle); if (exitCode != 0UL) { - reportError(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); - return false; + emit error(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); + m_result = false; + return; } else { - success = true; - QFile outFile(outPath); + m_result = true; + QFile outFile(m_outPath); outFile.open(QIODevice::ReadOnly); QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); QJsonArray array = doc.array(); @@ -311,29 +396,47 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) QJsonArray pluginMessages = pluginObj["messages"].toArray(); for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { QJsonObject msg = (*msgIter).toObject(); - core.pluginList()->addInformation(pluginObj["name"].toString(), + emit information( + pluginObj["name"].toString(), QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); } - if (pluginObj["dirty"].toString() == "yes") - core.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); + if (pluginObj["dirty"].toString() == "yes") { + emit information(pluginObj["name"].toString(), "dirty"); + } } - } } else { - reportError(QObject::tr("failed to start loot")); + emit error(QObject::tr("failed to start loot")); } } catch (const std::exception &e) { - reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + emit error(QObject::tr("failed to run loot: %1").arg(e.what())); } +} - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox( - QMessageBox::Warning, QObject::tr("Errors occurred"), - errorMessages.c_str(), QMessageBox::Ok, parent); - warn->setModal(false); - warn->show(); - } +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +{ + //m_OrganizerCore.currentProfile()->writeModlistNow(); + core.savePluginList(); - return success; + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. + + try { + Loot loot(parent, core, didUpdateMasterList); + LootDialog dialog(parent, core, loot); + + dialog.setText(QObject::tr("Please wait while LOOT is running")); + dialog.setIndeterminate(); + dialog.exec(); + + return dialog.result(); + } catch (const UsvfsConnectorException &e) { + log::debug("{}", e.what()); + return false; + } catch (const std::exception &e) { + reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + return false; + } } diff --git a/src/loot.h b/src/loot.h index 2314e5b3..11f1dbc8 100644 --- a/src/loot.h +++ b/src/loot.h @@ -1,10 +1,47 @@ #ifndef MODORGANIZER_LOOT_H #define MODORGANIZER_LOOT_H +#include #include class OrganizerCore; +class Loot : public QObject +{ + Q_OBJECT; + +public: + Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + ~Loot(); + + void start(); + void cancel(); + bool result() const; + +signals: + void output(const QString& s); + void progress(const QString& s); + void information(const QString& mod, const QString& info); + void errorMessage(const QString& s); + void error(const QString& s); + void finished(); + +private: + std::unique_ptr m_thread; + std::atomic m_cancel; + std::atomic m_result; + QString m_outPath; + HANDLE m_lootProcess; + HANDLE m_stdOutRead; + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + + void processLOOTOut(const std::string &lootOut); + void lootThread(); +}; + + bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); #endif // MODORGANIZER_LOOT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 11edfc81..9ea554a2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,6 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" -#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -191,6 +190,8 @@ const QSize SmallToolbarSize(24, 24); const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore -- cgit v1.3.1 From 9194bfa16bb78c10bc4e23abd26ba16c33956794 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:59:04 -0500 Subject: added option to hide confirmation when switching instances --- src/mainwindow.cpp | 16 ++++++++++------ src/settings.cpp | 10 ++++++++++ src/settings.h | 5 +++++ src/settingsdialog.ui | 7 +++++++ src/settingsdialoggeneral.cpp | 2 ++ 5 files changed, 34 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9ea554a2..844456a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6139,14 +6139,18 @@ void MainWindow::on_actionNotifications_triggered() void MainWindow::on_actionChange_Game_triggered() { - const auto r = QMessageBox::question( - this, tr("Are you sure?"), tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel); + if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { + const auto r = QMessageBox::question( + this, tr("Are you sure?"), tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel); - if (r == QMessageBox::Yes) { - InstanceManager::instance().clearCurrentInstance(); - ExitModOrganizer(Exit::Restart); + if (r != QMessageBox::Yes) { + return; + } } + + InstanceManager::instance().clearCurrentInstance(); + ExitModOrganizer(Exit::Restart); } void MainWindow::setCategoryListVisible(bool visible) diff --git a/src/settings.cpp b/src/settings.cpp index b533b400..e1e5c2da 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1961,6 +1961,16 @@ void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b) set(m_Settings, "CompletedWindowTutorials", windowName, b); } +bool InterfaceSettings::showChangeGameConfirmation() const +{ + return get(m_Settings, "Settings", "show_change_game_confirmation", true); +} + +void InterfaceSettings::setShowChangeGameConfirmation(bool b) const +{ + set(m_Settings, "Settings", "show_change_game_confirmation", b); +} + DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) : m_Settings(settings) diff --git a/src/settings.h b/src/settings.h index d71fabf4..870e0fc4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -605,6 +605,11 @@ public: bool isTutorialCompleted(const QString& windowName) const; void setTutorialCompleted(const QString& windowName, bool b=true); + // whether to show the confirmation when switching instances + // + bool showChangeGameConfirmation() const; + void setShowChangeGameConfirmation(bool b) const; + private: QSettings& m_Settings; }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b88c8b71..e63ca692 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -108,6 +108,13 @@ + + + + Show confirmation when changing instance + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index e21fc5d0..b0e64305 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -17,6 +17,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->colorTable->load(s); ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); + ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->checkForUpdates->setChecked(settings().checkForUpdates()); @@ -59,6 +60,7 @@ void GeneralSettingsTab::update() ui->colorTable->commitColors(); settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); + settings().interface().setShowChangeGameConfirmation(ui->changeGameConfirmation->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); -- cgit v1.3.1 From f85478a5b40ba609a0b36a2095e2b7fd3588857f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 10:00:37 -0500 Subject: fix sort in overwrite, remember settings remove useless header in archives list --- src/mainwindow.cpp | 1 + src/overwriteinfodialog.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 844456a6..79604c9a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -277,6 +277,7 @@ MainWindow::MainWindow(Settings &settings ui->espList->installEventFilter(m_OrganizerCore.pluginList()); ui->bsaList->setLocalMoveOnly(true); + ui->bsaList->setHeaderHidden(true); initDownloadView(); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 078bcfc9..b2f2f1f9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -85,6 +85,7 @@ OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) ui->filesView->setModel(m_FileSystemModel); ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); ui->filesView->setColumnWidth(0, 250); + ui->filesView->sortByColumn(0, Qt::AscendingOrder); m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); m_RenameAction = new QAction(tr("&Rename"), ui->filesView); @@ -106,13 +107,21 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - Settings::instance().geometry().restoreGeometry(this); + const auto& s = Settings::instance(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(ui->filesView->header()); + QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - Settings::instance().geometry().saveGeometry(this); + auto& s = Settings::instance(); + + s.geometry().saveGeometry(this); + s.geometry().saveState(ui->filesView->header()); + QDialog::done(r); } -- cgit v1.3.1 From 9519bd62193ac1233e8087c4c143689557288c34 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 10:31:26 -0500 Subject: added open mod info to data tab context menu --- src/mainwindow.cpp | 23 +++++++++++++++++++++++ src/mainwindow.h | 1 + 2 files changed, 24 insertions(+) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 79604c9a..06e22e4e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5428,6 +5428,27 @@ void MainWindow::openDataOriginExplorer_clicked() shell::Explore(fullPath); } +void MainWindow::openDataModInfo_clicked() +{ + if (m_ContextItem == nullptr) { + return; + } + + const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt(); + const auto& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + const auto& name = QString::fromStdWString(origin.getName()); + + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo) { + displayModInformation(modInfo, index, ModInfoTabIDs::None); + } +} + void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -5473,6 +5494,8 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } + menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked())); + // offer to hide/unhide file, but not for files from archives if (!isArchive) { if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 7c2ee1eb..0c96c15d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -448,6 +448,7 @@ private slots: void hideFile(); void unhideFile(); void openDataOriginExplorer_clicked(); + void openDataModInfo_clicked(); // pluginlist context menu void enableSelectedPlugins_clicked(); -- cgit v1.3.1 From 21cddd0ad3cef165c3860c2031b4cd00802c499d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 12:05:11 -0500 Subject: changed mod name of unmanaged files from "data" to "" in the data tab and for the root item in the archives tab log error when trying to open mod info for a managed file but the mod isn't found --- src/mainwindow.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 06e22e4e..e134d64a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -190,6 +190,11 @@ const QSize SmallToolbarSize(24, 24); const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +QString UnmanagedModName() +{ + return QObject::tr(""); +} + bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); @@ -1661,9 +1666,13 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director QString fileName = ToQString(current->getName()); QStringList columns(fileName); FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString source("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { + + QString source; + const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + + if (modIndex == UINT_MAX) { + source = UnmanagedModName(); + } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); source = modInfo->name(); } @@ -2046,12 +2055,17 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString int originID = iter->second->data(1, Qt::UserRole).toInt(); FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString modName("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { + + QString modName; + const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + + if (modIndex == UINT_MAX) { + modName = UnmanagedModName(); + } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modName = modInfo->name(); } + QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); QTreeWidgetItem * subItem = nullptr; if (items.length() > 0) { @@ -5435,11 +5449,17 @@ void MainWindow::openDataModInfo_clicked() } const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt(); + if (originID == 0) { + // unmanaged + return; + } + const auto& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); const auto& name = QString::fromStdWString(origin.getName()); unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { + log::error("can't open mod info, mod '{}' not found", name); return; } -- cgit v1.3.1 From d5e38fca6b3a8c7bf90c5a3d8ec779752a22c61d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:05:20 -0500 Subject: added not filter, not functional yet fixed no mods being displayed for OR with no conditions --- src/mainwindow.cpp | 5 +++++ src/mainwindow.h | 1 + src/mainwindow.ui | 7 +++++++ src/modlistsortproxy.cpp | 17 ++++++++++++----- src/modlistsortproxy.h | 4 ++-- 5 files changed, 27 insertions(+), 7 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e134d64a..21606fa9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6552,6 +6552,11 @@ void MainWindow::on_categoriesOrBtn_toggled(bool checked) } } +void MainWindow::on_categoriesNotBtn_toggled(bool checked) +{ + m_ModListSortProxy->setFilterNot(checked); +} + void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index 0c96c15d..cbf45635 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -659,6 +659,7 @@ private slots: // ui slots void on_saveModsButton_clicked(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); + void on_categoriesNotBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 723b42fe..cd9cbc4b 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -152,6 +152,13 @@ + + + + Not + + + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a9ff6463..805e77f4 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -36,10 +36,9 @@ using namespace MOBase; ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) , m_Profile(profile) - , m_CategoryFilter() - , m_CurrentFilter() , m_FilterActive(false) , m_FilterMode(FILTER_AND) + , m_FilterNot(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -312,8 +311,8 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false; } break; case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { - if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && - !info->hasFlag(ModInfo::FLAG_BACKUP) && + if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && !info->hasFlag(ModInfo::FLAG_SEPARATOR) && !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false; } break; @@ -381,7 +380,7 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const if (info->hasContent(static_cast(content))) return true; } - return false; + return m_CategoryFilter.empty() && m_ContentFilter.empty(); } bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const @@ -487,6 +486,14 @@ void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) } } +void ModListSortProxy::setFilterNot(bool b) +{ + if (b != m_FilterNot) { + m_FilterNot = b; + this->invalidate(); + } +} + bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == nullptr) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 5fe8d9d6..2e3e5709 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -85,6 +85,7 @@ public: bool isFilterActive() const { return m_FilterActive; } void setFilterMode(FilterMode mode); + void setFilterNot(bool b); /** * @brief tests if the specified index has child nodes @@ -129,9 +130,7 @@ private slots: void postDataChanged(); private: - Profile *m_Profile; - std::vector m_CategoryFilter; std::vector m_ContentFilter; std::bitset m_EnabledColumns; @@ -139,6 +138,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; + bool m_FilterNot; std::vector m_PreChangeFilters; -- cgit v1.3.1 From 17452071c9b72a48498e7578d65b9b52729f914f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:30:30 -0500 Subject: added separators filter changed notendorsed filter to include anything else than true --- src/mainwindow.cpp | 5 +++++ src/mainwindow.h | 1 + src/mainwindow.ui | 7 +++++++ src/modlistsortproxy.cpp | 20 ++++++++++++++++++-- src/modlistsortproxy.h | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 21606fa9..d5636ab9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6557,6 +6557,11 @@ void MainWindow::on_categoriesNotBtn_toggled(bool checked) m_ModListSortProxy->setFilterNot(checked); } +void MainWindow::on_categoriesSeparators_toggled(bool checked) +{ + m_ModListSortProxy->setFilterSeparators(checked); +} + void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index cbf45635..c99c724b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -660,6 +660,7 @@ private slots: // ui slots void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_categoriesNotBtn_toggled(bool checked); + void on_categoriesSeparators_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index cd9cbc4b..9648a586 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -159,6 +159,13 @@ + + + + Separators + + + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 0984d415..ddae675c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -39,6 +39,7 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_FilterActive(false) , m_FilterMode(FILTER_AND) , m_FilterNot(false) + , m_FilterSeparators(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -278,6 +279,10 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + if (!categoryMatchesMod(info, enabled, *iter)) { return false; } @@ -295,6 +300,10 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + if (categoryMatchesMod(info, enabled, *iter)) { return true; } @@ -332,7 +341,7 @@ bool ModListSortProxy::categoryMatchesMod( case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { ModInfo::EEndorsedState state = info->endorsedState(); - return ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)); + return (state != ModInfo::ENDORSED_TRUE); } case CategoryFactory::CATEGORY_SPECIAL_BACKUP: @@ -353,7 +362,6 @@ bool ModListSortProxy::categoryMatchesMod( info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && !info->hasFlag(ModInfo::FLAG_BACKUP) && - !info->hasFlag(ModInfo::FLAG_SEPARATOR) && !info->hasFlag(ModInfo::FLAG_OVERWRITE)); } @@ -480,6 +488,14 @@ void ModListSortProxy::setFilterNot(bool b) } } +void ModListSortProxy::setFilterSeparators(bool b) +{ + if (b != m_FilterSeparators) { + m_FilterSeparators = b; + this->invalidate(); + } +} + bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == nullptr) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 17888ae6..2ebfbcf0 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -86,6 +86,7 @@ public: void setFilterMode(FilterMode mode); void setFilterNot(bool b); + void setFilterSeparators(bool b); /** * @brief tests if the specified index has child nodes @@ -139,6 +140,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; bool m_FilterNot; + bool m_FilterSeparators; std::vector m_PreChangeFilters; -- cgit v1.3.1 From 4bbdbb000fd5051fe80b5dca21dda60910284333 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 19:14:37 -0500 Subject: split filter list --- src/CMakeLists.txt | 3 + src/filterlist.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filterlist.h | 42 ++++++++++++ src/mainwindow.cpp | 177 ++++--------------------------------------------- src/mainwindow.h | 12 +--- 5 files changed, 249 insertions(+), 174 deletions(-) create mode 100644 src/filterlist.cpp create mode 100644 src/filterlist.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3a42409..a46908ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ SET(organizer_SRCS uilocker.cpp loot.cpp lootdialog.cpp + filterlist.cpp shared/windows_error.cpp shared/error_report.cpp @@ -268,6 +269,7 @@ SET(organizer_HDRS loot.h lootdialog.h json.h + filterlist.h shared/windows_error.h shared/error_report.h @@ -320,6 +322,7 @@ SET(organizer_RCS source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)") set(application + filterlist iuserinterface main mainwindow diff --git a/src/filterlist.cpp b/src/filterlist.cpp new file mode 100644 index 00000000..8ef4b62d --- /dev/null +++ b/src/filterlist.cpp @@ -0,0 +1,189 @@ +#include "filterlist.h" +#include "ui_mainwindow.h" +#include "categories.h" +#include "categoriesdialog.h" +#include + +using namespace MOBase; + +FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) + : ui(ui), m_factory(factory) +{ + QObject::connect( + ui->categoriesList, &QTreeWidget::customContextMenuRequested, + [&](auto&& pos){ onContextMenu(pos); }); + + QObject::connect( + ui->categoriesList, &QTreeWidget::itemSelectionChanged, + [&]{ onSelection(); }); +} + +QTreeWidgetItem* FilterList::addFilterItem( + QTreeWidgetItem *root, const QString &name, int categoryID, + ModListSortProxy::FilterType type) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::ToolTipRole, name); + item->setData(0, Qt::UserRole, categoryID); + item->setData(0, Qt::UserRole + 1, type); + if (root != nullptr) { + root->addChild(item); + } else { + ui->categoriesList->addTopLevelItem(item); + } + return item; +} + +void FilterList::addContentFilters() +{ + for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { + addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); + } +} + +void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +{ + for (unsigned int i = 1; + i < static_cast(m_factory.numCategories()); ++i) { + if ((m_factory.getParentID(i) == targetID)) { + int categoryID = m_factory.getCategoryID(i); + if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { + QTreeWidgetItem *item = + addFilterItem(root, m_factory.getCategoryName(i), + categoryID, ModListSortProxy::TYPE_CATEGORY); + if (m_factory.hasChildren(i)) { + addCategoryFilters(item, categoriesUsed, categoryID); + } + } + } + } +} + +void FilterList::refresh() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + QVariant currentIndexName = ui->modList->currentIndex().data(); + ui->modList->setCurrentIndex(QModelIndex()); + + QStringList selectedItems; + for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { + selectedItems.append(item->text(0)); + } + + ui->categoriesList->clear(); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); + + addContentFilters(); + std::set categoriesUsed; + for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); + for (int categoryID : modInfo->getCategories()) { + int currentID = categoryID; + std::set cycleTest; + // also add parents so they show up in the tree + while (currentID != 0) { + categoriesUsed.insert(currentID); + if (!cycleTest.insert(currentID).second) { + log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); + break; + } + currentID = m_factory.getParentID(m_factory.getCategoryIndex(currentID)); + } + } + } + + addCategoryFilters(nullptr, categoriesUsed, 0); + + for (const QString &item : selectedItems) { + QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { + matches.at(0)->setSelected(true); + } + } + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + +void FilterList::setSelection(std::vector categories) +{ + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } +} + +void FilterList::clearSelection() +{ + ui->categoriesList->clearSelection(); +} + +void FilterList::onSelection() +{ + QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + std::vector categories; + std::vector content; + for (const QModelIndex &index : indices) { + int filterType = index.data(Qt::UserRole + 1).toInt(); + if ((filterType == ModListSortProxy::TYPE_CATEGORY) + || (filterType == ModListSortProxy::TYPE_SPECIAL)) { + int categoryId = index.data(Qt::UserRole).toInt(); + if (categoryId != CategoryFactory::CATEGORY_NONE) { + categories.push_back(categoryId); + } + } else if (filterType == ModListSortProxy::TYPE_CONTENT) { + int contentId = index.data(Qt::UserRole).toInt(); + content.push_back(contentId); + } + } + + emit changed(categories, content); + + ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); + + if (indices.count() == 0) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else if (indices.count() > 1) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else { + ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); + } + ui->modList->reset(); +} + +void FilterList::onContextMenu(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); + menu.addAction(tr("Deselect filter"), [&]{ clearSelection(); }); + + menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); +} + +void FilterList::editCategories() +{ + CategoriesDialog dialog(qApp->activeWindow()); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} diff --git a/src/filterlist.h b/src/filterlist.h new file mode 100644 index 00000000..ca74021a --- /dev/null +++ b/src/filterlist.h @@ -0,0 +1,42 @@ +#ifndef MODORGANIZER_CATEGORIESLIST_INCLUDED +#define MODORGANIZER_CATEGORIESLIST_INCLUDED + +#include "modlistsortproxy.h" +#include + +namespace Ui { class MainWindow; }; +class CategoryFactory; + +class FilterList : public QObject +{ + Q_OBJECT; + +public: + FilterList(Ui::MainWindow* ui, CategoryFactory& factory); + + void setSelection(std::vector categories); + void clearSelection(); + void refresh(); + +signals: + void changed(std::vector categories, std::vector content); + +private: + Ui::MainWindow* ui; + CategoryFactory& m_factory; + + void onContextMenu(const QPoint &pos); + void onSelection(); + + void editCategories(); + + QTreeWidgetItem* addFilterItem( + QTreeWidgetItem *root, const QString &name, int categoryID, + ModListSortProxy::FilterType type); + + void addContentFilters(); + void addCategoryFilters( + QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); +}; + +#endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5636ab9..b63f9211 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -75,6 +75,7 @@ along with Mod Organizer. If not, see . #include "appconfig.h" #include "eventfilter.h" #include "statusbar.h" +#include "filterlist.h" #include #include #include @@ -260,6 +261,11 @@ MainWindow::MainWindow(Settings &settings languageChange(settings.interface().language()); m_CategoryFactory.loadCategories(); + m_Filters.reset(new FilterList(ui, m_CategoryFactory)); + connect(m_Filters.get(), &FilterList::changed, [&](auto&& cats, auto&& content) { + m_ModListSortProxy->setCategoryFilter(cats); + m_ModListSortProxy->setContentFilter(content); + }); ui->logList->setCore(m_OrganizerCore); @@ -1223,7 +1229,7 @@ void MainWindow::showEvent(QShowEvent *event) if (!m_WasVisible) { readSettings(); - refreshFilters(); + m_Filters->refresh(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which @@ -2646,108 +2652,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName } } -QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type) -{ - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); - item->setData(0, Qt::ToolTipRole, name); - item->setData(0, Qt::UserRole, categoryID); - item->setData(0, Qt::UserRole + 1, type); - if (root != nullptr) { - root->addChild(item); - } else { - ui->categoriesList->addTopLevelItem(item); - } - return item; -} - -void MainWindow::addContentFilters() -{ - for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); - } -} - -void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) -{ - for (unsigned int i = 1; - i < static_cast(m_CategoryFactory.numCategories()); ++i) { - if ((m_CategoryFactory.getParentID(i) == targetID)) { - int categoryID = m_CategoryFactory.getCategoryID(i); - if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { - QTreeWidgetItem *item = - addFilterItem(root, m_CategoryFactory.getCategoryName(i), - categoryID, ModListSortProxy::TYPE_CATEGORY); - if (m_CategoryFactory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); - } - } - } - } -} - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - QVariant currentIndexName = ui->modList->currentIndex().data(); - ui->modList->setCurrentIndex(QModelIndex()); - - QStringList selectedItems; - for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { - selectedItems.append(item->text(0)); - } - - ui->categoriesList->clear(); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); - - addContentFilters(); - std::set categoriesUsed; - for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); - for (int categoryID : modInfo->getCategories()) { - int currentID = categoryID; - std::set cycleTest; - // also add parents so they show up in the tree - while (currentID != 0) { - categoriesUsed.insert(currentID); - if (!cycleTest.insert(currentID).second) { - log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); - break; - } - currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); - } - } - } - - addCategoryFilters(nullptr, categoriesUsed, 0); - - for (const QString &item : selectedItems) { - QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); - if (matches.size() > 0) { - matches.at(0)->setSelected(true); - } - } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } -} - void MainWindow::renameMod_clicked() { @@ -4121,7 +4025,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - refreshFilters(); + m_Filters->refresh(); } void MainWindow::replaceCategories_MenuHandler() { @@ -4165,7 +4069,7 @@ void MainWindow::replaceCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - refreshFilters(); + m_Filters->refresh(); } void MainWindow::saveArchiveList() @@ -4217,12 +4121,7 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } + m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); } } @@ -4848,40 +4747,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } -void MainWindow::on_categoriesList_itemSelectionChanged() -{ - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); - std::vector categories; - std::vector content; - for (const QModelIndex &index : indices) { - int filterType = index.data(Qt::UserRole + 1).toInt(); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) - || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - int contentId = index.data(Qt::UserRole).toInt(); - content.push_back(contentId); - } - } - - m_ModListSortProxy->setCategoryFilter(categories); - m_ModListSortProxy->setContentFilter(content); - ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); - - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); -} - - void MainWindow::deleteSavegame_clicked() { SaveGameInfo const *info = m_OrganizerCore.managedGame()->feature(); @@ -5071,7 +4936,7 @@ void MainWindow::on_actionSettings_triggered() instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); - refreshFilters(); + m_Filters->refresh(); if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); @@ -6213,27 +6078,9 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::editCategories() -{ - CategoriesDialog dialog(this); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - void MainWindow::deselectFilters() { - ui->categoriesList->clearSelection(); -} - -void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); - menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); - - menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); + m_Filters->clearSelection(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index c99c724b..04ba7a35 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . class Executable; class CategoryFactory; class OrganizerCore; +class FilterList; class PluginListSortProxy; namespace BSA { class Archive; } @@ -235,8 +236,6 @@ private: void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - void refreshFilters(); - /** * Sets category selections from menu; for multiple mods, this will only apply * the changes made in the menu (which is the delta between the current menu selection and the reference mod) @@ -266,10 +265,6 @@ private: size_t checkForProblems(); - QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); - void addContentFilters(); - void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - void setCategoryListVisible(bool visible); void displaySaveGameInfo(QListWidgetItem *newItem); @@ -327,6 +322,8 @@ private: MOBase::TutorialControl m_Tutorial; + std::unique_ptr m_Filters; + int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure @@ -515,7 +512,6 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); - void editCategories(); void deselectFilters(); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); @@ -630,7 +626,6 @@ private slots: // ui slots void on_clearFiltersButton_clicked(); void on_btnRefreshData_clicked(); void on_btnRefreshDownloads_clicked(); - void on_categoriesList_customContextMenuRequested(const QPoint &pos); void on_conflictsCheckBox_toggled(bool checked); void on_showArchiveDataCheckBox_toggled(bool checked); void on_dataTree_customContextMenuRequested(const QPoint &pos); @@ -647,7 +642,6 @@ private slots: // ui slots void on_espList_customContextMenuRequested(const QPoint &pos); void on_displayCategoriesBtn_toggled(bool checked); void on_groupCombo_currentIndexChanged(int index); - void on_categoriesList_itemSelectionChanged(); void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); -- cgit v1.3.1 From 93318a1474031035da5e61ad199171cad5803c2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 29 Nov 2019 23:50:33 -0500 Subject: moved all remaining filter stuff to FilterList renamed some widgets --- src/categories.cpp | 34 ++++++++++++++ src/categories.h | 2 + src/filterlist.cpp | 135 +++++++++++++++++++++++++++++++---------------------- src/filterlist.h | 6 ++- src/mainwindow.cpp | 107 +++++++++++++++++++++++++++++------------- src/mainwindow.h | 7 ++- src/mainwindow.ui | 12 ++--- 7 files changed, 204 insertions(+), 99 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 12b18998..082b4fbc 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -320,6 +320,40 @@ QString CategoryFactory::getCategoryName(unsigned int index) const return m_Categories[index].m_Name; } +QString CategoryFactory::getSpecialCategoryName(int type) const +{ + switch (type) + { + case CATEGORY_SPECIAL_CHECKED: return QObject::tr(""); + case CATEGORY_SPECIAL_UNCHECKED: return QObject::tr(""); + case CATEGORY_SPECIAL_UPDATEAVAILABLE: return QObject::tr(""); + case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); + case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); + case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); + case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); + case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); + case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); + default: return {}; + } +} + +QString CategoryFactory::getCategoryNameByID(int id) const +{ + auto itor = m_IDMap.find(id); + + if (itor == m_IDMap.end()) { + return getSpecialCategoryName(id); + } else { + const auto index = itor->second; + if (index >= m_Categories.size()) { + return {}; + } + + return m_Categories[index].m_Name; + } +} int CategoryFactory::getCategoryID(unsigned int index) const { diff --git a/src/categories.h b/src/categories.h index 67fee3e7..2041ce1f 100644 --- a/src/categories.h +++ b/src/categories.h @@ -144,6 +144,8 @@ public: * @return QString name of the category **/ QString getCategoryName(unsigned int index) const; + QString getSpecialCategoryName(int type) const; + QString getCategoryNameByID(int id) const; /** * @brief look up the id of a category by its index diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 8ef4b62d..5562736d 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -9,13 +9,33 @@ using namespace MOBase; FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { - QObject::connect( - ui->categoriesList, &QTreeWidget::customContextMenuRequested, + connect( + ui->filters, &QTreeWidget::customContextMenuRequested, [&](auto&& pos){ onContextMenu(pos); }); - QObject::connect( - ui->categoriesList, &QTreeWidget::itemSelectionChanged, + connect( + ui->filters, &QTreeWidget::itemSelectionChanged, [&]{ onSelection(); }); + + connect( + ui->filtersClear, &QPushButton::clicked, + [&]{ clearSelection(); }); + + connect( + ui->filtersAnd, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersOr, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersNot, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersSeparators, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); } QTreeWidgetItem* FilterList::addFilterItem( @@ -29,7 +49,7 @@ QTreeWidgetItem* FilterList::addFilterItem( if (root != nullptr) { root->addChild(item); } else { - ui->categoriesList->addTopLevelItem(item); + ui->filters->addTopLevelItem(item); } return item; } @@ -37,7 +57,9 @@ QTreeWidgetItem* FilterList::addFilterItem( void FilterList::addContentFilters() { for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); + addFilterItem( + nullptr, tr("").arg(ModInfo::getContentTypeName(i)), + i, ModListSortProxy::TYPE_CONTENT); } } @@ -59,32 +81,37 @@ void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set & } } -void FilterList::refresh() +void FilterList::addSpecialFilterItem(int type) { - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - QVariant currentIndexName = ui->modList->currentIndex().data(); - ui->modList->setCurrentIndex(QModelIndex()); + addFilterItem( + nullptr, m_factory.getSpecialCategoryName(type), + type, ModListSortProxy::TYPE_SPECIAL); +} +void FilterList::refresh() +{ QStringList selectedItems; - for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { + for (QTreeWidgetItem *item : ui->filters->selectedItems()) { selectedItems.append(item->text(0)); } - ui->categoriesList->clear(); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); + ui->filters->clear(); + + using F = CategoryFactory; + addSpecialFilterItem(F::CATEGORY_SPECIAL_CHECKED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UNCHECKED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); + addSpecialFilterItem(F::CATEGORY_SPECIAL_BACKUP); + addSpecialFilterItem(F::CATEGORY_SPECIAL_MANAGED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UNMANAGED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOCATEGORY); + addSpecialFilterItem(F::CATEGORY_SPECIAL_CONFLICT); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOTENDORSED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NONEXUSID); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOGAMEDATA); addContentFilters(); + std::set categoriesUsed; for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); @@ -106,27 +133,20 @@ void FilterList::refresh() addCategoryFilters(nullptr, categoriesUsed, 0); for (const QString &item : selectedItems) { - QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + QList matches = ui->filters->findItems( + item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { matches.at(0)->setSelected(true); } } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } } void FilterList::setSelection(std::vector categories) { - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { + if (ui->filters->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } } @@ -134,40 +154,32 @@ void FilterList::setSelection(std::vector categories) void FilterList::clearSelection() { - ui->categoriesList->clearSelection(); + ui->filters->clearSelection(); } void FilterList::onSelection() { - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); std::vector categories; std::vector content; + for (const QModelIndex &index : indices) { - int filterType = index.data(Qt::UserRole + 1).toInt(); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) - || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - int categoryId = index.data(Qt::UserRole).toInt(); + const int filterType = index.data(Qt::UserRole + 1).toInt(); + + if ((filterType == ModListSortProxy::TYPE_CATEGORY) || (filterType == ModListSortProxy::TYPE_SPECIAL)) { + const int categoryId = index.data(Qt::UserRole).toInt(); if (categoryId != CategoryFactory::CATEGORY_NONE) { categories.push_back(categoryId); } } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - int contentId = index.data(Qt::UserRole).toInt(); + const int contentId = index.data(Qt::UserRole).toInt(); content.push_back(contentId); } } - emit changed(categories, content); - - ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); + ui->filtersClear->setEnabled(categories.size() > 0 || content.size() >0); - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); + emit filtersChanged(categories, content); } void FilterList::onContextMenu(const QPoint &pos) @@ -176,7 +188,7 @@ void FilterList::onContextMenu(const QPoint &pos) menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); menu.addAction(tr("Deselect filter"), [&]{ clearSelection(); }); - menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); + menu.exec(ui->filters->viewport()->mapToGlobal(pos)); } void FilterList::editCategories() @@ -187,3 +199,14 @@ void FilterList::editCategories() dialog.commitChanges(); } } + +void FilterList::onCriteriaChanged() +{ + const auto mode = ui->filtersAnd->isChecked() ? + ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; + + const bool inverse = ui->filtersNot->isChecked(); + const bool separators = ui->filtersSeparators->isChecked(); + + emit criteriaChanged(mode, inverse, separators); +} diff --git a/src/filterlist.h b/src/filterlist.h index ca74021a..85982392 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -19,7 +19,8 @@ public: void refresh(); signals: - void changed(std::vector categories, std::vector content); + void filtersChanged(std::vector categories, std::vector content); + void criteriaChanged(ModListSortProxy::FilterMode mode, bool inverse, bool separators); private: Ui::MainWindow* ui; @@ -27,6 +28,7 @@ private: void onContextMenu(const QPoint &pos); void onSelection(); + void onCriteriaChanged(); void editCategories(); @@ -37,6 +39,8 @@ private: void addContentFilters(); void addCategoryFilters( QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); + void addSpecialFilterItem(int type); + }; #endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b63f9211..1319d906 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -262,10 +262,14 @@ MainWindow::MainWindow(Settings &settings m_CategoryFactory.loadCategories(); m_Filters.reset(new FilterList(ui, m_CategoryFactory)); - connect(m_Filters.get(), &FilterList::changed, [&](auto&& cats, auto&& content) { - m_ModListSortProxy->setCategoryFilter(cats); - m_ModListSortProxy->setContentFilter(content); - }); + + connect( + m_Filters.get(), &FilterList::filtersChanged, + [&](auto&& cats, auto&& content) { onFilters(cats, content); }); + + connect( + m_Filters.get(), &FilterList::criteriaChanged, + [&](auto mode, bool inv, bool sep) { onFiltersCriteria(mode, inv, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -1229,7 +1233,7 @@ void MainWindow::showEvent(QShowEvent *event) if (!m_WasVisible) { readSettings(); - m_Filters->refresh(); + refreshFilters(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which @@ -4025,7 +4029,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - m_Filters->refresh(); + refreshFilters(); } void MainWindow::replaceCategories_MenuHandler() { @@ -4069,7 +4073,7 @@ void MainWindow::replaceCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - m_Filters->refresh(); + refreshFilters(); } void MainWindow::saveArchiveList() @@ -4936,7 +4940,7 @@ void MainWindow::on_actionSettings_triggered() instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); - m_Filters->refresh(); + refreshFilters(); if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); @@ -6083,6 +6087,69 @@ void MainWindow::deselectFilters() m_Filters->clearSelection(); } +void MainWindow::refreshFilters() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + QVariant currentIndexName = ui->modList->currentIndex().data(); + ui->modList->setCurrentIndex(QModelIndex()); + + m_Filters->refresh(); + + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match( + ui->modList->model()->index(0, 0), + Qt::DisplayRole, + currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + +void MainWindow::onFilters( + const std::vector& categories, const std::vector& content) +{ + m_ModListSortProxy->setCategoryFilter(categories); + m_ModListSortProxy->setContentFilter(content); + + QString label = "?"; + + if ((categories.size() + content.size()) > 1) { + label = tr(""); + } else if (!categories.empty()) { + const int c = categories[0]; + label = m_CategoryFactory.getCategoryNameByID(c); + if (label.isEmpty()) { + log::error("category '{}' not found", c); + } + } else if (!content.empty()) { + const int c = content[0]; + try { + label = ModInfo::getContentTypeName(c); + } + catch(std::exception&) { + log::error("content filter '{}' not found", c); + } + } else { + label = ""; + } + + ui->currentCategoryLabel->setText(label); + ui->modList->reset(); +} + +void MainWindow::onFiltersCriteria( + ModListSortProxy::FilterMode mode, bool inverse, bool separators) +{ + m_ModListSortProxy->setFilterMode(mode); + m_ModListSortProxy->setFilterNot(inverse); + m_ModListSortProxy->setFilterSeparators(separators); +} void MainWindow::updateESPLock(bool locked) { @@ -6385,30 +6452,6 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_categoriesAndBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); - } -} - -void MainWindow::on_categoriesOrBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); - } -} - -void MainWindow::on_categoriesNotBtn_toggled(bool checked) -{ - m_ModListSortProxy->setFilterNot(checked); -} - -void MainWindow::on_categoriesSeparators_toggled(bool checked) -{ - m_ModListSortProxy->setFilterSeparators(checked); -} - void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index 04ba7a35..9837378b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -513,6 +513,9 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); void deselectFilters(); + void refreshFilters(); + void onFilters(const std::vector& categories, const std::vector& content); + void onFiltersCriteria(ModListSortProxy::FilterMode mode, bool inverse, bool separators); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); @@ -651,10 +654,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_categoriesAndBtn_toggled(bool checked); - void on_categoriesOrBtn_toggled(bool checked); - void on_categoriesNotBtn_toggled(bool checked); - void on_categoriesSeparators_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 7e11c70e..ed35f783 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -66,7 +66,7 @@ 1 - + 120 @@ -96,7 +96,7 @@ - + false @@ -130,7 +130,7 @@ - + Display mods that match all selected categories. @@ -143,7 +143,7 @@ - + Display mods that match at least one of the selected categories @@ -153,7 +153,7 @@ - + Invert each selected category @@ -163,7 +163,7 @@ - + Include separators -- cgit v1.3.1 From e99dfe153c62f914ada0605430305fca81a332a9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 01:53:09 -0500 Subject: renamed filters to criteria merged categories and content, they can be distinguished using the type added a not flag for criteria, not used yet --- src/filterlist.cpp | 116 +++++++++++++++++++++++++-------------------- src/filterlist.h | 14 +++--- src/mainwindow.cpp | 50 +++++++++----------- src/mainwindow.h | 4 +- src/mainwindow.ui | 38 +++++++-------- src/modlistsortproxy.cpp | 120 +++++++++++++++++------------------------------ src/modlistsortproxy.h | 42 +++++++++++------ 7 files changed, 182 insertions(+), 202 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 5562736d..9e5437b3 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -6,6 +6,9 @@ using namespace MOBase; +const int CategoryIDRole = Qt::UserRole; +const int CategoryTypeRole = Qt::UserRole + 1; + FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { @@ -29,61 +32,76 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filtersOr, &QCheckBox::toggled, [&]{ onCriteriaChanged(); }); - connect( - ui->filtersNot, &QCheckBox::toggled, - [&]{ onCriteriaChanged(); }); - connect( ui->filtersSeparators, &QCheckBox::toggled, [&]{ onCriteriaChanged(); }); + + ui->filters->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->filters->header()->resizeSection(1, 50); } -QTreeWidgetItem* FilterList::addFilterItem( +QTreeWidgetItem* FilterList::addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::FilterType type) + ModListSortProxy::CriteriaType type) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::ToolTipRole, name); - item->setData(0, Qt::UserRole, categoryID); - item->setData(0, Qt::UserRole + 1, type); + item->setData(0, CategoryIDRole, categoryID); + item->setData(0, CategoryTypeRole, type); + if (root != nullptr) { root->addChild(item); } else { ui->filters->addTopLevelItem(item); } + + auto* w = new QWidget; + w->setStyleSheet("background-color: rgba(0,0,0,0)"); + + auto* ly = new QVBoxLayout(w); + ly->setAlignment(Qt::AlignCenter); + ly->setContentsMargins(0, 0, 0, 0); + + auto* cb = new QCheckBox; + connect(cb, &QCheckBox::toggled, [&]{ onSelection(); }); + ly->addWidget(cb); + + ui->filters->setItemWidget(item, 1, w); + return item; } -void FilterList::addContentFilters() +void FilterList::addContentCriteria() { for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem( + addCriteriaItem( nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); } } -void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) { - for (unsigned int i = 1; - i < static_cast(m_factory.numCategories()); ++i) { - if ((m_factory.getParentID(i) == targetID)) { + const auto count = static_cast(m_factory.numCategories()); + for (unsigned int i = 1; i < count; ++i) { + if (m_factory.getParentID(i) == targetID) { int categoryID = m_factory.getCategoryID(i); if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { QTreeWidgetItem *item = - addFilterItem(root, m_factory.getCategoryName(i), + addCriteriaItem(root, m_factory.getCategoryName(i), categoryID, ModListSortProxy::TYPE_CATEGORY); if (m_factory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); + addCategoryCriteria(item, categoriesUsed, categoryID); } } } } } -void FilterList::addSpecialFilterItem(int type) +void FilterList::addSpecialCriteria(int type) { - addFilterItem( + addCriteriaItem( nullptr, m_factory.getSpecialCategoryName(type), type, ModListSortProxy::TYPE_SPECIAL); } @@ -98,19 +116,19 @@ void FilterList::refresh() ui->filters->clear(); using F = CategoryFactory; - addSpecialFilterItem(F::CATEGORY_SPECIAL_CHECKED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UNCHECKED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addSpecialFilterItem(F::CATEGORY_SPECIAL_BACKUP); - addSpecialFilterItem(F::CATEGORY_SPECIAL_MANAGED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UNMANAGED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOCATEGORY); - addSpecialFilterItem(F::CATEGORY_SPECIAL_CONFLICT); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOTENDORSED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NONEXUSID); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOGAMEDATA); - - addContentFilters(); + addSpecialCriteria(F::CATEGORY_SPECIAL_CHECKED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UNCHECKED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); + addSpecialCriteria(F::CATEGORY_SPECIAL_BACKUP); + addSpecialCriteria(F::CATEGORY_SPECIAL_MANAGED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UNMANAGED); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOCATEGORY); + addSpecialCriteria(F::CATEGORY_SPECIAL_CONFLICT); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOTENDORSED); + addSpecialCriteria(F::CATEGORY_SPECIAL_NONEXUSID); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOGAMEDATA); + + addContentCriteria(); std::set categoriesUsed; for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { @@ -130,7 +148,7 @@ void FilterList::refresh() } } - addCategoryFilters(nullptr, categoriesUsed, 0); + addCategoryCriteria(nullptr, categoriesUsed, 0); for (const QString &item : selectedItems) { QList matches = ui->filters->findItems( @@ -145,7 +163,7 @@ void FilterList::refresh() void FilterList::setSelection(std::vector categories) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { - if (ui->filters->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + if (ui->filters->topLevelItem(i)->data(0, CategoryIDRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } @@ -159,27 +177,24 @@ void FilterList::clearSelection() void FilterList::onSelection() { - QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); - std::vector categories; - std::vector content; + const QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); + std::vector criteria; - for (const QModelIndex &index : indices) { - const int filterType = index.data(Qt::UserRole + 1).toInt(); + for (auto* item: ui->filters->selectedItems()) { + const auto type = static_cast( + item->data(0, CategoryTypeRole).toInt()); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - const int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - const int contentId = index.data(Qt::UserRole).toInt(); - content.push_back(contentId); - } + const int id = item->data(0, CategoryIDRole).toInt(); + + auto* cb = static_cast(ui->filters->itemWidget(item, 1)); + const bool inverse = cb->isChecked(); + + criteria.push_back({type, id, inverse}); } - ui->filtersClear->setEnabled(categories.size() > 0 || content.size() >0); + ui->filtersClear->setEnabled(!criteria.empty()); - emit filtersChanged(categories, content); + emit criteriaChanged(criteria); } void FilterList::onContextMenu(const QPoint &pos) @@ -205,8 +220,7 @@ void FilterList::onCriteriaChanged() const auto mode = ui->filtersAnd->isChecked() ? ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; - const bool inverse = ui->filtersNot->isChecked(); const bool separators = ui->filtersSeparators->isChecked(); - emit criteriaChanged(mode, inverse, separators); + emit optionsChanged(mode, separators); } diff --git a/src/filterlist.h b/src/filterlist.h index 85982392..418989e7 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -19,8 +19,8 @@ public: void refresh(); signals: - void filtersChanged(std::vector categories, std::vector content); - void criteriaChanged(ModListSortProxy::FilterMode mode, bool inverse, bool separators); + void criteriaChanged(std::vector criteria); + void optionsChanged(ModListSortProxy::FilterMode mode, bool separators); private: Ui::MainWindow* ui; @@ -32,14 +32,14 @@ private: void editCategories(); - QTreeWidgetItem* addFilterItem( + QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::FilterType type); + ModListSortProxy::CriteriaType type); - void addContentFilters(); - void addCategoryFilters( + void addContentCriteria(); + void addCategoryCriteria( QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - void addSpecialFilterItem(int type); + void addSpecialCriteria(int type); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1319d906..03d61bc6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -264,12 +264,12 @@ MainWindow::MainWindow(Settings &settings m_Filters.reset(new FilterList(ui, m_CategoryFactory)); connect( - m_Filters.get(), &FilterList::filtersChanged, - [&](auto&& cats, auto&& content) { onFilters(cats, content); }); + m_Filters.get(), &FilterList::criteriaChanged, + [&](auto&& v) { onFiltersCriteria(v); }); connect( - m_Filters.get(), &FilterList::criteriaChanged, - [&](auto mode, bool inv, bool sep) { onFiltersCriteria(mode, inv, sep); }); + m_Filters.get(), &FilterList::optionsChanged, + [&](auto mode, bool sep) { onFiltersOptions(mode, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -4124,7 +4124,12 @@ void MainWindow::checkModsForUpdates() } if (updatesAvailable || checkingModsForUpdate) { - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + m_ModListSortProxy->setCriteria({{ + ModListSortProxy::TYPE_SPECIAL, + CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, + false} + }); + m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); } } @@ -6111,44 +6116,31 @@ void MainWindow::refreshFilters() } } -void MainWindow::onFilters( - const std::vector& categories, const std::vector& content) +void MainWindow::onFiltersCriteria(const std::vector& criteria) { - m_ModListSortProxy->setCategoryFilter(categories); - m_ModListSortProxy->setContentFilter(content); + m_ModListSortProxy->setCriteria(criteria); QString label = "?"; - if ((categories.size() + content.size()) > 1) { - label = tr(""); - } else if (!categories.empty()) { - const int c = categories[0]; - label = m_CategoryFactory.getCategoryNameByID(c); + if (criteria.empty()) { + label = ""; + } else if (criteria.size() == 1) { + const auto& c = criteria[0]; + label = m_CategoryFactory.getCategoryNameByID(c.id); if (label.isEmpty()) { - log::error("category '{}' not found", c); - } - } else if (!content.empty()) { - const int c = content[0]; - try { - label = ModInfo::getContentTypeName(c); - } - catch(std::exception&) { - log::error("content filter '{}' not found", c); + log::error("category '{}' not found", c.id); } } else { - label = ""; + label = tr(""); } ui->currentCategoryLabel->setText(label); ui->modList->reset(); } -void MainWindow::onFiltersCriteria( - ModListSortProxy::FilterMode mode, bool inverse, bool separators) +void MainWindow::onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators) { - m_ModListSortProxy->setFilterMode(mode); - m_ModListSortProxy->setFilterNot(inverse); - m_ModListSortProxy->setFilterSeparators(separators); + m_ModListSortProxy->setOptions(mode, separators); } void MainWindow::updateESPLock(bool locked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 9837378b..0b559300 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -514,8 +514,8 @@ private slots: void deselectFilters(); void refreshFilters(); - void onFilters(const std::vector& categories, const std::vector& content); - void onFiltersCriteria(ModListSortProxy::FilterMode mode, bool inverse, bool separators); + void onFiltersCriteria(const std::vector& filters); + void onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index ed35f783..7cc7dca4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -85,12 +85,26 @@ true + + false + + true + + + true + + false - 1 + Category + + + + + Invert @@ -152,16 +166,6 @@ - - - - Invert each selected category - - - Not - - - @@ -351,9 +355,6 @@ p, li { white-space: pre-wrap; } Qt::CustomContextMenu - - List of available mods. - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. @@ -448,14 +449,7 @@ p, li { white-space: pre-wrap; } - - - - 8 - true - - - + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d2a5e258..646401b9 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -38,7 +38,6 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_Profile(profile) , m_FilterActive(false) , m_FilterMode(FILTER_AND) - , m_FilterNot(false) , m_FilterSeparators(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter @@ -52,26 +51,20 @@ void ModListSortProxy::setProfile(Profile *profile) void ModListSortProxy::updateFilterActive() { - m_FilterActive = ((m_CategoryFilter.size() > 0) - || (m_ContentFilter.size() > 0) - || !m_CurrentFilter.isEmpty()); + m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); emit filterActive(m_FilterActive); } -void ModListSortProxy::setCategoryFilter(const std::vector &categories) +void ModListSortProxy::setCriteria(const std::vector& criteria) { - //avoid refreshing the filter unless we are checking all mods for update. - if (categories != m_CategoryFilter || (!categories.empty() && categories.at(0) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)) { - m_CategoryFilter = categories; - updateFilterActive(); - invalidate(); - } -} - -void ModListSortProxy::setContentFilter(const std::vector &content) -{ - if (content != m_ContentFilter) { - m_ContentFilter = content; + // avoid refreshing the filter unless we are checking all mods for update. + const bool changed = (criteria != m_Criteria); + const bool isForUpdates = ( + !criteria.empty() && + criteria[0].id == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); + + if (changed || isForUpdates) { + m_Criteria = criteria; updateFilterActive(); invalidate(); } @@ -248,12 +241,10 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, return lt; } -void ModListSortProxy::updateFilter(const QString &filter) +void ModListSortProxy::updateFilter(const QString& filter) { - m_CurrentFilter = filter; + m_Filter = filter; updateFilterActive(); - // using invalidateFilter here should be enough but that crashes the application? WTF? - // invalidateFilter(); invalidate(); } @@ -282,14 +273,8 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons return false; } - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (!categoryMatchesMod(info, enabled, *iter)) { - return false; - } - } - - foreach (int content, m_ContentFilter) { - if (!contentMatchesMod(info, enabled, content)) { + for (auto&& c : m_Criteria) { + if (!criteriaMatchesMod(info, enabled, c)) { return false; } } @@ -303,29 +288,35 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const return false; } - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (categoryMatchesMod(info, enabled, *iter)) { + for (auto&& c : m_Criteria) { + if (criteriaMatchesMod(info, enabled, c)) { return true; } } - if (!m_CategoryFilter.empty()) { + if (!m_Criteria.empty()) { // nothing matched return false; } - foreach (int content, m_ContentFilter) { - if (contentMatchesMod(info, enabled, content)) { - return true; - } - } + return true; +} - if (!m_ContentFilter.empty()) { - // nothing matched - return false; - } +bool ModListSortProxy::criteriaMatchesMod( + ModInfo::Ptr info, bool enabled, const Criteria& c) const +{ + switch (c.type) + { + case TYPE_SPECIAL: // fall-through + case TYPE_CATEGORY: + return categoryMatchesMod(info, enabled, c.id); - return true; + case TYPE_CONTENT: + return contentMatchesMod(info, enabled, c.id); + + default: + return false; + } } bool ModListSortProxy::categoryMatchesMod( @@ -414,29 +405,19 @@ bool ModListSortProxy::categoryMatchesMod( } } - if (m_FilterNot) { - b = !b; - } - return b; } bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const { - bool b = info->hasContent(static_cast(content)); - - if (m_FilterNot) { - b = !b; - } - - return b; + return info->hasContent(static_cast(content)); } bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { - if (!m_CurrentFilter.isEmpty()) { + if (!m_Filter.isEmpty()) { bool display = false; - QString filterCopy = QString(m_CurrentFilter); + QString filterCopy = QString(m_Filter); filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); QStringList ORList = filterCopy.split(";", QString::SkipEmptyParts); @@ -527,26 +508,11 @@ void ModListSortProxy::setColumnVisible(int column, bool visible) m_EnabledColumns[column] = visible; } -void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) +void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, bool separators) { - if (m_FilterMode != mode) { + if (m_FilterMode != mode || separators != m_FilterSeparators) { m_FilterMode = mode; - this->invalidate(); - } -} - -void ModListSortProxy::setFilterNot(bool b) -{ - if (b != m_FilterNot) { - m_FilterNot = b; - this->invalidate(); - } -} - -void ModListSortProxy::setFilterSeparators(bool b) -{ - if (b != m_FilterSeparators) { - m_FilterSeparators = b; + m_FilterSeparators = separators; this->invalidate(); } } @@ -623,8 +589,8 @@ void ModListSortProxy::aboutToChangeData() // (at least with some Qt versions) // this may be related to the fact that the item being edited may disappear from the view as a // result of the edit - m_PreChangeFilters = categoryFilter(); - setCategoryFilter(std::vector()); + m_PreChangeCriteria = m_Criteria; + setCriteria({}); } void ModListSortProxy::postDataChanged() @@ -633,8 +599,8 @@ void ModListSortProxy::postDataChanged() // or at least the view continues to think it's being edited. As a result no new editor can be // opened QTimer::singleShot(10, [this] () { - setCategoryFilter(m_PreChangeFilters); - m_PreChangeFilters.clear(); + setCriteria(m_PreChangeCriteria); + m_PreChangeCriteria.clear(); }); } diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 2ebfbcf0..5aeaccce 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -37,22 +37,38 @@ public: FILTER_OR }; - enum FilterType { + enum CriteriaType { TYPE_SPECIAL, TYPE_CATEGORY, TYPE_CONTENT }; + struct Criteria + { + CriteriaType type; + int id; + bool inverse; + + bool operator==(const Criteria& other) const + { + return + (type == other.type) && + (id == other.id) && + (inverse == other.inverse); + } + + bool operator!=(const Criteria& other) const + { + return !(*this == other); + } + }; + public: explicit ModListSortProxy(Profile *profile, QObject *parent = 0); void setProfile(Profile *profile); - void setCategoryFilter(const std::vector &categories); - std::vector categoryFilter() const { return m_CategoryFilter; } - - void setContentFilter(const std::vector &content); virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, @@ -84,9 +100,8 @@ public: */ bool isFilterActive() const { return m_FilterActive; } - void setFilterMode(FilterMode mode); - void setFilterNot(bool b); - void setFilterSeparators(bool b); + void setCriteria(const std::vector& criteria); + void setOptions(FilterMode mode, bool separators); /** * @brief tests if the specified index has child nodes @@ -131,19 +146,18 @@ private slots: void postDataChanged(); private: - Profile *m_Profile; - std::vector m_CategoryFilter; - std::vector m_ContentFilter; + Profile* m_Profile; + std::vector m_Criteria; + QString m_Filter; std::bitset m_EnabledColumns; - QString m_CurrentFilter; bool m_FilterActive; FilterMode m_FilterMode; - bool m_FilterNot; bool m_FilterSeparators; - std::vector m_PreChangeFilters; + std::vector m_PreChangeCriteria; + bool criteriaMatchesMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; }; -- cgit v1.3.1 From a38d1723bffcd20bc7011c0fe635636b936aa78b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:21:23 -0500 Subject: removed redundant categories now that there's a not filter disabled collapsing for filter, there's already a button to hide it --- src/categories.cpp | 24 +++++++++++------------- src/categories.h | 31 +++++++++++++------------------ src/filterlist.cpp | 28 +++++++++++++++------------- src/mainwindow.cpp | 4 ++-- src/mainwindow.ui | 3 +++ src/modlistsortproxy.cpp | 32 ++++++++++---------------------- 6 files changed, 54 insertions(+), 68 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index b75efefa..1bd56f7f 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -320,21 +320,19 @@ QString CategoryFactory::getCategoryName(unsigned int index) const return m_Categories[index].m_Name; } -QString CategoryFactory::getSpecialCategoryName(int type) const +QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const { switch (type) { - case CATEGORY_SPECIAL_CHECKED: return QObject::tr(""); - case CATEGORY_SPECIAL_UNCHECKED: return QObject::tr(""); - case CATEGORY_SPECIAL_UPDATEAVAILABLE: return QObject::tr(""); - case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); - case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); - case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); - case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); - case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); - case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); + case Checked: return QObject::tr(""); + case UpdateAvailable: return QObject::tr(""); + case HasNoCategory: return QObject::tr(""); + case Conflict: return QObject::tr(""); + case NotEndorsed: return QObject::tr(""); + case Backup: return QObject::tr(""); + case Managed: return QObject::tr(""); + case NoGameData: return QObject::tr(""); + case NoNexusID: return QObject::tr(""); default: return {}; } } @@ -344,7 +342,7 @@ QString CategoryFactory::getCategoryNameByID(int id) const auto itor = m_IDMap.find(id); if (itor == m_IDMap.end()) { - return getSpecialCategoryName(id); + return getSpecialCategoryName(static_cast(id)); } else { const auto index = itor->second; if (index >= m_Categories.size()) { diff --git a/src/categories.h b/src/categories.h index 2041ce1f..296e7711 100644 --- a/src/categories.h +++ b/src/categories.h @@ -37,25 +37,20 @@ class CategoryFactory { friend class CategoriesDialog; public: - - static const int CATEGORY_NONE = 0; - - static const int CATEGORY_SPECIAL_FIRST = 10000; - static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; - static const int CATEGORY_SPECIAL_UNCHECKED = 10001; - static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; - static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; - static const int CATEGORY_SPECIAL_CONFLICT = 10004; - static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; - static const int CATEGORY_SPECIAL_BACKUP = 10006; - static const int CATEGORY_SPECIAL_MANAGED = 10007; - static const int CATEGORY_SPECIAL_UNMANAGED = 10008; - static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009; - static const int CATEGORY_SPECIAL_NONEXUSID = 10010; - + enum SpecialCategories + { + Checked = 10000, + UpdateAvailable, + HasNoCategory, + Conflict, + NotEndorsed, + Backup, + Managed, + NoGameData, + NoNexusID + }; public: - struct Category { Category(int sortValue, int id, const QString &name, const std::vector &nexusIDs, int parentID) : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), @@ -144,7 +139,7 @@ public: * @return QString name of the category **/ QString getCategoryName(unsigned int index) const; - QString getSpecialCategoryName(int type) const; + QString getSpecialCategoryName(SpecialCategories type) const; QString getCategoryNameByID(int id) const; /** diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 8f297af6..36cdacd0 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -94,6 +94,8 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filters->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->filters->header()->resizeSection(1, 50); + ui->categoriesSplitter->setCollapsible(0, false); + ui->categoriesSplitter->setCollapsible(1, false); } QTreeWidgetItem* FilterList::addCriteriaItem( @@ -142,8 +144,10 @@ void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set void FilterList::addSpecialCriteria(int type) { + const auto sc = static_cast(type); + addCriteriaItem( - nullptr, m_factory.getSpecialCategoryName(type), + nullptr, m_factory.getSpecialCategoryName(sc), type, ModListSortProxy::TYPE_SPECIAL); } @@ -157,17 +161,15 @@ void FilterList::refresh() ui->filters->clear(); using F = CategoryFactory; - addSpecialCriteria(F::CATEGORY_SPECIAL_CHECKED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UNCHECKED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addSpecialCriteria(F::CATEGORY_SPECIAL_BACKUP); - addSpecialCriteria(F::CATEGORY_SPECIAL_MANAGED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UNMANAGED); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOCATEGORY); - addSpecialCriteria(F::CATEGORY_SPECIAL_CONFLICT); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOTENDORSED); - addSpecialCriteria(F::CATEGORY_SPECIAL_NONEXUSID); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOGAMEDATA); + addSpecialCriteria(F::Checked); + addSpecialCriteria(F::UpdateAvailable); + addSpecialCriteria(F::Backup); + addSpecialCriteria(F::Managed); + addSpecialCriteria(F::HasNoCategory); + addSpecialCriteria(F::Conflict); + addSpecialCriteria(F::NotEndorsed); + addSpecialCriteria(F::NoNexusID); + addSpecialCriteria(F::NoGameData); addContentCriteria(); @@ -211,7 +213,7 @@ void FilterList::setSelection(std::vector categories) continue; } - if (item->id() == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + if (item->id() == CategoryFactory::UpdateAvailable) { ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 03d61bc6..0ad57803 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4126,11 +4126,11 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCriteria({{ ModListSortProxy::TYPE_SPECIAL, - CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, + CategoryFactory::UpdateAvailable, false} }); - m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); + m_Filters->setSelection({CategoryFactory::UpdateAvailable}); } } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 7cc7dca4..6c35d239 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -39,6 +39,9 @@ Qt::Horizontal + + false + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 3bb02c0f..36dcae59 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -61,7 +61,7 @@ void ModListSortProxy::setCriteria(const std::vector& criteria) const bool changed = (criteria != m_Criteria); const bool isForUpdates = ( !criteria.empty() && - criteria[0].id == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); + criteria[0].id == CategoryFactory::UpdateAvailable); if (changed || isForUpdates) { m_Criteria = criteria; @@ -343,68 +343,56 @@ bool ModListSortProxy::categoryMatchesMod( switch (category) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: + case CategoryFactory::Checked: { b = (enabled || info->alwaysEnabled()); break; } - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: - { - b = (!enabled && !info->alwaysEnabled()); - break; - } - - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: + case CategoryFactory::UpdateAvailable: { b = (info->updateAvailable() || info->downgradeAvailable()); break; } - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: + case CategoryFactory::HasNoCategory: { b = (info->getCategories().size() == 0); break; } - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: + case CategoryFactory::Conflict: { b = (hasConflictFlag(info->getFlags())); break; } - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: + case CategoryFactory::NotEndorsed: { ModInfo::EEndorsedState state = info->endorsedState(); b = (state != ModInfo::ENDORSED_TRUE); break; } - case CategoryFactory::CATEGORY_SPECIAL_BACKUP: + case CategoryFactory::Backup: { b = (info->hasFlag(ModInfo::FLAG_BACKUP)); break; } - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: + case CategoryFactory::Managed: { b = (!info->hasFlag(ModInfo::FLAG_FOREIGN)); break; } - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: - { - b = (info->hasFlag(ModInfo::FLAG_FOREIGN)); - break; - } - - case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: + case CategoryFactory::NoGameData: { b = (info->hasFlag(ModInfo::FLAG_INVALID)); break; } - case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: + case CategoryFactory::NoNexusID: { b = ( info->getNexusID() == -1 && -- cgit v1.3.1 From 507d29a0fc0765f0f8de49795eb100a258970d01 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:29:54 -0500 Subject: fixed bad label for content categories --- src/mainwindow.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ad57803..420242ad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6126,9 +6126,15 @@ void MainWindow::onFiltersCriteria(const std::vector label = ""; } else if (criteria.size() == 1) { const auto& c = criteria[0]; - label = m_CategoryFactory.getCategoryNameByID(c.id); + + if (c.type == ModListSortProxy::TYPE_CONTENT) { + label = ModInfo::getContentTypeName(c.id); + } else { + label = m_CategoryFactory.getCategoryNameByID(c.id); + } + if (label.isEmpty()) { - log::error("category '{}' not found", c.id); + log::error("category {}:{} not found", c.type, c.id); } } else { label = tr(""); -- cgit v1.3.1 From 38d56ef8310674bc5a2f8b304ee17a6b7e1c5798 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 04:04:40 -0500 Subject: fixed setting selection when checking for updates --- src/filterlist.cpp | 10 ++++++---- src/filterlist.h | 2 +- src/mainwindow.cpp | 6 +++++- 3 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 66a9aed0..81f8b670 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -203,7 +203,7 @@ void FilterList::refresh() } } -void FilterList::setSelection(std::vector categories) +void FilterList::setSelection(const std::vector& criteria) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { const auto* item = dynamic_cast( @@ -213,9 +213,11 @@ void FilterList::setSelection(std::vector categories) continue; } - if (item->id() == CategoryFactory::UpdateAvailable) { - ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); - break; + for (auto&& c : criteria) { + if (item->type() == c.type && item->id() == c.id) { + ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); + break; + } } } } diff --git a/src/filterlist.h b/src/filterlist.h index e98f81a9..52b90ea7 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -14,7 +14,7 @@ class FilterList : public QObject public: FilterList(Ui::MainWindow* ui, CategoryFactory& factory); - void setSelection(std::vector categories); + void setSelection(const std::vector& criteria); void clearSelection(); void refresh(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 420242ad..096ea076 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4130,7 +4130,11 @@ void MainWindow::checkModsForUpdates() false} }); - m_Filters->setSelection({CategoryFactory::UpdateAvailable}); + m_Filters->setSelection({{ + ModListSortProxy::TYPE_SPECIAL, + CategoryFactory::UpdateAvailable, + false + }}); } } -- cgit v1.3.1 From 3c25117fe163f7fab7afa22ba171ea2d41112f23 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 10:41:52 -0500 Subject: three modes for separators, save state renamed enumerators --- src/filterlist.cpp | 28 ++++++++++++++++----- src/filterlist.h | 7 +++++- src/mainwindow.cpp | 16 +++++++----- src/mainwindow.h | 3 ++- src/mainwindow.ui | 21 ++++++++++++---- src/modlistsortproxy.cpp | 65 +++++++++++++++++++++++++++++------------------- src/modlistsortproxy.h | 25 ++++++++++++------- 7 files changed, 112 insertions(+), 53 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index b65f0f4a..05bff2dd 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -2,6 +2,7 @@ #include "ui_mainwindow.h" #include "categories.h" #include "categoriesdialog.h" +#include "settings.h" #include using namespace MOBase; @@ -157,7 +158,7 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) [&]{ onOptionsChanged(); }); connect( - ui->filtersSeparators, &QCheckBox::toggled, + ui->filtersSeparators, qOverload(&QComboBox::currentIndexChanged), [&]{ onOptionsChanged(); }); ui->filters->header()->setMinimumSectionSize(0); @@ -165,6 +166,20 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filters->header()->resizeSection(0, 30); ui->categoriesSplitter->setCollapsible(0, false); ui->categoriesSplitter->setCollapsible(1, false); + + ui->filtersSeparators->addItem(tr("Filter separators"), ModListSortProxy::SeparatorFilter); + ui->filtersSeparators->addItem(tr("Show separators"), ModListSortProxy::SeparatorShow); + ui->filtersSeparators->addItem(tr("Hide separators"), ModListSortProxy::SeparatorHide); +} + +void FilterList::restoreState(const Settings& s) +{ + s.widgets().restoreIndex(ui->filtersSeparators); +} + +void FilterList::saveState(Settings& s) const +{ + s.widgets().saveIndex(ui->filtersSeparators); } QTreeWidgetItem* FilterList::addCriteriaItem( @@ -189,7 +204,7 @@ void FilterList::addContentCriteria() for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { addCriteriaItem( nullptr, tr("").arg(ModInfo::getContentTypeName(i)), - i, ModListSortProxy::TYPE_CONTENT); + i, ModListSortProxy::TypeContent); } } @@ -202,7 +217,7 @@ void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { QTreeWidgetItem *item = addCriteriaItem(root, m_factory.getCategoryName(i), - categoryID, ModListSortProxy::TYPE_CATEGORY); + categoryID, ModListSortProxy::TypeCategory); if (m_factory.hasChildren(i)) { addCategoryCriteria(item, categoriesUsed, categoryID); } @@ -217,7 +232,7 @@ void FilterList::addSpecialCriteria(int type) addCriteriaItem( nullptr, m_factory.getSpecialCategoryName(sc), - type, ModListSortProxy::TYPE_SPECIAL); + type, ModListSortProxy::TypeSpecial); } void FilterList::refresh() @@ -361,9 +376,10 @@ void FilterList::editCategories() void FilterList::onOptionsChanged() { const auto mode = ui->filtersAnd->isChecked() ? - ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; + ModListSortProxy::FilterAnd: ModListSortProxy::FilterOr; - const bool separators = ui->filtersSeparators->isChecked(); + const auto separators = static_cast( + ui->filtersSeparators->currentData().toInt()); emit optionsChanged(mode, separators); } diff --git a/src/filterlist.h b/src/filterlist.h index fac1d683..671462d4 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -6,6 +6,7 @@ namespace Ui { class MainWindow; }; class CategoryFactory; +class Settings; class FilterList : public QObject { @@ -14,13 +15,17 @@ class FilterList : public QObject public: FilterList(Ui::MainWindow* ui, CategoryFactory& factory); + void restoreState(const Settings& s); + void saveState(Settings& s) const; + void setSelection(const std::vector& criteria); void clearSelection(); void refresh(); signals: void criteriaChanged(std::vector criteria); - void optionsChanged(ModListSortProxy::FilterMode mode, bool separators); + void optionsChanged( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); private: class CriteriaItem; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 096ea076..b5af9aa5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -269,7 +269,7 @@ MainWindow::MainWindow(Settings &settings connect( m_Filters.get(), &FilterList::optionsChanged, - [&](auto mode, bool sep) { onFiltersOptions(mode, sep); }); + [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -2205,6 +2205,7 @@ void MainWindow::readSettings() } s.widgets().restoreIndex(ui->groupCombo); + m_Filters->restoreState(s); { s.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2283,6 +2284,8 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->groupCombo); s.widgets().saveIndex(ui->executablesListBox); + + m_Filters->saveState(s); } QWidget* MainWindow::qtWidget() @@ -4125,13 +4128,13 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCriteria({{ - ModListSortProxy::TYPE_SPECIAL, + ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false} }); m_Filters->setSelection({{ - ModListSortProxy::TYPE_SPECIAL, + ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false }}); @@ -6131,7 +6134,7 @@ void MainWindow::onFiltersCriteria(const std::vector } else if (criteria.size() == 1) { const auto& c = criteria[0]; - if (c.type == ModListSortProxy::TYPE_CONTENT) { + if (c.type == ModListSortProxy::TypeContent) { label = ModInfo::getContentTypeName(c.id); } else { label = m_CategoryFactory.getCategoryNameByID(c.id); @@ -6148,9 +6151,10 @@ void MainWindow::onFiltersCriteria(const std::vector ui->modList->reset(); } -void MainWindow::onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators) +void MainWindow::onFiltersOptions( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) { - m_ModListSortProxy->setOptions(mode, separators); + m_ModListSortProxy->setOptions(mode, sep); } void MainWindow::updateESPLock(bool locked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 0b559300..69aee073 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -515,7 +515,8 @@ private slots: void deselectFilters(); void refreshFilters(); void onFiltersCriteria(const std::vector& filters); - void onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators); + void onFiltersOptions( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 92a41c67..85be22b3 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -153,6 +153,18 @@ + + 0 + + + 2 + + + 0 + + + 0 + @@ -177,12 +189,11 @@ - + - Include separators - - - Separators + Filter: only show the separators that match the current filters +Show: always show separators +Hide: never show separators diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index fd3dbc9e..7ac98f66 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -37,8 +37,8 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) , m_Profile(profile) , m_FilterActive(false) - , m_FilterMode(FILTER_AND) - , m_FilterSeparators(false) + , m_FilterMode(FilterAnd) + , m_FilterSeparators(SeparatorFilter) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -269,10 +269,6 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { - if (!optionsMatchMod(info, enabled)) { - return false; - } - for (auto&& c : m_Criteria) { if (!criteriaMatchMod(info, enabled, c)) { return false; @@ -284,10 +280,6 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { - if (!optionsMatchMod(info, enabled)) { - return false; - } - for (auto&& c : m_Criteria) { if (criteriaMatchMod(info, enabled, c)) { return true; @@ -304,16 +296,6 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const { - // don't check options if there are no filters selected - if (!m_FilterActive) { - return true; - } - - if (!m_FilterSeparators) { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { - return false; - } - } return true; } @@ -325,14 +307,14 @@ bool ModListSortProxy::criteriaMatchMod( switch (c.type) { - case TYPE_SPECIAL: // fall-through - case TYPE_CATEGORY: + case TypeSpecial: // fall-through + case TypeCategory: { b = categoryMatchesMod(info, enabled, c.id); break; } - case TYPE_CONTENT: + case TypeContent: { b = contentMatchesMod(info, enabled, c.id); break; @@ -439,6 +421,37 @@ bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int co bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { + // don't check if there are no filters selected + if (!m_FilterActive) { + return true; + } + + + // special case for separators + if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { + switch (m_FilterSeparators) + { + case SeparatorFilter: + { + // filter normally + break; + } + + case SeparatorShow: + { + // force visible + return true; + } + + case SeparatorHide: + { + // force hide + return false; + } + } + } + + if (!m_Filter.isEmpty()) { bool display = false; QString filterCopy = QString(m_Filter); @@ -519,7 +532,8 @@ bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const } }//if (!m_CurrentFilter.isEmpty()) - if (m_FilterMode == FILTER_AND) { + + if (m_FilterMode == FilterAnd) { return filterMatchesModAnd(info, enabled); } else { @@ -532,7 +546,8 @@ void ModListSortProxy::setColumnVisible(int column, bool visible) m_EnabledColumns[column] = visible; } -void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, bool separators) +void ModListSortProxy::setOptions( + ModListSortProxy::FilterMode mode, SeparatorsMode separators) { if (m_FilterMode != mode || separators != m_FilterSeparators) { m_FilterMode = mode; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 9b533492..46356fe9 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -31,16 +31,23 @@ class ModListSortProxy : public QSortFilterProxyModel Q_OBJECT public: - - enum FilterMode { - FILTER_AND, - FILTER_OR + enum FilterMode + { + FilterAnd, + FilterOr }; enum CriteriaType { - TYPE_SPECIAL, - TYPE_CATEGORY, - TYPE_CONTENT + TypeSpecial, + TypeCategory, + TypeContent + }; + + enum SeparatorsMode + { + SeparatorFilter, + SeparatorShow, + SeparatorHide }; struct Criteria @@ -101,7 +108,7 @@ public: bool isFilterActive() const { return m_FilterActive; } void setCriteria(const std::vector& criteria); - void setOptions(FilterMode mode, bool separators); + void setOptions(FilterMode mode, SeparatorsMode separators); /** * @brief tests if the specified index has child nodes @@ -153,7 +160,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; - bool m_FilterSeparators; + SeparatorsMode m_FilterSeparators; std::vector m_PreChangeCriteria; -- cgit v1.3.1 From 48fd9fd08070e1a937bb7e49d44189d9a071edc5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:50:01 -0500 Subject: added "open with vfs" to the data tab uniform order for file context menus --- src/mainwindow.cpp | 37 +++++++++++++++++++++++++------- src/mainwindow.h | 1 + src/modinfodialogconflicts.cpp | 48 ++++++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 31 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5af9aa5..cfa27fad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5304,6 +5304,21 @@ void MainWindow::openDataFile() .run(); } +void MainWindow::runDataFileHooked() +{ + if (m_ContextItem == nullptr) { + return; + } + + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); + + m_OrganizerCore.processRunner() + .setFromFile(this, targetInfo, true) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); +} + void MainWindow::openDataOriginExplorer_clicked() { if (m_ContextItem == nullptr) { @@ -5380,23 +5395,31 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) QMenu menu; if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0) && (m_ContextItem->data(0, Qt::UserRole + 3).toBool() != true)) { - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); - menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); - QString fileName = m_ContextItem->text(0); + const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); + + if (canRunFile(isArchive, fileName)) { + menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + } else if (canOpenFile(isArchive, fileName)) { + menu.addAction(tr("&Open"), this, SLOT(openDataFile())); + menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); + } + + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); + if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } - const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); - if (!isArchive && !isDirectory) { menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked())); + menu.addSeparator(); + // offer to hide/unhide file, but not for files from archives if (!isArchive) { if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { @@ -5405,8 +5428,6 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Hide"), this, SLOT(hideFile())); } } - - menu.addSeparator(); } menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index 69aee073..ada8e7a7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -440,6 +440,7 @@ private slots: // data-tree context menu void writeDataToFile(); void openDataFile(); + void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); void hideFile(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 58e935f2..6018306c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -608,6 +608,19 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.preview); } + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + emitModOpen(name); + }); + + actions.gotoMenu->addAction(a); + } + } + // explore if (actions.explore) { connect(actions.explore, &QAction::triggered, [&]{ @@ -617,6 +630,8 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.explore); } + menu.addSeparator(); + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ @@ -635,19 +650,6 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } - // goto - if (actions.gotoMenu) { - menu.addMenu(actions.gotoMenu); - - for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ - emitModOpen(name); - }); - - actions.gotoMenu->addAction(a); - } - } - if (!menu.isEmpty()) { menu.exec(tree->viewport()->mapToGlobal(pos)); } @@ -732,14 +734,6 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) Actions actions; - actions.hide = new QAction(tr("&Hide"), parentWidget()); - actions.hide->setEnabled(enableHide); - - // note that it is possible for hidden files to appear if they override other - // hidden files from another mod - actions.unhide = new QAction(tr("&Unhide"), parentWidget()); - actions.unhide->setEnabled(enableUnhide); - if (enableRun) { actions.open = new QAction(tr("&Execute"), parentWidget()); } else if (enableOpen) { @@ -750,11 +744,19 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); + actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); + actions.gotoMenu->setEnabled(enableGoto); + actions.explore = new QAction(tr("Open in &Explorer"), parentWidget()); actions.explore->setEnabled(enableExplore); - actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); - actions.gotoMenu->setEnabled(enableGoto); + actions.hide = new QAction(tr("&Hide"), parentWidget()); + actions.hide->setEnabled(enableHide); + + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + actions.unhide = new QAction(tr("&Unhide"), parentWidget()); + actions.unhide->setEnabled(enableUnhide); if (enableGoto && n == 1) { const auto* item = model->getItem(static_cast( -- cgit v1.3.1 From 3b78e436dbee043e4a3f81e5bfa09695c2a708c4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 15:18:55 -0500 Subject: double-click now opens files for the data tab, filetree and conflict lists --- src/mainwindow.cpp | 36 ++++++++++++++++++++++++++++++------ src/mainwindow.h | 2 ++ src/modinfodialogconflicts.cpp | 16 ++++++++++++++-- src/modinfodialogfiletree.cpp | 10 ++++++++++ 4 files changed, 56 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cfa27fad..3af0f30c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -350,6 +350,7 @@ MainWindow::MainWindow(Settings &settings connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); + connect(ui->dataTree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(activateDataTreeItem(QTreeWidgetItem*, int))); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); @@ -1767,6 +1768,10 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) } } +void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) +{ + openDataFile(item); +} bool MainWindow::refreshProfiles(bool selectProfile) { @@ -5295,7 +5300,19 @@ void MainWindow::openDataFile() return; } - const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + openDataFile(m_ContextItem); +} + +void MainWindow::openDataFile(QTreeWidgetItem* item) +{ + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() @@ -5389,8 +5406,7 @@ void MainWindow::motdReceived(const QString &motd) void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) { - QTreeWidget *dataTree = findChild("dataTree"); - m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); + m_ContextItem = ui->dataTree->itemAt(pos.x(), pos.y()); QMenu menu; if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0) @@ -5399,13 +5415,21 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); + QAction* open = nullptr; + if (canRunFile(isArchive, fileName)) { - menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); } else if (canOpenFile(isArchive, fileName)) { - menu.addAction(tr("&Open"), this, SLOT(openDataFile())); + open = menu.addAction(tr("&Open"), this, SLOT(openDataFile())); menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); } + if (open) { + auto bold = open->font(); + bold.setBold(true); + open->setFont(bold); + } + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { @@ -5432,7 +5456,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); - menu.exec(dataTree->viewport()->mapToGlobal(pos)); + menu.exec(ui->dataTree->viewport()->mapToGlobal(pos)); } void MainWindow::on_conflictsCheckBox_toggled(bool) diff --git a/src/mainwindow.h b/src/mainwindow.h index ada8e7a7..2894b2e7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -440,6 +440,7 @@ private slots: // data-tree context menu void writeDataToFile(); void openDataFile(); + void openDataFile(QTreeWidgetItem* item); void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); @@ -589,6 +590,7 @@ private slots: void refreshSavesIfOpen(); void expandDataTreeItem(QTreeWidgetItem *item); + void activateDataTreeItem(QTreeWidgetItem *item, int column); void about(); void delayedRemove(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 6018306c..daa40cb3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -587,6 +587,10 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) openItems(tree); }); + auto bold = actions.open->font(); + bold.setBold(true); + actions.open->setFont(bold); + menu.addAction(actions.open); } @@ -824,11 +828,15 @@ GeneralConflictsTab::GeneralConflictsTab( QObject::connect( ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&& item){ onOverwriteActivated(item); }); + [&](auto&&){ m_tab->openItems(ui->overwriteTree); }); QObject::connect( ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ onOverwrittenActivated(item); }); + [&](auto&& item){ m_tab->openItems(ui->overwrittenTree); }); + + QObject::connect( + ui->noConflictTree, &QTreeView::doubleClicked, + [&](auto&& item){ m_tab->openItems(ui->noConflictTree); }); QObject::connect( ui->overwriteTree, &QTreeView::customContextMenuRequested, @@ -1039,6 +1047,10 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); + QObject::connect( + ui->conflictsAdvancedList, &QTreeView::activated, + [&]{ m_tab->openItems(ui->conflictsAdvancedList); }); + QObject::connect( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 23d65fdb..e94b0a4f 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -32,6 +32,10 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_actions.hide = new QAction(tr("&Hide"), ui->filetree); m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); + auto bold = m_actions.open->font(); + bold.setBold(true); + m_actions.open->setFont(bold); + connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); @@ -47,6 +51,12 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) connect( ui->filetree, &QTreeView::customContextMenuRequested, [&](const QPoint& pos){ onContextMenu(pos); }); + + // disable renaming on double click, open the file instead + ui->filetree->setEditTriggers( + ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); + + connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onOpen(); }); } void FileTreeTab::clear() -- cgit v1.3.1 From dd6f0fed1085682f56d2815ff885a5ab58058a0b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 12:00:54 -0500 Subject: added explore button in the settings to open stylesheets folder added stylesheets folder to the open folders button moved "Open MO2 Logs folder" up to be with instance folders, removed "MO2" in the name --- src/mainwindow.cpp | 17 +++++++---------- src/mainwindow.h | 1 + src/settingsdialog.ui | 31 +++++++++++++++++++------------ src/settingsdialoggeneral.cpp | 42 +++++++++++++++++++++++++----------------- src/settingsdialoggeneral.h | 7 ++++--- 5 files changed, 56 insertions(+), 42 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3af0f30c..c365fb6b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4302,6 +4302,11 @@ void MainWindow::openPluginsFolder() shell::Explore(pluginsPath); } +void MainWindow::openStylesheetsFolder() +{ + QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + shell::Explore(ssPath); +} void MainWindow::openProfileFolder() { @@ -4519,33 +4524,25 @@ static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) QMenu *MainWindow::openFolderMenu() { - QMenu *FolderMenu = new QMenu(this); FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); - FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); - FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder())); FolderMenu->addSeparator(); FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); - FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); - FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); - FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); + FolderMenu->addAction(tr("Open Logs folder"), this, SLOT(openLogsFolder())); FolderMenu->addSeparator(); FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); - FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); - - FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); - + FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); return FolderMenu; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2894b2e7..f5bab586 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -545,6 +545,7 @@ private slots: void openLogsFolder(); void openInstallFolder(); void openPluginsFolder(); + void openStylesheetsFolder(); void openDownloadsFolder(); void openModsFolder(); void openProfileFolder(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index ad7e2ab3..bc59d635 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -45,7 +45,7 @@ - + 0 @@ -65,16 +65,6 @@ - - - - The language of the user interface. - - - The language of the user interface. - - - @@ -92,7 +82,24 @@ - + + + + Explore... + + + + + + + The language of the user interface. + + + The language of the user interface. + + + + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index b0e64305..dca4410b 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -3,8 +3,11 @@ #include "appconfig.h" #include "categoriesdialog.h" #include "colortable.h" +#include #include +using namespace MOBase; + GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { @@ -24,17 +27,16 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); + QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&]{ onExploreStyles(); }); + QObject::connect( - ui->categoriesBtn, &QPushButton::clicked, - [&]{ on_categoriesBtn_clicked(); }); + ui->categoriesBtn, &QPushButton::clicked, [&]{ onEditCategories(); }); QObject::connect( - ui->resetColorsBtn, &QPushButton::clicked, - [&]{ on_resetColorsBtn_clicked(); }); + ui->resetColorsBtn, &QPushButton::clicked, [&]{ onResetColors(); }); QObject::connect( - ui->resetDialogsButton, &QPushButton::clicked, - [&]{ on_resetDialogsButton_clicked(); }); + ui->resetDialogsButton, &QPushButton::clicked, [&]{ onResetDialogs(); }); } void GeneralSettingsTab::update() @@ -175,12 +177,27 @@ void GeneralSettingsTab::resetDialogs() settings().widgets().resetQuestionButtons(); } -void GeneralSettingsTab::on_resetColorsBtn_clicked() +void GeneralSettingsTab::onExploreStyles() +{ + QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + shell::Explore(ssPath); +} + +void GeneralSettingsTab::onEditCategories() +{ + CategoriesDialog dialog(&dialog()); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void GeneralSettingsTab::onResetColors() { ui->colorTable->resetColors(); } -void GeneralSettingsTab::on_resetDialogsButton_clicked() +void GeneralSettingsTab::onResetDialogs() { const auto r = QMessageBox::question( parentWidget(), @@ -194,12 +211,3 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked() resetDialogs(); } } - -void GeneralSettingsTab::on_categoriesBtn_clicked() -{ - CategoriesDialog dialog(&dialog()); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 706ba9ef..455edcaf 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -20,9 +20,10 @@ private: void resetDialogs(); - void on_categoriesBtn_clicked(); - void on_resetColorsBtn_clicked(); - void on_resetDialogsButton_clicked(); + void onExploreStyles(); + void onEditCategories(); + void onResetColors(); + void onResetDialogs(); }; #endif // SETTINGSDIALOGGENERAL_H -- cgit v1.3.1 From 91b95d00a6fda3788e774488ebeb72b28c89e656 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 13:24:31 -0500 Subject: implemented previews on double-click for the data and conflicts tabs --- src/mainwindow.cpp | 90 +++++++++++++++++++++++++++++++++++++----- src/mainwindow.h | 1 + src/modinfodialogconflicts.cpp | 57 +++++++++++++++++++------- src/modinfodialogconflicts.h | 3 ++ src/modinfodialogfiletree.cpp | 50 +++++++++++------------ src/modinfodialogfiletree.h | 1 + src/settingsdialog.ui | 3 ++ 7 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c365fb6b..01f683a7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -198,6 +198,55 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); +void setDefaultActivationActionForFile(QAction* open, QAction* preview) +{ + if (!open && !preview) { + return; + } + + QFont bold, notBold; + + if (open) { + bold = open->font(); + notBold = open->font(); + } else { + bold = preview->font(); + notBold = preview->font(); + } + + notBold.setBold(false); + bold.setBold(true); + + + const auto& s = Settings::instance(); + const auto openEnabled = (open && open->isEnabled()); + const auto previewEnabled = (preview && preview->isEnabled()); + + bool doPreview = false; + + // preview is bold if the file is previewable and [the preview on double-click + // option is enabled or the file can't be opened]; open is bold if the file + // can be opened and cannot be previewed + if (previewEnabled && s.interface().doubleClicksOpenPreviews()) { + doPreview = true; + } else if (openEnabled) { + doPreview = false; + } else if (previewEnabled) { + doPreview = true; + } else { + // shouldn't happen, checked above + return; + } + + if (open) { + open->setFont(doPreview ? notBold : bold); + } + + if (preview) { + preview->setFont(doPreview ? bold : notBold); + } +} + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore @@ -1770,7 +1819,23 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) { - openDataFile(item); + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); + + const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + previewDataFile(item); + } else { + openDataFile(item); + } } bool MainWindow::refreshProfiles(bool selectProfile) @@ -5287,7 +5352,16 @@ void MainWindow::disableSelectedMods_clicked() void MainWindow::previewDataFile() { - QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); + if (m_ContextItem == nullptr) { + return; + } + + previewDataFile(m_ContextItem); +} + +void MainWindow::previewDataFile(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); m_OrganizerCore.previewFileWithAlternatives(this, fileName); } @@ -5413,6 +5487,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); QAction* open = nullptr; + QAction* preview = nullptr; if (canRunFile(isArchive, fileName)) { open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); @@ -5421,16 +5496,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); } - if (open) { - auto bold = open->font(); - bold.setBold(true); - open->setFont(bold); - } - menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + preview = menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } if (!isArchive && !isDirectory) { @@ -5449,7 +5518,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Hide"), this, SLOT(hideFile())); } } + + setDefaultActivationActionForFile(open, preview); } + menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index f5bab586..98573423 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -444,6 +444,7 @@ private slots: void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); + void previewDataFile(QTreeWidgetItem* item); void hideFile(); void unhideFile(); void openDataOriginExplorer_clicked(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index daa40cb3..9c7ccc8c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -12,6 +12,9 @@ using namespace MOBase; // checking whether menu items apply to them, just show all of them const std::size_t max_small_selection = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + class ConflictItem { @@ -527,20 +530,43 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::activateItems(QTreeView* tree) { + const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); + // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion() - .run(); + const auto path = item->fileName(); + + if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { + previewItem(item); + } else { + openItem(item); + } + + return true; + }); +} +void ConflictsTab::openItems(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + openItem(item); return true; }); } +void ConflictsTab::openItem(const ConflictItem* item) +{ + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion() + .run(); +} + void ConflictsTab::runItemsHooked(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -560,11 +586,16 @@ void ConflictsTab::previewItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().previewFileWithAlternatives(parentWidget(), item->fileName()); + previewItem(item); return true; }); } +void ConflictsTab::previewItem(const ConflictItem* item) +{ + core().previewFileWithAlternatives(parentWidget(), item->fileName()); +} + void ConflictsTab::exploreItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -587,10 +618,6 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) openItems(tree); }); - auto bold = actions.open->font(); - bold.setBold(true); - actions.open->setFont(bold); - menu.addAction(actions.open); } @@ -654,6 +681,8 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } + setDefaultActivationActionForFile(actions.open, actions.preview); + if (!menu.isEmpty()) { menu.exec(tree->viewport()->mapToGlobal(pos)); } @@ -828,15 +857,15 @@ GeneralConflictsTab::GeneralConflictsTab( QObject::connect( ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&&){ m_tab->openItems(ui->overwriteTree); }); + [&](auto&&){ m_tab->activateItems(ui->overwriteTree); }); QObject::connect( ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->overwrittenTree); }); + [&](auto&& item){ m_tab->activateItems(ui->overwrittenTree); }); QObject::connect( ui->noConflictTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->noConflictTree); }); + [&](auto&& item){ m_tab->activateItems(ui->noConflictTree); }); QObject::connect( ui->overwriteTree, &QTreeView::customContextMenuRequested, @@ -1049,7 +1078,7 @@ AdvancedConflictsTab::AdvancedConflictsTab( QObject::connect( ui->conflictsAdvancedList, &QTreeView::activated, - [&]{ m_tab->openItems(ui->conflictsAdvancedList); }); + [&]{ m_tab->activateItems(ui->conflictsAdvancedList); }); QObject::connect( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 8baa62b6..3ac8de23 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -107,11 +107,14 @@ public: void restoreState(const Settings& s) override; bool canHandleUnmanaged() const override; + void activateItems(QTreeView* tree); void openItems(QTreeView* tree); void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); + void openItem(const ConflictItem* item); + void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); void showContextMenu(const QPoint &pos, QTreeView* tree); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index c79a5264..d1ae3823 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -14,6 +14,9 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { @@ -52,7 +55,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) ui->filetree->setEditTriggers( ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); - connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onOpen(); }); + connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onActivated(); }); } void FileTreeTab::clear() @@ -141,7 +144,7 @@ void FileTreeTab::onCreateDirectory() ui->filetree->edit(newIndex); } -void FileTreeTab::onOpen() +void FileTreeTab::onActivated() { auto selection = singleSelection(); if (!selection.isValid()) { @@ -152,13 +155,24 @@ void FileTreeTab::onOpen() const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); if (tryPreview && canPreviewFile(plugin(), false, path)) { - core().previewFile(parentWidget(), mod().name(), path); + onPreview(); } else { - core().processRunner() - .setFromFile(parentWidget(), path) - .setWaitForCompletion() - .run(); + onOpen(); + } +} + +void FileTreeTab::onOpen() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; } + + const auto path = m_fs->filePath(selection); + core().processRunner() + .setFromFile(parentWidget(), path) + .setWaitForCompletion() + .run(); } void FileTreeTab::onRunHooked() @@ -448,27 +462,7 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); - auto bold = m_actions.preview->font(); - bold.setBold(true); - auto notBold = m_actions.preview->font(); - notBold.setBold(false); - - // preview is bold if the file is previewable and [the preview on double-click - // option is enabled or the file can't be opened]; open is bold if the file - // can be opened and cannot be previewed - if (enablePreview && core().settings().interface().doubleClicksOpenPreviews()) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else if (enableOpen) { - m_actions.open->setFont(bold); - m_actions.preview->setFont(notBold); - } else if (enablePreview) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(notBold); - } + setDefaultActivationActionForFile(m_actions.open, m_actions.preview); menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 2f2e501c..c3c84ed4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -35,6 +35,7 @@ private: Actions m_actions; void onCreateDirectory(); + void onActivated(); void onOpen(); void onRunHooked(); void onPreview(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 8e175312..84ca5731 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -137,6 +137,9 @@ + + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. + Open previews on double-click -- cgit v1.3.1 From 317730054d3de1184f20d827eef49391e9b631d4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 21:53:41 -0500 Subject: moved logs back to the bottom of the list, they're not game related --- src/mainwindow.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01f683a7..cb242b88 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4591,23 +4591,27 @@ QMenu *MainWindow::openFolderMenu() { QMenu *FolderMenu = new QMenu(this); + // game folders that are not necessarily MO-specific FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder())); FolderMenu->addSeparator(); + // MO-specific folders that are related to modding the game FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); - FolderMenu->addAction(tr("Open Logs folder"), this, SLOT(openLogsFolder())); FolderMenu->addSeparator(); + // MO-specific folders that are not directly related to modding and are either + // in the installation folder or the instance FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); + FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); return FolderMenu; } -- cgit v1.3.1 From b3331ef2c0b50ab2cea4c328e78f6ab58bca099d Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 6 Dec 2019 23:12:18 -0600 Subject: Separate conflict flags and render them in separate columns --- src/CMakeLists.txt | 3 + src/colortable.cpp | 61 +- src/mainwindow.cpp | 22 +- src/modconflicticondelegate.cpp | 160 ++ src/modconflicticondelegate.h | 36 + src/modflagicondelegate.cpp | 74 +- src/modflagicondelegate.h | 4 - src/modinfo.h | 25 +- src/modinfobackup.cpp | 1 + src/modinfodialogfwd.h | 1 + src/modinfooverwrite.cpp | 9 + src/modinfooverwrite.h | 1 + src/modinfowithconflictinfo.cpp | 4 +- src/modinfowithconflictinfo.h | 3 +- src/modlist.cpp | 40 +- src/modlist.h | 3 + src/modlistsortproxy.cpp | 21 + src/modlistsortproxy.h | 1 + src/organizer_en.ts | 3671 ++++++++++++++++++++++----------------- 19 files changed, 2390 insertions(+), 1750 deletions(-) create mode 100644 src/modconflicticondelegate.cpp create mode 100644 src/modconflicticondelegate.h (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a46908ef..d0215930 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,7 @@ SET(organizer_SRCS previewdialog.cpp aboutdialog.cpp modflagicondelegate.cpp + modconflicticondelegate.cpp genericicondelegate.cpp organizerproxy.cpp viewmarkingscrollbar.cpp @@ -238,6 +239,7 @@ SET(organizer_HDRS previewdialog.h aboutdialog.h modflagicondelegate.h + modconflicticondelegate.h genericicondelegate.h organizerproxy.h viewmarkingscrollbar.h @@ -491,6 +493,7 @@ set(widgets loglist loghighlighter modflagicondelegate + modconflicticondelegate modidlineedit noeditdelegate qtgroupingproxy diff --git a/src/colortable.cpp b/src/colortable.cpp index 61c5ee5f..b1e4ef6c 100644 --- a/src/colortable.cpp +++ b/src/colortable.cpp @@ -1,5 +1,6 @@ #include "colortable.h" #include "modflagicondelegate.h" +#include "modconflicticondelegate.h" #include "settings.h" class ColorItem; @@ -52,21 +53,17 @@ public: } void paint( - QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override + QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { paintBackground(m_table, painter, option, index); ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); } protected: - QList getIcons(const QModelIndex &index) const override + QList getIcons(const QModelIndex& index) const override { const auto flags = { - ModInfo::FLAG_CONFLICT_MIXED, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, - ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, ModInfo::FLAG_BACKUP, ModInfo::FLAG_NOTENDORSED, ModInfo::FLAG_NOTES, @@ -76,7 +73,48 @@ protected: return getIconsForFlags(flags, false); } - size_t getNumIcons(const QModelIndex &index) const override + size_t getNumIcons(const QModelIndex& index) const override + { + return getIcons(index).size(); + } + +private: + QTableWidget* m_table; +}; + + +// delegate for the icons column; paints the background and icons +// +class FakeModConflictIconDelegate : public ModConflictIconDelegate +{ +public: + explicit FakeModConflictIconDelegate(QTableWidget* table) + : m_table(table) + { + } + + void paint( + QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + paintBackground(m_table, painter, option, index); + ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); + } + +protected: + QList getIcons(const QModelIndex& index) const override + { + const auto flags = { + ModInfo::FLAG_CONFLICT_MIXED, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED + }; + + return getIconsForFlags(flags, false); + } + + size_t getNumIcons(const QModelIndex& index) const override { return getIcons(index).size(); } @@ -183,11 +221,12 @@ void paintBackground( ColorTable::ColorTable(QWidget* parent) : QTableWidget(parent), m_settings(nullptr) { - setColumnCount(3); - setHorizontalHeaderLabels({"", "", ""}); + setColumnCount(4); + setHorizontalHeaderLabels({"", "", "", ""}); setItemDelegateForColumn(1, new ColoredBackgroundDelegate(this)); - setItemDelegateForColumn(2, new FakeModFlagIconDelegate(this)); + setItemDelegateForColumn(2, new FakeModConflictIconDelegate(this)); + setItemDelegateForColumn(3, new FakeModFlagIconDelegate(this)); connect( this, &QTableWidget::cellActivated, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cb242b88..68627c90 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -61,6 +61,7 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "tutorialmanager.h" #include "modflagicondelegate.h" +#include "modconflicticondelegate.h" #include "genericicondelegate.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -552,7 +553,16 @@ void MainWindow::setupModList() flagDelegate, SLOT(columnResized(int,int,int))); + ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate( + ui->modList, ModList::COL_CONFLICTFLAGS, 120); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int, int, int)), + conflictFlagDelegate, SLOT(columnResized(int, int, int))); + + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); + ui->modList->setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate); ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); @@ -2317,6 +2327,15 @@ void MainWindow::processUpdates(Settings& settings) { ui->downloadView->header()->hideSection(i); } } + + if (lastVersion < QVersionNumber(2, 2, 2)) { + bool lastHidden = true; + for (int i = ModList::COL_CONFLICTFLAGS; i < ui->modList->model()->columnCount(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(i); + ui->modList->header()->setSectionHidden(i, lastHidden); + lastHidden = hidden; + } + } } if (currentVersion < lastVersion) { @@ -3877,7 +3896,8 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; - case ModList::COL_FLAGS: tab = ModInfoTabIDs::Conflicts; break; + case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; + case ModList::COL_FLAGS: tab = ModInfoTabIDs::Flags; break; } displayModInformation(sourceIdx.row(), tab); diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp new file mode 100644 index 00000000..2ccf3363 --- /dev/null +++ b/src/modconflicticondelegate.cpp @@ -0,0 +1,160 @@ +#include "modconflicticondelegate.h" +#include +#include + +using namespace MOBase; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED + , ModInfo::FLAG_CONFLICT_OVERWRITE + , ModInfo::FLAG_CONFLICT_OVERWRITTEN + , ModInfo::FLAG_CONFLICT_REDUNDANT }; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE + , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED + , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE + , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; + +ModConflictIconDelegate::ModConflictIconDelegate(QObject *parent, int logicalIndex, int compactSize) + : IconDelegate(parent) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) +{ +} + +void ModConflictIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } +} + +QList ModConflictIconDelegate::getIconsForFlags( + std::vector flags, bool compact) +{ + QList result; + + // Don't do flags for overwrite + if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end()) + return result; + + // insert conflict icons to provide nicer alignment + { // insert loose file conflicts first + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ConflictFlags, m_ConflictFlags + 4); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert loose vs archive overwrite second + auto iter = std::find(flags.begin(), flags.end(), + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert loose vs archive overwritten third + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert archive conflicts last + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + auto iconPath = getFlagIcon(*iter); + if (!iconPath.isEmpty()) + result.append(iconPath); + } + + return result; +} + +QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const +{ + QVariant modid = index.data(Qt::UserRole + 1); + + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + return getIconsForFlags(info->getConflictFlags(), m_Compact); + } + + return {}; +} + +QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) +{ + switch (flag) { + case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); + case ModInfo::FLAG_OVERWRITE_CONFLICT: return QString(); + default: + log::warn("ModInfo flag {} has no defined icon", flag); + return QString(); + } +} + +size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const +{ + unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + std::vector flags = info->getConflictFlags(); + size_t count = flags.size(); + if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { + ++count; + } + return count; + } else { + return 0; + } +} + + +QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +{ + size_t count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + QSize result; + if (index < ModInfo::getNumMods()) { + result = QSize(static_cast(count) * 40, 20); + } else { + result = QSize(1, 20); + } + if (option.rect.width() > 0) { + result.setWidth(std::min(option.rect.width(), result.width())); + } + return result; +} + diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h new file mode 100644 index 00000000..d36477c6 --- /dev/null +++ b/src/modconflicticondelegate.h @@ -0,0 +1,36 @@ +#ifndef MODCONFLICTICONDELEGATE_H +#define MODCONFLICTICONDELEGATE_H + +#include "icondelegate.h" + +class ModConflictIconDelegate : public IconDelegate +{ + Q_OBJECT; + +public: + explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + static QList getIconsForFlags( + std::vector flags, bool compact); + + static QString getFlagIcon(ModInfo::EConflictFlag flag); + +public slots: + void columnResized(int logicalIndex, int oldSize, int newSize); + +protected: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; + +private: + static ModInfo::EConflictFlag m_ConflictFlags[4]; + static ModInfo::EConflictFlag m_ArchiveLooseConflictFlags[2]; + static ModInfo::EConflictFlag m_ArchiveConflictFlags[3]; + + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; +}; + +#endif // MODCONFLICTICONDELEGATE_H diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index a5e9aa22..6e1df147 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -4,18 +4,6 @@ using namespace MOBase; -ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED - , ModInfo::FLAG_CONFLICT_OVERWRITE - , ModInfo::FLAG_CONFLICT_OVERWRITTEN - , ModInfo::FLAG_CONFLICT_REDUNDANT }; - -ModInfo::EFlag ModFlagIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; - -ModInfo::EFlag ModFlagIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; - ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent, int logicalIndex, int compactSize) : IconDelegate(parent) , m_LogicalIndex(logicalIndex) @@ -37,54 +25,9 @@ QList ModFlagIconDelegate::getIconsForFlags( QList result; // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) return result; - // insert conflict icons to provide nicer alignment - { // insert loose file conflicts first - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ConflictFlags, m_ConflictFlags + 4); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert loose vs archive overwrite second - auto iter = std::find(flags.begin(), flags.end(), - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert loose vs archive overwritten third - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert archive conflicts last - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { auto iconPath = getFlagIcon(*iter); if (!iconPath.isEmpty()) @@ -113,15 +56,6 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) case ModInfo::FLAG_INVALID: return QStringLiteral(":/MO/gui/problem"); case ModInfo::FLAG_NOTENDORSED: return QStringLiteral(":/MO/gui/emblem_notendorsed"); case ModInfo::FLAG_NOTES: return QStringLiteral(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); case ModInfo::FLAG_ALTERNATE_GAME: return QStringLiteral(":/MO/gui/alternate_game"); case ModInfo::FLAG_FOREIGN: return QString(); case ModInfo::FLAG_SEPARATOR: return QString(); @@ -140,11 +74,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const if (modIdx < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(modIdx); std::vector flags = info->getFlags(); - size_t count = flags.size(); - if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { - ++count; - } - return count; + return flags.size(); } else { return 0; } diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index 4f22dd90..ecab7e95 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -24,10 +24,6 @@ protected: virtual size_t getNumIcons(const QModelIndex &index) const; private: - static ModInfo::EFlag m_ConflictFlags[4]; - static ModInfo::EFlag m_ArchiveLooseConflictFlags[2]; - static ModInfo::EFlag m_ArchiveConflictFlags[3]; - int m_LogicalIndex; int m_CompactSize; bool m_Compact; diff --git a/src/modinfo.h b/src/modinfo.h index 30a115c7..7c41e0a1 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -60,14 +60,7 @@ public: static QString s_HiddenExt; - enum EFlag { - FLAG_INVALID, - FLAG_BACKUP, - FLAG_SEPARATOR, - FLAG_OVERWRITE, - FLAG_FOREIGN, - FLAG_NOTENDORSED, - FLAG_NOTES, + enum EConflictFlag { FLAG_CONFLICT_OVERWRITE, FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, @@ -77,6 +70,17 @@ public: FLAG_ARCHIVE_CONFLICT_OVERWRITE, FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, + FLAG_OVERWRITE_CONFLICT, + }; + + enum EFlag { + FLAG_INVALID, + FLAG_BACKUP, + FLAG_SEPARATOR, + FLAG_OVERWRITE, + FLAG_FOREIGN, + FLAG_NOTENDORSED, + FLAG_NOTES, FLAG_PLUGIN_SELECTED, FLAG_ALTERNATE_GAME, FLAG_TRACKED, @@ -519,6 +523,11 @@ public: */ virtual std::vector getFlags() const = 0; + /** + * @return a list of conflict flags for this mod + */ + virtual std::vector getConflictFlags() const = 0; + /** * @return a list of content types contained in a mod */ diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp index 6e307103..6a34b86a 100644 --- a/src/modinfobackup.cpp +++ b/src/modinfobackup.cpp @@ -1,5 +1,6 @@ #include "modinfobackup.h" + std::vector ModInfoBackup::getFlags() const { std::vector result = ModInfoRegular::getFlags(); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 2147fc04..e4a61208 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -14,6 +14,7 @@ enum class ModInfoTabIDs Images, Esps, Conflicts, + Flags, Categories, Nexus, Notes, diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index fb110abb..9a6a22c1 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -37,6 +37,15 @@ std::vector ModInfoOverwrite::getFlags() const return result; } +std::vector ModInfoOverwrite::getConflictFlags() const +{ + std::vector result; + result.push_back(FLAG_OVERWRITE_CONFLICT); + for (auto flag : ModInfoWithConflictInfo::getConflictFlags()) + result.push_back(flag); + return result; +} + int ModInfoOverwrite::getHighlight() const { int highlight = (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index c5f58c2e..ecbdbe3d 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -51,6 +51,7 @@ public: virtual QDateTime getExpires() const { return QDateTime(); } virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; + virtual std::vector getConflictFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; virtual int getNexusFileStatus() const { return 0; } diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 7a2b0071..f5a243ae 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -14,9 +14,9 @@ void ModInfoWithConflictInfo::clearCaches() m_LastConflictCheck = QTime(); } -std::vector ModInfoWithConflictInfo::getFlags() const +std::vector ModInfoWithConflictInfo::getConflictFlags() const { - std::vector result; + std::vector result; switch (isConflicted()) { case CONFLICT_MIXED: { result.push_back(ModInfo::FLAG_CONFLICT_MIXED); diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 13d87d25..9869bc5a 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -12,7 +12,8 @@ public: ModInfoWithConflictInfo(PluginContainer *pluginContainer, MOShared::DirectoryEntry **directoryStructure); - std::vector getFlags() const; + std::vector getConflictFlags() const; + virtual std::vector getFlags() const { return std::vector(); }; /** * @brief clear all caches held for this mod diff --git a/src/modlist.cpp b/src/modlist.cpp index b7a9b0a1..c5b8c856 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -159,15 +159,6 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const output << QString("%1").arg(modInfo->notes()); return output.join(""); } - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); case ModInfo::FLAG_ALTERNATE_GAME: return tr("
This mod is for a different game, " "make sure it's compatible or it could cause crashes."); case ModInfo::FLAG_TRACKED: return tr("Mod is being tracked on the website"); @@ -176,6 +167,23 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const } +QString ModList::getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const +{ + switch (flag) { + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); + default: return ""; + } +} + + QVariantList ModList::contentsToIcons(const std::vector &contents) const { QVariantList result; @@ -218,7 +226,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { if ((column == COL_FLAGS) - || (column == COL_CONTENT)) { + || (column == COL_CONTENT) + || (column == COL_CONFLICTFLAGS)) { return QVariant(); } else if (column == COL_NAME) { auto flags = modInfo->getFlags(); @@ -443,6 +452,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const result += getFlagText(flag, modInfo); } + return result; + } else if (column == COL_CONFLICTFLAGS) { + QString result; + + for (ModInfo::EConflictFlag flag : modInfo->getConflictFlags()) { + if (result.length() != 0) result += "
"; + result += getConflictFlagText(flag, modInfo); + } + return result; } else if (column == COL_CONTENT) { return contentsToToolTip(modInfo->getContents()); @@ -1274,6 +1292,7 @@ void ModList::dropModeUpdate(bool dropOnItems) QString ModList::getColumnName(int column) { switch (column) { + case COL_CONFLICTFLAGS: return tr("Conflicts"); case COL_FLAGS: return tr("Flags"); case COL_CONTENT: return tr("Content"); case COL_NAME: return tr("Mod Name"); @@ -1299,6 +1318,7 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_GAME: return tr("The source game which was the origin of this mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); + case COL_CONFLICTFLAGS: return tr("Indicators of file conflicts between mods."); case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); case COL_CONTENT: return tr("Depicts the content of the mod:
" "" diff --git a/src/modlist.h b/src/modlist.h index 631401c0..8841ec29 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,6 +57,7 @@ public: enum EColumn { COL_NAME, + COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -289,6 +290,8 @@ private: QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; + QString getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const; + static QString getColumnToolTip(int column); QVariantList contentsToIcons(const std::vector &content) const; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 64d5de42..440786c5 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -115,6 +115,17 @@ unsigned long ModListSortProxy::flagsId(const std::vector &flags return result; } +unsigned long ModListSortProxy::conflictFlagsId(const std::vector& flags) const +{ + unsigned long result = 0; + for (ModInfo::EConflictFlag flag : flags) { + if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { + result += 1 << (int)flag; + } + } + return result; +} + bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { @@ -155,6 +166,16 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, lt = flagsId(leftFlags) < flagsId(rightFlags); } } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } + else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; case ModList::COL_CONTENT: { std::vector lContent = leftMod->getContents(); std::vector rContent = rightMod->getContents(); diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 46356fe9..d733b783 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -142,6 +142,7 @@ protected: private: unsigned long flagsId(const std::vector &flags) const; + unsigned long conflictFlagsId(const std::vector& flags) const; bool hasConflictFlag(const std::vector &flags) const; void updateFilterActive(); bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index e3531cb2..72972e4f 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -15,117 +15,122 @@ - - <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + + usvfs: - + + <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer">GitHub</a>.</p></body></html> + + + + Used Software - + Thanks - + Lead Developers && Maintainers - + LePresidente (Project Lead) - + MO2 Developers && Contributors - + Translators - + Cyb3r (Dutch) - + fruttyx (French) - + Yoplala (French) - + Faron (German) - + yohru (Japanese) - + Mordan (Greek) - + Yoosk (Polish) - + Brgodfx (Portuguese) - + zDas (Portuguese) - + Jax (Swedish) - + Nubbie (Swedish) - + ...and all other contributors! - + Other Supporters && Contributors - + Tannin (Original Creator) - + Close - + No license @@ -173,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -289,33 +294,43 @@ p, li { white-space: pre-wrap; } ConflictsTab - - &Hide + + &Execute - - &Unhide + + &Open - - &Open/Execute + + Open with &VFS - + &Preview - + + &Go to... + + + + Open in &Explorer - - &Go to... + + &Hide + + + + + &Unhide @@ -403,79 +418,84 @@ p, li { white-space: pre-wrap; } - - < game %1 mod %2 file %3 > + + Source Game - Unknown + < game %1 mod %2 file %3 > + Unknown + + + + Pending - + Started - + Canceling - + Pausing - + Canceled - + Paused - + Error - - - + + + Fetching Info - + Downloaded - + Installed - + Uninstalled - + Pending download - + Information missing, please select "Query Info" from the context menu to re-retrieve. @@ -483,156 +503,153 @@ p, li { white-space: pre-wrap; } DownloadListWidget - + Install - + Query Info - + Visit on Nexus - + Open File - - - + + + Show in Folder - - + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Resume - + Delete Installed Downloads... - Delete Installed... - + Delete Uninstalled Downloads... - Delete Uninstalled... - + Delete All Downloads... - Delete All... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - - - - + + + + Delete Files? - + This will permanently delete the selected download. Are you absolutely sure you want to proceed? - + This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed? - - - + + + Hide Files? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). @@ -645,37 +662,37 @@ Are you absolutely sure you want to proceed? - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -683,12 +700,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -696,276 +713,277 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + Hashing download file '%1' - + Cancel - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -998,19 +1016,18 @@ Canceling download "%2"... - + Remove the selected executable - + Remove - @@ -1018,7 +1035,11 @@ Canceling download "%2"... - + + Up + + + @@ -1026,261 +1047,339 @@ Canceling download "%2"... - + + Down + + + + Adds the executables provided by the game plugin and moves any existing executables out of the way - + Reset - + List of configured executables - + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. - + Title - + Name of the executable. This is only for display purposes. - + Binary - + Binary to run - + Browse filesystem - + Browse filesystem for the executable to run. - - + + ... - + Start in - + Arguments - + Arguments to pass to the application - + Allow the Steam AppID to be used for this executable to be changed. - + Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - + Overwrite Steam AppID - + Steam AppID to use for this executable that differs from the games AppID. - + Steam AppID to use for this executable that differs from the games AppID. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. - + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - - Create Files in Mod instead of Overwrite (*) + + Create files in mod instead of overwrite (*) - + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - - Force Load Libraries (*) + + Force load libraries (*) - + Configure Libraries - - Use Application's Icon for desktop shortcuts - Use Application's Icon for shortcuts + + Use application's icon for desktop shortcuts + + + + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + + + Hide in user interface + + + + + (*) Profile specific + + + + + Add from file... + + + + + Add empty - - (*) Profile Specific + + Clone selected - + Reset plugin executables - + This will restore all the executables provided by the game plugin. If there are existing executables with the same names, they will be automatically renamed and left unchanged. - - + + New Executable - - Select a binary + + Select a directory - - Executable (%1) + + Executables (*.exe *.bat *.jar) - - Select a directory + + All Files (*.*) - - Java (32-bit) required + + Select an executable - - MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. + + Java required + + + + + MO requires Java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. FileTreeTab - + &New Folder - + &Open/Execute - + + Open with &VFS + + + + &Preview - + Open in &Explorer - + &Rename - + &Delete - + &Hide - + &Unhide - - + + New Folder - + Failed to create "%1" - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 + + + &Execute + + + + + &Open + + + + + FilterList + + + Not + + + + + Filter separators + + + + + Show separators + + + + + Hide separators + + + + + <Contains %1> + + FindDialog @@ -1416,9 +1515,16 @@ Right now the only case I know of where this needs to be overwritten is for the Extracting files + + + + + Extraction failed: %1 + + - failed to create backup + Failed to create backup @@ -1511,29 +1617,6 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - - LockedDialog - - - Running virtualized processes - - - - - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - - - - - MO is locked while the executable is running. - - - - - Unlock - - - LogList @@ -1572,6 +1655,57 @@ This is likely due to a corrupted or incompatible download or unrecognized archi + + Loot + + + failed to start loot + + + + + Loot failed. Exit code was: %1 + + + + + LootDialog + + + LOOT + + + + + Progress + + + + + about:blank + + + + + Details + + + + + Open JSON report + + + + + Stopping LOOT... + + + + + Loot failed to run + + + MOApplication @@ -1598,17 +1732,17 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + failed to write to %1 - + file not found: %1 - + Save @@ -1632,48 +1766,59 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - - Categories + + Filters - + Clear - - If checked, only mods that match all selected categories are displayed. + + Edit... + + + + + Display mods that match all selected categories. - + And - - If checked, all mods that match at least one of the selected categories are displayed. + + Display mods that match at least one of the selected categories - + Or - + + Filter: only show the separators that match the current filters +Show: always show separators +Hide: never show separators + + + + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1683,84 +1828,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - List of available mods. - - - - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Clear all Filters - + No groups - + + Categories + + + + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1770,12 +1915,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1784,17 +1929,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1803,32 +1948,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1837,27 +1982,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> + + <html><head/><body><p>Currently detected archives. (<a href="#">What is an archive?</a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1865,72 +2010,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1941,1170 +2086,1119 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar - St&atus bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + + (no executables) + + + + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - - <Contains %1> + + failed to rename mod: %1 - - <Checked> + + Overwrite? - - <Unchecked> + + This will replace the existing mod "%1". Continue? - - <Update> + + failed to remove mod "%1" - - <Mod Backup> + + + + failed to rename "%1" to "%2" - - <Managed by MO> + + + + + Confirm - - <Managed outside MO> + + Remove the following mods?<br><ul>%1</ul> - - <No category> + + failed to remove mod: %1 - - <Conflicted> + + + + Failed - - <Not Endorsed> + + Installation file no longer exists - - failed to rename mod: %1 - - - - - Overwrite? - - - - - This will replace the existing mod "%1". Continue? - - - - - failed to remove mod "%1" - - - - - - - failed to rename "%1" to "%2" - - - - - - - - Confirm - - - - - Remove the following mods?<br><ul>%1</ul> - - - - - failed to remove mod: %1 - - - - - - - Failed - - - - - Installation file no longer exists - - - - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + + Open MO2 Stylesheets folder + + + + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - - <All> - - - - - <Multiple> - - - - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3112,12 +3206,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3125,349 +3219,330 @@ You can also use online editors and converters instead. - - Restarting MO + + Restart Mod Organizer - - Changing the managed game directory requires restarting MO. -Any pending downloads will be paused. - -Click OK to restart MO now. + + Mod Organizer must restart to finish configuration changes + + + + + Restart + + + + + Continue - + + Some things might be weird. + + + + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - - Please enter a name for the executable + + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - + Replace file? - + There already is a hidden version of this file. Replace it? - + File operation failed - + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - - Open/Execute + + &Execute - - Add as Executable + + &Open + + + + + Open with &VFS - + + &Add as Executable + + + + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - - Edit Categories... - - - - - Deselect filter + + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - - depends on missing "%1" - - - - - incompatible with "%1" - - - - - Please wait while LOOT is running - - - - - loot failed. Exit code was: %1 - - - - - failed to start loot - - - - - failed to run loot: %1 - - - - - Errors occurred - - - - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3484,101 +3559,100 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. ModInfo - + Plugins - + Textures - + Meshes - + Bethesda Archive - + UI Changes - + Sound Effects - + Scripts - + Script Extender - + Script Extender Files - + SkyProc Tools - + MCM Data - + INI files - + ModGroup files - + invalid content type: %1 - + invalid mod index: %1 - + remove: invalid mod index %1 - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. - You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. ModInfoBackup - + This is the backup of a mod @@ -3638,27 +3712,32 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. + Open with Preview Plugin + + + + Open in Explorer - + 0x0 - - + + Optional ESPs - + List of esps, esms, and esls that can not be loaded by the game. - + List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -3666,135 +3745,132 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Move a file to the data directory. - + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of MO. - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - + Make the selected mod in the right list unavailable. - Make the selected mod in the lower list unavailable. - + The selected esp (in the right list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - + Available ESPs - + ESPs in the data directory and thus visible to the game. - + <html><head/><body><p>These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window.</p></body></html> - + Conflicts - + General - + The following conflicted files are provided by this mod - + The following conflicted files are provided by other mods - + The following files have no conflicts - + Advanced - + Whether files that have no conflicts should be visible in the list - + Show files that have no conflicts - + Shows all mods overwriting or being overwritten by this mod - + Show all conflicting mods - + Shows only the nearest conflicting mods, in order of priority - + Show nearest conflicting mod - + Filter - + Categories - + Primary Category - + Nexus Info - + Mod ID - + Mod ID for this mod on Nexus. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3803,22 +3879,22 @@ p, li { white-space: pre-wrap; } - + Source Game - + Source game for this mod. - + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3827,83 +3903,83 @@ p, li { white-space: pre-wrap; } - + Version - - + + Refresh - + Refresh all information from Nexus. - - + + Open in Browser - + Endorse - + Track - + about:blank - + Use Custom URL - + Notes - - - + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - - + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3913,17 +3989,17 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close @@ -3954,7 +4030,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) @@ -3983,318 +4059,328 @@ p, li { white-space: pre-wrap; } ModList - + Game Plugins (ESP/ESM/ESL) - + Interface - + Meshes - + Bethesda Archive - + Scripts (Papyrus) - + Script Extender Plugin - + SkyProc Patcher - + Sound or Music - + Textures - + MCM Configuration - + INI files - + ModGroup files - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + Separator - + No valid game data - + Not endorsed yet - + + <br>This mod is for a different game, make sure it's compatible or it could cause crashes. + + + + + Mod is being tracked on the website + + + + Overwrites loose files - + Overwritten loose files - + Loose files Overwrites & Overwritten - + Redundant - + Overwrites an archive with loose files - + Archive is overwritten by loose files - + Overwrites another archive file - + Overwritten by another archive file - + Archive files overwrites & overwritten - - <br>This mod is for a different game, make sure it's compatible or it could cause crashes. - - - - - Mod is being tracked on the website - - - - + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + This file has been marked as "Old". There is most likely an updated version of this file available. - + This file has been marked as "Deleted"! You may want to check for an update or remove the nexus ID from this mod! - + %1 minute(s) and %2 second(s) - + This mod will be available to check in %2. - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + + Conflicts + + + + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - - Emblemes to highlight things that might require attention. + + Indicators of file conflicts between mods. + + + + + Emblems to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -4302,7 +4388,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4345,31 +4431,31 @@ p, li { white-space: pre-wrap; } NexusInterface - Failed to guess mod id for "%1", please pick the correct one + Please pick the mod ID for "%1" - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -4452,7 +4538,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4460,212 +4546,207 @@ p, li { white-space: pre-wrap; } OrganizerCore - - Failed to write settings - - - - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + + Failed to write settings + + + + An error occurred trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - - Error - - - - - No profile set - - - - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + + Error + + + + The designated write target "%1" is not enabled. @@ -4673,12 +4754,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4721,43 +4802,43 @@ Continue? - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - + + Are you sure you want to delete "%1"? - - + + Are you sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -4765,12 +4846,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -4778,18 +4859,18 @@ Continue? PluginContainer - + Some plugins could not be loaded - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: @@ -4797,152 +4878,176 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - - Name of your mods + + Name of the plugin + + + + + Emblems to highlight things that might require attention. - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + + Load priority of plugins. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - - The modindex determines the formids of objects originating from this mods. + + Determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + Plugin not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - - <b>Origin</b>: %1 + + Origin - - <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> + + This plugin can't be disabled (enforced by the game). - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - - failed to restore load order for %1 + + Incompatible with %1 - - - PluginListSortProxy - - Drag&Drop is only supported when sorting by priority or mod index + + Depends on missing %1 - - - PreviewDialog - - Preview + + Warning + + + + + Error + + + + + failed to restore load order for %1 + + + + + PluginListSortProxy + + + Drag&Drop is only supported when sorting by priority or mod index + + + + + PreviewDialog + + + Preview @@ -4965,11 +5070,6 @@ Continue? p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click a notification above to get more details...</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> @@ -5326,33 +5426,53 @@ p, li { white-space: pre-wrap; } - QApplication + QObject - + + + + INI file is read-only - - - Mod Organizer is attempting to write to "%1" which is currently set to read-only. Clear the read-only flag to allow the write? + + + Mod Organizer is attempting to write to "%1" which is currently set to read-only. - - File is read-only + + + Clear the read-only flag + + + + + + Allow the write once + + + + + + The file will be set to read-only again. + + + + + + Skip this file - - - QObject - - - - + + + + + Error @@ -5379,8 +5499,8 @@ p, li { white-space: pre-wrap; } - - failed to open temporary file + + Failed to save '{}', could not create a temporary file: {} (error {}) @@ -5399,52 +5519,73 @@ p, li { white-space: pre-wrap; } - - + + Error %1 + + + + + failed to create directory "%1" - - + + failed to copy "%1" to "%2" - + %1 MB - + %1 GB - + %1 TB - + %1 KB - + %1 B/s - + %1 KB/s - + %1 MB/s + + + Regular + + + + + Premium + + + + + + None + + Failed to save custom categories @@ -5454,15 +5595,95 @@ p, li { white-space: pre-wrap; } - + invalid category index: %1 - + + <Active> + + + + + <Update available> + + + + + <Has category> + + + + + <Conflicted> + + + + + <Endorsed> + + + + + <Has backup> + + + + + <Managed> + + + + + <Has valid game data> + + + + + <Has Nexus ID> + + + + + <Tracked on Nexus> + + + + invalid category id: %1 + + + Is overwritten (loose files) + + + + + Is overwriting (loose files) + + + + + Is overwritten (archives) + + + + + Is overwriting (archives) + + + + + Mod contains selected plugin + + + + + Plugin is contained in selected mod + + invalid field name "%1" @@ -5504,34 +5725,40 @@ p, li { white-space: pre-wrap; } - + The hidden file "%1" already exists. Replace it? - + The visible file "%1" already exists. Replace it? - + Replace file? - - + + File operation failed - - Failed to remove "%1". Maybe you lack the required file permissions? + + Failed to remove "%1": %2 - - failed to rename %1 to %2 + + Failed to rename file: %1. + +Source: +"%2" + +Destination: +"%3" @@ -5670,122 +5897,214 @@ If the folder was still in use, restart MO and try again. - + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. - - + + General messages + + + + + Plugins + + + + + No messages. + + + + + Incompatibilities + + + + + Missing masters + + + + + Verified clean by %1 + + + + + %1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es). + + + + + + + Warning + + + + + failed to run loot: %1 + + + + + Checking masterlist existence + + + + + Updating masterlist + + + + + Loading lists + + + + + Reading plugins + + + + + Sorting plugins + + + + + Writing loadorder.txt + + + + + Parsing loot messages + + + + + Done + + + + + Failed to create "%1". Your user account probably lacks permission. - + Plugin to handle %1 no longer installed - - - + + + The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. - + Could not use configuration settings for game "%1", path "%2". - - - + + + Please select the installation of %1 to manage - - - + + + Please select the game to manage - + Canceled finding %1 in "%2". - + Canceled finding game in "%1". - + %1 not identified in "%2". The directory is required to contain the game binary. - + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + + <Unmanaged> + + + + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5821,512 +6140,586 @@ If the folder was still in use, restart MO and try again. - - This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. + + Connecting to Nexus... - - This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. + + Waiting for Nexus... - - The file '%1' does not exist. + + Opened Nexus in browser. - - - - - Cannot start Steam + + Switch to your browser and accept the request. - - The path to the Steam executable cannot be found. You might try reinstalling Steam. + + Finished. - - - - Continue without starting Steam + + No answer from Nexus. - - - The program may fail to launch. + + + A firewall might be blocking Mod Organizer. - - Cannot launch program + + Nexus closed the connection. - - - - Cannot start %1 + + Cancelled. - - Cannot launch helper + + Failed to request %1 - - This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". - -You can restart Mod Organizer as administrator and try launching the program again. + + + Cancelled - - - Restart Mod Organizer as administrator + + Internal error - - - You must allow "helper.exe" to make changes to the system. + + HTTP code %1 - - Launch Steam + + Invalid JSON - - This program requires Steam + + Bad response - - Mod Organizer has detected that this program likely requires Steam to be running to function properly. + + API key is empty - - Start Steam + + SSL error - - - The program might fail to run. + + Timed out - - Steam is running as administrator + + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. - - Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. - -You can restart Mod Organizer as administrator and try launching the program again. + + failed to initialize plugin %1: %2 - - - - Continue + + Plugin error - - Event Log not running + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? +(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - - The Event Log service is not running + + failed to access %1 - - The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. + + failed to set file time %1 - - - Your mods might not work. + + + + No profile set - - Blacklisted program + + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - - The program %1 is blacklisted + + + attempt to store setting for unknown plugin "%1" - - The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. + + Failed - - Change the blacklist + + Failed to start the helper application: %1 - - Waiting + + + Debug - - Please press OK once you're logged into steam. + + + Info (recommended) - - One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is incompatible with MO2's VFS system. + + Trace - - Select binary + + Mini (recommended) - - Binary + + Data - - failed to initialize plugin %1: %2 + + Full - - Plugin error + + Confirm? - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? -(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) + + This will reset all the choices you made to dialogs and make them all visible again. Continue? - - failed to access %1 + + Connected. - - failed to set file time %1 + + Not connected. - - Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! + + Disconnected. - - - Elevation required + + Checking API key... - - This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. + + Received API key. - - Loading... + + Received user acount information - - &Save + + Linked with Nexus successfully. - - &Word wrap + + Failed to set API key - - &Open in Explorer + + + + + + + + + + + + Cancel - - Regular + + + + Enter API Key Manually - - Premium + + + + Connect to Nexus - - - None + + + + + + N/A - - - Connecting to Nexus... + + Failed to create "%1", you may not have the necessary permissions. Path remains unchanged. - - Waiting for Nexus... + + Select base directory - - Opened Nexus in browser. -Switch to your browser and accept the request. + + Select download directory - - - Finished. + + Select mod directory - - No answer from Nexus. -A firewall might be blocking Mod Organizer. + + Select cache directory - - Nexus closed the connection. + + Select profiles directory - - Cancelled. + + Select overwrite directory - - Invalid JSON + + Select game executable - - Bad response + + Executables Blacklist - - There was a timeout during the request + + Enter one executable per line to be blacklisted from the virtual file system. +Mods and other virtualized files will not be visible to these executables and +any executables launched by them. + +Example: + Chrome.exe + Firefox.exe - - Cancelled + + + + Restart Mod Organizer - - Failed to request %1 + + Geometries will be reset to their default values. - - - attempt to store setting for unknown plugin "%1" + + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. - - Failed + + This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. - - Failed to start the helper application + + The file '%1' does not exist. - - Debug + + + + + Cannot start Steam - - Info (recommended) + + The path to the Steam executable cannot be found. You might try reinstalling Steam. - - Warning + + + + Continue without starting Steam - - Mini (recommended) + + + The program may fail to launch. - - Data + + Cannot launch program - - Full + + + + Cannot start %1 - - Confirm? + + Cannot launch helper + + + + + + Elevation required + + + + + This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". + +You can restart Mod Organizer as administrator and try launching the program again. + + + + + + Restart Mod Organizer as administrator + + + + + + You must allow "helper.exe" to make changes to the system. + + + + + Launch Steam + + + + + This program requires Steam + + + + + Mod Organizer has detected that this program likely requires Steam to be running to function properly. + + + + + Start Steam + + + + + + The program might fail to run. + + + + + Steam is running as administrator + + + + + Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. + +You can restart Mod Organizer as administrator and try launching the program again. + + + + + + + Continue + + + + + Event Log not running + + + + + The Event Log service is not running - - This will reset all the choices you made to dialogs and make them all visible again. Continue? + + The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. - - Disconnected. + + + Your mods might not work. - - Checking API key... + + Blacklisted program - - Received API key. + + The program %1 is blacklisted - - Linked with Nexus successfully. + + The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. - - - - - - - - - - Cancel + + Change the blacklist - - - - Enter API Key Manually + + Waiting - - - - Connect to Nexus + + Please press OK once you're logged into steam. - - - - - - N/A + + Select binary - - Failed to create "%1", you may not have the necessary permissions. Path remains unchanged. + + Binary - - Select base directory + + This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. - - Select download directory + + Loading... - - Select mod directory + + &Save - - Select cache directory + + &Word wrap - - Select profiles directory + + &Open in Explorer - - Select overwrite directory + + Mod Organizer is locked while the application is running. - - Select game executable + + Mod Organizer is currently running an application. - - Executables Blacklist + + The application must run to completion because its output is required. - - Enter one executable per line to be blacklisted from the virtual file system. -Mods and other virtualized files will not be visible to these executables and -any executables launched by them. - -Example: - Chrome.exe - Firefox.exe + + Mod Organizer is waiting on application to close before exiting. - - Restart Mod Organizer? + + Unlock - - In order to reset the geometry, Mod Organizer must be restarted. -Restart now? + + Exit Now @@ -6445,58 +6838,58 @@ Restart now? - + New update available (%1) - + Do you want to install update? All your mods and setup will be left untouched. Select Show Details option to see the full change-log. - + Install - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - - Failed to start %1 + + Failed to start %1: %2 - + Error @@ -6514,473 +6907,488 @@ Select Show Details option to see the full change-log. - + + User Interface + + + + Language - - The display language + + Style - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + Visual theme of the user interface. - - Style + + Explore... - - graphical style + + + The language of the user interface. - - graphical style of the MO user interface + + https://www.transifex.com/tannin/mod-organizer/ - - Update to non-stable releases. + + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). - -Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - -If you use pre-releases, never contact me directly by e-mail or via private messages! + + + Dialogs will always be centered on the main window, but will remember their size. - - Install Pre-releases (Betas) + + Always center dialogs - - User interface + + Show confirmation when changing instance - - Colors + + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + Open previews on double-click - - Show mod list separator colors on the scrollbar + + + Reset all choices made in dialogs. - - Plugin is Contained in selected Mod + + Reset Dialog Choices - - Is overwritten (loose files) + + + Modify the categories available to arrange your mods. - - Is overwriting (loose files) + + Configure Mod Categories - - Reset Colors + + Download List - - Mod Contains selected Plugin + + + Show meta information instead of file names in the download list. - - Is overwritten (archive files) + + Show Meta Information - - Is overwriting (archive files) + + + Make the download list more compact. - - - Modify the categories available to arrange your mods. + + Compact List - - Configure Mod Categories + + Colors - - Reset stored information from dialogs. + + + Colors set on separators will also be shown in the mod list scrollbar at the location of the separator. This can be useful for quickly navigating to a specific separator. - - This will make all dialogs show up again where you checked the "Remember selection"-box. + + Show mod list separator colors on the scrollbar - - If checked, the download interface will be more compact. + + + Reset all colors to their default value. - - Compact Download Interface + + Reset Colors - - If checked, the download list will display meta information instead of file names. + + Updates + + + + + + Check for Mod Organizer updates on Github on startup. - - Download Meta Information + + Check for updates + + + + + + Update to non-stable releases. + + + + + Install Pre-releases (Betas) - + Paths - - - - + + + + ... - + Caches - + Overwrite - - + + Directory where downloads are stored. - + Downloads - + Profiles - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Mods - + Managed Game - + Base Directory - + Use %BASE_DIR% to refer to the Base Directory. - + Important: All directories have to be writable! - + Nexus - - Connect to Nexus - - - - - Manually enter the API key and try to login + + Nexus Account - - Enter API Key Manually + + User ID: - - Clear the stored Nexus API key and force reauthorization. + + id - - Disconnect from Nexus + + Name: - - - <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + + name - - Remove cache and cookies. + + Account: - - Clear Cache + + account - - Disable automatic internet features + + Statistics - - Reset Dialog Choices + + Daily requests: - - Nexus Account + + daily requests - - User ID: + + Hourly requests: - - id + + hourly requests - - Name: + + Nexus Connection - - name + + Connect to Nexus - - Account: + + Manually enter the API key and try to login - - account + + Enter API Key Manually - - Statistics + + Clear the stored Nexus API key and force reauthorization. - - Daily requests: + + Disconnect from Nexus - - daily requests + + Disable automatic internet features - - Hourly requests: + + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - - hourly requests + + Offline Mode - - Nexus Connection + + Use a proxy for network connections. - - Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) + + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - - Offline Mode + + Use HTTP Proxy (Uses System Settings) - - Use a proxy for network connections. + + Endorsement Integration - - Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. + + + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - - Use HTTP Proxy (Uses System Settings) + + Hide API Request Counter - - Endorsement Integration + + Associate with "Download with manager" links - - Hide API Request Counter + + Remove cache and cookies. - - Associate with "Download with manager" links + + Clear Cache - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + <html><head/><body><p>If you save your steam user ID and password here, they will be used when logging into steam.</p></body></html> - + Password - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6996,17 +7404,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -7017,28 +7425,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -7046,66 +7454,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -7114,81 +7522,81 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - - Max Dumps To Keep + + Hint: right click link and copy link location - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> + and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. + Sending logs and/or crash dumps to the developers can help investigate issues. + It is recommended to compress large log and dmp files before sending. + - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. - + + Log Level - - Hint: right click link and copy link location + + Decides the amount of data printed to "ModOrganizer.log" - + - Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> - and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. - Sending logs and/or crash dumps to the developers can help investigate issues. - It is recommended to compress large log and dmp files before sending. - + Decides the amount of data printed to "ModOrganizer.log". + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -7199,41 +7607,35 @@ programs you are intentionally running. - - Log Level + + Max Dumps To Keep - - Decides the amount of data printed to "ModOrganizer.log" + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + - Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. - - Restart Mod Organizer? + + LOOT Log Level - - In order to finish configuration changes, MO must be restarted. -Restart it now? - - - - + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? @@ -7241,22 +7643,22 @@ Restart it now? SingleInstance - + SHM error: %1 - + failed to connect to running instance: %1 - + failed to communicate with running instance: %1 - + failed to receive data from secondary instance: %1 @@ -7441,7 +7843,7 @@ On Windows XP: UsvfsConnector - + Preparing vfs @@ -7449,41 +7851,29 @@ On Windows XP: ValidationProgressDialog - + Validating Nexus Connection - - Hide - - - - - WaitingOnCloseDialog - - - Waiting for virtualized processes - - - - - This dialog should disappear automatically if the application/game is done. + + + Connecting to Nexus... - - Virtualized processes are still running, it is prefered to keep MO running until they are finished. + + Cancel - - Close Now + + Hide - - Cancel + + Trying again... @@ -7801,7 +8191,6 @@ Please open the "Nexus"-tab Use this interface to obtain an API key from NexusMods. This is used for all API connections - downloads, updates etc. MO2 uses the Windows Credential Manager to store this data securely. If the SSO page on Nexus is failing, use the manual entry and copy the API key from your profile. - Use this interface to obtain an API key from NexusMods.This is used for all API connections - downloads, updatesetc. MO2 uses the Windows Credential Manager to storethis data securely. If the SSO page on Nexus is failing,use the manual entry and copy the API key from your profile. -- cgit v1.3.1 From d13ea08be82d88c4f0b845983a3390729723d0e1 Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 6 Dec 2019 23:18:37 -0600 Subject: Fix mistakenly added modinfo tab ID --- src/mainwindow.cpp | 1 - src/modinfodialogfwd.h | 1 - 2 files changed, 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 68627c90..ebeb7459 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3897,7 +3897,6 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; - case ModList::COL_FLAGS: tab = ModInfoTabIDs::Flags; break; } displayModInformation(sourceIdx.row(), tab); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index e4a61208..2147fc04 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -14,7 +14,6 @@ enum class ModInfoTabIDs Images, Esps, Conflicts, - Flags, Categories, Nexus, Notes, -- cgit v1.3.1 From 267985a62dd90b1b66c14ba32436f80907f97edb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Dec 2019 02:29:39 -0500 Subject: removed unused on_clickBlankButton_clicked() --- src/mainwindow.cpp | 5 - src/mainwindow.h | 3 - src/organizer_en.ts | 364 ++++++++++++++++++++++++++-------------------------- 3 files changed, 182 insertions(+), 190 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebeb7459..43d5b820 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6719,11 +6719,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) QMainWindow::keyReleaseEvent(event); } -void MainWindow::on_clickBlankButton_clicked() -{ - deselectFilters(); -} - void MainWindow::on_clearFiltersButton_clicked() { ui->modFilterEdit->clear(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 98573423..2020b1a3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -199,9 +199,6 @@ protected: private slots: void on_actionChange_Game_triggered(); -private slots: - void on_clickBlankButton_clicked(); - private: void cleanup(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 72972e4f..61348380 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1851,7 +1851,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2027,8 +2027,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2313,7 +2313,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2434,8 +2434,8 @@ Error: %1 - - + + Endorse @@ -2616,16 +2616,16 @@ Error: %1 - - + + failed to rename "%1" to "%2" - - - + + + Confirm @@ -2714,7 +2714,7 @@ Error: %1 - + Create Mod... @@ -2759,7 +2759,7 @@ Please enter a name: - + Are you sure? @@ -2770,435 +2770,435 @@ Please enter a name: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3206,12 +3206,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3219,330 +3219,330 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6099,7 +6099,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From c9049bb07274aaefa79c9751ec40910bf8daf0ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:33:03 -0500 Subject: conflicts tab: run exes unhooked by default --- src/mainwindow.cpp | 3 +- src/modinfodialogconflicts.cpp | 72 +++++++++++++++++++++++------------------- src/modinfodialogconflicts.h | 5 ++- src/modinfodialogfiletree.cpp | 3 +- src/processrunner.cpp | 70 ++++++++++++++++++++++++++-------------- src/processrunner.h | 8 +++-- 6 files changed, 97 insertions(+), 64 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 43d5b820..532d8502 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5425,7 +5425,8 @@ void MainWindow::runDataFileHooked() const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() - .setFromFile(this, targetInfo, true) + .setFromFile(this, targetInfo) + .setHooked(true) .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 9c7ccc8c..81e8c7a3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -542,45 +542,32 @@ void ConflictsTab::activateItems(QTreeView* tree) if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { previewItem(item); } else { - openItem(item); + openItem(item, false); } return true; }); } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::openItems(QTreeView* tree, bool hooked) { // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - openItem(item); + openItem(item, hooked); return true; }); } -void ConflictsTab::openItem(const ConflictItem* item) +void ConflictsTab::openItem(const ConflictItem* item, bool hooked) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) + .setHooked(hooked) .setWaitForCompletion() .run(); } -void ConflictsTab::runItemsHooked(QTreeView* tree) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName(), true) - .setWaitForCompletion() - .run(); - - return true; - }); -} - void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -615,30 +602,44 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ - openItems(tree); + openItems(tree, false); }); + } - menu.addAction(actions.open); + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree); + }); + } + + if ((actions.open && actions.open->isEnabled()) && (actions.preview && actions.preview->isEnabled())) { + if (Settings::instance().interface().doubleClicksOpenPreviews()) { + menu.addAction(actions.preview); + menu.addAction(actions.open); + } else { + menu.addAction(actions.open); + menu.addAction(actions.preview); + } + } else { + if (actions.open) { + menu.addAction(actions.open); + } + + if (actions.preview) { + menu.addAction(actions.preview); + } } // run hooked if (actions.runHooked) { connect(actions.runHooked, &QAction::triggered, [&]{ - runItemsHooked(tree); + openItems(tree, true); }); menu.addAction(actions.runHooked); } - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewItems(tree); - }); - - menu.addAction(actions.preview); - } - // goto if (actions.gotoMenu) { menu.addMenu(actions.gotoMenu); @@ -681,9 +682,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } - setDefaultActivationActionForFile(actions.open, actions.preview); - if (!menu.isEmpty()) { + if (actions.open || actions.preview || actions.runHooked) { + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); + } + menu.exec(tree->viewport()->mapToGlobal(pos)); } } @@ -769,6 +776,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) if (enableRun) { actions.open = new QAction(tr("&Execute"), parentWidget()); + actions.runHooked = new QAction(tr("Execute with &VFS"), parentWidget()); } else if (enableOpen) { actions.open = new QAction(tr("&Open"), parentWidget()); actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3ac8de23..1297e536 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -108,12 +108,11 @@ public: bool canHandleUnmanaged() const override; void activateItems(QTreeView* tree); - void openItems(QTreeView* tree); - void runItemsHooked(QTreeView* tree); + void openItems(QTreeView* tree, bool hooked); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); - void openItem(const ConflictItem* item); + void openItem(const ConflictItem* item, bool hooked); void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index d1ae3823..0b9e8da4 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -183,7 +183,8 @@ void FileTreeTab::onRunHooked() } core().processRunner() - .setFromFile(parentWidget(), m_fs->filePath(selection), true) + .setFromFile(parentWidget(), m_fs->filePath(selection)) + .setHooked(true) .setWaitForCompletion() .run(); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 46065d69..19aae632 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -421,8 +421,8 @@ ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { - // all processes started in ProcessRunner are hooked - m_sp.hooked = true; + // all processes started in ProcessRunner are hooked by default + setHooked(true); } ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) @@ -475,8 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } +ProcessRunner& ProcessRunner::setHooked(bool b) +{ + m_sp.hooked = b; + return *this; +} + ProcessRunner& ProcessRunner::setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook) + QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->qtWidget(); @@ -500,24 +506,8 @@ ProcessRunner& ProcessRunner::setFromFile( case spawn::FileExecutionTypes::Other: // fall-through default: { - if (forceHook) { - auto assoc = env::getAssociation(targetInfo); - if (!assoc.executable.filePath().isEmpty()) { - setBinary(assoc.executable); - setArguments(assoc.formattedCommandLine); - setCurrentDirectory(assoc.executable.absoluteDir()); - - return *this; - } - - // if it fails, just use the regular shell open - } - - m_shellOpen = targetInfo.absoluteFilePath(); - - // picked up by postRun() - m_sp.hooked = false; - + m_shellOpen = targetInfo; + setHooked(false); break; } } @@ -649,11 +639,41 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( return *this; } +bool ProcessRunner::shouldRunShell() const +{ + return !m_shellOpen.filePath().isEmpty(); +} + ProcessRunner::Results ProcessRunner::run() { + // check if setHooked() was called after setFromFile(); this needs to + // modify the settings to run the associated executable instead of using + // shell::Open() + + if (shouldRunShell() && m_sp.hooked) { + // this is a non-executable file, but it should be hooked; the associated + // executable needs to be retrieved and run instead + auto assoc = env::getAssociation(m_shellOpen); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + m_shellOpen = {}; + } else { + // if it fails, just use the regular shell open + log::error("failed to get the associated executable, running unhooked"); + m_sp.hooked = false; + } + } else if (!shouldRunShell() && !m_sp.hooked) { + // this is an executable that should not be hooked; just run it through + // the shell + m_shellOpen = m_sp.binary; + } + + std::optional r; - if (!m_shellOpen.isEmpty()) { + if (shouldRunShell()) { r = runShell(); } else { r = runBinary(); @@ -669,9 +689,11 @@ ProcessRunner::Results ProcessRunner::run() std::optional ProcessRunner::runShell() { - log::debug("executing from shell: '{}'", m_shellOpen); + const auto file = m_shellOpen.absoluteFilePath(); + + log::debug("executing from shell: '{}'", file); - auto r = shell::Open(m_shellOpen); + auto r = shell::Open(file); if (!r.success()) { return Error; } diff --git a/src/processrunner.h b/src/processrunner.h index d576216a..1bfdc465 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -67,6 +67,7 @@ public: ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); + ProcessRunner& setHooked(bool b); // - if the target is an executable file, runs it hooked // - if the target is a file: @@ -74,8 +75,7 @@ public: // - if forceHook is true, gets the executable associated with the file // and runs that hooked by passing the file as an argument // - ProcessRunner& setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); + ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); @@ -150,11 +150,13 @@ private: QString m_profileName; UILocker::Reasons m_lockReason; WaitFlags m_waitFlags; - QString m_shellOpen; + QFileInfo m_shellOpen; env::HandlePtr m_handle; DWORD m_exitCode; + bool shouldRunShell() const; + // runs the command in m_shellOpen; returns empty if it can be waited for // std::optional runShell(); -- cgit v1.3.1 From df54f972fff57a7c7afe8dfb44efda43d76e8c78 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 8 Dec 2019 16:15:40 -0600 Subject: Rework logic to parse by visual index --- src/mainwindow.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 43d5b820..274e07c1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2310,9 +2310,9 @@ void MainWindow::processUpdates(Settings& settings) { if (!settings.firstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; - for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(i); - ui->modList->header()->setSectionHidden(i, lastHidden); + for (int i = ui->modList->header()->visualIndex(ModList::COL_GAME); i < ui->modList->header()->count(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); + ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); lastHidden = hidden; } } @@ -2330,9 +2330,9 @@ void MainWindow::processUpdates(Settings& settings) { if (lastVersion < QVersionNumber(2, 2, 2)) { bool lastHidden = true; - for (int i = ModList::COL_CONFLICTFLAGS; i < ui->modList->model()->columnCount(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(i); - ui->modList->header()->setSectionHidden(i, lastHidden); + for (int i = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); i < ui->modList->header()->count(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); + ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); lastHidden = hidden; } } -- cgit v1.3.1 From 27c05cebd58ea33e0465b886d47a2dd9d8fc2ddc Mon Sep 17 00:00:00 2001 From: Al Date: Mon, 9 Dec 2019 00:56:10 +0100 Subject: Change compact size for the conflicts column to be shorter since there are less flags here. --- src/mainwindow.cpp | 2 +- src/modconflicticondelegate.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 274e07c1..8eff26c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -554,7 +554,7 @@ void MainWindow::setupModList() ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate( - ui->modList, ModList::COL_CONFLICTFLAGS, 120); + ui->modList, ModList::COL_CONFLICTFLAGS, 80); connect( ui->modList->header(), SIGNAL(sectionResized(int, int, int)), diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h index d36477c6..8645da12 100644 --- a/src/modconflicticondelegate.h +++ b/src/modconflicticondelegate.h @@ -8,7 +8,7 @@ class ModConflictIconDelegate : public IconDelegate Q_OBJECT; public: - explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); + explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 80); virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; static QList getIconsForFlags( -- cgit v1.3.1 From 44084bfa415f906b8ba8b5d19e1be60a7612d692 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 8 Dec 2019 20:47:45 -0600 Subject: Rework column insertion to move to the intended position post-facto --- src/main.cpp | 2 -- src/mainwindow.cpp | 41 ++++++++++++++++++++++++++--------------- src/mainwindow.h | 2 +- src/modlist.h | 5 ++--- 4 files changed, 29 insertions(+), 21 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 1f6c57d1..eeabd497 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -735,8 +735,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(settings); - // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8eff26c5..585be402 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -178,6 +178,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS #include "modeltest.h" @@ -511,6 +512,7 @@ MainWindow::MainWindow(Settings &settings resetActionIcons(); updatePluginCount(); updateModCount(); + processUpdates(); } void MainWindow::setupModList() @@ -2299,7 +2301,8 @@ void MainWindow::readSettings() } } -void MainWindow::processUpdates(Settings& settings) { +void MainWindow::processUpdates() { + auto& settings = m_OrganizerCore.settings(); const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); const auto lastVersion = settings.version().value_or(earliest); @@ -2308,15 +2311,6 @@ void MainWindow::processUpdates(Settings& settings) { settings.processUpdates(currentVersion, lastVersion); if (!settings.firstStart()) { - if (lastVersion < QVersionNumber(2, 1, 3)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_GAME); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; - } - } - if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } @@ -2329,11 +2323,28 @@ void MainWindow::processUpdates(Settings& settings) { } if (lastVersion < QVersionNumber(2, 2, 2)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); + } + } + } else { + { // Move conflict flags + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2020b1a3..fcbbe039 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -116,7 +116,7 @@ public: QWidget *parent = 0); ~MainWindow(); - void processUpdates(Settings& settings); + void processUpdates(); QWidget* qtWidget() override; diff --git a/src/modlist.h b/src/modlist.h index 8841ec29..ce439f7b 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,7 +57,6 @@ public: enum EColumn { COL_NAME, - COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -67,8 +66,8 @@ public: COL_INSTALLTIME, COL_PRIORITY, COL_NOTES, - - COL_LASTCOLUMN = COL_NOTES + COL_CONFLICTFLAGS, + COL_LASTCOLUMN = COL_CONFLICTFLAGS, }; typedef boost::signals2::signal SignalModStateChanged; -- cgit v1.3.1 From 5521b12fb30c525b5d4b919e355266de9fb524ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 09:52:29 -0500 Subject: data tab: run exes unhooked by default --- src/mainwindow.cpp | 129 +++++++++---- src/mainwindow.h | 1 + src/organizer_en.ts | 518 +++++++++++++++++++++++++++------------------------- 3 files changed, 361 insertions(+), 287 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 532d8502..d3474838 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1827,27 +1827,6 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) } } -void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) -{ - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const QString path = item->data(0, Qt::UserRole).toString(); - const QFileInfo targetInfo(path); - - const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); - - if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { - previewDataFile(item); - } else { - openDataFile(item); - } -} - bool MainWindow::refreshProfiles(bool selectProfile) { QComboBox* profileBox = findChild("profileBox"); @@ -5373,19 +5352,25 @@ void MainWindow::disableSelectedMods_clicked() } -void MainWindow::previewDataFile() +void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) { - if (m_ContextItem == nullptr) { + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { return; } - previewDataFile(m_ContextItem); -} + const QString path = item->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); -void MainWindow::previewDataFile(QTreeWidgetItem* item) -{ - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore.previewFileWithAlternatives(this, fileName); + const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + previewDataFile(item); + } else { + openDataFile(item); + } } void MainWindow::openDataFile() @@ -5411,6 +5396,7 @@ void MainWindow::openDataFile(QTreeWidgetItem* item) m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) + .setHooked(false) .setWaitForCompletion(ProcessRunner::Refresh) .run(); } @@ -5421,7 +5407,19 @@ void MainWindow::runDataFileHooked() return; } - const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + runDataFileHooked(m_ContextItem); +} + +void MainWindow::runDataFileHooked(QTreeWidgetItem* item) +{ + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() @@ -5431,6 +5429,21 @@ void MainWindow::runDataFileHooked() .run(); } +void MainWindow::previewDataFile() +{ + if (m_ContextItem == nullptr) { + return; + } + + previewDataFile(m_ContextItem); +} + +void MainWindow::previewDataFile(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); + m_OrganizerCore.previewFileWithAlternatives(this, fileName); +} + void MainWindow::openDataOriginExplorer_clicked() { if (m_ContextItem == nullptr) { @@ -5511,21 +5524,57 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); QAction* open = nullptr; + QAction* runHooked = nullptr; QAction* preview = nullptr; if (canRunFile(isArchive, fileName)) { - open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + open = new QAction(tr("&Execute"), ui->dataTree); + runHooked = new QAction(tr("Execute with &VFS"), ui->dataTree); } else if (canOpenFile(isArchive, fileName)) { - open = menu.addAction(tr("&Open"), this, SLOT(openDataFile())); - menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); + open = new QAction(tr("&Open"), ui->dataTree); + runHooked = new QAction(tr("Open with &VFS"), ui->dataTree); } - menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); - if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - preview = menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + preview = new QAction(tr("Preview"), ui->dataTree); + } + + if (open) { + connect(open, &QAction::triggered, [&]{ openDataFile(); }); + } + + if (runHooked) { + connect(runHooked, &QAction::triggered, [&]{ runDataFileHooked(); }); + } + + if (preview) { + connect(preview, &QAction::triggered, [&]{ previewDataFile(); }); + } + + if (open && preview) { + if (m_OrganizerCore.settings().interface().doubleClicksOpenPreviews()) { + menu.addAction(preview); + menu.addAction(open); + } else { + menu.addAction(open); + menu.addAction(preview); + } + } else { + if (open) { + menu.addAction(open); + } + + if (preview) { + menu.addAction(preview); + } } + if (runHooked) { + menu.addAction(runHooked); + } + + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); + if (!isArchive && !isDirectory) { menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } @@ -5543,7 +5592,13 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) } } - setDefaultActivationActionForFile(open, preview); + if (open || preview || runHooked) { + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); + } } menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); diff --git a/src/mainwindow.h b/src/mainwindow.h index 2020b1a3..c462a186 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -439,6 +439,7 @@ private slots: void openDataFile(); void openDataFile(QTreeWidgetItem* item); void runDataFileHooked(); + void runDataFileHooked(QTreeWidgetItem* item); void addAsExecutable(); void previewDataFile(); void previewDataFile(QTreeWidgetItem* item); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index faff25ac..9f83278b 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -294,42 +294,47 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Execute - + + Execute with &VFS + + + + &Open - + Open with &VFS - + &Preview - + &Go to... - + Open in &Explorer - + &Hide - + &Unhide @@ -1278,6 +1283,7 @@ Right now the only case I know of where this needs to be overwritten is for the + Open with &VFS @@ -1323,32 +1329,37 @@ Right now the only case I know of where this needs to be overwritten is for the - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 - + &Execute - + + Execute with &VFS + + + + &Open @@ -1851,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2027,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2313,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2434,8 +2445,8 @@ Error: %1 - - + + Endorse @@ -2540,665 +2551,665 @@ Error: %1 - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3206,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3219,330 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + + Execute with &VFS + + + + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6098,13 +6114,13 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6263,9 +6279,9 @@ If the folder was still in use, restart MO and try again. - - - + + + No profile set @@ -6940,12 +6956,14 @@ Select Show Details option to see the full change-log. - https://www.transifex.com/tannin/mod-organizer/ + https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ + https://www.transifex.com/tannin/mod-organizer/ - <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> + <a href="https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/">Help translate Mod Organizer</a> + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> -- cgit v1.3.1 From 37891f29ca6f5935d4148bf5cf1f1d5a4eaa746c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 10:10:41 -0500 Subject: removed dead setDefaultActivationActionForFile() --- src/mainwindow.cpp | 50 ---- src/modinfodialogconflicts.cpp | 4 - src/modinfodialogfiletree.cpp | 3 - src/organizer_en.ts | 598 ++++++++++++++++++++--------------------- 4 files changed, 299 insertions(+), 356 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d3474838..1ea2a612 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -199,56 +199,6 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); -void setDefaultActivationActionForFile(QAction* open, QAction* preview) -{ - if (!open && !preview) { - return; - } - - QFont bold, notBold; - - if (open) { - bold = open->font(); - notBold = open->font(); - } else { - bold = preview->font(); - notBold = preview->font(); - } - - notBold.setBold(false); - bold.setBold(true); - - - const auto& s = Settings::instance(); - const auto openEnabled = (open && open->isEnabled()); - const auto previewEnabled = (preview && preview->isEnabled()); - - bool doPreview = false; - - // preview is bold if the file is previewable and [the preview on double-click - // option is enabled or the file can't be opened]; open is bold if the file - // can be opened and cannot be previewed - if (previewEnabled && s.interface().doubleClicksOpenPreviews()) { - doPreview = true; - } else if (openEnabled) { - doPreview = false; - } else if (previewEnabled) { - doPreview = true; - } else { - // shouldn't happen, checked above - return; - } - - if (open) { - open->setFont(doPreview ? notBold : bold); - } - - if (preview) { - preview->setFont(doPreview ? bold : notBold); - } -} - - MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 81e8c7a3..0fb8c5a6 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -12,10 +12,6 @@ using namespace MOBase; // checking whether menu items apply to them, just show all of them const std::size_t max_small_selection = 50; -// in mainwindow.cpp -void setDefaultActivationActionForFile(QAction* open, QAction* preview); - - class ConflictItem { public: diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index c49c767f..2c33a7f1 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -14,9 +14,6 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; -// in mainwindow.cpp -void setDefaultActivationActionForFile(QAction* open, QAction* preview); - FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { diff --git a/src/organizer_en.ts b/src/organizer_en.ts index a7796942..31612360 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -178,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -294,47 +294,47 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Preview - + &Go to... - + Open in &Explorer - + &Hide - + &Unhide @@ -1272,94 +1272,94 @@ Right now the only case I know of where this needs to be overwritten is for the FileTreeTab - + &New Folder - + &Open/Execute - - + + Open with &VFS - + &Preview - + Open in &Explorer - + &Rename - + &Delete - + &Hide - + &Unhide - - + + New Folder - + Failed to create "%1" - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 - + &Execute - + Execute with &VFS - + &Open @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2402,814 +2402,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4554,7 +4554,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4770,12 +4770,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4862,12 +4862,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -6109,18 +6109,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From e1d6f6380dc4f7719f369b48e327085b0e0f511b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:40:54 -0500 Subject: use paintEvent() to allow interactive resize because showEvent() is too early bumped to rc2 --- src/mainwindow.cpp | 16 +- src/mainwindow.h | 2 + src/organizer_en.ts | 672 ++++++++++++++++++++++++++-------------------------- src/version.rc | 2 +- 4 files changed, 350 insertions(+), 342 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f58473e9..eaecfca1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -207,6 +207,7 @@ MainWindow::MainWindow(Settings &settings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) + , m_FirstPaint(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -687,15 +688,12 @@ void MainWindow::allowListResize() for (int i = 0; i < ui->modList->header()->count(); ++i) { ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); } - //ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); ui->modList->header()->setStretchLastSection(true); - // allow resize on plugin list for (int i = 0; i < ui->espList->header()->count(); ++i) { ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); } - //ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); ui->espList->header()->setStretchLastSection(true); } @@ -1286,14 +1284,22 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); - allowListResize(); - m_OrganizerCore.settings().nexus().registerAsNXMHandler(false); m_WasVisible = true; updateProblemsButton(); } } +void MainWindow::paintEvent(QPaintEvent* event) +{ + if (m_FirstPaint) { + allowListResize(); + m_FirstPaint = false; + } + + QMainWindow::paintEvent(event); +} + void MainWindow::onBeforeClose() { storeSettings(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 4adab809..8f000983 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -189,6 +189,7 @@ signals: protected: virtual void showEvent(QShowEvent *event); + virtual void paintEvent(QPaintEvent* event); virtual void closeEvent(QCloseEvent *event); virtual bool eventFilter(QObject *obj, QEvent *event); virtual void resizeEvent(QResizeEvent *event); @@ -312,6 +313,7 @@ private: Ui::MainWindow *ui; bool m_WasVisible; + bool m_FirstPaint; // last separator on the toolbar, used to add spacer for right-alignment and // as an insert point for executables diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 31612360..e9578369 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1886,7 +1886,7 @@ p, li { white-space: pre-wrap; } - + Filter @@ -2037,9 +2037,9 @@ p, li { white-space: pre-wrap; } - - - + + + Refresh @@ -2049,44 +2049,44 @@ p, li { white-space: pre-wrap; } - + File - + Mod - + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -2097,337 +2097,337 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 @@ -2445,8 +2445,8 @@ Error: %1 - - + + Endorse @@ -2516,700 +2516,700 @@ Error: %1 - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6088,23 +6088,23 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + <Unmanaged> @@ -6114,13 +6114,13 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 diff --git a/src/version.rc b/src/version.rc index 3d431488..1544451e 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc1\0" +#define VER_FILEVERSION_STR "2.2.2rc2\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From ea6c793ce6047e0c958c13b3743976bcf953bf1a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:32:17 -0500 Subject: move the conflicts column when resetting geometries --- src/mainwindow.cpp | 45 +++-- src/mainwindow.h | 1 + src/organizer_en.ts | 516 ++++++++++++++++++++++++++-------------------------- 3 files changed, 281 insertions(+), 281 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eaecfca1..44011e5f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -528,6 +528,8 @@ void MainWindow::setupModList() ui->modList->header()->resizeSection(column, sectionSize); } } else { + fixConflictsColumn(); + // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); @@ -549,6 +551,25 @@ void MainWindow::setupModList() ui->modList->installEventFilter(m_OrganizerCore.modList()); } +void MainWindow::fixConflictsColumn() +{ + // the conflicts column should sit to the left of the flags column, but its + // enum is at the end to preserve compatibility + // + // this is called when updating from 2.2.1, or when there is no state saved + // for the mod list, so it's free to do whatever it wants with the column + + const auto flags = ui->modList->header()->visualIndex(ModList::COL_FLAGS); + const auto conflicts = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); + + // this can be called twice when migrating from 2.2.1: once in + // processUpdates() and again in setupModList() because the geometry names in + // the ini have changed; to a simple check to see if the column has been moved + if (conflicts > flags) { + ui->modList->header()->moveSection(conflicts, flags); + } +} + void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -2258,29 +2279,7 @@ void MainWindow::processUpdates() { } if (lastVersion < QVersionNumber(2, 2, 2)) { - int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); - int pos2 = 0; - if (pos1) { - ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); - pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); - ui->modList->header()->moveSection( - ui->modList->header()->visualIndexAt(pos2), - ui->modList->header()->visualIndexAt(pos1) - ); - } - } - } else { - { // Move conflict flags - int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); - int pos2 = 0; - if (pos1) { - ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); - pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); - ui->modList->header()->moveSection( - ui->modList->header()->visualIndexAt(pos2), - ui->modList->header()->visualIndexAt(pos1) - ); - } + fixConflictsColumn(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8f000983..0d7edefd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -665,6 +665,7 @@ private slots: // ui slots void storeSettings(); void readSettings(); void setupModList(); + void fixConflictsColumn(); }; #endif // MAINWINDOW_H diff --git a/src/organizer_en.ts b/src/organizer_en.ts index e9578369..73e9f547 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2422,794 +2422,794 @@ p, li { white-space: pre-wrap; } - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6109,18 +6109,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From 8d9a62a87dafd87381829b00b85753db510eef2b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 13:08:58 -0500 Subject: moved the conflicts column back to its proper position in the enum removed unnecessary compat code --- src/mainwindow.cpp | 26 --- src/mainwindow.h | 1 - src/modlist.h | 4 +- src/organizer_en.ts | 526 ++++++++++++++++++++++++++-------------------------- 4 files changed, 265 insertions(+), 292 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 44011e5f..b5900c6c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -178,7 +178,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #ifdef TEST_MODELS #include "modeltest.h" @@ -528,8 +527,6 @@ void MainWindow::setupModList() ui->modList->header()->resizeSection(column, sectionSize); } } else { - fixConflictsColumn(); - // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); @@ -551,25 +548,6 @@ void MainWindow::setupModList() ui->modList->installEventFilter(m_OrganizerCore.modList()); } -void MainWindow::fixConflictsColumn() -{ - // the conflicts column should sit to the left of the flags column, but its - // enum is at the end to preserve compatibility - // - // this is called when updating from 2.2.1, or when there is no state saved - // for the mod list, so it's free to do whatever it wants with the column - - const auto flags = ui->modList->header()->visualIndex(ModList::COL_FLAGS); - const auto conflicts = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); - - // this can be called twice when migrating from 2.2.1: once in - // processUpdates() and again in setupModList() because the geometry names in - // the ini have changed; to a simple check to see if the column has been moved - if (conflicts > flags) { - ui->modList->header()->moveSection(conflicts, flags); - } -} - void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -2277,10 +2255,6 @@ void MainWindow::processUpdates() { ui->downloadView->header()->hideSection(i); } } - - if (lastVersion < QVersionNumber(2, 2, 2)) { - fixConflictsColumn(); - } } if (currentVersion < lastVersion) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 0d7edefd..8f000983 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -665,7 +665,6 @@ private slots: // ui slots void storeSettings(); void readSettings(); void setupModList(); - void fixConflictsColumn(); }; #endif // MAINWINDOW_H diff --git a/src/modlist.h b/src/modlist.h index ce439f7b..3626c50c 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,6 +57,7 @@ public: enum EColumn { COL_NAME, + COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -66,8 +67,7 @@ public: COL_INSTALLTIME, COL_PRIORITY, COL_NOTES, - COL_CONFLICTFLAGS, - COL_LASTCOLUMN = COL_CONFLICTFLAGS, + COL_LASTCOLUMN = COL_NOTES, }; typedef boost::signals2::signal SignalModStateChanged; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 73e9f547..d462f1b6 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2402,814 +2402,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6104,23 +6104,23 @@ If the folder was still in use, restart MO and try again. - + <Unmanaged> - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From 930087ea92291bba1bc55f606aa5e1efcb622a75 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 13:25:04 -0500 Subject: set default window size to 1300x800 --- src/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5900c6c..ea202064 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2201,7 +2201,10 @@ void MainWindow::readSettings() { const auto& s = m_OrganizerCore.settings(); - s.geometry().restoreGeometry(this); + if (!s.geometry().restoreGeometry(this)) { + resize(1300, 800); + } + s.geometry().restoreState(this); s.geometry().restoreDocks(this); s.geometry().restoreToolbars(this); -- cgit v1.3.1 From 7af0e164f778ee8b2135e097856a597f5d68b873 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 14 Dec 2019 13:49:26 -0500 Subject: don't activate the root data item --- src/mainwindow.cpp | 4 ++ src/organizer_en.ts | 134 ++++++++++++++++++++++++++-------------------------- 2 files changed, 71 insertions(+), 67 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ea202064..a29ea8ab 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5305,6 +5305,10 @@ void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) } const QString path = item->data(0, Qt::UserRole).toString(); + if (path.isEmpty()) { + return; + } + const QFileInfo targetInfo(path); const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 633073a0..00bb771c 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1576,53 +1576,53 @@ Right now the only case I know of where this needs to be overwritten is for the - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error @@ -2051,7 +2051,7 @@ p, li { white-space: pre-wrap; } - + Refresh @@ -2336,7 +2336,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2782,7 +2782,7 @@ Please enter a name: - + Are you sure? @@ -3123,13 +3123,13 @@ You can also use online editors and converters instead. - + Enable selected - + Disable selected @@ -3205,13 +3205,13 @@ You can also use online editors and converters instead. - + Exception: - + Unknown exception @@ -3331,7 +3331,7 @@ You can also use online editors and converters instead. - + Set Priority @@ -3341,236 +3341,236 @@ You can also use online editors and converters instead. - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods -- cgit v1.3.1 From f3c5cebb6e9262625105d4339a54a810d7816811 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:56:50 -0500 Subject: fixed exiting before QThread joins when pressing the X twice --- src/mainwindow.cpp | 21 ++++++++++++++------- src/processrunner.cpp | 31 +++++++++++++++++++++---------- src/shared/util.cpp | 11 ++++++++++- src/shared/util.h | 1 + 4 files changed, 46 insertions(+), 18 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a29ea8ab..d1578d85 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1317,18 +1317,25 @@ void MainWindow::closeEvent(QCloseEvent* event) // // for 2), the settings have been saved and the window can just close - if (ModOrganizerExiting()) { + if (ModOrganizerCanCloseNow()) { // the user has confirmed if necessary and all settings have been saved, // just close it QMainWindow::closeEvent(event); - } else { - // never close the window because settings might need to be changed - event->ignore(); + return; + } - // start the process of exiting, which may require confirmation by calling - // canExit(), among other things - ExitModOrganizer(); + if (ModOrganizerExiting()) { + // ignore repeated attempts + event->ignore(); + return; } + + // never close the window because settings might need to be changed + event->ignore(); + + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); } bool MainWindow::canExit() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 19aae632..aead42d1 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -225,7 +225,7 @@ const std::chrono::milliseconds Infinite(-1); // std::optional timedWait( HANDLE handle, DWORD pid, UILocker::Session& ls, - std::chrono::milliseconds wait) + std::chrono::milliseconds wait, std::atomic& interrupt) { using namespace std::chrono; @@ -234,7 +234,7 @@ std::optional timedWait( start = high_resolution_clock::now(); } - for (;;) { + while (!interrupt) { // wait for a very short while, allows for processing events below const auto r = singleWait(handle, pid); @@ -286,10 +286,13 @@ std::optional timedWait( } } } + + log::debug("waiting for {} interrupted", pid); + return ProcessRunner::ForceUnlocked; } ProcessRunner::Results waitForProcessesThreadImpl( - HANDLE job, UILocker::Session& ls) + HANDLE job, UILocker::Session& ls, std::atomic& interrupt) { using namespace std::chrono; @@ -301,7 +304,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( const milliseconds defaultWait(50); auto wait = defaultWait; - for (;;) { + while (!interrupt) { auto ip = getInterestingProcess(job); if (!ip.handle) { // nothing to wait on @@ -325,7 +328,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( wait = Infinite; } - const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait); + const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait, interrupt); if (r) { if (*r == ProcessRunner::Results::Completed) { // process completed, check another one, reset the wait time to find @@ -344,9 +347,10 @@ ProcessRunner::Results waitForProcessesThreadImpl( } void waitForProcessesThread( - ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls) + ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls, + std::atomic& interrupt) { - result = waitForProcessesThreadImpl(job, ls); + result = waitForProcessesThreadImpl(job, ls, interrupt); ls.unlock(); } @@ -379,9 +383,11 @@ ProcessRunner::Results waitForProcesses( } auto results = ProcessRunner::Running; + std::atomic interrupt(false); auto* t = QThread::create( - waitForProcessesThread, std::ref(results), job.get(), std::ref(ls)); + waitForProcessesThread, + std::ref(results), job.get(), std::ref(ls), std::ref(interrupt)); QEventLoop events; QObject::connect(t, &QThread::finished, [&]{ @@ -391,6 +397,11 @@ ProcessRunner::Results waitForProcesses( t->start(); events.exec(); + if (t->isRunning()) { + interrupt = true; + t->wait(); + } + delete t; return results; @@ -861,7 +872,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; withLock([&](auto& ls) { - for (;;) { + for (;;) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { break; @@ -878,7 +889,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } r = Completed; - }); + }); return r; } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 32eb825c..baceddeb 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -294,6 +294,7 @@ QString getUsvfsVersionString() static bool g_exiting = false; +static bool g_canClose = false; MainWindow* findMainWindow() { @@ -312,6 +313,9 @@ bool ExitModOrganizer(ExitFlags e) return true; } + g_exiting = true; + MOBase::Guard g([&]{ g_exiting = false; }); + if (!e.testFlag(Exit::Force)) { if (auto* mw=findMainWindow()) { if (!mw->canExit()) { @@ -320,7 +324,7 @@ bool ExitModOrganizer(ExitFlags e) } } - g_exiting = true; + g_canClose = true; const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); qApp->exit(code); @@ -328,6 +332,11 @@ bool ExitModOrganizer(ExitFlags e) return true; } +bool ModOrganizerCanCloseNow() +{ + return g_canClose; +} + bool ModOrganizerExiting() { return g_exiting; diff --git a/src/shared/util.h b/src/shared/util.h index e87244b6..e8a58549 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -65,6 +65,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); bool ExitModOrganizer(ExitFlags e=Exit::Normal); bool ModOrganizerExiting(); +bool ModOrganizerCanCloseNow(); void ResetExitFlag(); #endif // UTIL_H -- cgit v1.3.1 From 05231eab45f86e3d0d342c429e35c8f7c813ea42 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 22:35:02 -0500 Subject: temporary fix to keep MO locked for all processes when closing save main window settings in closeEvent() --- src/main.cpp | 1 - src/mainwindow.cpp | 22 ++++++++++++++++++++++ src/processrunner.cpp | 26 ++++++++++++++++++-------- src/spawn.cpp | 6 ++++-- src/uilocker.cpp | 5 +++++ src/uilocker.h | 1 + 6 files changed, 50 insertions(+), 11 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index eeabd497..29d2d02c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -745,7 +745,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); - mainWindow.onBeforeClose(); mainWindow.close(); NexusInterface::instance(&pluginContainer) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d1578d85..284c33e3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1306,6 +1306,21 @@ void MainWindow::onBeforeClose() void MainWindow::closeEvent(QCloseEvent* event) { + if (isVisible()) { + // this is messy + // + // the main problem this is solving is when closing MO, then getting the + // lock overlay because processes are still running, then pressing the X + // again + // + // in this case, closeEvent() is _not_ called for the second event and the + // window is immediately hidden + // + // this always saves the settings here; in the event where a lock overlay + // is then shown, it might save settings multiple times, but it's harmless + onBeforeClose(); + } + // this happens for two reasons: // 1) the user requested to close the window, such as clicking the X // 2) close() is called in runApplication() after application.exec() @@ -1324,6 +1339,13 @@ void MainWindow::closeEvent(QCloseEvent* event) return; } + if (UILocker::instance().locked()) { + // don't bother asking the user to confirm if the ui is already locked + event->ignore(); + ExitModOrganizer(Exit::Force); + return; + } + if (ModOrganizerExiting()) { // ignore repeated attempts event->ignore(); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index aead42d1..945d61c3 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -344,6 +344,9 @@ ProcessRunner::Results waitForProcessesThreadImpl( // processes wait = std::min(wait * 2, milliseconds(2000)); } + + log::debug("waiting for processes interrupted"); + return ProcessRunner::ForceUnlocked; } void waitForProcessesThread( @@ -374,9 +377,12 @@ ProcessRunner::Results waitForProcesses( if (!::AssignProcessToJobObject(job.get(), h)) { const auto e = GetLastError(); - log::error( - "can't assign process to job to wait for processes, {}", - formatSystemMessage(e)); + // this happens when closing MO while multiple processes are running, + // so the logging is disabled until it gets fixed + + //log::error( + // "can't assign process to job to wait for processes, {}", + // formatSystemMessage(e)); // keep going } @@ -871,11 +877,12 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; - withLock([&](auto& ls) { for (;;) { + withLock([&](auto& ls) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { - break; + r = Completed; + return; } r = waitForProcesses(processes, ls); @@ -886,11 +893,14 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } // this process is completed, check for others - } - - r = Completed; + r = Running; }); + if (r != Running) { + break; + } + } + return r; } diff --git a/src/spawn.cpp b/src/spawn.cpp index f95846c8..33bdbc05 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -498,14 +498,16 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) const auto wcommandLine = commandLine.toStdWString(); const auto wcwd = cwd.toStdWString(); + const DWORD flags = CREATE_BREAKAWAY_FROM_JOB; + if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); diff --git a/src/uilocker.cpp b/src/uilocker.cpp index af9b9b96..07fe7f1b 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -461,6 +461,11 @@ std::shared_ptr UILocker::lock(Reasons reason) return ls; } +bool UILocker::locked() const +{ + return !m_sessions.empty(); +} + void UILocker::unlock(Session* s) { auto itor = m_sessions.begin(); diff --git a/src/uilocker.h b/src/uilocker.h index d8c22999..cc467184 100644 --- a/src/uilocker.h +++ b/src/uilocker.h @@ -70,6 +70,7 @@ public: void setUserInterface(QWidget* parent); std::shared_ptr lock(Reasons reason); + bool locked() const; Results result() const; -- cgit v1.3.1 From 19a20159a5c8ad72f6978331b268ee75a1cad94b Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 1 Jan 2020 21:38:18 -0700 Subject: Fix the API counter not being translated --- src/mainwindow.cpp | 3 +- src/organizer_en.ts | 654 ++++++++++++++++++++++++++-------------------------- src/statusbar.cpp | 4 +- 3 files changed, 328 insertions(+), 333 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 284c33e3..5da42b93 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -232,6 +232,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); + languageChange(settings.interface().language()); ui->statusBar->setup(ui, settings); { @@ -259,8 +260,6 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(settings.interface().language()); - m_CategoryFactory.loadCategories(); m_Filters.reset(new FilterList(ui, m_CategoryFactory)); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index fe4dfd32..d30057aa 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1572,7 +1572,6 @@ Right now the only case I know of where this needs to be overwritten is for the Something went wrong while installing this mod. - The mod was not installed completely. @@ -1689,7 +1688,6 @@ This is likely due to a corrupted or incompatible download or unrecognized archi No errors were reported. The log below might have more information. - No errors were reported. The log below might have more information. @@ -1896,7 +1894,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2072,8 +2070,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2358,7 +2356,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2436,814 +2434,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3251,12 +3249,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3264,335 +3262,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - - &Add as Executable + + Preview - - Preview + + &Add as Executable - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6147,18 +6145,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6328,6 +6326,21 @@ If the folder was still in use, restart MO and try again. Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! + + + '%1': file is blocked (%2) + + + + + '%1' seems to be missing, an antivirus may have deleted it + + + + + %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. + + @@ -6532,6 +6545,51 @@ Example: Geometries will be reset to their default values. + + + invalid origin name: + + + + + failed to change name lookup from {} to {} + + + + + failed to determine file time + + + + + invalid bsa file: + + + + + file "{}" not in directory "{}" + + + + + file "{}" not in directory "{}", directory empty + + + + + unexpected end of path + + + + + invalid file index for remove: {} + + + + + invalid file index for remove (for origin): {} + + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. @@ -6776,68 +6834,6 @@ You can restart Mod Organizer as administrator and try launching the program aga Exit Now - - - '%1': file is blocked (%2) - '%1': file is blocked ('%2') - - - - - '%1' seems to be missing, an antivirus may have deleted it - - - - - %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. - %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. (%2) - - - - - invalid origin name: - - - - - failed to change name lookup from {} to {} - - - - - failed to determine file time - - - - - invalid bsa file: - - - - - file "{}" not in directory "{}" - - - - - file "{}" not in directory "{}", directory empty - - - - - unexpected end of path - - - - - invalid file index for remove: {} - - - - - invalid file index for remove (for origin): {} - - QueryOverwriteDialog @@ -7057,13 +7053,11 @@ Select Show Details option to see the full change-log. https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ - https://www.transifex.com/tannin/mod-organizer/ <a href="https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/">Help translate Mod Organizer</a> - <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> diff --git a/src/statusbar.cpp b/src/statusbar.cpp index c2c54862..4729c6ad 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -69,7 +69,9 @@ void StatusBar::setProgress(int percent) void StatusBar::setNotifications(bool hasNotifications) { - m_notifications->set(hasNotifications); + if (m_notifications) { + m_notifications->set(hasNotifications); + } } void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) -- cgit v1.3.1