diff options
| author | Mikaël Capelle <capelle.mikael@gmail.com> | 2020-11-11 13:39:53 +0100 |
|---|---|---|
| committer | Mikaël Capelle <capelle.mikael@gmail.com> | 2020-11-11 20:30:34 +0100 |
| commit | 7a4f6d5344031db828f5dc2257c1d19f251e7f05 (patch) | |
| tree | b72b9da6eafb0c9cb747254c1b34e7a73469cd6e | |
| parent | eb1d340f2d98e65b1859fd103658f2671569c342 (diff) | |
Improve requirements handling.
| -rw-r--r-- | src/plugincontainer.cpp | 218 | ||||
| -rw-r--r-- | src/plugincontainer.h | 109 | ||||
| -rw-r--r-- | src/settings.cpp | 17 | ||||
| -rw-r--r-- | src/settingsdialog.ui | 10 | ||||
| -rw-r--r-- | src/settingsdialogplugins.cpp | 97 | ||||
| -rw-r--r-- | src/settingsdialogplugins.h | 7 |
6 files changed, 361 insertions, 97 deletions
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<unsigned int> 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::Problem> PluginRequirements::problems() const
+{
+ std::vector<Problem> 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<const GameDependencyRequirement*>(requirement.get())) {
+ return gdep->gameNames();
+ }
+ }
+
+ return {};
}
-QString PluginRequirementProxy::description(unsigned int id) const
+
+std::vector<MOBase::IPlugin*> PluginRequirements::requiredFor() const
{
- return m_Requirement->description(id);
+ std::vector<MOBase::IPlugin*> required;
+ std::set<MOBase::IPlugin*> 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<MOBase::IPlugin*> &required, std::set<MOBase::IPlugin*>& 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<const PluginDependencyRequirement*>(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<IPluginProxy*>(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<IPluginModPage*>(plugin);
- if (initPlugin(modPage)) {
+ if (initPlugin(modPage, pluginProxy)) {
bf::at_key<IPluginModPage>(m_Plugins).push_back(modPage);
return true;
}
}
{ // game plugin
IPluginGame *game = qobject_cast<IPluginGame*>(plugin);
-
if (game) {
game->detectGame();
- }
-
- if (initPlugin(game)) {
- bf::at_key<IPluginGame>(m_Plugins).push_back(game);
- registerGame(game);
- return true;
+ if (initPlugin(game, pluginProxy)) {
+ bf::at_key<IPluginGame>(m_Plugins).push_back(game);
+ registerGame(game);
+ return true;
+ }
}
}
{ // tool plugins
IPluginTool *tool = qobject_cast<IPluginTool*>(plugin);
- if (initPlugin(tool)) {
+ if (initPlugin(tool, pluginProxy)) {
bf::at_key<IPluginTool>(m_Plugins).push_back(tool);
return true;
}
}
{ // installer plugins
IPluginInstaller *installer = qobject_cast<IPluginInstaller*>(plugin);
- if (initPlugin(installer)) {
+ if (initPlugin(installer, pluginProxy)) {
bf::at_key<IPluginInstaller>(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<IPluginPreview*>(plugin);
- if (initPlugin(preview)) {
+ if (initPlugin(preview, pluginProxy)) {
bf::at_key<IPluginPreview>(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<IPluginProxy*>(plugin);
- if (initProxyPlugin(proxy)) {
+ if (initPlugin(proxy, pluginProxy)) {
bf::at_key<IPluginProxy>(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<QObject*> 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<IPlugin*>(plugin);
- if (initPlugin(dummy)) {
+ if (initPlugin(dummy, pluginProxy)) {
bf::at_key<IPlugin>(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<IPluginGame*>(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<PluginRequirementProxy> PluginContainer::requirements(IPlugin* plugin) const
+const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const
{
- auto* proxy = m_Proxies.at(plugin);
- std::vector<PluginRequirementProxy> 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<unsigned int> 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<Problem> 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<MOBase::IPlugin*> requiredFor() const;
+
+private:
+
+ // Accumulator version for requiredFor() to avoid infinite recursion.
+ void requiredFor(std::vector<MOBase::IPlugin*>& required, std::set<MOBase::IPlugin*>& visited) const;
friend class PluginContainer;
+ PluginContainer* m_PluginContainer;
+ MOBase::IPlugin* m_Plugin;
+ MOBase::IPluginProxy* m_PluginProxy;
+ std::vector<std::unique_ptr<const MOBase::IPluginRequirement>> m_Requirements;
+ MOBase::IOrganizer* m_Organizer;
+ std::vector<MOBase::IPlugin*> m_RequiredFor;
+
+ PluginRequirements(
+ PluginContainer* pluginContainer, MOBase::IPlugin* plugin,
+ MOBase::IOrganizer* proxy, MOBase::IPluginProxy* pluginProxy);
+
};
@@ -176,13 +229,18 @@ public: 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.
*
* @param plugin The plugin to check.
*
* @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;
@@ -190,13 +248,23 @@ public: bool isEnabled(MOBase::IPluginFileMapper* mapper) const;
/**
+ * @brief Enable or disable a plugin.
+ *
+ * @param plugin The plugin to enable or disable.
+ * @param enable true to enable, false to disable.
+ * @param dependencies If true and enable is false, dependencies will also
+ * be disabled (see PluginRequirements::requiredFor).
+ */
+ void setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies = true);
+
+ /**
* @brief Retrieve the requirements for the given plugin.
*
* @param plugin The plugin to retrieve the requirements for.
*
* @return the requirements (as proxy) for the given plugin.
*/
- std::vector<PluginRequirementProxy> 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<MOBase::IPlugin*, OrganizerProxy*> m_Proxies;
- std::map<MOBase::IPlugin*, std::vector<std::unique_ptr<const MOBase::IPluginRequirement>>> m_Requirements;
+ std::map<MOBase::IPlugin*, PluginRequirements> m_Requirements;
std::map<QString, MOBase::IPluginGame*> m_SupportedGames;
std::vector<boost::signals2::connection> 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 @@ </property> <item> <layout class="QFormLayout" name="formLayout_2"> + <property name="leftMargin"> + <number>6</number> + </property> <item row="0" column="0"> <widget class="QLabel" name="label_13"> <property name="text"> @@ -1172,6 +1175,13 @@ </property> </widget> </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="enabledCheckbox"> + <property name="text"> + <string>Enabled</string> + </property> + </widget> + </item> </layout> </item> <item> 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 <iplugin.h> +#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<IPluginGame>(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("<p>This plugin cannot be enabled:<p><ul>%1</ul>") + .arg("<li>" + descriptions.join("</li><li>") + "</li>"), QMessageBox::Ok); + ui->enabledCheckbox->setChecked(false); + return; + } + + m_pluginContainer->setEnabled(plugin, false, true); + } + else { + // Custom check for proxy + current game: + if (m_pluginContainer->implementInterface<IPluginProxy>(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( + "<p>Disabling this plugin will also disable the following plugins:</p><ul>%1</ul><p>Do you want to continue?</p>") + .arg("<li>" + pluginNames.join("</li><li>") + "</li>"); + 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<MOBase::IPluginGame>(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. * |
