From bb4f0badb8273558fc3c60a9f858fe2cbb510fe5 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] 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 | 57 +- src/mainwindow.h | 8 +- 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 | 16 +- src/settingsdialog.h | 10 +- src/settingsdialogplugins.cpp | 180 +--- src/settingsdialogplugins.h | 5 +- 70 files changed, 1588 insertions(+), 2398 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 dc99e2dff..a6bea2f70 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -230,13 +230,14 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) } MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& themeManager, - QWidget* parent) + 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), m_NumberOfProblems(0), m_ProblemsCheckRequired(false) @@ -308,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, [&] { @@ -377,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); @@ -434,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, @@ -1033,9 +1035,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(); @@ -1277,7 +1279,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 // @@ -1444,7 +1446,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), @@ -1455,7 +1457,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()); @@ -1547,7 +1549,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), @@ -1559,7 +1561,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()); @@ -2643,7 +2645,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(); @@ -2994,7 +2997,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; @@ -3050,7 +3053,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; @@ -3193,7 +3196,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; @@ -3293,7 +3296,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(); } @@ -3365,7 +3368,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; @@ -3511,7 +3514,7 @@ void MainWindow::on_actionNotifications_triggered() future.waitForFinished(); - ProblemsDialog problems(m_PluginContainer, this); + ProblemsDialog problems(m_PluginManager, this); problems.exec(); scheduleCheckForProblems(); @@ -3519,7 +3522,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 6656ed0c4..873f50a29 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 "tutorialcontrol.h" @@ -126,8 +126,8 @@ class MainWindow : public QMainWindow, public IUserInterface public: explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, - PluginContainer& pluginContainer, ThemeManager& manager, - QWidget* parent = 0); + PluginManager& pluginManager, ThemeManager& themeManager, + TranslationManager& translationManager, QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -296,7 +296,7 @@ private slots: QTime m_StartTime; OrganizerCore& m_OrganizerCore; - PluginContainer& m_PluginContainer; + PluginManager& m_PluginManager; ThemeManager& m_ThemeManager; QString m_CurrentLanguage; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 7fe46f224..9537919cf 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" @@ -221,7 +222,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 @@ -419,7 +420,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 b81ce69ff..2df549ace 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -31,7 +31,7 @@ class Instance; class MOMultiProcess; class NexusInterface; class OrganizerCore; -class PluginContainer; +class PluginManager; class Settings; namespace MOBase @@ -80,13 +80,13 @@ 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_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:") + - "
    "; - for (const QString& plugin : m_FailedPlugins) { - result += "
  • " + plugin + "
  • "; - } - result += "
      "; - return result; - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool PluginContainer::hasGuidedFix(unsigned int) const -{ - return false; -} - -void PluginContainer::startGuidedFix(unsigned int) const {} diff --git a/src/plugincontainer.h b/src/plugincontainer.h deleted file mode 100644 index d5ceca723..000000000 --- a/src/plugincontainer.h +++ /dev/null @@ -1,495 +0,0 @@ -#ifndef PLUGINCONTAINER_H -#define PLUGINCONTAINER_H - -#include "previewgenerator.h" - -class OrganizerCore; -class IUserInterface; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef Q_MOC_RUN -#include -#include -#include -#endif // Q_MOC_RUN -#include -#include - -class OrganizerProxy; - -/** - * @brief Class that wrap multiple requirements for a plugin together. THis - * class owns the requirements. - */ -class PluginRequirements -{ -public: - /** - * @return true if the plugin can be enabled (all requirements are met). - */ - bool canEnable() const; - - /** - * @return true if this is a core plugin, i.e. a plugin that should not be - * manually enabled or disabled by the user. - */ - bool isCorePlugin() const; - - /** - * @return true if this plugin has requirements (satisfied or not). - */ - bool hasRequirements() const; - - /** - * @return the proxy that created this plugin, if any. - */ - MOBase::IPluginProxy* proxy() const; - - /** - * @return the list of plugins this plugin proxies (if it's a proxy plugin). - */ - std::vector proxied() const; - - /** - * @return the master of this plugin, if any. - */ - MOBase::IPlugin* master() const; - - /** - * @return the plugins this plugin is master of. - */ - std::vector children() const; - - /** - * @return the list of problems to be resolved before enabling the plugin. - */ - std::vector problems() const; - - /** - * @return the name of the games (gameName()) this plugin can be used with, or an - * empty list if this plugin does not require particular games. - */ - QStringList requiredGames() const; - - /** - * @return the list of plugins currently enabled that would have to be disabled - * if this plugin was disabled. - */ - std::vector requiredFor() const; - -private: - // The list of "Core" plugins. - static const std::set s_CorePlugins; - - // Accumulator version for requiredFor() to avoid infinite recursion. - void requiredFor(std::vector& required, - std::set& visited) const; - - // Retrieve the requirements from the underlying plugin, take ownership on them - // and store them. We cannot do this in the constructor because we want to have a - // constructed object before calling init(). - void fetchRequirements(); - - // Set the master for this plugin. This is required to "fake" masters for proxied - // plugins. - void setMaster(MOBase::IPlugin* master); - - friend class OrganizerCore; - friend class PluginContainer; - - PluginContainer* m_PluginContainer; - MOBase::IPlugin* m_Plugin; - MOBase::IPluginProxy* m_PluginProxy; - MOBase::IPlugin* m_Master; - std::vector> m_Requirements; - OrganizerProxy* m_Organizer; - std::vector m_RequiredFor; - - PluginRequirements(PluginContainer* pluginContainer, MOBase::IPlugin* plugin, - OrganizerProxy* proxy, MOBase::IPluginProxy* pluginProxy); -}; - -/** - * - */ -class PluginContainer : public QObject, public MOBase::IPluginDiagnose -{ - - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - -private: - using PluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>>; - - using AccessPluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>>; - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - - /** - * This typedefs defines the order of plugin interface. This is increasing order of - * importance". - * - * @note IPlugin is the less important interface, followed by IPluginDiagnose and - * IPluginFileMapper as those are usually implemented together with another - * interface. Other interfaces are in a alphabetical order since it is unlikely a - * plugin will implement multiple ones. - */ - using PluginTypeOrder = boost::mp11::mp_transform< - std::add_pointer_t, - boost::mp11::mp_list< - MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, - MOBase::IPluginPreview, MOBase::IPluginProxy, MOBase::IPluginTool, - MOBase::IPluginDiagnose, MOBase::IPluginFileMapper, MOBase::IPlugin>>; - - static_assert(boost::mp11::mp_size::value == - boost::mp11::mp_size::value - 1); - -public: - /** - * @brief Retrieved the (localized) names of the various plugin interfaces. - * - * @return the (localized) names of the various plugin interfaces. - */ - static QStringList pluginInterfaces(); - -public: - PluginContainer(OrganizerCore* organizer); - virtual ~PluginContainer(); - - /** - * @brief Start the plugins. - * - * This function should not be called before MO2 is ready and plugins can be - * started, and will do the following: - * - connect the callbacks of the plugins, - * - set the parent widget for plugins that can have one, - * - notify plugins that MO2 has been started, including: - * - triggering a call to the "profile changed" callback for the initial profile, - * - triggering a call to the "user interface initialized" callback. - * - * @param userInterface The main user interface to use for the plugins. - */ - void startPlugins(IUserInterface* userInterface); - - /** - * @brief Load, unload or reload the plugin at the given path. - * - */ - void loadPlugin(QString const& filepath); - void unloadPlugin(QString const& filepath); - void reloadPlugin(QString const& filepath); - - /** - * @brief Load all plugins. - * - */ - void loadPlugins(); - - /** - * @brief Retrieve the list of plugins of the given type. - * - * @return the list of plugins of the specified type. - * - * @tparam T The type of plugin to retrieve. - */ - template - const std::vector& plugins() const - { - typename boost::fusion::result_of::at_key::type temp = - boost::fusion::at_key(m_Plugins); - return temp; - } - - /** - * @brief Check if a plugin implement a given interface. - * - * @param plugin The plugin to check. - * - * @return true if the plugin implements the interface, false otherwise. - * - * @tparam The interface type. - */ - template - bool implementInterface(MOBase::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 false; - } - - return qobject_cast(oPlugin); - } - - /** - * @brief Retrieve a plugin from its name or a corresponding non-IPlugin - * interface. - * - * @param t Name of the plugin to retrieve, or non-IPlugin interface. - * - * @return the corresponding plugin, or a null pointer. - * - * @note It is possible to have multiple plugins for the same name when - * dealing with proxied plugins (e.g. Python), in which case the - * most important one will be returned, as specified in PluginTypeOrder. - */ - MOBase::IPlugin* plugin(QString const& pluginName) const; - MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; - MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Find the game plugin corresponding to the given name. - * - * @param name The name of the game to find a plugin for (as returned by - * IPluginGame::gameName()). - * - * @return the game plugin for the given name, or a null pointer if no - * plugin exists for this game. - */ - MOBase::IPluginGame* game(const QString& name) const; - - /** - * @return the IPlugin interface to the currently managed game. - */ - MOBase::IPlugin* managedGame() const; - - /** - * @brief Check if the given plugin is enabled. - * - * @param plugin The plugin to check. - * - * @return true if the plugin is enabled, false otherwise. - */ - bool isEnabled(MOBase::IPlugin* plugin) const; - - // These are friendly methods that called isEnabled(plugin(arg)). - bool isEnabled(QString const& pluginName) const; - bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; - bool isEnabled(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Enable or disable a plugin. - * - * @param plugin The plugin to enable or disable. - * @param enable true to enable, false to disable. - * @param dependencies If true and enable is false, dependencies will also - * be disabled (see PluginRequirements::requiredFor). - */ - void setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies = true); - - /** - * @brief Retrieve the requirements for the given plugin. - * - * @param plugin The plugin to retrieve the requirements for. - * - * @return the requirements (as proxy) for the given plugin. - */ - const PluginRequirements& requirements(MOBase::IPlugin* plugin) const; - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - */ - QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; - - /** - * @brief Return the (localized) name of the most important interface implemented by - * the given plugin. - * - * The order of interfaces is defined in X. - * - * @param plugin The plugin to retrieve the interface for. - * - * @return the (localized) name of the most important interface implemented by this - * plugin. - */ - QString topImplementedInterface(MOBase::IPlugin* plugin) const; - - /** - * @return the preview generator. - */ - const PreviewGenerator& previewGenerator() const; - - /** - * @return the list of plugin file names, including proxied plugins. - */ - QStringList pluginFileNames() const; - -public: // IPluginDiagnose interface - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; - -signals: - - /** - * @brief Emitted when plugins are enabled or disabled. - */ - void pluginEnabled(MOBase::IPlugin*); - void pluginDisabled(MOBase::IPlugin*); - - /** - * @brief Emitted when plugins are registered or unregistered. - */ - void pluginRegistered(MOBase::IPlugin*); - void pluginUnregistered(MOBase::IPlugin*); - - void diagnosisUpdate(); - -private: - friend class PluginRequirements; - - // Unload all the plugins. - void unloadPlugins(); - - // Retrieve the organizer proxy for the given plugin. - OrganizerProxy* organizerProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the proxy plugin that instantiated the given plugin, or a null pointer - // if the plugin was not instantiated by a proxy. - MOBase::IPluginProxy* pluginProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the path to the file or folder corresponding to the plugin. - QString filepath(MOBase::IPlugin* plugin) const; - - // Load plugins from the given filepath using the given proxy. - std::vector loadProxied(const QString& filepath, - MOBase::IPluginProxy* proxy); - - // Load the Qt plugin from the given file. - QObject* loadQtPlugin(const QString& filepath); - - // check if a plugin is folder containing a Qt plugin, it is, return the path to the - // DLL containing the plugin in the folder, otherwise return an empty optional - // - // a Qt plugin folder is a folder with a DLL containing a library (not in a - // subdirectory), if multiple plugins are present, only the first one is returned - // - // extra DLLs are ignored by Qt so can be present in the folder - // - std::optional isQtPluginFolder(const QString& filepath) const; - - // See startPlugins for more details. This is simply an intermediate function - // that can be used when loading plugins after initialization. This uses the - // user interface in m_UserInterface. - void startPluginsImpl(const std::vector& plugins) const; - - /** - * @brief Unload the given plugin. - * - * This function is not public because it's kind of dangerous trying to unload - * plugin directly since some plugins are linked together. - * - * @param plugin The plugin to unload/unregister. - * @param object The QObject corresponding to the plugin. - */ - void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - * - * @note This function can be used to get implemented interfaces before registering - * a plugin. - */ - QStringList implementedInterfaces(QObject* plugin) const; - - /** - * @brief Check if a plugin implements a "better" interface than another - * one, as specified by PluginTypeOrder. - * - * @param lhs, rhs The plugin to compare. - * - * @return true if the left plugin implements a better interface than the right - * one, false otherwise (or if both implements the same interface). - */ - bool isBetterInterface(QObject* lhs, QObject* rhs) const; - - /** - * @brief Find the QObject* corresponding to the given plugin. - * - * @param plugin The plugin to find the QObject* for. - * - * @return a QObject* for the given plugin. - */ - QObject* as_qobject(MOBase::IPlugin* plugin) const; - - /** - * @brief Initialize a plugin. - * - * @param plugin The plugin to initialize. - * @param proxy The proxy that created this plugin (can be null). - * @param skipInit If true, IPlugin::init() will not be called, regardless - * of the state of the container. - * - * @return true if the plugin was initialized correctly, false otherwise. - */ - bool initPlugin(MOBase::IPlugin* plugin, MOBase::IPluginProxy* proxy, bool skipInit); - - void registerGame(MOBase::IPluginGame* game); - void unregisterGame(MOBase::IPluginGame* game); - - MOBase::IPlugin* registerPlugin(QObject* pluginObj, const QString& fileName, - MOBase::IPluginProxy* proxy); - - // Core organizer, can be null (e.g. on first MO2 startup). - OrganizerCore* m_Organizer; - - // Main user interface, can be null until MW has been initialized. - IUserInterface* m_UserInterface; - - PluginMap m_Plugins; - - // This maps allow access to IPlugin* from name or diagnose/mapper object. - AccessPluginMap m_AccessPlugins; - - std::map m_Requirements; - - std::map m_SupportedGames; - QStringList m_FailedPlugins; - std::vector m_PluginLoaders; - - PreviewGenerator m_PreviewGenerator; - - QFile m_PluginsCheck; -}; - -#endif // PLUGINCONTAINER_H diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp new file mode 100644 index 000000000..8766fefc2 --- /dev/null +++ b/src/pluginmanager.cpp @@ -0,0 +1,707 @@ +#include "pluginmanager.h" + +#include +#include + +#include +#include +#include +#include + +#include "extensionmanager.h" +#include "iuserinterface.h" +#include "organizercore.h" +#include "organizerproxy.h" +#include "previewgenerator.h" +#include "proxyqt.h" + +using namespace MOBase; +namespace bf = boost::fusion; + +// localized names + +template +struct PluginTypeName; + +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Plugin"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Diagnose"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Game"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Installer"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Mod Page"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Preview"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("Tool"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return PluginManager::tr("File Mapper"); } +}; + +// PluginDetails + +PluginDetails::PluginDetails(PluginManager* manager, PluginExtension const& extension, + IPlugin* plugin, OrganizerProxy* proxy) + : m_manager(manager), m_extension(&extension), m_plugin(plugin), m_organizer(proxy) +{} + +void PluginDetails::fetchRequirements() +{ + m_requirements = m_plugin->requirements(); +} + +std::vector PluginDetails::problems() const +{ + std::vector result; + for (auto& requirement : m_requirements) { + if (auto p = requirement->check(m_organizer)) { + result.push_back(*p); + } + } + return result; +} + +bool PluginDetails::canEnable() const +{ + return problems().empty(); +} + +bool PluginDetails::hasRequirements() const +{ + return !m_requirements.empty(); +} + +QStringList PluginDetails::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 {}; +} + +// PluginManager + +QStringList PluginManager::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; +} + +PluginManager::PluginManager(ExtensionManager const& manager, OrganizerCore* core) + : m_extensions{manager}, m_core{core} +{ + m_loaders = makeLoaders(); +} + +QStringList PluginManager::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 PluginManager::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 PluginManager::topImplementedInterface(IPlugin* plugin) const +{ + auto interfaces = implementedInterfaces(plugin); + return interfaces.isEmpty() ? "" : interfaces[0]; +} + +bool PluginManager::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; +} + +MOBase::IPluginGame* PluginManager::game(const QString& name) const +{ + auto iter = m_supportedGames.find(name); + if (iter != m_supportedGames.end()) { + return iter->second; + } else { + return nullptr; + } +} + +MOBase::IPluginGame* PluginManager::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_core->managedGame()); +} + +MOBase::IPlugin* PluginManager::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* PluginManager::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* PluginManager::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 PluginManager::isEnabled(MOBase::IPlugin* plugin) const +{ + // check if it is a game plugin + if (implementInterface(plugin)) { + return plugin == m_core->managedGame(); + } + + return m_extensions.isEnabled(details(plugin).extension()); +} + +bool PluginManager::isEnabled(QString const& pluginName) const +{ + IPlugin* p = plugin(pluginName); + return p ? isEnabled(p) : false; +} + +bool PluginManager::isEnabled(MOBase::IPluginDiagnose* diagnose) const +{ + IPlugin* p = plugin(diagnose); + return p ? isEnabled(p) : false; +} + +bool PluginManager::isEnabled(MOBase::IPluginFileMapper* mapper) const +{ + IPlugin* p = plugin(mapper); + return p ? isEnabled(p) : false; +} + +QObject* PluginManager::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 PluginManager::initPlugin(PluginExtension const& extension, IPlugin* plugin, + 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_core) { + proxy = new OrganizerProxy(m_core, this, plugin); + proxy->setParent(as_qobject(plugin)); + } + + auto [it, bl] = + m_details.emplace(plugin, PluginDetails(this, extension, plugin, proxy)); + + if (!m_core || skipInit) { + return true; + } + + if (!plugin->init(proxy)) { + log::warn("plugin failed to initialize"); + return false; + } + + // Update requirements: + it->second.fetchRequirements(); + + return true; +} + +IPlugin* PluginManager::registerPlugin(const PluginExtension& extension, + QObject* plugin, + QList const& pluginGroup) +{ + // 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; + } + + // we check if there is already a plugin with this name, if there is one, it must be + // from the same group + 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 group that's ok, we just need to skip + // initialization + if (pluginGroup.contains(as_qobject(other))) { + + // 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(), details(other).extension().metadata().name(), + extension.metadata().name()); + 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); + m_allPlugins.push_back(pluginObj); + + plugin->setParent(this); + + if (m_core) { + m_core->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([&, diagnose]() { + emit diagnosePluginInvalidated(diagnose); + }); + } + } + + { // 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(extension, modPage, 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(extension, game, 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(extension, tool, skipInit)) { + bf::at_key(m_plugins).push_back(tool); + emit pluginRegistered(tool); + return tool; + } + } + + { // installer plugins + IPluginInstaller* installer = qobject_cast(plugin); + if (initPlugin(extension, installer, skipInit)) { + bf::at_key(m_plugins).push_back(installer); + if (m_core) { + installer->setInstallationManager(m_core->installationManager()); + } + emit pluginRegistered(installer); + return installer; + } + } + + { // preview plugins + IPluginPreview* preview = qobject_cast(plugin); + if (initPlugin(extension, preview, skipInit)) { + bf::at_key(m_plugins).push_back(preview); + return preview; + } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin* dummy = qobject_cast(plugin); + if (initPlugin(extension, dummy, skipInit)) { + bf::at_key(m_plugins).push_back(dummy); + emit pluginRegistered(dummy); + return dummy; + } + } + + return nullptr; +} + +void PluginManager::loadPlugins() +{ + // TODO: order based on dependencies + for (auto& extension : m_extensions.extensions()) { + if (auto* pluginExtension = dynamic_cast(extension.get())) { + loadPlugins(*pluginExtension); + } + } +} + +bool PluginManager::loadPlugins(const MOBase::PluginExtension& extension) +{ + unloadPlugins(); + + // load plugins + QList> objects; + for (auto& loader : m_loaders) { + objects.append(loader->load(extension)); + } + + for (auto& objectGroup : objects) { + for (auto* object : objectGroup) { + registerPlugin(extension, object, objectGroup); + } + } + + // TODO: move this elsewhere, e.g., in core + if (m_core) { + bf::at_key(m_plugins).push_back(m_core); + m_core->connectPlugins(this); + } + return true; +} + +void PluginManager::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_core->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 + // + details(plugin).m_organizer->disconnectSignals(); + + // do this at the end + m_details.erase(plugin); +} + +bool PluginManager::unloadPlugins(const MOBase::PluginExtension& extension) +{ + std::vector objectsToDelete; + + // first we clear the internal structures, disconnect signales, etc. + { + auto& objects = bf::at_key(m_plugins); + for (auto it = objects.begin(); it != objects.end();) { + auto* plugin = qobject_cast(*it); + if (&details(plugin).extension() == &extension) { + unloadPlugin(plugin, *it); + objectsToDelete.push_back(*it); + it = objects.erase(it); + } else { + ++it; + } + } + } + + // then we let the loader unload the plugin + for (auto& loader : m_loaders) { + loader->unload(extension); + } + + // manual delete (for safety) + for (auto* object : objectsToDelete) { + object->deleteLater(); + } + + return true; +} + +void PluginManager::unloadPlugins() +{ + if (m_core) { + // 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_core->settings().plugins().clearPlugins(); + } + + bf::for_each(m_plugins, [](auto& t) { + t.second.clear(); + }); + bf::for_each(m_accessPlugins, [](auto& t) { + t.second.clear(); + }); + + m_details.clear(); + m_supportedGames.clear(); + + for (auto& loader : m_loaders) { + // TODO: + // loader->unloadAll(); + } +} + +bool PluginManager::reloadPlugins(const MOBase::PluginExtension& extension) +{ + unloadPlugins(extension); + return loadPlugins(extension); +} + +void PluginManager::registerGame(MOBase::IPluginGame* game) +{ + m_supportedGames.insert({game->gameName(), game}); +} + +void PluginManager::unregisterGame(MOBase::IPluginGame* game) +{ + m_supportedGames.erase(game->gameName()); +} + +void PluginManager::startPlugins(IUserInterface* userInterface) +{ + m_userInterface = userInterface; + startPluginsImpl(plugins()); +} + +void PluginManager::startPluginsImpl(const std::vector& plugins) const +{ + if (m_userInterface) { + for (auto* plugin : plugins) { + 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_core) { + for (auto* object : plugins) { + auto* plugin = qobject_cast(object); + auto* oproxy = details(plugin).m_organizer; + oproxy->connectSignals(); + oproxy->m_ProfileChanged(nullptr, m_core->currentProfile()); + + if (m_userInterface) { + oproxy->m_UserInterfaceInitialized(m_userInterface->mainWindow()); + } + } + } +} + +const PreviewGenerator& PluginManager::previewGenerator() const +{ + return *m_previews; +} + +PluginManager::PluginLoaderDeleter::PluginLoaderDeleter(QPluginLoader* qPluginLoader) + : m_qPluginLoader(qPluginLoader) +{} + +void PluginManager::PluginLoaderDeleter::operator()(MOBase::IPluginLoader* loader) const +{ + // if there is a QPluginLoader, the loader is responsible for unloading the plugin + if (m_qPluginLoader) { + m_qPluginLoader->unload(); + delete m_qPluginLoader; + } else { + delete loader; + } +} + +std::vector PluginManager::makeLoaders() +{ + std::vector loaders; + + // create the Qt loader + loaders.push_back(PluginLoaderPtr(new ProxyQtLoader(), PluginLoaderDeleter{})); + + // load the python proxy + { + auto pluginLoader = std::make_unique( + QCoreApplication::applicationDirPath() + "/proxies/python/python_proxy.dll", + this); + + if (auto* object = pluginLoader->instance(); object) { + auto loader = qobject_cast(object); + loaders.push_back( + PluginLoaderPtr(loader, PluginLoaderDeleter{pluginLoader.release()})); + } + } + + return loaders; +} diff --git a/src/pluginmanager.h b/src/pluginmanager.h new file mode 100644 index 000000000..604750ab2 --- /dev/null +++ b/src/pluginmanager.h @@ -0,0 +1,345 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include + +#ifndef Q_MOC_RUN +#include +#include +#include +#endif // Q_MOC_RUN + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "previewgenerator.h" + +class ExtensionManager; +class IUserInterface; +class OrganizerCore; +class OrganizerProxy; +class PluginManager; + +// class containing extra useful information for plugins +// +class PluginDetails +{ +public: + // check if the plugin can be enabled (all requirements are met) + // + bool canEnable() const; + + // check if this plugin has requirements (satisfied or not) + // + bool hasRequirements() const; + + // the organizer proxy for this plugin + // + const auto* proxy() const { return m_organizer; } + + // the extension containing this plugin + // + const MOBase::PluginExtension& extension() const { return *m_extension; } + + // retrieve the list of problems to be resolved before enabling the plugin + // + std::vector problems() const; + + // retrieve the name of the games (gameName()) this plugin can be used with, or an + // empty list if this plugin does not require particular games. + // + QStringList requiredGames() const; + +private: + // retrieve the requirements from the underlying plugin, take ownership on them and + // store them + // + // we cannot do this in the constructor because we want to have a constructed object + // before calling init() + // + void fetchRequirements(); + + friend class PluginManager; + + PluginManager* m_manager; + MOBase::IPlugin* m_plugin; + const MOBase::PluginExtension* m_extension; + std::vector> m_requirements; + OrganizerProxy* m_organizer; + + PluginDetails(PluginManager* manager, MOBase::PluginExtension const& extension, + MOBase::IPlugin* plugin, OrganizerProxy* proxy); +}; + +// manager for plugins +// +class PluginManager : public QObject +{ + Q_OBJECT +public: + // retrieve the (localized) names of the various plugin interfaces + // + static QStringList pluginInterfaces(); + +public: + PluginManager(ExtensionManager const& manager, OrganizerCore* core); + +public: // access + // retrieve the list of plugins of a given type + // + // - if no type is specified, return the list of all plugins as IPlugin + // - if IPlugin is specified, returns only plugins that only extends IPlugin + // + // + template + const auto& plugins() const + { + if constexpr (std::is_void_v) { + + return m_allPlugins; + } else { + return boost::fusion::at_key(m_plugins); + } + } + + // retrieve the details for the given plugin + // + const auto& details(MOBase::IPlugin* plugin) const { return m_details.at(plugin); } + + // retrieve the (localized) names of interfaces implemented by the given plugin + // + QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; + + // retrieve the (localized) name of the most important interface implemented by the + // given plugin + // + QString topImplementedInterface(MOBase::IPlugin* plugin) const; + + // retrieve a plugin from its name or a corresponding non-IPlugin interface + // + MOBase::IPlugin* plugin(QString const& pluginName) const; + MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; + MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; + + // find the game plugin corresponding to the given name, returns a null pointer if no + // game exists + // + MOBase::IPluginGame* game(const QString& name) const; + + // retrieve the IPlugin interface to the currently managed game. + // + MOBase::IPluginGame* managedGame() const; + + // retrieve the preview generator + // + const PreviewGenerator& previewGenerator() const; + +public: // checks + // check if a plugin implement a given plugin interface + // + template + bool implementInterface(MOBase::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 false; + } + + return qobject_cast(oPlugin); + } + + // check if a plugin is enabled + // + bool isEnabled(MOBase::IPlugin* plugin) const; + bool isEnabled(QString const& pluginName) const; + bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; + bool isEnabled(MOBase::IPluginFileMapper* mapper) const; + +public: // load + // load all plugins from the extension manager + // + void loadPlugins(); + + // load plugins from the given extension + // + bool loadPlugins(const MOBase::PluginExtension& extension); + bool unloadPlugins(const MOBase::PluginExtension& extension); + bool reloadPlugins(const MOBase::PluginExtension& extension); + + // start the plugins + // + // this function should not be called before MO2 is ready and plugins can be started, + // and will do the following: + // - connect the callbacks of the plugins, + // - set the parent widget for plugins that can have one, + // - notify plugins that MO2 has been started, including: + // - triggering a call to the "profile changed" callback for the initial profile, + // - triggering a call to the "user interface initialized" callback. + // + void startPlugins(IUserInterface* userInterface); + +signals: + + // emitted when plugins are enabled or disabled + // + void pluginEnabled(MOBase::IPlugin*); + void pluginDisabled(MOBase::IPlugin*); + + // emitted when plugins are registered or unregistered + // + void pluginRegistered(MOBase::IPlugin*); + void pluginUnregistered(MOBase::IPlugin*); + + // enmitted when a diagnose plugin invalidates() itself + // + void diagnosePluginInvalidated(MOBase::IPluginDiagnose*); + +private: + friend class PluginDetails; + +private: + using PluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + using AccessPluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + // type defining the order of plugin interface, in increasing order of importance + // + // IPlugin is the less important interface, followed by IPluginDiagnose and + // IPluginFileMapper as those are usually implemented together with another interface + // + // other interfaces are in a alphabetical order since it is unlikely a plugin will + // implement multiple ones + // + using PluginTypeOrder = boost::mp11::mp_transform< + std::add_pointer_t, + boost::mp11::mp_list>; + static_assert(boost::mp11::mp_size::value == + boost::mp11::mp_size::value - 1); + +private: + // retrieve the (localized) names of interfaces implemented by the given plugin + // + // this function can be used to get implemented interfaces before registering a plugin + // + QStringList implementedInterfaces(QObject* plugin) const; + + // check if the left plugin implements a "better" interface than the right one, as + // specified by PluginTypeOrder + // + bool isBetterInterface(QObject* lhs, QObject* rhs) const; + + // find the QObject* corresponding to the given plugin + // + QObject* as_qobject(MOBase::IPlugin* plugin) const; + + // see startPlugins for more details + // + // this is simply an intermediate function that can be used when loading plugins after + // initialization which uses the user interface in m_userInterface + // + void startPluginsImpl(const std::vector& plugins) const; + + // unload the given plugin + // + // this function is not public because it's kind of dangerous trying to unload plugin + // directly since some plugins are linked together + // + void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); + + // unload all plugins + // + void unloadPlugins(); + + // register/unregister a game plugin + // + void registerGame(MOBase::IPluginGame* game); + void unregisterGame(MOBase::IPluginGame* game); + + // initialize a plugin and creates approriate PluginDetails for it + // + bool initPlugin(MOBase::PluginExtension const& extension, MOBase::IPlugin* plugin, + bool skipInit); + + // register a plugin for the given extension + // + MOBase::IPlugin* registerPlugin(const MOBase::PluginExtension& extension, + QObject* pluginObj, + QList const& pluginGroup); + +private: + struct PluginLoaderDeleter + { + PluginLoaderDeleter(QPluginLoader* qPluginLoader = nullptr); + + void operator()(MOBase::IPluginLoader* loader) const; + + private: + QPluginLoader* m_qPluginLoader; + }; + + using PluginLoaderPtr = std::unique_ptr; + + // create the loaders + // + std::vector makeLoaders(); + +private: + const ExtensionManager& m_extensions; + + // core organizer, can be null (e.g. on first MO2 startup). + OrganizerCore* m_core; + + // main user interface, can be null until MW has been initialized. + IUserInterface* m_userInterface; + + // plugin loaders + std::vector m_loaders; + + PluginMap m_plugins; + std::vector m_allPlugins; + + // this maps allow access to IPlugin* from name or diagnose/mapper object, and from + // game + AccessPluginMap m_accessPlugins; + std::map m_supportedGames; + + // details for plugins + std::map m_details; + + // the preview generator + PreviewGenerator* m_previews; +}; + +#endif diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index ba73c0650..a4a24b41a 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -25,19 +25,19 @@ along with Mod Organizer. If not, see . #include #include -#include "plugincontainer.h" +#include "pluginmanager.h" using namespace MOBase; -PreviewGenerator::PreviewGenerator(const PluginContainer& pluginContainer) - : m_PluginContainer(pluginContainer) +PreviewGenerator::PreviewGenerator(const PluginManager& pluginManager) + : m_PluginManager(pluginManager) { m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; } bool PreviewGenerator::previewSupported(const QString& fileExtension) const { - auto& previews = m_PluginContainer.plugins(); + auto& previews = m_PluginManager.plugins(); for (auto* preview : previews) { if (preview->supportedExtensions().contains(fileExtension)) { return true; @@ -49,9 +49,9 @@ bool PreviewGenerator::previewSupported(const QString& fileExtension) const QWidget* PreviewGenerator::genPreview(const QString& fileName) const { const QString ext = QFileInfo(fileName).suffix().toLower(); - auto& previews = m_PluginContainer.plugins(); + auto& previews = m_PluginManager.plugins(); for (auto* preview : previews) { - if (m_PluginContainer.isEnabled(preview) && + if (m_PluginManager.isEnabled(preview) && preview->supportedExtensions().contains(ext)) { return preview->genFilePreview(fileName, m_MaxSize); } diff --git a/src/previewgenerator.h b/src/previewgenerator.h index dd0d2cd4f..8a045857e 100644 --- a/src/previewgenerator.h +++ b/src/previewgenerator.h @@ -26,19 +26,19 @@ along with Mod Organizer. If not, see . #include #include -class PluginContainer; +class PluginManager; class PreviewGenerator { public: - PreviewGenerator(const PluginContainer& pluginContainer); + PreviewGenerator(const PluginManager& pluginManager); bool previewSupported(const QString& fileExtension) const; QWidget* genPreview(const QString& fileName) const; private: - const PluginContainer& m_PluginContainer; + const PluginManager& m_PluginManager; QSize m_MaxSize; }; diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index f6b23a100..406eb4fc6 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -11,8 +11,8 @@ using namespace MOBase; -ProblemsDialog::ProblemsDialog(const PluginContainer& pluginContainer, QWidget* parent) - : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginContainer(pluginContainer), +ProblemsDialog::ProblemsDialog(const PluginManager& pluginManager, QWidget* parent) + : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginManager(pluginManager), m_hasProblems(false) { ui->setupUi(this); @@ -41,13 +41,13 @@ void ProblemsDialog::runDiagnosis() m_hasProblems = false; ui->problemsWidget->clear(); - for (IPluginDiagnose* diagnose : m_PluginContainer.plugins()) { - if (!m_PluginContainer.isEnabled(diagnose)) { + for (IPluginDiagnose* diagnose : m_PluginManager.plugins()) { + if (!m_PluginManager.isEnabled(diagnose)) { continue; } std::vector activeProblems = diagnose->activeProblems(); - foreach (unsigned int key, activeProblems) { + for (const auto key : activeProblems) { QTreeWidgetItem* newItem = new QTreeWidgetItem(); newItem->setText(0, diagnose->shortDescription(key)); newItem->setData(0, Qt::UserRole, diagnose->fullDescription(key)); diff --git a/src/problemsdialog.h b/src/problemsdialog.h index 288f50491..25ab83763 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -10,14 +10,14 @@ namespace Ui class ProblemsDialog; } -class PluginContainer; +class PluginManager; class ProblemsDialog : public QDialog { Q_OBJECT public: - explicit ProblemsDialog(PluginContainer const& pluginContainer, QWidget* parent = 0); + explicit ProblemsDialog(PluginManager const& pluginContainer, QWidget* parent = 0); ~ProblemsDialog(); // also saves and restores geometry @@ -37,7 +37,7 @@ private slots: private: Ui::ProblemsDialog* ui; - const PluginContainer& m_PluginContainer; + const PluginManager& m_PluginManager; bool m_hasProblems; }; diff --git a/src/proxyqt.cpp b/src/proxyqt.cpp new file mode 100644 index 000000000..b1ec86dac --- /dev/null +++ b/src/proxyqt.cpp @@ -0,0 +1,70 @@ +#include "proxyqt.h" + +#include + +using namespace MOBase; + +void ProxyQtLoader::QPluginLoaderDeleter::operator()(QPluginLoader* loader) const +{ + if (loader) { + loader->unload(); + delete loader; + } +} + +ProxyQtLoader::ProxyQtLoader() {} + +bool ProxyQtLoader::initialize(QString& errorMessage) +{ + return true; +} + +QList> ProxyQtLoader::load(const MOBase::PluginExtension& extension) +{ + QList> plugins; + + // TODO - retrieve plugins from extension instead of listing them + + QDirIterator iter(QDir(extension.directory(), {}, QDir::NoSort, + 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) + auto loader = QPluginLoaderPtr(new QPluginLoader(filePath)); + if (loader->metaData().isEmpty()) { + log::debug("no metadata found in '{}', skipping", filePath); + continue; + } + + QObject* instance = loader->instance(); + if (!instance) { + log::warn("failed to load plugin from '{}', skipping", filePath); + continue; + } + + m_loaders[&extension].push_back(std::move(loader)); + plugins.push_back({instance}); + } + + return plugins; +} + +void ProxyQtLoader::unload(const MOBase::PluginExtension& extension) +{ + if (auto it = m_loaders.find(&extension); it != m_loaders.end()) { + m_loaders.erase(it); + } +} + +void ProxyQtLoader::unloadAll() +{ + m_loaders.clear(); +} diff --git a/src/proxyqt.h b/src/proxyqt.h new file mode 100644 index 000000000..80c7fb95b --- /dev/null +++ b/src/proxyqt.h @@ -0,0 +1,32 @@ +#ifndef PROXYQTLOADER_H +#define PROXYQTLOADER_H + +#include + +#include + +class ProxyQtLoader : public MOBase::IPluginLoader +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPluginLoader) + Q_PLUGIN_METADATA(IID "org.mo2.ProxyQt") + +public: + ProxyQtLoader(); + + bool initialize(QString& errorMessage) override; + QList> load(const MOBase::PluginExtension& extension) override; + void unload(const MOBase::PluginExtension& extension) override; + void unloadAll() override; + +private: + struct QPluginLoaderDeleter + { + void operator()(QPluginLoader*) const; + }; + using QPluginLoaderPtr = std::unique_ptr; + + std::map> m_loaders; +}; + +#endif diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 7762c9004..22207fec8 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -26,7 +26,7 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" #include "settings.h" #include "shared/util.h" #include "updatedialog.h" @@ -81,9 +81,9 @@ void SelfUpdater::setUserInterface(QWidget* widget) m_Parent = widget; } -void SelfUpdater::setPluginContainer(PluginContainer* pluginContainer) +void SelfUpdater::setPluginManager(PluginManager* pluginManager) { - m_Interface->setPluginContainer(pluginContainer); + m_Interface->setPluginManager(pluginManager); } void SelfUpdater::testForUpdate(const Settings& settings) diff --git a/src/selfupdater.h b/src/selfupdater.h index cc68ad275..8800e479f 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -27,7 +27,7 @@ along with Mod Organizer. If not, see . class Archive; class NexusInterface; -class PluginContainer; +class PluginManager; namespace MOBase { class IPluginGame; @@ -82,7 +82,7 @@ class SelfUpdater : public QObject void setUserInterface(QWidget* widget); - void setPluginContainer(PluginContainer* pluginContainer); + void setPluginManager(PluginManager* pluginManager); /** * @brief request information about the current version diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index e79847cc8..2af2f0650 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -30,11 +30,12 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, - ThemeManager const& manager, Settings& settings, - QWidget* parent) +SettingsDialog::SettingsDialog(PluginManager& pluginManager, + 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) + m_settings(settings), m_exit(Exit::None), m_pluginManager(&pluginManager) { ui->setupUi(this); @@ -49,16 +50,11 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr( - new PluginsSettingsTab(settings, m_pluginContainer, *this))); + new PluginsSettingsTab(settings, *m_pluginManager, *this))); m_tabs.push_back( std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); } -PluginContainer* SettingsDialog::pluginContainer() -{ - return m_pluginContainer; -} - QWidget* SettingsDialog::parentWidgetForDialogs() { if (isVisible()) { diff --git a/src/settingsdialog.h b/src/settingsdialog.h index ad45bc6f9..53ee31919 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -23,8 +23,7 @@ along with Mod Organizer. If not, see . #include "shared/util.h" #include "tutorabledialog.h" -class ThemeManager; -class PluginContainer; +class PluginManager; class Settings; class SettingsDialog; namespace Ui @@ -64,7 +63,9 @@ class SettingsDialog : public MOBase::TutorableDialog friend class SettingsTab; public: - explicit SettingsDialog(PluginContainer* pluginContainer, ThemeManager const& manager, + explicit SettingsDialog(PluginManager& pluginManager, + ThemeManager const& themeManager, + TranslationManager const& translationManager, Settings& settings, QWidget* parent = 0); ~SettingsDialog(); @@ -75,7 +76,6 @@ class SettingsDialog : public MOBase::TutorableDialog */ QString getColoredButtonStyleSheet() const; - PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); void setExitNeeded(ExitFlags e); @@ -91,7 +91,7 @@ public slots: Settings& m_settings; std::vector> m_tabs; ExitFlags m_exit; - PluginContainer* m_pluginContainer; + PluginManager* m_pluginManager; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index db00ca081..6853c4155 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -3,69 +3,52 @@ #include "ui_settingsdialog.h" #include -#include "disableproxyplugindialog.h" #include "organizercore.h" -#include "plugincontainer.h" +#include "pluginmanager.h" using namespace MOBase; -PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginContainer, +struct PluginExtensionComparator +{ + bool operator()(const PluginExtension* lhs, const PluginExtension* rhs) const + { + return lhs->metadata().name().compare(rhs->metadata().name(), Qt::CaseInsensitive); + } +}; + +PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginManager& pluginManager, SettingsDialog& d) - : SettingsTab(s, d), m_pluginContainer(pluginContainer) + : SettingsTab(s, d), m_pluginManager(&pluginManager) { ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); - - // Create top-level tree widget: - QStringList pluginInterfaces = m_pluginContainer->pluginInterfaces(); - pluginInterfaces.sort(Qt::CaseInsensitive); - std::map topItems; - for (QString interfaceName : pluginInterfaces) { - auto* item = new QTreeWidgetItem(ui->pluginsList, {interfaceName}); - item->setFlags(item->flags() & ~Qt::ItemIsSelectable); - auto font = item->font(0); - font.setBold(true); - item->setFont(0, font); - topItems[interfaceName] = item; - item->setExpanded(true); - item->setFlags(item->flags() & ~Qt::ItemIsSelectable); - } ui->pluginsList->setHeaderHidden(true); // display plugin settings - QSet handledNames; - for (IPlugin* plugin : settings().plugins().plugins()) { - if (handledNames.contains(plugin->name()) || - m_pluginContainer->requirements(plugin).master()) { - continue; - } + std::map, PluginExtensionComparator> + pluginsPerExtension; + + for (IPlugin* plugin : pluginManager.plugins()) { + pluginsPerExtension[&pluginManager.details(plugin).extension()].push_back(plugin); + } + + for (auto& [extension, plugins] : pluginsPerExtension) { + + QTreeWidgetItem* extensionItem = new QTreeWidgetItem(); + extensionItem->setData(0, Qt::DisplayRole, extension->metadata().name()); + ui->pluginsList->addTopLevelItem(extensionItem); - QTreeWidgetItem* listItem = new QTreeWidgetItem( - topItems.at(m_pluginContainer->topImplementedInterface(plugin))); - listItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - listItem->setData(0, PluginRole, QVariant::fromValue((void*)plugin)); - listItem->setData(0, SettingsRole, settings().plugins().settings(plugin->name())); - listItem->setData(0, DescriptionsRole, - settings().plugins().descriptions(plugin->name())); + QSet handledNames; // Handle child item: - auto children = m_pluginContainer->requirements(plugin).children(); - for (auto* child : children) { - QTreeWidgetItem* childItem = new QTreeWidgetItem(listItem); - childItem->setData(0, Qt::DisplayRole, child->localizedName()); - childItem->setData(0, PluginRole, QVariant::fromValue((void*)child)); - childItem->setData(0, SettingsRole, settings().plugins().settings(child->name())); - childItem->setData(0, DescriptionsRole, - settings().plugins().descriptions(child->name())); - - handledNames.insert(child->name()); - } + for (auto* plugin : plugins) { + if (handledNames.contains(plugin->name())) { + continue; + } - handledNames.insert(plugin->name()); - } + QTreeWidgetItem* pluginItem = new QTreeWidgetItem(extensionItem); + pluginItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - for (auto& [k, item] : topItems) { - if (item->childCount() == 0) { - item->setHidden(true); + handledNames.insert(plugin->name()); } } @@ -82,9 +65,6 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta [&](auto* current, auto* previous) { on_pluginsList_currentItemChanged(current, previous); }); - QObject::connect(ui->enabledCheckbox, &QCheckBox::clicked, [&](bool checked) { - on_checkboxEnabled_clicked(checked); - }); QShortcut* delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); @@ -95,8 +75,8 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta filterPluginList(); }); - updateListItems(); - filterPluginList(); + // updateListItems(); + // filterPluginList(); } void PluginsSettingsTab::updateListItems() @@ -107,8 +87,8 @@ void PluginsSettingsTab::updateListItems() auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); - bool inactive = !m_pluginContainer->implementInterface(plugin) && - !m_pluginContainer->isEnabled(plugin); + bool inactive = !m_pluginManager->implementInterface(plugin) && + !m_pluginManager->isEnabled(plugin); auto font = item->font(0); font.setItalic(inactive); @@ -138,11 +118,6 @@ void PluginsSettingsTab::filterPluginList() bool match = m_filter.matches([plugin](const QRegularExpression& regex) { return regex.match(plugin->localizedName()).hasMatch(); }); - for (auto* child : m_pluginContainer->requirements(plugin).children()) { - match = match || m_filter.matches([child](const QRegularExpression& regex) { - return regex.match(child->localizedName()).hasMatch(); - }); - } if (match) { found = true; @@ -209,77 +184,6 @@ void PluginsSettingsTab::closing() storeSettings(ui->pluginsList->currentItem()); } -void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) -{ - // Retrieve the plugin: - auto* item = ui->pluginsList->currentItem(); - if (!item || !item->data(0, PluginRole).isValid()) { - return; - } - IPlugin* plugin = this->plugin(item); - const auto& requirements = m_pluginContainer->requirements(plugin); - - // User wants to enable: - if (checked) { - m_pluginContainer->setEnabled(plugin, true, false); - } else { - // Custom check for proxy + current game: - if (m_pluginContainer->implementInterface(plugin)) { - - // Current game: - auto* game = m_pluginContainer->managedGame(); - if (m_pluginContainer->requirements(game).proxy() == plugin) { - QMessageBox::warning(parentWidget(), QObject::tr("Cannot disable plugin"), - QObject::tr("The '%1' plugin is used by the current game " - "plugin and cannot disabled.") - .arg(plugin->localizedName()), - QMessageBox::Ok); - ui->enabledCheckbox->setChecked(true); - return; - } - - // Check the proxied plugins: - auto proxied = requirements.proxied(); - if (!proxied.empty()) { - DisableProxyPluginDialog dialog(plugin, proxied, parentWidget()); - if (dialog.exec() != QDialog::Accepted) { - ui->enabledCheckbox->setChecked(true); - return; - } - } - } - - // Check if the plugins is required for other plugins: - auto requiredFor = requirements.requiredFor(); - if (!requiredFor.empty()) { - QStringList pluginNames; - for (auto& p : requiredFor) { - pluginNames.append(p->localizedName()); - } - pluginNames.sort(); - QString message = - QObject::tr("

      Disabling the '%1' plugin will also disable the following " - "plugins:

        %1

      Do you want to continue?

      ") - .arg(plugin->localizedName()) - .arg("
    • " + pluginNames.join("
    • ") + "
    • "); - if (QMessageBox::warning(parentWidget(), QObject::tr("Really disable plugin?"), - message, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - ui->enabledCheckbox->setChecked(true); - return; - } - } - m_pluginContainer->setEnabled(plugin, false, true); - } - - // Proxy was disabled / enabled, need restart: - if (m_pluginContainer->implementInterface(plugin)) { - dialog().setExitNeeded(Exit::Restart); - } - - updateListItems(); -} - void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { @@ -298,21 +202,15 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* curr // Checkbox, do not show for children or game plugins, disable // if the plugin cannot be enabled. ui->enabledCheckbox->setVisible( - !m_pluginContainer->implementInterface(plugin) && + !m_pluginManager->implementInterface(plugin) && plugin->master().isEmpty()); - bool enabled = m_pluginContainer->isEnabled(plugin); - auto& requirements = m_pluginContainer->requirements(plugin); + bool enabled = m_pluginManager->isEnabled(plugin); + auto& requirements = m_pluginManager->details(plugin); auto problems = requirements.problems(); - if (m_pluginContainer->requirements(plugin).isCorePlugin()) { - ui->enabledCheckbox->setDisabled(true); - ui->enabledCheckbox->setToolTip( - QObject::tr("This plugin is required for Mod Organizer to work properly and " - "cannot be disabled.")); - } // Plugin is enable or can be enabled. - else if (enabled || problems.empty()) { + if (enabled || problems.empty()) { ui->enabledCheckbox->setDisabled(false); ui->enabledCheckbox->setToolTip(""); ui->enabledCheckbox->setChecked(enabled); diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 416f457ae..218bd7469 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -9,7 +9,7 @@ class PluginsSettingsTab : public SettingsTab { public: - PluginsSettingsTab(Settings& settings, PluginContainer* pluginContainer, + PluginsSettingsTab(Settings& settings, PluginManager& pluginManager, SettingsDialog& dialog); void update(); @@ -18,7 +18,6 @@ class PluginsSettingsTab : public SettingsTab private: void on_pluginsList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - void on_checkboxEnabled_clicked(bool checked); void deleteBlacklistItem(); void storeSettings(QTreeWidgetItem* pluginItem); @@ -49,7 +48,7 @@ private slots: }; private: - PluginContainer* m_pluginContainer; + PluginManager* m_pluginManager; MOBase::FilterWidget m_filter; };