KXmlGui

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

KDE's Doxygen guidelines are available online.