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

source code of kxmlgui/src/kxmlguiclient.cpp