File indexing completed on 2024-09-08 12:23:26
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 }