File indexing completed on 2024-04-14 14:30:05

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
0004     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kxmlguiclient.h"
0010 
0011 #include "debug.h"
0012 #include "kactioncollection.h"
0013 #include "kxmlguibuilder.h"
0014 #include "kxmlguifactory.h"
0015 #include "kxmlguiversionhandler_p.h"
0016 
0017 #include <QAction>
0018 #include <QCoreApplication>
0019 #include <QDir>
0020 #include <QDomDocument>
0021 #include <QFile>
0022 #include <QPointer>
0023 #include <QStandardPaths>
0024 
0025 #include <KAuthorized>
0026 #include <KLocalizedString>
0027 
0028 #include <cassert>
0029 
0030 class KXMLGUIClientPrivate
0031 {
0032 public:
0033     KXMLGUIClientPrivate()
0034         : m_componentName(QCoreApplication::applicationName())
0035         , m_textTagNames({QStringLiteral("text"), QStringLiteral("Text"), QStringLiteral("title")})
0036     {
0037     }
0038     ~KXMLGUIClientPrivate()
0039     {
0040     }
0041 
0042     bool mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection);
0043     bool isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const;
0044 
0045     QDomElement findMatchingElement(const QDomElement &base, const QDomElement &additive);
0046 
0047     QString m_componentName;
0048 
0049     QDomDocument m_doc;
0050     KActionCollection *m_actionCollection = nullptr;
0051     QDomDocument m_buildDocument;
0052     QPointer<KXMLGUIFactory> m_factory;
0053     KXMLGUIClient *m_parent = nullptr;
0054     // QPtrList<KXMLGUIClient> m_supers;
0055     QList<KXMLGUIClient *> m_children;
0056     KXMLGUIBuilder *m_builder = nullptr;
0057     QString m_xmlFile;
0058     QString m_localXMLFile;
0059     const QStringList m_textTagNames;
0060 
0061     // Actions to enable/disable on a state change
0062     QMap<QString, KXMLGUIClient::StateChange> m_actionsStateMap;
0063 };
0064 
0065 KXMLGUIClient::KXMLGUIClient()
0066     : d(new KXMLGUIClientPrivate)
0067 {
0068 }
0069 
0070 KXMLGUIClient::KXMLGUIClient(KXMLGUIClient *parent)
0071     : d(new KXMLGUIClientPrivate)
0072 {
0073     Q_INIT_RESOURCE(kxmlgui);
0074 
0075     parent->insertChildClient(this);
0076 }
0077 
0078 KXMLGUIClient::~KXMLGUIClient()
0079 {
0080     if (d->m_parent) {
0081         d->m_parent->removeChildClient(this);
0082     }
0083 
0084     if (d->m_factory) {
0085         qCWarning(DEBUG_KXMLGUI)
0086             << this << "deleted without having been removed from the factory first. This will leak standalone popupmenus and could lead to crashes.";
0087         d->m_factory->forgetClient(this);
0088     }
0089 
0090     for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0091         if (d->m_factory) {
0092             d->m_factory->forgetClient(client);
0093         }
0094         assert(client->d->m_parent == this);
0095         client->d->m_parent = nullptr;
0096     }
0097 
0098     delete d->m_actionCollection;
0099 
0100     delete d;
0101 }
0102 
0103 QAction *KXMLGUIClient::action(const char *name) const
0104 {
0105     QAction *act = actionCollection()->action(QLatin1String(name));
0106     if (!act) {
0107         for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0108             act = client->actionCollection()->action(QLatin1String(name));
0109             if (act) {
0110                 break;
0111             }
0112         }
0113     }
0114     return act;
0115 }
0116 
0117 KActionCollection *KXMLGUIClient::actionCollection() const
0118 {
0119     if (!d->m_actionCollection) {
0120         d->m_actionCollection = new KActionCollection(this);
0121         d->m_actionCollection->setObjectName(QStringLiteral("KXMLGUIClient-KActionCollection"));
0122     }
0123     return d->m_actionCollection;
0124 }
0125 
0126 QAction *KXMLGUIClient::action(const QDomElement &element) const
0127 {
0128     return actionCollection()->action(element.attribute(QStringLiteral("name")));
0129 }
0130 
0131 QString KXMLGUIClient::componentName() const
0132 {
0133     return d->m_componentName;
0134 }
0135 
0136 QDomDocument KXMLGUIClient::domDocument() const
0137 {
0138     return d->m_doc;
0139 }
0140 
0141 QString KXMLGUIClient::xmlFile() const
0142 {
0143     return d->m_xmlFile;
0144 }
0145 
0146 QString KXMLGUIClient::localXMLFile() const
0147 {
0148     if (!d->m_localXMLFile.isEmpty()) {
0149         return d->m_localXMLFile;
0150     }
0151 
0152     if (!QDir::isRelativePath(d->m_xmlFile)) {
0153         return QString(); // can't save anything here
0154     }
0155 
0156     if (d->m_xmlFile.isEmpty()) { // setXMLFile not called at all, can't save. Use case: ToolBarHandler
0157         return QString();
0158     }
0159 
0160     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/%1/%2").arg(componentName(), d->m_xmlFile);
0161 }
0162 
0163 void KXMLGUIClient::reloadXML()
0164 {
0165     // TODO: this method can't be used for the KXmlGuiWindow, since it doesn't merge in ui_standards.rc!
0166     //   -> KDE5: load ui_standards_rc in setXMLFile using a flag, and remember that flag?
0167     //            and then KEditToolBar can use reloadXML.
0168     QString file(xmlFile());
0169     if (!file.isEmpty()) {
0170         setXMLFile(file);
0171     }
0172 }
0173 
0174 void KXMLGUIClient::setComponentName(const QString &componentName, const QString &componentDisplayName)
0175 {
0176     d->m_componentName = componentName;
0177     actionCollection()->setComponentName(componentName);
0178     actionCollection()->setComponentDisplayName(componentDisplayName);
0179     if (d->m_builder) {
0180         d->m_builder->setBuilderClient(this);
0181     }
0182 }
0183 
0184 QString KXMLGUIClient::standardsXmlFileLocation()
0185 {
0186     if (QStandardPaths::isTestModeEnabled()) {
0187         return QStringLiteral(":/kxmlgui5/ui_standards.rc");
0188     }
0189     QString file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("ui/ui_standards.rc"));
0190     if (file.isEmpty()) {
0191         // fallback to resource, to allow to use the rc file compiled into this framework, must exist!
0192         file = QStringLiteral(":/kxmlgui5/ui_standards.rc");
0193         Q_ASSERT(QFile::exists(file));
0194     }
0195     return file;
0196 }
0197 
0198 void KXMLGUIClient::loadStandardsXmlFile()
0199 {
0200     setXML(KXMLGUIFactory::readConfigFile(standardsXmlFileLocation()));
0201 }
0202 
0203 void KXMLGUIClient::setXMLFile(const QString &_file, bool merge, bool setXMLDoc)
0204 {
0205     // store our xml file name
0206     if (!_file.isNull()) {
0207         d->m_xmlFile = _file;
0208     }
0209 
0210     if (!setXMLDoc) {
0211         return;
0212     }
0213 
0214     QString file = _file;
0215     QStringList allFiles;
0216     if (!QDir::isRelativePath(file)) {
0217         allFiles.append(file);
0218     } else {
0219         const QString filter = componentName() + QLatin1Char('/') + _file;
0220 
0221         // files on filesystem
0222         allFiles << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0223                                               QStringLiteral("kxmlgui" QT_STRINGIFY(QT_VERSION_MAJOR)) + QLatin1Char('/') + filter); // KF >= 5.1
0224 
0225         // KF >= 5.4 (resource file)
0226         const QString qrcFile(QLatin1String(":/kxmlgui5/") + filter);
0227         if (QFile::exists(qrcFile)) {
0228             allFiles << qrcFile;
0229         }
0230 
0231         // then compat locations
0232         const QStringList compatFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, filter) + // kdelibs4, KF 5.0
0233             QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, _file); // kdelibs4, KF 5.0, caller passes component name
0234 
0235         if (allFiles.isEmpty() && !compatFiles.isEmpty()) {
0236             qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << compatFiles
0237                                      << "-- please use ${KDE_INSTALL_KXMLGUI5DIR} to install this file instead.";
0238         }
0239         allFiles += compatFiles;
0240     }
0241     if (allFiles.isEmpty() && !_file.isEmpty()) {
0242         // if a non-empty file gets passed and we can't find it,
0243         // inform the developer using some debug output
0244         qCWarning(DEBUG_KXMLGUI) << "cannot find .rc file" << _file << "for component" << componentName();
0245     }
0246 
0247     // make sure to merge the settings from any file specified by setLocalXMLFile()
0248     if (!d->m_localXMLFile.isEmpty() && !file.endsWith(QLatin1String("ui_standards.rc"))) {
0249         const bool exists = QDir::isRelativePath(d->m_localXMLFile) || QFile::exists(d->m_localXMLFile);
0250         if (exists && !allFiles.contains(d->m_localXMLFile)) {
0251             allFiles.prepend(d->m_localXMLFile);
0252         }
0253     }
0254 
0255     QString doc;
0256     if (!allFiles.isEmpty()) {
0257         file = findMostRecentXMLFile(allFiles, doc);
0258     }
0259 
0260     // Always call setXML, even on error, so that we don't keep all ui_standards.rc menus.
0261     setXML(doc, merge);
0262 }
0263 
0264 void KXMLGUIClient::setLocalXMLFile(const QString &file)
0265 {
0266     d->m_localXMLFile = file;
0267 }
0268 
0269 void KXMLGUIClient::replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge)
0270 {
0271     if (!QDir::isAbsolutePath(xmlfile)) {
0272         qCWarning(DEBUG_KXMLGUI) << "xml file" << xmlfile << "is not an absolute path";
0273     }
0274 
0275     setLocalXMLFile(localxmlfile);
0276     setXMLFile(xmlfile, merge);
0277 }
0278 
0279 // The top document element may have translation domain attribute set,
0280 // or the translation domain may be implicitly the application domain.
0281 // This domain must be used to fetch translations for all text elements
0282 // in the document that do not have their own domain attribute.
0283 // In order to preserve this semantics through document mergings,
0284 // the top or application domain must be propagated to all text elements
0285 // lacking their own domain attribute.
0286 static void propagateTranslationDomain(QDomDocument &doc, const QStringList &tagNames)
0287 {
0288     const QLatin1String attrDomain("translationDomain");
0289     QDomElement base = doc.documentElement();
0290     QString domain = base.attribute(attrDomain);
0291     if (domain.isEmpty()) {
0292         domain = QString::fromUtf8(KLocalizedString::applicationDomain());
0293         if (domain.isEmpty()) {
0294             return;
0295         }
0296     }
0297     for (const QString &tagName : tagNames) {
0298         QDomNodeList textNodes = base.elementsByTagName(tagName);
0299         for (int i = 0; i < textNodes.length(); ++i) {
0300             QDomElement e = textNodes.item(i).toElement();
0301             QString localDomain = e.attribute(attrDomain);
0302             if (localDomain.isEmpty()) {
0303                 e.setAttribute(attrDomain, domain);
0304             }
0305         }
0306     }
0307 }
0308 
0309 void KXMLGUIClient::setXML(const QString &document, bool merge)
0310 {
0311     QDomDocument doc;
0312     QString errorMsg;
0313     int errorLine = 0;
0314     int errorColumn = 0;
0315     // QDomDocument raises a parse error on empty document, but we accept no app-specific document,
0316     // in which case you only get ui_standards.rc layout.
0317     bool result = document.isEmpty() || doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
0318     if (result) {
0319         propagateTranslationDomain(doc, d->m_textTagNames);
0320         setDOMDocument(doc, merge);
0321     } else {
0322 #ifdef NDEBUG
0323         qCCritical(DEBUG_KXMLGUI) << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
0324         setDOMDocument(QDomDocument(), merge); // otherwise empty menus from ui_standards.rc stay around
0325 #else
0326         qCCritical(DEBUG_KXMLGUI) << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
0327         abort();
0328 #endif
0329     }
0330 }
0331 
0332 void KXMLGUIClient::setDOMDocument(const QDomDocument &document, bool merge)
0333 {
0334     if (merge && !d->m_doc.isNull()) {
0335         QDomElement base = d->m_doc.documentElement();
0336 
0337         QDomElement e = document.documentElement();
0338 
0339         // merge our original (global) xml with our new one
0340         d->mergeXML(base, e, actionCollection());
0341 
0342         // reassign our pointer as mergeXML might have done something
0343         // strange to it
0344         base = d->m_doc.documentElement();
0345 
0346         // qCDebug(DEBUG_KXMLGUI) << "Result of xmlgui merging:" << d->m_doc.toString();
0347 
0348         // we want some sort of failsafe.. just in case
0349         if (base.isNull()) {
0350             d->m_doc = document;
0351         }
0352     } else {
0353         d->m_doc = document;
0354     }
0355 
0356     setXMLGUIBuildDocument(QDomDocument());
0357 }
0358 
0359 // if (equals(a,b)) is more readable than if (a.compare(b, Qt::CaseInsensitive)==0)
0360 static inline bool equalstr(const QString &a, const QString &b)
0361 {
0362     return a.compare(b, Qt::CaseInsensitive) == 0;
0363 }
0364 static inline bool equalstr(const QString &a, QLatin1String b)
0365 {
0366     return a.compare(b, Qt::CaseInsensitive) == 0;
0367 }
0368 
0369 bool KXMLGUIClientPrivate::mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection)
0370 {
0371     const QLatin1String tagAction("Action");
0372     const QLatin1String tagMerge("Merge");
0373     const QLatin1String tagSeparator("Separator");
0374     const QLatin1String tagMergeLocal("MergeLocal");
0375     const QLatin1String tagText("text");
0376     const QLatin1String attrAppend("append");
0377     const QString attrName(QStringLiteral("name"));
0378     const QString attrWeakSeparator(QStringLiteral("weakSeparator"));
0379     const QString attrAlreadyVisited(QStringLiteral("alreadyVisited"));
0380     const QString attrNoMerge(QStringLiteral("noMerge"));
0381     const QLatin1String attrOne("1");
0382 
0383     // there is a possibility that we don't want to merge in the
0384     // additive.. rather, we might want to *replace* the base with the
0385     // additive.  this can be for any container.. either at a file wide
0386     // level or a simple container level.  we look for the 'noMerge'
0387     // tag, in any event and just replace the old with the new
0388     if (additive.attribute(attrNoMerge) == attrOne) { // ### use toInt() instead? (Simon)
0389         base.parentNode().replaceChild(additive, base);
0390         return true;
0391     } else {
0392         // Merge attributes
0393         {
0394             const QDomNamedNodeMap attribs = additive.attributes();
0395             const int attribcount = attribs.count();
0396 
0397             for (int i = 0; i < attribcount; ++i) {
0398                 const QDomNode node = attribs.item(i);
0399                 base.setAttribute(node.nodeName(), node.nodeValue());
0400             }
0401         }
0402 
0403         // iterate over all elements in the container (of the global DOM tree)
0404         QDomNode n = base.firstChild();
0405         while (!n.isNull()) {
0406             QDomElement e = n.toElement();
0407             n = n.nextSibling(); // Advance now so that we can safely delete e
0408             if (e.isNull()) {
0409                 continue;
0410             }
0411 
0412             const QString tag = e.tagName();
0413 
0414             // if there's an action tag in the global tree and the action is
0415             // not implemented, then we remove the element
0416             if (equalstr(tag, tagAction)) {
0417                 const QString name = e.attribute(attrName);
0418                 if (!actionCollection->action(name) || !KAuthorized::authorizeAction(name)) {
0419                     // remove this child as we aren't using it
0420                     base.removeChild(e);
0421                     continue;
0422                 }
0423             }
0424 
0425             // if there's a separator defined in the global tree, then add an
0426             // attribute, specifying that this is a "weak" separator
0427             else if (equalstr(tag, tagSeparator)) {
0428                 e.setAttribute(attrWeakSeparator, uint(1));
0429 
0430                 // okay, hack time. if the last item was a weak separator OR
0431                 // this is the first item in a container, then we nuke the
0432                 // current one
0433                 QDomElement prev = e.previousSibling().toElement();
0434                 if (prev.isNull() //
0435                     || (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull()) //
0436                     || (equalstr(prev.tagName(), tagText))) {
0437                     // the previous element was a weak separator or didn't exist
0438                     base.removeChild(e);
0439                     continue;
0440                 }
0441             }
0442 
0443             // the MergeLocal tag lets us specify where non-standard elements
0444             // of the local tree shall be merged in.  After inserting the
0445             // elements we delete this element
0446             else if (equalstr(tag, tagMergeLocal)) {
0447                 QDomNode it = additive.firstChild();
0448                 while (!it.isNull()) {
0449                     QDomElement newChild = it.toElement();
0450                     it = it.nextSibling();
0451                     if (newChild.isNull()) {
0452                         continue;
0453                     }
0454 
0455                     if (equalstr(newChild.tagName(), tagText)) {
0456                         continue;
0457                     }
0458 
0459                     if (newChild.attribute(attrAlreadyVisited) == attrOne) {
0460                         continue;
0461                     }
0462 
0463                     QString itAppend(newChild.attribute(attrAppend));
0464                     QString elemName(e.attribute(attrName));
0465 
0466                     if ((itAppend.isNull() && elemName.isEmpty()) || (itAppend == elemName)) {
0467                         // first, see if this new element matches a standard one in
0468                         // the global file.  if it does, then we skip it as it will
0469                         // be merged in, later
0470                         QDomElement matchingElement = findMatchingElement(newChild, base);
0471                         if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator)) {
0472                             base.insertBefore(newChild, e);
0473                         }
0474                     }
0475                 }
0476 
0477                 base.removeChild(e);
0478                 continue;
0479             }
0480 
0481             else if (equalstr(tag, tagText)) {
0482                 continue;
0483             } else if (equalstr(tag, tagMerge)) {
0484                 continue;
0485             }
0486 
0487             // in this last case we check for a separator tag and, if not, we
0488             // can be sure that it is a container --> proceed with child nodes
0489             // recursively and delete the just proceeded container item in
0490             // case it is empty (if the recursive call returns true)
0491             else {
0492                 QDomElement matchingElement = findMatchingElement(e, additive);
0493                 if (!matchingElement.isNull()) {
0494                     matchingElement.setAttribute(attrAlreadyVisited, uint(1));
0495 
0496                     if (mergeXML(e, matchingElement, actionCollection)) {
0497                         base.removeChild(e);
0498                         additive.removeChild(matchingElement); // make sure we don't append it below
0499                         continue;
0500                     }
0501 
0502                     continue;
0503                 } else {
0504                     // this is an important case here! We reach this point if the
0505                     // "local" tree does not contain a container definition for
0506                     // this container. However we have to call mergeXML recursively
0507                     // and make it check if there are actions implemented for this
0508                     // container. *If* none, then we can remove this container now
0509                     QDomElement dummy;
0510                     if (mergeXML(e, dummy, actionCollection)) {
0511                         base.removeChild(e);
0512                     }
0513                     continue;
0514                 }
0515             }
0516         }
0517 
0518         // here we append all child elements which were not inserted
0519         // previously via the LocalMerge tag
0520         n = additive.firstChild();
0521         while (!n.isNull()) {
0522             QDomElement e = n.toElement();
0523             n = n.nextSibling(); // Advance now so that we can safely delete e
0524             if (e.isNull()) {
0525                 continue;
0526             }
0527 
0528             QDomElement matchingElement = findMatchingElement(e, base);
0529 
0530             if (matchingElement.isNull()) {
0531                 base.appendChild(e);
0532             }
0533         }
0534 
0535         // do one quick check to make sure that the last element was not
0536         // a weak separator
0537         QDomElement last = base.lastChild().toElement();
0538         if (equalstr(last.tagName(), tagSeparator) && (!last.attribute(attrWeakSeparator).isNull())) {
0539             base.removeChild(last);
0540         }
0541     }
0542 
0543     return isEmptyContainer(base, actionCollection);
0544 }
0545 
0546 bool KXMLGUIClientPrivate::isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const
0547 {
0548     // now we check if we are empty (in which case we return "true", to
0549     // indicate the caller that it can delete "us" (the base element
0550     // argument of "this" call)
0551     QDomNode n = base.firstChild();
0552     while (!n.isNull()) {
0553         const QDomElement e = n.toElement();
0554         n = n.nextSibling(); // Advance now so that we can safely delete e
0555         if (e.isNull()) {
0556             continue;
0557         }
0558 
0559         const QString tag = e.tagName();
0560 
0561         if (equalstr(tag, QLatin1String("Action"))) {
0562             // if base contains an implemented action, then we must not get
0563             // deleted (note that the actionCollection contains both,
0564             // "global" and "local" actions)
0565             if (actionCollection->action(e.attribute(QStringLiteral("name")))) {
0566                 return false;
0567             }
0568         } else if (equalstr(tag, QLatin1String("Separator"))) {
0569             // if we have a separator which has *not* the weak attribute
0570             // set, then it must be owned by the "local" tree in which case
0571             // we must not get deleted either
0572             const QString weakAttr = e.attribute(QStringLiteral("weakSeparator"));
0573             if (weakAttr.isEmpty() || weakAttr.toInt() != 1) {
0574                 return false;
0575             }
0576         }
0577 
0578         else if (equalstr(tag, QLatin1String("merge"))) {
0579             continue;
0580         }
0581 
0582         // a text tag is NOT enough to spare this container
0583         else if (equalstr(tag, QLatin1String("text"))) {
0584             continue;
0585         }
0586 
0587         // what's left are non-empty containers! *don't* delete us in this
0588         // case (at this position we can be *sure* that the container is
0589         // *not* empty, as the recursive call for it was in the first loop
0590         // which deleted the element in case the call returned "true"
0591         else {
0592             return false;
0593         }
0594     }
0595 
0596     return true; // I'm empty, please delete me.
0597 }
0598 
0599 QDomElement KXMLGUIClientPrivate::findMatchingElement(const QDomElement &base, const QDomElement &additive)
0600 {
0601     QDomNode n = additive.firstChild();
0602     while (!n.isNull()) {
0603         QDomElement e = n.toElement();
0604         n = n.nextSibling(); // Advance now so that we can safely delete e -- TODO we don't, so simplify this
0605         if (e.isNull()) {
0606             continue;
0607         }
0608 
0609         const QString tag = e.tagName();
0610         // skip all action and merge tags as we will never use them
0611         if (equalstr(tag, QLatin1String("Action")) //
0612             || equalstr(tag, QLatin1String("MergeLocal"))) {
0613             continue;
0614         }
0615 
0616         // now see if our tags are equivalent
0617         if (equalstr(tag, base.tagName()) //
0618             && e.attribute(QStringLiteral("name")) == base.attribute(QStringLiteral("name"))) {
0619             return e;
0620         }
0621     }
0622 
0623     // nope, return a (now) null element
0624     return QDomElement();
0625 }
0626 
0627 void KXMLGUIClient::setXMLGUIBuildDocument(const QDomDocument &doc)
0628 {
0629     d->m_buildDocument = doc;
0630 }
0631 
0632 QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
0633 {
0634     return d->m_buildDocument;
0635 }
0636 
0637 void KXMLGUIClient::setFactory(KXMLGUIFactory *factory)
0638 {
0639     d->m_factory = factory;
0640 }
0641 
0642 KXMLGUIFactory *KXMLGUIClient::factory() const
0643 {
0644     return d->m_factory;
0645 }
0646 
0647 KXMLGUIClient *KXMLGUIClient::parentClient() const
0648 {
0649     return d->m_parent;
0650 }
0651 
0652 void KXMLGUIClient::insertChildClient(KXMLGUIClient *child)
0653 {
0654     if (child->d->m_parent) {
0655         child->d->m_parent->removeChildClient(child);
0656     }
0657     d->m_children.append(child);
0658     child->d->m_parent = this;
0659 }
0660 
0661 void KXMLGUIClient::removeChildClient(KXMLGUIClient *child)
0662 {
0663     assert(d->m_children.contains(child));
0664     d->m_children.removeAll(child);
0665     child->d->m_parent = nullptr;
0666 }
0667 
0668 /*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super )
0669 {
0670   if ( d->m_supers.contains( super ) )
0671     return false;
0672   d->m_supers.append( super );
0673   return true;
0674 }*/
0675 
0676 QList<KXMLGUIClient *> KXMLGUIClient::childClients()
0677 {
0678     return d->m_children;
0679 }
0680 
0681 void KXMLGUIClient::setClientBuilder(KXMLGUIBuilder *builder)
0682 {
0683     d->m_builder = builder;
0684 }
0685 
0686 KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
0687 {
0688     return d->m_builder;
0689 }
0690 
0691 void KXMLGUIClient::plugActionList(const QString &name, const QList<QAction *> &actionList)
0692 {
0693     if (!d->m_factory) {
0694         return;
0695     }
0696 
0697     d->m_factory->plugActionList(this, name, actionList);
0698 }
0699 
0700 void KXMLGUIClient::unplugActionList(const QString &name)
0701 {
0702     if (!d->m_factory) {
0703         return;
0704     }
0705 
0706     d->m_factory->unplugActionList(this, name);
0707 }
0708 
0709 QString KXMLGUIClient::findMostRecentXMLFile(const QStringList &files, QString &doc)
0710 {
0711     KXmlGuiVersionHandler versionHandler(files);
0712     doc = versionHandler.finalDocument();
0713     return versionHandler.finalFile();
0714 }
0715 
0716 void KXMLGUIClient::addStateActionEnabled(const QString &state, const QString &action)
0717 {
0718     StateChange stateChange = getActionsToChangeForState(state);
0719 
0720     stateChange.actionsToEnable.append(action);
0721     // qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")";
0722 
0723     d->m_actionsStateMap.insert(state, stateChange);
0724 }
0725 
0726 void KXMLGUIClient::addStateActionDisabled(const QString &state, const QString &action)
0727 {
0728     StateChange stateChange = getActionsToChangeForState(state);
0729 
0730     stateChange.actionsToDisable.append(action);
0731     // qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")";
0732 
0733     d->m_actionsStateMap.insert(state, stateChange);
0734 }
0735 
0736 KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString &state)
0737 {
0738     return d->m_actionsStateMap[state];
0739 }
0740 
0741 void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
0742 {
0743     const StateChange stateChange = getActionsToChangeForState(newstate);
0744 
0745     bool setTrue = (reverse == StateNoReverse);
0746     bool setFalse = !setTrue;
0747 
0748     // Enable actions which need to be enabled...
0749     //
0750     for (const auto &actionId : stateChange.actionsToEnable) {
0751         QAction *action = actionCollection()->action(actionId);
0752         if (action) {
0753             action->setEnabled(setTrue);
0754         }
0755     }
0756 
0757     // and disable actions which need to be disabled...
0758     //
0759     for (const auto &actionId : stateChange.actionsToDisable) {
0760         QAction *action = actionCollection()->action(actionId);
0761         if (action) {
0762             action->setEnabled(setFalse);
0763         }
0764     }
0765 }
0766 
0767 void KXMLGUIClient::beginXMLPlug(QWidget *w)
0768 {
0769     actionCollection()->addAssociatedWidget(w);
0770     for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0771         client->beginXMLPlug(w);
0772     }
0773 }
0774 
0775 void KXMLGUIClient::endXMLPlug()
0776 {
0777 }
0778 
0779 void KXMLGUIClient::prepareXMLUnplug(QWidget *w)
0780 {
0781     actionCollection()->removeAssociatedWidget(w);
0782     for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0783         client->prepareXMLUnplug(w);
0784     }
0785 }
0786 
0787 void KXMLGUIClient::virtual_hook(int, void *)
0788 {
0789     /*BASE::virtual_hook( id, data );*/
0790 }
0791 
0792 QString KXMLGUIClient::findVersionNumber(const QString &xml)
0793 {
0794     enum {
0795         ST_START,
0796         ST_AFTER_OPEN,
0797         ST_AFTER_GUI,
0798         ST_EXPECT_VERSION,
0799         ST_VERSION_NUM,
0800     } state = ST_START;
0801     const int length = xml.length();
0802     for (int pos = 0; pos < length; pos++) {
0803         switch (state) {
0804         case ST_START:
0805             if (xml[pos] == QLatin1Char('<')) {
0806                 state = ST_AFTER_OPEN;
0807             }
0808             break;
0809         case ST_AFTER_OPEN: {
0810             // Jump to gui..
0811             const int guipos = xml.indexOf(QLatin1String("gui"), pos, Qt::CaseInsensitive);
0812             if (guipos == -1) {
0813                 return QString(); // Reject
0814             }
0815 
0816             pos = guipos + 2; // Position at i, so we're moved ahead to the next character by the ++;
0817             state = ST_AFTER_GUI;
0818             break;
0819         }
0820         case ST_AFTER_GUI:
0821             state = ST_EXPECT_VERSION;
0822             break;
0823         case ST_EXPECT_VERSION: {
0824             const int verpos = xml.indexOf(QLatin1String("version"), pos, Qt::CaseInsensitive);
0825             if (verpos == -1) {
0826                 return QString(); // Reject
0827             }
0828             pos = verpos + 7; // strlen("version") is 7
0829             while (xml.at(pos).isSpace()) {
0830                 ++pos;
0831             }
0832             if (xml.at(pos++) != QLatin1Char('=')) {
0833                 return QString(); // Reject
0834             }
0835             while (xml.at(pos).isSpace()) {
0836                 ++pos;
0837             }
0838 
0839             state = ST_VERSION_NUM;
0840             break;
0841         }
0842         case ST_VERSION_NUM: {
0843             int endpos;
0844             for (endpos = pos; endpos < length; endpos++) {
0845                 const ushort ch = xml[endpos].unicode();
0846                 if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) {
0847                     continue; // Number..
0848                 }
0849                 if (ch == QLatin1Char('"')) { // End of parameter
0850                     break;
0851                 } else { // This shouldn't be here..
0852                     endpos = length;
0853                 }
0854             }
0855 
0856             if (endpos != pos && endpos < length) {
0857                 const QString matchCandidate = xml.mid(pos, endpos - pos); // Don't include " ".
0858                 return matchCandidate;
0859             }
0860 
0861             state = ST_EXPECT_VERSION; // Try to match a well-formed version..
0862             break;
0863         } // case..
0864         } // switch
0865     } // for
0866 
0867     return QString();
0868 }