1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 1999, 2000 Simon Hausmann <hausmann@kde.org>
4     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "config-xmlgui.h"
10 
11 #include "kxmlguifactory.h"
12 
13 #include "debug.h"
14 #include "kactioncollection.h"
15 #include "kshortcutschemeshelper_p.h"
16 #include "kshortcutsdialog.h"
17 #include "kxmlguibuilder.h"
18 #include "kxmlguiclient.h"
19 #include "kxmlguifactory_p.h"
20 
21 #include <QAction>
22 #include <QCoreApplication>
23 #include <QDir>
24 #include <QDomDocument>
25 #include <QFile>
26 #include <QStandardPaths>
27 #include <QTextCodec>
28 #include <QTextStream>
29 #include <QVariant>
30 #include <QWidget>
31 
32 #include <KConfigGroup>
33 #include <KSharedConfig>
34 #if HAVE_GLOBALACCEL
35 #include <KGlobalAccel>
36 #endif
37 
38 Q_DECLARE_METATYPE(QList<QKeySequence>)
39 
40 using namespace KXMLGUI;
41 
42 class KXMLGUIFactoryPrivate : public BuildState
43 {
44 public:
45     enum ShortcutOption { SetActiveShortcut = 1, SetDefaultShortcut = 2 };
46 
KXMLGUIFactoryPrivate()47     KXMLGUIFactoryPrivate()
48     {
49         m_rootNode = new ContainerNode(nullptr, QString(), QString());
50         attrName = QStringLiteral("name");
51     }
~KXMLGUIFactoryPrivate()52     ~KXMLGUIFactoryPrivate()
53     {
54         delete m_rootNode;
55     }
56 
pushState()57     void pushState()
58     {
59         m_stateStack.push(*this);
60     }
61 
popState()62     void popState()
63     {
64         BuildState::operator=(m_stateStack.pop());
65     }
66 
emptyState() const67     bool emptyState() const
68     {
69         return m_stateStack.isEmpty();
70     }
71 
72     QWidget *findRecursive(KXMLGUI::ContainerNode *node, bool tag);
73     QList<QWidget *> findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName);
74     void applyActionProperties(const QDomElement &element, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
75     void configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
76     void configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
77 
78     void applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList<QAction *> &actions);
79     void refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc);
80     void saveDefaultActionProperties(const QList<QAction *> &actions);
81 
82     ContainerNode *m_rootNode;
83 
84     /*
85      * Contains the container which is searched for in ::container .
86      */
87     QString m_containerName;
88 
89     /*
90      * List of all clients
91      */
92     QList<KXMLGUIClient *> m_clients;
93 
94     QString attrName;
95 
96     BuildStateStack m_stateStack;
97 };
98 
readConfigFile(const QString & filename,const QString & _componentName)99 QString KXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName)
100 {
101     QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
102     QString xml_file;
103 
104     if (!QDir::isRelativePath(filename)) {
105         xml_file = filename;
106     } else {
107         // KF >= 5.1 (KDE_INSTALL_KXMLGUI5DIR)
108         xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kxmlgui5/") + componentName + QLatin1Char('/') + filename);
109         if (!QFile::exists(xml_file)) {
110             // KF >= 5.4 (resource file)
111             xml_file = QLatin1String(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
112         }
113 
114         bool warn = false;
115         if (!QFile::exists(xml_file)) {
116             // kdelibs4 / KF 5.0 solution
117             xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, componentName + QLatin1Char('/') + filename);
118             warn = true;
119         }
120 
121         if (!QFile::exists(xml_file)) {
122             // kdelibs4 / KF 5.0 solution, and the caller includes the component name
123             // This was broken (lead to component/component/ in kdehome) and unnecessary
124             // (they can specify it with setComponentName instead)
125             xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, filename);
126             warn = true;
127         }
128 
129         if (warn && !xml_file.isEmpty()) {
130             qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << xml_file
131                                      << "-- please use ${KDE_INSTALL_KXMLGUI5DIR} to install these files instead.";
132         }
133     }
134 
135     QFile file(xml_file);
136     if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) {
137         qCCritical(DEBUG_KXMLGUI) << "No such XML file" << filename;
138         return QString();
139     }
140 
141     QByteArray buffer(file.readAll());
142     return QString::fromUtf8(buffer.constData(), buffer.size());
143 }
144 
saveConfigFile(const QDomDocument & doc,const QString & filename,const QString & _componentName)145 bool KXMLGUIFactory::saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &_componentName)
146 {
147     QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
148     QString xml_file(filename);
149 
150     if (QDir::isRelativePath(xml_file)) {
151         xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/%1/%2").arg(componentName, filename);
152     }
153 
154     QFileInfo fileInfo(xml_file);
155     QDir().mkpath(fileInfo.absolutePath());
156     QFile file(xml_file);
157     if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) {
158         qCCritical(DEBUG_KXMLGUI) << "Could not write to" << filename;
159         return false;
160     }
161 
162     // write out our document
163     QTextStream ts(&file);
164     ts.setCodec(QTextCodec::codecForName("UTF-8"));
165     ts << doc;
166 
167     file.close();
168     return true;
169 }
170 
KXMLGUIFactory(KXMLGUIBuilder * builder,QObject * parent)171 KXMLGUIFactory::KXMLGUIFactory(KXMLGUIBuilder *builder, QObject *parent)
172     : QObject(parent)
173     , d(new KXMLGUIFactoryPrivate)
174 {
175     Q_INIT_RESOURCE(kxmlgui);
176 
177     d->builder = builder;
178     d->guiClient = nullptr;
179     if (d->builder) {
180         d->builderContainerTags = d->builder->containerTags();
181         d->builderCustomTags = d->builder->customTags();
182     }
183 }
184 
~KXMLGUIFactory()185 KXMLGUIFactory::~KXMLGUIFactory()
186 {
187     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
188         client->setFactory(nullptr);
189     }
190 }
191 
addClient(KXMLGUIClient * client)192 void KXMLGUIFactory::addClient(KXMLGUIClient *client)
193 {
194     // qCDebug(DEBUG_KXMLGUI) << client;
195     if (client->factory()) {
196         if (client->factory() == this) {
197             return;
198         } else {
199             client->factory()->removeClient(client); // just in case someone does stupid things ;-)
200         }
201     }
202 
203     if (d->emptyState()) {
204         Q_EMIT makingChanges(true);
205     }
206     d->pushState();
207 
208     //    QTime dt; dt.start();
209 
210     d->guiClient = client;
211 
212     // add this client to our client list
213     if (!d->m_clients.contains(client)) {
214         d->m_clients.append(client);
215     }
216     // else
217     // qCDebug(DEBUG_KXMLGUI) << "XMLGUI client already added " << client;
218 
219     // Tell the client that plugging in is process and
220     //  let it know what builder widget its mainwindow shortcuts
221     //  should be attached to.
222     client->beginXMLPlug(d->builder->widget());
223 
224     // try to use the build document for building the client's GUI, as the build document
225     // contains the correct container state information (like toolbar positions, sizes, etc.) .
226     // if there is non available, then use the "real" document.
227     QDomDocument doc = client->xmlguiBuildDocument();
228     if (doc.documentElement().isNull()) {
229         doc = client->domDocument();
230     }
231 
232     QDomElement docElement = doc.documentElement();
233 
234     d->m_rootNode->index = -1;
235 
236     // cache some variables
237 
238     d->clientName = docElement.attribute(d->attrName);
239     d->clientBuilder = client->clientBuilder();
240 
241     if (d->clientBuilder) {
242         d->clientBuilderContainerTags = d->clientBuilder->containerTags();
243         d->clientBuilderCustomTags = d->clientBuilder->customTags();
244     } else {
245         d->clientBuilderContainerTags.clear();
246         d->clientBuilderCustomTags.clear();
247     }
248 
249     // load shortcut schemes, user-defined shortcuts and other action properties
250     d->saveDefaultActionProperties(client->actionCollection()->actions());
251     if (!doc.isNull()) {
252         d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
253     }
254 
255     BuildHelper(*d, d->m_rootNode).build(docElement);
256 
257     // let the client know that we built its GUI.
258     client->setFactory(this);
259 
260     // call the finalizeGUI method, to fix up the positions of toolbars for example.
261     // Note: the client argument is ignored
262     d->builder->finalizeGUI(d->guiClient);
263 
264     // reset some variables, for safety
265     d->BuildState::reset();
266 
267     client->endXMLPlug();
268 
269     d->popState();
270 
271     Q_EMIT clientAdded(client);
272 
273     // build child clients
274     const auto children = client->childClients();
275     for (KXMLGUIClient *child : children) {
276         addClient(child);
277     }
278 
279     if (d->emptyState()) {
280         Q_EMIT makingChanges(false);
281     }
282     /*
283         QString unaddedActions;
284         Q_FOREACH (KActionCollection* ac, KActionCollection::allCollections())
285           Q_FOREACH (QAction* action, ac->actions())
286             if (action->associatedWidgets().isEmpty())
287               unaddedActions += action->objectName() + ' ';
288 
289         if (!unaddedActions.isEmpty())
290           qCWarning(DEBUG_KXMLGUI) << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions;
291     */
292 
293     //    qCDebug(DEBUG_KXMLGUI) << "addClient took " << dt.elapsed();
294 }
295 
refreshActionProperties()296 void KXMLGUIFactory::refreshActionProperties()
297 {
298     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
299         d->guiClient = client;
300         QDomDocument doc = client->xmlguiBuildDocument();
301         if (doc.documentElement().isNull()) {
302             client->reloadXML();
303             doc = client->domDocument();
304         }
305         d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
306     }
307     d->guiClient = nullptr;
308 }
309 
currentShortcutScheme()310 static QString currentShortcutScheme()
311 {
312     const KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes");
313     return cg.readEntry("Current Scheme", "Default");
314 }
315 
316 // Find the right ActionProperties element, otherwise return null element
findActionPropertiesElement(const QDomDocument & doc)317 static QDomElement findActionPropertiesElement(const QDomDocument &doc)
318 {
319     const QLatin1String tagActionProp("ActionProperties");
320     const QString schemeName = currentShortcutScheme();
321     QDomElement e = doc.documentElement().firstChildElement();
322     for (; !e.isNull(); e = e.nextSiblingElement()) {
323         if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0
324             && (e.attribute(QStringLiteral("scheme"), QStringLiteral("Default")) == schemeName)) {
325             return e;
326         }
327     }
328     return QDomElement();
329 }
330 
refreshActionProperties(KXMLGUIClient * client,const QList<QAction * > & actions,const QDomDocument & doc)331 void KXMLGUIFactoryPrivate::refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc)
332 {
333     // try to find and apply shortcuts schemes
334     const QString schemeName = KShortcutSchemesHelper::currentShortcutSchemeName();
335     // qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": applying shortcut scheme" << schemeName;
336 
337     if (schemeName != QLatin1String("Default")) {
338         applyShortcutScheme(schemeName, client, actions);
339     } else {
340         // apply saved default shortcuts
341         for (QAction *action : actions) {
342             QVariant savedDefaultShortcut = action->property("_k_DefaultShortcut");
343             if (savedDefaultShortcut.isValid()) {
344                 QList<QKeySequence> shortcut = savedDefaultShortcut.value<QList<QKeySequence>>();
345                 action->setShortcuts(shortcut);
346                 action->setProperty("defaultShortcuts", QVariant::fromValue(shortcut));
347                 // qCDebug(DEBUG_KXMLGUI) << "scheme said" << action->shortcut().toString() << "for action" << action->objectName();
348             } else {
349                 action->setShortcuts(QList<QKeySequence>());
350             }
351         }
352     }
353 
354     // try to find and apply user-defined shortcuts
355     const QDomElement actionPropElement = findActionPropertiesElement(doc);
356     if (!actionPropElement.isNull()) {
357         applyActionProperties(actionPropElement);
358     }
359 }
360 
saveDefaultActionProperties(const QList<QAction * > & actions)361 void KXMLGUIFactoryPrivate::saveDefaultActionProperties(const QList<QAction *> &actions)
362 {
363     // This method is called every time the user activated a new
364     // kxmlguiclient. We only want to execute the following code only once in
365     // the lifetime of an action.
366     for (QAction *action : actions) {
367         // Skip nullptr actions or those we have seen already.
368         if (!action || action->property("_k_DefaultShortcut").isValid()) {
369             continue;
370         }
371 
372         // Check if the default shortcut is set
373         QList<QKeySequence> defaultShortcut = action->property("defaultShortcuts").value<QList<QKeySequence>>();
374         QList<QKeySequence> activeShortcut = action->shortcuts();
375         // qCDebug(DEBUG_KXMLGUI) << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString();
376 
377         // Check if we have an empty default shortcut and an non empty
378         // custom shortcut. Print out a warning and correct the mistake.
379         if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) {
380             qCCritical(DEBUG_KXMLGUI) << "Shortcut for action " << action->objectName() << action->text()
381                                       << "set with QAction::setShortcut()! Use KActionCollection::setDefaultShortcut(s) instead.";
382             action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut));
383         } else {
384             action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut));
385         }
386     }
387 }
388 
changeShortcutScheme(const QString & scheme)389 void KXMLGUIFactory::changeShortcutScheme(const QString &scheme)
390 {
391     qCDebug(DEBUG_KXMLGUI) << "Changing shortcut scheme to" << scheme;
392     KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes");
393     cg.writeEntry("Current Scheme", scheme);
394 
395     refreshActionProperties();
396 }
397 
forgetClient(KXMLGUIClient * client)398 void KXMLGUIFactory::forgetClient(KXMLGUIClient *client)
399 {
400     d->m_clients.removeAll(client);
401 }
402 
removeClient(KXMLGUIClient * client)403 void KXMLGUIFactory::removeClient(KXMLGUIClient *client)
404 {
405     // qCDebug(DEBUG_KXMLGUI) << client;
406 
407     // don't try to remove the client's GUI if we didn't build it
408     if (!client || client->factory() != this) {
409         return;
410     }
411 
412     if (d->emptyState()) {
413         Q_EMIT makingChanges(true);
414     }
415 
416     // remove this client from our client list
417     d->m_clients.removeAll(client);
418 
419     // remove child clients first (create a copy of the list just in case the
420     // original list is modified directly or indirectly in removeClient())
421     const QList<KXMLGUIClient *> childClients(client->childClients());
422     for (KXMLGUIClient *child : childClients) {
423         removeClient(child);
424     }
425 
426     // qCDebug(DEBUG_KXMLGUI) << "calling removeRecursive";
427 
428     d->pushState();
429 
430     // cache some variables
431 
432     d->guiClient = client;
433     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
434     d->clientBuilder = client->clientBuilder();
435 
436     client->setFactory(nullptr);
437 
438     // if we don't have a build document for that client, yet, then create one by
439     // cloning the original document, so that saving container information in the
440     // DOM tree does not touch the original document.
441     QDomDocument doc = client->xmlguiBuildDocument();
442     if (doc.documentElement().isNull()) {
443         doc = client->domDocument().cloneNode(true).toDocument();
444         client->setXMLGUIBuildDocument(doc);
445     }
446 
447     d->m_rootNode->destruct(doc.documentElement(), *d);
448 
449     // reset some variables
450     d->BuildState::reset();
451 
452     // This will destruct the KAccel object built around the given widget.
453     client->prepareXMLUnplug(d->builder->widget());
454 
455     d->popState();
456 
457     if (d->emptyState()) {
458         Q_EMIT makingChanges(false);
459     }
460 
461     Q_EMIT clientRemoved(client);
462 }
463 
clients() const464 QList<KXMLGUIClient *> KXMLGUIFactory::clients() const
465 {
466     return d->m_clients;
467 }
468 
container(const QString & containerName,KXMLGUIClient * client,bool useTagName)469 QWidget *KXMLGUIFactory::container(const QString &containerName, KXMLGUIClient *client, bool useTagName)
470 {
471     d->pushState();
472     d->m_containerName = containerName;
473     d->guiClient = client;
474 
475     QWidget *result = d->findRecursive(d->m_rootNode, useTagName);
476 
477     d->guiClient = nullptr;
478     d->m_containerName.clear();
479 
480     d->popState();
481 
482     return result;
483 }
484 
containers(const QString & tagName)485 QList<QWidget *> KXMLGUIFactory::containers(const QString &tagName)
486 {
487     return d->findRecursive(d->m_rootNode, tagName);
488 }
489 
reset()490 void KXMLGUIFactory::reset()
491 {
492     d->m_rootNode->reset();
493 
494     d->m_rootNode->clearChildren();
495 }
496 
resetContainer(const QString & containerName,bool useTagName)497 void KXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName)
498 {
499     if (containerName.isEmpty()) {
500         return;
501     }
502 
503     ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName);
504     if (container && container->parent) {
505         container->parent->removeChild(container);
506     }
507 }
508 
findRecursive(KXMLGUI::ContainerNode * node,bool tag)509 QWidget *KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, bool tag)
510 {
511     if (((!tag && node->name == m_containerName) || (tag && node->tagName == m_containerName)) //
512         && (!guiClient || node->client == guiClient)) {
513         return node->container;
514     }
515 
516     for (ContainerNode *child : std::as_const(node->children)) {
517         QWidget *cont = findRecursive(child, tag);
518         if (cont) {
519             return cont;
520         }
521     }
522 
523     return nullptr;
524 }
525 
526 // Case insensitive equality without calling toLower which allocates a new string
equals(const QString & str1,const char * str2)527 static inline bool equals(const QString &str1, const char *str2)
528 {
529     return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0;
530 }
equals(const QString & str1,const QString & str2)531 static inline bool equals(const QString &str1, const QString &str2)
532 {
533     return str1.compare(str2, Qt::CaseInsensitive) == 0;
534 }
535 
findRecursive(KXMLGUI::ContainerNode * node,const QString & tagName)536 QList<QWidget *> KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName)
537 {
538     QList<QWidget *> res;
539 
540     if (equals(node->tagName, tagName)) {
541         res.append(node->container);
542     }
543 
544     for (KXMLGUI::ContainerNode *child : std::as_const(node->children)) {
545         res << findRecursive(child, tagName);
546     }
547 
548     return res;
549 }
550 
plugActionList(KXMLGUIClient * client,const QString & name,const QList<QAction * > & actionList)551 void KXMLGUIFactory::plugActionList(KXMLGUIClient *client, const QString &name, const QList<QAction *> &actionList)
552 {
553     d->pushState();
554     d->guiClient = client;
555     d->actionListName = name;
556     d->actionList = actionList;
557     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
558 
559     d->m_rootNode->plugActionList(*d);
560 
561     // Load shortcuts for these new actions
562     d->saveDefaultActionProperties(actionList);
563     d->refreshActionProperties(client, actionList, client->domDocument());
564 
565     d->BuildState::reset();
566     d->popState();
567 }
568 
unplugActionList(KXMLGUIClient * client,const QString & name)569 void KXMLGUIFactory::unplugActionList(KXMLGUIClient *client, const QString &name)
570 {
571     d->pushState();
572     d->guiClient = client;
573     d->actionListName = name;
574     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
575 
576     d->m_rootNode->unplugActionList(*d);
577 
578     d->BuildState::reset();
579     d->popState();
580 }
581 
applyActionProperties(const QDomElement & actionPropElement,ShortcutOption shortcutOption)582 void KXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement, ShortcutOption shortcutOption)
583 {
584     for (QDomElement e = actionPropElement.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
585         if (!equals(e.tagName(), "action")) {
586             continue;
587         }
588 
589         QAction *action = guiClient->action(e);
590         if (!action) {
591             continue;
592         }
593 
594         configureAction(action, e.attributes(), shortcutOption);
595     }
596 }
597 
configureAction(QAction * action,const QDomNamedNodeMap & attributes,ShortcutOption shortcutOption)598 void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption)
599 {
600     for (int i = 0; i < attributes.length(); i++) {
601         QDomAttr attr = attributes.item(i).toAttr();
602         if (attr.isNull()) {
603             continue;
604         }
605 
606         configureAction(action, attr, shortcutOption);
607     }
608 }
609 
configureAction(QAction * action,const QDomAttr & attribute,ShortcutOption shortcutOption)610 void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption)
611 {
612     QString attrName = attribute.name();
613     // If the attribute is a deprecated "accel", change to "shortcut".
614     if (equals(attrName, "accel")) {
615         attrName = QStringLiteral("shortcut");
616     }
617 
618     // No need to re-set name, particularly since it's "objectName" in Qt4
619     if (equals(attrName, "name")) {
620         return;
621     }
622 
623     if (equals(attrName, "icon")) {
624         action->setIcon(QIcon::fromTheme(attribute.value()));
625         return;
626     }
627 
628     QVariant propertyValue;
629 
630     QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type();
631     bool isShortcut = (propertyType == QVariant::KeySequence);
632 
633     if (propertyType == QVariant::Int) {
634         propertyValue = QVariant(attribute.value().toInt());
635     } else if (propertyType == QVariant::UInt) {
636         propertyValue = QVariant(attribute.value().toUInt());
637     } else if (isShortcut) {
638         // Setting the shortcut by property also sets the default shortcut (which is incorrect), so we have to do it directly
639         if (attrName == QLatin1String("globalShortcut")) {
640 #if HAVE_GLOBALACCEL
641             KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(attribute.value()));
642 #endif
643         } else {
644             action->setShortcuts(QKeySequence::listFromString(attribute.value()));
645         }
646         if (shortcutOption & KXMLGUIFactoryPrivate::SetDefaultShortcut) {
647             action->setProperty("defaultShortcuts", QVariant::fromValue(QKeySequence::listFromString(attribute.value())));
648         }
649     } else {
650         propertyValue = QVariant(attribute.value());
651     }
652     if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) {
653         qCWarning(DEBUG_KXMLGUI) << "Error: Unknown action property " << attrName << " will be ignored!";
654     }
655 }
656 
applyShortcutScheme(const QString & schemeName,KXMLGUIClient * client,const QList<QAction * > & actions)657 void KXMLGUIFactoryPrivate::applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList<QAction *> &actions)
658 {
659     // First clear all existing shortcuts
660     for (QAction *action : actions) {
661         action->setShortcuts(QList<QKeySequence>());
662         // We clear the default shortcut as well because the shortcut scheme will set its own defaults
663         action->setProperty("defaultShortcuts", QVariant::fromValue(QList<QKeySequence>()));
664     }
665 
666     // Find the document for the shortcut scheme using the current application path.
667     // This allows to install a single XML file for a shortcut scheme for kdevelop
668     // rather than 10.
669     // Also look for the current xmlguiclient path.
670     // Per component xml files make sense for making kmail shortcuts available in kontact.
671     QString schemeFileName = KShortcutSchemesHelper::shortcutSchemeFileName(client->componentName(), schemeName);
672     if (schemeFileName.isEmpty()) {
673         schemeFileName = KShortcutSchemesHelper::applicationShortcutSchemeFileName(schemeName);
674     }
675     if (schemeFileName.isEmpty()) {
676         qCWarning(DEBUG_KXMLGUI) << client->componentName() << ": shortcut scheme file not found:" << schemeName << "after trying"
677                                  << QCoreApplication::applicationName() << "and" << client->componentName();
678         return;
679     }
680 
681     QDomDocument scheme;
682     QFile schemeFile(schemeFileName);
683     if (schemeFile.open(QIODevice::ReadOnly)) {
684         qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": found shortcut scheme XML" << schemeFileName;
685         scheme.setContent(&schemeFile);
686     }
687 
688     if (scheme.isNull()) {
689         return;
690     }
691 
692     QDomElement docElement = scheme.documentElement();
693     QDomElement actionPropElement = docElement.namedItem(QStringLiteral("ActionProperties")).toElement();
694 
695     // Check if we really have the shortcut configuration here
696     if (!actionPropElement.isNull()) {
697         // qCDebug(DEBUG_KXMLGUI) << "Applying shortcut scheme for XMLGUI client" << client->componentName();
698 
699         // Apply all shortcuts we have
700         applyActionProperties(actionPropElement, KXMLGUIFactoryPrivate::SetDefaultShortcut);
701         //} else {
702         // qCDebug(DEBUG_KXMLGUI) << "Invalid shortcut scheme file";
703     }
704 }
705 
706 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 84)
configureShortcuts(bool letterCutsOk,bool bSaveSettings)707 int KXMLGUIFactory::configureShortcuts(bool letterCutsOk, bool bSaveSettings)
708 {
709     auto *dlg = new KShortcutsDialog(KShortcutsEditor::AllActions,
710                                      letterCutsOk ? KShortcutsEditor::LetterShortcutsAllowed : KShortcutsEditor::LetterShortcutsDisallowed,
711                                      qobject_cast<QWidget *>(parent()));
712     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
713         if (client) {
714             qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions";
715             dlg->addCollection(client->actionCollection(), client->componentName());
716         }
717     }
718     connect(dlg, &KShortcutsDialog::saved, this, &KXMLGUIFactory::shortcutsSaved);
719     return dlg->configure(bSaveSettings);
720 }
721 #endif
722 
showConfigureShortcutsDialog()723 void KXMLGUIFactory::showConfigureShortcutsDialog()
724 {
725     auto *dlg = new KShortcutsDialog(qobject_cast<QWidget *>(parent()));
726     dlg->setAttribute(Qt::WA_DeleteOnClose);
727 
728     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
729         if (client) {
730             qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions";
731 
732             dlg->addCollection(client->actionCollection(), client->componentName());
733         }
734     }
735 
736     connect(dlg, &KShortcutsDialog::saved, this, &KXMLGUIFactory::shortcutsSaved);
737     dlg->configure(true /*save settings on accept*/);
738 }
739 
740 // Find or create
actionPropertiesElement(QDomDocument & doc)741 QDomElement KXMLGUIFactory::actionPropertiesElement(QDomDocument &doc)
742 {
743     // first, lets see if we have existing properties
744     QDomElement elem = findActionPropertiesElement(doc);
745 
746     // if there was none, create one
747     if (elem.isNull()) {
748         elem = doc.createElement(QStringLiteral("ActionProperties"));
749         elem.setAttribute(QStringLiteral("scheme"), currentShortcutScheme());
750         doc.documentElement().appendChild(elem);
751     }
752     return elem;
753 }
754 
findActionByName(QDomElement & elem,const QString & sName,bool create)755 QDomElement KXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create)
756 {
757     const QLatin1String attrName("name");
758     for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) {
759         QDomElement e = it.toElement();
760         if (e.attribute(attrName) == sName) {
761             return e;
762         }
763     }
764 
765     if (create) {
766         QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action"));
767         act_elem.setAttribute(attrName, sName);
768         elem.appendChild(act_elem);
769         return act_elem;
770     }
771     return QDomElement();
772 }
773