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