1 /** ===========================================================
2 * @file
3 *
4 * This file is a part of KDE project
5 *
6 *
7 * @date 2004-02-01
8 * @brief plugin interface
9 *
10 * @author Copyright (C) 2004-2018 by Gilles Caulier
11 * <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
12 * @author Copyright (C) 2012 by Victor Dodon
13 * <a href="mailto:dodonvictor at gmail dot com">dodonvictor at gmail dot com</a>
14 * @author Copyright (C) 2004-2005 by Renchi Raju
15 * <a href="mailto:renchi dot raju at gmail dot com">renchi dot raju at gmail dot com</a>
16 * @author Copyright (C) 2004-2005 by Jesper K. Pedersen
17 * <a href="mailto:blackie at kde dot org">blackie at kde dot org</a>
18 * @author Copyright (C) 2004-2005 by Aurelien Gateau
19 * <a href="mailto:aurelien dot gateau at free dot fr">aurelien dot gateau at free dot fr</a>
20 *
21 * This program is free software; you can redistribute it
22 * and/or modify it under the terms of the GNU General
23 * Public License as published by the Free Software Foundation;
24 * either version 2, or (at your option)
25 * any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * ============================================================ */
33
34 #include "plugin.h"
35
36 // Qt includes
37
38 #include <QApplication>
39 #include <QWidget>
40 #include <QFile>
41 #include <QDir>
42 #include <QAction>
43 #include <QStandardPaths>
44
45 // KF includes
46
47 #include <KActionCollection>
48
49 // Local includes
50
51 #include "libkipi_version.h"
52 #include "libkipi_debug.h"
53 #include "interface.h"
54 #include "pluginloader.h"
55
56 namespace KIPI
57 {
58
59 class Q_DECL_HIDDEN Plugin::Private
60 {
61 public:
62
Private()63 Private() :
64 uiBaseName(QString())
65 {
66 defaultWidget = nullptr;
67 defaultCategory = InvalidCategory;
68 }
69
70 ActionCategoryMap actionsCat;
71 QWidget* defaultWidget;
72 QString uiBaseName;
73 Category defaultCategory;
74
75 public:
76
77 class XMLParser
78 {
79
80 public:
81
82 static QDomElement makeElement(QDomDocument& domDoc, const QDomElement& from);
83 static void buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths);
84 static int findByNameAttr(const QDomNodeList& list, const QDomElement& node);
85 static void removeDisabledActions(QDomElement& elem);
86
87 private:
88
89 XMLParser();
90 static void buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths, QDomElemList& stack);
91 };
92 };
93
makeElement(QDomDocument & domDoc,const QDomElement & from)94 QDomElement Plugin::Private::XMLParser::makeElement(QDomDocument& domDoc, const QDomElement& from)
95 {
96 if (domDoc.isNull() || from.isNull())
97 return QDomElement();
98
99 QDomElement elem = domDoc.createElement(from.tagName());
100 QDomNamedNodeMap attributes = from.attributes();
101
102 for (int i = 0; i < attributes.size(); ++i)
103 {
104 QDomAttr attr = attributes.item(i).toAttr();
105
106 if (attr.name() != QString::fromLatin1("alreadyVisited"))
107 elem.setAttributeNode(attr);
108 }
109
110 return elem;
111 }
112
buildPaths(const QDomElement & original,const QDomNodeList & localNodes,QHashPath & paths)113 void Plugin::Private::XMLParser::buildPaths(const QDomElement& original, const QDomNodeList& localNodes, QHashPath& paths)
114 {
115 /*
116 * For each child element of "local", we will construct the path from the
117 * "original" element to first appearance of the respective child in the
118 * subtree.
119 */
120 QDomElemList stack;
121 buildPaths(original, localNodes, paths, stack);
122 }
123
findByNameAttr(const QDomNodeList & list,const QDomElement & node)124 int Plugin::Private::XMLParser::findByNameAttr(const QDomNodeList& list, const QDomElement& node)
125 {
126 const QString nodeName = node.toElement().attribute(QString::fromLatin1("name"));
127 const QString nodeTag = node.toElement().tagName();
128
129 for (int i = 0; i < list.size(); ++i)
130 {
131 QDomElement e = list.at(i).toElement();
132
133 if (e.tagName() == nodeTag && e.attribute(QString::fromLatin1("name")) == nodeName)
134 return i;
135 }
136
137 return -1;
138 }
139
removeDisabledActions(QDomElement & elem)140 void Plugin::Private::XMLParser::removeDisabledActions(QDomElement& elem)
141 {
142 QDomNodeList actionList = elem.elementsByTagName(QString::fromLatin1("Action"));
143 QStringList disabledActions = PluginLoader::instance()->disabledPluginActions();
144 QDomElemList disabledElements;
145
146 for(int i = 0; i < actionList.size(); ++i)
147 {
148 QDomElement el = actionList.item(i).toElement();
149
150 if (el.isNull())
151 continue;
152
153 if (disabledActions.contains(el.attribute(QString::fromLatin1("name"))))
154 {
155 disabledElements << el;
156 }
157 }
158
159 foreach(QDomElement element, disabledElements)
160 {
161 //qCDebug(LIBKIPI_LOG) << "Plugin action '" << element.attribute("name") << "' is disabled.";
162 QDomElement parent = element.parentNode().toElement();
163 parent.removeChild(element);
164 }
165 }
166
buildPaths(const QDomElement & original,const QDomNodeList & localNodes,QHashPath & paths,QDomElemList & stack)167 void Plugin::Private::XMLParser::buildPaths(const QDomElement& original, const QDomNodeList& localNodes,
168 QHashPath& paths, QDomElemList& stack)
169 {
170 stack.push_back(original.cloneNode(true).toElement());
171
172 int idx;
173
174 if ((idx = findByNameAttr(localNodes, original)) != -1)
175 {
176 paths[localNodes.item(idx).toElement().attribute(QString::fromLatin1("name"))] = stack;
177 }
178
179 if (!original.hasChildNodes())
180 {
181 stack.pop_back();
182 return;
183 }
184
185 for (QDomNode n = original.firstChild(); !n.isNull(); n = n.nextSibling())
186 {
187 QDomElement e = n.toElement();
188
189 if (e.tagName() == QString::fromLatin1("Menu") && e.hasChildNodes())
190 {
191 buildPaths(e, localNodes, paths, stack);
192 }
193 }
194
195 stack.pop_back();
196 }
197
198 // --------------------------------------------------------------------------------------------------------------
199
Plugin(QObject * const parent,const char * name)200 Plugin::Plugin(QObject* const parent, const char* name)
201 : QObject(parent), d(new Private)
202 {
203 setObjectName(QString::fromLatin1(name));
204 }
205
~Plugin()206 Plugin::~Plugin()
207 {
208 clearActions();
209 delete d;
210 }
211
actions(QWidget * const widget) const212 QList<QAction *> Plugin::actions(QWidget* const widget) const
213 {
214 QWidget* const w = !widget ? d->defaultWidget : widget;
215
216 if (!d->actionsCat.contains(w))
217 {
218 qCWarning(LIBKIPI_LOG) << "Error in plugin. It needs to call Plugin::setup(QWidget*) "
219 << "as the very first line when overriding the setup method.";
220 }
221
222 return d->actionsCat[w].keys();
223 }
224
addAction(const QString & name,QAction * const action)225 void Plugin::addAction(const QString& name, QAction* const action)
226 {
227 if (!action || name.isEmpty())
228 return;
229
230 if (!PluginLoader::instance()->disabledPluginActions().contains(name))
231 {
232 actionCollection()->addAction(name, action);
233 addAction(action);
234 }
235 else
236 {
237 //qCDebug(LIBKIPI_LOG) << "Action '" << name << "' is disabled.";
238 }
239 }
240
addAction(QAction * const action)241 void Plugin::addAction(QAction* const action)
242 {
243 addAction(action, d->defaultCategory);
244 }
245
addAction(const QString & name,QAction * const action,Category cat)246 void Plugin::addAction(const QString& name, QAction* const action, Category cat)
247 {
248 if (!action || name.isEmpty())
249 return;
250
251 if (!PluginLoader::instance()->disabledPluginActions().contains(name))
252 {
253 actionCollection()->addAction(name, action);
254 addAction(action, cat);
255 }
256 else
257 {
258 //qCDebug(LIBKIPI_LOG) << "Action '" << name << "' is disabled.";
259 }
260 }
261
addAction(QAction * const action,Category cat)262 void Plugin::addAction(QAction* const action, Category cat)
263 {
264 if (cat == InvalidCategory)
265 {
266 qCWarning(LIBKIPI_LOG) << "Error in plugin. Action '" << action->objectName() << "has "
267 "invalid category. You must set default plugin category or "
268 "to use a valid Category";
269 }
270
271 d->actionsCat[d->defaultWidget].insert(action, cat);
272 }
273
setup(QWidget * const widget)274 void Plugin::setup(QWidget* const widget)
275 {
276 clearActions();
277 d->defaultWidget = widget;
278 d->actionsCat.insert(widget, QMap<QAction*, Category>());
279 }
280
category(QAction * const action) const281 Category Plugin::category(QAction* const action) const
282 {
283 QMap<QAction *, Category>::const_iterator it = d->actionsCat[d->defaultWidget].constFind(action);
284
285 if (it != d->actionsCat[d->defaultWidget].constEnd())
286 {
287 return it.value();
288 }
289 else
290 {
291 if (d->defaultCategory == InvalidCategory)
292 {
293 qCWarning(LIBKIPI_LOG) << "Error in plugin. Invalid category. "
294 "You must set default plugin category.";
295 }
296
297 return d->defaultCategory;
298 }
299 }
300
interface() const301 Interface* Plugin::interface() const
302 {
303 return (dynamic_cast<Interface*>(parent()));
304 }
305
setUiBaseName(const char * name)306 void Plugin::setUiBaseName(const char* name)
307 {
308 if (name && *name)
309 d->uiBaseName = QString::fromLatin1(name);
310 }
311
uiBaseName() const312 QString Plugin::uiBaseName() const
313 {
314 return d->uiBaseName;
315 }
316
mergeXMLFile(KXMLGUIClient * const host)317 void Plugin::mergeXMLFile(KXMLGUIClient *const host)
318 {
319 if (!host)
320 {
321 qCCritical(LIBKIPI_LOG) << "Host KXMLGUIClient is null!";
322 return;
323 }
324
325 if (d->uiBaseName.isEmpty())
326 {
327 qCCritical(LIBKIPI_LOG) << "UI file basename is not set! You must first call setUiBaseName.";
328 return;
329 }
330
331 const QString componentName = QApplication::applicationName();
332 const QString defaultUI = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("kxmlgui5/kipi/") + d->uiBaseName);
333 const QString localUIdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString::fromLatin1("/kxmlgui5/") +
334 componentName;
335 const QString localUI = localUIdir + QString::fromLatin1("/") + d->uiBaseName;
336
337 qCDebug(LIBKIPI_LOG) << "UI file :" << defaultUI;
338
339 QFile defaultUIFile(defaultUI);
340 QDomDocument defaultDomDoc;
341
342 if (!defaultUIFile.open(QFile::ReadOnly) || !defaultDomDoc.setContent(&defaultUIFile))
343 {
344 qCCritical(LIBKIPI_LOG) << "Could not open default ui file " << defaultUI << " for ui basename " << d->uiBaseName;
345 return;
346 }
347
348 defaultUIFile.close();
349 const QDomDocument hostDoc = host->domDocument();
350
351 if (hostDoc.isNull() || defaultDomDoc.isNull())
352 {
353 qCCritical(LIBKIPI_LOG) << "Cannot merge the XML files, at least one is null!";
354 return;
355 }
356
357 QDomElement hostGuiElem = hostDoc.firstChildElement(QString::fromLatin1("kpartgui"));
358 QDomElement hostMenuBarElem = hostGuiElem.firstChildElement(QString::fromLatin1("MenuBar"));
359
360 QDomDocument newPluginDoc(defaultDomDoc.doctype());
361 QDomElement defGuiElem = defaultDomDoc.firstChildElement(QString::fromLatin1("gui"));
362
363 Private::XMLParser::removeDisabledActions(defGuiElem);
364
365 QDomElement newGuiElem = Private::XMLParser::makeElement(newPluginDoc, defGuiElem);
366 QDomElement defMenuBarElem = defGuiElem.firstChildElement(QString::fromLatin1("MenuBar"));
367 QDomElement newMenuBarElem = Private::XMLParser::makeElement(newPluginDoc, defMenuBarElem);
368 QDomElement defToolBarElem = defGuiElem.firstChildElement(QString::fromLatin1("ToolBar"));
369 QDomElement defActionPropElem = defGuiElem.firstChildElement(QString::fromLatin1("ActionProperties"));
370
371 QHashPath paths;
372 Private::XMLParser::buildPaths(hostMenuBarElem, defMenuBarElem.childNodes(), paths);
373
374 for (QDomNode n = defMenuBarElem.firstChild(); !n.isNull(); n = n.nextSibling())
375 {
376 QDomElemList path = paths[n.toElement().attribute(QString::fromLatin1("name"))];
377 QDomElement current = newMenuBarElem;
378 QDomElement origCurr = defMenuBarElem;
379
380 if (path.empty())
381 {
382 newMenuBarElem.appendChild(n.cloneNode());
383 }
384 else
385 {
386 for (int i = 1; i < path.size() - 1; ++i)
387 {
388 int idx = Private::XMLParser::findByNameAttr(current.childNodes(), path[i]);
389 origCurr = path[i];
390
391 if (idx == -1)
392 {
393 if (!path[i].isNull())
394 {
395 QDomElement newChild = Private::XMLParser::makeElement(newPluginDoc, path[i]);
396 QDomElement textElem = origCurr.firstChildElement(QString::fromLatin1("text"));
397
398 if (!textElem.isNull())
399 {
400 newChild.appendChild(textElem.cloneNode());
401 }
402
403 current.appendChild(newChild);
404 current = newChild;
405 }
406 }
407 else
408 {
409 current = current.childNodes().item(idx).toElement();
410 }
411 }
412 }
413
414 if (!current.isNull())
415 current.appendChild(n.cloneNode());
416 }
417
418 newGuiElem.appendChild(newMenuBarElem);
419 QFile localUIFile(localUI);
420 QDomDocument localDomDoc;
421 // be safe rather than sorry
422 // create the appname folder in kxmlgui5
423 QDir localUIDir(localUIdir);
424 if (!localUIDir.exists())
425 QDir().mkpath(localUIdir);
426
427 if (!localUIFile.exists() || !localUIFile.open(QFile::ReadOnly) || !localDomDoc.setContent(&localUIFile))
428 {
429 newGuiElem.appendChild(defToolBarElem.cloneNode());
430 newGuiElem.appendChild(defActionPropElem.cloneNode());
431 }
432 else
433 {
434 QDomElement localGuiElem = localDomDoc.firstChildElement(QString::fromLatin1("gui"));
435
436 Private::XMLParser::removeDisabledActions(localGuiElem);
437
438 QDomElement localToolBarElem = localGuiElem.firstChildElement(QString::fromLatin1("ToolBar"));
439 QDomElement localActionPropElem = localGuiElem.firstChildElement(QString::fromLatin1("ActionProperties"));
440
441 newGuiElem.appendChild(localToolBarElem.cloneNode());
442 newGuiElem.appendChild(localActionPropElem.cloneNode());
443 }
444
445 localUIFile.close();
446 QFile writeFile(localUI);
447
448 if (!writeFile.open(QFile::WriteOnly | QFile::Truncate))
449 {
450 qCCritical(LIBKIPI_LOG) << "Could not open " << localUI << " for writing!";
451 return;
452 }
453
454 newPluginDoc.appendChild(newGuiElem);
455
456 writeFile.write(newPluginDoc.toString().toUtf8());
457 writeFile.close();
458
459 setXMLFile(d->uiBaseName);
460 }
461
clearActions()462 void Plugin::clearActions()
463 {
464 QList<QAction*> actions = actionCollection()->actions();
465
466 foreach (QAction* const action, actions)
467 {
468 actionCollection()->removeAction(action);
469 }
470 }
471
setupXML()472 void Plugin::setupXML()
473 {
474 mergeXMLFile(dynamic_cast<KXMLGUIClient*>(interface()->parent()));
475 }
476
rebuild()477 void Plugin::rebuild()
478 {
479 QString file = xmlFile();
480
481 if (!file.isEmpty())
482 {
483 setXMLGUIBuildDocument(QDomDocument());
484 setXMLFile(file, false);
485 }
486 }
487
setDefaultCategory(Category cat)488 void Plugin::setDefaultCategory(Category cat)
489 {
490 d->defaultCategory = cat;
491 }
492
defaultCategory() const493 Category Plugin::defaultCategory() const
494 {
495 return d->defaultCategory;
496 }
497
498 } // namespace KIPI
499