File indexing completed on 2024-04-14 03:57:14

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 QString &name) const
0104 {
0105     QAction *act = actionCollection()->action(name);
0106     if (!act) {
0107         for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0108             act = client->actionCollection()->action(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("kxmlgui5/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, QStringLiteral("kxmlgui5/") + filter);
0223 
0224         // built-in resource file
0225         const QString qrcFile(QLatin1String(":/kxmlgui5/") + filter);
0226         if (QFile::exists(qrcFile)) {
0227             allFiles << qrcFile;
0228         }
0229     }
0230     if (allFiles.isEmpty() && !_file.isEmpty()) {
0231         // if a non-empty file gets passed and we can't find it,
0232         // inform the developer using some debug output
0233         qCWarning(DEBUG_KXMLGUI) << "cannot find .rc file" << _file << "for component" << componentName();
0234     }
0235 
0236     // make sure to merge the settings from any file specified by setLocalXMLFile()
0237     if (!d->m_localXMLFile.isEmpty() && !file.endsWith(QLatin1String("ui_standards.rc"))) {
0238         const bool exists = QDir::isRelativePath(d->m_localXMLFile) || QFile::exists(d->m_localXMLFile);
0239         if (exists && !allFiles.contains(d->m_localXMLFile)) {
0240             allFiles.prepend(d->m_localXMLFile);
0241         }
0242     }
0243 
0244     QString doc;
0245     if (!allFiles.isEmpty()) {
0246         file = findMostRecentXMLFile(allFiles, doc);
0247     }
0248 
0249     // Always call setXML, even on error, so that we don't keep all ui_standards.rc menus.
0250     setXML(doc, merge);
0251 }
0252 
0253 void KXMLGUIClient::setLocalXMLFile(const QString &file)
0254 {
0255     d->m_localXMLFile = file;
0256 }
0257 
0258 void KXMLGUIClient::replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge)
0259 {
0260     if (!QDir::isAbsolutePath(xmlfile)) {
0261         qCWarning(DEBUG_KXMLGUI) << "xml file" << xmlfile << "is not an absolute path";
0262     }
0263 
0264     setLocalXMLFile(localxmlfile);
0265     setXMLFile(xmlfile, merge);
0266 }
0267 
0268 // The top document element may have translation domain attribute set,
0269 // or the translation domain may be implicitly the application domain.
0270 // This domain must be used to fetch translations for all text elements
0271 // in the document that do not have their own domain attribute.
0272 // In order to preserve this semantics through document mergings,
0273 // the top or application domain must be propagated to all text elements
0274 // lacking their own domain attribute.
0275 static void propagateTranslationDomain(QDomDocument &doc, const QStringList &tagNames)
0276 {
0277     const QLatin1String attrDomain("translationDomain");
0278     QDomElement base = doc.documentElement();
0279     QString domain = base.attribute(attrDomain);
0280     if (domain.isEmpty()) {
0281         domain = QString::fromUtf8(KLocalizedString::applicationDomain());
0282         if (domain.isEmpty()) {
0283             return;
0284         }
0285     }
0286     for (const QString &tagName : tagNames) {
0287         QDomNodeList textNodes = base.elementsByTagName(tagName);
0288         for (int i = 0; i < textNodes.length(); ++i) {
0289             QDomElement e = textNodes.item(i).toElement();
0290             QString localDomain = e.attribute(attrDomain);
0291             if (localDomain.isEmpty()) {
0292                 e.setAttribute(attrDomain, domain);
0293             }
0294         }
0295     }
0296 }
0297 
0298 void KXMLGUIClient::setXML(const QString &document, bool merge)
0299 {
0300     QDomDocument doc;
0301     // QDomDocument raises a parse error on empty document, but we accept no app-specific document,
0302     // in which case you only get ui_standards.rc layout.
0303     if (!document.isEmpty()) {
0304         const QDomDocument::ParseResult result = doc.setContent(document);
0305         if (!result) {
0306             qCCritical(DEBUG_KXMLGUI) << "Error parsing XML document:" << result.errorMessage << "at line" << result.errorLine << "column"
0307                                       << result.errorColumn;
0308 #ifdef NDEBUG
0309             setDOMDocument(QDomDocument(), merge); // otherwise empty menus from ui_standards.rc stay around
0310 #else
0311             abort();
0312 #endif
0313             return;
0314         }
0315     }
0316 
0317     propagateTranslationDomain(doc, d->m_textTagNames);
0318     setDOMDocument(doc, merge);
0319 }
0320 
0321 void KXMLGUIClient::setDOMDocument(const QDomDocument &document, bool merge)
0322 {
0323     if (merge && !d->m_doc.isNull()) {
0324         QDomElement base = d->m_doc.documentElement();
0325 
0326         QDomElement e = document.documentElement();
0327 
0328         // merge our original (global) xml with our new one
0329         d->mergeXML(base, e, actionCollection());
0330 
0331         // reassign our pointer as mergeXML might have done something
0332         // strange to it
0333         base = d->m_doc.documentElement();
0334 
0335         // qCDebug(DEBUG_KXMLGUI) << "Result of xmlgui merging:" << d->m_doc.toString();
0336 
0337         // we want some sort of failsafe.. just in case
0338         if (base.isNull()) {
0339             d->m_doc = document;
0340         }
0341     } else {
0342         d->m_doc = document;
0343     }
0344 
0345     setXMLGUIBuildDocument(QDomDocument());
0346 }
0347 
0348 // if (equals(a,b)) is more readable than if (a.compare(b, Qt::CaseInsensitive)==0)
0349 static inline bool equalstr(const QString &a, const QString &b)
0350 {
0351     return a.compare(b, Qt::CaseInsensitive) == 0;
0352 }
0353 static inline bool equalstr(const QString &a, QLatin1String b)
0354 {
0355     return a.compare(b, Qt::CaseInsensitive) == 0;
0356 }
0357 
0358 bool KXMLGUIClientPrivate::mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection)
0359 {
0360     const QLatin1String tagAction("Action");
0361     const QLatin1String tagMerge("Merge");
0362     const QLatin1String tagSeparator("Separator");
0363     const QLatin1String tagMergeLocal("MergeLocal");
0364     const QLatin1String tagText("text");
0365     const QLatin1String attrAppend("append");
0366     const QString attrName(QStringLiteral("name"));
0367     const QString attrWeakSeparator(QStringLiteral("weakSeparator"));
0368     const QString attrAlreadyVisited(QStringLiteral("alreadyVisited"));
0369     const QString attrNoMerge(QStringLiteral("noMerge"));
0370     const QLatin1String attrOne("1");
0371 
0372     // there is a possibility that we don't want to merge in the
0373     // additive.. rather, we might want to *replace* the base with the
0374     // additive.  this can be for any container.. either at a file wide
0375     // level or a simple container level.  we look for the 'noMerge'
0376     // tag, in any event and just replace the old with the new
0377     if (additive.attribute(attrNoMerge) == attrOne) { // ### use toInt() instead? (Simon)
0378         base.parentNode().replaceChild(additive, base);
0379         return true;
0380     } else {
0381         // Merge attributes
0382         {
0383             const QDomNamedNodeMap attribs = additive.attributes();
0384             const int attribcount = attribs.count();
0385 
0386             for (int i = 0; i < attribcount; ++i) {
0387                 const QDomNode node = attribs.item(i);
0388                 base.setAttribute(node.nodeName(), node.nodeValue());
0389             }
0390         }
0391 
0392         // iterate over all elements in the container (of the global DOM tree)
0393         QDomNode n = base.firstChild();
0394         while (!n.isNull()) {
0395             QDomElement e = n.toElement();
0396             n = n.nextSibling(); // Advance now so that we can safely delete e
0397             if (e.isNull()) {
0398                 continue;
0399             }
0400 
0401             const QString tag = e.tagName();
0402 
0403             // if there's an action tag in the global tree and the action is
0404             // not implemented, then we remove the element
0405             if (equalstr(tag, tagAction)) {
0406                 const QString name = e.attribute(attrName);
0407                 if (!actionCollection->action(name) || !KAuthorized::authorizeAction(name)) {
0408                     // remove this child as we aren't using it
0409                     base.removeChild(e);
0410                     continue;
0411                 }
0412             }
0413 
0414             // if there's a separator defined in the global tree, then add an
0415             // attribute, specifying that this is a "weak" separator
0416             else if (equalstr(tag, tagSeparator)) {
0417                 e.setAttribute(attrWeakSeparator, uint(1));
0418 
0419                 // okay, hack time. if the last item was a weak separator OR
0420                 // this is the first item in a container, then we nuke the
0421                 // current one
0422                 QDomElement prev = e.previousSibling().toElement();
0423                 if (prev.isNull() //
0424                     || (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull()) //
0425                     || (equalstr(prev.tagName(), tagText))) {
0426                     // the previous element was a weak separator or didn't exist
0427                     base.removeChild(e);
0428                     continue;
0429                 }
0430             }
0431 
0432             // the MergeLocal tag lets us specify where non-standard elements
0433             // of the local tree shall be merged in.  After inserting the
0434             // elements we delete this element
0435             else if (equalstr(tag, tagMergeLocal)) {
0436                 QDomNode it = additive.firstChild();
0437                 while (!it.isNull()) {
0438                     QDomElement newChild = it.toElement();
0439                     it = it.nextSibling();
0440                     if (newChild.isNull()) {
0441                         continue;
0442                     }
0443 
0444                     if (equalstr(newChild.tagName(), tagText)) {
0445                         continue;
0446                     }
0447 
0448                     if (newChild.attribute(attrAlreadyVisited) == attrOne) {
0449                         continue;
0450                     }
0451 
0452                     QString itAppend(newChild.attribute(attrAppend));
0453                     QString elemName(e.attribute(attrName));
0454 
0455                     if ((itAppend.isNull() && elemName.isEmpty()) || (itAppend == elemName)) {
0456                         // first, see if this new element matches a standard one in
0457                         // the global file.  if it does, then we skip it as it will
0458                         // be merged in, later
0459                         QDomElement matchingElement = findMatchingElement(newChild, base);
0460                         if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator)) {
0461                             base.insertBefore(newChild, e);
0462                         }
0463                     }
0464                 }
0465 
0466                 base.removeChild(e);
0467                 continue;
0468             }
0469 
0470             else if (equalstr(tag, tagText)) {
0471                 continue;
0472             } else if (equalstr(tag, tagMerge)) {
0473                 continue;
0474             }
0475 
0476             // in this last case we check for a separator tag and, if not, we
0477             // can be sure that it is a container --> proceed with child nodes
0478             // recursively and delete the just proceeded container item in
0479             // case it is empty (if the recursive call returns true)
0480             else {
0481                 QDomElement matchingElement = findMatchingElement(e, additive);
0482                 if (!matchingElement.isNull()) {
0483                     matchingElement.setAttribute(attrAlreadyVisited, uint(1));
0484 
0485                     if (mergeXML(e, matchingElement, actionCollection)) {
0486                         base.removeChild(e);
0487                         additive.removeChild(matchingElement); // make sure we don't append it below
0488                         continue;
0489                     }
0490 
0491                     continue;
0492                 } else {
0493                     // this is an important case here! We reach this point if the
0494                     // "local" tree does not contain a container definition for
0495                     // this container. However we have to call mergeXML recursively
0496                     // and make it check if there are actions implemented for this
0497                     // container. *If* none, then we can remove this container now
0498                     QDomElement dummy;
0499                     if (mergeXML(e, dummy, actionCollection)) {
0500                         base.removeChild(e);
0501                     }
0502                     continue;
0503                 }
0504             }
0505         }
0506 
0507         // here we append all child elements which were not inserted
0508         // previously via the LocalMerge tag
0509         n = additive.firstChild();
0510         while (!n.isNull()) {
0511             QDomElement e = n.toElement();
0512             n = n.nextSibling(); // Advance now so that we can safely delete e
0513             if (e.isNull()) {
0514                 continue;
0515             }
0516 
0517             QDomElement matchingElement = findMatchingElement(e, base);
0518 
0519             if (matchingElement.isNull()) {
0520                 base.appendChild(e);
0521             }
0522         }
0523 
0524         // do one quick check to make sure that the last element was not
0525         // a weak separator
0526         QDomElement last = base.lastChild().toElement();
0527         if (equalstr(last.tagName(), tagSeparator) && (!last.attribute(attrWeakSeparator).isNull())) {
0528             base.removeChild(last);
0529         }
0530     }
0531 
0532     return isEmptyContainer(base, actionCollection);
0533 }
0534 
0535 bool KXMLGUIClientPrivate::isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const
0536 {
0537     // now we check if we are empty (in which case we return "true", to
0538     // indicate the caller that it can delete "us" (the base element
0539     // argument of "this" call)
0540     QDomNode n = base.firstChild();
0541     while (!n.isNull()) {
0542         const QDomElement e = n.toElement();
0543         n = n.nextSibling(); // Advance now so that we can safely delete e
0544         if (e.isNull()) {
0545             continue;
0546         }
0547 
0548         const QString tag = e.tagName();
0549 
0550         if (equalstr(tag, QLatin1String("Action"))) {
0551             // if base contains an implemented action, then we must not get
0552             // deleted (note that the actionCollection contains both,
0553             // "global" and "local" actions)
0554             if (actionCollection->action(e.attribute(QStringLiteral("name")))) {
0555                 return false;
0556             }
0557         } else if (equalstr(tag, QLatin1String("Separator"))) {
0558             // if we have a separator which has *not* the weak attribute
0559             // set, then it must be owned by the "local" tree in which case
0560             // we must not get deleted either
0561             const QString weakAttr = e.attribute(QStringLiteral("weakSeparator"));
0562             if (weakAttr.isEmpty() || weakAttr.toInt() != 1) {
0563                 return false;
0564             }
0565         }
0566 
0567         else if (equalstr(tag, QLatin1String("merge"))) {
0568             continue;
0569         }
0570 
0571         // a text tag is NOT enough to spare this container
0572         else if (equalstr(tag, QLatin1String("text"))) {
0573             continue;
0574         }
0575 
0576         // what's left are non-empty containers! *don't* delete us in this
0577         // case (at this position we can be *sure* that the container is
0578         // *not* empty, as the recursive call for it was in the first loop
0579         // which deleted the element in case the call returned "true"
0580         else {
0581             return false;
0582         }
0583     }
0584 
0585     return true; // I'm empty, please delete me.
0586 }
0587 
0588 QDomElement KXMLGUIClientPrivate::findMatchingElement(const QDomElement &base, const QDomElement &additive)
0589 {
0590     const QString idAttribute(base.tagName() == QLatin1String("ActionProperties") ? QStringLiteral("scheme") : QStringLiteral("name"));
0591 
0592     QDomNode n = additive.firstChild();
0593     while (!n.isNull()) {
0594         QDomElement e = n.toElement();
0595         n = n.nextSibling(); // Advance now so that we can safely delete e -- TODO we don't, so simplify this
0596         if (e.isNull()) {
0597             continue;
0598         }
0599 
0600         const QString tag = e.tagName();
0601         // skip all action and merge tags as we will never use them
0602         if (equalstr(tag, QLatin1String("Action")) //
0603             || equalstr(tag, QLatin1String("MergeLocal"))) {
0604             continue;
0605         }
0606 
0607         // now see if our tags are equivalent
0608         if (equalstr(tag, base.tagName()) //
0609             && e.attribute(idAttribute) == base.attribute(idAttribute)) {
0610             return e;
0611         }
0612     }
0613 
0614     // nope, return a (now) null element
0615     return QDomElement();
0616 }
0617 
0618 void KXMLGUIClient::setXMLGUIBuildDocument(const QDomDocument &doc)
0619 {
0620     d->m_buildDocument = doc;
0621 }
0622 
0623 QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
0624 {
0625     return d->m_buildDocument;
0626 }
0627 
0628 void KXMLGUIClient::setFactory(KXMLGUIFactory *factory)
0629 {
0630     d->m_factory = factory;
0631 }
0632 
0633 KXMLGUIFactory *KXMLGUIClient::factory() const
0634 {
0635     return d->m_factory;
0636 }
0637 
0638 KXMLGUIClient *KXMLGUIClient::parentClient() const
0639 {
0640     return d->m_parent;
0641 }
0642 
0643 void KXMLGUIClient::insertChildClient(KXMLGUIClient *child)
0644 {
0645     if (child->d->m_parent) {
0646         child->d->m_parent->removeChildClient(child);
0647     }
0648     d->m_children.append(child);
0649     child->d->m_parent = this;
0650 }
0651 
0652 void KXMLGUIClient::removeChildClient(KXMLGUIClient *child)
0653 {
0654     assert(d->m_children.contains(child));
0655     d->m_children.removeAll(child);
0656     child->d->m_parent = nullptr;
0657 }
0658 
0659 /*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super )
0660 {
0661   if ( d->m_supers.contains( super ) )
0662     return false;
0663   d->m_supers.append( super );
0664   return true;
0665 }*/
0666 
0667 QList<KXMLGUIClient *> KXMLGUIClient::childClients()
0668 {
0669     return d->m_children;
0670 }
0671 
0672 void KXMLGUIClient::setClientBuilder(KXMLGUIBuilder *builder)
0673 {
0674     d->m_builder = builder;
0675 }
0676 
0677 KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
0678 {
0679     return d->m_builder;
0680 }
0681 
0682 void KXMLGUIClient::plugActionList(const QString &name, const QList<QAction *> &actionList)
0683 {
0684     if (!d->m_factory) {
0685         return;
0686     }
0687 
0688     d->m_factory->plugActionList(this, name, actionList);
0689 }
0690 
0691 void KXMLGUIClient::unplugActionList(const QString &name)
0692 {
0693     if (!d->m_factory) {
0694         return;
0695     }
0696 
0697     d->m_factory->unplugActionList(this, name);
0698 }
0699 
0700 QString KXMLGUIClient::findMostRecentXMLFile(const QStringList &files, QString &doc)
0701 {
0702     KXmlGuiVersionHandler versionHandler(files);
0703     doc = versionHandler.finalDocument();
0704     return versionHandler.finalFile();
0705 }
0706 
0707 void KXMLGUIClient::addStateActionEnabled(const QString &state, const QString &action)
0708 {
0709     StateChange stateChange = getActionsToChangeForState(state);
0710 
0711     stateChange.actionsToEnable.append(action);
0712     // qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")";
0713 
0714     d->m_actionsStateMap.insert(state, stateChange);
0715 }
0716 
0717 void KXMLGUIClient::addStateActionDisabled(const QString &state, const QString &action)
0718 {
0719     StateChange stateChange = getActionsToChangeForState(state);
0720 
0721     stateChange.actionsToDisable.append(action);
0722     // qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")";
0723 
0724     d->m_actionsStateMap.insert(state, stateChange);
0725 }
0726 
0727 KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString &state)
0728 {
0729     return d->m_actionsStateMap[state];
0730 }
0731 
0732 void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
0733 {
0734     const StateChange stateChange = getActionsToChangeForState(newstate);
0735 
0736     bool setTrue = (reverse == StateNoReverse);
0737     bool setFalse = !setTrue;
0738 
0739     // Enable actions which need to be enabled...
0740     //
0741     for (const auto &actionId : stateChange.actionsToEnable) {
0742         QAction *action = actionCollection()->action(actionId);
0743         if (action) {
0744             action->setEnabled(setTrue);
0745         }
0746     }
0747 
0748     // and disable actions which need to be disabled...
0749     //
0750     for (const auto &actionId : stateChange.actionsToDisable) {
0751         QAction *action = actionCollection()->action(actionId);
0752         if (action) {
0753             action->setEnabled(setFalse);
0754         }
0755     }
0756 }
0757 
0758 void KXMLGUIClient::beginXMLPlug(QWidget *w)
0759 {
0760     actionCollection()->addAssociatedWidget(w);
0761     for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0762         client->beginXMLPlug(w);
0763     }
0764 }
0765 
0766 void KXMLGUIClient::endXMLPlug()
0767 {
0768 }
0769 
0770 void KXMLGUIClient::prepareXMLUnplug(QWidget *w)
0771 {
0772     actionCollection()->removeAssociatedWidget(w);
0773     for (KXMLGUIClient *client : std::as_const(d->m_children)) {
0774         client->prepareXMLUnplug(w);
0775     }
0776 }
0777 
0778 void KXMLGUIClient::virtual_hook(int, void *)
0779 {
0780     /*BASE::virtual_hook( id, data );*/
0781 }
0782 
0783 QString KXMLGUIClient::findVersionNumber(const QString &xml)
0784 {
0785     enum {
0786         ST_START,
0787         ST_AFTER_OPEN,
0788         ST_AFTER_GUI,
0789         ST_EXPECT_VERSION,
0790         ST_VERSION_NUM,
0791     } state = ST_START;
0792     const int length = xml.length();
0793     for (int pos = 0; pos < length; pos++) {
0794         switch (state) {
0795         case ST_START:
0796             if (xml[pos] == QLatin1Char('<')) {
0797                 state = ST_AFTER_OPEN;
0798             }
0799             break;
0800         case ST_AFTER_OPEN: {
0801             // Jump to gui..
0802             const int guipos = xml.indexOf(QLatin1String("gui"), pos, Qt::CaseInsensitive);
0803             if (guipos == -1) {
0804                 return QString(); // Reject
0805             }
0806 
0807             pos = guipos + 2; // Position at i, so we're moved ahead to the next character by the ++;
0808             state = ST_AFTER_GUI;
0809             break;
0810         }
0811         case ST_AFTER_GUI:
0812             state = ST_EXPECT_VERSION;
0813             break;
0814         case ST_EXPECT_VERSION: {
0815             const int verpos = xml.indexOf(QLatin1String("version"), pos, Qt::CaseInsensitive);
0816             if (verpos == -1) {
0817                 return QString(); // Reject
0818             }
0819             pos = verpos + 7; // strlen("version") is 7
0820             while (xml.at(pos).isSpace()) {
0821                 ++pos;
0822             }
0823             if (xml.at(pos++) != QLatin1Char('=')) {
0824                 return QString(); // Reject
0825             }
0826             while (xml.at(pos).isSpace()) {
0827                 ++pos;
0828             }
0829 
0830             state = ST_VERSION_NUM;
0831             break;
0832         }
0833         case ST_VERSION_NUM: {
0834             int endpos;
0835             for (endpos = pos; endpos < length; endpos++) {
0836                 const ushort ch = xml[endpos].unicode();
0837                 if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) {
0838                     continue; // Number..
0839                 }
0840                 if (ch == QLatin1Char('"')) { // End of parameter
0841                     break;
0842                 } else { // This shouldn't be here..
0843                     endpos = length;
0844                 }
0845             }
0846 
0847             if (endpos != pos && endpos < length) {
0848                 const QString matchCandidate = xml.mid(pos, endpos - pos); // Don't include " ".
0849                 return matchCandidate;
0850             }
0851 
0852             state = ST_EXPECT_VERSION; // Try to match a well-formed version..
0853             break;
0854         } // case..
0855         } // switch
0856     } // for
0857 
0858     return QString();
0859 }