// Copyright (C) 2022 ~ 2022 Deepin Technology Co., Ltd. // SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include "dockplugincontroller.h" #include "docksettings.h" #include "pluginsiteminterface.h" #include "pluginsiteminterface_v20.h" #include "pluginadapter.h" #include "utils.h" #include #include #include #include #include #include #define PLUGININFO "pluginInfo" static const QStringList CompatiblePluginApiList { "1.1.1", "1.2", "1.2.1", "1.2.2", DOCK_PLUGIN_API_VERSION }; class PluginInfo : public QObject { public: PluginInfo() : QObject(nullptr), m_loaded(false), m_visible(false) {} bool m_loaded; bool m_visible; QString m_itemKey; }; DockPluginController::DockPluginController(PluginProxyInterface *proxyInter, QObject *parent) : QObject(parent) , m_dbusDaemonInterface(QDBusConnection::sessionBus().interface()) // , m_dockDaemonInter(new DockInter(dockServiceName(), dockServicePath(), QDBusConnection::sessionBus(), this)) , m_proxyInter(proxyInter) { qApp->installEventFilter(this); refreshPluginSettings(); connect(DockSettings::instance(), &DockSettings::quickPluginsChanged, this, &DockPluginController::onConfigChanged); // connect(m_dockDaemonInter, &DockInter::PluginSettingsSynced, this, &DockPluginController::refreshPluginSettings, Qt::QueuedConnection); } DockPluginController::~DockPluginController() { for (auto inter : m_pluginsMap.keys()) { delete m_pluginsMap.value(inter).value("pluginloader"); m_pluginsMap[inter]["pluginloader"] = nullptr; if (m_pluginsMap[inter].contains(PLUGININFO)) m_pluginsMap[inter][PLUGININFO]->deleteLater(); m_pluginsMap.remove(inter); delete inter; inter = nullptr; } } QList DockPluginController::plugins() const { return m_pluginsMap.keys(); } QList DockPluginController::pluginsInSetting() const { // 插件有三种状态 // 1、所有的插件,不管这个插件是否调用itemAdded方法,只要是通过dock加载的插件(换句话说,也就是在/lib/dde-dock/plugins目录下的插件) // 2、在1的基础上,插件自身调用了itemAdded方法的插件,例如机器上没有蓝牙设备,那么蓝牙插件就不会调用itemAdded方法,这时候就不算 // 3、在2的基础上,由控制中心来决定那些插件是否在任务栏显示的插件 // 此处返回的是第二种插件 QList settingPlugins; QMap pluginSort; for (auto it = m_pluginsMap.begin(); it != m_pluginsMap.end(); it++) { PluginsItemInterface *plugin = it.key(); qInfo() << plugin->pluginName(); if (plugin->pluginDisplayName().isEmpty()) continue; QMap pluginMap = it.value(); // 如果不包含PLUGININFO这个key值,肯定是未加载 if (!pluginMap.contains(PLUGININFO)) continue; PluginInfo *pluginInfo = static_cast(pluginMap[PLUGININFO]); if (!pluginInfo->m_loaded) continue; // 这里只需要返回插件为可以在控制中心设置的插件 if (!(plugin->flags() & PluginFlag::Attribute_CanSetting)) continue; settingPlugins << plugin; pluginSort[plugin] = plugin->itemSortKey(pluginInfo->m_itemKey); } std::sort(settingPlugins.begin(), settingPlugins.end(), [ pluginSort ](PluginsItemInterface *plugin1, PluginsItemInterface *plugin2) { return pluginSort[plugin1] < pluginSort[plugin2]; }); return settingPlugins; } QList DockPluginController::currentPlugins() const { QList loadedPlugins; QMap pluginSortMap; for (auto it = m_pluginsMap.begin(); it != m_pluginsMap.end(); it++) { QMap objectMap = it.value(); if (!objectMap.contains(PLUGININFO)) continue; PluginInfo *pluginInfo = static_cast(objectMap[PLUGININFO]); if (!pluginInfo->m_loaded) continue; PluginsItemInterface *plugin = it.key(); loadedPlugins << plugin; pluginSortMap[plugin] = plugin->itemSortKey(pluginInfo->m_itemKey); } std::sort(loadedPlugins.begin(), loadedPlugins.end(), [ pluginSortMap ](PluginsItemInterface *pluginItem1, PluginsItemInterface *pluginItem2) { return pluginSortMap.value(pluginItem1) < pluginSortMap.value(pluginItem2); }); return loadedPlugins; } void DockPluginController::saveValue(PluginsItemInterface *const itemInter, const QString &key, const QVariant &value) { savePluginValue(getPluginInterface(itemInter), key, value); } const QVariant DockPluginController::getValue(PluginsItemInterface *const itemInter, const QString &key, const QVariant &fallback) { return getPluginValue(getPluginInterface(itemInter), key, fallback); } void DockPluginController::removeValue(PluginsItemInterface *const itemInter, const QStringList &keyList) { removePluginValue(getPluginInterface(itemInter), keyList); } void DockPluginController::itemAdded(PluginsItemInterface * const itemInter, const QString &itemKey) { PluginsItemInterface *pluginItem = getPluginInterface(itemInter); PluginAdapter *pluginAdapter = dynamic_cast(pluginItem); if (pluginAdapter) { // 如果该插件可以正常转换为PluginAdapter插件,表示当前插件是v20插件,为了兼容v20插件 // 中获取ICON,因此,使用调用插件的itemWidget来截图的方式返回QIcon,所以此处传入itemKey pluginAdapter->setItemKey(itemKey); } // 如果是通过插件来调用m_proxyInter的 PluginInfo *pluginInfo = nullptr; QMap &interfaceData = m_pluginsMap[pluginItem]; if (interfaceData.contains(PLUGININFO)) { pluginInfo = static_cast(interfaceData[PLUGININFO]); // 如果插件已经加载,则无需再次加载(此处保证插件出现重复调用itemAdded的情况) if (pluginInfo->m_loaded) return; } else { pluginInfo = new PluginInfo; interfaceData[PLUGININFO] = pluginInfo; } pluginInfo->m_itemKey = itemKey; pluginInfo->m_loaded = true; if (pluginCanDock(pluginItem)) addPluginItem(pluginItem, itemKey); Q_EMIT pluginInserted(pluginItem, itemKey); } void DockPluginController::itemUpdate(PluginsItemInterface * const itemInter, const QString &itemKey) { m_proxyInter->itemUpdate(getPluginInterface(itemInter), itemKey); } void DockPluginController::itemRemoved(PluginsItemInterface * const itemInter, const QString &itemKey) { PluginsItemInterface *pluginInter = getPluginInterface(itemInter); // 更新字段中的isLoaded字段,表示当前没有加载 QMap &interfaceData = m_pluginsMap[pluginInter]; if (interfaceData.contains(PLUGININFO)) { PluginInfo *pluginInfo = static_cast(interfaceData[PLUGININFO]); // 将是否加载的标记修改为未加载 pluginInfo->m_loaded = false; } removePluginItem(pluginInter, itemKey); Q_EMIT pluginRemoved(pluginInter); } void DockPluginController::requestWindowAutoHide(PluginsItemInterface * const itemInter, const QString &itemKey, const bool autoHide) { m_proxyInter->requestWindowAutoHide(getPluginInterface(itemInter), itemKey, autoHide); } void DockPluginController::requestRefreshWindowVisible(PluginsItemInterface * const itemInter, const QString &itemKey) { m_proxyInter->requestRefreshWindowVisible(getPluginInterface(itemInter), itemKey); } // 请求页面显示或者隐藏,由插件内部来调用,例如在移除蓝牙插件后,如果已经弹出了蓝牙插件的面板,则隐藏面板 void DockPluginController::requestSetAppletVisible(PluginsItemInterface * const itemInter, const QString &itemKey, const bool visible) { PluginsItemInterface *pluginInter = getPluginInterface(itemInter); Q_EMIT requestAppletVisible(pluginInter, itemKey, visible); m_proxyInter->requestSetAppletVisible(pluginInter, itemKey, visible); } PluginsItemInterface *DockPluginController::getPluginInterface(PluginsItemInterface * const itemInter) { // 先从事先定义好的map中查找,如果没有找到,就是v23插件,直接返回当前插件的指针 qulonglong pluginAddr = (qulonglong)itemInter; if (m_pluginAdapterMap.contains(pluginAddr)) return m_pluginAdapterMap[pluginAddr]; return itemInter; } void DockPluginController::addPluginItem(PluginsItemInterface * const itemInter, const QString &itemKey) { // 如果这个插件都没有加载,那么此处肯定是无需新增 if (!m_pluginsMap.contains(itemInter)) return; PluginInfo *pluginInfo = nullptr; QMap &interfaceData = m_pluginsMap[itemInter]; // 此处的PLUGININFO的数据已经在前面调用的地方给填充了数据,如果没有获取到这个数据,则无需新增 if (!interfaceData.contains(PLUGININFO)) return; pluginInfo = static_cast(interfaceData[PLUGININFO]); pluginInfo->m_visible = true; m_proxyInter->itemAdded(itemInter, itemKey); } void DockPluginController::removePluginItem(PluginsItemInterface * const itemInter, const QString &itemKey) { if (!m_pluginsMap.contains(itemInter)) return; // 更新字段中的isLoaded字段,表示当前没有加载 QMap &interfaceData = m_pluginsMap[itemInter]; if (!interfaceData.contains(PLUGININFO)) return; PluginInfo *pluginInfo = static_cast(interfaceData[PLUGININFO]); // 将是否在任务栏显示的标记改为不显示 pluginInfo->m_visible = false; if (QWidget * popup = itemInter->itemPopupApplet(itemKey)) popup->hide(); m_proxyInter->itemRemoved(itemInter, itemKey); } QString DockPluginController::itemKey(PluginsItemInterface *itemInter) const { if (!m_pluginsMap.contains(itemInter)) return QString(); QMap interfaceData = m_pluginsMap[itemInter]; if (!interfaceData.contains(PLUGININFO)) return QString(); PluginInfo *pluginInfo = static_cast(interfaceData[PLUGININFO]); return pluginInfo->m_itemKey; } QJsonObject DockPluginController::metaData(PluginsItemInterface *pluginItem) { if (!m_pluginsMap.contains(pluginItem)) return QJsonObject(); QPluginLoader *pluginLoader = qobject_cast(m_pluginsMap[pluginItem].value("pluginloader")); if (!pluginLoader) return QJsonObject(); return pluginLoader->metaData().value("MetaData").toObject(); } void DockPluginController::savePluginValue(PluginsItemInterface * const itemInter, const QString &key, const QVariant &value) { // is it necessary? // refreshPluginSettings(); // save to local cache QJsonObject localObject = m_pluginSettingsObject.value(itemInter->pluginName()).toObject(); localObject.insert(key, QJsonValue::fromVariant(value)); //Note: QVariant::toJsonValue() not work in Qt 5.7 // save to daemon QJsonObject remoteObject, remoteObjectInter; remoteObjectInter.insert(key, QJsonValue::fromVariant(value)); //Note: QVariant::toJsonValue() not work in Qt 5.7 remoteObject.insert(itemInter->pluginName(), remoteObjectInter); if (itemInter->type() == PluginsItemInterface::Fixed && key == "enable" && !value.toBool()) { int fixedPluginCount = 0; // 遍历FixPlugin插件个数 for (auto it(m_pluginsMap.begin()); it != m_pluginsMap.end();) { if (it.key()->type() == PluginsItemInterface::Fixed) { fixedPluginCount++; } ++it; } // 修改插件的order值,位置为队尾 QString name = localObject.keys().last(); // 此次做一下判断,有可能初始数据不存在pos_*字段,会导致enable字段被修改。或者此处可以循环所有字段是否存在pos_开头的字段? if (name != key) { localObject.insert(name, QJsonValue::fromVariant(fixedPluginCount)); //Note: QVariant::toJsonValue() not work in Qt 5.7 // daemon中同样修改 remoteObjectInter.insert(name, QJsonValue::fromVariant(fixedPluginCount)); //Note: QVariant::toJsonValue() not work in Qt 5.7 remoteObject.insert(itemInter->pluginName(), remoteObjectInter); } } m_pluginSettingsObject.insert(itemInter->pluginName(), localObject); DockSettings::instance()->mergePluginSettings(QJsonDocument(remoteObject).toJson(QJsonDocument::JsonFormat::Compact)); } const QVariant DockPluginController::getPluginValue(PluginsItemInterface * const itemInter, const QString &key, const QVariant &fallback) { // load from local cache QVariant v = m_pluginSettingsObject.value(itemInter->pluginName()).toObject().value(key).toVariant(); if (v.isNull() || !v.isValid()) { v = fallback; } return v; } void DockPluginController::removePluginValue(PluginsItemInterface * const itemInter, const QStringList &keyList) { if (keyList.isEmpty()) { m_pluginSettingsObject.remove(itemInter->pluginName()); } else { QJsonObject localObject = m_pluginSettingsObject.value(itemInter->pluginName()).toObject(); for (auto key : keyList) { localObject.remove(key); } m_pluginSettingsObject.insert(itemInter->pluginName(), localObject); } DockSettings::instance()->removePluginSettings(itemInter->pluginName(), keyList); } void DockPluginController::startLoadPlugin(const QStringList &dirs) { QDir dir; for (const QString &path : dirs) { if (!dir.exists(path)) continue; startLoader(new PluginLoader(path, this)); } } bool DockPluginController::isPluginLoaded(PluginsItemInterface *itemInter) { if (!m_pluginsMap.contains(itemInter)) return false; QMap pluginObject = m_pluginsMap.value(itemInter); if (!pluginObject.contains(PLUGININFO)) return false; PluginInfo *pluginInfo = static_cast(pluginObject.value(PLUGININFO)); return pluginInfo->m_visible; } QObject *DockPluginController::pluginItemAt(PluginsItemInterface *const itemInter, const QString &itemKey) const { if (!m_pluginsMap.contains(itemInter)) return nullptr; return m_pluginsMap[itemInter][itemKey]; } PluginsItemInterface *DockPluginController::pluginInterAt(const QString &itemKey) { QMapIterator> it(m_pluginsMap); while (it.hasNext()) { it.next(); if (it.value().keys().contains(itemKey)) { return it.key(); } } return nullptr; } PluginsItemInterface *DockPluginController::pluginInterAt(QObject *destItem) { QMapIterator> it(m_pluginsMap); while (it.hasNext()) { it.next(); if (it.value().values().contains(destItem)) { return it.key(); } } return nullptr; } void DockPluginController::startLoader(PluginLoader *loader) { connect(loader, &PluginLoader::finished, loader, &PluginLoader::deleteLater, Qt::QueuedConnection); connect(loader, &PluginLoader::pluginFounded, this, [ = ](const QString &pluginFile) { QPair pair; pair.first = pluginFile; pair.second = nullptr; m_pluginLoadMap.insert(pair, false); }); connect(loader, &PluginLoader::pluginFounded, this, &DockPluginController::loadPlugin, Qt::QueuedConnection); int delay = Utils::SettingValue("com.deepin.dde.dock", "/com/deepin/dde/dock/", "delay-plugins-time", 0).toInt(); QTimer::singleShot(delay, loader, [ = ] { loader->start(QThread::LowestPriority); }); } void DockPluginController::displayModeChanged() { const Dock::DisplayMode displayMode = qApp->property(PROP_DISPLAY_MODE).value(); const auto inters = m_pluginsMap.keys(); for (auto inter : inters) inter->displayModeChanged(displayMode); } void DockPluginController::positionChanged() { const Dock::Position position = qApp->property(PROP_POSITION).value(); const auto inters = m_pluginsMap.keys(); for (auto inter : inters) inter->positionChanged(position); } void DockPluginController::loadPlugin(const QString &pluginFile) { QPluginLoader *pluginLoader = new QPluginLoader(pluginFile, this); const QJsonObject &meta = pluginLoader->metaData().value("MetaData").toObject(); const QString &pluginApi = meta.value("api").toString(); bool pluginIsValid = true; if (pluginApi.isEmpty() || !CompatiblePluginApiList.contains(pluginApi)) { qDebug() << objectName() << "plugin api version not matched! expect versions:" << CompatiblePluginApiList << ", got version:" << pluginApi << ", the plugin file is:" << pluginFile; pluginIsValid = false; } PluginsItemInterface *interface = qobject_cast(pluginLoader->instance()); if (!interface) { // 如果识别当前插件失败,就认为这个插件是v20的插件,将其转换为v20插件接口 PluginsItemInterface_V20 *interface_v20 = qobject_cast(pluginLoader->instance()); if (interface_v20) { // 将v20插件接口通过适配器转换成v23的接口,方便在后面识别 PluginAdapter *pluginAdapter = new PluginAdapter(interface_v20, pluginLoader); // 将适配器的地址保存到map列表中,因为适配器自己会调用itemAdded方法,转换成PluginsItemInterface类,但是实际上它 // 对应的是PluginAdapter类,因此,这个map用于在后面的itemAdded方法中用来查找 m_pluginAdapterMap[(qulonglong)(interface_v20)] = pluginAdapter; interface = pluginAdapter; } } if (!interface) { qDebug() << objectName() << "load plugin failed!!!" << pluginLoader->errorString() << pluginFile; pluginLoader->unload(); pluginLoader->deleteLater(); pluginIsValid = false; } if (!pluginIsValid) { for (auto &pair : m_pluginLoadMap.keys()) { if (pair.first == pluginFile) { m_pluginLoadMap.remove(pair); } } QString notifyMessage(tr("The plugin %1 is not compatible with the system.")); Dtk::Core::DUtil::DNotifySender(notifyMessage.arg(QFileInfo(pluginFile).fileName())).appIcon("dialog-warning").call(); return; } if (interface->pluginName() == "multitasking" && (Utils::IS_WAYLAND_DISPLAY || Dtk::Core::DSysInfo::deepinType() == Dtk::Core::DSysInfo::DeepinServer)) { for (auto &pair : m_pluginLoadMap.keys()) { if (pair.first == pluginFile) { m_pluginLoadMap.remove(pair); } } return; } QMapIterator, bool> it(m_pluginLoadMap); while (it.hasNext()) { it.next(); if (it.key().first == pluginFile) { m_pluginLoadMap.remove(it.key()); QPair newPair; newPair.first = pluginFile; newPair.second = interface; m_pluginLoadMap.insert(newPair, false); break; } } // 保存 PluginLoader 对象指针 QMap interfaceData; interfaceData["pluginloader"] = pluginLoader; m_pluginsMap.insert(interface, interfaceData); QString dbusService = meta.value("depends-daemon-dbus-service").toString(); if (!dbusService.isEmpty() && !m_dbusDaemonInterface->isServiceRegistered(dbusService).value()) { qDebug() << objectName() << dbusService << "daemon has not started, waiting for signal"; connect(m_dbusDaemonInterface, &QDBusConnectionInterface::serviceOwnerChanged, this, [ = ](const QString & name, const QString & oldOwner, const QString & newOwner) { Q_UNUSED(oldOwner); if (name == dbusService && !newOwner.isEmpty()) { qDebug() << objectName() << dbusService << "daemon started, init plugin and disconnect"; initPlugin(interface); disconnect(m_dbusDaemonInterface); } } ); return; } // NOTE(justforlxz): 插件的所有初始化工作都在init函数中进行, // loadPlugin函数是按队列执行的,initPlugin函数会有可能导致 // 函数执行被阻塞。 QTimer::singleShot(1, this, [ = ] { initPlugin(interface); }); } void DockPluginController::initPlugin(PluginsItemInterface *interface) { if (!interface) return; qDebug() << objectName() << "init plugin: " << interface->pluginName(); interface->init(this); for (const auto &pair : m_pluginLoadMap.keys()) { if (pair.second == interface) m_pluginLoadMap.insert(pair, true); } bool loaded = true; for (int i = 0; i < m_pluginLoadMap.keys().size(); ++i) { if (!m_pluginLoadMap.values()[i]) { loaded = false; break; } } // 插件全部加载完成 if (loaded) { emit pluginLoadFinished(); } qDebug() << objectName() << "init plugin finished: " << interface->pluginName(); } void DockPluginController::refreshPluginSettings() { const QString &pluginSettings = DockSettings::instance()->getPluginSettings(); if (pluginSettings.isEmpty()) { qDebug() << "Error! get plugin settings from dbus failed!"; return; } const QJsonObject &pluginSettingsObject = QJsonDocument::fromJson(pluginSettings.toLocal8Bit()).object(); if (pluginSettingsObject.isEmpty()) { return; } // nothing changed if (pluginSettingsObject == m_pluginSettingsObject) { return; } for (auto pluginsIt = pluginSettingsObject.constBegin(); pluginsIt != pluginSettingsObject.constEnd(); ++pluginsIt) { const QString &pluginName = pluginsIt.key(); const QJsonObject &settingsObject = pluginsIt.value().toObject(); QJsonObject newSettingsObject = m_pluginSettingsObject.value(pluginName).toObject(); for (auto settingsIt = settingsObject.constBegin(); settingsIt != settingsObject.constEnd(); ++settingsIt) { newSettingsObject.insert(settingsIt.key(), settingsIt.value()); } // TODO: remove not exists key-values m_pluginSettingsObject.insert(pluginName, newSettingsObject); } // not notify plugins to refresh settings if this update is not emit by dock daemon // if (sender() != m_dockDaemonInter) { // return; // } // notify all plugins to reload plugin settings for (PluginsItemInterface *pluginInter : m_pluginsMap.keys()) { pluginInter->pluginSettingsChanged(); } // reload all plugin items for sort order or container QMap> pluginsMapTemp = m_pluginsMap; for (auto it = pluginsMapTemp.constBegin(); it != pluginsMapTemp.constEnd(); ++it) { const QList &itemKeyList = it.value().keys(); for (auto key : itemKeyList) { if (key != "pluginloader") { itemRemoved(it.key(), key); } } for (auto key : itemKeyList) { if (key != "pluginloader") { itemAdded(it.key(), key); } } } } bool DockPluginController::eventFilter(QObject *object, QEvent *event) { if (object != qApp || event->type() != QEvent::DynamicPropertyChange) return false; QDynamicPropertyChangeEvent *const dpce = static_cast(event); const QString propertyName = dpce->propertyName(); if (propertyName == PROP_POSITION) positionChanged(); else if (propertyName == PROP_DISPLAY_MODE) displayModeChanged(); return false; } bool DockPluginController::pluginCanDock(PluginsItemInterface *plugin) const { const QStringList configPlugins = DockSettings::instance()->getQuickPlugins(); return pluginCanDock(configPlugins, plugin); } bool DockPluginController::pluginCanDock(const QStringList &config, PluginsItemInterface *plugin) const { // 1、如果插件是强制驻留任务栏,则始终显示 // 2、如果插件是托盘插件,例如U盘插件,则始终显示 if ((plugin->flags() & PluginFlag::Attribute_ForceDock) || (plugin->flags() & PluginFlag::Type_Tray)) return true; // 3、如果该插件并未加载(未调用itemAdde或已经调用itemRemoved),则该插件不显示 if (!m_pluginsMap.contains(plugin)) return false; const QMap &pluginMap = m_pluginsMap[plugin]; // 如果不包含PLUGININFO,说明该插件从未调用itemAdded方法,无需加载 if (!pluginMap.contains(PLUGININFO)) return false; // 如果该插件信息的m_loaded为true,说明已经调用过itemAdded方法,并且之后又调用了itemRemoved方法,则插件也无需加载 PluginInfo *pluginInfo = static_cast(pluginMap[PLUGININFO]); if (!pluginInfo->m_loaded) return false; // 4、插件已经驻留在任务栏,则始终显示 return config.contains(plugin->pluginName()); } void DockPluginController::updateDockInfo(PluginsItemInterface * const itemInter, const DockPart &part) { m_proxyInter->updateDockInfo(itemInter, part); Q_EMIT pluginUpdated(itemInter, part); } void DockPluginController::onConfigChanged(const QStringList &pluginNames) { // 这里只处理工具插件(回收站)和系统插件(电源插件) for (PluginsItemInterface *plugin : plugins()) { QString itemKey = this->itemKey(plugin); bool canDock = pluginCanDock(pluginNames, plugin); if (!canDock && isPluginLoaded(plugin)) { // 如果当前配置中不包含当前插件,但是当前插件已经加载,那么就移除该插件 removePluginItem(plugin, itemKey); QWidget *itemWidget = plugin->itemWidget(itemKey); if (itemWidget) itemWidget->setVisible(false); } else if (canDock && !isPluginLoaded(plugin)) { // 如果当前配置中包含当前插件,但是当前插件并未加载,那么就加载该插件 if (!pluginNames.contains(plugin->pluginName())) { // deepin-screen-recorder has Attribute_ForceDock flag // FIX https://github.com/linuxdeepin/developer-center/issues/4215 continue; } addPluginItem(plugin, itemKey); // 工具|固定区域 插件是通过QWidget的方式进行显示的 if (plugin->flags() & (PluginFlag::Type_Tool | PluginFlag::Type_Fixed)) { QWidget *itemWidget = plugin->itemWidget(itemKey); if (itemWidget) itemWidget->setVisible(true); } } } }