From 74ec078976fbd64433142e6974f30cc113047c00 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 10 Nov 2020 21:38:58 +0100 Subject: Replace usage of IPlugin::isActive(). --- src/installationmanager.cpp | 32 +++++++----- src/installationmanager.h | 17 ++++-- src/mainwindow.cpp | 8 +-- src/organizercore.cpp | 3 +- src/organizerproxy.cpp | 10 ++++ src/organizerproxy.h | 8 ++- src/plugincontainer.cpp | 118 +++++++++++++++++++++++++++++++++++++---- src/plugincontainer.h | 125 +++++++++++++++++++++++++++++++++++++++----- src/previewgenerator.cpp | 11 ++-- src/previewgenerator.h | 6 ++- src/problemsdialog.cpp | 15 +++--- src/problemsdialog.h | 5 +- src/proxyutils.h | 2 +- 13 files changed, 296 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index e9773a95..06909994 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -78,7 +78,8 @@ static T resolveFunction(QLibrary &lib, const char *name) } InstallationManager::InstallationManager() - : m_ParentWidget(nullptr), + : + m_ParentWidget(nullptr), m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}), m_IsRunning(false) { m_ArchiveHandler = CreateArchive(); @@ -106,7 +107,7 @@ InstallationManager::InstallationManager() // Connect the query password slot - This is the only way I found to be able to query user // from a separate thread. We use a BlockingQueuedConnection so that calling passwordRequested() // will block until the end of the slot. - connect(this, &InstallationManager::passwordRequested, + connect(this, &InstallationManager::passwordRequested, this, &InstallationManager::queryPassword, Qt::BlockingQueuedConnection); } @@ -122,6 +123,11 @@ void InstallationManager::setParentWidget(QWidget *widget) } } +void InstallationManager::setPluginContainer(const PluginContainer* pluginContainer) +{ + m_PluginContainer = pluginContainer; +} + void InstallationManager::queryPassword() { m_Password = QInputDialog::getText(m_ParentWidget, tr("Password required"), tr("Password"), QLineEdit::Password); @@ -284,7 +290,7 @@ QStringList InstallationManager::extractFiles(std::vector entry) +QString InstallationManager::createFile(std::shared_ptr entry) { // Use QTemporaryFile to create the temporary file with the given template: QTemporaryFile tempFile(QDir::cleanPath(QDir::tempPath() + QDir::separator() + "mo2-install")); @@ -308,11 +314,11 @@ QString InstallationManager::createFile(std::shared_ptr fileTree) +void InstallationManager::cleanCreatedFiles(std::shared_ptr fileTree) { // We simply have to check if all the entries have fileTree as a parent: for (auto it = std::begin(m_CreatedFiles); it != std::end(m_CreatedFiles); ) { - + // Find the parent - Could this be in FileTreeEntry? bool found = false; { @@ -495,12 +501,12 @@ IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue filesTree = + std::shared_ptr filesTree = archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; @@ -701,7 +707,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil for (IPluginInstaller *installer : m_Installers) { // don't use inactive installers (installer can't be null here but vc static code analysis thinks it could) - if ((installer == nullptr) || !installer->isActive()) { + if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) { continue; } @@ -731,7 +737,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil if (p == nullptr) { throw IncompatibilityException(tr("Invalid file tree returned by plugin.")); } - + // Detach the file tree (this ensure the parent is null and call to path() // stops at this root): p->detach(); @@ -786,7 +792,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil case IPluginInstaller::RESULT_SUCCESSCANCEL: { if (filesTree != nullptr) { auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY); - hasIniTweaks = iniTweakEntry != nullptr + hasIniTweaks = iniTweakEntry != nullptr && !iniTweakEntry->astree()->empty(); } return IPluginInstaller::RESULT_SUCCESS; @@ -860,7 +866,7 @@ QStringList InstallationManager::getSupportedExtensions() const void InstallationManager::notifyInstallationStart(QString const& archive, bool reinstallation, ModInfo::Ptr currentMod) { for (auto* installer : m_Installers) { - if (installer->isActive()) { + if (m_PluginContainer->isEnabled(installer)) { installer->onInstallationStart(archive, reinstallation, currentMod.get()); } } @@ -871,7 +877,7 @@ void InstallationManager::notifyInstallationEnd( ModInfo::Ptr newMod) { for (auto* installer : m_Installers) { - if (installer->isActive()) { + if (m_PluginContainer->isEnabled(installer)) { installer->onInstallationEnd(result, newMod.get()); } } diff --git a/src/installationmanager.h b/src/installationmanager.h index 66f11eae..e1db5e1f 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see . #include #include "modinfo.h" +#include "plugincontainer.h" /** @@ -87,6 +88,11 @@ public: */ void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; } + /** + * + */ + void setPluginContainer(const PluginContainer* pluginContainer); + /** * @brief update the directory where downloads are stored * @param downloadDirectory the download directory @@ -126,8 +132,8 @@ public: * @brief register an installer-plugin * @param the installer to register */ - void registerInstaller(MOBase::IPluginInstaller *installer); - + void registerInstaller(MOBase::IPluginInstaller *installer); + /** * @return the extensions of archives supported by this installation manager. */ @@ -167,7 +173,7 @@ public: * * The flatten argument is not present here while it is present in the deprecated QStringList * version for multiple reasons: 1) it was never used, 2) it is kind of fishy because there - * is no way to know if a file is going to be overriden, 3) it is quite easy to flatten a + * is no way to know if a file is going to be overriden, 3) it is quite easy to flatten a * IFileTree and thus to given a list of entries flattened (this was not possible with the * QStringList version since these were based on the name of the file inside the archive). */ @@ -185,7 +191,7 @@ public: * @return the path to the created file. */ virtual QString createFile(std::shared_ptr entry) override; - + /** * @brief Installs the given archive. * @@ -279,6 +285,9 @@ private: private: + // The plugin container, mostly to check if installer are enabled or not: + const PluginContainer *m_PluginContainer; + bool m_IsRunning; QWidget *m_ParentWidget; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 94bc5800..93ca415d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1132,7 +1132,7 @@ void MainWindow::checkForProblemsImpl() size_t numProblems = 0; for (QObject *pluginObj : m_PluginContainer.plugins()) { IPlugin *plugin = qobject_cast(pluginObj); - if (plugin == nullptr || plugin->isActive()) { + if (plugin == nullptr || m_PluginContainer.isEnabled(plugin)) { IPluginDiagnose *diagnose = qobject_cast(pluginObj); if (diagnose != nullptr) numProblems += diagnose->activeProblems().size(); @@ -1663,9 +1663,11 @@ void MainWindow::registerPluginTools(std::vector toolPlugins) } ); + // TODO: I don't know when this method is called? Maybe the check should be perform when + // the context menu is opened? // Remove inactive plugins toolPlugins.erase( - std::remove_if(toolPlugins.begin(), toolPlugins.end(), [](IPluginTool *plugin) -> bool { return !plugin->isActive(); }), + std::remove_if(toolPlugins.begin(), toolPlugins.end(), [this](IPluginTool *plugin) { return !m_PluginContainer.isEnabled(plugin); }), toolPlugins.end() ); @@ -5972,7 +5974,7 @@ void MainWindow::on_actionNotifications_triggered() future.waitForFinished(); - ProblemsDialog problems(m_PluginContainer.plugins(), this); + ProblemsDialog problems(m_PluginContainer, this); problems.exec(); scheduleCheckForProblems(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index f57903e2..86afe59a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -287,6 +287,7 @@ void OrganizerCore::connectPlugins(PluginContainer *container) m_InstallationManager.getSupportedExtensions()); m_PluginContainer = container; m_Updater.setPluginContainer(m_PluginContainer); + m_InstallationManager.setPluginContainer(m_PluginContainer); m_DownloadManager.setPluginContainer(m_PluginContainer); m_ModList.setPluginContainer(m_PluginContainer); @@ -2040,7 +2041,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, for (MOBase::IPluginFileMapper *mapper : m_PluginContainer->plugins()) { IPlugin *plugin = dynamic_cast(mapper); - if (plugin->isActive()) { + if (m_PluginContainer->isEnabled(plugin)) { MappingType pluginMap = mapper->mappings(); result.reserve(result.size() + pluginMap.size()); result.insert(result.end(), pluginMap.begin(), pluginMap.end()); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index a988ba9f..77b7c2d6 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -83,6 +83,16 @@ void OrganizerProxy::modDataChanged(IModInterface *mod) m_Proxied->modDataChanged(mod); } +bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const +{ + return m_PluginContainer->isEnabled(pluginName); +} + +bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const +{ + return m_PluginContainer->isEnabled(plugin); +} + QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const { return m_Proxied->pluginSetting(pluginName, key); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 6690d612..4329bdda 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -35,8 +35,6 @@ public: virtual MOBase::IPluginGame *getGame(const QString &gameName) const; virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); virtual void modDataChanged(MOBase::IModInterface *mod); - virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; - virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); virtual QString pluginDataPath() const; @@ -64,6 +62,12 @@ public: virtual bool onProfileRenamed(std::function const& func) override; virtual bool onProfileRemoved(std::function const& func) override; virtual bool onProfileChanged(std::function const& func) override; + + // Plugin related: + virtual bool isPluginEnabled(QString const& pluginName) const override; + virtual bool isPluginEnabled(MOBase::IPlugin* plugin) const override; + virtual QVariant pluginSetting(const QString& pluginName, const QString& key) const override; + virtual void setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override; virtual bool onPluginSettingChanged(std::function const& func) override; virtual MOBase::IPluginGame const *managedGame() const; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 5992f28d..aa38803f 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -51,9 +51,26 @@ QStringList PluginContainer::pluginInterfaces() } +// PluginRequirementProxy + +std::vector PluginRequirementProxy::problems() const +{ + return m_Requirement->problems(m_Proxy); +} +QString PluginRequirementProxy::description(unsigned int id) const +{ + return m_Requirement->description(id); +} +PluginRequirementProxy::PluginRequirementProxy(const MOBase::PluginRequirement* requirement, OrganizerProxy* proxy) : + m_Requirement(requirement), m_Proxy(proxy) { } + + +// PluginContainer + PluginContainer::PluginContainer(OrganizerCore *organizer) : m_Organizer(organizer) , m_UserInterface(nullptr) + , m_PreviewGenerator(this) { } @@ -184,6 +201,7 @@ bool PluginContainer::initPlugin(IPlugin *plugin) if (m_Organizer) { auto* proxy = new OrganizerProxy(m_Organizer, this, plugin); + m_Proxies[plugin] = proxy; if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); @@ -191,6 +209,10 @@ bool PluginContainer::initPlugin(IPlugin *plugin) } } + for (auto* requirement : plugin->requirements()) { + m_Requirements[plugin].emplace_back(requirement); + } + return true; } @@ -202,9 +224,10 @@ bool PluginContainer::initProxyPlugin(IPlugin *plugin) return false; } - IOrganizer* proxy = nullptr; + OrganizerProxy* proxy = nullptr; if (m_Organizer) { proxy = new OrganizerProxy(m_Organizer, this, plugin); + m_Proxies[plugin] = proxy; } if (!plugin->init(proxy)) { @@ -212,6 +235,10 @@ bool PluginContainer::initProxyPlugin(IPlugin *plugin) return false; } + for (auto* requirement : plugin->requirements()) { + m_Requirements[plugin].emplace_back(requirement); + } + return true; } @@ -227,24 +254,25 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) // way to cast directly between IPlugin* and IPluginDiagnose* bf::at_key(m_Plugins).push_back(plugin); - { // generic treatment for all plugins - IPlugin *pluginObj = qobject_cast(plugin); - if (pluginObj == nullptr) { - log::debug("not an IPlugin"); - return false; - } + // generic treatment for all plugins + IPlugin *pluginObj = qobject_cast(plugin); + if (pluginObj == nullptr) { + log::debug("not an IPlugin"); + return false; + } + bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - plugin->setProperty("filename", fileName); + plugin->setProperty("filename", fileName); - if (m_Organizer) { - m_Organizer->settings().plugins().registerPlugin(pluginObj); - } + 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; m_DiagnosisConnections.push_back( diagnose->onInvalidated([&] () { emit diagnosisUpdate(); }) ); @@ -254,6 +282,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) 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 @@ -383,6 +412,73 @@ void PluginContainer::unloadPlugins() } } +bool PluginContainer::isEnabled(IPlugin* plugin) const +{ + // Check if it's a game plugin: + if (implementInterface(plugin)) { + return plugin == m_Organizer->managedGame(); + } + + // Check if the plugin is enabled: + if (!m_Organizer->pluginSetting(plugin->name(), "enabled").toBool()) { + return false; + } + + // Check the requirements: + auto* proxy = m_Proxies.at(plugin); + for (auto& requirement : m_Requirements.at(plugin)) { + if (!requirement->problems(proxy).empty()) { + return false; + } + } + + return true; +} + +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 { return isEnabled(plugin(pluginName)); } +bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const { return isEnabled(plugin(diagnose)); } +bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const { return isEnabled(plugin(mapper)); } + +std::vector PluginContainer::requirements(IPlugin* plugin) const +{ + auto* proxy = m_Proxies.at(plugin); + std::vector proxies; + for (auto& requirement : m_Requirements.at(plugin)) { + proxies.push_back(PluginRequirementProxy(requirement.get(), proxy)); + } + return proxies; +} + IPluginGame *PluginContainer::managedGame(const QString &name) const { auto iter = m_SupportedGames.find(name); diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 2b39726b..1a3173fb 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -22,6 +22,30 @@ class IUserInterface; #include #endif // Q_MOC_RUN #include +#include + + +class OrganizerProxy; + + +// Small class that allows calling check() for plugin requirements +// without passing the IOrganizer. +class PluginRequirementProxy { +public: + + std::vector problems() const; + QString description(unsigned int id) const; + +private: + + const MOBase::PluginRequirement* m_Requirement; + OrganizerProxy* m_Proxy; + + PluginRequirementProxy(const MOBase::PluginRequirement* requirement, OrganizerProxy* proxy); + + friend class PluginContainer; + +}; class PluginContainer : public QObject, public MOBase::IPluginDiagnose @@ -32,18 +56,24 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose private: - typedef 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> - > PluginMap; + 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; @@ -112,6 +142,71 @@ public: 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; + } + + // Find all the names: + bool implement = false; + boost::mp11::mp_for_each([oPlugin, &implement](const auto* p) { + using plugin_type = std::decay_t; + if (qobject_cast(oPlugin)) { + implement = true; + } + }); + + return implement; + } + + /** + * @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. + */ + MOBase::IPlugin* plugin(QString const& pluginName) const; + MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; + MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) 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 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. + */ + std::vector requirements(MOBase::IPlugin* plugin) const; + /** * @brief Retrieved the (localized) names of interfaces implemented by the given * plugin. @@ -179,6 +274,12 @@ private: PluginMap m_Plugins; + // This maps allow access to IPlugin* from name or diagnose/mapper object. + AccessPluginMap m_AccessPlugins; + + std::map m_Proxies; + std::map>> m_Requirements; + std::map m_SupportedGames; std::vector m_DiagnosisConnections; QStringList m_FailedPlugins; diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index b406b7bc..c7df5a25 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -18,14 +18,17 @@ along with Mod Organizer. If not, see . */ #include "previewgenerator.h" + #include #include #include #include #include -PreviewGenerator::PreviewGenerator() -{ +#include "plugincontainer.h" + +PreviewGenerator::PreviewGenerator(const PluginContainer* pluginContainer) : + m_PluginContainer(pluginContainer) { m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; } @@ -42,13 +45,13 @@ bool PreviewGenerator::previewSupported(const QString &fileExtension) const if (it == m_PreviewPlugins.end()) { return false; } - return it->second->isActive(); + return m_PluginContainer->isEnabled(it->second); } QWidget *PreviewGenerator::genPreview(const QString &fileName) const { auto iter = m_PreviewPlugins.find(QFileInfo(fileName).suffix().toLower()); - if (iter != m_PreviewPlugins.end() && iter->second->isActive()) { + if (iter != m_PreviewPlugins.end() && m_PluginContainer->isEnabled(iter->second)) { return iter->second->genFilePreview(fileName, m_MaxSize); } else { return nullptr; diff --git a/src/previewgenerator.h b/src/previewgenerator.h index e872b06b..0d8f0781 100644 --- a/src/previewgenerator.h +++ b/src/previewgenerator.h @@ -26,10 +26,12 @@ along with Mod Organizer. If not, see . #include #include +class PluginContainer; + class PreviewGenerator { public: - PreviewGenerator(); + PreviewGenerator(const PluginContainer* pluginContainer); void registerPlugin(MOBase::IPluginPreview *plugin); @@ -44,8 +46,8 @@ private: private: + const PluginContainer* m_PluginContainer; std::map m_PreviewPlugins; - QSize m_MaxSize; }; diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 4b944ed2..d72f8c20 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -7,12 +7,13 @@ #include #include +#include "plugincontainer.h" using namespace MOBase; -ProblemsDialog::ProblemsDialog(std::vector pluginObjects, QWidget *parent) : - QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginObjects(pluginObjects), +ProblemsDialog::ProblemsDialog(const PluginContainer& pluginContainer, QWidget *parent) : + QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginContainer(pluginContainer), m_hasProblems(false) { ui->setupUi(this); @@ -40,14 +41,10 @@ void ProblemsDialog::runDiagnosis() m_hasProblems = false; ui->problemsWidget->clear(); - for(QObject *pluginObj : m_PluginObjects) { - IPlugin *plugin = qobject_cast(pluginObj); - if (plugin != nullptr && !plugin->isActive()) - continue; - - IPluginDiagnose *diagnose = qobject_cast(pluginObj); - if (diagnose == nullptr) + for (IPluginDiagnose *diagnose : m_PluginContainer.plugins()) { + if (!m_PluginContainer.isEnabled(diagnose)) { continue; + } std::vector activeProblems = diagnose->activeProblems(); foreach (unsigned int key, activeProblems) { diff --git a/src/problemsdialog.h b/src/problemsdialog.h index a30c8d48..504a0a10 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -11,13 +11,14 @@ namespace Ui { class ProblemsDialog; } +class PluginContainer; class ProblemsDialog : public QDialog { Q_OBJECT public: - explicit ProblemsDialog(std::vector pluginObjects, QWidget *parent = 0); + explicit ProblemsDialog(PluginContainer const& pluginContainer, QWidget *parent = 0); ~ProblemsDialog(); // also saves and restores geometry @@ -37,7 +38,7 @@ private slots: private: Ui::ProblemsDialog *ui; - std::vector m_PluginObjects; + const PluginContainer& m_PluginContainer; bool m_hasProblems; }; diff --git a/src/proxyutils.h b/src/proxyutils.h index 4c1717d8..4f26c070 100644 --- a/src/proxyutils.h +++ b/src/proxyutils.h @@ -10,7 +10,7 @@ namespace MOShared { template auto callIfPluginActive(OrganizerProxy* proxy, Fn&& callback, T defaultReturn = T{}) { return [fn = std::forward(callback), proxy, defaultReturn](auto&& ...args) { - if (proxy->plugin()->isActive()) { + if (proxy->isPluginEnabled(proxy->plugin())) { return fn(std::forward(args)...); } else { -- cgit v1.3.1 From 6ee74831f7d6e0ac15d42fe0fcb211321615d684 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 10 Nov 2020 21:44:38 +0100 Subject: Insert 'enabled' plugin if not present. --- src/settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 54d32786..0f661bfb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1296,6 +1296,11 @@ void PluginSettings::registerPlugin(IPlugin *plugin) .arg(setting.description) .arg(setting.defaultValue.toString()); } + + if (!m_PluginSettings.contains("enabled")) { + m_PluginSettings[plugin->name()]["enabled"] = true; + m_PluginDescriptions[plugin->name()]["enabled"] = QString(); + } } std::vector PluginSettings::plugins() const -- cgit v1.3.1 From eb1d340f2d98e65b1859fd103658f2671569c342 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 11:21:30 +0100 Subject: PluginRequirement -> IPluginRequirement and minor fixes. --- src/plugincontainer.cpp | 32 ++++++++++++++++++++++++-------- src/plugincontainer.h | 17 ++++------------- 2 files changed, 28 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index aa38803f..9045e457 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -61,7 +61,7 @@ QString PluginRequirementProxy::description(unsigned int id) const { return m_Requirement->description(id); } -PluginRequirementProxy::PluginRequirementProxy(const MOBase::PluginRequirement* requirement, OrganizerProxy* proxy) : +PluginRequirementProxy::PluginRequirementProxy(const MOBase::IPluginRequirement* requirement, OrganizerProxy* proxy) : m_Requirement(requirement), m_Proxy(proxy) { } @@ -203,14 +203,18 @@ bool PluginContainer::initPlugin(IPlugin *plugin) auto* proxy = new OrganizerProxy(m_Organizer, this, plugin); m_Proxies[plugin] = proxy; + // Create requirements before init() in case a plugin use + // IOrganizer::isPluginEnabled on himself during init(). + m_Requirements.emplace(plugin, 0); + if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } - } - for (auto* requirement : plugin->requirements()) { - m_Requirements[plugin].emplace_back(requirement); + for (auto* requirement : plugin->requirements()) { + m_Requirements[plugin].emplace_back(requirement); + } } return true; @@ -230,6 +234,9 @@ bool PluginContainer::initProxyPlugin(IPlugin *plugin) m_Proxies[plugin] = proxy; } + // Create requirements before init() in case a plugin use + // IOrganizer::isPluginEnabled on himself during init(). + m_Requirements.emplace(plugin, 0); if (!plugin->init(proxy)) { log::warn("proxy plugin failed to initialize"); return false; @@ -420,7 +427,7 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const } // Check if the plugin is enabled: - if (!m_Organizer->pluginSetting(plugin->name(), "enabled").toBool()) { + if (!m_Organizer->persistent(plugin->name(), "enabled", true).toBool()) { return false; } @@ -465,9 +472,18 @@ MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginFileMapper* mapper) cons return it->second; } -bool PluginContainer::isEnabled(QString const& pluginName) const { return isEnabled(plugin(pluginName)); } -bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const { return isEnabled(plugin(diagnose)); } -bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const { return isEnabled(plugin(mapper)); } +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; +} std::vector PluginContainer::requirements(IPlugin* plugin) const { diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 1a3173fb..26ad2046 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -38,10 +38,10 @@ public: private: - const MOBase::PluginRequirement* m_Requirement; + const MOBase::IPluginRequirement* m_Requirement; OrganizerProxy* m_Proxy; - PluginRequirementProxy(const MOBase::PluginRequirement* requirement, OrganizerProxy* proxy); + PluginRequirementProxy(const MOBase::IPluginRequirement* requirement, OrganizerProxy* proxy); friend class PluginContainer; @@ -160,16 +160,7 @@ public: return false; } - // Find all the names: - bool implement = false; - boost::mp11::mp_for_each([oPlugin, &implement](const auto* p) { - using plugin_type = std::decay_t; - if (qobject_cast(oPlugin)) { - implement = true; - } - }); - - return implement; + return qobject_cast(oPlugin); } /** @@ -278,7 +269,7 @@ private: AccessPluginMap m_AccessPlugins; std::map m_Proxies; - std::map>> m_Requirements; + std::map>> m_Requirements; std::map m_SupportedGames; std::vector m_DiagnosisConnections; -- cgit v1.3.1 From 7a4f6d5344031db828f5dc2257c1d19f251e7f05 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 13:39:53 +0100 Subject: Improve requirements handling. --- src/plugincontainer.cpp | 218 +++++++++++++++++++++++++++--------------- src/plugincontainer.h | 109 +++++++++++++++++---- src/settings.cpp | 17 +++- src/settingsdialog.ui | 10 ++ src/settingsdialogplugins.cpp | 97 ++++++++++++++++++- src/settingsdialogplugins.h | 7 ++ 6 files changed, 361 insertions(+), 97 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 9045e457..021fe3c8 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -53,18 +53,108 @@ QStringList PluginContainer::pluginInterfaces() // PluginRequirementProxy -std::vector PluginRequirementProxy::problems() const +PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, MOBase::IPlugin* plugin, IOrganizer* proxy, MOBase::IPluginProxy* pluginProxy) + : m_PluginContainer(pluginContainer) + , m_Plugin(plugin) + , m_PluginProxy(pluginProxy) + , m_Organizer(proxy) { - return m_Requirement->problems(m_Proxy); + for (auto* requirement : plugin->requirements()) { + m_Requirements.emplace_back(requirement); + } + + // TODO: + if (pluginProxy) { + m_Requirements.emplace_back(PluginRequirementFactory::pluginDependency(pluginProxy->name())); + } +} + +MOBase::IPluginProxy* PluginRequirements::proxy() const +{ + return m_PluginProxy; +} + +std::vector PluginRequirements::problems() const +{ + std::vector result; + for (auto& requirement : m_Requirements) { + for (auto p : requirement->problems(m_Organizer)) { + result.push_back(Problem(requirement.get(), p)); + } + } + return result; +} + +bool PluginRequirements::canEnable() const +{ + return problems().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 {}; } -QString PluginRequirementProxy::description(unsigned int id) const + +std::vector PluginRequirements::requiredFor() const { - return m_Requirement->description(id); + std::vector required; + std::set visited; + requiredFor(required, visited); + return required; } -PluginRequirementProxy::PluginRequirementProxy(const MOBase::IPluginRequirement* requirement, OrganizerProxy* proxy) : - m_Requirement(requirement), m_Proxy(proxy) { } +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) @@ -186,7 +276,7 @@ QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const return *it; } -bool PluginContainer::initPlugin(IPlugin *plugin) +bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) { // when MO has no instance loaded, init() is not called on plugins, except // for proxy plugins, where init() is called with a null IOrganizer @@ -199,63 +289,34 @@ bool PluginContainer::initPlugin(IPlugin *plugin) return false; } + OrganizerProxy* proxy = nullptr; if (m_Organizer) { - auto* proxy = new OrganizerProxy(m_Organizer, this, plugin); - m_Proxies[plugin] = proxy; - - // Create requirements before init() in case a plugin use - // IOrganizer::isPluginEnabled on himself during init(). - m_Requirements.emplace(plugin, 0); - - if (!plugin->init(proxy)) { - log::warn("plugin failed to initialize"); - return false; - } - - for (auto* requirement : plugin->requirements()) { - m_Requirements[plugin].emplace_back(requirement); - } + proxy = new OrganizerProxy(m_Organizer, this, plugin); } - return true; -} + // Check if it is a proxy plugin: + bool isProxy = dynamic_cast(plugin); -bool PluginContainer::initProxyPlugin(IPlugin *plugin) -{ - // see initPlugin() above for info - - if (plugin == nullptr) { - return false; + if (!m_Organizer && !isProxy) { + return true; } - OrganizerProxy* proxy = nullptr; - if (m_Organizer) { - proxy = new OrganizerProxy(m_Organizer, this, plugin); - m_Proxies[plugin] = proxy; - } - - // Create requirements before init() in case a plugin use - // IOrganizer::isPluginEnabled on himself during init(). - m_Requirements.emplace(plugin, 0); if (!plugin->init(proxy)) { - log::warn("proxy plugin failed to initialize"); + log::warn("plugin failed to initialize"); return false; } - for (auto* requirement : plugin->requirements()) { - m_Requirements[plugin].emplace_back(requirement); - } + m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); return true; } - void PluginContainer::registerGame(IPluginGame *game) { m_SupportedGames.insert({ game->gameName(), game }); } -bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) +bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, MOBase::IPluginProxy* pluginProxy) { // 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* @@ -294,34 +355,32 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // mod page plugin IPluginModPage *modPage = qobject_cast(plugin); - if (initPlugin(modPage)) { + if (initPlugin(modPage, pluginProxy)) { bf::at_key(m_Plugins).push_back(modPage); return true; } } { // game plugin IPluginGame *game = qobject_cast(plugin); - if (game) { game->detectGame(); - } - - if (initPlugin(game)) { - bf::at_key(m_Plugins).push_back(game); - registerGame(game); - return true; + if (initPlugin(game, pluginProxy)) { + bf::at_key(m_Plugins).push_back(game); + registerGame(game); + return true; + } } } { // tool plugins IPluginTool *tool = qobject_cast(plugin); - if (initPlugin(tool)) { + if (initPlugin(tool, pluginProxy)) { bf::at_key(m_Plugins).push_back(tool); return true; } } { // installer plugins IPluginInstaller *installer = qobject_cast(plugin); - if (initPlugin(installer)) { + if (initPlugin(installer, pluginProxy)) { bf::at_key(m_Plugins).push_back(installer); if (m_Organizer) { m_Organizer->installationManager()->registerInstaller(installer); @@ -331,7 +390,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // preview plugins IPluginPreview *preview = qobject_cast(plugin); - if (initPlugin(preview)) { + if (initPlugin(preview, pluginProxy)) { bf::at_key(m_Plugins).push_back(preview); m_PreviewGenerator.registerPlugin(preview); return true; @@ -339,7 +398,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // proxy plugins IPluginProxy *proxy = qobject_cast(plugin); - if (initProxyPlugin(proxy)) { + if (initPlugin(proxy, pluginProxy)) { bf::at_key(m_Plugins).push_back(proxy); QStringList pluginNames = proxy->pluginList( QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); @@ -349,7 +408,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) QList matchingPlugins = proxy->instantiate(pluginName); for (QObject *proxiedPlugin : matchingPlugins) { if (proxiedPlugin != nullptr) { - if (registerPlugin(proxiedPlugin, pluginName)) { + if (registerPlugin(proxiedPlugin, pluginName, proxy)) { log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); } else { @@ -371,7 +430,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // dummy plugins // only initialize these, no processing otherwise IPlugin *dummy = qobject_cast(plugin); - if (initPlugin(dummy)) { + if (initPlugin(dummy, pluginProxy)) { bf::at_key(m_Plugins).push_back(dummy); return true; } @@ -419,6 +478,14 @@ void PluginContainer::unloadPlugins() } } +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: @@ -431,15 +498,21 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const return false; } - // Check the requirements: - auto* proxy = m_Proxies.at(plugin); - for (auto& requirement : m_Requirements.at(plugin)) { - if (!requirement->problems(proxy).empty()) { - return false; + // Check the requirements (if a plugin checks in init(), the requirements have + // not been computed yet): + auto it = m_Requirements.find(plugin); + + return it == std::end(m_Requirements) ? true : it->second.canEnable(); +} + +void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies) +{ + if (!enable && dependencies) { + for (auto* p : requirements(plugin).requiredFor()) { + setEnabled(p, false, false); // No need to "recurse" here since requiredFor already does it. } } - - return true; + m_Organizer->setPersistent(plugin->name(), "enabled", enable, true); } MOBase::IPlugin* PluginContainer::plugin(QString const& pluginName) const @@ -485,14 +558,9 @@ bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const { return p ? isEnabled(p) : false; } -std::vector PluginContainer::requirements(IPlugin* plugin) const +const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const { - auto* proxy = m_Proxies.at(plugin); - std::vector proxies; - for (auto& requirement : m_Requirements.at(plugin)) { - proxies.push_back(PluginRequirementProxy(requirement.get(), proxy)); - } - return proxies; + return m_Requirements.at(plugin); } IPluginGame *PluginContainer::managedGame(const QString &name) const @@ -517,7 +585,7 @@ void PluginContainer::loadPlugins() unloadPlugins(); for (QObject *plugin : QPluginLoader::staticInstances()) { - registerPlugin(plugin, ""); + registerPlugin(plugin, "", nullptr); } QFile loadCheck; @@ -613,7 +681,7 @@ void PluginContainer::loadPlugins() "failed to load plugin {}: {}", pluginName, pluginLoader->errorString()); } else { - if (registerPlugin(pluginLoader->instance(), pluginName)) { + if (registerPlugin(pluginLoader->instance(), pluginName, nullptr)) { log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); m_PluginLoaders.push_back(pluginLoader.release()); } else { diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 26ad2046..cd1d03ca 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -27,24 +27,77 @@ class IUserInterface; class OrganizerProxy; +/** + * @brief Class that wrap multiple requirements for a plugin together. THis + * class owns the requirements. + */ +class PluginRequirements { +public: + + // Small intermediate class. + struct Problem { + public: + + QString description() const { return m_Requirement->description(m_Id); } + + + private: + Problem(const MOBase::IPluginRequirement* requirement, unsigned int id) : + m_Requirement(requirement), m_Id(id) { } + + const MOBase::IPluginRequirement* m_Requirement; + unsigned int m_Id; + + friend class PluginRequirements; + }; -// Small class that allows calling check() for plugin requirements -// without passing the IOrganizer. -class PluginRequirementProxy { public: - std::vector problems() const; - QString description(unsigned int id) const; + /** + * @return true if the plugin can be enabled (all requirements are met). + */ + bool canEnable() const; -private: + /** + * @return the proxy that created this plugin, if any. + */ + MOBase::IPluginProxy* proxy() const; - const MOBase::IPluginRequirement* m_Requirement; - OrganizerProxy* m_Proxy; + /** + * @return the list of problems to be resolved before enabling the plugin. + */ + std::vector problems() const; - PluginRequirementProxy(const MOBase::IPluginRequirement* requirement, OrganizerProxy* proxy); + /** + * @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: + + // Accumulator version for requiredFor() to avoid infinite recursion. + void requiredFor(std::vector& required, std::set& visited) const; friend class PluginContainer; + PluginContainer* m_PluginContainer; + MOBase::IPlugin* m_Plugin; + MOBase::IPluginProxy* m_PluginProxy; + std::vector> m_Requirements; + MOBase::IOrganizer* m_Organizer; + std::vector m_RequiredFor; + + PluginRequirements( + PluginContainer* pluginContainer, MOBase::IPlugin* plugin, + MOBase::IOrganizer* proxy, MOBase::IPluginProxy* pluginProxy); + }; @@ -175,6 +228,11 @@ public: MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; + /** + * @return the IPlugin interface to the currently managed game. + */ + MOBase::IPlugin* managedGame() const; + /** * @brief Check if the given plugin is enabled. * @@ -182,13 +240,23 @@ public: * * @return true if the plugin is enabled, false otherwise. */ - bool isEnabled(MOBase::IPlugin *plugin) const; + 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. * @@ -196,7 +264,7 @@ public: * * @return the requirements (as proxy) for the given plugin. */ - std::vector requirements(MOBase::IPlugin* plugin) const; + const PluginRequirements& requirements(MOBase::IPlugin* plugin) const; /** * @brief Retrieved the (localized) names of interfaces implemented by the given @@ -244,6 +312,8 @@ signals: private: + friend class PluginRequirements; + /** * @brief Find the QObject* corresponding to the given plugin. * @@ -253,11 +323,19 @@ private: */ 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). + * + * @return true if the plugin was initialized correctly, false otherwise. + */ + bool initPlugin(MOBase::IPlugin *plugin, MOBase::IPluginProxy* proxy); - bool initPlugin(MOBase::IPlugin *plugin); - bool initProxyPlugin(MOBase::IPlugin *plugin); void registerGame(MOBase::IPluginGame *game); - bool registerPlugin(QObject *pluginObj, const QString &fileName); + + bool registerPlugin(QObject *pluginObj, const QString &fileName, MOBase::IPluginProxy *proxy); OrganizerCore *m_Organizer; @@ -268,8 +346,7 @@ private: // This maps allow access to IPlugin* from name or diagnose/mapper object. AccessPluginMap m_AccessPlugins; - std::map m_Proxies; - std::map>> m_Requirements; + std::map m_Requirements; std::map m_SupportedGames; std::vector m_DiagnosisConnections; diff --git a/src/settings.cpp b/src/settings.cpp index 0f661bfb..a76d5e76 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1288,6 +1288,11 @@ void PluginSettings::registerPlugin(IPlugin *plugin) temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; + + // If there was no previous "enabled" value, skip it: + if (setting.key == "enabled") { + continue; + } } m_PluginSettings[plugin->name()][setting.key] = temp; @@ -1297,9 +1302,15 @@ void PluginSettings::registerPlugin(IPlugin *plugin) .arg(setting.defaultValue.toString()); } - if (!m_PluginSettings.contains("enabled")) { - m_PluginSettings[plugin->name()]["enabled"] = true; - m_PluginDescriptions[plugin->name()]["enabled"] = QString(); + // Handle previous "enabled" settings: + if (m_PluginSettings[plugin->name()].contains("enabled")) { + setPersistent(plugin->name(), "enabled", m_PluginSettings[plugin->name()]["enabled"].toBool(), true); + m_PluginSettings[plugin->name()].remove("enabled"); + m_PluginDescriptions[plugin->name()].remove("enabled"); + + // We need to drop it manually in Settings since it is not possible to remove plugin + // settings: + remove(m_Settings, "Plugins", plugin->name() + "/enabled"); } } diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 85c5a4e4..4fd34b21 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1127,6 +1127,9 @@ + + 6 + @@ -1172,6 +1175,13 @@ + + + + Enabled + + + diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 5faa0dc9..8a70475c 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -3,9 +3,10 @@ #include "noeditdelegate.h" #include +#include "organizercore.h" #include "plugincontainer.h" -using MOBase::IPlugin; +using namespace MOBase; PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginContainer, SettingsDialog& d) : SettingsTab(s, d), m_pluginContainer(pluginContainer) @@ -66,15 +67,36 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta QObject::connect( ui->pluginsList, &QTreeWidget::currentItemChanged, [&](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); QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { deleteBlacklistItem(); }); - QObject::connect(&m_filter, &MOBase::FilterWidget::changed, [&] { filterPluginList(); }); + QObject::connect(&m_filter, &FilterWidget::changed, [&] { filterPluginList(); }); + updateListItems(); filterPluginList(); } +void PluginsSettingsTab::updateListItems() +{ + for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { + auto* topLevelItem = ui->pluginsList->topLevelItem(i); + for (auto j = 0; j < topLevelItem->childCount(); ++j) { + auto* item = topLevelItem->child(j); + auto* plugin = this->plugin(item); + + if (!m_pluginContainer->implementInterface(plugin) + && !m_pluginContainer->isEnabled(plugin)) { + item->setBackgroundColor(0, Qt::gray); + } + } + } + +} + void PluginsSettingsTab::filterPluginList() { QTreeWidgetItem* firstNotHidden = nullptr; @@ -109,7 +131,7 @@ void PluginsSettingsTab::filterPluginList() auto selectedItems = ui->pluginsList->selectedItems(); if (!selectedItems.isEmpty() && selectedItems[0]->isHidden()) { selectedItems[0]->setSelected(false); - + if (firstNotHidden) { firstNotHidden->setSelected(true); } @@ -150,6 +172,71 @@ 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, ROLE_PLUGIN).isValid()) { + return; + } + IPlugin* plugin = this->plugin(item); + const auto& requirements = m_pluginContainer->requirements(plugin); + + // User wants to enable: + if (checked) { + auto problems = requirements.problems(); + if (!problems.empty()) { + QStringList descriptions; + for (auto& problem : problems) { + descriptions.append(problem.description()); + } + QMessageBox::warning( + parentWidget(), QObject::tr("Cannot enable plugin"), + QObject::tr("

This plugin cannot be enabled:

    %1
") + .arg("
  • " + descriptions.join("
  • ") + "
  • "), QMessageBox::Ok); + ui->enabledCheckbox->setChecked(false); + return; + } + + m_pluginContainer->setEnabled(plugin, false, true); + } + else { + // Custom check for proxy + current game: + if (m_pluginContainer->implementInterface(plugin)) { + auto* game = m_pluginContainer->managedGame(); + if (m_pluginContainer->requirements(game).proxy() == plugin) { + QMessageBox::warning( + parentWidget(), QObject::tr("Cannot disable plugin"), + QObject::tr("This plugin is used by the current game plugin and cannot disabled."), QMessageBox::Ok); + 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 this plugin will also disable the following plugins:

      %1

    Do you want to continue?

    ") + .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); + } + + updateListItems(); +} + void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) { storeSettings(previous); @@ -164,6 +251,10 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->versionLabel->setText(plugin->version().canonicalString()); ui->descriptionLabel->setText(plugin->description()); + ui->enabledCheckbox->setVisible( + !m_pluginContainer->implementInterface(plugin)); + ui->enabledCheckbox->setChecked(m_pluginContainer->isEnabled(plugin)); + QVariantMap settings = current->data(0, ROLE_SETTINGS).toMap(); QVariantMap descriptions = current->data(0, ROLE_DESCRIPTIONS).toMap(); ui->pluginSettingsList->setEnabled(settings.count() != 0); diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index add8fede..3de0d7dc 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -16,10 +16,17 @@ public: private: void on_pluginsList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); + void on_checkboxEnabled_clicked(bool checked); void deleteBlacklistItem(); void storeSettings(QTreeWidgetItem *pluginItem); private slots: + + /** + * @brief Update the list item to display inactive plugins. + */ + void updateListItems(); + /** * @brief Filter the plugin list according to the filter widget. * -- cgit v1.3.1 From f53d04a84d148a771dab3de9fe5f2f28e2d0240d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 14:08:11 +0100 Subject: Hide Tool action instead of not adding them. --- src/mainwindow.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 93ca415d..9bbe3da9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1652,6 +1652,9 @@ void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); menu->addAction(action); + if (!m_PluginContainer.isEnabled(tool)) { + action->setVisible(false); + } } void MainWindow::registerPluginTools(std::vector toolPlugins) @@ -1663,14 +1666,6 @@ void MainWindow::registerPluginTools(std::vector toolPlugins) } ); - // TODO: I don't know when this method is called? Maybe the check should be perform when - // the context menu is opened? - // Remove inactive plugins - toolPlugins.erase( - std::remove_if(toolPlugins.begin(), toolPlugins.end(), [this](IPluginTool *plugin) { return !m_PluginContainer.isEnabled(plugin); }), - toolPlugins.end() - ); - // Group the plugins into submenus QMap>> submenuMap; for (auto toolPlugin : toolPlugins) { -- cgit v1.3.1 From 0dddc5eb7b60efd4f124485fd6e3e982c04f8a28 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 14:08:23 +0100 Subject: Fix handling of previous 'enabled' settings. --- src/settings.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index a76d5e76..db48cfd2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1280,19 +1280,22 @@ void PluginSettings::registerPlugin(IPlugin *plugin) const QString settingName = plugin->name() + "/" + setting.key; QVariant temp = get( - m_Settings, "Plugins", settingName, setting.defaultValue); + m_Settings, "Plugins", settingName, QVariant()); - if (!temp.convert(setting.defaultValue.type())) { + // No previous enabled? Skip. + if (setting.key == "enabled" && (!temp.isValid() || !temp.canConvert())) { + continue; + } + + if (!temp.isValid()) { + temp = setting.defaultValue; + } + else if (!temp.convert(setting.defaultValue.type())) { log::warn( "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; - - // If there was no previous "enabled" value, skip it: - if (setting.key == "enabled") { - continue; - } } m_PluginSettings[plugin->name()][setting.key] = temp; -- cgit v1.3.1 From e528d8b0ad843bf2e728f26c3a1bade145e5ad07 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 14:59:11 +0100 Subject: Fix display of tools in the MainWindow menu. --- src/iuserinterface.h | 2 -- src/mainwindow.cpp | 51 ++++++++++++++++++++++--------------------- src/mainwindow.h | 9 ++++---- src/plugincontainer.cpp | 4 ---- src/settingsdialogplugins.cpp | 9 ++++---- 5 files changed, 34 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index cce89070..a2a91e62 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -13,8 +13,6 @@ class IUserInterface { public: - virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; - virtual void registerPluginTools(std::vector toolPlugins) = 0; virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; virtual void installTranslator(const QString &name) = 0; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9bbe3da9..b5a6382f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -467,6 +467,7 @@ MainWindow::MainWindow(Settings &settings connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); + connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { updateToolMenu(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -508,8 +509,6 @@ MainWindow::MainWindow(Settings &settings installTranslator(QFileInfo(fileName).baseName()); } - registerPluginTools(m_PluginContainer.plugins()); - for (IPluginModPage *modPagePlugin : m_PluginContainer.plugins()) { registerModPage(modPagePlugin); } @@ -1597,22 +1596,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) return false; } - -void MainWindow::toolPluginInvoke() -{ - QAction *triggeredAction = qobject_cast(sender()); - IPluginTool *plugin = qobject_cast(triggeredAction->data().value()); - if (plugin != nullptr) { - try { - plugin->display(); - } catch (const std::exception &e) { - reportError(tr("Plugin \"%1\" failed: %2").arg(plugin->name()).arg(e.what())); - } catch (...) { - reportError(tr("Plugin \"%1\" failed").arg(plugin->name())); - } - } -} - void MainWindow::modPagePluginInvoke() { QAction *triggeredAction = qobject_cast(sender()); @@ -1648,24 +1631,42 @@ void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu QAction *action = new QAction(tool->icon(), name, menu); action->setToolTip(tool->tooltip()); tool->setParentWidget(this); - action->setData(QVariant::fromValue((QObject*)tool)); - connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); + connect(action, &QAction::triggered, this, [this, tool]() { + try { + tool->display(); + } + catch (const std::exception& e) { + reportError(tr("Plugin \"%1\" failed: %2").arg(tool->localizedName()).arg(e.what())); + } + catch (...) { + reportError(tr("Plugin \"%1\" failed").arg(tool->localizedName())); + } + }, Qt::QueuedConnection); menu->addAction(action); - if (!m_PluginContainer.isEnabled(tool)) { - action->setVisible(false); - } } -void MainWindow::registerPluginTools(std::vector toolPlugins) +void MainWindow::updateToolMenu() { + // Clear the menu: + ui->actionTool->menu()->clear(); + + std::vector toolPlugins = m_PluginContainer.plugins(); + // Sort the plugins by display name - std::sort(toolPlugins.begin(), toolPlugins.end(), + std::sort(std::begin(toolPlugins), std::end(toolPlugins), [](IPluginTool *left, IPluginTool *right) { return left->displayName().toLower() < right->displayName().toLower(); } ); + // Remove disabled plugins: + toolPlugins.erase( + std::remove_if(std::begin(toolPlugins), std::end(toolPlugins), [&](auto* tool) { + return !m_PluginContainer.isEnabled(tool); + }), + toolPlugins.end()); + // Group the plugins into submenus QMap>> submenuMap; for (auto toolPlugin : toolPlugins) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 8b2188c8..d4aa0bc2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -129,10 +129,6 @@ public: void saveArchiveList(); - void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr); - void registerPluginTools(std::vector toolPlugins); - void registerModPage(MOBase::IPluginModPage *modPage); - void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); void installTranslator(const QString &name); @@ -160,7 +156,6 @@ public slots: void directory_refreshed(); - void toolPluginInvoke(); void modPagePluginInvoke(); signals: @@ -212,7 +207,11 @@ private: void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); + void registerModPage(MOBase::IPluginModPage* modPage); + void registerPluginTool(MOBase::IPluginTool* tool, QString name = QString(), QMenu* menu = nullptr); + void updateToolbarMenu(); + void updateToolMenu(); void updateViewMenu(); QMenu* createPopupMenu() override; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 021fe3c8..030fdb31 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -180,10 +180,6 @@ void PluginContainer::setUserInterface(IUserInterface *userInterface, QWidget *w for (IPluginModPage *modPage : bf::at_key(m_Plugins)) { userInterface->registerModPage(modPage); } - - for (IPluginTool *tool : bf::at_key(m_Plugins)) { - userInterface->registerPluginTool(tool); - } } m_UserInterface = userInterface; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 8a70475c..0c02ffb0 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -88,10 +88,9 @@ void PluginsSettingsTab::updateListItems() auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); - if (!m_pluginContainer->implementInterface(plugin) - && !m_pluginContainer->isEnabled(plugin)) { - item->setBackgroundColor(0, Qt::gray); - } + bool inactive = !m_pluginContainer->implementInterface(plugin) + && !m_pluginContainer->isEnabled(plugin); + // TODO: Better display. } } @@ -198,7 +197,7 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) return; } - m_pluginContainer->setEnabled(plugin, false, true); + m_pluginContainer->setEnabled(plugin, true, false); } else { // Custom check for proxy + current game: -- cgit v1.3.1 From a449098db96527f8ac772cd518657d8e22018468 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 16:14:55 +0100 Subject: Cleaner IPluginRequirement. --- src/plugincontainer.cpp | 9 +++++---- src/plugincontainer.h | 21 +-------------------- src/settingsdialogplugins.cpp | 2 +- 3 files changed, 7 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 030fdb31..a218b1f0 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -74,12 +74,13 @@ MOBase::IPluginProxy* PluginRequirements::proxy() const return m_PluginProxy; } -std::vector PluginRequirements::problems() const +std::vector PluginRequirements::problems() const { - std::vector result; + std::vector result; for (auto& requirement : m_Requirements) { - for (auto p : requirement->problems(m_Organizer)) { - result.push_back(Problem(requirement.get(), p)); + auto p = requirement->check(m_Organizer); + if (p) { + result.push_back(*p); } } return result; diff --git a/src/plugincontainer.h b/src/plugincontainer.h index cd1d03ca..520ee83e 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -32,25 +32,6 @@ class OrganizerProxy; * class owns the requirements. */ class PluginRequirements { -public: - - // Small intermediate class. - struct Problem { - public: - - QString description() const { return m_Requirement->description(m_Id); } - - - private: - Problem(const MOBase::IPluginRequirement* requirement, unsigned int id) : - m_Requirement(requirement), m_Id(id) { } - - const MOBase::IPluginRequirement* m_Requirement; - unsigned int m_Id; - - friend class PluginRequirements; - }; - public: /** @@ -66,7 +47,7 @@ public: /** * @return the list of problems to be resolved before enabling the plugin. */ - std::vector problems() const; + std::vector problems() const; /** * @return the name of the games (gameName()) this plugin can be used with, or an empty diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 0c02ffb0..1e2117cf 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -187,7 +187,7 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) if (!problems.empty()) { QStringList descriptions; for (auto& problem : problems) { - descriptions.append(problem.description()); + descriptions.append(problem.shortDescription()); } QMessageBox::warning( parentWidget(), QObject::tr("Cannot enable plugin"), -- cgit v1.3.1 From d3bd237c226f44004a9917a29e1b98b2038ff271 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 20:34:28 +0100 Subject: Fix rebase. --- src/plugincontainer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index a218b1f0..85068d2d 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -78,8 +78,7 @@ std::vector PluginRequirements::problems() const { std::vector result; for (auto& requirement : m_Requirements) { - auto p = requirement->check(m_Organizer); - if (p) { + if (auto p = requirement->check(m_Organizer)) { result.push_back(*p); } } -- cgit v1.3.1 From c0ace25a871c4d938fb4be10b0357e263db677cb Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 21:01:17 +0100 Subject: Use IPlugin::master() when present. --- src/plugincontainer.cpp | 40 ++++++++++++++++++++++++++++++++++++++- src/plugincontainer.h | 15 +++++++++++++++ src/settingsdialogplugins.cpp | 44 +++++++++++++++++++++++++++++++++---------- 3 files changed, 88 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 85068d2d..ab68cc1d 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -69,11 +69,31 @@ PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, MOBase: } } -MOBase::IPluginProxy* PluginRequirements::proxy() const +IPluginProxy* PluginRequirements::proxy() const { return m_PluginProxy; } +IPlugin* PluginRequirements::master() const +{ + if (m_Plugin->master().isEmpty()) { + return nullptr; + } + return m_PluginContainer->plugin(m_Plugin->master()); +} + +std::vector PluginRequirements::children() const +{ + std::vector children; + for (auto* obj : m_PluginContainer->plugins()) { + auto* plugin = qobject_cast(obj); + if (plugin && plugin->master().compare(m_Plugin->name(), Qt::CaseInsensitive) == 0) { + children.push_back(plugin); + } + } + return children; +} + std::vector PluginRequirements::problems() const { std::vector result; @@ -90,6 +110,11 @@ bool PluginRequirements::canEnable() const return problems().empty(); } +bool PluginRequirements::hasRequirements() const +{ + return !m_Requirements.empty(); +} + QStringList PluginRequirements::requiredGames() const { // We look for a "GameDependencyRequirement" - There can be only one since otherwise @@ -304,6 +329,11 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); + if (!plugin->master().isEmpty() && m_Requirements.at(plugin).hasRequirements()) { + log::warn("a plugin cannot have requirements if it has a master"); + return false; + } + return true; } @@ -503,11 +533,19 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const 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); } diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 520ee83e..e9761cb1 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -39,11 +39,26 @@ public: */ bool canEnable() 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 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. */ diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 1e2117cf..15c0b424 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -31,14 +31,10 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta // display plugin settings QSet handledNames; for (IPlugin* plugin : settings().plugins().plugins()) { - if (handledNames.contains(plugin->name())) { - continue; - } - if (!m_filter.matches([plugin](const QRegularExpression& regex) { - return regex.match(plugin->localizedName()).hasMatch(); - })) { + if (handledNames.contains(plugin->name()) || !plugin->master().isEmpty()) { continue; } + QTreeWidgetItem* listItem = new QTreeWidgetItem( topItems.at(m_pluginContainer->topImplementedInterface(plugin))); listItem->setData(0, Qt::DisplayRole, plugin->localizedName()); @@ -46,6 +42,18 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta listItem->setData(0, ROLE_SETTINGS, settings().plugins().settings(plugin->name())); listItem->setData(0, ROLE_DESCRIPTIONS, settings().plugins().descriptions(plugin->name())); + // 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, ROLE_PLUGIN, QVariant::fromValue((void*)child)); + childItem->setData(0, ROLE_SETTINGS, settings().plugins().settings(child->name())); + childItem->setData(0, ROLE_DESCRIPTIONS, settings().plugins().descriptions(child->name())); + + handledNames.insert(child->name()); + } + handledNames.insert(plugin->name()); } @@ -90,7 +98,13 @@ void PluginsSettingsTab::updateListItems() bool inactive = !m_pluginContainer->implementInterface(plugin) && !m_pluginContainer->isEnabled(plugin); - // TODO: Better display. + + auto font = item->font(0); + font.setItalic(inactive); + item->setFont(0, font); + for (auto k = 0; k < item->childCount(); ++k) { + item->child(k)->setFont(0, font); + } } } @@ -108,9 +122,18 @@ void PluginsSettingsTab::filterPluginList() auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); - if (m_filter.matches([plugin](const QRegularExpression& regex) { + // Check the item or the child - If any match (item or child), the whole + // group is displayed. + bool match = m_filter.matches([plugin](const QRegularExpression& regex) { return regex.match(plugin->localizedName()).hasMatch(); - })) { + }); + for (auto* child : m_pluginContainer->requirements(plugin).children()) { + m_filter.matches([child](const QRegularExpression& regex) { + return regex.match(child->localizedName()).hasMatch(); + }); + } + + if (match) { found = true; item->setHidden(false); @@ -251,7 +274,8 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->descriptionLabel->setText(plugin->description()); ui->enabledCheckbox->setVisible( - !m_pluginContainer->implementInterface(plugin)); + !m_pluginContainer->implementInterface(plugin) + && plugin->master().isEmpty()); ui->enabledCheckbox->setChecked(m_pluginContainer->isEnabled(plugin)); QVariantMap settings = current->data(0, ROLE_SETTINGS).toMap(); -- cgit v1.3.1 From 7ec0128eb59c085cd5961995280238cc22683b8e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 21:42:05 +0100 Subject: Remove incorrect warning. --- src/plugincontainer.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index ab68cc1d..bcd0db44 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -329,11 +329,6 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - if (!plugin->master().isEmpty() && m_Requirements.at(plugin).hasRequirements()) { - log::warn("a plugin cannot have requirements if it has a master"); - return false; - } - return true; } -- cgit v1.3.1 From 90beed8c7b356ef58431721ffe8eb4abff3da032 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 22:45:33 +0100 Subject: Handle proxy dependencies differently. --- src/plugincontainer.cpp | 37 ++++++++++++++++++++++++------------- src/plugincontainer.h | 10 ++++++++++ src/settingsdialogplugins.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index bcd0db44..05c6790d 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -58,14 +58,11 @@ PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, MOBase: , m_Plugin(plugin) , m_PluginProxy(pluginProxy) , m_Organizer(proxy) -{ - for (auto* requirement : plugin->requirements()) { - m_Requirements.emplace_back(requirement); - } +{ } - // TODO: - if (pluginProxy) { - m_Requirements.emplace_back(PluginRequirementFactory::pluginDependency(pluginProxy->name())); +void PluginRequirements::fetchRequirements() { + for (auto* requirement : m_Plugin->requirements()) { + m_Requirements.emplace_back(requirement); } } @@ -74,6 +71,20 @@ 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 (m_Plugin->master().isEmpty()) { @@ -322,12 +333,15 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) return true; } + auto [it, bl] = m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); + if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } - m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); + // Update requirements: + it->second.fetchRequirements(); return true; } @@ -519,11 +533,8 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const return false; } - // Check the requirements (if a plugin checks in init(), the requirements have - // not been computed yet): - auto it = m_Requirements.find(plugin); - - return it == std::end(m_Requirements) ? true : it->second.canEnable(); + // Check the requirements: + return m_Requirements.at(plugin).canEnable(); } void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies) diff --git a/src/plugincontainer.h b/src/plugincontainer.h index e9761cb1..b37b48cd 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -49,6 +49,11 @@ public: */ 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. */ @@ -81,6 +86,11 @@ private: // 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(); + friend class PluginContainer; PluginContainer* m_PluginContainer; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 15c0b424..e9d36c57 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -225,6 +225,8 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) 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( @@ -233,6 +235,25 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) ui->enabledCheckbox->setChecked(true); return; } + + // Check the proxied plugins: + auto proxied = requirements.proxied(); + QStringList pluginNames; + for (auto& p : proxied) { + pluginNames.append(p->localizedName()); + } + pluginNames.sort(); + QString message = QObject::tr( + "

    Disabling this plugin will prevent the following plugins from working correctly:

      %1
    " + "

    Do you want to continue? You will need to restart ModOrganizer2 for the change to take effect.

    ") + .arg("
  • " + pluginNames.join("
  • ") + "
  • "); + if (QMessageBox::warning( + parentWidget(), QObject::tr("Really disable plugin?"), message, + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + ui->enabledCheckbox->setChecked(true); + return; + } + } // Check if the plugins is required for other plugins: @@ -256,6 +277,11 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) m_pluginContainer->setEnabled(plugin, false, true); } + // Proxy was disabled / enabled, need restart: + if (m_pluginContainer->implementInterface(plugin)) { + dialog().setExitNeeded(Exit::Restart); + } + updateListItems(); } -- cgit v1.3.1 From 4fa44fe37e9b2f9837f7fcbaf9361f9675669782 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 11 Nov 2020 22:54:45 +0100 Subject: Remove duplicate dependencies. --- src/settingsdialogplugins.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index e9d36c57..556cadcb 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -242,12 +242,13 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) for (auto& p : proxied) { pluginNames.append(p->localizedName()); } + pluginNames.removeDuplicates(); pluginNames.sort(); QString message = QObject::tr( - "

    Disabling this plugin will prevent the following plugins from working correctly:

      %1
    " + "

    Disabling this plugin will prevent the following plugins from working:

      %1
    " "

    Do you want to continue? You will need to restart ModOrganizer2 for the change to take effect.

    ") .arg("
  • " + pluginNames.join("
  • ") + "
  • "); - if (QMessageBox::warning( + if (QMessageBox::critical( parentWidget(), QObject::tr("Really disable plugin?"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { ui->enabledCheckbox->setChecked(true); @@ -263,6 +264,7 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) for (auto& p : requiredFor) { pluginNames.append(p->localizedName()); } + pluginNames.removeDuplicates(); pluginNames.sort(); QString message = QObject::tr( "

    Disabling this plugin will also disable the following plugins:

      %1

    Do you want to continue?

    ") -- cgit v1.3.1 From 15059907c9bd0061f502c64457fce20c7e60a07f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 12 Nov 2020 20:47:12 +0100 Subject: Proper handling of proxied plugins that implement multiple interfaces. --- src/plugincontainer.cpp | 149 ++++++++++++++++++++++++++++++++++-------- src/plugincontainer.h | 24 ++++++- src/settingsdialogplugins.cpp | 2 - 3 files changed, 144 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 05c6790d..94e4760c 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -53,12 +53,18 @@ QStringList PluginContainer::pluginInterfaces() // PluginRequirementProxy -PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, MOBase::IPlugin* plugin, IOrganizer* proxy, MOBase::IPluginProxy* pluginProxy) +PluginRequirements::PluginRequirements( + PluginContainer* pluginContainer, MOBase::IPlugin* plugin, IOrganizer* 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() { for (auto* requirement : m_Plugin->requirements()) { @@ -87,18 +93,36 @@ std::vector PluginRequirements::proxied() const 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); - if (plugin && plugin->master().compare(m_Plugin->name(), Qt::CaseInsensitive) == 0) { + + // 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 had it here. + if (plugin + && m_PluginContainer->requirements(plugin).master() == m_Plugin + && plugin->name() != m_Plugin->name()) { children.push_back(plugin); } } @@ -205,7 +229,6 @@ PluginContainer::~PluginContainer() { unloadPlugins(); } - void PluginContainer::setUserInterface(IUserInterface *userInterface, QWidget *widget) { for (IPluginProxy *proxy : bf::at_key(m_Plugins)) { @@ -221,7 +244,6 @@ void PluginContainer::setUserInterface(IUserInterface *userInterface, QWidget *w m_UserInterface = userInterface; } - QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const { // We need a QObject to be able to qobject_cast<> to the plugin types: @@ -251,7 +273,6 @@ QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const return names; } - QString PluginContainer::topImplementedInterface(IPlugin* plugin) const { // We need a QObject to be able to qobject_cast<> to the plugin types: @@ -276,6 +297,21 @@ QString PluginContainer::topImplementedInterface(IPlugin* plugin) const return name; } +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 { @@ -292,14 +328,13 @@ QStringList PluginContainer::pluginFileNames() const 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; @@ -351,19 +386,44 @@ void PluginContainer::registerGame(IPluginGame *game) m_SupportedGames.insert({ game->gameName(), game }); } -bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, MOBase::IPluginProxy* pluginProxy) +IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, MOBase::IPluginProxy* pluginProxy) { - // 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); // generic treatment for all plugins IPlugin *pluginObj = qobject_cast(plugin); if (pluginObj == nullptr) { - log::debug("not an IPlugin"); - return false; + log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); + return nullptr; } - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; + + // If we already a plugin with this name: + 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 + && as_qobject(other)->property("filename") == fileName) { + if (isBetterInterface(plugin, as_qobject(other))) { + log::debug("replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", + pluginObj->name(), implementedInterfaces(other).join(", "), implementedInterfaces(pluginObj).join(", ")); + bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; + } + } + else { + log::warn("Trying to register two plugins with the name '{}', the second one will not be registered.", + pluginObj->name()); + } + } + 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("filename", fileName); @@ -392,7 +452,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M IPluginModPage *modPage = qobject_cast(plugin); if (initPlugin(modPage, pluginProxy)) { bf::at_key(m_Plugins).push_back(modPage); - return true; + return modPage; } } { // game plugin @@ -402,7 +462,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M if (initPlugin(game, pluginProxy)) { bf::at_key(m_Plugins).push_back(game); registerGame(game); - return true; + return game; } } } @@ -410,7 +470,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M IPluginTool *tool = qobject_cast(plugin); if (initPlugin(tool, pluginProxy)) { bf::at_key(m_Plugins).push_back(tool); - return true; + return tool; } } { // installer plugins @@ -420,7 +480,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M if (m_Organizer) { m_Organizer->installationManager()->registerInstaller(installer); } - return true; + return installer; } } { // preview plugins @@ -428,7 +488,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M if (initPlugin(preview, pluginProxy)) { bf::at_key(m_Plugins).push_back(preview); m_PreviewGenerator.registerPlugin(preview); - return true; + return preview; } } { // proxy plugins @@ -439,12 +499,21 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); for (const QString &pluginName : pluginNames) { try { - // we get a list of matching plugins as proxies don't necessarily have a good way of supporting multiple inheritance + // 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->instantiate(pluginName); + + // We are going to group plugin by names and "fix" them later: + std::map> proxiedByNames; + for (QObject *proxiedPlugin : matchingPlugins) { if (proxiedPlugin != nullptr) { - if (registerPlugin(proxiedPlugin, pluginName, proxy)) { - log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); + if (IPlugin* proxied = registerPlugin(proxiedPlugin, pluginName, proxy); proxied) { + log::debug("loaded plugin '{}' from '{}' - [{}]", + proxied->name(), QFileInfo(pluginName).fileName(), implementedInterfaces(proxied).join(", ")); + + // Store the plugin for later: + proxiedByNames[proxied->name()].push_back(proxied); } else { log::warn( @@ -454,11 +523,28 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M } } } + + // 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(pluginName).arg(e.what())); } } - return true; + return proxy; } } @@ -467,13 +553,13 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, M IPlugin *dummy = qobject_cast(plugin); if (initPlugin(dummy, pluginProxy)) { bf::at_key(m_Plugins).push_back(dummy); - return true; + return dummy; } } log::debug("no matching plugin interface"); - return false; + return nullptr; } struct clearPlugins @@ -533,6 +619,12 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const return false; } + auto& requirements = m_Requirements.at(plugin); + + if (requirements.master()) { + return isEnabled(requirements.master()); + } + // Check the requirements: return m_Requirements.at(plugin).canEnable(); } @@ -721,8 +813,9 @@ void PluginContainer::loadPlugins() "failed to load plugin {}: {}", pluginName, pluginLoader->errorString()); } else { - if (registerPlugin(pluginLoader->instance(), pluginName, nullptr)) { - log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); + if (IPlugin* plugin = registerPlugin(pluginLoader->instance(), pluginName, nullptr); plugin) { + log::debug("loaded plugin '{}' from '{}' - [{}]", + plugin->name(), QFileInfo(pluginName).fileName(), implementedInterfaces(plugin).join(", ")); m_PluginLoaders.push_back(pluginLoader.release()); } else { m_FailedPlugins.push_back(pluginName); diff --git a/src/plugincontainer.h b/src/plugincontainer.h index b37b48cd..89ab5528 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -91,11 +91,15 @@ private: // 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 PluginContainer; PluginContainer* m_PluginContainer; MOBase::IPlugin* m_Plugin; MOBase::IPluginProxy* m_PluginProxy; + MOBase::IPlugin* m_Master; std::vector> m_Requirements; MOBase::IOrganizer* m_Organizer; std::vector m_RequiredFor; @@ -107,6 +111,9 @@ private: }; +/** + * + */ class PluginContainer : public QObject, public MOBase::IPluginDiagnose { @@ -229,6 +236,10 @@ public: * @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; @@ -320,6 +331,17 @@ private: friend class PluginRequirements; + /** + * @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. * @@ -341,7 +363,7 @@ private: void registerGame(MOBase::IPluginGame *game); - bool registerPlugin(QObject *pluginObj, const QString &fileName, MOBase::IPluginProxy *proxy); + MOBase::IPlugin* registerPlugin(QObject *pluginObj, const QString &fileName, MOBase::IPluginProxy *proxy); OrganizerCore *m_Organizer; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 556cadcb..ba690d41 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -242,7 +242,6 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) for (auto& p : proxied) { pluginNames.append(p->localizedName()); } - pluginNames.removeDuplicates(); pluginNames.sort(); QString message = QObject::tr( "

    Disabling this plugin will prevent the following plugins from working:

      %1
    " @@ -264,7 +263,6 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) for (auto& p : requiredFor) { pluginNames.append(p->localizedName()); } - pluginNames.removeDuplicates(); pluginNames.sort(); QString message = QObject::tr( "

    Disabling this plugin will also disable the following plugins:

      %1

    Do you want to continue?

    ") -- cgit v1.3.1 From 56dcdd33a2110a8c5ad13cc9401ce87fb3d7bf80 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 12 Nov 2020 21:23:35 +0100 Subject: It's 2020. --- src/plugincontainer.cpp | 51 +++++++++++++------------------------------ src/plugincontainer.h | 14 ++++++++++++ src/settingsdialogplugins.cpp | 2 +- 3 files changed, 30 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 94e4760c..d2f42fb4 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -253,6 +253,11 @@ QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const 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) { @@ -275,26 +280,8 @@ QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const QString PluginContainer::topImplementedInterface(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 {}; - } - - // Find all the names: - QString name; - boost::mp11::mp_for_each([oPlugin, &name](auto* p) { - using plugin_type = std::decay_t; - if (name.isEmpty() && qobject_cast(oPlugin)) { - auto tname = PluginTypeName::value(); - if (!tname.isEmpty()) { - name = tname; - } - } - }); - - return name; + auto interfaces = implementedInterfaces(plugin); + return interfaces.isEmpty() ? "" : interfaces[0]; } bool PluginContainer::isBetterInterface(QObject* lhs, QObject* rhs) const @@ -408,7 +395,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam && as_qobject(other)->property("filename") == fileName) { if (isBetterInterface(plugin, as_qobject(other))) { log::debug("replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", - pluginObj->name(), implementedInterfaces(other).join(", "), implementedInterfaces(pluginObj).join(", ")); + pluginObj->name(), implementedInterfaces(other).join(", "), implementedInterfaces(plugin).join(", ")); bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; } } @@ -562,15 +549,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam return nullptr; } -struct clearPlugins -{ - template - void operator()(T& t) const - { - t.second.clear(); - } -}; - void PluginContainer::unloadPlugins() { if (m_UserInterface != nullptr) { @@ -582,7 +560,7 @@ void PluginContainer::unloadPlugins() m_Organizer->disconnectPlugins(); } - bf::for_each(m_Plugins, clearPlugins()); + bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); }); for (const boost::signals2::connection &connection : m_DiagnosisConnections) { connection.disconnect(); @@ -614,17 +592,18 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const return plugin == m_Organizer->managedGame(); } - // Check if the plugin is enabled: - if (!m_Organizer->persistent(plugin->name(), "enabled", true).toBool()) { - return false; - } - + // 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", true).toBool()) { + return false; + } + // Check the requirements: return m_Requirements.at(plugin).canEnable(); } diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 89ab5528..ac40e413 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -331,6 +331,20 @@ private: friend class PluginRequirements; + + /** + * @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. diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index ba690d41..f019cd2f 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -31,7 +31,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta // display plugin settings QSet handledNames; for (IPlugin* plugin : settings().plugins().plugins()) { - if (handledNames.contains(plugin->name()) || !plugin->master().isEmpty()) { + if (handledNames.contains(plugin->name()) || m_pluginContainer->requirements(plugin).master()) { continue; } -- cgit v1.3.1 From a4acfc1f992eb5135c9ae6542ae5ffa8b09a2c2d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 12 Nov 2020 21:49:57 +0100 Subject: Add callbacks for plugin enabled/disabled. --- src/organizercore.cpp | 15 +++++++++++++++ src/organizercore.h | 5 +++++ src/organizerproxy.cpp | 30 +++++++++++++++++++++++++++++- src/organizerproxy.h | 4 ++++ src/plugincontainer.cpp | 7 +++++++ src/plugincontainer.h | 6 ++++++ 6 files changed, 66 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 86afe59a..e004ba78 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -295,6 +295,11 @@ void OrganizerCore::connectPlugins(PluginContainer *container) m_GamePlugin = m_PluginContainer->managedGame(m_GameName); emit managedGameChanged(m_GamePlugin); } + + connect(m_PluginContainer, &PluginContainer::pluginEnabled, + [&](IPlugin* plugin) { m_PluginEnabled(plugin); }); + connect(m_PluginContainer, &PluginContainer::pluginDisabled, + [&](IPlugin* plugin) { m_PluginDisabled(plugin); }); } void OrganizerCore::disconnectPlugins() @@ -1225,6 +1230,16 @@ bool OrganizerCore::onPluginSettingChanged(std::function const& func) +{ + return m_PluginEnabled.connect(func).connected(); +} + +bool OrganizerCore::onPluginDisabled(std::function const& func) +{ + return m_PluginDisabled.connect(func).connected(); +} + void OrganizerCore::refresh(bool saveChanges) { // don't lose changes! diff --git a/src/organizercore.h b/src/organizercore.h index b566e626..23b624e8 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -87,6 +87,7 @@ private: using SignalProfileRemoved = boost::signals2::signal; using SignalProfileChanged = boost::signals2::signal; using SignalPluginSettingChanged = boost::signals2::signal; + using SignalPluginEnabled = boost::signals2::signal; public: @@ -335,6 +336,8 @@ public: bool onProfileRemoved(std::function const& func); bool onProfileChanged(std::function const& func); bool onPluginSettingChanged(std::function const& func); + bool onPluginEnabled(std::function const& func); + bool onPluginDisabled(std::function const& func); bool getArchiveParsing() const { @@ -457,6 +460,8 @@ private: SignalProfileRemoved m_ProfileRemoved; SignalProfileChanged m_ProfileChanged; SignalPluginSettingChanged m_PluginSettingChanged; + SignalPluginEnabled m_PluginEnabled; + SignalPluginEnabled m_PluginDisabled; ModList m_ModList; PluginList m_PluginList; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 77b7c2d6..c2283acc 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -293,8 +293,36 @@ bool OrganizerProxy::onProfileChanged(std::functiononProfileChanged(MOShared::callIfPluginActive(this, func)); } +// Always call these one, otherwise plugin cannot detect they are being enabled / disabled: bool OrganizerProxy::onPluginSettingChanged(std::function const& func) { - // Always call this one, otherwise plugin cannot detect they are being enabled / disabled: return m_Proxied->onPluginSettingChanged(func); } + +bool OrganizerProxy::onPluginEnabled(std::function const& func) +{ + return m_Proxied->onPluginEnabled(func); +} + +bool OrganizerProxy::onPluginEnabled(const QString& pluginName, std::function const& func) +{ + return m_Proxied->onPluginEnabled([=](const IPlugin* plugin) { + if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { + func(); + } + }); +} + +bool OrganizerProxy::onPluginDisabled(std::function const& func) +{ + return m_Proxied->onPluginDisabled(func); +} + +bool OrganizerProxy::onPluginDisabled(const QString& pluginName, std::function const& func) +{ + return m_Proxied->onPluginDisabled([=](const IPlugin* plugin) { + if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { + func(); + } + }); +} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 4329bdda..8664c64f 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -69,6 +69,10 @@ public: virtual QVariant pluginSetting(const QString& pluginName, const QString& key) const override; virtual void setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override; virtual bool onPluginSettingChanged(std::function const& func) override; + virtual bool onPluginEnabled(std::function const& func) override; + virtual bool onPluginEnabled(const QString& pluginName, std::function const& func) override; + virtual bool onPluginDisabled(std::function const& func) override; + virtual bool onPluginDisabled(const QString& pluginName, std::function const& func) override; virtual MOBase::IPluginGame const *managedGame() const; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index d2f42fb4..1c2a1a1a 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -624,6 +624,13 @@ void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, bool depe } 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 diff --git a/src/plugincontainer.h b/src/plugincontainer.h index ac40e413..54da37d0 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -325,6 +325,12 @@ public: // IPluginDiagnose interface signals: + /** + * @brief Emitted plugins are enabled or disabled. + */ + void pluginEnabled(MOBase::IPlugin*); + void pluginDisabled(MOBase::IPlugin*); + void diagnosisUpdate(); private: -- cgit v1.3.1 From 0d6bdf5721f1c968444cd55fbd8a89beb8de7980 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 12 Nov 2020 22:08:55 +0100 Subject: Do not initialize proxied plugins twice. --- src/plugincontainer.cpp | 26 +++++++++++++++++--------- src/plugincontainer.h | 4 +++- 2 files changed, 20 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 1c2a1a1a..91655b2c 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -330,7 +330,7 @@ QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const return *it; } -bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) +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 @@ -357,7 +357,7 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy) auto [it, bl] = m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - if (!plugin->init(proxy)) { + if (!skipInit && !plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } @@ -384,6 +384,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } // If we already a plugin with this name: + bool skipInit = false; auto& mapNames = bf::at_key(m_AccessPlugins); if (mapNames.contains(pluginObj->name())) { @@ -393,6 +394,10 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam // ok (in theory some one could write two different classes from the same Python file/module): if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy && as_qobject(other)->property("filename") == fileName) { + + // 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(", ")); @@ -402,6 +407,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam else { log::warn("Trying to register two plugins with the name '{}', the second one will not be registered.", pluginObj->name()); + return nullptr; } } else { @@ -437,7 +443,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } { // mod page plugin IPluginModPage *modPage = qobject_cast(plugin); - if (initPlugin(modPage, pluginProxy)) { + if (initPlugin(modPage, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(modPage); return modPage; } @@ -446,7 +452,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam IPluginGame *game = qobject_cast(plugin); if (game) { game->detectGame(); - if (initPlugin(game, pluginProxy)) { + if (initPlugin(game, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(game); registerGame(game); return game; @@ -455,14 +461,14 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } { // tool plugins IPluginTool *tool = qobject_cast(plugin); - if (initPlugin(tool, pluginProxy)) { + if (initPlugin(tool, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(tool); return tool; } } { // installer plugins IPluginInstaller *installer = qobject_cast(plugin); - if (initPlugin(installer, pluginProxy)) { + if (initPlugin(installer, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(installer); if (m_Organizer) { m_Organizer->installationManager()->registerInstaller(installer); @@ -472,7 +478,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } { // preview plugins IPluginPreview *preview = qobject_cast(plugin); - if (initPlugin(preview, pluginProxy)) { + if (initPlugin(preview, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(preview); m_PreviewGenerator.registerPlugin(preview); return preview; @@ -480,7 +486,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } { // proxy plugins IPluginProxy *proxy = qobject_cast(plugin); - if (initPlugin(proxy, pluginProxy)) { + if (initPlugin(proxy, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(proxy); QStringList pluginNames = proxy->pluginList( QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); @@ -538,7 +544,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam { // dummy plugins // only initialize these, no processing otherwise IPlugin *dummy = qobject_cast(plugin); - if (initPlugin(dummy, pluginProxy)) { + if (initPlugin(dummy, pluginProxy, skipInit)) { bf::at_key(m_Plugins).push_back(dummy); return dummy; } @@ -561,6 +567,8 @@ void PluginContainer::unloadPlugins() } bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); }); + bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); }); + m_Requirements.clear(); for (const boost::signals2::connection &connection : m_DiagnosisConnections) { connection.disconnect(); diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 54da37d0..e9f0f453 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -376,10 +376,12 @@ private: * * @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 initPlugin(MOBase::IPlugin *plugin, MOBase::IPluginProxy* proxy, bool skipInit); void registerGame(MOBase::IPluginGame *game); -- cgit v1.3.1 From 5384fb1cf6c2b1377a35dbef17166d6907b74652 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 12 Nov 2020 22:30:22 +0100 Subject: Feeling like Shakespeare. --- src/plugincontainer.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 91655b2c..03950413 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -21,6 +21,54 @@ 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. A plugin is considered disabled +// if the setting is false. +// - If the setting is true or does not exist, 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; -- cgit v1.3.1 From fe6654f89220c6739405ff7d9a54bc49f92a4c5b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 13 Nov 2020 13:04:22 +0100 Subject: Better 'Browse Mod Page' menu. --- src/mainwindow.cpp | 56 +++++++++++++++++++++--------------------------------- src/mainwindow.h | 4 ---- src/mainwindow.ui | 22 +++++++++++++++++++++ 3 files changed, 44 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5a6382f..d20fd3ba 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -260,7 +260,6 @@ MainWindow::MainWindow(Settings &settings , m_ContextItem(nullptr) , m_ContextAction(nullptr) , m_ContextRow(-1) - , m_browseModPage(nullptr) , m_CurrentSaveView(nullptr) , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) @@ -811,6 +810,7 @@ static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex void MainWindow::setupToolbar() { + setupActionMenu(ui->actionModPage); setupActionMenu(ui->actionTool); setupActionMenu(ui->actionHelp); setupActionMenu(ui->actionEndorseMO); @@ -1596,29 +1596,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) return false; } -void MainWindow::modPagePluginInvoke() -{ - QAction *triggeredAction = qobject_cast(sender()); - IPluginModPage *plugin = qobject_cast(triggeredAction->data().value()); - if (plugin != nullptr) { - if (plugin->useIntegratedBrowser()) { - - if (!m_IntegratedBrowser) { - m_IntegratedBrowser.reset(new BrowserDialog); - - connect( - m_IntegratedBrowser.get(), SIGNAL(requestDownload(QUrl,QNetworkReply*)), - &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); - } - - m_IntegratedBrowser->setWindowTitle(plugin->displayName()); - m_IntegratedBrowser->openUrl(plugin->pageURL()); - } else { - QDesktopServices::openUrl(QUrl(plugin->pageURL())); - } - } -} - void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu) { if (!menu) { @@ -1694,23 +1671,34 @@ void MainWindow::updateToolMenu() void MainWindow::registerModPage(IPluginModPage *modPage) { // turn the browser action into a drop-down menu if necessary - if (!m_browseModPage) { - m_browseModPage = new QAction(ui->actionNexus->icon(), tr("Browse Mod Page"), this); - setupActionMenu(m_browseModPage); - - m_browseModPage->menu()->addAction(ui->actionNexus); - - ui->toolBar->insertAction(ui->actionNexus, m_browseModPage); + if (!ui->actionModPage->isVisible()) { ui->toolBar->removeAction(ui->actionNexus); + ui->actionModPage->menu()->addAction(ui->actionNexus); + ui->actionModPage->setVisible(true); } QAction *action = new QAction(modPage->icon(), modPage->displayName(), this); modPage->setParentWidget(this); - action->setData(QVariant::fromValue(reinterpret_cast(modPage))); + connect(action, &QAction::triggered, this, [this, modPage]() { + if (modPage->useIntegratedBrowser()) { + + if (!m_IntegratedBrowser) { + m_IntegratedBrowser.reset(new BrowserDialog); + + connect( + m_IntegratedBrowser.get(), SIGNAL(requestDownload(QUrl, QNetworkReply*)), + &m_OrganizerCore, SLOT(requestDownload(QUrl, QNetworkReply*))); + } - connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); + m_IntegratedBrowser->setWindowTitle(modPage->displayName()); + m_IntegratedBrowser->openUrl(modPage->pageURL()); + } + else { + QDesktopServices::openUrl(QUrl(modPage->pageURL())); + } + }, Qt::QueuedConnection); - m_browseModPage->menu()->addAction(action); + ui->actionModPage->menu()->addAction(action); } diff --git a/src/mainwindow.h b/src/mainwindow.h index d4aa0bc2..78ffec47 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -156,8 +156,6 @@ public slots: void directory_refreshed(); - void modPagePluginInvoke(); - signals: /** @@ -325,8 +323,6 @@ private: QTreeWidgetItem *m_ContextItem; QAction *m_ContextAction; - QAction* m_browseModPage; - CategoryFactory &m_CategoryFactory; QTimer m_CheckBSATimer; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index ace0dfeb..dbc6013d 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1411,6 +1411,7 @@ p, li { white-space: pre-wrap; } + @@ -1684,6 +1685,27 @@ p, li { white-space: pre-wrap; } Ctrl+N + + + + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png + + + Browse Mod Page + + + Browse Mod Page + + + Browse Mod Page + + + Browse Mod Page + + + false + + false -- cgit v1.3.1 From 4d94ba8dbba20da6d030a8a3adf73d2dbd6fc56f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 13 Nov 2020 13:18:05 +0100 Subject: Proper handling of mod pages. --- src/mainwindow.cpp | 51 +++++++++++++++++++++++++++++++++++++++++---------- src/mainwindow.h | 1 + 2 files changed, 42 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d20fd3ba..c927a691 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -467,6 +467,11 @@ MainWindow::MainWindow(Settings &settings connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { updateToolMenu(); }); + connect(&m_PluginContainer, &PluginContainer::pluginEnabled, this, [this](IPlugin* plugin) { + if (m_PluginContainer.implementInterface(plugin)) { updateModPageMenu(); } }); + connect(&m_PluginContainer, &PluginContainer::pluginDisabled, this, [this](IPlugin* plugin) { + if (m_PluginContainer.implementInterface(plugin)) { updateModPageMenu(); } }); + connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -508,9 +513,7 @@ MainWindow::MainWindow(Settings &settings installTranslator(QFileInfo(fileName).baseName()); } - for (IPluginModPage *modPagePlugin : m_PluginContainer.plugins()) { - registerModPage(modPagePlugin); - } + updateModPageMenu(); // refresh profiles so the current profile can be activated refreshProfiles(false); @@ -1670,13 +1673,6 @@ void MainWindow::updateToolMenu() void MainWindow::registerModPage(IPluginModPage *modPage) { - // turn the browser action into a drop-down menu if necessary - if (!ui->actionModPage->isVisible()) { - ui->toolBar->removeAction(ui->actionNexus); - ui->actionModPage->menu()->addAction(ui->actionNexus); - ui->actionModPage->setVisible(true); - } - QAction *action = new QAction(modPage->icon(), modPage->displayName(), this); modPage->setParentWidget(this); connect(action, &QAction::triggered, this, [this, modPage]() { @@ -1701,6 +1697,41 @@ void MainWindow::registerModPage(IPluginModPage *modPage) ui->actionModPage->menu()->addAction(action); } +void MainWindow::updateModPageMenu() +{ + // Clear the menu: + ui->actionModPage->menu()->clear(); + ui->actionModPage->menu()->addAction(ui->actionNexus); + + std::vector modPagePlugins = m_PluginContainer.plugins(); + + // Sort the plugins by display name + std::sort(std::begin(modPagePlugins), std::end(modPagePlugins), + [](IPluginModPage* left, IPluginModPage* right) { + return left->displayName().toLower() < right->displayName().toLower(); + } + ); + + // Remove disabled plugins: + modPagePlugins.erase( + std::remove_if(std::begin(modPagePlugins), std::end(modPagePlugins), [&](auto* tool) { + return !m_PluginContainer.isEnabled(tool); + }), + modPagePlugins.end()); + + for (auto* modPagePlugin : modPagePlugins) { + registerModPage(modPagePlugin); + } + + // No mod page plugin and the menu was visible: + if (modPagePlugins.empty()) { + ui->toolBar->insertAction(ui->actionAdd_Profile, ui->actionNexus); + } + else { + ui->toolBar->removeAction(ui->actionNexus); + } + ui->actionModPage->setVisible(!modPagePlugins.empty()); +} void MainWindow::startExeAction() { diff --git a/src/mainwindow.h b/src/mainwindow.h index 78ffec47..e2805b25 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -210,6 +210,7 @@ private: void updateToolbarMenu(); void updateToolMenu(); + void updateModPageMenu(); void updateViewMenu(); QMenu* createPopupMenu() override; -- cgit v1.3.1 From 34e9fb001567a244eec296f5ab7e747a9f23db2a Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 13 Nov 2020 18:26:49 +0100 Subject: Check for disabled mod page plugins. --- src/organizercore.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index e004ba78..ae031fe9 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1465,10 +1465,13 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) { - if (m_PluginContainer != nullptr) { - for (IPluginModPage *modPage : - m_PluginContainer->plugins()) { - ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); + if (!m_PluginContainer) { + return; + } + for (IPluginModPage *modPage : + m_PluginContainer->plugins()) { + if (m_PluginContainer->isEnabled(modPage)) { + ModRepositoryFileInfo* fileInfo = new ModRepositoryFileInfo(); if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { fileInfo->repository = modPage->name(); m_DownloadManager.addDownload(reply, fileInfo); -- cgit v1.3.1 From 037ca381a4c39490998c7642537dd54cc4f4aeee Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 10:27:52 +0100 Subject: Fix filter for child plugins. --- src/settingsdialogplugins.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index f019cd2f..19515426 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -128,7 +128,7 @@ void PluginsSettingsTab::filterPluginList() return regex.match(plugin->localizedName()).hasMatch(); }); for (auto* child : m_pluginContainer->requirements(plugin).children()) { - m_filter.matches([child](const QRegularExpression& regex) { + match = match || m_filter.matches([child](const QRegularExpression& regex) { return regex.match(child->localizedName()).hasMatch(); }); } -- cgit v1.3.1 From e0b73a7d7d27d42b87b3061c51d5d22f57646c45 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 10:45:29 +0100 Subject: Fix selection in Plugins settings. --- src/settingsdialog.ui | 130 +++++++++++++++++++++++------------------- src/settingsdialogplugins.cpp | 22 +++++-- 2 files changed, 87 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 4fd34b21..d05910cb 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1111,8 +1111,8 @@
    - - + + 0 @@ -1126,63 +1126,65 @@ 0 - - - 6 - - - - - Author: - - - - - - - - - - - - - - Version: - - - - - - - - - - - - - - - - - true - - - - - - - Description: - - - - - - - Enabled - - - - + + + + 6 + + + + + Author: + + + + + + + + + + + + + + Version: + + + + + + + + + + + + + + Description: + + + + + + + + + + true + + + + + + + Enabled + + + + + @@ -1216,6 +1218,16 @@ + + + + No plugin found. + + + Qt::AlignCenter + + + diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 19515426..a86ba5c9 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -25,6 +25,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta item->setFont(0, font); topItems[interfaceName] = item; item->setExpanded(true); + item->setFlags(item->flags() & ~Qt::ItemIsSelectable); } ui->pluginsList->setHeaderHidden(true); @@ -112,6 +113,7 @@ void PluginsSettingsTab::updateListItems() void PluginsSettingsTab::filterPluginList() { + auto selectedItems = ui->pluginsList->selectedItems(); QTreeWidgetItem* firstNotHidden = nullptr; for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { @@ -150,13 +152,21 @@ void PluginsSettingsTab::filterPluginList() } // Unselect item if hidden: - auto selectedItems = ui->pluginsList->selectedItems(); - if (!selectedItems.isEmpty() && selectedItems[0]->isHidden()) { - selectedItems[0]->setSelected(false); - - if (firstNotHidden) { - firstNotHidden->setSelected(true); + if (firstNotHidden) { + ui->pluginDescription->setVisible(true); + ui->pluginSettingsList->setVisible(true); + ui->noPluginLabel->setVisible(false); + if (selectedItems.isEmpty()) { + ui->pluginsList->setCurrentItem(firstNotHidden); } + else if (selectedItems[0]->isHidden()) { + ui->pluginsList->setCurrentItem(firstNotHidden); + } + } + else { + ui->pluginDescription->setVisible(false); + ui->pluginSettingsList->setVisible(false); + ui->noPluginLabel->setVisible(true); } } -- cgit v1.3.1 From c62da01478822daa9837529916b3290ab07fd7a3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 10:49:29 +0100 Subject: Replace : by . in comments. --- src/installationmanager.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/installationmanager.h b/src/installationmanager.h index e1db5e1f..145abdb6 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -285,7 +285,7 @@ private: private: - // The plugin container, mostly to check if installer are enabled or not: + // The plugin container, mostly to check if installer are enabled or not. const PluginContainer *m_PluginContainer; bool m_IsRunning; @@ -298,13 +298,13 @@ private: std::vector m_Installers; std::set m_SupportedExtensions; - // Archive management: + // Archive management. std::unique_ptr m_ArchiveHandler; QString m_CurrentFile; QString m_Password; // Map from entries in the tree that is used by the installer and absolute - // paths to temporary files: + // paths to temporary files. std::map, QString> m_CreatedFiles; std::set m_TempFilesToDelete; }; -- cgit v1.3.1 From 9fd2a757fa0c25c72c6d8927924d0a8668e948c4 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 10:50:27 +0100 Subject: Fix had -> add. --- src/plugincontainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 03950413..8c2ece76 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -167,7 +167,7 @@ std::vector PluginRequirements::children() const // 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 had it here. + // master, we do not add it here. if (plugin && m_PluginContainer->requirements(plugin).master() == m_Plugin && plugin->name() != m_Plugin->name()) { -- cgit v1.3.1 From 1e67c3e68dbe4141c9cd3bfd7b29c747fca03753 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 12:30:59 +0100 Subject: Add core plugins and better messages. --- src/plugincontainer.cpp | 20 +++++++++++++++++++- src/plugincontainer.h | 9 +++++++++ src/settingsdialogplugins.cpp | 13 +++++++------ 3 files changed, 35 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 8c2ece76..7884498c 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -101,6 +101,10 @@ QStringList PluginContainer::pluginInterfaces() // PluginRequirementProxy +const std::set PluginRequirements::s_CorePlugins{ + "INI Bakery" +}; + PluginRequirements::PluginRequirements( PluginContainer* pluginContainer, MOBase::IPlugin* plugin, IOrganizer* proxy, MOBase::IPluginProxy* pluginProxy) @@ -193,6 +197,16 @@ 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(); @@ -405,7 +419,11 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy, boo auto [it, bl] = m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - if (!skipInit && !plugin->init(proxy)) { + if (skipInit) { + return true; + } + + if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } diff --git a/src/plugincontainer.h b/src/plugincontainer.h index e9f0f453..36edfad9 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -39,6 +39,12 @@ public: */ 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). */ @@ -83,6 +89,9 @@ public: 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; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index a86ba5c9..0c860fa0 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -241,7 +241,8 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) if (m_pluginContainer->requirements(game).proxy() == plugin) { QMessageBox::warning( parentWidget(), QObject::tr("Cannot disable plugin"), - QObject::tr("This plugin is used by the current game plugin and cannot disabled."), QMessageBox::Ok); + 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; } @@ -254,9 +255,9 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) } pluginNames.sort(); QString message = QObject::tr( - "

    Disabling this plugin will prevent the following plugins from working:

      %1
    " + "

    Disabling the '%1' plugin will prevent the following plugins from working:

      %1
    " "

    Do you want to continue? You will need to restart ModOrganizer2 for the change to take effect.

    ") - .arg("
  • " + pluginNames.join("
  • ") + "
  • "); + .arg(plugin->localizedName()).arg("
  • " + pluginNames.join("
  • ") + "
  • "); if (QMessageBox::critical( parentWidget(), QObject::tr("Really disable plugin?"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { @@ -275,8 +276,8 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) } pluginNames.sort(); QString message = QObject::tr( - "

    Disabling this plugin will also disable the following plugins:

      %1

    Do you want to continue?

    ") - .arg("
  • " + pluginNames.join("
  • ") + "
  • "); + "

    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) { @@ -310,7 +311,7 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->descriptionLabel->setText(plugin->description()); ui->enabledCheckbox->setVisible( - !m_pluginContainer->implementInterface(plugin) + !m_pluginContainer->requirements(plugin).isCorePlugin() && plugin->master().isEmpty()); ui->enabledCheckbox->setChecked(m_pluginContainer->isEnabled(plugin)); -- cgit v1.3.1 From e9a4f91fac08087ef83ce5b7716abeda90b6bde3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 13:24:57 +0100 Subject: Display inactive checkbox for core plugins (not for games). --- src/settingsdialogplugins.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 0c860fa0..eb0035b2 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -310,9 +310,17 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->versionLabel->setText(plugin->version().canonicalString()); ui->descriptionLabel->setText(plugin->description()); + ui->enabledCheckbox->setDisabled(false); + ui->enabledCheckbox->setToolTip(""); ui->enabledCheckbox->setVisible( - !m_pluginContainer->requirements(plugin).isCorePlugin() + !m_pluginContainer->implementInterface(plugin) && plugin->master().isEmpty()); + 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.")); + } + ui->enabledCheckbox->setChecked(m_pluginContainer->isEnabled(plugin)); QVariantMap settings = current->data(0, ROLE_SETTINGS).toMap(); -- cgit v1.3.1 From bed3475362f68a660d56dd31546b13e49586e36e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 14:28:00 +0100 Subject: Fix crash for proxied plugins on first startup. --- src/plugincontainer.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 7884498c..f7a556e0 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -413,12 +413,12 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy, boo // 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; } - auto [it, bl] = m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - if (skipInit) { return true; } @@ -616,8 +616,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam } } - log::debug("no matching plugin interface"); - return nullptr; } -- cgit v1.3.1 From 8886674379c507fe46bfc6cb2ef013537567441b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 14:28:43 +0100 Subject: Better dialog for warning when disabling proxy plugin. --- src/CMakeLists.txt | 1 + src/disableproxyplugindialog.cpp | 30 +++++++ src/disableproxyplugindialog.h | 43 ++++++++++ src/disableproxyplugindialog.ui | 174 +++++++++++++++++++++++++++++++++++++++ src/settingsdialogplugins.cpp | 28 +++---- 5 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 src/disableproxyplugindialog.cpp create mode 100644 src/disableproxyplugindialog.h create mode 100644 src/disableproxyplugindialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bb4b151f..a53d845d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,6 +186,7 @@ add_filter(NAME src/settingsdialog GROUPS settingsdialogplugins settingsdialogsteam settingsdialogworkarounds + disableproxyplugindialog ) add_filter(NAME src/utilities GROUPS diff --git a/src/disableproxyplugindialog.cpp b/src/disableproxyplugindialog.cpp new file mode 100644 index 00000000..f392113b --- /dev/null +++ b/src/disableproxyplugindialog.cpp @@ -0,0 +1,30 @@ +#include "disableproxyplugindialog.h" + +#include "ui_proxyplugindialog.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 new file mode 100644 index 00000000..52f552e1 --- /dev/null +++ b/src/disableproxyplugindialog.h @@ -0,0 +1,43 @@ +/* +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 new file mode 100644 index 00000000..9f068787 --- /dev/null +++ b/src/disableproxyplugindialog.ui @@ -0,0 +1,174 @@ + + + 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/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index eb0035b2..4313b85e 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -3,6 +3,7 @@ #include "noeditdelegate.h" #include +#include "disableproxyplugindialog.h" #include "organizercore.h" #include "plugincontainer.h" @@ -242,29 +243,20 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) 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; + .arg(plugin->localizedName()), QMessageBox::Ok); + ui->enabledCheckbox->setChecked(true); + return; } // Check the proxied plugins: auto proxied = requirements.proxied(); - QStringList pluginNames; - for (auto& p : proxied) { - pluginNames.append(p->localizedName()); - } - pluginNames.sort(); - QString message = QObject::tr( - "

    Disabling the '%1' plugin will prevent the following plugins from working:

      %1
    " - "

    Do you want to continue? You will need to restart ModOrganizer2 for the change to take effect.

    ") - .arg(plugin->localizedName()).arg("
  • " + pluginNames.join("
  • ") + "
  • "); - if (QMessageBox::critical( - parentWidget(), QObject::tr("Really disable plugin?"), message, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - ui->enabledCheckbox->setChecked(true); - return; + 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: -- cgit v1.3.1 From 1d6d01dec887126b70d1de4bbf562d3803072f68 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 18:24:10 +0100 Subject: Disable checkbox when plugin cannot be enabled. --- src/disableproxyplugindialog.cpp | 2 +- src/settingsdialogplugins.cpp | 46 ++++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/disableproxyplugindialog.cpp b/src/disableproxyplugindialog.cpp index f392113b..c05e30e3 100644 --- a/src/disableproxyplugindialog.cpp +++ b/src/disableproxyplugindialog.cpp @@ -1,6 +1,6 @@ #include "disableproxyplugindialog.h" -#include "ui_proxyplugindialog.h" +#include "ui_disableproxyplugindialog.h" using namespace MOBase; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 4313b85e..16f6fb0a 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -217,20 +217,6 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) // User wants to enable: if (checked) { - auto problems = requirements.problems(); - if (!problems.empty()) { - QStringList descriptions; - for (auto& problem : problems) { - descriptions.append(problem.shortDescription()); - } - QMessageBox::warning( - parentWidget(), QObject::tr("Cannot enable plugin"), - QObject::tr("

    This plugin cannot be enabled:

      %1
    ") - .arg("
  • " + descriptions.join("
  • ") + "
  • "), QMessageBox::Ok); - ui->enabledCheckbox->setChecked(false); - return; - } - m_pluginContainer->setEnabled(plugin, true, false); } else { @@ -302,18 +288,42 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->versionLabel->setText(plugin->version().canonicalString()); ui->descriptionLabel->setText(plugin->description()); - ui->enabledCheckbox->setDisabled(false); - ui->enabledCheckbox->setToolTip(""); + // Checkbox, do not show for children or game plugins, disable + // if the plugin cannot be enabled. ui->enabledCheckbox->setVisible( !m_pluginContainer->implementInterface(plugin) && plugin->master().isEmpty()); + + bool enabled = m_pluginContainer->isEnabled(plugin); + auto& requirements = m_pluginContainer->requirements(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.")); } - - ui->enabledCheckbox->setChecked(m_pluginContainer->isEnabled(plugin)); + // Plugin is enable or can be enabled. + else if (enabled || problems.empty()) { + ui->enabledCheckbox->setDisabled(false); + ui->enabledCheckbox->setToolTip(""); + ui->enabledCheckbox->setChecked(enabled); + } + // Plugin is disable and cannot be enabled. + else { + ui->enabledCheckbox->setDisabled(true); + ui->enabledCheckbox->setChecked(false); + if (problems.size() == 1) { + ui->enabledCheckbox->setToolTip(problems[0].shortDescription()); + } + else { + QStringList descriptions; + for (auto& problem : problems) { + descriptions.append(problem.shortDescription()); + } + ui->enabledCheckbox->setToolTip("
    • " + descriptions.join("
    • ") + "
    "); + } + } QVariantMap settings = current->data(0, ROLE_SETTINGS).toMap(); QVariantMap descriptions = current->data(0, ROLE_DESCRIPTIONS).toMap(); -- cgit v1.3.1 From 61554d3df7be909acf6609b4b7465b03d7f16081 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 17 Nov 2020 18:27:21 +0100 Subject: Better vertical alignment for plugin descriptions. --- src/settingsdialog.ui | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index d05910cb..2d4ca993 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1128,6 +1128,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + 6 @@ -1136,6 +1139,9 @@ Author: + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -1143,6 +1149,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -1150,6 +1159,9 @@ Version: + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -1157,6 +1169,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -1164,6 +1179,9 @@ Description: + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -1171,6 +1189,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + true -- cgit v1.3.1