1 /* This file is part of the KDE libraries
2    Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
3    Copyright (C) 2000 Kurt Granroth <granroth@kde.org>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License version 2 as published by the Free Software Foundation.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kxmlguiclient.h"
21 
22 #include "kxmlguiversionhandler_p.h"
23 #include "kxmlguifactory.h"
24 #include "kxmlguibuilder.h"
25 #include "kactioncollection.h"
26 
27 #include <QAction>
28 #include <QDir>
29 #include <QFile>
30 #include <QDomDocument>
31 #include <QTextStream>
32 #include <QRegExp>
33 #include <QPointer>
34 #include <QCoreApplication>
35 #include <QStandardPaths>
36 #include <QDebug>
37 
38 #include <klocalizedstring.h>
39 
40 #include <assert.h>
41 
42 #if defined(KCONFIG_BEFORE_5_24)
43 # define authorizeAction authorizeKAction
44 #endif
45 
46 class KXMLGUIClientPrivate
47 {
48 public:
KXMLGUIClientPrivate()49     KXMLGUIClientPrivate()
50         : m_componentName(QCoreApplication::applicationName()),
51           m_actionCollection(0),
52           m_parent(0L),
53           m_builder(0L)
54     {
55         m_textTagNames.append(QLatin1String("text"));
56         m_textTagNames.append(QLatin1String("Text"));
57         m_textTagNames.append(QLatin1String("title"));
58     }
~KXMLGUIClientPrivate()59     ~KXMLGUIClientPrivate()
60     {
61     }
62 
63     bool mergeXML(QDomElement &base, QDomElement &additive,
64                   KActionCollection *actionCollection);
65     bool isEmptyContainer(const QDomElement &base,
66                           KActionCollection *actionCollection) const;
67 
68     QDomElement findMatchingElement(const QDomElement &base,
69                                     const QDomElement &additive);
70 
71     QString m_componentName;
72 
73     QDomDocument m_doc;
74     KActionCollection *m_actionCollection;
75     QDomDocument m_buildDocument;
76     QPointer<KXMLGUIFactory> m_factory;
77     KXMLGUIClient *m_parent;
78     //QPtrList<KXMLGUIClient> m_supers;
79     QList<KXMLGUIClient *> m_children;
80     KXMLGUIBuilder *m_builder;
81     QString m_xmlFile;
82     QString m_localXMLFile;
83     QStringList m_textTagNames;
84 
85     // Actions to enable/disable on a state change
86     QMap<QString, KXMLGUIClient::StateChange> m_actionsStateMap;
87 };
88 
KXMLGUIClient()89 KXMLGUIClient::KXMLGUIClient()
90     : d(new KXMLGUIClientPrivate)
91 {
92 }
93 
KXMLGUIClient(KXMLGUIClient * parent)94 KXMLGUIClient::KXMLGUIClient(KXMLGUIClient *parent)
95     : d(new KXMLGUIClientPrivate)
96 {
97     parent->insertChildClient(this);
98 }
99 
~KXMLGUIClient()100 KXMLGUIClient::~KXMLGUIClient()
101 {
102     if (d->m_parent) {
103         d->m_parent->removeChildClient(this);
104     }
105 
106     if (d->m_factory) {
107         qWarning() << this << "deleted without having been removed from the factory first. This will leak standalone popupmenus and could lead to crashes.";
108         d->m_factory->forgetClient(this);
109     }
110 
111     Q_FOREACH (KXMLGUIClient *client, d->m_children) {
112         if (d->m_factory) {
113             d->m_factory->forgetClient(client);
114         }
115         assert(client->d->m_parent == this);
116         client->d->m_parent = 0;
117     }
118 
119     delete d->m_actionCollection;
120     delete d;
121 }
122 
action(const char * name) const123 QAction *KXMLGUIClient::action(const char *name) const
124 {
125     QAction *act = actionCollection()->action(QLatin1String(name));
126     if (!act) {
127         Q_FOREACH (KXMLGUIClient *client, d->m_children) {
128             act = client->actionCollection()->action(QLatin1String(name));
129             if (act) {
130                 break;
131             }
132         }
133     }
134     return act;
135 }
136 
actionCollection() const137 KActionCollection *KXMLGUIClient::actionCollection() const
138 {
139     if (!d->m_actionCollection) {
140         d->m_actionCollection = new KActionCollection(this);
141         d->m_actionCollection->setObjectName(QStringLiteral("KXMLGUIClient-KActionCollection"));
142     }
143     return d->m_actionCollection;
144 }
145 
action(const QDomElement & element) const146 QAction *KXMLGUIClient::action(const QDomElement &element) const
147 {
148     return actionCollection()->action(element.attribute(QStringLiteral("name")));
149 }
150 
componentName() const151 QString KXMLGUIClient::componentName() const
152 {
153     return d->m_componentName;
154 }
155 
domDocument() const156 QDomDocument KXMLGUIClient::domDocument() const
157 {
158     return d->m_doc;
159 }
160 
xmlFile() const161 QString KXMLGUIClient::xmlFile() const
162 {
163     return d->m_xmlFile;
164 }
165 
localXMLFile() const166 QString KXMLGUIClient::localXMLFile() const
167 {
168     if (!d->m_localXMLFile.isEmpty()) {
169         return d->m_localXMLFile;
170     }
171 
172     if (!QDir::isRelativePath(d->m_xmlFile)) {
173         return QString();    // can't save anything here
174     }
175 
176     if (d->m_xmlFile.isEmpty()) { // setXMLFile not called at all, can't save. Use case: ToolBarHandler
177         return QString();
178     }
179 
180     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/kxmlgui5/") +
181            componentName() + QLatin1Char('/') + d->m_xmlFile;
182 }
183 
reloadXML()184 void KXMLGUIClient::reloadXML()
185 {
186     // TODO: this method can't be used for the KXmlGuiWindow, since it doesn't merge in ui_standards.xmlgui!
187     //   -> KDE5: load ui_standards_rc in setXMLFile using a flag, and remember that flag?
188     //            and then KEditToolBar can use reloadXML.
189     QString file(xmlFile());
190     if (!file.isEmpty()) {
191         setXMLFile(file);
192     }
193 }
194 
setComponentName(const QString & componentName,const QString & componentDisplayName)195 void KXMLGUIClient::setComponentName(const QString &componentName, const QString &componentDisplayName)
196 {
197     d->m_componentName = componentName;
198     actionCollection()->setComponentName(componentName);
199     actionCollection()->setComponentDisplayName(componentDisplayName);
200     if (d->m_builder) {
201         d->m_builder->setBuilderClient(this);
202     }
203 }
204 
standardsXmlFileLocation()205 QString KXMLGUIClient::standardsXmlFileLocation()
206 {
207     QString file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("ui/ui_standards.xmlgui"));
208     if (file.isEmpty()) {
209         // fallback to resource, to allow to use the rc file compiled into this framework, must exist!
210         file = QStringLiteral(":/kxmlgui5/ui_standards.xmlgui");
211         Q_ASSERT(QFile::exists(file));
212     }
213     return file;
214 }
215 
loadStandardsXmlFile()216 void KXMLGUIClient::loadStandardsXmlFile()
217 {
218     setXML(KXMLGUIFactory::readConfigFile(standardsXmlFileLocation()));
219 }
220 
setXMLFile(const QString & _file,bool merge,bool setXMLDoc)221 void KXMLGUIClient::setXMLFile(const QString &_file, bool merge, bool setXMLDoc)
222 {
223     // store our xml file name
224     if (!_file.isNull()) {
225         d->m_xmlFile = _file;
226     }
227 
228     if (!setXMLDoc) {
229         return;
230     }
231 
232     QString file = _file;
233     QStringList allFiles;
234     if (!QDir::isRelativePath(file)) {
235         allFiles.append(file);
236     } else {
237         const QString filter = componentName() + QLatin1Char('/') + _file;
238 
239         // files on filesystem
240         allFiles << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("kxmlgui5/") + filter); // KF >= 5.1
241 
242         // KF >= 5.4 (resource file)
243         const QString qrcFile(QStringLiteral(":/kxmlgui5/") + filter);
244         if (QFile::exists(qrcFile)) {
245             allFiles << qrcFile;
246         }
247 
248         // then compat locations
249         const QStringList compatFiles =
250                    QStandardPaths::locateAll(QStandardPaths::AppDataLocation, filter) + // kdelibs4, KF 5.0
251                    QStandardPaths::locateAll(QStandardPaths::AppDataLocation, _file); // kdelibs4, KF 5.0, caller passes component name
252 
253         if (allFiles.isEmpty() && !compatFiles.isEmpty()) {
254             qWarning() << "kxmlguiclient: KXMLGUI file found at deprecated location" << compatFiles << "-- please use ${KXMLGUI_INSTALL_DIR} to install this file instead.";
255         }
256         allFiles += compatFiles;
257     }
258     if (allFiles.isEmpty() && !_file.isEmpty()) {
259         // if a non-empty file gets passed and we can't find it,
260         // inform the developer using some debug output
261         qWarning() << "cannot find .xmlgui file" << _file << "for component" << componentName();
262     }
263 
264     // make sure to merge the settings from any file specified by setLocalXMLFile()
265     if (!d->m_localXMLFile.isEmpty() && !file.endsWith(QStringLiteral("ui_standards.xmlgui"))) {
266         const bool exists = QDir::isRelativePath(d->m_localXMLFile) || QFile::exists(d->m_localXMLFile);
267         if (exists && !allFiles.contains(d->m_localXMLFile)) {
268             allFiles.prepend(d->m_localXMLFile);
269         }
270     }
271 
272     QString doc;
273     if (!allFiles.isEmpty()) {
274         file = findMostRecentXMLFile(allFiles, doc);
275     }
276 
277     // Always call setXML, even on error, so that we don't keep all ui_standards.xmlgui menus.
278     setXML(doc, merge);
279 }
280 
setLocalXMLFile(const QString & file)281 void KXMLGUIClient::setLocalXMLFile(const QString &file)
282 {
283     d->m_localXMLFile = file;
284 }
285 
replaceXMLFile(const QString & xmlfile,const QString & localxmlfile,bool merge)286 void KXMLGUIClient::replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge)
287 {
288     if (!QDir::isAbsolutePath(xmlfile)) {
289         qWarning() << "xml file" << xmlfile << "is not an absolute path";
290     }
291 
292     setLocalXMLFile(localxmlfile);
293     setXMLFile(xmlfile, merge);
294 }
295 
296 // The top document element may have translation domain attribute set,
297 // or the translation domain may be implicitly the application domain.
298 // This domain must be used to fetch translations for all text elements
299 // in the document that do not have their own domain attribute.
300 // In order to preserve this semantics through document mergings,
301 // the top or application domain must be propagated to all text elements
302 // lacking their own domain attribute.
propagateTranslationDomain(QDomDocument & doc,const QStringList tagNames)303 static void propagateTranslationDomain(QDomDocument &doc, const QStringList tagNames)
304 {
305     const QLatin1String attrDomain("translationDomain");
306     QDomElement base = doc.documentElement();
307     QString domain = base.attribute(attrDomain);
308     if (domain.isEmpty()) {
309         domain = QString::fromUtf8(KLocalizedString::applicationDomain());
310         if (domain.isEmpty()) {
311             return;
312         }
313     }
314     foreach (const QString &tagName, tagNames) {
315         QDomNodeList textNodes = base.elementsByTagName(tagName);
316         for (int i = 0; i < textNodes.length(); ++i) {
317             QDomElement e = textNodes.item(i).toElement();
318             QString localDomain = e.attribute(attrDomain);
319             if (localDomain.isEmpty()) {
320                  e.setAttribute(attrDomain, domain);
321             }
322         }
323     }
324 }
325 
setXML(const QString & document,bool merge)326 void KXMLGUIClient::setXML(const QString &document, bool merge)
327 {
328     QDomDocument doc;
329     QString errorMsg;
330     int errorLine, errorColumn;
331     // QDomDocument raises a parse error on empty document, but we accept no app-specific document,
332     // in which case you only get ui_standards.xmlgui layout.
333     bool result = document.isEmpty() || doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
334     if (result) {
335         propagateTranslationDomain(doc, d->m_textTagNames);
336         setDOMDocument(doc, merge);
337     } else {
338 #ifdef NDEBUG
339         qCritical() << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
340         setDOMDocument(QDomDocument(), merge); // otherwise empty menus from ui_standards.xmlgui stay around
341 #else
342         qCritical() << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
343         abort();
344 #endif
345     }
346 }
347 
setDOMDocument(const QDomDocument & document,bool merge)348 void KXMLGUIClient::setDOMDocument(const QDomDocument &document, bool merge)
349 {
350     if (merge && !d->m_doc.isNull()) {
351         QDomElement base = d->m_doc.documentElement();
352 
353         QDomElement e = document.documentElement();
354 
355         // merge our original (global) xml with our new one
356         d->mergeXML(base, e, actionCollection());
357 
358         // reassign our pointer as mergeXML might have done something
359         // strange to it
360         base = d->m_doc.documentElement();
361 
362         //qDebug(260) << "Result of xmlgui merging:" << d->m_doc.toString();
363 
364         // we want some sort of failsafe.. just in case
365         if (base.isNull()) {
366             d->m_doc = document;
367         }
368     } else {
369         d->m_doc = document;
370     }
371 
372     setXMLGUIBuildDocument(QDomDocument());
373 }
374 
375 // if (equals(a,b)) is more readable than if (a.compare(b, Qt::CaseInsensitive)==0)
equalstr(const QString & a,const QString & b)376 static inline bool equalstr(const QString &a, const QString &b)
377 {
378     return a.compare(b, Qt::CaseInsensitive) == 0;
379 }
equalstr(const QString & a,const QLatin1String & b)380 static inline bool equalstr(const QString &a, const QLatin1String &b)
381 {
382     return a.compare(b, Qt::CaseInsensitive) == 0;
383 }
384 
mergeXML(QDomElement & base,QDomElement & additive,KActionCollection * actionCollection)385 bool KXMLGUIClientPrivate::mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection)
386 {
387     const QLatin1String tagAction("Action");
388     const QLatin1String tagMerge("Merge");
389     const QLatin1String tagSeparator("Separator");
390     const QLatin1String tagMergeLocal("MergeLocal");
391     const QLatin1String tagText("text");
392     const QLatin1String attrAppend("append");
393     const QString       attrName(QStringLiteral("name"));
394     const QString       attrWeakSeparator(QStringLiteral("weakSeparator"));
395     const QString       attrAlreadyVisited(QStringLiteral("alreadyVisited"));
396     const QString       attrNoMerge(QStringLiteral("noMerge"));
397     const QLatin1String attrOne("1");
398 
399     // there is a possibility that we don't want to merge in the
400     // additive.. rather, we might want to *replace* the base with the
401     // additive.  this can be for any container.. either at a file wide
402     // level or a simple container level.  we look for the 'noMerge'
403     // tag, in any event and just replace the old with the new
404     if (additive.attribute(attrNoMerge) == attrOne) { // ### use toInt() instead? (Simon)
405         base.parentNode().replaceChild(additive, base);
406         return true;
407     } else {
408         // Merge attributes
409         {
410             const QDomNamedNodeMap attribs = additive.attributes();
411             const uint attribcount = attribs.count();
412 
413             for (uint i = 0; i < attribcount; ++i) {
414                 const QDomNode node = attribs.item(i);
415                 base.setAttribute(node.nodeName(), node.nodeValue());
416             }
417         }
418 
419         // iterate over all elements in the container (of the global DOM tree)
420         QDomNode n = base.firstChild();
421         while (!n.isNull()) {
422             QDomElement e = n.toElement();
423             n = n.nextSibling(); // Advance now so that we can safely delete e
424             if (e.isNull()) {
425                 continue;
426             }
427 
428             const QString tag = e.tagName();
429 
430             // if there's an action tag in the global tree and the action is
431             // not implemented, then we remove the element
432             if (equalstr(tag, tagAction)) {
433                 const QString name =  e.attribute(attrName);
434                 if (!actionCollection->action(name)) {
435                     // remove this child as we aren't using it
436                     base.removeChild(e);
437                     continue;
438                 }
439             }
440 
441             // if there's a separator defined in the global tree, then add an
442             // attribute, specifying that this is a "weak" separator
443             else if (equalstr(tag, tagSeparator)) {
444                 e.setAttribute(attrWeakSeparator, (uint)1);
445 
446                 // okay, hack time. if the last item was a weak separator OR
447                 // this is the first item in a container, then we nuke the
448                 // current one
449                 QDomElement prev = e.previousSibling().toElement();
450                 if (prev.isNull() ||
451                         (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull()) ||
452                         (equalstr(prev.tagName(), tagText))) {
453                     // the previous element was a weak separator or didn't exist
454                     base.removeChild(e);
455                     continue;
456                 }
457             }
458 
459             // the MergeLocal tag lets us specify where non-standard elements
460             // of the local tree shall be merged in.  After inserting the
461             // elements we delete this element
462             else if (equalstr(tag, tagMergeLocal)) {
463                 QDomNode it = additive.firstChild();
464                 while (!it.isNull()) {
465                     QDomElement newChild = it.toElement();
466                     it = it.nextSibling();
467                     if (newChild.isNull()) {
468                         continue;
469                     }
470 
471                     if (equalstr(newChild.tagName(), tagText)) {
472                         continue;
473                     }
474 
475                     if (newChild.attribute(attrAlreadyVisited) == attrOne) {
476                         continue;
477                     }
478 
479                     QString itAppend(newChild.attribute(attrAppend));
480                     QString elemName(e.attribute(attrName));
481 
482                     if ((itAppend.isNull() && elemName.isEmpty()) ||
483                             (itAppend == elemName)) {
484                         // first, see if this new element matches a standard one in
485                         // the global file.  if it does, then we skip it as it will
486                         // be merged in, later
487                         QDomElement matchingElement = findMatchingElement(newChild, base);
488                         if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator)) {
489                             base.insertBefore(newChild, e);
490                         }
491                     }
492                 }
493 
494                 base.removeChild(e);
495                 continue;
496             }
497 
498             else if (equalstr(tag, tagText)) {
499                 continue;
500             } else if (equalstr(tag, tagMerge)) {
501                 continue;
502             }
503 
504             // in this last case we check for a separator tag and, if not, we
505             // can be sure that it is a container --> proceed with child nodes
506             // recursively and delete the just proceeded container item in
507             // case it is empty (if the recursive call returns true)
508             else {
509                 QDomElement matchingElement = findMatchingElement(e, additive);
510                 if (!matchingElement.isNull()) {
511                     matchingElement.setAttribute(attrAlreadyVisited, (uint)1);
512 
513                     if (mergeXML(e, matchingElement, actionCollection)) {
514                         base.removeChild(e);
515                         additive.removeChild(matchingElement); // make sure we don't append it below
516                         continue;
517                     }
518 
519                     continue;
520                 } else {
521                     // this is an important case here! We reach this point if the
522                     // "local" tree does not contain a container definition for
523                     // this container. However we have to call mergeXML recursively
524                     // and make it check if there are actions implemented for this
525                     // container. *If* none, then we can remove this container now
526                     QDomElement dummy;
527                     if (mergeXML(e, dummy, actionCollection)) {
528                         base.removeChild(e);
529                     }
530                     continue;
531                 }
532             }
533         }
534 
535         //here we append all child elements which were not inserted
536         //previously via the LocalMerge tag
537         n = additive.firstChild();
538         while (!n.isNull()) {
539             QDomElement e = n.toElement();
540             n = n.nextSibling(); // Advance now so that we can safely delete e
541             if (e.isNull()) {
542                 continue;
543             }
544 
545             QDomElement matchingElement = findMatchingElement(e, base);
546 
547             if (matchingElement.isNull()) {
548                 base.appendChild(e);
549             }
550         }
551 
552         // do one quick check to make sure that the last element was not
553         // a weak separator
554         QDomElement last = base.lastChild().toElement();
555         if (equalstr(last.tagName(), tagSeparator) &&
556                 (!last.attribute(attrWeakSeparator).isNull())) {
557             base.removeChild(last);
558         }
559     }
560 
561     return isEmptyContainer(base, actionCollection);
562 }
563 
isEmptyContainer(const QDomElement & base,KActionCollection * actionCollection) const564 bool KXMLGUIClientPrivate::isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const
565 {
566     // now we check if we are empty (in which case we return "true", to
567     // indicate the caller that it can delete "us" (the base element
568     // argument of "this" call)
569     QDomNode n = base.firstChild();
570     while (!n.isNull()) {
571         const QDomElement e = n.toElement();
572         n = n.nextSibling(); // Advance now so that we can safely delete e
573         if (e.isNull()) {
574             continue;
575         }
576 
577         const QString tag = e.tagName();
578 
579         if (equalstr(tag, QLatin1String("Action"))) {
580             // if base contains an implemented action, then we must not get
581             // deleted (note that the actionCollection contains both,
582             // "global" and "local" actions)
583             if (actionCollection->action(e.attribute(QStringLiteral("name")))) {
584                 return false;
585             }
586         } else if (equalstr(tag, QLatin1String("Separator"))) {
587             // if we have a separator which has *not* the weak attribute
588             // set, then it must be owned by the "local" tree in which case
589             // we must not get deleted either
590             const QString weakAttr = e.attribute(QStringLiteral("weakSeparator"));
591             if (weakAttr.isEmpty() || weakAttr.toInt() != 1) {
592                 return false;
593             }
594         }
595 
596         else if (equalstr(tag, QLatin1String("merge"))) {
597             continue;
598         }
599 
600         // a text tag is NOT enough to spare this container
601         else if (equalstr(tag, QLatin1String("text"))) {
602             continue;
603         }
604 
605         // what's left are non-empty containers! *don't* delete us in this
606         // case (at this position we can be *sure* that the container is
607         // *not* empty, as the recursive call for it was in the first loop
608         // which deleted the element in case the call returned "true"
609         else {
610             return false;
611         }
612     }
613 
614     return true; // I'm empty, please delete me.
615 }
616 
findMatchingElement(const QDomElement & base,const QDomElement & additive)617 QDomElement KXMLGUIClientPrivate::findMatchingElement(const QDomElement &base, const QDomElement &additive)
618 {
619     QDomNode n = additive.firstChild();
620     while (!n.isNull()) {
621         QDomElement e = n.toElement();
622         n = n.nextSibling(); // Advance now so that we can safely delete e -- TODO we don't, so simplify this
623         if (e.isNull()) {
624             continue;
625         }
626 
627         const QString tag = e.tagName();
628         // skip all action and merge tags as we will never use them
629         if (equalstr(tag, QLatin1String("Action"))
630                 || equalstr(tag, QLatin1String("MergeLocal"))) {
631             continue;
632         }
633 
634         // now see if our tags are equivalent
635         if (equalstr(tag, base.tagName()) &&
636                 e.attribute(QStringLiteral("name")) == base.attribute(QStringLiteral("name"))) {
637             return e;
638         }
639     }
640 
641     // nope, return a (now) null element
642     return QDomElement();
643 }
644 
setXMLGUIBuildDocument(const QDomDocument & doc)645 void KXMLGUIClient::setXMLGUIBuildDocument(const QDomDocument &doc)
646 {
647     d->m_buildDocument = doc;
648 }
649 
xmlguiBuildDocument() const650 QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
651 {
652     return d->m_buildDocument;
653 }
654 
setFactory(KXMLGUIFactory * factory)655 void KXMLGUIClient::setFactory(KXMLGUIFactory *factory)
656 {
657     d->m_factory = factory;
658 }
659 
factory() const660 KXMLGUIFactory *KXMLGUIClient::factory() const
661 {
662     return d->m_factory;
663 }
664 
parentClient() const665 KXMLGUIClient *KXMLGUIClient::parentClient() const
666 {
667     return d->m_parent;
668 }
669 
insertChildClient(KXMLGUIClient * child)670 void KXMLGUIClient::insertChildClient(KXMLGUIClient *child)
671 {
672     if (child->d->m_parent) {
673         child->d->m_parent->removeChildClient(child);
674     }
675     d->m_children.append(child);
676     child->d->m_parent = this;
677 }
678 
removeChildClient(KXMLGUIClient * child)679 void KXMLGUIClient::removeChildClient(KXMLGUIClient *child)
680 {
681     assert(d->m_children.contains(child));
682     d->m_children.removeAll(child);
683     child->d->m_parent = 0;
684 }
685 
686 /*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super )
687 {
688   if ( d->m_supers.contains( super ) )
689     return false;
690   d->m_supers.append( super );
691   return true;
692 }*/
693 
childClients()694 QList<KXMLGUIClient *> KXMLGUIClient::childClients()
695 {
696     return d->m_children;
697 }
698 
setClientBuilder(KXMLGUIBuilder * builder)699 void KXMLGUIClient::setClientBuilder(KXMLGUIBuilder *builder)
700 {
701     d->m_builder = builder;
702 }
703 
clientBuilder() const704 KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
705 {
706     return d->m_builder;
707 }
708 
plugActionList(const QString & name,const QList<QAction * > & actionList)709 void KXMLGUIClient::plugActionList(const QString &name, const QList<QAction *> &actionList)
710 {
711     if (!d->m_factory) {
712         return;
713     }
714 
715     d->m_factory->plugActionList(this, name, actionList);
716 }
717 
unplugActionList(const QString & name)718 void KXMLGUIClient::unplugActionList(const QString &name)
719 {
720     if (!d->m_factory) {
721         return;
722     }
723 
724     d->m_factory->unplugActionList(this, name);
725 }
726 
findMostRecentXMLFile(const QStringList & files,QString & doc)727 QString KXMLGUIClient::findMostRecentXMLFile(const QStringList &files, QString &doc)
728 {
729     KXmlGuiVersionHandler versionHandler(files);
730     doc = versionHandler.finalDocument();
731     return versionHandler.finalFile();
732 }
733 
addStateActionEnabled(const QString & state,const QString & action)734 void KXMLGUIClient::addStateActionEnabled(const QString &state,
735         const QString &action)
736 {
737     StateChange stateChange = getActionsToChangeForState(state);
738 
739     stateChange.actionsToEnable.append(action);
740     //qDebug(260) << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")";
741 
742     d->m_actionsStateMap.insert(state, stateChange);
743 }
744 
addStateActionDisabled(const QString & state,const QString & action)745 void KXMLGUIClient::addStateActionDisabled(const QString &state,
746         const QString &action)
747 {
748     StateChange stateChange = getActionsToChangeForState(state);
749 
750     stateChange.actionsToDisable.append(action);
751     //qDebug(260) << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")";
752 
753     d->m_actionsStateMap.insert(state, stateChange);
754 }
755 
getActionsToChangeForState(const QString & state)756 KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString &state)
757 {
758     return d->m_actionsStateMap[state];
759 }
760 
stateChanged(const QString & newstate,KXMLGUIClient::ReverseStateChange reverse)761 void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
762 {
763     StateChange stateChange = getActionsToChangeForState(newstate);
764 
765     bool setTrue = (reverse == StateNoReverse);
766     bool setFalse = !setTrue;
767 
768     // Enable actions which need to be enabled...
769     //
770     for (QStringList::const_iterator it = stateChange.actionsToEnable.constBegin();
771             it != stateChange.actionsToEnable.constEnd(); ++it) {
772 
773         QAction *action = actionCollection()->action(*it);
774         if (action) {
775             action->setEnabled(setTrue);
776         }
777     }
778 
779     // and disable actions which need to be disabled...
780     //
781     for (QStringList::const_iterator it = stateChange.actionsToDisable.constBegin();
782             it != stateChange.actionsToDisable.constEnd(); ++it) {
783 
784         QAction *action = actionCollection()->action(*it);
785         if (action) {
786             action->setEnabled(setFalse);
787         }
788     }
789 
790 }
791 
beginXMLPlug(QWidget * w)792 void KXMLGUIClient::beginXMLPlug(QWidget *w)
793 {
794     actionCollection()->addAssociatedWidget(w);
795     foreach (KXMLGUIClient *client, d->m_children) {
796         client->beginXMLPlug(w);
797     }
798 }
799 
endXMLPlug()800 void KXMLGUIClient::endXMLPlug()
801 {
802 }
803 
prepareXMLUnplug(QWidget * w)804 void KXMLGUIClient::prepareXMLUnplug(QWidget *w)
805 {
806     actionCollection()->removeAssociatedWidget(w);
807     foreach (KXMLGUIClient *client, d->m_children) {
808         client->prepareXMLUnplug(w);
809     }
810 }
811 
virtual_hook(int,void *)812 void KXMLGUIClient::virtual_hook(int, void *)
813 {
814     /*BASE::virtual_hook( id, data );*/
815 }
816