File indexing completed on 2024-05-05 04:33:15
0001 /* 0002 SPDX-FileCopyrightText: 2004-2018 Gilles Caulier <caulier dot gilles at gmail dot com> 0003 SPDX-FileCopyrightText: 2012 Victor Dodon <dodonvictor at gmail dot com> 0004 SPDX-FileCopyrightText: 2004-2005 Renchi Raju <renchi dot raju at gmail dot com> 0005 SPDX-FileCopyrightText: 2004-2005 Jesper K. Pedersen <blackie at kde dot org> 0006 SPDX-FileCopyrightText: 2004-2005 Aurelien Gateau <aurelien dot gateau at free dot fr> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "plugin.h" 0012 0013 // Qt includes 0014 0015 #include <QApplication> 0016 #include <QWidget> 0017 #include <QFile> 0018 #include <QDir> 0019 #include <QAction> 0020 #include <QStandardPaths> 0021 0022 // KF includes 0023 0024 #include <KActionCollection> 0025 0026 // Local includes 0027 0028 #include "libkipi_version.h" 0029 #include "libkipi_debug.h" 0030 #include "interface.h" 0031 #include "pluginloader.h" 0032 0033 namespace KIPI 0034 { 0035 0036 class Q_DECL_HIDDEN Plugin::Private 0037 { 0038 public: 0039 0040 Private() : 0041 uiBaseName(QString()) 0042 { 0043 defaultWidget = nullptr; 0044 defaultCategory = InvalidCategory; 0045 } 0046 0047 ActionCategoryMap actionsCat; 0048 QWidget* defaultWidget; 0049 QString uiBaseName; 0050 Category defaultCategory; 0051 0052 public: 0053 0054 class XMLParser 0055 { 0056 0057 public: 0058 0059 static QDomElement makeElement(QDomDocument& domDoc, const QDomElement& from); 0060 static void buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths); 0061 static int findByNameAttr(const QDomNodeList& list, const QDomElement& node); 0062 static void removeDisabledActions(QDomElement& elem); 0063 0064 private: 0065 0066 XMLParser(); 0067 static void buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths, QDomElemList& stack); 0068 }; 0069 }; 0070 0071 QDomElement Plugin::Private::XMLParser::makeElement(QDomDocument& domDoc, const QDomElement& from) 0072 { 0073 if (domDoc.isNull() || from.isNull()) 0074 return QDomElement(); 0075 0076 QDomElement elem = domDoc.createElement(from.tagName()); 0077 QDomNamedNodeMap attributes = from.attributes(); 0078 0079 for (int i = 0; i < attributes.size(); ++i) 0080 { 0081 QDomAttr attr = attributes.item(i).toAttr(); 0082 0083 if (attr.name() != QString::fromLatin1("alreadyVisited")) 0084 elem.setAttributeNode(attr); 0085 } 0086 0087 return elem; 0088 } 0089 0090 void Plugin::Private::XMLParser::buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths) 0091 { 0092 /* 0093 * For each child element of "local", we will construct the path from the 0094 * "original" element to first appearance of the respective child in the 0095 * subtree. 0096 */ 0097 QDomElemList stack; 0098 buildPaths(original, localNodes, paths, stack); 0099 } 0100 0101 int Plugin::Private::XMLParser::findByNameAttr(const QDomNodeList& list, const QDomElement& node) 0102 { 0103 const QString nodeName = node.toElement().attribute(QString::fromLatin1("name")); 0104 const QString nodeTag = node.toElement().tagName(); 0105 0106 for (int i = 0; i < list.size(); ++i) 0107 { 0108 QDomElement e = list.at(i).toElement(); 0109 0110 if (e.tagName() == nodeTag && e.attribute(QString::fromLatin1("name")) == nodeName) 0111 return i; 0112 } 0113 0114 return -1; 0115 } 0116 0117 void Plugin::Private::XMLParser::removeDisabledActions(QDomElement& elem) 0118 { 0119 QDomNodeList actionList = elem.elementsByTagName(QString::fromLatin1("Action")); 0120 QStringList disabledActions = PluginLoader::instance()->disabledPluginActions(); 0121 QDomElemList disabledElements; 0122 0123 for(int i = 0; i < actionList.size(); ++i) 0124 { 0125 QDomElement el = actionList.item(i).toElement(); 0126 0127 if (el.isNull()) 0128 continue; 0129 0130 if (disabledActions.contains(el.attribute(QString::fromLatin1("name")))) 0131 { 0132 disabledElements << el; 0133 } 0134 } 0135 0136 for (QDomElement element : std::as_const(disabledElements)) 0137 { 0138 //qCDebug(LIBKIPI_LOG) << "Plugin action '" << element.attribute("name") << "' is disabled."; 0139 QDomElement parent = element.parentNode().toElement(); 0140 parent.removeChild(element); 0141 } 0142 } 0143 0144 void Plugin::Private::XMLParser::buildPaths(const QDomElement& original, const QDomNodeList& localNodes, 0145 QHashPath& paths, QDomElemList& stack) 0146 { 0147 stack.push_back(original.cloneNode(true).toElement()); 0148 0149 int idx; 0150 0151 if ((idx = findByNameAttr(localNodes, original)) != -1) 0152 { 0153 paths[localNodes.item(idx).toElement().attribute(QString::fromLatin1("name"))] = stack; 0154 } 0155 0156 if (!original.hasChildNodes()) 0157 { 0158 stack.pop_back(); 0159 return; 0160 } 0161 0162 for (QDomNode n = original.firstChild(); !n.isNull(); n = n.nextSibling()) 0163 { 0164 QDomElement e = n.toElement(); 0165 0166 if (e.tagName() == QString::fromLatin1("Menu") && e.hasChildNodes()) 0167 { 0168 buildPaths(e, localNodes, paths, stack); 0169 } 0170 } 0171 0172 stack.pop_back(); 0173 } 0174 0175 // -------------------------------------------------------------------------------------------------------------- 0176 0177 Plugin::Plugin(QObject* const parent, const char* name) 0178 : QObject(parent), d(new Private) 0179 { 0180 setObjectName(QString::fromLatin1(name)); 0181 } 0182 0183 Plugin::~Plugin() 0184 { 0185 clearActions(); 0186 } 0187 0188 QList<QAction *> Plugin::actions(QWidget* const widget) const 0189 { 0190 QWidget* const w = !widget ? d->defaultWidget : widget; 0191 0192 if (!d->actionsCat.contains(w)) 0193 { 0194 qCWarning(LIBKIPI_LOG) << "Error in plugin. It needs to call Plugin::setup(QWidget*) " 0195 << "as the very first line when overriding the setup method."; 0196 } 0197 0198 return d->actionsCat[w].keys(); 0199 } 0200 0201 void Plugin::addAction(const QString& name, QAction* const action) 0202 { 0203 if (!action || name.isEmpty()) 0204 return; 0205 0206 if (!PluginLoader::instance()->disabledPluginActions().contains(name)) 0207 { 0208 actionCollection()->addAction(name, action); 0209 addAction(action); 0210 } 0211 else 0212 { 0213 //qCDebug(LIBKIPI_LOG) << "Action '" << name << "' is disabled."; 0214 } 0215 } 0216 0217 void Plugin::addAction(QAction* const action) 0218 { 0219 addAction(action, d->defaultCategory); 0220 } 0221 0222 void Plugin::addAction(const QString& name, QAction* const action, Category cat) 0223 { 0224 if (!action || name.isEmpty()) 0225 return; 0226 0227 if (!PluginLoader::instance()->disabledPluginActions().contains(name)) 0228 { 0229 actionCollection()->addAction(name, action); 0230 addAction(action, cat); 0231 } 0232 else 0233 { 0234 //qCDebug(LIBKIPI_LOG) << "Action '" << name << "' is disabled."; 0235 } 0236 } 0237 0238 void Plugin::addAction(QAction* const action, Category cat) 0239 { 0240 if (cat == InvalidCategory) 0241 { 0242 qCWarning(LIBKIPI_LOG) << "Error in plugin. Action '" << action->objectName() << "has " 0243 "invalid category. You must set default plugin category or " 0244 "to use a valid Category"; 0245 } 0246 0247 d->actionsCat[d->defaultWidget].insert(action, cat); 0248 } 0249 0250 void Plugin::setup(QWidget* const widget) 0251 { 0252 clearActions(); 0253 d->defaultWidget = widget; 0254 d->actionsCat.insert(widget, QMap<QAction*, Category>()); 0255 } 0256 0257 Category Plugin::category(QAction* const action) const 0258 { 0259 QMap<QAction *, Category>::const_iterator it = d->actionsCat[d->defaultWidget].constFind(action); 0260 0261 if (it != d->actionsCat[d->defaultWidget].constEnd()) 0262 { 0263 return it.value(); 0264 } 0265 else 0266 { 0267 if (d->defaultCategory == InvalidCategory) 0268 { 0269 qCWarning(LIBKIPI_LOG) << "Error in plugin. Invalid category. " 0270 "You must set default plugin category."; 0271 } 0272 0273 return d->defaultCategory; 0274 } 0275 } 0276 0277 Interface* Plugin::interface() const 0278 { 0279 return (dynamic_cast<Interface*>(parent())); 0280 } 0281 0282 void Plugin::setUiBaseName(const char* name) 0283 { 0284 if (name && *name) 0285 d->uiBaseName = QString::fromLatin1(name); 0286 } 0287 0288 QString Plugin::uiBaseName() const 0289 { 0290 return d->uiBaseName; 0291 } 0292 0293 void Plugin::mergeXMLFile(KXMLGUIClient *const host) 0294 { 0295 if (!host) 0296 { 0297 qCCritical(LIBKIPI_LOG) << "Host KXMLGUIClient is null!"; 0298 return; 0299 } 0300 0301 if (d->uiBaseName.isEmpty()) 0302 { 0303 qCCritical(LIBKIPI_LOG) << "UI file basename is not set! You must first call setUiBaseName."; 0304 return; 0305 } 0306 0307 const QString componentName = QApplication::applicationName(); 0308 const QString defaultUI = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("kxmlgui5/kipi/") + d->uiBaseName); 0309 const QString localUIdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString::fromLatin1("/kxmlgui5/") + 0310 componentName; 0311 const QString localUI = localUIdir + QString::fromLatin1("/") + d->uiBaseName; 0312 0313 qCDebug(LIBKIPI_LOG) << "UI file :" << defaultUI; 0314 0315 QFile defaultUIFile(defaultUI); 0316 QDomDocument defaultDomDoc; 0317 0318 if (!defaultUIFile.open(QFile::ReadOnly) || !defaultDomDoc.setContent(&defaultUIFile)) 0319 { 0320 qCCritical(LIBKIPI_LOG) << "Could not open default ui file " << defaultUI << " for ui basename " << d->uiBaseName; 0321 return; 0322 } 0323 0324 defaultUIFile.close(); 0325 const QDomDocument hostDoc = host->domDocument(); 0326 0327 if (hostDoc.isNull() || defaultDomDoc.isNull()) 0328 { 0329 qCCritical(LIBKIPI_LOG) << "Cannot merge the XML files, at least one is null!"; 0330 return; 0331 } 0332 0333 QDomElement hostGuiElem = hostDoc.firstChildElement(QString::fromLatin1("kpartgui")); 0334 QDomElement hostMenuBarElem = hostGuiElem.firstChildElement(QString::fromLatin1("MenuBar")); 0335 0336 QDomDocument newPluginDoc(defaultDomDoc.doctype()); 0337 QDomElement defGuiElem = defaultDomDoc.firstChildElement(QString::fromLatin1("gui")); 0338 0339 Private::XMLParser::removeDisabledActions(defGuiElem); 0340 0341 QDomElement newGuiElem = Private::XMLParser::makeElement(newPluginDoc, defGuiElem); 0342 QDomElement defMenuBarElem = defGuiElem.firstChildElement(QString::fromLatin1("MenuBar")); 0343 QDomElement newMenuBarElem = Private::XMLParser::makeElement(newPluginDoc, defMenuBarElem); 0344 QDomElement defToolBarElem = defGuiElem.firstChildElement(QString::fromLatin1("ToolBar")); 0345 QDomElement defActionPropElem = defGuiElem.firstChildElement(QString::fromLatin1("ActionProperties")); 0346 0347 QHashPath paths; 0348 Private::XMLParser::buildPaths(hostMenuBarElem, defMenuBarElem.childNodes(), paths); 0349 0350 for (QDomNode n = defMenuBarElem.firstChild(); !n.isNull(); n = n.nextSibling()) 0351 { 0352 QDomElemList path = paths[n.toElement().attribute(QString::fromLatin1("name"))]; 0353 QDomElement current = newMenuBarElem; 0354 QDomElement origCurr = defMenuBarElem; 0355 0356 if (path.empty()) 0357 { 0358 newMenuBarElem.appendChild(n.cloneNode()); 0359 } 0360 else 0361 { 0362 for (int i = 1; i < path.size() - 1; ++i) 0363 { 0364 int idx = Private::XMLParser::findByNameAttr(current.childNodes(), path[i]); 0365 origCurr = path[i]; 0366 0367 if (idx == -1) 0368 { 0369 if (!path[i].isNull()) 0370 { 0371 QDomElement newChild = Private::XMLParser::makeElement(newPluginDoc, path[i]); 0372 QDomElement textElem = origCurr.firstChildElement(QString::fromLatin1("text")); 0373 0374 if (!textElem.isNull()) 0375 { 0376 newChild.appendChild(textElem.cloneNode()); 0377 } 0378 0379 current.appendChild(newChild); 0380 current = newChild; 0381 } 0382 } 0383 else 0384 { 0385 current = current.childNodes().item(idx).toElement(); 0386 } 0387 } 0388 } 0389 0390 if (!current.isNull()) 0391 current.appendChild(n.cloneNode()); 0392 } 0393 0394 newGuiElem.appendChild(newMenuBarElem); 0395 QFile localUIFile(localUI); 0396 QDomDocument localDomDoc; 0397 // be safe rather than sorry 0398 // create the appname folder in kxmlgui5 0399 QDir localUIDir(localUIdir); 0400 if (!localUIDir.exists()) 0401 QDir().mkpath(localUIdir); 0402 0403 if (!localUIFile.exists() || !localUIFile.open(QFile::ReadOnly) || !localDomDoc.setContent(&localUIFile)) 0404 { 0405 newGuiElem.appendChild(defToolBarElem.cloneNode()); 0406 newGuiElem.appendChild(defActionPropElem.cloneNode()); 0407 } 0408 else 0409 { 0410 QDomElement localGuiElem = localDomDoc.firstChildElement(QString::fromLatin1("gui")); 0411 0412 Private::XMLParser::removeDisabledActions(localGuiElem); 0413 0414 QDomElement localToolBarElem = localGuiElem.firstChildElement(QString::fromLatin1("ToolBar")); 0415 QDomElement localActionPropElem = localGuiElem.firstChildElement(QString::fromLatin1("ActionProperties")); 0416 0417 newGuiElem.appendChild(localToolBarElem.cloneNode()); 0418 newGuiElem.appendChild(localActionPropElem.cloneNode()); 0419 } 0420 0421 localUIFile.close(); 0422 QFile writeFile(localUI); 0423 0424 if (!writeFile.open(QFile::WriteOnly | QFile::Truncate)) 0425 { 0426 qCCritical(LIBKIPI_LOG) << "Could not open " << localUI << " for writing!"; 0427 return; 0428 } 0429 0430 newPluginDoc.appendChild(newGuiElem); 0431 0432 writeFile.write(newPluginDoc.toString().toUtf8()); 0433 writeFile.close(); 0434 0435 setXMLFile(d->uiBaseName); 0436 } 0437 0438 void Plugin::clearActions() 0439 { 0440 const QList<QAction*> actions = actionCollection()->actions(); 0441 0442 for (QAction* const action : actions) 0443 { 0444 actionCollection()->removeAction(action); 0445 } 0446 } 0447 0448 void Plugin::setupXML() 0449 { 0450 mergeXMLFile(dynamic_cast<KXMLGUIClient*>(interface()->parent())); 0451 } 0452 0453 void Plugin::rebuild() 0454 { 0455 QString file = xmlFile(); 0456 0457 if (!file.isEmpty()) 0458 { 0459 setXMLGUIBuildDocument(QDomDocument()); 0460 setXMLFile(file, false); 0461 } 0462 } 0463 0464 void Plugin::setDefaultCategory(Category cat) 0465 { 0466 d->defaultCategory = cat; 0467 } 0468 0469 Category Plugin::defaultCategory() const 0470 { 0471 return d->defaultCategory; 0472 } 0473 0474 } // namespace KIPI 0475 0476 #include "moc_plugin.cpp"