File indexing completed on 2024-09-15 03:42:01
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 }