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

source code of kxmlgui/src/kxmlguiclient.cpp