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"