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.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/mainwindow.h') 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(); -- 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.h') 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.h') 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 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.h') 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.h') 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 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.h') 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 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.h') 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.h') 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.h') 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 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.h') 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.h') 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 89415ca5c3903ced870d3bf5698dfa0e53122520 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:32:12 -0400 Subject: moved geometry handling to PreviewDialog fixed dialogs not having a parent --- src/mainwindow.h | 1 - src/organizercore.cpp | 23 +++-------------------- src/previewdialog.cpp | 16 ++++++++++++++++ src/previewdialog.h | 4 ++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 6 files changed, 36 insertions(+), 21 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.h b/src/mainwindow.h index 46f04784..7460019d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -83,7 +83,6 @@ class QProgressDialog; class QTranslator; class QTreeWidgetItem; class QUrl; -class QSettings; class QWidget; #ifndef Q_MOC_RUN diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a64d93b4..2d11dafd 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1297,7 +1297,8 @@ bool OrganizerCore::previewFileWithAlternatives( } // set up preview dialog - PreviewDialog preview(fileName); + PreviewDialog preview(fileName, parent); + auto addFunc = [&](int originId) { FilesOrigin &origin = directoryStructure()->getOriginByID(originId); QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; @@ -1352,16 +1353,7 @@ bool OrganizerCore::previewFileWithAlternatives( } if (preview.numVariants() > 0) { - QSettings &s = settings().directInterface(); - QString key = QString("geometry/%1").arg(preview.objectName()); - if (s.contains(key)) { - preview.restoreGeometry(s.value(key).toByteArray()); - } - preview.exec(); - - s.setValue(key, preview.saveGeometry()); - return true; } else { @@ -1381,7 +1373,7 @@ bool OrganizerCore::previewFile( return false; } - PreviewDialog preview(path); + PreviewDialog preview(path, parent); QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path); if (wid == nullptr) { @@ -1390,17 +1382,8 @@ bool OrganizerCore::previewFile( } preview.addVariant(originName, wid); - - QSettings &s = settings().directInterface(); - QString key = QString("geometry/%1").arg(preview.objectName()); - if (s.contains(key)) { - preview.restoreGeometry(s.value(key).toByteArray()); - } - preview.exec(); - s.setValue(key, preview.saveGeometry()); - return true; } diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index de33cdd0..06dcd674 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -1,5 +1,6 @@ #include "previewdialog.h" #include "ui_previewdialog.h" +#include "settings.h" #include PreviewDialog::PreviewDialog(const QString &fileName, QWidget *parent) : @@ -17,6 +18,21 @@ PreviewDialog::~PreviewDialog() delete ui; } +int PreviewDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getPreviewDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setPreviewDialog(saveGeometry()); + + return r; +} + void PreviewDialog::addVariant(const QString &modName, QWidget *widget) { widget->setProperty("modName", modName); diff --git a/src/previewdialog.h b/src/previewdialog.h index 0011bc50..9525f127 100644 --- a/src/previewdialog.h +++ b/src/previewdialog.h @@ -15,6 +15,10 @@ public: explicit PreviewDialog(const QString &fileName, QWidget *parent = 0); ~PreviewDialog(); + // also saves and restores geometry + // + int exec() override; + void addVariant(const QString &modName, QWidget *widget); int numVariants() const; diff --git a/src/settings.cpp b/src/settings.cpp index d9440aa4..44aa56ba 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -957,6 +957,16 @@ void GeometrySettings::setCategoriesDialog(const QByteArray& v) m_Settings.setValue("geometry/CategoriesDialog", v); } +std::optional GeometrySettings::getPreviewDialog() const +{ + return getOptional(m_Settings, "geometry/PreviewDialog"); +} + +void GeometrySettings::setPreviewDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/PreviewDialog", v); +} + std::optional GeometrySettings::getPluginListHeader() const { return getOptional(m_Settings, "geometry/espList"); diff --git a/src/settings.h b/src/settings.h index 110cfa76..615cdcbe 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,6 +70,9 @@ public: std::optional getCategoriesDialog() const; void setCategoriesDialog(const QByteArray& v); + std::optional getPreviewDialog() const; + void setPreviewDialog(const QByteArray& v); + std::optional getPluginListHeader() const; void setPluginListHeader(const QByteArray& v) const; -- cgit v1.3.1 From 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.h') 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 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.h') 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.h') 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.h') 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 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.h') 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 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.h') 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 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.h') 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.h') 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.h') 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 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.h') 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.h') 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 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.h') 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 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.h') 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 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.h') 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.h') 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.h') 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.h') 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.h') 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 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.h') 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.h') 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.h') 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.h') 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.h') 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 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.h') 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 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.h') 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.h') 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 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.h') 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.h') 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.h') 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