1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
4 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
5 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
6 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
7 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
8 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
9 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
10 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
11 SPDX-FileCopyrightText: 2005-2007 Hamish Rodda <rodda@kde.org>
12
13 SPDX-License-Identifier: LGPL-2.0-only
14*/
15
16#include "config-xmlgui.h"
17
18#include "kactioncollection.h"
19
20#include "debug.h"
21#include "kactioncategory.h"
22#include "kxmlguiclient.h"
23#include "kxmlguifactory.h"
24
25#include <KAuthorized>
26#include <KConfigGroup>
27#if HAVE_GLOBALACCEL
28#include <KGlobalAccel>
29#endif
30#include <KSharedConfig>
31
32#include <QDomDocument>
33#include <QGuiApplication>
34#include <QList>
35#include <QMetaMethod>
36#include <QSet>
37
38static bool actionHasGlobalShortcut(const QAction *action)
39{
40#if HAVE_GLOBALACCEL
41 return KGlobalAccel::self()->hasShortcut(action);
42#else
43 Q_UNUSED(action)
44 return false;
45#endif
46}
47
48struct ActionStorage {
49 void addAction(const QString &name, QAction *action)
50 {
51 Q_ASSERT(std::find(m_actions.begin(), m_actions.end(), action) == m_actions.end());
52 m_actions.push_back(t: action);
53 m_names.push_back(t: name);
54 Q_ASSERT(m_names.size() == m_actions.size());
55 }
56
57 bool removeAction(QAction *action)
58 {
59 auto it = std::find(first: m_actions.begin(), last: m_actions.end(), val: action);
60 if (it != m_actions.end()) {
61 // We can't have the same action twice.
62 Q_ASSERT(std::find(it + 1, m_actions.end(), action) == m_actions.end());
63 auto idx = std::distance(first: m_actions.begin(), last: it);
64 m_names.erase(pos: m_names.begin() + idx);
65 m_actions.erase(pos: it);
66 return true;
67 }
68 Q_ASSERT(m_names.size() == m_actions.size());
69 return false;
70 }
71
72 QAction *findAction(const QString &name) const
73 {
74 auto it = std::find(first: m_names.begin(), last: m_names.end(), val: name);
75 if (it != m_names.end() && *it == name) {
76 return m_actions[std::distance(first: m_names.begin(), last: it)];
77 }
78 return nullptr;
79 }
80
81 void clear()
82 {
83 m_actions = {};
84 m_names = {};
85 }
86
87 int size() const
88 {
89 return m_actions.size();
90 }
91
92 template<typename F>
93 void foreachAction(F f)
94 {
95 Q_ASSERT(m_names.size() == m_actions.size());
96 for (int i = 0; i < m_actions.size(); ++i) {
97 f(m_names.at(i), m_actions.at(i));
98 }
99 }
100
101 const QList<QAction *> &actions() const
102 {
103 return m_actions;
104 }
105
106private:
107 // 1:1 list of names and actions
108 QList<QString> m_names;
109 QList<QAction *> m_actions;
110};
111
112class KActionCollectionPrivate
113{
114public:
115 KActionCollectionPrivate(KActionCollection *qq)
116 : q(qq)
117 , configIsGlobal(false)
118 , connectTriggered(false)
119 , connectHovered(false)
120
121 {
122 }
123
124 void setComponentForAction(QAction *action)
125 {
126 const bool hasGlobalShortcut = actionHasGlobalShortcut(action);
127 if (!hasGlobalShortcut) {
128 action->setProperty(name: "componentName", value: m_componentName);
129 action->setProperty(name: "componentDisplayName", value: m_componentDisplayName);
130 }
131 }
132
133 static QList<KActionCollection *> s_allCollections;
134
135 void _k_associatedWidgetDestroyed(QObject *obj);
136 void _k_actionDestroyed(QObject *obj);
137
138 bool writeKXMLGUIConfigFile();
139
140 QString m_componentName;
141 QString m_componentDisplayName;
142
143 //! Remove a action from our internal bookkeeping. Returns a nullptr if the
144 //! action doesn't belong to us.
145 QAction *unlistAction(QAction *);
146
147 ActionStorage actionStore;
148
149 KActionCollection *q = nullptr;
150
151 const KXMLGUIClient *m_parentGUIClient = nullptr;
152
153 QString configGroup{QStringLiteral("Shortcuts")};
154 bool configIsGlobal : 1;
155
156 bool connectTriggered : 1;
157 bool connectHovered : 1;
158
159 QList<QWidget *> associatedWidgets;
160};
161
162QList<KActionCollection *> KActionCollectionPrivate::s_allCollections;
163
164KActionCollection::KActionCollection(QObject *parent, const QString &cName)
165 : QObject(parent)
166 , d(new KActionCollectionPrivate(this))
167{
168 KActionCollectionPrivate::s_allCollections.append(t: this);
169
170 setComponentName(cName);
171}
172
173KActionCollection::KActionCollection(const KXMLGUIClient *parent)
174 : QObject(nullptr)
175 , d(new KActionCollectionPrivate(this))
176{
177 KActionCollectionPrivate::s_allCollections.append(t: this);
178
179 d->m_parentGUIClient = parent;
180 d->m_componentName = parent->componentName();
181}
182
183KActionCollection::~KActionCollection()
184{
185 KActionCollectionPrivate::s_allCollections.removeAll(t: this);
186}
187
188void KActionCollection::clear()
189{
190 const QList<QAction *> copy = d->actionStore.actions();
191 qDeleteAll(c: copy);
192 d->actionStore.clear();
193}
194
195QAction *KActionCollection::action(const QString &name) const
196{
197 if (!name.isEmpty()) {
198 return d->actionStore.findAction(name);
199 }
200 return nullptr;
201}
202
203QAction *KActionCollection::action(int index) const
204{
205 // ### investigate if any apps use this at all
206 return actions().value(i: index);
207}
208
209int KActionCollection::count() const
210{
211 return d->actionStore.size();
212}
213
214bool KActionCollection::isEmpty() const
215{
216 return count() == 0;
217}
218
219void KActionCollection::setComponentName(const QString &cName)
220{
221 for (auto *a : d->actionStore.actions()) {
222 if (actionHasGlobalShortcut(action: a)) {
223 // Its component name is part of an action's signature in the context of
224 // global shortcuts and the semantics of changing an existing action's
225 // signature are, as it seems, impossible to get right.
226 qCWarning(DEBUG_KXMLGUI) << "KActionCollection::setComponentName does not work on a KActionCollection containing actions with global shortcuts!"
227 << cName;
228 break;
229 }
230 }
231
232 if (!cName.isEmpty()) {
233 d->m_componentName = cName;
234 } else {
235 d->m_componentName = QCoreApplication::applicationName();
236 }
237}
238
239QString KActionCollection::componentName() const
240{
241 return d->m_componentName;
242}
243
244void KActionCollection::setComponentDisplayName(const QString &displayName)
245{
246 d->m_componentDisplayName = displayName;
247}
248
249QString KActionCollection::componentDisplayName() const
250{
251 if (!d->m_componentDisplayName.isEmpty()) {
252 return d->m_componentDisplayName;
253 }
254 if (!QGuiApplication::applicationDisplayName().isEmpty()) {
255 return QGuiApplication::applicationDisplayName();
256 }
257 return QCoreApplication::applicationName();
258}
259
260const KXMLGUIClient *KActionCollection::parentGUIClient() const
261{
262 return d->m_parentGUIClient;
263}
264
265QList<QAction *> KActionCollection::actions() const
266{
267 return d->actionStore.actions();
268}
269
270const QList<QAction *> KActionCollection::actionsWithoutGroup() const
271{
272 QList<QAction *> ret;
273 for (auto *action : std::as_const(t: d->actionStore.actions())) {
274 if (!action->actionGroup()) {
275 ret.append(t: action);
276 }
277 }
278 return ret;
279}
280
281const QList<QActionGroup *> KActionCollection::actionGroups() const
282{
283 QSet<QActionGroup *> set;
284 for (auto *action : std::as_const(t: d->actionStore.actions())) {
285 if (action->actionGroup()) {
286 set.insert(value: action->actionGroup());
287 }
288 }
289 return set.values();
290}
291
292QAction *KActionCollection::addAction(const QString &name, QAction *action)
293{
294 if (!action) {
295 return action;
296 }
297
298 const QString objectName = action->objectName();
299 QString indexName = name;
300
301 if (indexName.isEmpty()) {
302 // No name provided. Use the objectName.
303 indexName = objectName;
304
305 } else {
306 // A name was provided. Check against objectName.
307 if ((!objectName.isEmpty()) && (objectName != indexName)) {
308 // The user specified a new name and the action already has a
309 // different one. The objectName is used for saving shortcut
310 // settings to disk. Both for local and global shortcuts.
311 qCDebug(DEBUG_KXMLGUI) << "Registering action " << objectName << " under new name " << indexName;
312 // If there is a global shortcuts it's a very bad idea.
313#if HAVE_GLOBALACCEL
314 if (KGlobalAccel::self()->hasShortcut(action)) {
315 // In debug mode assert
316 Q_ASSERT(!KGlobalAccel::self()->hasShortcut(action));
317 // In release mode keep the old name
318 qCCritical(DEBUG_KXMLGUI) << "Changing action name from " << objectName << " to " << indexName
319 << "\nignored because of active global shortcut.";
320 indexName = objectName;
321 }
322#endif
323 }
324
325 // Set the new name
326 action->setObjectName(indexName);
327 }
328
329 // No name provided and the action had no name. Make one up. This will not
330 // work when trying to save shortcuts. Both local and global shortcuts.
331 if (indexName.isEmpty()) {
332 indexName = QString::asprintf(format: "unnamed-%p", (void *)action);
333 action->setObjectName(indexName);
334 }
335
336 // From now on the objectName has to have a value. Else we cannot safely
337 // remove actions.
338 Q_ASSERT(!action->objectName().isEmpty());
339
340 // look if we already have THIS action under THIS name ;)
341 auto oldAction = d->actionStore.findAction(name: indexName);
342 if (oldAction == action) {
343 return action;
344 }
345
346 if (!KAuthorized::authorizeAction(action: indexName)) {
347 // Disable this action
348 action->setEnabled(false);
349 action->setVisible(false);
350 action->blockSignals(b: true);
351 }
352
353 // Check if we have another action under this name
354 if (oldAction) {
355 takeAction(action: oldAction);
356 }
357
358 // Remove if we have this action under a different name.
359 // Not using takeAction because we don't want to remove it from categories,
360 // and because it has the new name already.
361 d->actionStore.removeAction(action);
362
363 // Add action to our lists.
364 d->actionStore.addAction(name: indexName, action);
365
366 for (QWidget *widget : std::as_const(t&: d->associatedWidgets)) {
367 widget->addAction(action);
368 }
369
370 connect(sender: action, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj) {
371 d->_k_actionDestroyed(obj);
372 });
373
374 d->setComponentForAction(action);
375
376 if (d->connectHovered) {
377 connect(sender: action, signal: &QAction::hovered, context: this, slot: &KActionCollection::slotActionHovered);
378 }
379
380 if (d->connectTriggered) {
381 connect(sender: action, signal: &QAction::triggered, context: this, slot: &KActionCollection::slotActionTriggered);
382 }
383
384 Q_EMIT inserted(action);
385 Q_EMIT changed();
386 return action;
387}
388
389void KActionCollection::addActions(const QList<QAction *> &actions)
390{
391 for (QAction *action : actions) {
392 addAction(name: action->objectName(), action);
393 }
394}
395
396void KActionCollection::removeAction(QAction *action)
397{
398 delete takeAction(action);
399}
400
401QAction *KActionCollection::takeAction(QAction *action)
402{
403 if (!d->unlistAction(action)) {
404 return nullptr;
405 }
406
407 // Remove the action from all widgets
408 for (QWidget *widget : std::as_const(t&: d->associatedWidgets)) {
409 widget->removeAction(action);
410 }
411
412 action->disconnect(receiver: this);
413
414 Q_EMIT changed();
415 return action;
416}
417
418QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
419{
420 QAction *action = KStandardAction::create(id: actionType, recvr: receiver, slot: member, parent: this);
421 return action;
422}
423
424QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver, const char *member)
425{
426 // pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically
427 // adds the action to it under the default name. We would trigger the
428 // warning about renaming the action then.
429 QAction *action = KStandardAction::create(id: actionType, recvr: receiver, slot: member, parent: nullptr);
430 // Give it a parent for gc.
431 action->setParent(this);
432 // Remove the name to get rid of the "rename action" warning above
433 action->setObjectName(name);
434 // And now add it with the desired name.
435 return addAction(name, action);
436}
437
438QAction *KActionCollection::addAction(KStandardActions::StandardAction actionType)
439{
440 // Use implementation from KConfigWidgets instead of KConfigGui
441 // as it provides tighter integration with QtWidgets applications.
442 // KStandardAction automatically adds it to the collection.
443 QAction *action = KStandardAction::create(id: static_cast<KStandardAction::StandardAction>(actionType), recvr: nullptr, slot: {}, parent: this);
444 return action;
445}
446
447QAction *KActionCollection::addAction(KStandardActions::StandardAction actionType, const QString &name)
448{
449 // Use implementation from KConfigWidgets instead of KConfigGui
450 // as it provides tighter integration with QtWidgets applications.
451 QAction *action = KStandardAction::create(id: static_cast<KStandardAction::StandardAction>(actionType), recvr: nullptr, slot: {}, parent: nullptr);
452 action->setParent(this);
453 action->setObjectName(name);
454 return addAction(name, action);
455}
456
457QAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member)
458{
459 QAction *a = new QAction(this);
460 if (receiver && member) {
461 connect(sender: a, SIGNAL(triggered(bool)), receiver, member);
462 }
463 return addAction(name, action: a);
464}
465
466QKeySequence KActionCollection::defaultShortcut(QAction *action)
467{
468 const QList<QKeySequence> shortcuts = defaultShortcuts(action);
469 return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
470}
471
472QList<QKeySequence> KActionCollection::defaultShortcuts(QAction *action)
473{
474 return action->property(name: "defaultShortcuts").value<QList<QKeySequence>>();
475}
476
477void KActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
478{
479 setDefaultShortcuts(action, shortcuts: QList<QKeySequence>() << shortcut);
480}
481
482void KActionCollection::setDefaultShortcuts(QAction *action, const QList<QKeySequence> &shortcuts)
483{
484 action->setShortcuts(shortcuts);
485 action->setProperty(name: "defaultShortcuts", value: QVariant::fromValue(value: shortcuts));
486}
487
488bool KActionCollection::isShortcutsConfigurable(QAction *action)
489{
490 // Considered as true by default
491 const QVariant value = action->property(name: "isShortcutConfigurable");
492 return value.isValid() ? value.toBool() : true;
493}
494
495void KActionCollection::setShortcutsConfigurable(QAction *action, bool configurable)
496{
497 action->setProperty(name: "isShortcutConfigurable", value: configurable);
498}
499
500QString KActionCollection::configGroup() const
501{
502 return d->configGroup;
503}
504
505void KActionCollection::setConfigGroup(const QString &group)
506{
507 d->configGroup = group;
508}
509
510bool KActionCollection::configIsGlobal() const
511{
512 return d->configIsGlobal;
513}
514
515void KActionCollection::setConfigGlobal(bool global)
516{
517 d->configIsGlobal = global;
518}
519
520void KActionCollection::importGlobalShortcuts(KConfigGroup *config)
521{
522#if HAVE_GLOBALACCEL
523 Q_ASSERT(config);
524 if (!config || !config->exists()) {
525 return;
526 }
527
528 d->actionStore.foreachAction(f: [config](const QString &actionName, QAction *action) {
529 if (!action) {
530 return;
531 }
532
533 if (isShortcutsConfigurable(action)) {
534 QString entry = config->readEntry(key: actionName, aDefault: QString());
535 if (!entry.isEmpty()) {
536 KGlobalAccel::self()->setShortcut(action, shortcut: QKeySequence::listFromString(str: entry), loadFlag: KGlobalAccel::NoAutoloading);
537 } else {
538 QList<QKeySequence> defaultShortcut = KGlobalAccel::self()->defaultShortcut(action);
539 KGlobalAccel::self()->setShortcut(action, shortcut: defaultShortcut, loadFlag: KGlobalAccel::NoAutoloading);
540 }
541 }
542 });
543#else
544 Q_UNUSED(config);
545#endif
546}
547
548void KActionCollection::readSettings(KConfigGroup *config)
549{
550 KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
551 if (!config) {
552 config = &cg;
553 }
554
555 if (!config->exists()) {
556 return;
557 }
558
559 d->actionStore.foreachAction(f: [config](const QString &actionName, QAction *action) {
560 if (!action) {
561 return;
562 }
563
564 if (isShortcutsConfigurable(action)) {
565 QString entry = config->readEntry(key: actionName, aDefault: QString());
566 if (!entry.isEmpty()) {
567 action->setShortcuts(QKeySequence::listFromString(str: entry));
568 } else {
569 action->setShortcuts(defaultShortcuts(action));
570 }
571 }
572 });
573
574 // qCDebug(DEBUG_KXMLGUI) << " done";
575}
576
577void KActionCollection::exportGlobalShortcuts(KConfigGroup *config, bool writeAll) const
578{
579#if HAVE_GLOBALACCEL
580 Q_ASSERT(config);
581 if (!config) {
582 return;
583 }
584
585 d->actionStore.foreachAction(f: [config, this, writeAll](const QString &actionName, QAction *action) {
586 if (!action) {
587 return;
588 }
589 // If the action name starts with unnamed- spit out a warning. That name
590 // will change at will and will break loading writing
591 if (actionName.startsWith(s: QLatin1String("unnamed-"))) {
592 qCCritical(DEBUG_KXMLGUI) << "Skipped exporting Shortcut for action without name " << action->text() << "!";
593 return;
594 }
595
596 if (isShortcutsConfigurable(action) && KGlobalAccel::self()->hasShortcut(action)) {
597 bool bConfigHasAction = !config->readEntry(key: actionName, aDefault: QString()).isEmpty();
598 bool bSameAsDefault = (KGlobalAccel::self()->shortcut(action) == KGlobalAccel::self()->defaultShortcut(action));
599 // If we're using a global config or this setting
600 // differs from the default, then we want to write.
601 KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
602 if (configIsGlobal()) {
603 flags |= KConfigGroup::Global;
604 }
605 if (writeAll || !bSameAsDefault) {
606 QString s = QKeySequence::listToString(list: KGlobalAccel::self()->shortcut(action));
607 if (s.isEmpty()) {
608 s = QStringLiteral("none");
609 }
610 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
611 config->writeEntry(key: actionName, value: s, pFlags: flags);
612 }
613 // Otherwise, this key is the same as default
614 // but exists in config file. Remove it.
615 else if (bConfigHasAction) {
616 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
617 config->deleteEntry(pKey: actionName, pFlags: flags);
618 }
619 }
620 });
621
622 config->sync();
623#else
624 Q_UNUSED(config);
625 Q_UNUSED(writeAll);
626#endif
627}
628
629bool KActionCollectionPrivate::writeKXMLGUIConfigFile()
630{
631 const KXMLGUIClient *kxmlguiClient = q->parentGUIClient();
632 // return false if there is no KXMLGUIClient
633 if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) {
634 return false;
635 }
636
637 qCDebug(DEBUG_KXMLGUI) << "xmlFile=" << kxmlguiClient->xmlFile();
638
639 QString attrShortcut = QStringLiteral("shortcut");
640
641 // Read XML file
642 QString sXml(KXMLGUIFactory::readConfigFile(filename: kxmlguiClient->xmlFile(), componentName: q->componentName()));
643 QDomDocument doc;
644 doc.setContent(data: sXml);
645
646 // Process XML data
647
648 // Get hold of ActionProperties tag
649 QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc);
650
651 // now, iterate through our actions
652 actionStore.foreachAction(f: [&elem, &attrShortcut, this](const QString &actionName, QAction *action) {
653 if (!action) {
654 return;
655 }
656
657 // If the action name starts with unnamed- spit out a warning and ignore
658 // it. That name will change at will and will break loading writing
659 if (actionName.startsWith(s: QLatin1String("unnamed-"))) {
660 qCCritical(DEBUG_KXMLGUI) << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!";
661 return;
662 }
663
664 bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action));
665 qCDebug(DEBUG_KXMLGUI) << "name = " << actionName << " shortcut = " << QKeySequence::listToString(list: action->shortcuts())
666#if HAVE_GLOBALACCEL
667 << " globalshortcut = " << QKeySequence::listToString(list: KGlobalAccel::self()->shortcut(action))
668#endif
669 << " def = " << QKeySequence::listToString(list: q->defaultShortcuts(action));
670
671 // now see if this element already exists
672 // and create it if necessary (unless bSameAsDefault)
673 QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, sName: actionName, create: !bSameAsDefault);
674 if (act_elem.isNull()) {
675 return;
676 }
677
678 if (bSameAsDefault) {
679 act_elem.removeAttribute(name: attrShortcut);
680 // qCDebug(DEBUG_KXMLGUI) << "act_elem.attributes().count() = " << act_elem.attributes().count();
681 if (act_elem.attributes().count() == 1) {
682 elem.removeChild(oldChild: act_elem);
683 }
684 } else {
685 act_elem.setAttribute(name: attrShortcut, value: QKeySequence::listToString(list: action->shortcuts()));
686 }
687 });
688
689 // Write back to XML file
690 KXMLGUIFactory::saveConfigFile(doc, filename: kxmlguiClient->localXMLFile(), componentName: q->componentName());
691 // and since we just changed the xml file clear the dom we have in memory
692 // it'll be rebuilt if needed
693 const_cast<KXMLGUIClient *>(kxmlguiClient)->setXMLGUIBuildDocument({});
694 return true;
695}
696
697void KActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const
698{
699 // If the caller didn't provide a config group we try to save the KXMLGUI
700 // Configuration file. If that succeeds we are finished.
701 if (config == nullptr && d->writeKXMLGUIConfigFile()) {
702 return;
703 }
704
705 KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
706 if (!config) {
707 config = &cg;
708 }
709
710 QList<QAction *> writeActions;
711 if (oneAction) {
712 writeActions.append(t: oneAction);
713 } else {
714 writeActions = actions();
715 }
716
717 d->actionStore.foreachAction(f: [config, this, writeAll](const QString &actionName, QAction *action) {
718 if (!action) {
719 return;
720 }
721
722 // If the action name starts with unnamed- spit out a warning and ignore
723 // it. That name will change at will and will break loading writing
724 if (actionName.startsWith(s: QLatin1String("unnamed-"))) {
725 qCCritical(DEBUG_KXMLGUI) << "Skipped saving Shortcut for action without name " << action->text() << "!";
726 return;
727 }
728
729 // Write the shortcut
730 if (isShortcutsConfigurable(action)) {
731 bool bConfigHasAction = !config->readEntry(key: actionName, aDefault: QString()).isEmpty();
732 bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action));
733 // If we're using a global config or this setting
734 // differs from the default, then we want to write.
735 KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
736
737 // Honor the configIsGlobal() setting
738 if (configIsGlobal()) {
739 flags |= KConfigGroup::Global;
740 }
741
742 if (writeAll || !bSameAsDefault) {
743 // We are instructed to write all shortcuts or the shortcut is
744 // not set to its default value. Write it
745 QString s = QKeySequence::listToString(list: action->shortcuts());
746 if (s.isEmpty()) {
747 s = QStringLiteral("none");
748 }
749 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
750 config->writeEntry(key: actionName, value: s, pFlags: flags);
751
752 } else if (bConfigHasAction) {
753 // Otherwise, this key is the same as default but exists in
754 // config file. Remove it.
755 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
756 config->deleteEntry(pKey: actionName, pFlags: flags);
757 }
758 }
759 });
760
761 config->sync();
762}
763
764void KActionCollection::slotActionTriggered()
765{
766 QAction *action = qobject_cast<QAction *>(object: sender());
767 if (action) {
768 Q_EMIT actionTriggered(action);
769 }
770}
771
772void KActionCollection::slotActionHovered()
773{
774 QAction *action = qobject_cast<QAction *>(object: sender());
775 if (action) {
776 Q_EMIT actionHovered(action);
777 }
778}
779
780// The downcast from a QObject to a QAction triggers UBSan
781// but we're only comparing pointers, so UBSan shouldn't check vptrs
782// Similar to https://github.com/itsBelinda/plog/pull/1/files
783#if defined(__clang__) || __GNUC__ >= 8
784__attribute__((no_sanitize("vptr")))
785#endif
786void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj)
787{
788 // obj isn't really a QAction anymore. So make sure we don't do fancy stuff
789 // with it.
790 QAction *action = static_cast<QAction *>(obj);
791
792 if (!unlistAction(action)) {
793 return;
794 }
795
796 Q_EMIT q->changed();
797}
798
799void KActionCollection::connectNotify(const QMetaMethod &signal)
800{
801 if (d->connectHovered && d->connectTriggered) {
802 return;
803 }
804
805 if (signal.methodSignature() == "actionHovered(QAction*)") {
806 if (!d->connectHovered) {
807 d->connectHovered = true;
808
809 for (auto *action : d->actionStore.actions()) {
810 connect(sender: action, signal: &QAction::hovered, context: this, slot: &KActionCollection::slotActionHovered);
811 }
812 }
813
814 } else if (signal.methodSignature() == "actionTriggered(QAction*)") {
815 if (!d->connectTriggered) {
816 d->connectTriggered = true;
817 for (auto *action : d->actionStore.actions()) {
818 connect(sender: action, signal: &QAction::triggered, context: this, slot: &KActionCollection::slotActionTriggered);
819 }
820 }
821 }
822
823 QObject::connectNotify(signal);
824}
825
826const QList<KActionCollection *> &KActionCollection::allCollections()
827{
828 return KActionCollectionPrivate::s_allCollections;
829}
830
831void KActionCollection::associateWidget(QWidget *widget) const
832{
833 for (auto *action : d->actionStore.actions()) {
834 if (!widget->actions().contains(t: action)) {
835 widget->addAction(action);
836 }
837 }
838}
839
840void KActionCollection::addAssociatedWidget(QWidget *widget)
841{
842 if (!d->associatedWidgets.contains(t: widget)) {
843 widget->addActions(actions: actions());
844
845 d->associatedWidgets.append(t: widget);
846 connect(sender: widget, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj) {
847 d->_k_associatedWidgetDestroyed(obj);
848 });
849 }
850}
851
852void KActionCollection::removeAssociatedWidget(QWidget *widget)
853{
854 for (auto *action : d->actionStore.actions()) {
855 widget->removeAction(action);
856 }
857
858 d->associatedWidgets.removeAll(t: widget);
859 disconnect(sender: widget, signal: &QObject::destroyed, receiver: this, zero: nullptr);
860}
861
862QAction *KActionCollectionPrivate::unlistAction(QAction *action)
863{
864 // ATTENTION:
865 // This method is called with an QObject formerly known as a QAction
866 // during _k_actionDestroyed(). So don't do fancy stuff here that needs a
867 // real QAction!
868
869 // Remove the action
870 if (!actionStore.removeAction(action)) {
871 return nullptr;
872 }
873
874 // Remove the action from the categories. Should be only one
875 const QList<KActionCategory *> categories = q->findChildren<KActionCategory *>();
876 for (KActionCategory *category : categories) {
877 category->unlistAction(action);
878 }
879
880 return action;
881}
882
883QList<QWidget *> KActionCollection::associatedWidgets() const
884{
885 return d->associatedWidgets;
886}
887
888void KActionCollection::clearAssociatedWidgets()
889{
890 for (QWidget *widget : std::as_const(t&: d->associatedWidgets)) {
891 for (auto *action : d->actionStore.actions()) {
892 widget->removeAction(action);
893 }
894 }
895
896 d->associatedWidgets.clear();
897}
898
899void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj)
900{
901 associatedWidgets.removeAll(t: static_cast<QWidget *>(obj));
902}
903
904#include "moc_kactioncollection.cpp"
905

source code of kxmlgui/src/kactioncollection.cpp