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

source code of kxmlgui/src/kactioncollection.cpp