File indexing completed on 2024-04-21 15:05:46

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
0004     SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
0005     SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
0006     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0007     SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
0008     SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
0009     SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
0010     SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
0011     SPDX-FileCopyrightText: 2005-2007 Hamish Rodda <rodda@kde.org>
0012 
0013     SPDX-License-Identifier: LGPL-2.0-only
0014 */
0015 
0016 #include "config-xmlgui.h"
0017 
0018 #include "kactioncollection.h"
0019 
0020 #include "debug.h"
0021 #include "kactioncategory.h"
0022 #include "kxmlguiclient.h"
0023 #include "kxmlguifactory.h"
0024 
0025 #include <KAuthorized>
0026 #include <KConfigGroup>
0027 #if HAVE_GLOBALACCEL
0028 #include <KGlobalAccel>
0029 #endif
0030 #include <KSharedConfig>
0031 
0032 #include <QDomDocument>
0033 #include <QGuiApplication>
0034 #include <QList>
0035 #include <QMap>
0036 #include <QMetaMethod>
0037 #include <QSet>
0038 
0039 #include <cstdio>
0040 
0041 static bool actionHasGlobalShortcut(const QAction *action)
0042 {
0043 #if HAVE_GLOBALACCEL
0044     return KGlobalAccel::self()->hasShortcut(action);
0045 #else
0046     return false;
0047 #endif
0048 }
0049 
0050 class KActionCollectionPrivate
0051 {
0052 public:
0053     KActionCollectionPrivate(KActionCollection *qq)
0054         : q(qq)
0055         , configIsGlobal(false)
0056         , connectTriggered(false)
0057         , connectHovered(false)
0058 
0059     {
0060     }
0061 
0062     void setComponentForAction(QAction *action)
0063     {
0064         const bool hasGlobalShortcut = actionHasGlobalShortcut(action);
0065         if (!hasGlobalShortcut) {
0066             action->setProperty("componentName", m_componentName);
0067             action->setProperty("componentDisplayName", m_componentDisplayName);
0068         }
0069     }
0070 
0071     static QList<KActionCollection *> s_allCollections;
0072 
0073     void _k_associatedWidgetDestroyed(QObject *obj);
0074     void _k_actionDestroyed(QObject *obj);
0075 
0076     bool writeKXMLGUIConfigFile();
0077 
0078     QString m_componentName;
0079     QString m_componentDisplayName;
0080 
0081     //! Remove a action from our internal bookkeeping. Returns a nullptr if the
0082     //! action doesn't belong to us.
0083     QAction *unlistAction(QAction *);
0084 
0085     QMap<QString, QAction *> actionByName;
0086     QList<QAction *> actions;
0087 
0088     KActionCollection *q = nullptr;
0089 
0090     const KXMLGUIClient *m_parentGUIClient = nullptr;
0091 
0092     QString configGroup{QStringLiteral("Shortcuts")};
0093     bool configIsGlobal : 1;
0094 
0095     bool connectTriggered : 1;
0096     bool connectHovered : 1;
0097 
0098     QList<QWidget *> associatedWidgets;
0099 };
0100 
0101 QList<KActionCollection *> KActionCollectionPrivate::s_allCollections;
0102 
0103 KActionCollection::KActionCollection(QObject *parent, const QString &cName)
0104     : QObject(parent)
0105     , d(new KActionCollectionPrivate(this))
0106 {
0107     KActionCollectionPrivate::s_allCollections.append(this);
0108 
0109     setComponentName(cName);
0110 }
0111 
0112 KActionCollection::KActionCollection(const KXMLGUIClient *parent)
0113     : QObject(nullptr)
0114     , d(new KActionCollectionPrivate(this))
0115 {
0116     KActionCollectionPrivate::s_allCollections.append(this);
0117 
0118     d->m_parentGUIClient = parent;
0119     d->m_componentName = parent->componentName();
0120 }
0121 
0122 KActionCollection::~KActionCollection()
0123 {
0124     KActionCollectionPrivate::s_allCollections.removeAll(this);
0125 }
0126 
0127 void KActionCollection::clear()
0128 {
0129     d->actionByName.clear();
0130     qDeleteAll(d->actions);
0131     d->actions.clear();
0132 }
0133 
0134 QAction *KActionCollection::action(const QString &name) const
0135 {
0136     QAction *action = nullptr;
0137 
0138     if (!name.isEmpty()) {
0139         action = d->actionByName.value(name);
0140     }
0141 
0142     return action;
0143 }
0144 
0145 QAction *KActionCollection::action(int index) const
0146 {
0147     // ### investigate if any apps use this at all
0148     return actions().value(index);
0149 }
0150 
0151 int KActionCollection::count() const
0152 {
0153     return d->actions.count();
0154 }
0155 
0156 bool KActionCollection::isEmpty() const
0157 {
0158     return count() == 0;
0159 }
0160 
0161 void KActionCollection::setComponentName(const QString &cName)
0162 {
0163     for (QAction *a : std::as_const(d->actions)) {
0164         if (actionHasGlobalShortcut(a)) {
0165             // Its component name is part of an action's signature in the context of
0166             // global shortcuts and the semantics of changing an existing action's
0167             // signature are, as it seems, impossible to get right.
0168             qCWarning(DEBUG_KXMLGUI) << "KActionCollection::setComponentName does not work on a KActionCollection containing actions with global shortcuts!"
0169                                      << cName;
0170             break;
0171         }
0172     }
0173 
0174     if (!cName.isEmpty()) {
0175         d->m_componentName = cName;
0176     } else {
0177         d->m_componentName = QCoreApplication::applicationName();
0178     }
0179 }
0180 
0181 QString KActionCollection::componentName() const
0182 {
0183     return d->m_componentName;
0184 }
0185 
0186 void KActionCollection::setComponentDisplayName(const QString &displayName)
0187 {
0188     d->m_componentDisplayName = displayName;
0189 }
0190 
0191 QString KActionCollection::componentDisplayName() const
0192 {
0193     if (!d->m_componentDisplayName.isEmpty()) {
0194         return d->m_componentDisplayName;
0195     }
0196     if (!QGuiApplication::applicationDisplayName().isEmpty()) {
0197         return QGuiApplication::applicationDisplayName();
0198     }
0199     return QCoreApplication::applicationName();
0200 }
0201 
0202 const KXMLGUIClient *KActionCollection::parentGUIClient() const
0203 {
0204     return d->m_parentGUIClient;
0205 }
0206 
0207 QList<QAction *> KActionCollection::actions() const
0208 {
0209     return d->actions;
0210 }
0211 
0212 const QList<QAction *> KActionCollection::actionsWithoutGroup() const
0213 {
0214     QList<QAction *> ret;
0215     for (QAction *action : std::as_const(d->actions)) {
0216         if (!action->actionGroup()) {
0217             ret.append(action);
0218         }
0219     }
0220     return ret;
0221 }
0222 
0223 const QList<QActionGroup *> KActionCollection::actionGroups() const
0224 {
0225     QSet<QActionGroup *> set;
0226     for (QAction *action : std::as_const(d->actions)) {
0227         if (action->actionGroup()) {
0228             set.insert(action->actionGroup());
0229         }
0230     }
0231     return set.values();
0232 }
0233 
0234 QAction *KActionCollection::addAction(const QString &name, QAction *action)
0235 {
0236     if (!action) {
0237         return action;
0238     }
0239 
0240     const QString objectName = action->objectName();
0241     QString indexName = name;
0242 
0243     if (indexName.isEmpty()) {
0244         // No name provided. Use the objectName.
0245         indexName = objectName;
0246 
0247     } else {
0248         // A name was provided. Check against objectName.
0249         if ((!objectName.isEmpty()) && (objectName != indexName)) {
0250             // The user specified a new name and the action already has a
0251             // different one. The objectName is used for saving shortcut
0252             // settings to disk. Both for local and global shortcuts.
0253             qCDebug(DEBUG_KXMLGUI) << "Registering action " << objectName << " under new name " << indexName;
0254             // If there is a global shortcuts it's a very bad idea.
0255 #if HAVE_GLOBALACCEL
0256             if (KGlobalAccel::self()->hasShortcut(action)) {
0257                 // In debug mode assert
0258                 Q_ASSERT(!KGlobalAccel::self()->hasShortcut(action));
0259                 // In release mode keep the old name
0260                 qCCritical(DEBUG_KXMLGUI) << "Changing action name from " << objectName << " to " << indexName
0261                                           << "\nignored because of active global shortcut.";
0262                 indexName = objectName;
0263             }
0264 #endif
0265         }
0266 
0267         // Set the new name
0268         action->setObjectName(indexName);
0269     }
0270 
0271     // No name provided and the action had no name. Make one up. This will not
0272     // work when trying to save shortcuts. Both local and global shortcuts.
0273     if (indexName.isEmpty()) {
0274         indexName = QString::asprintf("unnamed-%p", (void *)action);
0275         action->setObjectName(indexName);
0276     }
0277 
0278     // From now on the objectName has to have a value. Else we cannot safely
0279     // remove actions.
0280     Q_ASSERT(!action->objectName().isEmpty());
0281 
0282     // look if we already have THIS action under THIS name ;)
0283     if (d->actionByName.value(indexName, nullptr) == action) {
0284         // This is not a multi map!
0285         Q_ASSERT(d->actionByName.count(indexName) == 1);
0286         return action;
0287     }
0288 
0289     if (!KAuthorized::authorizeAction(indexName)) {
0290         // Disable this action
0291         action->setEnabled(false);
0292         action->setVisible(false);
0293         action->blockSignals(true);
0294     }
0295 
0296     // Check if we have another action under this name
0297     if (QAction *oldAction = d->actionByName.value(indexName)) {
0298         takeAction(oldAction);
0299     }
0300 
0301     // Check if we have this action under a different name.
0302     // Not using takeAction because we don't want to remove it from categories,
0303     // and because it has the new name already.
0304     const int oldIndex = d->actions.indexOf(action);
0305     if (oldIndex != -1) {
0306         d->actionByName.remove(d->actionByName.key(action));
0307         d->actions.removeAt(oldIndex);
0308     }
0309 
0310     // Add action to our lists.
0311     d->actionByName.insert(indexName, action);
0312     d->actions.append(action);
0313 
0314     for (QWidget *widget : std::as_const(d->associatedWidgets)) {
0315         widget->addAction(action);
0316     }
0317 
0318     connect(action, &QObject::destroyed, this, [this](QObject *obj) {
0319         d->_k_actionDestroyed(obj);
0320     });
0321 
0322     d->setComponentForAction(action);
0323 
0324     if (d->connectHovered) {
0325         connect(action, &QAction::hovered, this, &KActionCollection::slotActionHovered);
0326     }
0327 
0328     if (d->connectTriggered) {
0329         connect(action, &QAction::triggered, this, &KActionCollection::slotActionTriggered);
0330     }
0331 
0332     Q_EMIT inserted(action);
0333     Q_EMIT changed();
0334     return action;
0335 }
0336 
0337 void KActionCollection::addActions(const QList<QAction *> &actions)
0338 {
0339     for (QAction *action : actions) {
0340         addAction(action->objectName(), action);
0341     }
0342 }
0343 
0344 void KActionCollection::removeAction(QAction *action)
0345 {
0346     delete takeAction(action);
0347 }
0348 
0349 QAction *KActionCollection::takeAction(QAction *action)
0350 {
0351     if (!d->unlistAction(action)) {
0352         return nullptr;
0353     }
0354 
0355     // Remove the action from all widgets
0356     for (QWidget *widget : std::as_const(d->associatedWidgets)) {
0357         widget->removeAction(action);
0358     }
0359 
0360     action->disconnect(this);
0361 
0362 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
0363     Q_EMIT removed(action); // deprecated
0364 #endif
0365     Q_EMIT changed();
0366     return action;
0367 }
0368 
0369 QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
0370 {
0371     QAction *action = KStandardAction::create(actionType, receiver, member, this);
0372     return action;
0373 }
0374 
0375 QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver, const char *member)
0376 {
0377     // pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically
0378     // adds the action to it under the default name. We would trigger the
0379     // warning about renaming the action then.
0380     QAction *action = KStandardAction::create(actionType, receiver, member, nullptr);
0381     // Give it a parent for gc.
0382     action->setParent(this);
0383     // Remove the name to get rid of the "rename action" warning above
0384     action->setObjectName(name);
0385     // And now add it with the desired name.
0386     return addAction(name, action);
0387 }
0388 
0389 QAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member)
0390 {
0391     QAction *a = new QAction(this);
0392     if (receiver && member) {
0393         connect(a, SIGNAL(triggered(bool)), receiver, member);
0394     }
0395     return addAction(name, a);
0396 }
0397 
0398 QKeySequence KActionCollection::defaultShortcut(QAction *action) const
0399 {
0400     const QList<QKeySequence> shortcuts = defaultShortcuts(action);
0401     return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
0402 }
0403 
0404 QList<QKeySequence> KActionCollection::defaultShortcuts(QAction *action) const
0405 {
0406     return action->property("defaultShortcuts").value<QList<QKeySequence>>();
0407 }
0408 
0409 void KActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
0410 {
0411     setDefaultShortcuts(action, QList<QKeySequence>() << shortcut);
0412 }
0413 
0414 void KActionCollection::setDefaultShortcuts(QAction *action, const QList<QKeySequence> &shortcuts)
0415 {
0416     action->setShortcuts(shortcuts);
0417     action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts));
0418 }
0419 
0420 bool KActionCollection::isShortcutsConfigurable(QAction *action) const
0421 {
0422     // Considered as true by default
0423     const QVariant value = action->property("isShortcutConfigurable");
0424     return value.isValid() ? value.toBool() : true;
0425 }
0426 
0427 void KActionCollection::setShortcutsConfigurable(QAction *action, bool configurable)
0428 {
0429     action->setProperty("isShortcutConfigurable", configurable);
0430 }
0431 
0432 QString KActionCollection::configGroup() const
0433 {
0434     return d->configGroup;
0435 }
0436 
0437 void KActionCollection::setConfigGroup(const QString &group)
0438 {
0439     d->configGroup = group;
0440 }
0441 
0442 bool KActionCollection::configIsGlobal() const
0443 {
0444     return d->configIsGlobal;
0445 }
0446 
0447 void KActionCollection::setConfigGlobal(bool global)
0448 {
0449     d->configIsGlobal = global;
0450 }
0451 
0452 void KActionCollection::importGlobalShortcuts(KConfigGroup *config)
0453 {
0454 #if HAVE_GLOBALACCEL
0455     Q_ASSERT(config);
0456     if (!config || !config->exists()) {
0457         return;
0458     }
0459 
0460     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0461         QAction *action = it.value();
0462         if (!action) {
0463             continue;
0464         }
0465 
0466         const QString &actionName = it.key();
0467 
0468         if (isShortcutsConfigurable(action)) {
0469             QString entry = config->readEntry(actionName, QString());
0470             if (!entry.isEmpty()) {
0471                 KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(entry), KGlobalAccel::NoAutoloading);
0472             } else {
0473                 QList<QKeySequence> defaultShortcut = KGlobalAccel::self()->defaultShortcut(action);
0474                 KGlobalAccel::self()->setShortcut(action, defaultShortcut, KGlobalAccel::NoAutoloading);
0475             }
0476         }
0477     }
0478 #else
0479     Q_UNUSED(config);
0480 #endif
0481 }
0482 
0483 void KActionCollection::readSettings(KConfigGroup *config)
0484 {
0485     KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
0486     if (!config) {
0487         config = &cg;
0488     }
0489 
0490     if (!config->exists()) {
0491         return;
0492     }
0493 
0494     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0495         QAction *action = it.value();
0496         if (!action) {
0497             continue;
0498         }
0499 
0500         if (isShortcutsConfigurable(action)) {
0501             const QString &actionName = it.key();
0502             QString entry = config->readEntry(actionName, QString());
0503             if (!entry.isEmpty()) {
0504                 action->setShortcuts(QKeySequence::listFromString(entry));
0505             } else {
0506                 action->setShortcuts(defaultShortcuts(action));
0507             }
0508         }
0509     }
0510 
0511     // qCDebug(DEBUG_KXMLGUI) << " done";
0512 }
0513 
0514 void KActionCollection::exportGlobalShortcuts(KConfigGroup *config, bool writeAll) const
0515 {
0516 #if HAVE_GLOBALACCEL
0517     Q_ASSERT(config);
0518     if (!config) {
0519         return;
0520     }
0521 
0522     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0523         QAction *action = it.value();
0524         if (!action) {
0525             continue;
0526         }
0527         const QString &actionName = it.key();
0528 
0529         // If the action name starts with unnamed- spit out a warning. That name
0530         // will change at will and will break loading writing
0531         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0532             qCCritical(DEBUG_KXMLGUI) << "Skipped exporting Shortcut for action without name " << action->text() << "!";
0533             continue;
0534         }
0535 
0536         if (isShortcutsConfigurable(action) && KGlobalAccel::self()->hasShortcut(action)) {
0537             bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
0538             bool bSameAsDefault = (KGlobalAccel::self()->shortcut(action) == KGlobalAccel::self()->defaultShortcut(action));
0539             // If we're using a global config or this setting
0540             //  differs from the default, then we want to write.
0541             KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
0542             if (configIsGlobal()) {
0543                 flags |= KConfigGroup::Global;
0544             }
0545             if (writeAll || !bSameAsDefault) {
0546                 QString s = QKeySequence::listToString(KGlobalAccel::self()->shortcut(action));
0547                 if (s.isEmpty()) {
0548                     s = QStringLiteral("none");
0549                 }
0550                 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
0551                 config->writeEntry(actionName, s, flags);
0552             }
0553             // Otherwise, this key is the same as default
0554             //  but exists in config file.  Remove it.
0555             else if (bConfigHasAction) {
0556                 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
0557                 config->deleteEntry(actionName, flags);
0558             }
0559         }
0560     }
0561 
0562     config->sync();
0563 #else
0564     Q_UNUSED(config);
0565     Q_UNUSED(writeAll);
0566 #endif
0567 }
0568 
0569 bool KActionCollectionPrivate::writeKXMLGUIConfigFile()
0570 {
0571     const KXMLGUIClient *kxmlguiClient = q->parentGUIClient();
0572     // return false if there is no KXMLGUIClient
0573     if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) {
0574         return false;
0575     }
0576 
0577     qCDebug(DEBUG_KXMLGUI) << "xmlFile=" << kxmlguiClient->xmlFile();
0578 
0579     QString attrShortcut = QStringLiteral("shortcut");
0580 
0581     // Read XML file
0582     QString sXml(KXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentName()));
0583     QDomDocument doc;
0584     doc.setContent(sXml);
0585 
0586     // Process XML data
0587 
0588     // Get hold of ActionProperties tag
0589     QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc);
0590 
0591     // now, iterate through our actions
0592     for (QMap<QString, QAction *>::ConstIterator it = actionByName.constBegin(); it != actionByName.constEnd(); ++it) {
0593         QAction *action = it.value();
0594         if (!action) {
0595             continue;
0596         }
0597 
0598         const QString &actionName = it.key();
0599 
0600         // If the action name starts with unnamed- spit out a warning and ignore
0601         // it. That name will change at will and will break loading writing
0602         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0603             qCCritical(DEBUG_KXMLGUI) << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!";
0604             continue;
0605         }
0606 
0607         bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action));
0608         qCDebug(DEBUG_KXMLGUI) << "name = " << actionName << " shortcut = " << QKeySequence::listToString(action->shortcuts())
0609 #if HAVE_GLOBALACCEL
0610                                << " globalshortcut = " << QKeySequence::listToString(KGlobalAccel::self()->shortcut(action))
0611 #endif
0612                                << " def = " << QKeySequence::listToString(q->defaultShortcuts(action));
0613 
0614         // now see if this element already exists
0615         // and create it if necessary (unless bSameAsDefault)
0616         QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault);
0617         if (act_elem.isNull()) {
0618             continue;
0619         }
0620 
0621         if (bSameAsDefault) {
0622             act_elem.removeAttribute(attrShortcut);
0623             // qCDebug(DEBUG_KXMLGUI) << "act_elem.attributes().count() = " << act_elem.attributes().count();
0624             if (act_elem.attributes().count() == 1) {
0625                 elem.removeChild(act_elem);
0626             }
0627         } else {
0628             act_elem.setAttribute(attrShortcut, QKeySequence::listToString(action->shortcuts()));
0629         }
0630     }
0631 
0632     // Write back to XML file
0633     KXMLGUIFactory::saveConfigFile(doc, kxmlguiClient->localXMLFile(), q->componentName());
0634     // and since we just changed the xml file clear the dom we have in memory
0635     // it'll be rebuilt if needed
0636     const_cast<KXMLGUIClient *>(kxmlguiClient)->setXMLGUIBuildDocument({});
0637     return true;
0638 }
0639 
0640 void KActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const
0641 {
0642     // If the caller didn't provide a config group we try to save the KXMLGUI
0643     // Configuration file. If that succeeds we are finished.
0644     if (config == nullptr && d->writeKXMLGUIConfigFile()) {
0645         return;
0646     }
0647 
0648     KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
0649     if (!config) {
0650         config = &cg;
0651     }
0652 
0653     QList<QAction *> writeActions;
0654     if (oneAction) {
0655         writeActions.append(oneAction);
0656     } else {
0657         writeActions = actions();
0658     }
0659 
0660     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0661         QAction *action = it.value();
0662         if (!action) {
0663             continue;
0664         }
0665 
0666         const QString &actionName = it.key();
0667 
0668         // If the action name starts with unnamed- spit out a warning and ignore
0669         // it. That name will change at will and will break loading writing
0670         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0671             qCCritical(DEBUG_KXMLGUI) << "Skipped saving Shortcut for action without name " << action->text() << "!";
0672             continue;
0673         }
0674 
0675         // Write the shortcut
0676         if (isShortcutsConfigurable(action)) {
0677             bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
0678             bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action));
0679             // If we're using a global config or this setting
0680             //  differs from the default, then we want to write.
0681             KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
0682 
0683             // Honor the configIsGlobal() setting
0684             if (configIsGlobal()) {
0685                 flags |= KConfigGroup::Global;
0686             }
0687 
0688             if (writeAll || !bSameAsDefault) {
0689                 // We are instructed to write all shortcuts or the shortcut is
0690                 // not set to its default value. Write it
0691                 QString s = QKeySequence::listToString(action->shortcuts());
0692                 if (s.isEmpty()) {
0693                     s = QStringLiteral("none");
0694                 }
0695                 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
0696                 config->writeEntry(actionName, s, flags);
0697 
0698             } else if (bConfigHasAction) {
0699                 // Otherwise, this key is the same as default but exists in
0700                 // config file. Remove it.
0701                 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
0702                 config->deleteEntry(actionName, flags);
0703             }
0704         }
0705     }
0706 
0707     config->sync();
0708 }
0709 
0710 void KActionCollection::slotActionTriggered()
0711 {
0712     QAction *action = qobject_cast<QAction *>(sender());
0713     if (action) {
0714         Q_EMIT actionTriggered(action);
0715     }
0716 }
0717 
0718 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
0719 void KActionCollection::slotActionHighlighted()
0720 {
0721     slotActionHovered();
0722 }
0723 #endif
0724 
0725 void KActionCollection::slotActionHovered()
0726 {
0727     QAction *action = qobject_cast<QAction *>(sender());
0728     if (action) {
0729 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
0730         Q_EMIT actionHighlighted(action);
0731 #endif
0732         Q_EMIT actionHovered(action);
0733     }
0734 }
0735 
0736 // The downcast from a QObject to a QAction triggers UBSan
0737 // but we're only comparing pointers, so UBSan shouldn't check vptrs
0738 // Similar to https://github.com/itsBelinda/plog/pull/1/files
0739 #if defined(__clang__) || __GNUC__ >= 8
0740 __attribute__((no_sanitize("vptr")))
0741 #endif
0742 void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj)
0743 {
0744     // obj isn't really a QAction anymore. So make sure we don't do fancy stuff
0745     // with it.
0746     QAction *action = static_cast<QAction *>(obj);
0747 
0748     if (!unlistAction(action)) {
0749         return;
0750     }
0751 
0752     // HACK the object we emit is partly destroyed
0753 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
0754     Q_EMIT q->removed(action);
0755 #endif
0756     Q_EMIT q->changed();
0757 }
0758 
0759 void KActionCollection::connectNotify(const QMetaMethod &signal)
0760 {
0761     if (d->connectHovered && d->connectTriggered) {
0762         return;
0763     }
0764 
0765     if (
0766 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
0767         signal.methodSignature() == "actionHighlighted(QAction*)" ||
0768 #endif
0769         signal.methodSignature() == "actionHovered(QAction*)") {
0770         if (!d->connectHovered) {
0771             d->connectHovered = true;
0772             for (QAction *action : std::as_const(d->actions)) {
0773                 connect(action, &QAction::hovered, this, &KActionCollection::slotActionHovered);
0774             }
0775         }
0776 
0777     } else if (signal.methodSignature() == "actionTriggered(QAction*)") {
0778         if (!d->connectTriggered) {
0779             d->connectTriggered = true;
0780             for (QAction *action : std::as_const(d->actions)) {
0781                 connect(action, &QAction::triggered, this, &KActionCollection::slotActionTriggered);
0782             }
0783         }
0784     }
0785 
0786     QObject::connectNotify(signal);
0787 }
0788 
0789 const QList<KActionCollection *> &KActionCollection::allCollections()
0790 {
0791     return KActionCollectionPrivate::s_allCollections;
0792 }
0793 
0794 void KActionCollection::associateWidget(QWidget *widget) const
0795 {
0796     for (QAction *action : std::as_const(d->actions)) {
0797         if (!widget->actions().contains(action)) {
0798             widget->addAction(action);
0799         }
0800     }
0801 }
0802 
0803 void KActionCollection::addAssociatedWidget(QWidget *widget)
0804 {
0805     if (!d->associatedWidgets.contains(widget)) {
0806         widget->addActions(actions());
0807 
0808         d->associatedWidgets.append(widget);
0809         connect(widget, &QObject::destroyed, this, [this](QObject *obj) {
0810             d->_k_associatedWidgetDestroyed(obj);
0811         });
0812     }
0813 }
0814 
0815 void KActionCollection::removeAssociatedWidget(QWidget *widget)
0816 {
0817     for (QAction *action : std::as_const(d->actions)) {
0818         widget->removeAction(action);
0819     }
0820 
0821     d->associatedWidgets.removeAll(widget);
0822     disconnect(widget, &QObject::destroyed, this, nullptr);
0823 }
0824 
0825 QAction *KActionCollectionPrivate::unlistAction(QAction *action)
0826 {
0827     // ATTENTION:
0828     //   This method is called with an QObject formerly known as a QAction
0829     //   during _k_actionDestroyed(). So don't do fancy stuff here that needs a
0830     //   real QAction!
0831 
0832     // Get the index for the action
0833     int index = actions.indexOf(action);
0834 
0835     // Action not found.
0836     if (index == -1) {
0837         return nullptr;
0838     }
0839 
0840     // An action collection can't have the same action twice.
0841     Q_ASSERT(actions.indexOf(action, index + 1) == -1);
0842 
0843     // Get the actions name
0844     const QString name = action->objectName();
0845 
0846     // Remove the action
0847     actionByName.remove(name);
0848     actions.removeAt(index);
0849 
0850     // Remove the action from the categories. Should be only one
0851     const QList<KActionCategory *> categories = q->findChildren<KActionCategory *>();
0852     for (KActionCategory *category : categories) {
0853         category->unlistAction(action);
0854     }
0855 
0856     return action;
0857 }
0858 
0859 QList<QWidget *> KActionCollection::associatedWidgets() const
0860 {
0861     return d->associatedWidgets;
0862 }
0863 
0864 void KActionCollection::clearAssociatedWidgets()
0865 {
0866     for (QWidget *widget : std::as_const(d->associatedWidgets)) {
0867         for (QAction *action : std::as_const(d->actions)) {
0868             widget->removeAction(action);
0869         }
0870     }
0871 
0872     d->associatedWidgets.clear();
0873 }
0874 
0875 void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj)
0876 {
0877     associatedWidgets.removeAll(static_cast<QWidget *>(obj));
0878 }
0879 
0880 #include "moc_kactioncollection.cpp"