From 439d0d4fcdee213b998dbfe735a0a0d6bcad7eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 14:44:18 +0200 Subject: [PATCH 01/13] Start implementing the theme manager. --- src/CMakeLists.txt | 8 +- src/extensionwatcher.h | 31 ++++ src/main.cpp | 1 + src/mainwindow.cpp | 21 +-- src/mainwindow.h | 7 +- src/moapplication.cpp | 114 ++------------- src/moapplication.h | 20 +-- src/settings.cpp | 4 +- src/settings.h | 6 +- src/settingsdialog.cpp | 6 +- src/settingsdialog.h | 5 +- src/settingsdialogtheme.cpp | 53 ++++--- src/settingsdialogtheme.h | 7 +- src/thememanager.cpp | 282 ++++++++++++++++++++++++++++++++++++ src/thememanager.h | 114 +++++++++++++++ 15 files changed, 511 insertions(+), 168 deletions(-) create mode 100644 src/extensionwatcher.h create mode 100644 src/thememanager.cpp create mode 100644 src/thememanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 500b0ca5b..85a4eb6ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,6 @@ mo2_add_filter(NAME src/core GROUPS nexusinterface nxmaccessmanager organizercore - plugincontainer apiuseraccount processrunner qdirfiletree @@ -57,6 +56,13 @@ mo2_add_filter(NAME src/core GROUPS uilocker ) +mo2_add_filter(NAME src/extensions GROUPS + plugincontainer + thememanager + translationmanager + extensionconsumer +) + mo2_add_filter(NAME src/dialogs GROUPS aboutdialog activatemodsdialog diff --git a/src/extensionwatcher.h b/src/extensionwatcher.h new file mode 100644 index 000000000..54846e746 --- /dev/null +++ b/src/extensionwatcher.h @@ -0,0 +1,31 @@ +#ifndef EXTENSIONWATCHER_H +#define EXTENSIONWATCHER_H + +#include + +// an extension watcher is a class that watches extensions get loaded/unloaded, +// typically to extract information from theme that are needed by MO2 +// +class ExtensionWatcher +{ +public: + // called when a new extension is found and loaded + // + virtual void extensionLoaded(MOBase::IExtension const& extension) = 0; + + // called when a new extension is unloaded + // + virtual void extensionUnloaded(MOBase::IExtension const& extension) = 0; + + // called when a new extension is disabled + // + virtual void extensionEnabled(MOBase::IExtension const& extension) = 0; + + // called when a new extension is disabled + // + virtual void extensionDisabled(MOBase::IExtension const& extension) = 0; + + virtual ~ExtensionWatcher() {} +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index de1578593..b6642394a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "multiprocess.h" #include "organizercore.h" #include "shared/util.h" +#include "thememanager.h" #include "thread_utils.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebe7da98d..f57b76aa2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,12 +230,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, QWidget* parent) + PluginContainer& pluginContainer, ThemeManager& themeManager, + QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), - m_PluginContainer(pluginContainer), + m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -389,8 +390,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, - SIGNAL(styleChanged(QString))); + connect(&m_OrganizerCore.settings(), &Settings::themeChanged, this, + &MainWindow::themeChanged); connect(m_OrganizerCore.updater(), SIGNAL(restart()), this, SLOT(close())); connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, @@ -1232,7 +1233,7 @@ void MainWindow::showEvent(QShowEvent* event) // by connecting the event here, changing the style setting will first be // handled by MOApplication, and then in updateStyle(), at which point the // stylesheet has already been set correctly - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + connect(this, &MainWindow::themeChanged, this, &MainWindow::updateStyle); // only the first time the window becomes visible m_Tutorial.registerControl(); @@ -1864,7 +1865,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives, }; for (FileEntryPtr current : files) { - QFileInfo fileInfo(ToQString(current->getName().c_str())); + QFileInfo fileInfo(ToQString(current->getName())); if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") { int index = activeArchives.indexOf(fileInfo.fileName()); @@ -2642,7 +2643,7 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, settings, this); + SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, settings, this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -3524,12 +3525,14 @@ void MainWindow::on_actionChange_Game_triggered() void MainWindow::setCategoryListVisible(bool visible) { + using namespace std::literals; + if (visible) { ui->categoriesGroup->show(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); + ui->displayCategoriesBtn->setText(ToQString(L"\u00ab"sv)); } else { ui->categoriesGroup->hide(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); + ui->displayCategoriesBtn->setText(ToQString(L"\u00bb"sv)); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8cd55c901..6656ed0c4 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include "modlistsortproxy.h" #include "plugincontainer.h" //class PluginContainer; #include "shared/fileregisterfwd.h" +#include "thememanager.h" #include "tutorialcontrol.h" #include @@ -125,7 +126,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, QWidget* parent = 0); + PluginContainer& pluginContainer, ThemeManager& manager, + QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -165,7 +167,7 @@ public slots: /** * @brief emitted when the selected style changes */ - void styleChanged(const QString& styleFile); + void themeChanged(const QString& themeIdentifier); void checkForProblemsDone(); @@ -295,6 +297,7 @@ private slots: OrganizerCore& m_OrganizerCore; PluginContainer& m_PluginContainer; + ThemeManager& m_ThemeManager; QString m_CurrentLanguage; std::vector m_Translators; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index e131d3d96..2e2a3ac0d 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -31,12 +31,12 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "shared/util.h" +#include "thememanager.h" #include "thread_utils.h" #include "tutorialmanager.h" #include #include #include -#include #include #include #include @@ -56,55 +56,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -// style proxy that changes the appearance of drop indicators -// -class ProxyStyle : public QProxyStyle -{ -public: - ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} - - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, - QPainter* painter, const QWidget* widget) const override - { - if (element == QStyle::PE_IndicatorItemViewItemDrop) { - - // 0. Fix a bug that made the drop indicator sometimes appear on top - // of the mod list when selecting a mod. - if (option->rect.height() == 0 && option->rect.bottomRight() == QPoint(-1, -1)) { - return; - } - - // 1. full-width drop indicator - QRect rect(option->rect); - if (auto* view = qobject_cast(widget)) { - rect.setLeft(view->indentation()); - rect.setRight(widget->width()); - } - - // 2. stylish drop indicator - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor col(option->palette.windowText().color()); - QPen pen(col); - pen.setWidth(2); - col.setAlpha(50); - - painter->setPen(pen); - painter->setBrush(QBrush(col)); - if (rect.height() == 0) { - QPoint tri[3] = {rect.topLeft(), rect.topLeft() + QPoint(-5, 5), - rect.topLeft() + QPoint(-5, -5)}; - painter->drawPolygon(tri, 3); - painter->drawLine(rect.topLeft(), rect.topRight()); - } else { - painter->drawRoundedRect(rect, 5, 5); - } - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - } -}; - // This adds the `dlls` directory to the path so the dlls can be found. How // MO is able to find dlls in there is a bit convoluted: // @@ -148,14 +99,6 @@ MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) TimeThis tt("MOApplication()"); qputenv("QML_DISABLE_DISK_CACHE", "true"); - - connect(&m_styleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file) { - log::debug("style file '{}' changed, reloading", file); - updateStyle(file); - }); - - m_defaultStyle = style()->objectName(); - setStyle(new ProxyStyle(style())); addDllsToPath(); } @@ -267,6 +210,8 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) tt.start("MOApplication::doOneRun() plugins"); log::debug("initializing plugins"); + m_themes = std::make_unique(this); + m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); @@ -329,25 +274,26 @@ int MOApplication::run(MOMultiProcess& multiProcess) m_core.get()); // styling - if (!setStyleFile(m_settings->interface().styleName().value_or(""))) { + if (!m_themes->load(m_settings->interface().themeName().value_or("").toStdString())) { // disable invalid stylesheet - m_settings->interface().setStyleName(""); + m_settings->interface().setThemeName(""); } int res = 1; { tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins); + MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes); // the nexus interface can show dialogs, make sure they're parented to the // main window m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow); + // TODO: connect( - &mainWindow, &MainWindow::styleChanged, this, - [this](auto&& file) { - setStyleFile(file); + &mainWindow, &MainWindow::themeChanged, this, + [this](auto&& themeIdentifier) { + m_themes->load(themeIdentifier.toStdString()); }, Qt::QueuedConnection); @@ -516,31 +462,6 @@ void MOApplication::resetForRestart() m_instance = {}; } -bool MOApplication::setStyleFile(const QString& styleName) -{ - // remove all files from watch - QStringList currentWatch = m_styleWatcher.files(); - if (currentWatch.count() != 0) { - m_styleWatcher.removePaths(currentWatch); - } - // set new stylesheet or clear it - if (styleName.length() != 0) { - QString styleSheetName = applicationDirPath() + "/" + - MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + - styleName; - if (QFile::exists(styleSheetName)) { - m_styleWatcher.addPath(styleSheetName); - updateStyle(styleSheetName); - } else { - updateStyle(styleName); - } - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); - setStyleSheet(""); - } - return true; -} - bool MOApplication::notify(QObject* receiver, QEvent* event) { try { @@ -558,21 +479,6 @@ bool MOApplication::notify(QObject* receiver, QEvent* event) } } -void MOApplication::updateStyle(const QString& fileName) -{ - if (QStyleFactory::keys().contains(fileName)) { - setStyleSheet(""); - setStyle(new ProxyStyle(QStyleFactory::create(fileName))); - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); - if (QFile::exists(fileName)) { - setStyleSheet(QString("file:///%1").arg(fileName)); - } else { - log::warn("invalid stylesheet: {}", fileName); - } - } -} - MOSplash::MOSplash(const Settings& settings, const QString& dataPath, const MOBase::IPluginGame* game) { diff --git a/src/moapplication.h b/src/moapplication.h index 498242f3e..e7ad983a8 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,12 +24,15 @@ along with Mod Organizer. If not, see . #include #include -class Settings; -class MOMultiProcess; +#include "thememanager.h" + class Instance; -class PluginContainer; -class OrganizerCore; +class MOMultiProcess; class NexusInterface; +class OrganizerCore; +class PluginContainer; +class Settings; +class ThemeManager; namespace MOBase { @@ -70,21 +73,14 @@ class MOApplication : public QApplication // bool notify(QObject* receiver, QEvent* event) override; -public slots: - bool setStyleFile(const QString& style); - -private slots: - void updateStyle(const QString& fileName); - private: - QFileSystemWatcher m_styleWatcher; - QString m_defaultStyle; std::unique_ptr m_modules; std::unique_ptr m_instance; std::unique_ptr m_settings; std::unique_ptr m_nexus; std::unique_ptr m_plugins; + std::unique_ptr m_themes; std::unique_ptr m_core; void externalMessage(const QString& message); diff --git a/src/settings.cpp b/src/settings.cpp index fc9985f47..ecc526568 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2058,12 +2058,12 @@ void InterfaceSettings::setLockGUI(bool b) set(m_Settings, "Settings", "lock_gui", b); } -std::optional InterfaceSettings::styleName() const +std::optional InterfaceSettings::themeName() const { return getOptional(m_Settings, "Settings", "style"); } -void InterfaceSettings::setStyleName(const QString& name) +void InterfaceSettings::setThemeName(const QString& name) { set(m_Settings, "Settings", "style", name); } diff --git a/src/settings.h b/src/settings.h index 6a11d6620..d92cbbdd4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -584,8 +584,8 @@ class InterfaceSettings // filename of the theme // - std::optional styleName() const; - void setStyleName(const QString& name); + std::optional themeName() const; + void setThemeName(const QString& name); // whether to use collapsible separators when possible // @@ -872,7 +872,7 @@ public slots: // these are fired from outside the settings, mostly by the settings dialog // void languageChanged(const QString& newLanguage); - void styleChanged(const QString& newStyle); + void themeChanged(const QString& themeIdentifier); private: static Settings* s_Instance; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 425a1cb80..e79847cc8 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -30,7 +30,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& settings, +SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, + ThemeManager const& manager, Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) @@ -39,7 +40,8 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti m_tabs.push_back( std::unique_ptr(new GeneralSettingsTab(settings, *this))); - m_tabs.push_back(std::unique_ptr(new ThemeSettingsTab(settings, *this))); + m_tabs.push_back( + std::unique_ptr(new ThemeSettingsTab(settings, manager, *this))); m_tabs.push_back( std::unique_ptr(new ModListSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index eca0b2662..ad45bc6f9 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" +class ThemeManager; class PluginContainer; class Settings; class SettingsDialog; @@ -63,8 +64,8 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, Settings& settings, - QWidget* parent = 0); + explicit SettingsDialog(PluginContainer* pluginContainer, ThemeManager const& manager, + Settings& settings, QWidget* parent = 0); ~SettingsDialog(); diff --git a/src/settingsdialogtheme.cpp b/src/settingsdialogtheme.cpp index 75bcdb598..57f6936cd 100644 --- a/src/settingsdialogtheme.cpp +++ b/src/settingsdialogtheme.cpp @@ -9,10 +9,12 @@ using namespace MOBase; -ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) +ThemeSettingsTab::ThemeSettingsTab(Settings& s, ThemeManager const& manager, + SettingsDialog& d) + : SettingsTab(s, d) { // style - addStyles(); + addStyles(manager); selectStyle(); // colors @@ -21,61 +23,56 @@ ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&] { ui->colorTable->resetColors(); }); - - QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&] { - onExploreStyles(); - }); } void ThemeSettingsTab::update() { // style - const QString oldStyle = settings().interface().styleName().value_or(""); + const QString oldStyle = settings().interface().themeName().value_or(""); const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - settings().interface().setStyleName(newStyle); - emit settings().styleChanged(newStyle); + settings().interface().setThemeName(newStyle); + emit settings().themeChanged(newStyle); } // colors ui->colorTable->commitColors(); } -void ThemeSettingsTab::addStyles() +void ThemeSettingsTab::addStyles(ThemeManager const& manager) { ui->styleBox->addItem("None", ""); - for (auto&& key : QStyleFactory::keys()) { - ui->styleBox->addItem(key, key); - } - ui->styleBox->insertSeparator(ui->styleBox->count()); + auto themes = manager.themes(); - QDirIterator iter(QCoreApplication::applicationDirPath() + "/" + - QString::fromStdWString(AppConfig::stylesheetsPath()), - QStringList("*.qss"), QDir::Files); + std::sort(themes.begin(), themes.end(), [&manager](auto&& lhs, auto&& rhs) { + if (manager.isBuiltIn(lhs) == manager.isBuiltIn(rhs)) { + return lhs->name() < rhs->name(); + } else { + // put built-in before others + return manager.isBuiltIn(rhs) < manager.isBuiltIn(lhs); + } + }); - while (iter.hasNext()) { - iter.next(); + bool separator = true; + for (auto&& theme : themes) { + if (separator && !manager.isBuiltIn(theme)) { + ui->styleBox->insertSeparator(ui->styleBox->count()); + separator = false; + } - ui->styleBox->addItem(iter.fileInfo().completeBaseName(), iter.fileName()); + ui->styleBox->addItem(ToQString(theme->identifier()), ToQString(theme->name())); } } void ThemeSettingsTab::selectStyle() { const int currentID = - ui->styleBox->findData(settings().interface().styleName().value_or("")); + ui->styleBox->findData(settings().interface().themeName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } - -void ThemeSettingsTab::onExploreStyles() -{ - QString ssPath = QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::stylesheetsPath()); - shell::Explore(ssPath); -} diff --git a/src/settingsdialogtheme.h b/src/settingsdialogtheme.h index 2a53dc38c..2ca5c9458 100644 --- a/src/settingsdialogtheme.h +++ b/src/settingsdialogtheme.h @@ -5,18 +5,19 @@ #include "settings.h" #include "settingsdialog.h" +#include "thememanager.h" class ThemeSettingsTab : public SettingsTab { public: - ThemeSettingsTab(Settings& settings, SettingsDialog& dialog); + ThemeSettingsTab(Settings& settings, ThemeManager const& manager, + SettingsDialog& dialog); void update() override; private: - void addStyles(); + void addStyles(ThemeManager const& manager); void selectStyle(); - void onExploreStyles(); }; #endif // SETTINGSDIALOGGENERAL_H diff --git a/src/thememanager.cpp b/src/thememanager.cpp new file mode 100644 index 000000000..aefc449e3 --- /dev/null +++ b/src/thememanager.cpp @@ -0,0 +1,282 @@ +#include "thememanager.h" + +#include +#include +#include + +#include +#include + +#include "shared/appconfig.h" + +using namespace MOBase; + +// style proxy that changes the appearance of drop indicators +// +class ProxyStyle : public QProxyStyle +{ +public: + ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} + + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, + QPainter* painter, const QWidget* widget) const override + { + if (element == QStyle::PE_IndicatorItemViewItemDrop) { + + // 0. Fix a bug that made the drop indicator sometimes appear on top + // of the mod list when selecting a mod. + if (option->rect.height() == 0 && option->rect.bottomRight() == QPoint(-1, -1)) { + return; + } + + // 1. full-width drop indicator + QRect rect(option->rect); + if (auto* view = qobject_cast(widget)) { + rect.setLeft(view->indentation()); + rect.setRight(widget->width()); + } + + // 2. stylish drop indicator + painter->setRenderHint(QPainter::Antialiasing, true); + + QColor col(option->palette.windowText().color()); + QPen pen(col); + pen.setWidth(2); + col.setAlpha(50); + + painter->setPen(pen); + painter->setBrush(QBrush(col)); + if (rect.height() == 0) { + QPoint tri[3] = {rect.topLeft(), rect.topLeft() + QPoint(-5, 5), + rect.topLeft() + QPoint(-5, -5)}; + painter->drawPolygon(tri, 3); + painter->drawLine(rect.topLeft(), rect.topRight()); + } else { + painter->drawRoundedRect(rect, 5, 5); + } + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + } +}; + +ThemeManager::ThemeManager(QApplication* application) : m_app{application} +{ + // add built-in themes + addQtThemes(); + + // TODO: remove this + addOldFormatThemes(); + + // find the default theme + m_defaultTheme = + m_baseThemesByIdentifier.at(m_app->style()->objectName().toStdString()); + + // for ease, we set the empty identifier to the default theme + m_baseThemesByIdentifier[""] = m_defaultTheme; + + // load the default theme + load(m_defaultTheme); + + // connect the style watcher + m_app->connect(&m_watcher, &QFileSystemWatcher::fileChanged, [this](auto&&) { + reload(); + }); +} + +// TODO: remove this +void ThemeManager::addOldFormatThemes() +{ + QDirIterator iter(QCoreApplication::applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::stylesheetsPath()), + QStringList("*.qss"), QDir::Files); + + while (iter.hasNext()) { + iter.next(); + registerTheme( + std::make_shared(iter.fileInfo().completeBaseName().toStdString(), + iter.fileInfo().baseName().toStdString(), + iter.fileInfo().filesystemFilePath())); + } +} + +bool ThemeManager::load(std::shared_ptr theme) +{ + // no theme -> default + if (!theme) { + theme = m_defaultTheme; + } + + // do not reload the current theme + if (theme == m_currentTheme) { + return true; + } + + // set the current theme + m_currentTheme = theme; + + if (isBuiltIn(theme)) { + loadQtTheme(theme->identifier()); + watchThemeFiles(nullptr); + } else { + loadExtensionTheme(theme); + watchThemeFiles(theme); + } + + return true; +} + +bool ThemeManager::load(std::string_view themeIdentifier) +{ + auto it = m_baseThemesByIdentifier.find(themeIdentifier); + if (it == m_baseThemesByIdentifier.end()) { + log::error("theme '{}' not found", themeIdentifier); + return false; + } + + return load(it->second); +} + +void ThemeManager::loadQtTheme(std::string_view themeIdentifier) +{ + m_app->setStyleSheet(""); + m_app->setStyle(new ProxyStyle(QStyleFactory::create(ToQString(themeIdentifier)))); +} + +void ThemeManager::loadExtensionTheme(std::shared_ptr const& theme) +{ + // load the default theme + m_app->setStyle( + new ProxyStyle(QStyleFactory::create(ToQString(m_defaultTheme->identifier())))); + + // build the stylesheet and set it + m_app->setStyleSheet(buildStyleSheet(theme)); +} + +void ThemeManager::unload() +{ + // load the default style + load(m_defaultTheme); +} + +void ThemeManager::reload() +{ + // cannot reload if there is no theme or builtin themes + if (!m_currentTheme || m_currentTheme->stylesheet().empty()) { + return; + } + + if (isBuiltIn(m_currentTheme)) { + loadQtTheme(m_currentTheme->identifier()); + } else { + loadExtensionTheme(m_currentTheme); + } +} + +void ThemeManager::registerTheme(std::shared_ptr const& theme) +{ + // two themes with same identifier, skip (+ warn) + auto it = m_baseThemesByIdentifier.find(theme->identifier()); + if (it != m_baseThemesByIdentifier.end()) { + log::warn("found existing theme with identifier '{}', skipping", + theme->identifier()); + return; + } + + m_baseThemes.push_back(theme); + m_baseThemesByIdentifier[theme->identifier()] = theme; +} + +void ThemeManager::addQtThemes() +{ + for (const auto& key : QStyleFactory::keys()) { + registerTheme(std::make_shared(key.toStdString(), key.toStdString(), + std::filesystem::path{})); + } +} + +QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) const +{ + // TODO: + return QString("file:///%1").arg(ToQString(absolute(theme->stylesheet()).native())); +} + +void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) +{ + // clear previous files + QStringList currentWatch = m_watcher.files(); + if (currentWatch.count() != 0) { + m_watcher.removePaths(currentWatch); + } + + if (!theme) { + return; + } + + // find theme files + QStringList themeFiles; + + themeFiles.append(ToQString(absolute(theme->stylesheet()).native())); + + for (auto&& themeExtension : m_globalExtensions) { + themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + } + + for (auto&& themeExtension : m_themeExtensionsByIdentifier[theme->identifier()]) { + themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + } + + // add all files + m_watcher.addPaths(themeFiles); +} + +void ThemeManager::extensionLoaded(IExtension const& extension) +{ + for (const auto& theme : extension.themes()) { + registerTheme(theme); + } + + for (const auto& themeExtension : extension.themeExtensions()) { + const auto identifier = themeExtension->baseIdentifier(); + + if (identifier.has_value()) { + m_themeExtensionsByIdentifier[*identifier].push_back(themeExtension); + } else { + m_globalExtensions.push_back(themeExtension); + } + } +} + +void ThemeManager::extensionUnloaded(IExtension const& extension) +{ + // remove theme, unload if needed + for (const auto& theme : extension.themes()) { + if (m_currentTheme == theme) { + unload(); + } + + if (std::erase(m_baseThemes, theme) > 0) { + m_themeExtensionsByIdentifier.erase(theme->identifier()); + } + } + + for (const auto& themeExtension : extension.themeExtensions()) { + std::erase(m_globalExtensions, themeExtension); + + if (themeExtension->baseIdentifier().has_value() && + m_themeExtensionsByIdentifier.contains(*themeExtension->baseIdentifier())) { + std::erase(m_themeExtensionsByIdentifier[*themeExtension->baseIdentifier()], + themeExtension); + } + } +} + +void ThemeManager::extensionEnabled(IExtension const& extension) +{ + extensionLoaded(extension); +} + +void ThemeManager::extensionDisabled(IExtension const& extension) +{ + extensionUnloaded(extension); +} diff --git a/src/thememanager.h b/src/thememanager.h new file mode 100644 index 000000000..9ecc10e80 --- /dev/null +++ b/src/thememanager.h @@ -0,0 +1,114 @@ +#ifndef THEMEMANAGER_H +#define THEMEMANAGER_H + +#include +#include + +#include "extensionwatcher.h" + +class ThemeManager : public ExtensionWatcher +{ +public: + ThemeManager(QApplication* application); + + // retrieve the list of available themes + // + const auto& themes() const { return m_baseThemes; } + + // load the given theme + // + bool load(std::shared_ptr theme); + bool load(std::string_view themeIdentifier); + + // unload the current theme + // + void unload(); + + // retrieve the current theme, if there is one + // + auto currentTheme() const { return m_currentTheme; } + + // check if the given theme is a built-in Qt theme (not from an extension) + // + bool isBuiltIn(std::shared_ptr const& theme) const + { + return theme->stylesheet().empty(); + } + +public: // ExtensionWatcher + void extensionLoaded(MOBase::IExtension const& extension) override; + void extensionUnloaded(MOBase::IExtension const& extension) override; + void extensionEnabled(MOBase::IExtension const& extension) override; + void extensionDisabled(MOBase::IExtension const& extension) override; + +private: + // reload the current style + // + void reload(); + + // register a theme + // + void registerTheme(std::shared_ptr const& theme); + + // add built-in themes + // + void addQtThemes(); + + // load a Qt theme + // + void loadQtTheme(std::string_view identifier); + + // load an extension theme + // + void loadExtensionTheme(std::shared_ptr const& theme); + + // build a stylesheet for a theme + // + QString buildStyleSheet(std::shared_ptr const& theme) const; + + // watch files for the given theme (can be nullptr to stop watching) + // + void watchThemeFiles(std::shared_ptr const& theme); + + // [deprecated] add themes for the stylesheets folder + // + [[deprecated]] void addOldFormatThemes(); + +private: + // TODO: move these two elsewhere + struct string_equal : std::equal_to + { + using is_transparent = std::true_type; + }; + + struct string_hash : std::hash + { + using is_transparent = std::true_type; + }; + + // application and file system watcher + QApplication* m_app; + QFileSystemWatcher m_watcher; + + // the default current theme + std::shared_ptr m_defaultTheme; + std::shared_ptr m_currentTheme; + + // the list of base themes + std::vector> m_baseThemes; + std::unordered_map, string_hash, + string_equal> + m_baseThemesByIdentifier; + + // the list of theme extensions per theme identifier, for extension that + // have identifiers + std::unordered_map>, + string_hash, string_equal> + m_themeExtensionsByIdentifier; + + // theme extensions for all themes + std::vector> m_globalExtensions; +}; + +#endif From 1ce68366fd3822374488d1c1ec1a19eeafe5a222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 15:23:43 +0200 Subject: [PATCH 02/13] Implement theme extensions. --- src/thememanager.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++-- src/thememanager.h | 5 ++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/thememanager.cpp b/src/thememanager.cpp index aefc449e3..f8f15c6ae 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -195,10 +195,66 @@ void ThemeManager::addQtThemes() } } +namespace +{ +QString readWholeFile(std::filesystem::path const& path) +{ + QFile file(path); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return {}; + } + + return QTextStream(&file).readAll(); +} +} // namespace + QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) const { - // TODO: - return QString("file:///%1").arg(ToQString(absolute(theme->stylesheet()).native())); + // create the base stylesheet + QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), + theme->stylesheet().parent_path()); + + for (auto&& globalExtension : m_globalExtensions) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(globalExtension->stylesheet()), + globalExtension->stylesheet().parent_path()); + } + + auto it = m_themeExtensionsByIdentifier.find(theme->identifier()); + if (it != m_themeExtensionsByIdentifier.end()) { + for (auto&& themExtension : m_themeExtensionsByIdentifier.at(theme->identifier())) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(themExtension->stylesheet()), + themExtension->stylesheet().parent_path()); + } + } + + return stylesheet; +} + +QString ThemeManager::patchStyleSheet(QString stylesheet, + std::filesystem::path const& folder) const +{ + // we try to extract url() from the stylesheet and replace them + QRegularExpression urlRegex(R"re((:|\s+)url\("?([^")]+)"?\))re"); + + QString newStyleSheet = ""; + while (!stylesheet.isEmpty()) { + auto match = urlRegex.match(stylesheet); + + if (match.hasMatch()) { + QFileInfo path(match.captured(2)); + if (path.isRelative()) { + path = QFileInfo(QDir(folder), match.captured(2)); + } + newStyleSheet += stylesheet.left(match.capturedStart()) + match.captured(1) + + "url(\"" + path.absoluteFilePath() + "\")"; + stylesheet = stylesheet.mid(match.capturedEnd()); + } else { + newStyleSheet += stylesheet; + stylesheet = ""; + } + } + + return newStyleSheet; } void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) diff --git a/src/thememanager.h b/src/thememanager.h index 9ecc10e80..7c48de9c7 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -66,6 +66,11 @@ class ThemeManager : public ExtensionWatcher // QString buildStyleSheet(std::shared_ptr const& theme) const; + // patch the given stylesheet by replacing url() to be relative to the given folder + // + QString patchStyleSheet(QString stylesheet, + std::filesystem::path const& folder) const; + // watch files for the given theme (can be nullptr to stop watching) // void watchThemeFiles(std::shared_ptr const& theme); From 1409d44ba47704922371b652783355c5b1de1d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 May 2022 17:07:57 +0200 Subject: [PATCH 03/13] Implement translation manager. --- src/iuserinterface.h | 2 - src/mainwindow.cpp | 45 ++----- src/mainwindow.h | 13 +- src/moapplication.cpp | 5 +- src/moapplication.h | 2 + src/settingsdialog.cpp | 13 +- src/settingsdialog.h | 8 +- src/settingsdialoggeneral.cpp | 60 ++------- src/settingsdialoggeneral.h | 6 +- src/thememanager.cpp | 2 +- src/thememanager.h | 2 + src/translationmanager.cpp | 222 ++++++++++++++++++++++++++++++++++ src/translationmanager.h | 83 +++++++++++++ 13 files changed, 357 insertions(+), 106 deletions(-) create mode 100644 src/translationmanager.cpp create mode 100644 src/translationmanager.h diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 7ddc545d0..01a6e7902 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -13,8 +13,6 @@ class IUserInterface public: virtual void registerModPage(MOBase::IPluginModPage* modPage) = 0; - virtual void installTranslator(const QString& name) = 0; - virtual bool closeWindow() = 0; virtual void setWindowEnabled(bool enabled) = 0; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f57b76aa2..fa45dfc94 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -231,12 +231,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, PluginContainer& pluginContainer, ThemeManager& themeManager, - QWidget* parent) + TranslationManager& translationManager, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), + m_TranslationManager(translationManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -263,7 +264,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, MOShared::SetThisThreadName("main"); ui->setupUi(this); - languageChange(settings.interface().language()); + onLanguageChanged(settings.interface().language()); ui->statusBar->setup(ui, settings); { @@ -388,8 +389,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, - SLOT(languageChange(QString))); + connect(&m_OrganizerCore.settings(), &Settings::languageChanged, this, + &MainWindow::onLanguageChanged); connect(&m_OrganizerCore.settings(), &Settings::themeChanged, this, &MainWindow::themeChanged); @@ -502,9 +503,6 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, [=](auto&& message) { showMessage(message); }); - for (const QString& fileName : m_PluginContainer.pluginFileNames()) { - installTranslator(QFileInfo(fileName).baseName()); - } updateModPageMenu(); @@ -2643,7 +2641,8 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, settings, this); + SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, m_TranslationManager, + settings, this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -2755,36 +2754,10 @@ void MainWindow::on_actionNexus_triggered() shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName))); } -void MainWindow::installTranslator(const QString& name) +void MainWindow::onLanguageChanged(const QString& newLanguage) { - QTranslator* translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; - if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { - log::debug("localization file %s not found", fileName); - } // we don't actually expect localization files for English (en, en-us, en-uk, and - // any variation thereof) - } + m_TranslationManager.load(newLanguage.toStdString()); - qApp->installTranslator(translator); - m_Translators.push_back(translator); -} - -void MainWindow::languageChange(const QString& newLanguage) -{ - for (QTranslator* trans : m_Translators) { - qApp->removeTranslator(trans); - } - m_Translators.clear(); - - m_CurrentLanguage = newLanguage; - - installTranslator("qt"); - installTranslator("qtbase"); - installTranslator(ToQString(AppConfig::translationPrefix())); - for (const QString& fileName : m_PluginContainer.pluginFileNames()) { - installTranslator(QFileInfo(fileName).baseName()); - } ui->retranslateUi(this); log::debug("loaded language {}", newLanguage); diff --git a/src/mainwindow.h b/src/mainwindow.h index 6656ed0c4..a6135b8bd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -32,6 +32,7 @@ along with Mod Organizer. If not, see . #include "plugincontainer.h" //class PluginContainer; #include "shared/fileregisterfwd.h" #include "thememanager.h" +#include "translationmanager.h" #include "tutorialcontrol.h" #include @@ -126,8 +127,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& manager, - QWidget* parent = 0); + PluginContainer& pluginContainer, ThemeManager& themeManager, + TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -140,8 +141,6 @@ class MainWindow : public QMainWindow, public IUserInterface void saveArchiveList(); - void installTranslator(const QString& name); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; @@ -298,9 +297,7 @@ private slots: OrganizerCore& m_OrganizerCore; PluginContainer& m_PluginContainer; ThemeManager& m_ThemeManager; - - QString m_CurrentLanguage; - std::vector m_Translators; + TranslationManager& m_TranslationManager; std::unique_ptr m_IntegratedBrowser; @@ -342,7 +339,7 @@ private slots: void linkDesktop(); void linkMenu(); - void languageChange(const QString& newLanguage); + void onLanguageChanged(const QString& newLanguage); void windowTutorialFinished(const QString& windowName); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 2e2a3ac0d..02fef66d6 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -210,7 +210,8 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) tt.start("MOApplication::doOneRun() plugins"); log::debug("initializing plugins"); - m_themes = std::make_unique(this); + m_themes = std::make_unique(this); + m_translations = std::make_unique(this); m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); @@ -283,7 +284,7 @@ int MOApplication::run(MOMultiProcess& multiProcess) { tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes); + MainWindow mainWindow(*m_settings, *m_core, *m_plugins, *m_themes, *m_translations); // the nexus interface can show dialogs, make sure they're parented to the // main window diff --git a/src/moapplication.h b/src/moapplication.h index e7ad983a8..8f7572240 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include "thememanager.h" +#include "translationmanager.h" class Instance; class MOMultiProcess; @@ -81,6 +82,7 @@ class MOApplication : public QApplication std::unique_ptr m_nexus; std::unique_ptr m_plugins; std::unique_ptr m_themes; + std::unique_ptr m_translations; std::unique_ptr m_core; void externalMessage(const QString& message); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index e79847cc8..abe54d242 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -31,17 +31,18 @@ along with Mod Organizer. If not, see . using namespace MOBase; SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, - ThemeManager const& manager, Settings& settings, - QWidget* parent) + ThemeManager const& themeManager, + TranslationManager const& translationManager, + Settings& settings, QWidget* parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) { ui->setupUi(this); - m_tabs.push_back( - std::unique_ptr(new GeneralSettingsTab(settings, *this))); - m_tabs.push_back( - std::unique_ptr(new ThemeSettingsTab(settings, manager, *this))); + m_tabs.push_back(std::unique_ptr( + new GeneralSettingsTab(settings, translationManager, *this))); + m_tabs.push_back(std::unique_ptr( + new ThemeSettingsTab(settings, themeManager, *this))); m_tabs.push_back( std::unique_ptr(new ModListSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index ad45bc6f9..60f69ccf8 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,10 +23,12 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" -class ThemeManager; class PluginContainer; class Settings; class SettingsDialog; +class ThemeManager; +class TranslationManager; + namespace Ui { class SettingsDialog; @@ -64,7 +66,9 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, ThemeManager const& manager, + explicit SettingsDialog(PluginContainer* pluginContainer, + ThemeManager const& themeManager, + TranslationManager const& translationManager, Settings& settings, QWidget* parent = 0); ~SettingsDialog(); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 95655ac86..7afdb145e 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -8,11 +8,13 @@ using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, + TranslationManager const& translationManager, + SettingsDialog& d) : SettingsTab(s, d) { // language - addLanguages(); + addLanguages(translationManager); selectLanguage(); // download list @@ -73,54 +75,18 @@ void GeneralSettingsTab::update() ui->doubleClickPreviews->isChecked()); } -void GeneralSettingsTab::addLanguages() +void GeneralSettingsTab::addLanguages(TranslationManager const& manager) { - // matches the end of filenames for something like "_en.qm" or "_zh_CN.qm" - const QString pattern = QString::fromStdWString(AppConfig::translationPrefix()) + - "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + auto translations = manager.translations(); - const QRegularExpression exp(QRegularExpression::anchoredPattern(pattern)); - - QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", - QDir::Files); - - std::vector> languages; - - while (iter.hasNext()) { - iter.next(); - - const QString file = iter.fileName(); - auto match = exp.match(file); - if (!match.hasMatch()) { - continue; - } - - const QString languageCode = match.captured(1); - const QLocale locale(languageCode); - - QString languageString = QString("%1 (%2)") - .arg(locale.nativeLanguageName()) - .arg(locale.nativeCountryName()); - - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (Traditional)"; - } else { - languageString = "Chinese (Simplified)"; - } - } - - languages.push_back({languageString, match.captured(1)}); - } - - if (!ui->languageBox->findText("English")) { - languages.push_back({QString("English"), QString("en_US")}); - } - - std::sort(languages.begin(), languages.end()); + std::sort(translations.begin(), translations.end(), [](auto&& lhs, auto&& rhs) { + return std::forward_as_tuple(lhs->language(), lhs->identifier()) < + std::forward_as_tuple(rhs->language(), rhs->identifier()); + }); - for (const auto& lang : languages) { - ui->languageBox->addItem(lang.first, lang.second); + for (const auto& translation : translations) { + ui->languageBox->addItem(ToQString(translation->language()), + ToQString(translation->identifier())); } } diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 619381905..ec1744b43 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -3,16 +3,18 @@ #include "settings.h" #include "settingsdialog.h" +#include "translationmanager.h" class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); + GeneralSettingsTab(Settings& settings, TranslationManager const& translationManager, + SettingsDialog& dialog); void update(); private: - void addLanguages(); + void addLanguages(TranslationManager const& translationManager); void selectLanguage(); void resetDialogs(); diff --git a/src/thememanager.cpp b/src/thememanager.cpp index f8f15c6ae..db06008b5 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -312,7 +312,7 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) } if (std::erase(m_baseThemes, theme) > 0) { - m_themeExtensionsByIdentifier.erase(theme->identifier()); + m_baseThemesByIdentifier.erase(theme->identifier()); } } diff --git a/src/thememanager.h b/src/thememanager.h index 7c48de9c7..69789a4f7 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -4,6 +4,8 @@ #include #include +#include + #include "extensionwatcher.h" class ThemeManager : public ExtensionWatcher diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp new file mode 100644 index 000000000..b10e7ed68 --- /dev/null +++ b/src/translationmanager.cpp @@ -0,0 +1,222 @@ +#include "translationmanager.h" + +#include +#include +#include + +#include +#include + +#include "shared/appconfig.h" + +using namespace MOBase; + +TranslationManager::TranslationManager(QApplication* application) : m_app{application} +{ + // TODO: remove this + addOldFormatTranslations(); + + registerTranslation(std::make_shared( + "en_US", "English", std::vector{})); + + // specific translation to allow load("") to actually unload + m_translationByLanguage[""] = nullptr; +} + +// TODO: remove this +void TranslationManager::addOldFormatTranslations() +{ + const QRegularExpression mainTranslationPattern(QRegularExpression::anchoredPattern( + QString::fromStdWString(AppConfig::translationPrefix()) + + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm")); + + // extract the main translations + QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", + QDir::Files); + while (iter.hasNext()) { + iter.next(); + + const QString file = iter.fileName(); + auto match = mainTranslationPattern.match(file); + if (!match.hasMatch()) { + continue; + } + + const QString languageCode = match.captured(1); + const QLocale locale(languageCode); + + QString languageString = QString("%1 (%2)") + .arg(locale.nativeLanguageName()) + .arg(locale.nativeCountryName()); + + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (Traditional)"; + } else { + languageString = "Chinese (Simplified)"; + } + } + + std::vector qm_files{iter.fileInfo().filesystemFilePath()}; + + if (auto qt_path = qm_files[0].parent_path() / ("qt_" + languageCode.toStdString()); + exists(qt_path)) { + qm_files.push_back(qt_path); + } + + if (auto qtbase_path = + qm_files[0].parent_path() / ("qtbase_" + languageCode.toStdString()); + exists(qtbase_path)) { + qm_files.push_back(qtbase_path); + } + + registerTranslation(std::make_shared( + languageCode.toStdString(), languageString.toStdString(), qm_files)); + } + + // lookup each file except for main and Qt and add an extension for them + for (auto& [code, translation] : m_translationByLanguage) { + QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", + {ToQString("*_" + code + ".qm")}, QDir::Files); + + while (iter.hasNext()) { + iter.next(); + const auto filename = iter.fileName(); + + // skip main files + if (filename.startsWith(AppConfig::translationPrefix()) || + filename.startsWith("qt")) { + continue; + } + + m_translationExtensions[code].push_back(std::make_shared( + code, std::vector{iter.fileInfo().filesystemFilePath()})); + } + } +} + +bool TranslationManager::load(std::shared_ptr translation) +{ + // no translation -> abort + if (!translation) { + unload(); + return true; + } + + // do not reload the current translation + if (translation == m_currentTranslation) { + return true; + } + + // unload previous translations + unload(); + + // set the current translation + m_currentTranslation = translation; + + // retrieve all files + std::vector qm_files = translation->files(); + { + auto it = m_translationExtensions.find(m_currentTranslation->identifier()); + if (it != m_translationExtensions.end()) { + for (auto&& translationExtension : it->second) { + const auto& ext_files = translationExtension->files(); + qm_files.insert(qm_files.end(), ext_files.begin(), ext_files.end()); + } + } + } + + // add translators + for (const auto& qm_file : qm_files) { + auto translator = std::make_unique(); + if (translator->load(ToQString(absolute(qm_file).native()))) { + m_app->installTranslator(translator.get()); + m_translators.push_back(std::move(translator)); + } else { + log::warn("failed to load translation from '{}'", qm_file.native()); + } + } + + return true; +} + +bool TranslationManager::load(std::string_view language) +{ + auto it = m_translationByLanguage.find(language); + if (it == m_translationByLanguage.end()) { + log::error("translation for '{}' not found", language); + return false; + } + + return load(it->second); +} + +void TranslationManager::unload() +{ + // remove translators from application + for (auto&& translator : m_translators) { + m_app->removeTranslator(translator.get()); + } + m_translators.clear(); + + // unset current translation + m_currentTranslation = nullptr; +} + +void TranslationManager::registerTranslation( + std::shared_ptr const& translation) +{ + // two translations with same identifier, skip (+ warn) + auto it = m_translationByLanguage.find(translation->identifier()); + if (it != m_translationByLanguage.end()) { + log::warn("found existing translation with identifier '{}', skipping", + translation->identifier()); + return; + } + + m_translations.push_back(translation); + m_translationByLanguage[translation->identifier()] = translation; +} + +void TranslationManager::extensionLoaded(IExtension const& extension) +{ + for (const auto& translation : extension.translations()) { + registerTranslation(translation); + } + + for (const auto& translationExtension : extension.translationExtensions()) { + const auto identifier = translationExtension->baseIdentifier(); + m_translationExtensions[identifier].push_back(translationExtension); + } +} + +void TranslationManager::extensionUnloaded(IExtension const& extension) +{ + // remove translation, unload if needed + for (const auto& translation : extension.translations()) { + if (m_currentTranslation == translation) { + unload(); + } + + if (std::erase(m_translations, translation) > 0) { + m_translationByLanguage.erase(translation->identifier()); + } + } + + for (const auto& translationExtension : extension.translationExtensions()) { + if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { + std::erase(m_translationExtensions[translationExtension->baseIdentifier()], + translationExtension); + } + } +} + +void TranslationManager::extensionEnabled(IExtension const& extension) +{ + extensionLoaded(extension); +} + +void TranslationManager::extensionDisabled(IExtension const& extension) +{ + extensionUnloaded(extension); +} diff --git a/src/translationmanager.h b/src/translationmanager.h new file mode 100644 index 000000000..ccb0eb08e --- /dev/null +++ b/src/translationmanager.h @@ -0,0 +1,83 @@ +#ifndef TRANSLATIONMANAGER_H +#define TRANSLATIONMANAGER_H + +#include +#include + +#include + +#include "extensionwatcher.h" + +class TranslationManager : public ExtensionWatcher +{ +public: + TranslationManager(QApplication* application); + + // retrieve the list of available translations + // + const auto& translations() const { return m_translations; } + + // load the given translation + // + bool load(std::shared_ptr translation); + bool load(std::string_view language); + + // unload the current translation + // + void unload(); + + // retrieve the current language, if there is one + // + auto currentTranslation() const { return m_currentTranslation; } + +public: // ExtensionWatcher + void extensionLoaded(MOBase::IExtension const& extension) override; + void extensionUnloaded(MOBase::IExtension const& extension) override; + void extensionEnabled(MOBase::IExtension const& extension) override; + void extensionDisabled(MOBase::IExtension const& extension) override; + +private: + // register a translation + // + void + registerTranslation(std::shared_ptr const& translation); + + // [deprecated] add themes for the translations folder + // + [[deprecated]] void addOldFormatTranslations(); + +private: + // TODO: move these two elsewhere + struct string_equal : std::equal_to + { + using is_transparent = std::true_type; + }; + + struct string_hash : std::hash + { + using is_transparent = std::true_type; + }; + + // application + QApplication* m_app; + + // installed translators + std::vector> m_translators; + + // the current translation + std::shared_ptr m_currentTranslation; + + // the list of base translations + std::vector> m_translations; + std::unordered_map, + string_hash, string_equal> + m_translationByLanguage; + + // the list of translations extensions + std::unordered_map>, + string_hash, string_equal> + m_translationExtensions; +}; + +#endif From 81c605061397756c46904d1d469373bbc33d9de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 23 May 2022 12:49:27 +0200 Subject: [PATCH 04/13] Fix after uibase changes. --- src/thememanager.cpp | 46 +++++++++++--------------------------- src/thememanager.h | 9 +------- src/translationmanager.cpp | 6 ++--- src/translationmanager.h | 2 +- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/src/thememanager.cpp b/src/thememanager.cpp index db06008b5..7fe3eb791 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -214,16 +214,10 @@ QString ThemeManager::buildStyleSheet(std::shared_ptr const& theme) QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), theme->stylesheet().parent_path()); - for (auto&& globalExtension : m_globalExtensions) { - stylesheet += "\n" + patchStyleSheet(readWholeFile(globalExtension->stylesheet()), - globalExtension->stylesheet().parent_path()); - } - - auto it = m_themeExtensionsByIdentifier.find(theme->identifier()); - if (it != m_themeExtensionsByIdentifier.end()) { - for (auto&& themExtension : m_themeExtensionsByIdentifier.at(theme->identifier())) { - stylesheet += "\n" + patchStyleSheet(readWholeFile(themExtension->stylesheet()), - themExtension->stylesheet().parent_path()); + for (auto&& themeAddition : m_additions) { + if (themeAddition->isAdditionFor(*theme)) { + stylesheet += "\n" + patchStyleSheet(readWholeFile(themeAddition->stylesheet()), + themeAddition->stylesheet().parent_path()); } } @@ -274,12 +268,10 @@ void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) themeFiles.append(ToQString(absolute(theme->stylesheet()).native())); - for (auto&& themeExtension : m_globalExtensions) { - themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); - } - - for (auto&& themeExtension : m_themeExtensionsByIdentifier[theme->identifier()]) { - themeFiles.append(ToQString(absolute(themeExtension->stylesheet()).native())); + for (auto&& themeAddition : m_additions) { + if (themeAddition->isAdditionFor(*theme)) { + themeFiles.append(ToQString(absolute(themeAddition->stylesheet()).native())); + } } // add all files @@ -292,15 +284,9 @@ void ThemeManager::extensionLoaded(IExtension const& extension) registerTheme(theme); } - for (const auto& themeExtension : extension.themeExtensions()) { - const auto identifier = themeExtension->baseIdentifier(); - - if (identifier.has_value()) { - m_themeExtensionsByIdentifier[*identifier].push_back(themeExtension); - } else { - m_globalExtensions.push_back(themeExtension); - } - } + const auto& themeAdditions = extension.themeAdditions(); + m_additions.insert(m_additions.end(), std::begin(themeAdditions), + std::end(themeAdditions)); } void ThemeManager::extensionUnloaded(IExtension const& extension) @@ -316,14 +302,8 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) } } - for (const auto& themeExtension : extension.themeExtensions()) { - std::erase(m_globalExtensions, themeExtension); - - if (themeExtension->baseIdentifier().has_value() && - m_themeExtensionsByIdentifier.contains(*themeExtension->baseIdentifier())) { - std::erase(m_themeExtensionsByIdentifier[*themeExtension->baseIdentifier()], - themeExtension); - } + for (const auto& themeAddition : extension.themeAdditions()) { + std::erase(m_additions, themeAddition); } } diff --git a/src/thememanager.h b/src/thememanager.h index 69789a4f7..2cf146444 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -107,15 +107,8 @@ class ThemeManager : public ExtensionWatcher string_equal> m_baseThemesByIdentifier; - // the list of theme extensions per theme identifier, for extension that - // have identifiers - std::unordered_map>, - string_hash, string_equal> - m_themeExtensionsByIdentifier; - // theme extensions for all themes - std::vector> m_globalExtensions; + std::vector> m_additions; }; #endif diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp index b10e7ed68..6e805b5a9 100644 --- a/src/translationmanager.cpp +++ b/src/translationmanager.cpp @@ -89,7 +89,7 @@ void TranslationManager::addOldFormatTranslations() continue; } - m_translationExtensions[code].push_back(std::make_shared( + m_translationExtensions[code].push_back(std::make_shared( code, std::vector{iter.fileInfo().filesystemFilePath()})); } } @@ -184,7 +184,7 @@ void TranslationManager::extensionLoaded(IExtension const& extension) registerTranslation(translation); } - for (const auto& translationExtension : extension.translationExtensions()) { + for (const auto& translationExtension : extension.translationAdditions()) { const auto identifier = translationExtension->baseIdentifier(); m_translationExtensions[identifier].push_back(translationExtension); } @@ -203,7 +203,7 @@ void TranslationManager::extensionUnloaded(IExtension const& extension) } } - for (const auto& translationExtension : extension.translationExtensions()) { + for (const auto& translationExtension : extension.translationAdditions()) { if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { std::erase(m_translationExtensions[translationExtension->baseIdentifier()], translationExtension); diff --git a/src/translationmanager.h b/src/translationmanager.h index ccb0eb08e..5b7a3f887 100644 --- a/src/translationmanager.h +++ b/src/translationmanager.h @@ -75,7 +75,7 @@ class TranslationManager : public ExtensionWatcher // the list of translations extensions std::unordered_map>, + std::vector>, string_hash, string_equal> m_translationExtensions; }; From d3a20c32c133198e2d5cfe787f6ec289efe4c7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 23 May 2022 19:29:33 +0200 Subject: [PATCH 05/13] Load themes and translations from extensions/. --- src/CMakeLists.txt | 4 +-- src/createinstancedialog.cpp | 6 ++-- src/extensionmanager.cpp | 48 +++++++++++++++++++++++++++ src/extensionmanager.h | 63 ++++++++++++++++++++++++++++++++++++ src/extensionwatcher.h | 11 ++++--- src/main.cpp | 1 - src/mainwindow.cpp | 2 +- src/moapplication.cpp | 9 ++++++ src/moapplication.h | 3 +- src/modlistview.cpp | 15 +++++---- src/settingsdialogtheme.cpp | 2 +- src/thememanager.cpp | 63 ++++++++++++++++++++++++++++-------- src/thememanager.h | 16 ++++++--- src/translationmanager.cpp | 55 +++++++++++++++++++++++-------- src/translationmanager.h | 16 ++++++--- 15 files changed, 257 insertions(+), 57 deletions(-) create mode 100644 src/extensionmanager.cpp create mode 100644 src/extensionmanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85a4eb6ef..153f63785 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,6 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5" RENAME dlls.manifest) install(DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/stylesheets" "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" DESTINATION bin) @@ -60,7 +59,8 @@ mo2_add_filter(NAME src/extensions GROUPS plugincontainer thememanager translationmanager - extensionconsumer + extensionmanager + extensionwatcher ) mo2_add_filter(NAME src/dialogs GROUPS diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index c5f51e0ec..7231bcf86 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -132,13 +132,13 @@ CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* addShortcutAction(QKeySequence::Find, Actions::Find); - addShortcut(Qt::ALT + Qt::Key_Left, [&] { + addShortcut(Qt::ALT | Qt::Key_Left, [&] { back(); }); - addShortcut(Qt::ALT + Qt::Key_Right, [&] { + addShortcut(Qt::ALT | Qt::Key_Right, [&] { next(false); }); - addShortcut(Qt::CTRL + Qt::Key_Return, [&] { + addShortcut(Qt::CTRL | Qt::Key_Return, [&] { next(); }); diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp new file mode 100644 index 000000000..75211e0e7 --- /dev/null +++ b/src/extensionmanager.cpp @@ -0,0 +1,48 @@ +#include "extensionmanager.h" + +#include + +using namespace MOBase; +namespace fs = std::filesystem; + +void ExtensionManager::loadExtensions(fs::path const& directory) +{ + for (const auto& entry : fs::directory_iterator{directory}) { + if (entry.is_directory()) { + auto extension = ExtensionFactory::loadExtension(entry.path()); + + if (extension) { + // check if we have a duplicate identifier + const auto it = std::find_if( + m_extensions.begin(), m_extensions.end(), [&extension](const auto& value) { + return value->metadata().identifier().compare( + extension->metadata().identifier(), Qt::CaseInsensitive) == 0; + }); + if (it != m_extensions.end()) { + log::error("an extension '{}' already exists", + extension->metadata().identifier()); + continue; + } + + log::debug("extension correctly loaded from '{}': {}, {}", + entry.path().native(), extension->metadata().identifier(), + extension->metadata().type()); + + triggerWatchers(*extension); + m_extensions.push_back(std::move(extension)); + } + } + } +} + +void ExtensionManager::triggerWatchers(const MOBase::IExtension& extension) const +{ + boost::fusion::for_each(m_watchers, [&extension](auto& watchers) { + using KeyType = typename std::decay_t::first_type; + if (auto* p = dynamic_cast(&extension)) { + for (auto& watcher : watchers.second) { + watcher->extensionLoaded(*p); + } + } + }); +} diff --git a/src/extensionmanager.h b/src/extensionmanager.h new file mode 100644 index 000000000..5c662446e --- /dev/null +++ b/src/extensionmanager.h @@ -0,0 +1,63 @@ +#ifndef EXTENSIONMANAGER_H +#define EXTENSIONMANAGER_H + +#include + +#include +#include +#include + +#include + +#include "extensionwatcher.h" + +class ExtensionManager +{ +public: + // load all extensions from the given directory + // + // trigger all currently registered watchers + // + void loadExtensions(std::filesystem::path const& directory); + + // retrieve the list of currently loaded extensions + // + const auto& extensions() const { return m_extensions; } + + // register an object implementing one or many watcher classes + // + template + void registerWatcher(Watcher& watcher) + { + using WatcherType = std::decay_t; + boost::fusion::for_each(m_watchers, [&watcher](auto& watchers) { + using KeyType = + ExtensionWatcher::first_type>; + if constexpr (std::is_base_of_v) { + watchers.second.push_back(&watcher); + } + }); + } + +private: + // trigger appropriate watchers for the given extension + // + void triggerWatchers(const MOBase::IExtension& extension) const; + +private: + std::vector> m_extensions; + + using WatcherMap = boost::fusion::map< + boost::fusion::pair*>>, + boost::fusion::pair*>>, + boost::fusion::pair*>>, + boost::fusion::pair*>>>; + + WatcherMap m_watchers; +}; + +#endif diff --git a/src/extensionwatcher.h b/src/extensionwatcher.h index 54846e746..5dd47259b 100644 --- a/src/extensionwatcher.h +++ b/src/extensionwatcher.h @@ -6,24 +6,27 @@ // an extension watcher is a class that watches extensions get loaded/unloaded, // typically to extract information from theme that are needed by MO2 // +template class ExtensionWatcher { + static_assert(std::is_base_of_v); + public: // called when a new extension is found and loaded // - virtual void extensionLoaded(MOBase::IExtension const& extension) = 0; + virtual void extensionLoaded(ExtensionType const& extension) = 0; // called when a new extension is unloaded // - virtual void extensionUnloaded(MOBase::IExtension const& extension) = 0; + virtual void extensionUnloaded(ExtensionType const& extension) = 0; // called when a new extension is disabled // - virtual void extensionEnabled(MOBase::IExtension const& extension) = 0; + virtual void extensionEnabled(ExtensionType const& extension) = 0; // called when a new extension is disabled // - virtual void extensionDisabled(MOBase::IExtension const& extension) = 0; + virtual void extensionDisabled(ExtensionType const& extension) = 0; virtual ~ExtensionWatcher() {} }; diff --git a/src/main.cpp b/src/main.cpp index b6642394a..02aa2a183 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,6 @@ int run(int argc, char* argv[]) // must be after logging TimeThis tt("main() multiprocess"); - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); MOApplication app(argc, argv); // check if the command line wants to run something right now diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fa45dfc94..64d9a1321 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2798,7 +2798,7 @@ void MainWindow::motdReceived(const QString& motd) // don't show motd after 5 seconds, may be annoying. Hopefully the user's // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { - uint hash = qHash(motd); + unsigned int hash = static_cast(qHash(motd)); if (hash != m_OrganizerCore.settings().motdHash()) { MotDDialog dialog(motd); dialog.exec(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 02fef66d6..b6a01f2c6 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "commandline.h" +#include "extensionmanager.h" #include "instancemanager.h" #include "loglist.h" #include "mainwindow.h" @@ -213,6 +214,14 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) m_themes = std::make_unique(this); m_translations = std::make_unique(this); + m_extensions = std::make_unique(); + m_extensions->registerWatcher(*m_themes); + m_extensions->registerWatcher(*m_translations); + + m_extensions->loadExtensions( + QDir(QCoreApplication::applicationDirPath() + "/extensions") + .filesystemAbsolutePath()); + m_plugins = std::make_unique(m_core.get()); m_plugins->loadPlugins(); diff --git a/src/moapplication.h b/src/moapplication.h index 8f7572240..533337942 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include #include +#include "extensionmanager.h" #include "thememanager.h" #include "translationmanager.h" @@ -33,7 +34,6 @@ class NexusInterface; class OrganizerCore; class PluginContainer; class Settings; -class ThemeManager; namespace MOBase { @@ -80,6 +80,7 @@ class MOApplication : public QApplication std::unique_ptr m_instance; std::unique_ptr m_settings; std::unique_ptr m_nexus; + std::unique_ptr m_extensions; std::unique_ptr m_plugins; std::unique_ptr m_themes; std::unique_ptr m_translations; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 73bbd33df..bd4442bd3 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1136,7 +1136,7 @@ void ModListView::setHighlightedMods(const std::vector& pluginIndi QColor ModListView::markerColor(const QModelIndex& index) const { unsigned int modIndex = index.data(ModList::IndexRole).toInt(); - bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); + bool highlight = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end(); bool archiveOverwrite = m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end(); @@ -1149,7 +1149,7 @@ QColor ModListView::markerColor(const QModelIndex& index) const bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != m_markers.archiveLooseOverwritten.end(); - if (highligth) { + if (highlight) { return Settings::instance().colors().modlistContainsPlugin(); } else if (overwritten || archiveLooseOverwritten) { return Settings::instance().colors().modlistOverwritingLoose(); @@ -1187,8 +1187,8 @@ QColor ModListView::markerColor(const QModelIndex& index) const a += color.alpha(); } - return QColor(r / colors.size(), g / colors.size(), b / colors.size(), - a / colors.size()); + const int ncolors = static_cast(colors.size()); + return QColor(r / ncolors, g / ncolors, b / ncolors, a / ncolors); } return QColor(); @@ -1391,9 +1391,10 @@ void ModListView::dropEvent(QDropEvent* event) { // from Qt source QModelIndex index; - if (viewport()->rect().contains(event->pos())) { - index = indexAt(event->pos()); - if (!index.isValid() || !visualRect(index).contains(event->pos())) + const auto position = event->position().toPoint(); + if (viewport()->rect().contains(position)) { + index = indexAt(position); + if (!index.isValid() || !visualRect(index).contains(position)) index = QModelIndex(); } diff --git a/src/settingsdialogtheme.cpp b/src/settingsdialogtheme.cpp index 57f6936cd..49dd72668 100644 --- a/src/settingsdialogtheme.cpp +++ b/src/settingsdialogtheme.cpp @@ -63,7 +63,7 @@ void ThemeSettingsTab::addStyles(ThemeManager const& manager) separator = false; } - ui->styleBox->addItem(ToQString(theme->identifier()), ToQString(theme->name())); + ui->styleBox->addItem(ToQString(theme->name()), ToQString(theme->identifier())); } } diff --git a/src/thememanager.cpp b/src/thememanager.cpp index 7fe3eb791..cc9dfa008 100644 --- a/src/thememanager.cpp +++ b/src/thememanager.cpp @@ -65,12 +65,15 @@ ThemeManager::ThemeManager(QApplication* application) : m_app{application} // add built-in themes addQtThemes(); - // TODO: remove this - addOldFormatThemes(); - - // find the default theme - m_defaultTheme = - m_baseThemesByIdentifier.at(m_app->style()->objectName().toStdString()); + // find the default theme - this might be a built-in Qt theme, or null, in which case + // we just create a default theme + if (auto it = + m_baseThemesByIdentifier.find(m_app->style()->objectName().toStdString()); + it != m_baseThemesByIdentifier.end()) { + m_defaultTheme = it->second; + } else { + m_defaultTheme = std::make_shared("", "", std::filesystem::path{}); + } // for ease, we set the empty identifier to the default theme m_baseThemesByIdentifier[""] = m_defaultTheme; @@ -278,18 +281,14 @@ void ThemeManager::watchThemeFiles(std::shared_ptr const& theme) m_watcher.addPaths(themeFiles); } -void ThemeManager::extensionLoaded(IExtension const& extension) +void ThemeManager::extensionLoaded(ThemeExtension const& extension) { for (const auto& theme : extension.themes()) { registerTheme(theme); } - - const auto& themeAdditions = extension.themeAdditions(); - m_additions.insert(m_additions.end(), std::begin(themeAdditions), - std::end(themeAdditions)); } -void ThemeManager::extensionUnloaded(IExtension const& extension) +void ThemeManager::extensionUnloaded(ThemeExtension const& extension) { // remove theme, unload if needed for (const auto& theme : extension.themes()) { @@ -301,18 +300,54 @@ void ThemeManager::extensionUnloaded(IExtension const& extension) m_baseThemesByIdentifier.erase(theme->identifier()); } } +} + +void ThemeManager::extensionEnabled(ThemeExtension const& extension) +{ + extensionLoaded(extension); +} +void ThemeManager::extensionDisabled(ThemeExtension const& extension) +{ + extensionUnloaded(extension); +} + +void ThemeManager::extensionLoaded(PluginExtension const& extension) +{ + bool needReload = false; + for (const auto& themeAddition : extension.themeAdditions()) { + m_additions.push_back(themeAddition); + + // check if the addition is for the current theme + needReload = needReload || themeAddition->isAdditionFor(*m_currentTheme); + } + + if (needReload) { + reload(); + } +} + +void ThemeManager::extensionUnloaded(PluginExtension const& extension) +{ + bool needReload = false; for (const auto& themeAddition : extension.themeAdditions()) { std::erase(m_additions, themeAddition); + + // check if the addition is for the current theme + needReload = needReload || themeAddition->isAdditionFor(*m_currentTheme); + } + + if (needReload) { + reload(); } } -void ThemeManager::extensionEnabled(IExtension const& extension) +void ThemeManager::extensionEnabled(PluginExtension const& extension) { extensionLoaded(extension); } -void ThemeManager::extensionDisabled(IExtension const& extension) +void ThemeManager::extensionDisabled(PluginExtension const& extension) { extensionUnloaded(extension); } diff --git a/src/thememanager.h b/src/thememanager.h index 2cf146444..a5211ba2c 100644 --- a/src/thememanager.h +++ b/src/thememanager.h @@ -8,7 +8,8 @@ #include "extensionwatcher.h" -class ThemeManager : public ExtensionWatcher +class ThemeManager : public ExtensionWatcher, + public ExtensionWatcher { public: ThemeManager(QApplication* application); @@ -38,10 +39,15 @@ class ThemeManager : public ExtensionWatcher } public: // ExtensionWatcher - void extensionLoaded(MOBase::IExtension const& extension) override; - void extensionUnloaded(MOBase::IExtension const& extension) override; - void extensionEnabled(MOBase::IExtension const& extension) override; - void extensionDisabled(MOBase::IExtension const& extension) override; + void extensionLoaded(MOBase::ThemeExtension const& extension) override; + void extensionUnloaded(MOBase::ThemeExtension const& extension) override; + void extensionEnabled(MOBase::ThemeExtension const& extension) override; + void extensionDisabled(MOBase::ThemeExtension const& extension) override; + + void extensionLoaded(MOBase::PluginExtension const& extension) override; + void extensionUnloaded(MOBase::PluginExtension const& extension) override; + void extensionEnabled(MOBase::PluginExtension const& extension) override; + void extensionDisabled(MOBase::PluginExtension const& extension) override; private: // reload the current style diff --git a/src/translationmanager.cpp b/src/translationmanager.cpp index 6e805b5a9..339209417 100644 --- a/src/translationmanager.cpp +++ b/src/translationmanager.cpp @@ -14,7 +14,7 @@ using namespace MOBase; TranslationManager::TranslationManager(QApplication* application) : m_app{application} { // TODO: remove this - addOldFormatTranslations(); + // addOldFormatTranslations(); registerTranslation(std::make_shared( "en_US", "English", std::vector{})); @@ -178,19 +178,14 @@ void TranslationManager::registerTranslation( m_translationByLanguage[translation->identifier()] = translation; } -void TranslationManager::extensionLoaded(IExtension const& extension) +void TranslationManager::extensionLoaded(TranslationExtension const& extension) { for (const auto& translation : extension.translations()) { registerTranslation(translation); } - - for (const auto& translationExtension : extension.translationAdditions()) { - const auto identifier = translationExtension->baseIdentifier(); - m_translationExtensions[identifier].push_back(translationExtension); - } } -void TranslationManager::extensionUnloaded(IExtension const& extension) +void TranslationManager::extensionUnloaded(TranslationExtension const& extension) { // remove translation, unload if needed for (const auto& translation : extension.translations()) { @@ -202,21 +197,55 @@ void TranslationManager::extensionUnloaded(IExtension const& extension) m_translationByLanguage.erase(translation->identifier()); } } +} +void TranslationManager::extensionEnabled(TranslationExtension const& extension) +{ + extensionLoaded(extension); +} + +void TranslationManager::extensionDisabled(TranslationExtension const& extension) +{ + extensionUnloaded(extension); +} + +void TranslationManager::extensionLoaded(PluginExtension const& extension) +{ for (const auto& translationExtension : extension.translationAdditions()) { - if (m_translationExtensions.contains(translationExtension->baseIdentifier())) { - std::erase(m_translationExtensions[translationExtension->baseIdentifier()], - translationExtension); + const auto identifier = translationExtension->baseIdentifier(); + m_translationExtensions[identifier].push_back(translationExtension); + } +} + +void TranslationManager::extensionUnloaded(PluginExtension const& extension) +{ + for (const auto& translationAddition : extension.translationAdditions()) { + if (m_translationExtensions.contains(translationAddition->baseIdentifier())) { + std::erase(m_translationExtensions[translationAddition->baseIdentifier()], + translationAddition); + } + + // unload translator if there is one + const auto& files = translationAddition->files(); + const auto it = std::find_if( + m_translators.begin(), m_translators.end(), [&files](const auto& translator) { + const auto path = QFileInfo(translator->filePath()).filesystemFilePath(); + return std::find(files.begin(), files.end(), path) != files.end(); + }); + + if (it != m_translators.end()) { + m_app->removeTranslator(it->get()); + m_translators.erase(it); } } } -void TranslationManager::extensionEnabled(IExtension const& extension) +void TranslationManager::extensionEnabled(PluginExtension const& extension) { extensionLoaded(extension); } -void TranslationManager::extensionDisabled(IExtension const& extension) +void TranslationManager::extensionDisabled(PluginExtension const& extension) { extensionUnloaded(extension); } diff --git a/src/translationmanager.h b/src/translationmanager.h index 5b7a3f887..0afab7a82 100644 --- a/src/translationmanager.h +++ b/src/translationmanager.h @@ -8,7 +8,8 @@ #include "extensionwatcher.h" -class TranslationManager : public ExtensionWatcher +class TranslationManager : public ExtensionWatcher, + public ExtensionWatcher { public: TranslationManager(QApplication* application); @@ -31,10 +32,15 @@ class TranslationManager : public ExtensionWatcher auto currentTranslation() const { return m_currentTranslation; } public: // ExtensionWatcher - void extensionLoaded(MOBase::IExtension const& extension) override; - void extensionUnloaded(MOBase::IExtension const& extension) override; - void extensionEnabled(MOBase::IExtension const& extension) override; - void extensionDisabled(MOBase::IExtension const& extension) override; + void extensionLoaded(MOBase::TranslationExtension const& extension) override; + void extensionUnloaded(MOBase::TranslationExtension const& extension) override; + void extensionEnabled(MOBase::TranslationExtension const& extension) override; + void extensionDisabled(MOBase::TranslationExtension const& extension) override; + + void extensionLoaded(MOBase::PluginExtension const& extension) override; + void extensionUnloaded(MOBase::PluginExtension const& extension) override; + void extensionEnabled(MOBase::PluginExtension const& extension) override; + void extensionDisabled(MOBase::PluginExtension const& extension) override; private: // register a translation From 1fb59d50a654d0b6275b5ed5bdd1c333d65147bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 24 May 2022 17:08:51 +0200 Subject: [PATCH 06/13] Update plugin manager. --- src/CMakeLists.txt | 5 +- src/commandline.cpp | 3 +- src/createinstancedialog.cpp | 4 +- src/createinstancedialog.h | 9 +- src/createinstancedialogpages.cpp | 4 +- src/createinstancedialogpages.h | 2 +- src/datatab.cpp | 6 +- src/datatab.h | 7 +- src/disableproxyplugindialog.cpp | 34 - src/disableproxyplugindialog.h | 44 - src/disableproxyplugindialog.ui | 174 ---- src/downloadmanager.cpp | 4 +- src/downloadmanager.h | 4 +- src/extensionmanager.cpp | 24 + src/extensionmanager.h | 21 +- src/filetree.cpp | 5 +- src/filetree.h | 6 +- src/inibakery.cpp | 52 ++ src/inibakery.h | 24 + src/installationmanager.cpp | 20 +- src/installationmanager.h | 6 +- src/instancemanager.cpp | 27 +- src/instancemanager.h | 14 +- src/instancemanagerdialog.cpp | 11 +- src/instancemanagerdialog.h | 6 +- src/mainwindow.cpp | 55 +- src/mainwindow.h | 6 +- src/moapplication.cpp | 5 +- src/moapplication.h | 6 +- src/modinfo.cpp | 10 +- src/modinfo.h | 4 +- src/modinfodialog.cpp | 10 +- src/modinfodialog.h | 6 +- src/modinfodialogconflicts.cpp | 4 +- src/modinfodialogconflictsmodels.cpp | 4 +- src/modinfodialogconflictsmodels.h | 4 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfwd.h | 4 +- src/modinfodialogimages.cpp | 2 +- src/modinfodialognexus.cpp | 4 +- src/modinfodialogtab.cpp | 6 +- src/modinfodialogtab.h | 10 +- src/modinforegular.cpp | 3 +- src/modlist.cpp | 12 +- src/modlist.h | 8 +- src/modlistbypriorityproxy.cpp | 14 +- src/modlistcontextmenu.cpp | 12 +- src/modlistviewactions.cpp | 4 +- src/nexusinterface.cpp | 12 +- src/nexusinterface.h | 8 +- src/organizercore.cpp | 63 +- src/organizercore.h | 12 +- src/organizerproxy.cpp | 13 +- src/organizerproxy.h | 8 +- src/plugincontainer.cpp | 1249 -------------------------- src/plugincontainer.h | 495 ---------- src/pluginmanager.cpp | 707 +++++++++++++++ src/pluginmanager.h | 345 +++++++ src/previewgenerator.cpp | 12 +- src/previewgenerator.h | 6 +- src/problemsdialog.cpp | 10 +- src/problemsdialog.h | 6 +- src/proxyqt.cpp | 70 ++ src/proxyqt.h | 32 + src/selfupdater.cpp | 6 +- src/selfupdater.h | 4 +- src/settingsdialog.cpp | 11 +- src/settingsdialog.h | 7 +- src/settingsdialogplugins.cpp | 180 +--- src/settingsdialogplugins.h | 5 +- 70 files changed, 1580 insertions(+), 2394 deletions(-) delete mode 100644 src/disableproxyplugindialog.cpp delete mode 100644 src/disableproxyplugindialog.h delete mode 100644 src/disableproxyplugindialog.ui create mode 100644 src/inibakery.cpp create mode 100644 src/inibakery.h delete mode 100644 src/plugincontainer.cpp delete mode 100644 src/plugincontainer.h create mode 100644 src/pluginmanager.cpp create mode 100644 src/pluginmanager.h create mode 100644 src/proxyqt.cpp create mode 100644 src/proxyqt.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 153f63785..4d015aece 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ add_executable(organizer) set_target_properties(organizer PROPERTIES OUTPUT_NAME "ModOrganizer") mo2_configure_executable(organizer WARNINGS OFF - EXTRA_TRANSLATIONS ${MO2_SUPER_PATH}/game_gamebryo/src ${MO2_UIBASE_PATH}/src + TRANSLATIONS ON PRIVATE_DEPENDS uibase githubpp bsatk esptk archive usvfs lootcli boost::program_options Qt::WebEngineWidgets Qt::WebSockets) @@ -44,6 +44,7 @@ mo2_add_filter(NAME src/browser GROUPS mo2_add_filter(NAME src/core GROUPS categories archivefiletree + inibakery installationmanager nexusinterface nxmaccessmanager @@ -61,6 +62,8 @@ mo2_add_filter(NAME src/extensions GROUPS translationmanager extensionmanager extensionwatcher + pluginmanager + proxyqt ) mo2_add_filter(NAME src/dialogs GROUPS diff --git a/src/commandline.cpp b/src/commandline.cpp index 9082d710a..2c5453bc6 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -827,8 +827,9 @@ std::optional ReloadPluginCommand::runPostOrganizer(OrganizerCore& core) QDir(qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())) .absoluteFilePath(name); + // TODO: reload extension, not plugin log::debug("reloading plugin from {}", filepath); - core.pluginContainer().reloadPlugin(filepath); + // core.pluginManager().reloadPlugin(filepath); return {}; } diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 7231bcf86..bb5f1b999 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -95,7 +95,7 @@ class DirectoryCreator std::vector m_created; }; -CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* s, +CreateInstanceDialog::CreateInstanceDialog(const PluginManager& pc, Settings* s, QWidget* parent) : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), m_switching(false), m_singlePage(false) @@ -160,7 +160,7 @@ Ui::CreateInstanceDialog* CreateInstanceDialog::getUI() return ui.get(); } -const PluginContainer& CreateInstanceDialog::pluginContainer() +const PluginManager& CreateInstanceDialog::pluginManager() { return m_pc; } diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index fe3c5646f..f6fe0d9c2 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -16,7 +16,7 @@ namespace cid class Page; } -class PluginContainer; +class PluginManager; class Settings; // this is a wizard for creating a new instance, it is made out of Page objects, @@ -80,13 +80,12 @@ class CreateInstanceDialog : public QDialog Paths paths; }; - CreateInstanceDialog(const PluginContainer& pc, Settings* s, - QWidget* parent = nullptr); + CreateInstanceDialog(const PluginManager& pc, Settings* s, QWidget* parent = nullptr); ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); - const PluginContainer& pluginContainer(); + const PluginManager& pluginManager(); Settings* settings(); // disables all the pages except for the given one, used on startup when some @@ -175,7 +174,7 @@ class CreateInstanceDialog : public QDialog private: std::unique_ptr ui; - const PluginContainer& m_pc; + const PluginManager& m_pc; Settings* m_settings; std::vector> m_pages; QString m_originalNext; diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index c8cec45f2..fe260ca22 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -1,7 +1,7 @@ #include "createinstancedialogpages.h" #include "filesystemutilities.h" #include "instancemanager.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "settings.h" #include "settingsdialognexus.h" #include "shared/appconfig.h" @@ -55,7 +55,7 @@ void PlaceholderLabel::setVisible(bool b) } Page::Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false), + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginManager()), m_skip(false), m_firstActivation(true) {} diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index d9751a526..3f898aadb 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -113,7 +113,7 @@ class Page protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; - const PluginContainer& m_pc; + const PluginManager& m_pc; bool m_skip; bool m_firstActivation; diff --git a/src/datatab.cpp b/src/datatab.cpp index 567fa99d7..2f19405c5 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -14,9 +14,9 @@ using namespace MOBase; // in mainwindow.cpp QString UnmanagedModName(); -DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, +DataTab::DataTab(OrganizerCore& core, PluginManager& pc, QWidget* parent, Ui::MainWindow* mwui) - : m_core(core), m_pluginContainer(pc), + : m_core(core), m_pluginManager(pc), m_parent(parent), ui{mwui->tabWidget, mwui->dataTab, mwui->dataTabRefresh, @@ -25,7 +25,7 @@ DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, mwui->dataTabShowFromArchives}, m_needUpdate(true) { - m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); + m_filetree.reset(new FileTree(core, m_pluginManager, ui.tree)); m_filter.setUseSourceSort(true); m_filter.setFilterColumn(FileTreeModel::FileName); m_filter.setEdit(mwui->dataTabFilter); diff --git a/src/datatab.h b/src/datatab.h index 3eb916811..31a481b52 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -14,7 +14,7 @@ class MainWindow; } class OrganizerCore; class Settings; -class PluginContainer; +class PluginManager; class FileTree; namespace MOShared @@ -27,8 +27,7 @@ class DataTab : public QObject Q_OBJECT; public: - DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, - Ui::MainWindow* ui); + DataTab(OrganizerCore& core, PluginManager& pc, QWidget* parent, Ui::MainWindow* ui); void saveState(Settings& s) const; void restoreState(const Settings& s); @@ -57,7 +56,7 @@ class DataTab : public QObject }; OrganizerCore& m_core; - PluginContainer& m_pluginContainer; + PluginManager& m_pluginManager; QWidget* m_parent; DataTabUi ui; std::unique_ptr m_filetree; diff --git a/src/disableproxyplugindialog.cpp b/src/disableproxyplugindialog.cpp deleted file mode 100644 index a99b0a073..000000000 --- a/src/disableproxyplugindialog.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "disableproxyplugindialog.h" - -#include "ui_disableproxyplugindialog.h" - -using namespace MOBase; - -DisableProxyPluginDialog::DisableProxyPluginDialog( - MOBase::IPlugin* proxyPlugin, std::vector const& required, - QWidget* parent) - : QDialog(parent), ui(new Ui::DisableProxyPluginDialog) -{ - ui->setupUi(this); - - ui->topLabel->setText(QObject::tr("Disabling the '%1' plugin will prevent the " - "following %2 plugin(s) from working:", - "", required.size()) - .arg(proxyPlugin->localizedName()) - .arg(required.size())); - - connect(ui->noBtn, &QPushButton::clicked, this, &QDialog::reject); - connect(ui->yesBtn, &QPushButton::clicked, this, &QDialog::accept); - - ui->requiredPlugins->setSelectionMode(QAbstractItemView::NoSelection); - ui->requiredPlugins->setRowCount(required.size()); - for (int i = 0; i < required.size(); ++i) { - ui->requiredPlugins->setItem(i, 0, - new QTableWidgetItem(required[i]->localizedName())); - ui->requiredPlugins->setItem(i, 1, - new QTableWidgetItem(required[i]->description())); - ui->requiredPlugins->setRowHeight(i, 9); - } - ui->requiredPlugins->verticalHeader()->setVisible(false); - ui->requiredPlugins->sortByColumn(0, Qt::AscendingOrder); -} diff --git a/src/disableproxyplugindialog.h b/src/disableproxyplugindialog.h deleted file mode 100644 index 421697b67..000000000 --- a/src/disableproxyplugindialog.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (C) 2020 Mikaël Capelle. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DISABLEPROXYPLUGINDIALOG_H -#define DISABLEPROXYPLUGINDIALOG_H - -#include - -#include "ipluginproxy.h" - -namespace Ui -{ -class DisableProxyPluginDialog; -} - -class DisableProxyPluginDialog : public QDialog -{ -public: - DisableProxyPluginDialog(MOBase::IPlugin* proxyPlugin, - std::vector const& required, - QWidget* parent = nullptr); - -private slots: - - Ui::DisableProxyPluginDialog* ui; -}; - -#endif diff --git a/src/disableproxyplugindialog.ui b/src/disableproxyplugindialog.ui deleted file mode 100644 index 9f0687879..000000000 --- a/src/disableproxyplugindialog.ui +++ /dev/null @@ -1,174 +0,0 @@ - - - DisableProxyPluginDialog - - - - 0 - 0 - 522 - 417 - - - - Really disable plugin? - - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - - - Qt::PlainText - - - :/MO/gui/remove - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - Disabling the '%1' plugin will prevent the following plugins from working: - - - - - - - - - - 2 - - - true - - - - Plugin - - - - - Description - - - - - - - - Do you want to continue? You will need to restart Mod Organizer for the change to take effect. - - - - - - - - 0 - 0 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 80 - 0 - - - - Yes - - - - :/MO/gui/remove:/MO/gui/remove - - - - - - - - 0 - 0 - - - - - 80 - 0 - - - - No - - - true - - - false - - - - - - - - - - - - - diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6fb88c3b9..576bdac19 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -308,9 +308,9 @@ void DownloadManager::setShowHidden(bool showHidden) refreshList(); } -void DownloadManager::setPluginContainer(PluginContainer* pluginContainer) +void DownloadManager::setPluginManager(PluginManager* pluginManager) { - m_NexusInterface->setPluginContainer(pluginContainer); + m_NexusInterface->setPluginManager(pluginManager); } void DownloadManager::refreshList() diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 305c10e3e..c2ee11b02 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -49,7 +49,7 @@ class IPluginGame; } class NexusInterface; -class PluginContainer; +class PluginManager; class OrganizerCore; /*! @@ -213,7 +213,7 @@ class DownloadManager : public QObject */ void setShowHidden(bool showHidden); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); /** * @brief download from an already open network connection diff --git a/src/extensionmanager.cpp b/src/extensionmanager.cpp index 75211e0e7..e894abe98 100644 --- a/src/extensionmanager.cpp +++ b/src/extensionmanager.cpp @@ -46,3 +46,27 @@ void ExtensionManager::triggerWatchers(const MOBase::IExtension& extension) cons } }); } + +const IExtension* ExtensionManager::extension(QString const& identifier) const +{ + // TODO: use a map for faster lookup + auto it = std::find_if(m_extensions.begin(), m_extensions.end(), + [&identifier](const auto& ext) { + return identifier.compare(ext->metadata().identifier(), + Qt::CaseInsensitive) == 0; + }); + + return it == m_extensions.end() ? nullptr : it->get(); +} + +bool ExtensionManager::isEnabled(MOBase::IExtension const& extension) const +{ + // TODO + return true; +} + +bool ExtensionManager::isEnabled(QString const& identifier) const +{ + const auto* e = extension(identifier); + return e ? isEnabled(*e) : false; +} diff --git a/src/extensionmanager.h b/src/extensionmanager.h index 5c662446e..5ecc05595 100644 --- a/src/extensionmanager.h +++ b/src/extensionmanager.h @@ -13,6 +13,23 @@ class ExtensionManager { +public: + // retrieve the list of currently loaded extensions + // + const auto& extensions() const { return m_extensions; } + + // retrieve the extension with the given identifier, or a null pointer if there is + // none + // + // identifier are case insensitive + // + const MOBase::IExtension* extension(QString const& identifier) const; + + // check if the given extension is enabled + // + bool isEnabled(MOBase::IExtension const& extension) const; + bool isEnabled(QString const& extension) const; + public: // load all extensions from the given directory // @@ -20,10 +37,6 @@ class ExtensionManager // void loadExtensions(std::filesystem::path const& directory); - // retrieve the list of currently loaded extensions - // - const auto& extensions() const { return m_extensions; } - // register an object implementing one or many watcher classes // template diff --git a/src/filetree.cpp b/src/filetree.cpp index f043448f4..d999f20b2 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -3,6 +3,7 @@ #include "filetreeitem.h" #include "filetreemodel.h" #include "organizercore.h" +#include "previewgenerator.h" #include "shared/directoryentry.h" #include "shared/fileentry.h" #include "shared/filesorigin.h" @@ -12,7 +13,7 @@ using namespace MOShared; using namespace MOBase; -bool canPreviewFile(const PluginContainer& pc, const FileEntry& file) +bool canPreviewFile(const PluginManager& pc, const FileEntry& file) { return canPreviewFile(pc, file.isFromArchive(), QString::fromStdWString(file.getName())); @@ -116,7 +117,7 @@ class MenuItem } }; -FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) +FileTree::FileTree(OrganizerCore& core, PluginManager& pc, QTreeView* tree) : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) { m_tree->sortByColumn(0, Qt::AscendingOrder); diff --git a/src/filetree.h b/src/filetree.h index d9597322a..b91cd5181 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -10,7 +10,7 @@ class FileEntry; } class OrganizerCore; -class PluginContainer; +class PluginManager; class FileTreeModel; class FileTreeItem; @@ -19,7 +19,7 @@ class FileTree : public QObject Q_OBJECT; public: - FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree); + FileTree(OrganizerCore& core, PluginManager& pc, QTreeView* tree); FileTreeModel* model(); void refresh(); @@ -52,7 +52,7 @@ class FileTree : public QObject private: OrganizerCore& m_core; - PluginContainer& m_plugins; + PluginManager& m_plugins; QTreeView* m_tree; FileTreeModel* m_model; diff --git a/src/inibakery.cpp b/src/inibakery.cpp new file mode 100644 index 000000000..268692c4b --- /dev/null +++ b/src/inibakery.cpp @@ -0,0 +1,52 @@ +#include "inibakery.h" + +#include +#include + +#include "organizercore.h" + +using namespace MOBase; + +IniBakery::IniBakery(OrganizerCore& core) : m_core{core} +{ + m_core.onAboutToRun([this](auto&&) { + return prepareIni(); + }); +} + +bool IniBakery::prepareIni() const +{ + const IPluginGame* game = m_core.managedGame(); + + LocalSavegames* savegames = game->feature(); + if (savegames != nullptr) { + savegames->prepareProfile(m_core.currentProfile()); + } + + BSAInvalidation* invalidation = game->feature(); + if (invalidation != nullptr) { + invalidation->prepareProfile(m_core.currentProfile()); + } + + return true; +} + +MappingType IniBakery::mappings() const +{ + MappingType result; + + const auto iniFileNames = m_core.managedGame()->iniFiles(); + const IPluginGame* game = m_core.managedGame(); + + IProfile* profile = m_core.currentProfile(); + + if (profile->localSettingsEnabled()) { + for (const QString& iniFile : iniFileNames) { + result.push_back({m_core.profilePath() + "/" + QFileInfo(iniFile).fileName(), + game->documentsDirectory().absoluteFilePath(iniFile), false, + false}); + } + } + + return result; +} diff --git a/src/inibakery.h b/src/inibakery.h new file mode 100644 index 000000000..2707085aa --- /dev/null +++ b/src/inibakery.h @@ -0,0 +1,24 @@ +#ifndef INIBAKERY_H +#define INIBAKERY_H + +#include + +#include + +class OrganizerCore; + +class IniBakery +{ +public: + IniBakery(OrganizerCore& core); + + MappingType mappings() const; + +private: + bool prepareIni() const; + +private: + OrganizerCore& m_core; +}; + +#endif diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 8c2ce78d3..da1ee1bf8 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -116,9 +116,9 @@ void InstallationManager::setParentWidget(QWidget* widget) m_ParentWidget = widget; } -void InstallationManager::setPluginContainer(const PluginContainer* pluginContainer) +void InstallationManager::setPluginManager(const PluginManager* pluginManager) { - m_PluginContainer = pluginContainer; + m_PluginManager = pluginManager; } void InstallationManager::queryPassword() @@ -728,7 +728,7 @@ InstallationResult InstallationManager::install(const QString& fileName, std::shared_ptr filesTree = archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; - auto installers = m_PluginContainer->plugins(); + auto installers = m_PluginManager->plugins(); std::sort(installers.begin(), installers.end(), [](IPluginInstaller* lhs, IPluginInstaller* rhs) { @@ -740,7 +740,7 @@ InstallationResult InstallationManager::install(const QString& fileName, for (IPluginInstaller* installer : installers) { // don't use inactive installers (installer can't be null here but vc static code // analysis thinks it could) - if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) { + if ((installer == nullptr) || !m_PluginManager->isEnabled(installer)) { continue; } @@ -887,8 +887,8 @@ QStringList InstallationManager::getSupportedExtensions() const { std::set supportedExtensions( {"zip", "rar", "7z", "fomod", "001"}); - for (auto* installer : m_PluginContainer->plugins()) { - if (m_PluginContainer->isEnabled(installer)) { + for (auto* installer : m_PluginManager->plugins()) { + if (m_PluginManager->isEnabled(installer)) { if (auto* installerCustom = dynamic_cast(installer)) { std::set extensions = installerCustom->supportedExtensions(); supportedExtensions.insert(extensions.begin(), extensions.end()); @@ -902,9 +902,9 @@ void InstallationManager::notifyInstallationStart(QString const& archive, bool reinstallation, ModInfo::Ptr currentMod) { - auto& installers = m_PluginContainer->plugins(); + auto& installers = m_PluginManager->plugins(); for (auto* installer : installers) { - if (m_PluginContainer->isEnabled(installer)) { + if (m_PluginManager->isEnabled(installer)) { installer->onInstallationStart(archive, reinstallation, currentMod.get()); } } @@ -913,9 +913,9 @@ void InstallationManager::notifyInstallationStart(QString const& archive, void InstallationManager::notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod) { - auto& installers = m_PluginContainer->plugins(); + auto& installers = m_PluginManager->plugins(); for (auto* installer : installers) { - if (m_PluginContainer->isEnabled(installer)) { + if (m_PluginManager->isEnabled(installer)) { installer->onInstallationEnd(result.result(), newMod.get()); } } diff --git a/src/installationmanager.h b/src/installationmanager.h index e8e975152..cdfbf5b25 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -35,7 +35,7 @@ along with Mod Organizer. If not, see . #include #include "modinfo.h" -#include "plugincontainer.h" +#include "pluginmanager.h" // contains installation result from the manager, internal class // for MO2 that is not forwarded to plugin @@ -129,7 +129,7 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager /** * */ - void setPluginContainer(const PluginContainer* pluginContainer); + void setPluginManager(const PluginManager* pluginManager); /** * @brief update the directory where downloads are stored @@ -332,7 +332,7 @@ private slots: private: // The plugin container, mostly to check if installer are enabled or not. - const PluginContainer* m_PluginContainer; + const PluginManager* m_PluginManager; bool m_IsRunning; diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index df4b0078d..f2f04e96d 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -20,10 +20,11 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "createinstancedialog.h" #include "createinstancedialogpages.h" +#include "extensionmanager.h" #include "filesystemutilities.h" #include "instancemanagerdialog.h" #include "nexusinterface.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "selectiondialog.h" #include "settings.h" #include "shared/appconfig.h" @@ -146,7 +147,7 @@ bool Instance::readFromIni() return true; } -Instance::SetupResults Instance::setup(PluginContainer& plugins) +Instance::SetupResults Instance::setup(PluginManager& plugins) { // read initial values from the ini if (!readFromIni()) { @@ -208,7 +209,7 @@ void Instance::setVariant(const QString& name) m_gameVariant = name; } -Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) +Instance::SetupResults Instance::getGamePlugin(PluginManager& plugins) { if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) { // normal case: both the name and dir are in the ini @@ -607,15 +608,15 @@ bool InstanceManager::allowedToChangeInstance() const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(const QString& instanceDir, - PluginContainer& plugins) const + PluginManager& plugins) const { return const_cast( - gamePluginForDirectory(instanceDir, const_cast(plugins))); + gamePluginForDirectory(instanceDir, const_cast(plugins))); } const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(const QString& instanceDir, - const PluginContainer& plugins) const + const PluginManager& plugins) const { const QString ini = iniPath(instanceDir); @@ -699,9 +700,13 @@ std::unique_ptr selectInstance() auto& m = InstanceManager::singleton(); // since there is no instance currently active, load plugins with a null - // OrganizerCore; see PluginContainer::initPlugin() + // OrganizerCore; see PluginManager::initPlugin() NexusInterface ni(nullptr); - PluginContainer pc(nullptr); + ExtensionManager ec; + ec.loadExtensions(QDir(QCoreApplication::applicationDirPath() + "/extensions") + .filesystemAbsolutePath()); + + PluginManager pc(ec, nullptr); pc.loadPlugins(); if (m.hasAnyInstances()) { @@ -740,7 +745,7 @@ std::unique_ptr selectInstance() // this is used below in setupInstance() when the game directory is gone or // no plugins can recognize it // -SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) +SetupInstanceResults selectGame(Instance& instance, PluginManager& pc) { CreateInstanceDialog dlg(pc, nullptr); @@ -772,7 +777,7 @@ SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) // or when a new variant has become supported by the plugin for a game the // user already has an instance for // -SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) +SetupInstanceResults selectVariant(Instance& instance, PluginManager& pc) { CreateInstanceDialog dlg(pc, nullptr); @@ -798,7 +803,7 @@ SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) return SetupInstanceResults::TryAgain; } -SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) +SetupInstanceResults setupInstance(Instance& instance, PluginManager& pc) { // set up the instance const auto setupResult = instance.setup(pc); diff --git a/src/instancemanager.h b/src/instancemanager.h index 176033e81..efed37c93 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -10,7 +10,7 @@ class IPluginGame; } class Settings; -class PluginContainer; +class PluginManager; // represents an instance, either global or portable // @@ -110,7 +110,7 @@ class Instance // setup() tries to recover from some errors, but can fail for a variety of // reasons, see SetupResults // - SetupResults setup(PluginContainer& plugins); + SetupResults setup(PluginManager& plugins); // overrides the game name and directory // @@ -198,7 +198,7 @@ class Instance // figures out the game plugin for this instance // - SetupResults getGamePlugin(PluginContainer& plugins); + SetupResults getGamePlugin(PluginManager& plugins); // figures out the profile name for this instance // @@ -243,11 +243,11 @@ class InstanceManager // // returns null if all of this fails // - const MOBase::IPluginGame* - gamePluginForDirectory(const QString& dir, const PluginContainer& plugins) const; + const MOBase::IPluginGame* gamePluginForDirectory(const QString& dir, + const PluginManager& plugins) const; MOBase::IPluginGame* gamePluginForDirectory(const QString& dir, - PluginContainer& plugins) const; + PluginManager& plugins) const; // clears the instance name from the registry; on restart, this will make MO // either select the portable instance if it exists, or display the instance @@ -359,6 +359,6 @@ std::unique_ptr selectInstance(); // // - if the instance has been set up correctly, returns Okay // -SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc); +SetupInstanceResults setupInstance(Instance& instance, PluginManager& pc); #endif // MODORGANIZER_INSTANCEMANAGER_INCLUDED diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index b5139e908..0d34a9a0f 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -17,7 +17,7 @@ using namespace MOBase; // returns the icon for the given instance or an empty 32x32 icon if the game // plugin couldn't be found // -QIcon instanceIcon(PluginContainer& pc, const Instance& i) +QIcon instanceIcon(PluginManager& pc, const Instance& i) { auto* game = InstanceManager::singleton().gamePluginForDirectory(i.directory(), pc); @@ -146,7 +146,7 @@ QString getInstanceName(QWidget* parent, const QString& title, const QString& mo InstanceManagerDialog::~InstanceManagerDialog() = default; -InstanceManagerDialog::InstanceManagerDialog(PluginContainer& pc, QWidget* parent) +InstanceManagerDialog::InstanceManagerDialog(PluginManager& pc, QWidget* parent) : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), m_model(nullptr), m_restartOnSelect(true) { @@ -313,7 +313,7 @@ void InstanceManagerDialog::select(std::size_t i) fillData(*ii); ui->list->selectionModel()->select( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + m_filter.mapFromSource(m_filter.sourceModel()->index(static_cast(i), 0)), QItemSelectionModel::ClearAndSelect); } else { clearData(); @@ -341,7 +341,8 @@ void InstanceManagerDialog::selectActiveInstance() if (m_instances[i]->displayName() == active->displayName()) { select(i); - ui->list->scrollTo(m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + ui->list->scrollTo(m_filter.mapFromSource( + m_filter.sourceModel()->index(static_cast(i), 0))); return; } @@ -455,7 +456,7 @@ void InstanceManagerDialog::rename() auto newInstance = std::make_unique(dest, false); i = newInstance.get(); - m_model->item(selIndex)->setText(newName); + m_model->item(static_cast(selIndex))->setText(newName); m_instances[selIndex] = std::move(newInstance); fillData(*i); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 884beaa53..3a50d61aa 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -10,7 +10,7 @@ class InstanceManagerDialog; }; class Instance; -class PluginContainer; +class PluginManager; // a dialog to manage existing instances // @@ -19,7 +19,7 @@ class InstanceManagerDialog : public QDialog Q_OBJECT public: - explicit InstanceManagerDialog(PluginContainer& pc, QWidget* parent = nullptr); + explicit InstanceManagerDialog(PluginManager& pc, QWidget* parent = nullptr); ~InstanceManagerDialog(); @@ -90,7 +90,7 @@ class InstanceManagerDialog : public QDialog static const std::size_t NoSelection = -1; std::unique_ptr ui; - PluginContainer& m_pc; + PluginManager& m_pc; std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 64d9a1321..fe19895d4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,13 +230,13 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& themeManager, + PluginManager& pluginManager, ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), m_OldProfileIndex(-1), m_OldExecutableIndex(-1), m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), - m_PluginContainer(pluginContainer), m_ThemeManager(themeManager), + m_PluginManager(pluginManager), m_ThemeManager(themeManager), m_TranslationManager(translationManager), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), @@ -309,7 +309,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, settings.geometry().restoreState(ui->espList->header()); // data tab - m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginContainer, this, ui)); + m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginManager, this, ui)); m_DataTab->restoreState(settings); connect(m_DataTab.get(), &DataTab::executablesChanged, [&] { @@ -378,8 +378,9 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, "You will probably have to use a third-party tool.")); } - connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, - SLOT(scheduleCheckForProblems())); + connect(&m_PluginManager, &PluginManager::diagnosePluginInvalidated, [this] { + scheduleCheckForProblems(); + }); connect(&m_OrganizerCore, &OrganizerCore::directoryStructureReady, this, &MainWindow::onDirectoryStructureChanged); @@ -435,21 +436,21 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { updateToolMenu(); }); - connect(&m_PluginContainer, &PluginContainer::pluginEnabled, this, + connect(&m_PluginManager, &PluginManager::pluginEnabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { + if (m_PluginManager.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginDisabled, this, + connect(&m_PluginManager, &PluginManager::pluginDisabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { + if (m_PluginManager.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginRegistered, this, + connect(&m_PluginManager, &PluginManager::pluginRegistered, this, &MainWindow::onPluginRegistrationChanged); - connect(&m_PluginContainer, &PluginContainer::pluginUnregistered, this, + connect(&m_PluginManager, &PluginManager::pluginUnregistered, this, &MainWindow::onPluginRegistrationChanged); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, @@ -1031,9 +1032,9 @@ void MainWindow::checkForProblemsImpl() m_ProblemsCheckRequired = false; TimeThis tt("MainWindow::checkForProblemsImpl()"); size_t numProblems = 0; - for (QObject* pluginObj : m_PluginContainer.plugins()) { + for (QObject* pluginObj : m_PluginManager.plugins()) { IPlugin* plugin = qobject_cast(pluginObj); - if (plugin == nullptr || m_PluginContainer.isEnabled(plugin)) { + if (plugin == nullptr || m_PluginManager.isEnabled(plugin)) { IPluginDiagnose* diagnose = qobject_cast(pluginObj); if (diagnose != nullptr) numProblems += diagnose->activeProblems().size(); @@ -1275,7 +1276,7 @@ void MainWindow::showEvent(QShowEvent* event) updateProblemsButton(); // notify plugins that the MO2 is ready - m_PluginContainer.startPlugins(this); + m_PluginManager.startPlugins(this); // forces a log list refresh to display startup logs // @@ -1442,7 +1443,7 @@ void MainWindow::updateToolMenu() // Clear the menu: ui->actionTool->menu()->clear(); - std::vector toolPlugins = m_PluginContainer.plugins(); + std::vector toolPlugins = m_PluginManager.plugins(); // Sort the plugins by display name std::sort(std::begin(toolPlugins), std::end(toolPlugins), @@ -1453,7 +1454,7 @@ void MainWindow::updateToolMenu() // Remove disabled plugins: toolPlugins.erase(std::remove_if(std::begin(toolPlugins), std::end(toolPlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); + return !m_PluginManager.isEnabled(tool); }), toolPlugins.end()); @@ -1545,7 +1546,7 @@ void MainWindow::updateModPageMenu() // Determine the loaded mod page plugins std::vector modPagePlugins = - m_PluginContainer.plugins(); + m_PluginManager.plugins(); // Sort the plugins by display name std::sort(std::begin(modPagePlugins), std::end(modPagePlugins), @@ -1557,7 +1558,7 @@ void MainWindow::updateModPageMenu() modPagePlugins.erase(std::remove_if(std::begin(modPagePlugins), std::end(modPagePlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); + return !m_PluginManager.isEnabled(tool); }), modPagePlugins.end()); @@ -2641,8 +2642,8 @@ void MainWindow::on_actionSettings_triggered() const bool oldCheckForUpdates = settings.checkForUpdates(); const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - SettingsDialog dialog(&m_PluginContainer, m_ThemeManager, m_TranslationManager, - settings, this); + SettingsDialog dialog(m_PluginManager, m_ThemeManager, m_TranslationManager, settings, + this); dialog.exec(); auto e = dialog.exitNeeded(); @@ -2967,7 +2968,7 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int) { QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3023,7 +3024,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD QList fileUpdates = resultInfo["file_updates"].toList(); QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3166,7 +3167,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD QString gameNameReal; bool foundUpdate = false; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3266,7 +3267,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int) { QMap gameNames; - for (auto game : m_PluginContainer.plugins()) { + for (auto game : m_PluginManager.plugins()) { gameNames[game->gameNexusName()] = game->gameShortName(); } @@ -3338,7 +3339,7 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in // update last checked timestamp on orphaned mods as well to avoid repeating // requests QString gameNameReal; - for (IPluginGame* game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginManager.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3484,7 +3485,7 @@ void MainWindow::on_actionNotifications_triggered() future.waitForFinished(); - ProblemsDialog problems(m_PluginContainer, this); + ProblemsDialog problems(m_PluginManager, this); problems.exec(); scheduleCheckForProblems(); @@ -3492,7 +3493,7 @@ void MainWindow::on_actionNotifications_triggered() void MainWindow::on_actionChange_Game_triggered() { - InstanceManagerDialog dlg(m_PluginContainer, this); + InstanceManagerDialog dlg(m_PluginManager, this); dlg.exec(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index a6135b8bd..071ec67fd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -29,7 +29,7 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "modlistbypriorityproxy.h" #include "modlistsortproxy.h" -#include "plugincontainer.h" //class PluginContainer; +#include "plugincontainer.h" //class PluginManager; #include "shared/fileregisterfwd.h" #include "thememanager.h" #include "translationmanager.h" @@ -127,7 +127,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& themeManager, + PluginManager& pluginManager, ThemeManager& themeManager, TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); @@ -295,7 +295,7 @@ private slots: QTime m_StartTime; OrganizerCore& m_OrganizerCore; - PluginContainer& m_PluginContainer; + PluginManager& m_PluginManager; ThemeManager& m_ThemeManager; TranslationManager& m_TranslationManager; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index b6a01f2c6..189c29970 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "organizercore.h" +#include "pluginmanager.h" #include "sanitychecks.h" #include "settings.h" #include "shared/appconfig.h" @@ -222,7 +223,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) QDir(QCoreApplication::applicationDirPath() + "/extensions") .filesystemAbsolutePath()); - m_plugins = std::make_unique(m_core.get()); + m_plugins = std::make_unique(*m_extensions, m_core.get()); m_plugins->loadPlugins(); // instance @@ -420,7 +421,7 @@ std::unique_ptr MOApplication::getCurrentInstance(bool forceSelect) } std::optional MOApplication::setupInstanceLoop(Instance& currentInstance, - PluginContainer& pc) + PluginManager& pc) { for (;;) { const auto setupResult = setupInstance(currentInstance, pc); diff --git a/src/moapplication.h b/src/moapplication.h index 533337942..55a8b943f 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -32,7 +32,7 @@ class Instance; class MOMultiProcess; class NexusInterface; class OrganizerCore; -class PluginContainer; +class PluginManager; class Settings; namespace MOBase @@ -81,14 +81,14 @@ class MOApplication : public QApplication std::unique_ptr m_settings; std::unique_ptr m_nexus; std::unique_ptr m_extensions; - std::unique_ptr m_plugins; + std::unique_ptr m_plugins; std::unique_ptr m_themes; std::unique_ptr m_translations; std::unique_ptr m_core; void externalMessage(const QString& message); std::unique_ptr getCurrentInstance(bool forceSelect); - std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); + std::optional setupInstanceLoop(Instance& currentInstance, PluginManager& pc); void purgeOldFiles(); }; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1d30ac952..fde852fd8 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -94,7 +94,7 @@ ModInfo::Ptr ModInfo::createFrom(const QDir& dir, OrganizerCore& core) } else { result = ModInfo::Ptr(new ModInfoRegular(dir, core)); } - result->m_Index = s_Collection.size(); + result->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(result); return result; } @@ -106,7 +106,7 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString& modName, const QString& es QMutexLocker locker(&s_Mutex); ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core)); - result->m_Index = s_Collection.size(); + result->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(result); return result; } @@ -115,7 +115,7 @@ ModInfo::Ptr ModInfo::createFromOverwrite(OrganizerCore& core) { QMutexLocker locker(&s_Mutex); ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(core)); - overwrite->m_Index = s_Collection.size(); + overwrite->m_Index = static_cast(s_Collection.size()); s_Collection.push_back(overwrite); return overwrite; } @@ -295,7 +295,7 @@ void ModInfo::updateIndices() ModInfo::ModInfo(OrganizerCore& core) : m_PrimaryCategory(-1), m_Core(core) {} -bool ModInfo::checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver) +bool ModInfo::checkAllForUpdate(PluginManager* pluginManager, QObject* receiver) { bool updatesAvailable = true; @@ -314,7 +314,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer* pluginContainer, QObject* recei // Detect invalid source games for (auto itr = games.begin(); itr != games.end();) { - auto gamePlugins = pluginContainer->plugins(); + auto gamePlugins = pluginManager->plugins(); IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { if (plugin != nullptr && diff --git a/src/modinfo.h b/src/modinfo.h index 9895d44c6..30fa0ec23 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -25,7 +25,7 @@ along with Mod Organizer. If not, see . #include "versioninfo.h" class OrganizerCore; -class PluginContainer; +class PluginManager; class QDir; class QDateTime; @@ -215,7 +215,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @return true if any mods are checked for update. */ - static bool checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver); + static bool checkAllForUpdate(PluginManager* pluginManager, QObject* receiver); /** * diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d0e8d1ad2..0e7787e67 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -39,7 +39,7 @@ namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; -bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, +bool canPreviewFile(const PluginManager& pluginManager, bool isArchive, const QString& filename) { if (isArchive) { @@ -47,7 +47,7 @@ bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, } const auto ext = QFileInfo(filename).suffix().toLower(); - return pluginContainer.previewGenerator().previewSupported(ext); + return pluginManager.previewGenerator().previewSupported(ext); } bool isExecutableFilename(const QString& filename) @@ -168,11 +168,11 @@ bool ModInfoDialog::TabInfo::isVisible() const return (realPos != -1); } -ModInfoDialog::ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, +ModInfoDialog::ModInfoDialog(OrganizerCore& core, PluginManager& plugins, ModInfo::Ptr mod, ModListView* modListView, QWidget* parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_core(core), - m_plugin(plugin), m_modListView(modListView), m_initialTab(ModInfoTabIDs::None), + m_plugins(plugins), m_modListView(modListView), m_initialTab(ModInfoTabIDs::None), m_arrangingTabs(false) { ui->setupUi(this); @@ -222,7 +222,7 @@ template std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); + d.m_core, d.m_plugins, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } void ModInfoDialog::createTabs() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index e894efb0c..cb74c6f02 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -34,7 +34,7 @@ namespace MOShared class FilesOrigin; } -class PluginContainer; +class PluginManager; class OrganizerCore; class Settings; class ModInfoDialogTab; @@ -56,7 +56,7 @@ class ModInfoDialog : public MOBase::TutorableDialog ModInfoTabIDs index); public: - ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, ModInfo::Ptr mod, + ModInfoDialog(OrganizerCore& core, PluginManager& plugins, ModInfo::Ptr mod, ModListView* view, QWidget* parent = nullptr); ~ModInfoDialog(); @@ -123,7 +123,7 @@ class ModInfoDialog : public MOBase::TutorableDialog std::unique_ptr ui; OrganizerCore& m_core; - PluginContainer& m_plugin; + PluginManager& m_plugins; ModListView* m_modListView; ModInfo::Ptr m_mod; std::vector m_tabs; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 715214b33..e47f94147 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -221,7 +221,7 @@ void ConflictsTab::activateItems(QTreeView* tree) forEachInSelection(tree, [&](const ConflictItem* item) { const auto path = item->fileName(); - if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { + if (tryPreview && canPreviewFile(plugins(), item->isArchive(), path)) { previewItem(item); } else { openItem(item, false); @@ -424,7 +424,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableUnhide = item->canUnhide(); enableRun = item->canRun(); enableOpen = item->canOpen(); - enablePreview = item->canPreview(plugin()); + enablePreview = item->canPreview(plugins()); enableExplore = item->canExplore(); enableGoto = item->hasAlts(); } else { diff --git a/src/modinfodialogconflictsmodels.cpp b/src/modinfodialogconflictsmodels.cpp index a1806e61f..d2a6b18fe 100644 --- a/src/modinfodialogconflictsmodels.cpp +++ b/src/modinfodialogconflictsmodels.cpp @@ -73,9 +73,9 @@ bool ConflictItem::canOpen() const return canOpenFile(isArchive(), fileName()); } -bool ConflictItem::canPreview(PluginContainer& pluginContainer) const +bool ConflictItem::canPreview(PluginManager& pluginManager) const { - return canPreviewFile(pluginContainer, isArchive(), fileName()); + return canPreviewFile(pluginManager, isArchive(), fileName()); } bool ConflictItem::canExplore() const diff --git a/src/modinfodialogconflictsmodels.h b/src/modinfodialogconflictsmodels.h index 263723285..851c440e3 100644 --- a/src/modinfodialogconflictsmodels.h +++ b/src/modinfodialogconflictsmodels.h @@ -1,6 +1,6 @@ #include "shared/fileentry.h" -class PluginContainer; +class PluginManager; class ConflictItem { @@ -25,7 +25,7 @@ class ConflictItem bool canUnhide() const; bool canRun() const; bool canOpen() const; - bool canPreview(PluginContainer& pluginContainer) const; + bool canPreview(PluginManager& pluginManager) const; bool canExplore() const; private: diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 140b813d3..76bb28a22 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -178,7 +178,7 @@ void FileTreeTab::onActivated() const auto path = m_fs->filePath(selection); const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); - if (tryPreview && canPreviewFile(plugin(), false, path)) { + if (tryPreview && canPreviewFile(plugins(), false, path)) { onPreview(); } else { onOpen(); @@ -446,7 +446,7 @@ void FileTreeTab::onContextMenu(const QPoint& pos) } } - enablePreview = canPreviewFile(plugin(), false, fileName); + enablePreview = canPreviewFile(plugins(), false, fileName); enableExplore = canExploreFile(false, fileName); enableHide = canHideFile(false, fileName); enableUnhide = canUnhideFile(false, fileName); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 086263236..ee3cac219 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -21,9 +21,9 @@ enum class ModInfoTabIDs Filetree }; -class PluginContainer; +class PluginManager; -bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, +bool canPreviewFile(const PluginManager& pluginManager, bool isArchive, const QString& filename); bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index e48a34d33..dc5f1e410 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -258,7 +258,7 @@ void ImagesTab::select(std::size_t i, Visibility v) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); - if (plugin().previewGenerator().previewSupported( + if (plugins().previewGenerator().previewSupported( QFileInfo(f->path()).suffix().toLower())) ui->previewPluginButton->setEnabled(true); else diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 566361458..42b891465 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -96,7 +96,7 @@ void NexusTab::update() if (core().managedGame()->validShortNames().size() == 0) { ui->sourceGame->setDisabled(true); } else { - for (auto game : plugin().plugins()) { + for (auto game : plugins().plugins()) { for (QString gameName : core().managedGame()->validShortNames()) { if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { ui->sourceGame->addItem(game->gameName(), game->gameShortName()); @@ -339,7 +339,7 @@ void NexusTab::onSourceGameChanged() return; } - for (auto game : plugin().plugins()) { + for (auto game : plugins().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { mod().setGameName(game->gameShortName()); mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index c443a389b..96e901668 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,7 +5,7 @@ #include "ui_modinfodialog.h" ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) - : ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + : ui(cx.ui), m_core(cx.core), m_plugins(cx.plugins), m_parent(cx.parent), m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) {} @@ -112,9 +112,9 @@ OrganizerCore& ModInfoDialogTab::core() return m_core; } -PluginContainer& ModInfoDialogTab::plugin() +PluginManager& ModInfoDialogTab::plugins() { - return m_plugin; + return m_plugins; } QWidget* ModInfoDialogTab::parentWidget() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index dff1732d0..40706c022 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -21,17 +21,17 @@ class OrganizerCore; struct ModInfoDialogTabContext { OrganizerCore& core; - PluginContainer& plugin; + PluginManager& plugins; QWidget* parent; Ui::ModInfoDialog* ui; ModInfoTabIDs id; ModInfoPtr mod; MOShared::FilesOrigin* origin; - ModInfoDialogTabContext(OrganizerCore& core, PluginContainer& plugin, QWidget* parent, + ModInfoDialogTabContext(OrganizerCore& core, PluginManager& plugins, QWidget* parent, Ui::ModInfoDialog* ui, ModInfoTabIDs id, ModInfoPtr mod, MOShared::FilesOrigin* origin) - : core(core), plugin(plugin), parent(parent), ui(ui), id(id), mod(mod), + : core(core), plugins(plugins), parent(parent), ui(ui), id(id), mod(mod), origin(origin) {} }; @@ -227,7 +227,7 @@ class ModInfoDialogTab : public QObject ModInfoDialogTab(ModInfoDialogTabContext cx); OrganizerCore& core(); - PluginContainer& plugin(); + PluginManager& plugins(); QWidget* parentWidget(); // emits originModified @@ -254,7 +254,7 @@ class ModInfoDialogTab : public QObject OrganizerCore& m_core; // plugin - PluginContainer& m_plugin; + PluginManager& m_plugins; // current mod, never null ModInfoPtr m_mod; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 4c1004e6b..c8a864a5a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -34,8 +34,7 @@ ModInfoRegular::ModInfoRegular(const QDir& path, OrganizerCore& core) m_GameName(core.managedGame()->gameShortName()), m_IsAlternate(false), m_Converted(false), m_Validated(false), m_MetaInfoChanged(false), m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN), - m_TrackedState(TrackedState::TRACKED_UNKNOWN), - m_NexusBridge(&core.pluginContainer()) + m_TrackedState(TrackedState::TRACKED_UNKNOWN), m_NexusBridge() { m_CreationTime = QFileInfo(path.absolutePath()).birthTime(); // read out the meta-file for information diff --git a/src/modlist.cpp b/src/modlist.cpp index 7a9513695..e93b0d919 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -60,10 +60,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; -ModList::ModList(PluginContainer* pluginContainer, OrganizerCore* organizer) +ModList::ModList(PluginManager* pluginManager, OrganizerCore* organizer) : QAbstractItemModel(organizer), m_Organizer(organizer), m_Profile(nullptr), m_NexusInterface(nullptr), m_Modified(false), m_InNotifyChange(false), - m_FontMetrics(QFont()), m_PluginContainer(pluginContainer) + m_FontMetrics(QFont()), m_PluginManager(pluginManager) { m_LastCheck.start(); } @@ -218,8 +218,8 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const return QVariant(); } } else if (column == COL_GAME) { - if (m_PluginContainer != nullptr) { - for (auto game : m_PluginContainer->plugins()) { + if (m_PluginManager != nullptr) { + for (auto game : m_PluginManager->plugins()) { if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == 0) return game->gameName(); @@ -726,9 +726,9 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) emit modPrioritiesChanged({index(sourceIndex, 0)}); } -void ModList::setPluginContainer(PluginContainer* pluginContianer) +void ModList::setPluginManager(PluginManager* pluginContianer) { - m_PluginContainer = pluginContianer; + m_PluginManager = pluginContianer; } bool ModList::modInfoAboutToChange(ModInfo::Ptr info) diff --git a/src/modlist.h b/src/modlist.h index 112070e12..cf05351b6 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -41,7 +41,7 @@ along with Mod Organizer. If not, see . #include class QSortFilterProxyModel; -class PluginContainer; +class PluginManager; class OrganizerCore; class ModListDropInfo; @@ -106,7 +106,7 @@ class ModList : public QAbstractItemModel * @todo ensure this view works without a profile set, otherwise there are *intransparent dependencies on the initialisation order **/ - ModList(PluginContainer* pluginContainer, OrganizerCore* parent); + ModList(PluginManager* pluginManager, OrganizerCore* parent); ~ModList(); @@ -136,7 +136,7 @@ class ModList : public QAbstractItemModel void changeModPriority(int sourceIndex, int newPriority); void changeModPriority(std::vector sourceIndices, int newPriority); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginContainer); bool modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); @@ -414,7 +414,7 @@ public slots: QElapsedTimer m_LastCheck; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; }; #endif // MODLIST_H diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index ba53ee749..bc0def559 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -131,8 +131,8 @@ void ModListByPriorityProxy::onModelLayoutChanged(const QList(idx.internalPointer()); - toPersistent.append( - createIndex(item->parent->childIndex(item), idx.column(), item)); + toPersistent.append(createIndex(static_cast(item->parent->childIndex(item)), + idx.column(), item)); } changePersistentIndexList(persistent, toPersistent); @@ -171,7 +171,8 @@ QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex } auto* item = m_IndexToItem.at(sourceIndex.row()).get(); - return createIndex(item->parent->childIndex(item), sourceIndex.column(), item); + return createIndex(static_cast(item->parent->childIndex(item)), + sourceIndex.column(), item); } QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) const @@ -187,13 +188,13 @@ QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) c int ModListByPriorityProxy::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { - return m_Root.children.size(); + return static_cast(m_Root.children.size()); } auto* item = static_cast(parent.internalPointer()); if (item->mod->isSeparator()) { - return item->children.size(); + return static_cast(item->children.size()); } return 0; @@ -216,7 +217,8 @@ QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const return QModelIndex(); } - return createIndex(item->parent->parent->childIndex(item->parent), 0, item->parent); + return createIndex(static_cast(item->parent->parent->childIndex(item->parent)), + 0, item->parent); } bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index e88cadc8a..3066bf1fc 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -145,19 +145,19 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, targetMenu = menu->addMenu(factory.getCategoryName(i).replace('&', "&&")); } - int id = factory.getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); - bool enabled = categories.find(id) != categories.end(); + int id = factory.getCategoryID(i); + QCheckBox* checkBox = new QCheckBox(targetMenu); + bool enabled = categories.find(id) != categories.end(); checkBox->setText(factory.getCategoryName(i).replace('&', "&&")); if (enabled) { childEnabled = true; } checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); + QWidgetAction* checkableAction = new QWidgetAction(targetMenu); + checkableAction->setDefaultWidget(checkBox); checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); + targetMenu->addAction(checkableAction); if (factory.hasChildren(i)) { if (populate(targetMenu, factory, mod, factory.getCategoryID(i)) || enabled) { diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a6f0f07ee..ad6caee4e 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -224,7 +224,7 @@ void ModListViewActions::checkModsForUpdates() const bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { checkingModsForUpdate = - ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); + ModInfo::checkAllForUpdate(&m_core.pluginManager(), m_receiver); NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); @@ -527,7 +527,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, } else { modInfo->saveMeta(); - ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent); + ModInfoDialog dialog(m_core, m_core.pluginManager(), modInfo, m_view, m_parent); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 2c536b144..8d5780e49 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -46,7 +46,7 @@ void throttledWarning(const APIUserAccount& user) APIUserAccount::ThrottleThreshold, user.remainingRequests()); } -NexusBridge::NexusBridge(PluginContainer* pluginContainer, const QString& subModule) +NexusBridge::NexusBridge(const QString& subModule) : m_Interface(&NexusInterface::instance()), m_SubModule(subModule) {} @@ -252,7 +252,7 @@ NexusInterface::parseLimits(const QList& headers) static NexusInterface* g_instance = nullptr; -NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) +NexusInterface::NexusInterface(Settings* s) : m_PluginManager(nullptr) { MO_ASSERT(!g_instance); g_instance = this; @@ -417,7 +417,7 @@ NexusInterface::getGameChoices(const MOBase::IPluginGame* game) choices.push_back( std::pair(game->gameShortName(), game->gameName())); for (QString gameName : game->validShortNames()) { - for (auto gamePlugin : m_PluginContainer->plugins()) { + for (auto gamePlugin : m_PluginManager->plugins()) { if (gamePlugin->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { choices.push_back(std::pair(gamePlugin->gameShortName(), gamePlugin->gameName())); @@ -438,9 +438,9 @@ bool NexusInterface::isModURL(int modID, const QString& url) const return QUrl(alt) == QUrl(url); } -void NexusInterface::setPluginContainer(PluginContainer* pluginContainer) +void NexusInterface::setPluginManager(PluginManager* pluginContainer) { - m_PluginContainer = pluginContainer; + m_PluginManager = pluginContainer; } int NexusInterface::requestDescription(QString gameName, int modID, QObject* receiver, @@ -763,7 +763,7 @@ int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray& hash, IPluginGame* NexusInterface::getGame(QString gameName) const { - auto gamePlugins = m_PluginContainer->plugins(); + auto gamePlugins = m_PluginManager->plugins(); IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { if (plugin != nullptr && diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 331ce55d4..2a40df5db 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -21,7 +21,7 @@ along with Mod Organizer. If not, see . #define NEXUSINTERFACE_H #include "apiuseraccount.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include #include @@ -58,7 +58,7 @@ class NexusBridge : public MOBase::IModRepositoryBridge Q_OBJECT public: - NexusBridge(PluginContainer* pluginContainer, const QString& subModule = ""); + NexusBridge(const QString& subModule = ""); /** * @brief request description for a mod @@ -522,7 +522,7 @@ class NexusInterface : public QObject */ bool isModURL(int modID, QString const& url) const; - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); signals: @@ -638,7 +638,7 @@ private slots: std::list m_ActiveRequest; QQueue m_RequestQueue; MOBase::VersionInfo m_MOVersion; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; APIUserAccount m_User; }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index e7b0c748f..16be9c98c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -17,7 +17,7 @@ #include "modrepositoryfileinfo.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "previewdialog.h" #include "profile.h" #include "shared/appconfig.h" @@ -70,6 +70,7 @@ #include #include +#include "inibakery.h" #include "organizerproxy.h" using namespace MOShared; @@ -88,9 +89,9 @@ QStringList toStringList(InputIterator current, InputIterator end) } OrganizerCore::OrganizerCore(Settings& settings) - : m_UserInterface(nullptr), m_PluginContainer(nullptr), m_CurrentProfile(nullptr), + : m_UserInterface(nullptr), m_PluginManager(nullptr), m_CurrentProfile(nullptr), m_Settings(settings), m_Updater(&NexusInterface::instance()), - m_ModList(m_PluginContainer, this), m_PluginList(*this), + m_ModList(m_PluginManager, this), m_PluginList(*this), m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount())), m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)), m_VirtualFileTree([this]() { @@ -100,6 +101,9 @@ OrganizerCore::OrganizerCore(Settings& settings) m_ArchivesInit(false), m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { + // need to initialize here for aboutToRun() to be callable + m_IniBakery = std::make_unique(*this); + env::setHandleCloserThreadCount(settings.refreshThreadCount()); m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads(), false); @@ -210,7 +214,7 @@ void OrganizerCore::storeSettings() void OrganizerCore::updateExecutablesList() { - if (m_PluginContainer == nullptr) { + if (m_PluginManager == nullptr) { log::error("can't update executables list now"); return; } @@ -253,23 +257,23 @@ void OrganizerCore::checkForUpdates() } } -void OrganizerCore::connectPlugins(PluginContainer* container) +void OrganizerCore::connectPlugins(PluginManager* manager) { - m_PluginContainer = container; - m_Updater.setPluginContainer(m_PluginContainer); - m_InstallationManager.setPluginContainer(m_PluginContainer); - m_DownloadManager.setPluginContainer(m_PluginContainer); - m_ModList.setPluginContainer(m_PluginContainer); + m_PluginManager = manager; + m_Updater.setPluginManager(m_PluginManager); + m_InstallationManager.setPluginManager(m_PluginManager); + m_DownloadManager.setPluginManager(m_PluginManager); + m_ModList.setPluginManager(m_PluginManager); if (!m_GameName.isEmpty()) { - m_GamePlugin = m_PluginContainer->game(m_GameName); + m_GamePlugin = m_PluginManager->game(m_GameName); emit managedGameChanged(m_GamePlugin); } - connect(m_PluginContainer, &PluginContainer::pluginEnabled, [&](IPlugin* plugin) { + connect(m_PluginManager, &PluginManager::pluginEnabled, [&](IPlugin* plugin) { m_PluginEnabled(plugin); }); - connect(m_PluginContainer, &PluginContainer::pluginDisabled, [&](IPlugin* plugin) { + connect(m_PluginManager, &PluginManager::pluginDisabled, [&](IPlugin* plugin) { m_PluginDisabled(plugin); }); } @@ -598,7 +602,7 @@ void OrganizerCore::setCurrentProfile(const QString& profileName) MOBase::IModRepositoryBridge* OrganizerCore::createNexusBridge() const { - return new NexusBridge(m_PluginContainer); + return new NexusBridge(); } QString OrganizerCore::profileName() const @@ -646,7 +650,7 @@ MOBase::VersionInfo OrganizerCore::appVersion() const MOBase::IPluginGame* OrganizerCore::getGame(const QString& name) const { - for (IPluginGame* game : m_PluginContainer->plugins()) { + for (IPluginGame* game : m_PluginManager->plugins()) { if (game != nullptr && game->gameShortName().compare(name, Qt::CaseInsensitive) == 0) return game; @@ -943,7 +947,7 @@ QStringList OrganizerCore::getFileOrigins(const QString& fileName) const if (file.get() != nullptr) { result.append( ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); - foreach (const auto& i, file->getAlternatives()) { + for (const auto& i : file->getAlternatives()) { result.append( ToQString(m_DirectoryStructure->getOriginByID(i.originID()).getName())); } @@ -1042,7 +1046,7 @@ bool OrganizerCore::previewFileWithAlternatives(QWidget* parent, QString fileNam if (QFile::exists(filePath)) { // it's very possible the file doesn't exist, because it's inside an archive. we // don't support that - QWidget* wid = m_PluginContainer->previewGenerator().genPreview(filePath); + QWidget* wid = m_PluginManager->previewGenerator().genPreview(filePath); if (wid == nullptr) { reportError(tr("failed to generate preview for %1").arg(filePath)); } else { @@ -1110,7 +1114,7 @@ bool OrganizerCore::previewFile(QWidget* parent, const QString& originName, PreviewDialog preview(path, parent); - QWidget* wid = m_PluginContainer->previewGenerator().genPreview(path); + QWidget* wid = m_PluginManager->previewGenerator().genPreview(path); if (wid == nullptr) { reportError(tr("Failed to generate preview for %1").arg(path)); return false; @@ -1403,11 +1407,11 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) { - if (!m_PluginContainer) { + if (!m_PluginManager) { return; } - for (IPluginModPage* modPage : m_PluginContainer->plugins()) { - if (m_PluginContainer->isEnabled(modPage)) { + for (IPluginModPage* modPage : m_PluginManager->plugins()) { + if (m_PluginManager->isEnabled(modPage)) { ModRepositoryFileInfo* fileInfo = new ModRepositoryFileInfo(); if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { fileInfo->repository = modPage->name(); @@ -1453,9 +1457,9 @@ void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) } } -PluginContainer& OrganizerCore::pluginContainer() const +PluginManager& OrganizerCore::pluginManager() const { - return *m_PluginContainer; + return *m_PluginManager; } IPluginGame const* OrganizerCore::managedGame() const @@ -1465,7 +1469,7 @@ IPluginGame const* OrganizerCore::managedGame() const IOrganizer const* OrganizerCore::managedGameOrganizer() const { - return m_PluginContainer->requirements(m_GamePlugin).m_Organizer; + return m_PluginManager->details(m_GamePlugin).proxy(); } std::vector OrganizerCore::enabledArchives() @@ -2034,10 +2038,17 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, result.insert(result.end(), {QDir::toNativeSeparators(m_Settings.paths().overwrite()), dataPath, true, customOverwrite.isEmpty()}); + // ini bakery + { + const auto iniBakeryMapping = m_IniBakery->mappings(); + result.reserve(result.size() + iniBakeryMapping.size()); + result.insert(result.end(), iniBakeryMapping.begin(), iniBakeryMapping.end()); + } + for (MOBase::IPluginFileMapper* mapper : - m_PluginContainer->plugins()) { + m_PluginManager->plugins()) { IPlugin* plugin = dynamic_cast(mapper); - if (m_PluginContainer->isEnabled(plugin)) { + if (m_PluginManager->isEnabled(plugin)) { MappingType pluginMap = mapper->mappings(); result.reserve(result.size() + pluginMap.size()); result.insert(result.end(), pluginMap.begin(), pluginMap.end()); diff --git a/src/organizercore.h b/src/organizercore.h index daeb7a7f3..038eb650a 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -35,11 +35,12 @@ #include #include +class IniBakery; class ModListSortProxy; class PluginListSortProxy; class Profile; class IUserInterface; -class PluginContainer; +class PluginManager; class DirectoryRefresher; namespace MOBase @@ -217,7 +218,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose ~OrganizerCore(); void setUserInterface(IUserInterface* ui); - void connectPlugins(PluginContainer* container); + void connectPlugins(PluginManager* manager); void setManagedGame(MOBase::IPluginGame* game); @@ -245,9 +246,9 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } - // return the plugin container + // return the plugin manager // - PluginContainer& pluginContainer() const; + PluginManager& pluginManager() const; MOBase::IPluginGame const* managedGame() const; @@ -485,7 +486,8 @@ private slots: private: IUserInterface* m_UserInterface; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; + std::unique_ptr m_IniBakery; QString m_GameName; MOBase::IPluginGame* m_GamePlugin; ModDataContentHolder m_Contents; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index a47e8798a..9e1bedc29 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -4,8 +4,8 @@ #include "glob_matching.h" #include "modlistproxy.h" #include "organizercore.h" -#include "plugincontainer.h" #include "pluginlistproxy.h" +#include "pluginmanager.h" #include "proxyutils.h" #include "settings.h" #include "shared/appconfig.h" @@ -17,10 +17,9 @@ using namespace MOBase; using namespace MOShared; -OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, - PluginContainer* pluginContainer, +OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, MOBase::IPlugin* plugin) - : m_Proxied(organizer), m_PluginContainer(pluginContainer), m_Plugin(plugin), + : m_Proxied(organizer), m_PluginManager(pluginManager), m_Plugin(plugin), m_DownloadManagerProxy( std::make_unique(this, organizer->downloadManager())), m_ModListProxy(std::make_unique(this, organizer->modList())), @@ -78,7 +77,7 @@ void OrganizerProxy::disconnectSignals() IModRepositoryBridge* OrganizerProxy::createNexusBridge() const { - return new NexusBridge(m_PluginContainer, m_Plugin->name()); + return new NexusBridge(m_Plugin->name()); } QString OrganizerProxy::profileName() const @@ -133,12 +132,12 @@ void OrganizerProxy::modDataChanged(IModInterface* mod) bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const { - return m_PluginContainer->isEnabled(pluginName); + return m_PluginManager->isEnabled(pluginName); } bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const { - return m_PluginContainer->isEnabled(plugin); + return m_PluginManager->isEnabled(plugin); } QVariant OrganizerProxy::pluginSetting(const QString& pluginName, diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 771d09810..c54ca98e8 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -8,7 +8,7 @@ #include "organizercore.h" -class PluginContainer; +class PluginManager; class DownloadManagerProxy; class ModListProxy; class PluginListProxy; @@ -17,7 +17,7 @@ class OrganizerProxy : public MOBase::IOrganizer { public: - OrganizerProxy(OrganizerCore* organizer, PluginContainer* pluginContainer, + OrganizerProxy(OrganizerCore* organizer, PluginManager* pluginManager, MOBase::IPlugin* plugin); ~OrganizerProxy(); @@ -110,7 +110,7 @@ class OrganizerProxy : public MOBase::IOrganizer protected: // The container needs access to some callbacks to simulate startup. - friend class PluginContainer; + friend class PluginManager; /** * @brief Connect the signals from this proxy and all the child proxies (plugin list, @@ -127,7 +127,7 @@ class OrganizerProxy : public MOBase::IOrganizer private: OrganizerCore* m_Proxied; - PluginContainer* m_PluginContainer; + PluginManager* m_PluginManager; MOBase::IPlugin* m_Plugin; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp deleted file mode 100644 index 157d33995..000000000 --- a/src/plugincontainer.cpp +++ /dev/null @@ -1,1249 +0,0 @@ -#include "plugincontainer.h" -#include "iuserinterface.h" -#include "organizercore.h" -#include "organizerproxy.h" -#include "report.h" -#include "shared/appconfig.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -namespace bf = boost::fusion; - -// Welcome to the wonderful world of MO2 plugin management! -// -// We'll start by the C++ side. -// -// There are 9 types of MO2 plugins, two of which cannot be standalone: IPluginDiagnose -// and IPluginFileMapper. This means that you can have a class implementing IPluginGame, -// IPluginDiagnose and IPluginFileMapper. It is not possible for a class to implement -// two full plugin types (e.g. IPluginPreview and IPluginTool). -// -// Plugins are fetch as QObject initially and must be "qobject-casted" to the right -// type. -// -// Plugins are stored in the PluginContainer class in various C++ containers: there is a -// vector that stores all the plugin as QObject, multiple vectors that stores the plugin -// of each types, a map to find IPlugin object from their names or from IPluginDiagnose -// or IFileMapper (since these do not inherit IPlugin, they cannot be downcasted). -// -// Requirements for plugins are stored in m_Requirements: -// - IPluginGame cannot be enabled by user. A game plugin is considered enable only if -// it is -// the one corresponding to the currently managed games. -// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled -// by users, -// and will follow the enabled/disabled state of its parent. -// - Each plugin has an "enabled" setting stored in persistence. If the setting does -// not exist, -// the plugin's enabledByDefault is used instead. -// - A plugin is considered disabled if the setting is false. -// - If the setting is true, a plugin is considered disabled if one of its -// requirements is not met. -// - Users cannot enable a plugin if one of its requirements is not met. -// -// Now let's move to the Proxy side... Or the as of now, the Python side. -// -// Proxied plugins are much more annoying because they can implement all interfaces, and -// are given to MO2 as separate plugins... A Python class implementing IPluginGame and -// IPluginDiagnose will be seen by MO2 as two separate QObject, and they will all have -// the same name. -// -// When a proxied plugin is registered, a few things must be taken care of: -// - There can only be one plugin mapped to a name in the PluginContainer class, so we -// keep the -// plugin corresponding to the most relevant class (see PluginTypeOrder), e.g. if the -// class inherits both IPluginGame and IPluginFileMapper, we map the name to the C++ -// QObject corresponding to the IPluginGame. -// - When a proxied plugin implements multiple interfaces, the IPlugin corresponding to -// the most -// important interface is set as the parent (hidden) of the other IPlugin through -// PluginRequirements. This way, the plugin are managed together (enabled/disabled -// state). The "fake" children plugins will not be returned by -// PluginRequirements::children(). -// - Since each interface corresponds to a different QObject, we need to take care not -// to call -// IPlugin::init() on each QObject, but only on the first one. -// -// All the proxied plugins are linked to the proxy plugin by PluginRequirements. If the -// proxy plugin is disabled, the proxied plugins are not even loaded so not visible in -// the plugin management tab. - -template -struct PluginTypeName; - -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Plugin"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Diagnose"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Game"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Installer"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Mod Page"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Preview"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Tool"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("Proxy"); } -}; -template <> -struct PluginTypeName -{ - static QString value() { return QT_TR_NOOP("File Mapper"); } -}; - -QStringList PluginContainer::pluginInterfaces() -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([&names](const auto* p) { - using plugin_type = std::decay_t; - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - }); - - return names; -} - -// PluginRequirementProxy - -const std::set PluginRequirements::s_CorePlugins{"INI Bakery"}; - -PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, - MOBase::IPlugin* plugin, OrganizerProxy* proxy, - MOBase::IPluginProxy* pluginProxy) - : m_PluginContainer(pluginContainer), m_Plugin(plugin), m_PluginProxy(pluginProxy), - m_Master(nullptr), m_Organizer(proxy) -{ - // There are a lots of things we cannot set here (e.g. m_Master) because we do not - // know the order plugins are loaded. -} - -void PluginRequirements::fetchRequirements() -{ - m_Requirements = m_Plugin->requirements(); -} - -IPluginProxy* PluginRequirements::proxy() const -{ - return m_PluginProxy; -} - -std::vector PluginRequirements::proxied() const -{ - std::vector children; - if (dynamic_cast(m_Plugin)) { - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - if (plugin && m_PluginContainer->requirements(plugin).proxy() == m_Plugin) { - children.push_back(plugin); - } - } - } - return children; -} - -IPlugin* PluginRequirements::master() const -{ - // If we have a m_Master, it was forced and thus override the default master(). - if (m_Master) { - return m_Master; - } - - if (m_Plugin->master().isEmpty()) { - return nullptr; - } - - return m_PluginContainer->plugin(m_Plugin->master()); -} - -void PluginRequirements::setMaster(IPlugin* master) -{ - m_Master = master; -} - -std::vector PluginRequirements::children() const -{ - std::vector children; - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - - // Not checking master() but requirements().master() due to "hidden" - // masters. - // If the master has the same name as the plugin, this is a "hidden" - // master, we do not add it here. - if (plugin && m_PluginContainer->requirements(plugin).master() == m_Plugin && - plugin->name() != m_Plugin->name()) { - children.push_back(plugin); - } - } - return children; -} - -std::vector PluginRequirements::problems() const -{ - std::vector result; - for (auto& requirement : m_Requirements) { - if (auto p = requirement->check(m_Organizer)) { - result.push_back(*p); - } - } - return result; -} - -bool PluginRequirements::canEnable() const -{ - return problems().empty(); -} - -bool PluginRequirements::isCorePlugin() const -{ - // Let's consider game plugins as "core": - if (m_PluginContainer->implementInterface(m_Plugin)) { - return true; - } - - return s_CorePlugins.contains(m_Plugin->name()); -} - -bool PluginRequirements::hasRequirements() const -{ - return !m_Requirements.empty(); -} - -QStringList PluginRequirements::requiredGames() const -{ - // We look for a "GameDependencyRequirement" - There can be only one since otherwise - // it'd mean that the plugin requires two games at once. - for (auto& requirement : m_Requirements) { - if (auto* gdep = - dynamic_cast(requirement.get())) { - return gdep->gameNames(); - } - } - - return {}; -} - -std::vector PluginRequirements::requiredFor() const -{ - std::vector required; - std::set visited; - requiredFor(required, visited); - return required; -} - -void PluginRequirements::requiredFor(std::vector& required, - std::set& visited) const -{ - // Handle cyclic dependencies. - if (visited.contains(m_Plugin)) { - return; - } - visited.insert(m_Plugin); - - for (auto& [plugin, requirements] : m_PluginContainer->m_Requirements) { - - // If the plugin is not enabled, discard: - if (!m_PluginContainer->isEnabled(plugin)) { - continue; - } - - // Check the requirements: - for (auto& requirement : requirements.m_Requirements) { - - // We check for plugin dependency. Game dependency are not checked this way. - if (auto* pdep = - dynamic_cast(requirement.get())) { - - // Check if at least one of the plugin in the requirements is enabled (except - // this one): - bool oneEnabled = false; - for (auto& pluginName : pdep->pluginNames()) { - if (pluginName != m_Plugin->name() && - m_PluginContainer->isEnabled(pluginName)) { - oneEnabled = true; - break; - } - } - - // No plugin enabled found, so the plugin requires this plugin: - if (!oneEnabled) { - required.push_back(plugin); - requirements.requiredFor(required, visited); - break; - } - } - } - } -} - -// PluginContainer - -PluginContainer::PluginContainer(OrganizerCore* organizer) - : m_Organizer(organizer), m_UserInterface(nullptr), m_PreviewGenerator(*this) -{} - -PluginContainer::~PluginContainer() -{ - m_Organizer = nullptr; - unloadPlugins(); -} - -void PluginContainer::startPlugins(IUserInterface* userInterface) -{ - m_UserInterface = userInterface; - startPluginsImpl(plugins()); -} - -QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const -{ - // We need a QObject to be able to qobject_cast<> to the plugin types: - QObject* oPlugin = as_qobject(plugin); - - if (!oPlugin) { - return {}; - } - - return implementedInterfaces(oPlugin); -} - -QStringList PluginContainer::implementedInterfaces(QObject* oPlugin) const -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([oPlugin, &names](const auto* p) { - using plugin_type = std::decay_t; - if (qobject_cast(oPlugin)) { - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - } - }); - - // If the plugin implements at least one interface other than IPlugin, remove IPlugin: - if (names.size() > 1) { - names.removeAll(PluginTypeName::value()); - } - - return names; -} - -QString PluginContainer::topImplementedInterface(IPlugin* plugin) const -{ - auto interfaces = implementedInterfaces(plugin); - return interfaces.isEmpty() ? "" : interfaces[0]; -} - -bool PluginContainer::isBetterInterface(QObject* lhs, QObject* rhs) const -{ - int count = 0, lhsIdx = -1, rhsIdx = -1; - boost::mp11::mp_for_each([&](const auto* p) { - using plugin_type = std::decay_t; - if (lhsIdx < 0 && qobject_cast(lhs)) { - lhsIdx = count; - } - if (rhsIdx < 0 && qobject_cast(rhs)) { - rhsIdx = count; - } - ++count; - }); - return lhsIdx < rhsIdx; -} - -QStringList PluginContainer::pluginFileNames() const -{ - QStringList result; - for (QPluginLoader* loader : m_PluginLoaders) { - result.append(loader->fileName()); - } - std::vector proxyList = bf::at_key(m_Plugins); - for (IPluginProxy* proxy : proxyList) { - QStringList proxiedPlugins = - proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - result.append(proxiedPlugins); - } - return result; -} - -QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const -{ - // Find the correspond QObject - Can this be done safely with a cast? - auto& objects = bf::at_key(m_Plugins); - auto it = - std::find_if(std::begin(objects), std::end(objects), [plugin](QObject* obj) { - return qobject_cast(obj) == plugin; - }); - - if (it == std::end(objects)) { - return nullptr; - } - - return *it; -} - -bool PluginContainer::initPlugin(IPlugin* plugin, IPluginProxy* pluginProxy, - bool skipInit) -{ - // when MO has no instance loaded, init() is not called on plugins, except - // for proxy plugins, where init() is called with a null IOrganizer - // - // after proxies are initialized, instantiate() is called for all the plugins - // they've discovered, but as for regular plugins, init() won't be - // called on them if m_OrganizerCore is null - - if (plugin == nullptr) { - return false; - } - - OrganizerProxy* proxy = nullptr; - if (m_Organizer) { - proxy = new OrganizerProxy(m_Organizer, this, plugin); - proxy->setParent(as_qobject(plugin)); - } - - // Check if it is a proxy plugin: - bool isProxy = dynamic_cast(plugin); - - auto [it, bl] = m_Requirements.emplace( - plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - - if (!m_Organizer && !isProxy) { - return true; - } - - if (skipInit) { - return true; - } - - if (!plugin->init(proxy)) { - log::warn("plugin failed to initialize"); - return false; - } - - // Update requirements: - it->second.fetchRequirements(); - - return true; -} - -void PluginContainer::registerGame(IPluginGame* game) -{ - m_SupportedGames.insert({game->gameName(), game}); -} - -void PluginContainer::unregisterGame(MOBase::IPluginGame* game) -{ - m_SupportedGames.erase(game->gameName()); -} - -IPlugin* PluginContainer::registerPlugin(QObject* plugin, const QString& filepath, - MOBase::IPluginProxy* pluginProxy) -{ - - // generic treatment for all plugins - IPlugin* pluginObj = qobject_cast(plugin); - if (pluginObj == nullptr) { - log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); - return nullptr; - } - - // If we already a plugin with this name: - bool skipInit = false; - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(pluginObj->name())) { - - IPlugin* other = mapNames[pluginObj->name()]; - - // If both plugins are from the same proxy and the same file, this is usually - // ok (in theory some one could write two different classes from the same Python - // file/module): - if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy && - this->filepath(other) == QDir::cleanPath(filepath)) { - - // Plugin has already been initialized: - skipInit = true; - - if (isBetterInterface(plugin, as_qobject(other))) { - log::debug( - "replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", - pluginObj->name(), implementedInterfaces(other).join(", "), - implementedInterfaces(plugin).join(", ")); - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - } else { - log::warn("Trying to register two plugins with the name '{}' (from {} and {}), " - "the second one will not be registered.", - pluginObj->name(), this->filepath(other), QDir::cleanPath(filepath)); - return nullptr; - } - } else { - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - - // Storing the original QObject* is a bit of a hack as I couldn't figure out any - // way to cast directly between IPlugin* and IPluginDiagnose* - bf::at_key(m_Plugins).push_back(plugin); - - plugin->setProperty("filepath", QDir::cleanPath(filepath)); - plugin->setParent(this); - - if (m_Organizer) { - m_Organizer->settings().plugins().registerPlugin(pluginObj); - } - - { // diagnosis plugin - IPluginDiagnose* diagnose = qobject_cast(plugin); - if (diagnose != nullptr) { - bf::at_key(m_Plugins).push_back(diagnose); - bf::at_key(m_AccessPlugins)[diagnose] = pluginObj; - diagnose->onInvalidated([&]() { - emit diagnosisUpdate(); - }); - } - } - { // file mapper plugin - IPluginFileMapper* mapper = qobject_cast(plugin); - if (mapper != nullptr) { - bf::at_key(m_Plugins).push_back(mapper); - bf::at_key(m_AccessPlugins)[mapper] = pluginObj; - } - } - { // mod page plugin - IPluginModPage* modPage = qobject_cast(plugin); - if (initPlugin(modPage, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(modPage); - emit pluginRegistered(modPage); - return modPage; - } - } - { // game plugin - IPluginGame* game = qobject_cast(plugin); - if (game) { - game->detectGame(); - if (initPlugin(game, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(game); - registerGame(game); - emit pluginRegistered(game); - return game; - } - } - } - { // tool plugins - IPluginTool* tool = qobject_cast(plugin); - if (initPlugin(tool, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(tool); - emit pluginRegistered(tool); - return tool; - } - } - { // installer plugins - IPluginInstaller* installer = qobject_cast(plugin); - if (initPlugin(installer, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(installer); - if (m_Organizer) { - installer->setInstallationManager(m_Organizer->installationManager()); - } - emit pluginRegistered(installer); - return installer; - } - } - { // preview plugins - IPluginPreview* preview = qobject_cast(plugin); - if (initPlugin(preview, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(preview); - return preview; - } - } - { // proxy plugins - IPluginProxy* proxy = qobject_cast(plugin); - if (initPlugin(proxy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(proxy); - emit pluginRegistered(proxy); - - QStringList filepaths = - proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - for (const QString& filepath : filepaths) { - loadProxied(filepath, proxy); - } - return proxy; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin* dummy = qobject_cast(plugin); - if (initPlugin(dummy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(dummy); - emit pluginRegistered(dummy); - return dummy; - } - } - - return nullptr; -} - -IPlugin* PluginContainer::managedGame() const -{ - // TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so - // returning a const-version if painful. This should be fixed by making methods accept - // a const IPlugin* instead, but there are a few tricks with qobject_cast and const. - return const_cast(m_Organizer->managedGame()); -} - -bool PluginContainer::isEnabled(IPlugin* plugin) const -{ - // Check if it's a game plugin: - if (implementInterface(plugin)) { - return plugin == m_Organizer->managedGame(); - } - - // Check the master, if any: - auto& requirements = m_Requirements.at(plugin); - - if (requirements.master()) { - return isEnabled(requirements.master()); - } - - // Check if the plugin is enabled: - if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()) - .toBool()) { - return false; - } - - // Check the requirements: - return m_Requirements.at(plugin).canEnable(); -} - -void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, - bool dependencies) -{ - // If required, disable dependencies: - if (!enable && dependencies) { - for (auto* p : requirements(plugin).requiredFor()) { - setEnabled( - p, false, - false); // No need to "recurse" here since requiredFor already does it. - } - } - - // Always disable/enable child plugins: - for (auto* p : requirements(plugin).children()) { - // "Child" plugin should have no dependencies. - setEnabled(p, enable, false); - } - - m_Organizer->setPersistent(plugin->name(), "enabled", enable, true); - - if (enable) { - emit pluginEnabled(plugin); - } else { - emit pluginDisabled(plugin); - } -} - -MOBase::IPlugin* PluginContainer::plugin(QString const& pluginName) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(pluginName); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginDiagnose* diagnose) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(diagnose); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginFileMapper* mapper) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(mapper); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -bool PluginContainer::isEnabled(QString const& pluginName) const -{ - IPlugin* p = plugin(pluginName); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const -{ - IPlugin* p = plugin(diagnose); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const -{ - IPlugin* p = plugin(mapper); - return p ? isEnabled(p) : false; -} - -const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const -{ - return m_Requirements.at(plugin); -} - -OrganizerProxy* PluginContainer::organizerProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).m_Organizer; -} - -MOBase::IPluginProxy* PluginContainer::pluginProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).proxy(); -} - -QString PluginContainer::filepath(MOBase::IPlugin* plugin) const -{ - return as_qobject(plugin)->property("filepath").toString(); -} - -IPluginGame* PluginContainer::game(const QString& name) const -{ - auto iter = m_SupportedGames.find(name); - if (iter != m_SupportedGames.end()) { - return iter->second; - } else { - return nullptr; - } -} - -const PreviewGenerator& PluginContainer::previewGenerator() const -{ - return m_PreviewGenerator; -} - -void PluginContainer::startPluginsImpl(const std::vector& plugins) const -{ - // setUserInterface() - if (m_UserInterface) { - for (auto* plugin : plugins) { - if (auto* proxy = qobject_cast(plugin)) { - proxy->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* modPage = qobject_cast(plugin)) { - modPage->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* tool = qobject_cast(plugin)) { - tool->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* installer = qobject_cast(plugin)) { - installer->setParentWidget(m_UserInterface->mainWindow()); - } - } - } - - // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged. - if (m_Organizer) { - for (auto* object : plugins) { - auto* plugin = qobject_cast(object); - auto* oproxy = organizerProxy(plugin); - oproxy->connectSignals(); - oproxy->m_ProfileChanged(nullptr, m_Organizer->currentProfile()); - - if (m_UserInterface) { - oproxy->m_UserInterfaceInitialized(m_UserInterface->mainWindow()); - } - } - } -} - -std::vector PluginContainer::loadProxied(const QString& filepath, - IPluginProxy* proxy) -{ - std::vector proxiedPlugins; - - try { - // We get a list of matching plugins as proxies can return multiple plugins - // per file and do not have a good way of supporting multiple inheritance. - QList matchingPlugins = proxy->load(filepath); - - // We are going to group plugin by names and "fix" them later: - std::map> proxiedByNames; - - for (QObject* proxiedPlugin : matchingPlugins) { - if (proxiedPlugin != nullptr) { - - if (IPlugin* proxied = registerPlugin(proxiedPlugin, filepath, proxy); - proxied) { - log::debug("loaded plugin '{}' from '{}' - [{}]", proxied->name(), - QFileInfo(filepath).fileName(), - implementedInterfaces(proxied).join(", ")); - - // Store the plugin for later: - proxiedPlugins.push_back(proxiedPlugin); - proxiedByNames[proxied->name()].push_back(proxied); - } else { - log::warn("plugin \"{}\" failed to load. If this plugin is for an older " - "version of MO " - "you have to update it or delete it if no update exists.", - filepath); - } - } - } - - // Fake masters: - for (auto& [name, proxiedPlugins] : proxiedByNames) { - if (proxiedPlugins.size() > 1) { - auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins), - [&](auto const& lhs, auto const& rhs) { - return isBetterInterface(as_qobject(lhs), - as_qobject(rhs)); - }); - - for (auto& proxiedPlugin : proxiedPlugins) { - if (proxiedPlugin != *it) { - m_Requirements.at(proxiedPlugin).setMaster(*it); - } - } - } - } - } catch (const std::exception& e) { - reportError( - QObject::tr("failed to initialize plugin %1: %2").arg(filepath).arg(e.what())); - } - - return proxiedPlugins; -} - -QObject* PluginContainer::loadQtPlugin(const QString& filepath) -{ - std::unique_ptr pluginLoader(new QPluginLoader(filepath, this)); - if (pluginLoader->instance() == nullptr) { - m_FailedPlugins.push_back(filepath); - log::error("failed to load plugin {}: {}", filepath, pluginLoader->errorString()); - } else { - QObject* object = pluginLoader->instance(); - if (IPlugin* plugin = registerPlugin(object, filepath, nullptr); plugin) { - log::debug("loaded plugin '{}' from '{}' - [{}]", plugin->name(), - QFileInfo(filepath).fileName(), - implementedInterfaces(plugin).join(", ")); - m_PluginLoaders.push_back(pluginLoader.release()); - return object; - } else { - m_FailedPlugins.push_back(filepath); - log::warn("plugin '{}' failed to load (may be outdated)", filepath); - } - } - return nullptr; -} - -std::optional PluginContainer::isQtPluginFolder(const QString& filepath) const -{ - - if (!QFileInfo(filepath).isDir()) { - return {}; - } - - QDirIterator iter(filepath, QDir::Files | QDir::NoDotAndDotDot); - while (iter.hasNext()) { - iter.next(); - const auto filePath = iter.filePath(); - - // not a library, skip - if (!QLibrary::isLibrary(filePath)) { - continue; - } - - // check if we have proper metadata - this does not load the plugin (metaData() - // should be very lightweight) - const QPluginLoader loader(filePath); - if (!loader.metaData().isEmpty()) { - return filePath; - } - } - - return {}; -} - -void PluginContainer::loadPlugin(QString const& filepath) -{ - std::vector plugins; - if (QFileInfo(filepath).isFile() && QLibrary::isLibrary(filepath)) { - QObject* plugin = loadQtPlugin(filepath); - if (plugin) { - plugins.push_back(plugin); - } - } else if (auto p = isQtPluginFolder(filepath)) { - QObject* plugin = loadQtPlugin(*p); - if (plugin) { - plugins.push_back(plugin); - } - } else { - // We need to check if this can be handled by a proxy. - for (auto* proxy : this->plugins()) { - auto filepaths = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())); - if (filepaths.contains(filepath)) { - plugins = loadProxied(filepath, proxy); - break; - } - } - } - - for (auto* plugin : plugins) { - emit pluginRegistered(qobject_cast(plugin)); - } - - startPluginsImpl(plugins); -} - -void PluginContainer::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) -{ - if (auto* game = qobject_cast(object)) { - - if (game == managedGame()) { - throw Exception("cannot unload the plugin for the currently managed game"); - } - - unregisterGame(game); - } - - // We need to remove from the m_Plugins maps BEFORE unloading from the proxy - // otherwise the qobject_cast to check the plugin type will not work. - bf::for_each(m_Plugins, [object](auto& t) { - using type = typename std::decay_t::value_type; - - // We do not want to remove from QObject since we are iterating over them. - if constexpr (!std::is_same{}) { - auto itp = - std::find(t.second.begin(), t.second.end(), qobject_cast(object)); - if (itp != t.second.end()) { - t.second.erase(itp); - } - } - }); - - emit pluginUnregistered(plugin); - - // Remove from the members. - if (auto* diagnose = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(diagnose); - } - if (auto* mapper = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(mapper); - } - - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(plugin->name())) { - mapNames.erase(plugin->name()); - } - - m_Organizer->settings().plugins().unregisterPlugin(plugin); - - // Force disconnection of the signals from the proxies. This is a safety - // operations since those signals should be disconnected when the proxies - // are destroyed anyway. - organizerProxy(plugin)->disconnectSignals(); - - // Is this a proxied plugin? - auto* proxy = pluginProxy(plugin); - - if (proxy) { - proxy->unload(filepath(plugin)); - } else { - // We need to find the loader. - auto it = std::find_if(m_PluginLoaders.begin(), m_PluginLoaders.end(), - [object](auto* loader) { - return loader->instance() == object; - }); - - if (it != m_PluginLoaders.end()) { - if (!(*it)->unload()) { - log::error("failed to unload {}: {}", (*it)->fileName(), (*it)->errorString()); - } - delete *it; - m_PluginLoaders.erase(it); - } else { - log::error("loader for plugin {} does not exist, cannot unload", plugin->name()); - } - } - - object->deleteLater(); - - // Do this at the end. - m_Requirements.erase(plugin); -} - -void PluginContainer::unloadPlugin(QString const& filepath) -{ - // We need to find all the plugins from the given path and - // unload them: - QString cleanPath = QDir::cleanPath(filepath); - auto& objects = bf::at_key(m_Plugins); - for (auto it = objects.begin(); it != objects.end();) { - auto* plugin = qobject_cast(*it); - if (this->filepath(plugin) == filepath) { - unloadPlugin(plugin, *it); - it = objects.erase(it); - } else { - ++it; - } - } -} - -void PluginContainer::reloadPlugin(QString const& filepath) -{ - unloadPlugin(filepath); - loadPlugin(filepath); -} - -void PluginContainer::unloadPlugins() -{ - if (m_Organizer) { - // this will clear several structures that can hold on to pointers to - // plugins, as well as read the plugin blacklist from the ini file, which - // is used in loadPlugins() below to skip plugins - // - // note that the first thing loadPlugins() does is call unloadPlugins(), - // so this makes sure the blacklist is always available - m_Organizer->settings().plugins().clearPlugins(); - } - - bf::for_each(m_Plugins, [](auto& t) { - t.second.clear(); - }); - bf::for_each(m_AccessPlugins, [](auto& t) { - t.second.clear(); - }); - m_Requirements.clear(); - - while (!m_PluginLoaders.empty()) { - QPluginLoader* loader = m_PluginLoaders.back(); - m_PluginLoaders.pop_back(); - if ((loader != nullptr) && !loader->unload()) { - log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); - } - delete loader; - } -} - -void PluginContainer::loadPlugins() -{ - TimeThis tt("PluginContainer::loadPlugins()"); - - unloadPlugins(); - - for (QObject* plugin : QPluginLoader::staticInstances()) { - registerPlugin(plugin, "", nullptr); - } - - QFile loadCheck; - QString skipPlugin; - - if (m_Organizer) { - loadCheck.setFileName(qApp->property("dataPath").toString() + - "/plugin_loadcheck.tmp"); - - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded - // last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - - log::warn("loadcheck file found for plugin '{}'", fileName); - - MOBase::TaskDialog dlg; - - const auto Skip = QMessageBox::Ignore; - const auto Blacklist = QMessageBox::Cancel; - const auto Load = QMessageBox::Ok; - - const auto r = - dlg.title(tr("Plugin error")) - .main(tr("Mod Organizer failed to load the plugin '%1' last time it was " - "started.") - .arg(fileName)) - .content(tr( - "The plugin can be skipped for this session, blacklisted, " - "or loaded normally, in which case it might fail again. Blacklisted " - "plugins can be re-enabled later in the settings.")) - .icon(QMessageBox::Warning) - .button({tr("Skip this plugin"), Skip}) - .button({tr("Blacklist this plugin"), Blacklist}) - .button({tr("Load this plugin"), Load}) - .exec(); - - switch (r) { - case Skip: - log::warn("user wants to skip plugin '{}'", fileName); - skipPlugin = fileName; - break; - - case Blacklist: - log::warn("user wants to blacklist plugin '{}'", fileName); - m_Organizer->settings().plugins().addBlacklist(fileName); - break; - - case Load: - log::warn("user wants to load plugin '{}' anyway", fileName); - break; - } - - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - } - - QString pluginPath = - qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); - QDirIterator iter(pluginPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - - if (skipPlugin == iter.fileName()) { - log::debug("plugin \"{}\" skipped for this session", iter.fileName()); - continue; - } - - if (m_Organizer) { - if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { - log::debug("plugin \"{}\" blacklisted", iter.fileName()); - continue; - } - } - - if (loadCheck.isOpen()) { - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - QString filepath = iter.filePath(); - if (QLibrary::isLibrary(filepath)) { - loadQtPlugin(filepath); - } else if (auto p = isQtPluginFolder(filepath)) { - loadQtPlugin(*p); - } - } - - if (skipPlugin.isEmpty()) { - // remove the load check file on success - if (loadCheck.isOpen()) { - loadCheck.remove(); - } - } else { - // remember the plugin for next time - if (loadCheck.isOpen()) { - loadCheck.close(); - } - - log::warn("user skipped plugin '{}', remembering in loadcheck", skipPlugin); - loadCheck.open(QIODevice::WriteOnly); - loadCheck.write(skipPlugin.toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - bf::at_key(m_Plugins).push_back(this); - - if (m_Organizer) { - bf::at_key(m_Plugins).push_back(m_Organizer); - m_Organizer->connectPlugins(this); - } -} - -std::vector PluginContainer::activeProblems() const -{ - std::vector problems; - if (m_FailedPlugins.size()) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - return problems; -} - -QString PluginContainer::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString PluginContainer::fullDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - QString result = - tr("The following plugins could not be loaded. The reason may be missing " - "dependencies (i.e. python) or an outdated version:") + - "