File indexing completed on 2024-04-14 03:57:07

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     Q_EMIT changed();
0363     return action;
0364 }
0365 
0366 QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
0367 {
0368     QAction *action = KStandardAction::create(actionType, receiver, member, this);
0369     return action;
0370 }
0371 
0372 QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver, const char *member)
0373 {
0374     // pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically
0375     // adds the action to it under the default name. We would trigger the
0376     // warning about renaming the action then.
0377     QAction *action = KStandardAction::create(actionType, receiver, member, nullptr);
0378     // Give it a parent for gc.
0379     action->setParent(this);
0380     // Remove the name to get rid of the "rename action" warning above
0381     action->setObjectName(name);
0382     // And now add it with the desired name.
0383     return addAction(name, action);
0384 }
0385 
0386 QAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member)
0387 {
0388     QAction *a = new QAction(this);
0389     if (receiver && member) {
0390         connect(a, SIGNAL(triggered(bool)), receiver, member);
0391     }
0392     return addAction(name, a);
0393 }
0394 
0395 QKeySequence KActionCollection::defaultShortcut(QAction *action)
0396 {
0397     const QList<QKeySequence> shortcuts = defaultShortcuts(action);
0398     return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
0399 }
0400 
0401 QList<QKeySequence> KActionCollection::defaultShortcuts(QAction *action)
0402 {
0403     return action->property("defaultShortcuts").value<QList<QKeySequence>>();
0404 }
0405 
0406 void KActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
0407 {
0408     setDefaultShortcuts(action, QList<QKeySequence>() << shortcut);
0409 }
0410 
0411 void KActionCollection::setDefaultShortcuts(QAction *action, const QList<QKeySequence> &shortcuts)
0412 {
0413     action->setShortcuts(shortcuts);
0414     action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts));
0415 }
0416 
0417 bool KActionCollection::isShortcutsConfigurable(QAction *action)
0418 {
0419     // Considered as true by default
0420     const QVariant value = action->property("isShortcutConfigurable");
0421     return value.isValid() ? value.toBool() : true;
0422 }
0423 
0424 void KActionCollection::setShortcutsConfigurable(QAction *action, bool configurable)
0425 {
0426     action->setProperty("isShortcutConfigurable", configurable);
0427 }
0428 
0429 QString KActionCollection::configGroup() const
0430 {
0431     return d->configGroup;
0432 }
0433 
0434 void KActionCollection::setConfigGroup(const QString &group)
0435 {
0436     d->configGroup = group;
0437 }
0438 
0439 bool KActionCollection::configIsGlobal() const
0440 {
0441     return d->configIsGlobal;
0442 }
0443 
0444 void KActionCollection::setConfigGlobal(bool global)
0445 {
0446     d->configIsGlobal = global;
0447 }
0448 
0449 void KActionCollection::importGlobalShortcuts(KConfigGroup *config)
0450 {
0451 #if HAVE_GLOBALACCEL
0452     Q_ASSERT(config);
0453     if (!config || !config->exists()) {
0454         return;
0455     }
0456 
0457     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0458         QAction *action = it.value();
0459         if (!action) {
0460             continue;
0461         }
0462 
0463         const QString &actionName = it.key();
0464 
0465         if (isShortcutsConfigurable(action)) {
0466             QString entry = config->readEntry(actionName, QString());
0467             if (!entry.isEmpty()) {
0468                 KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(entry), KGlobalAccel::NoAutoloading);
0469             } else {
0470                 QList<QKeySequence> defaultShortcut = KGlobalAccel::self()->defaultShortcut(action);
0471                 KGlobalAccel::self()->setShortcut(action, defaultShortcut, KGlobalAccel::NoAutoloading);
0472             }
0473         }
0474     }
0475 #else
0476     Q_UNUSED(config);
0477 #endif
0478 }
0479 
0480 void KActionCollection::readSettings(KConfigGroup *config)
0481 {
0482     KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
0483     if (!config) {
0484         config = &cg;
0485     }
0486 
0487     if (!config->exists()) {
0488         return;
0489     }
0490 
0491     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0492         QAction *action = it.value();
0493         if (!action) {
0494             continue;
0495         }
0496 
0497         if (isShortcutsConfigurable(action)) {
0498             const QString &actionName = it.key();
0499             QString entry = config->readEntry(actionName, QString());
0500             if (!entry.isEmpty()) {
0501                 action->setShortcuts(QKeySequence::listFromString(entry));
0502             } else {
0503                 action->setShortcuts(defaultShortcuts(action));
0504             }
0505         }
0506     }
0507 
0508     // qCDebug(DEBUG_KXMLGUI) << " done";
0509 }
0510 
0511 void KActionCollection::exportGlobalShortcuts(KConfigGroup *config, bool writeAll) const
0512 {
0513 #if HAVE_GLOBALACCEL
0514     Q_ASSERT(config);
0515     if (!config) {
0516         return;
0517     }
0518 
0519     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0520         QAction *action = it.value();
0521         if (!action) {
0522             continue;
0523         }
0524         const QString &actionName = it.key();
0525 
0526         // If the action name starts with unnamed- spit out a warning. That name
0527         // will change at will and will break loading writing
0528         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0529             qCCritical(DEBUG_KXMLGUI) << "Skipped exporting Shortcut for action without name " << action->text() << "!";
0530             continue;
0531         }
0532 
0533         if (isShortcutsConfigurable(action) && KGlobalAccel::self()->hasShortcut(action)) {
0534             bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
0535             bool bSameAsDefault = (KGlobalAccel::self()->shortcut(action) == KGlobalAccel::self()->defaultShortcut(action));
0536             // If we're using a global config or this setting
0537             //  differs from the default, then we want to write.
0538             KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
0539             if (configIsGlobal()) {
0540                 flags |= KConfigGroup::Global;
0541             }
0542             if (writeAll || !bSameAsDefault) {
0543                 QString s = QKeySequence::listToString(KGlobalAccel::self()->shortcut(action));
0544                 if (s.isEmpty()) {
0545                     s = QStringLiteral("none");
0546                 }
0547                 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
0548                 config->writeEntry(actionName, s, flags);
0549             }
0550             // Otherwise, this key is the same as default
0551             //  but exists in config file.  Remove it.
0552             else if (bConfigHasAction) {
0553                 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
0554                 config->deleteEntry(actionName, flags);
0555             }
0556         }
0557     }
0558 
0559     config->sync();
0560 #else
0561     Q_UNUSED(config);
0562     Q_UNUSED(writeAll);
0563 #endif
0564 }
0565 
0566 bool KActionCollectionPrivate::writeKXMLGUIConfigFile()
0567 {
0568     const KXMLGUIClient *kxmlguiClient = q->parentGUIClient();
0569     // return false if there is no KXMLGUIClient
0570     if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) {
0571         return false;
0572     }
0573 
0574     qCDebug(DEBUG_KXMLGUI) << "xmlFile=" << kxmlguiClient->xmlFile();
0575 
0576     QString attrShortcut = QStringLiteral("shortcut");
0577 
0578     // Read XML file
0579     QString sXml(KXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentName()));
0580     QDomDocument doc;
0581     doc.setContent(sXml);
0582 
0583     // Process XML data
0584 
0585     // Get hold of ActionProperties tag
0586     QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc);
0587 
0588     // now, iterate through our actions
0589     for (QMap<QString, QAction *>::ConstIterator it = actionByName.constBegin(); it != actionByName.constEnd(); ++it) {
0590         QAction *action = it.value();
0591         if (!action) {
0592             continue;
0593         }
0594 
0595         const QString &actionName = it.key();
0596 
0597         // If the action name starts with unnamed- spit out a warning and ignore
0598         // it. That name will change at will and will break loading writing
0599         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0600             qCCritical(DEBUG_KXMLGUI) << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!";
0601             continue;
0602         }
0603 
0604         bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action));
0605         qCDebug(DEBUG_KXMLGUI) << "name = " << actionName << " shortcut = " << QKeySequence::listToString(action->shortcuts())
0606 #if HAVE_GLOBALACCEL
0607                                << " globalshortcut = " << QKeySequence::listToString(KGlobalAccel::self()->shortcut(action))
0608 #endif
0609                                << " def = " << QKeySequence::listToString(q->defaultShortcuts(action));
0610 
0611         // now see if this element already exists
0612         // and create it if necessary (unless bSameAsDefault)
0613         QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault);
0614         if (act_elem.isNull()) {
0615             continue;
0616         }
0617 
0618         if (bSameAsDefault) {
0619             act_elem.removeAttribute(attrShortcut);
0620             // qCDebug(DEBUG_KXMLGUI) << "act_elem.attributes().count() = " << act_elem.attributes().count();
0621             if (act_elem.attributes().count() == 1) {
0622                 elem.removeChild(act_elem);
0623             }
0624         } else {
0625             act_elem.setAttribute(attrShortcut, QKeySequence::listToString(action->shortcuts()));
0626         }
0627     }
0628 
0629     // Write back to XML file
0630     KXMLGUIFactory::saveConfigFile(doc, kxmlguiClient->localXMLFile(), q->componentName());
0631     // and since we just changed the xml file clear the dom we have in memory
0632     // it'll be rebuilt if needed
0633     const_cast<KXMLGUIClient *>(kxmlguiClient)->setXMLGUIBuildDocument({});
0634     return true;
0635 }
0636 
0637 void KActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const
0638 {
0639     // If the caller didn't provide a config group we try to save the KXMLGUI
0640     // Configuration file. If that succeeds we are finished.
0641     if (config == nullptr && d->writeKXMLGUIConfigFile()) {
0642         return;
0643     }
0644 
0645     KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
0646     if (!config) {
0647         config = &cg;
0648     }
0649 
0650     QList<QAction *> writeActions;
0651     if (oneAction) {
0652         writeActions.append(oneAction);
0653     } else {
0654         writeActions = actions();
0655     }
0656 
0657     for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
0658         QAction *action = it.value();
0659         if (!action) {
0660             continue;
0661         }
0662 
0663         const QString &actionName = it.key();
0664 
0665         // If the action name starts with unnamed- spit out a warning and ignore
0666         // it. That name will change at will and will break loading writing
0667         if (actionName.startsWith(QLatin1String("unnamed-"))) {
0668             qCCritical(DEBUG_KXMLGUI) << "Skipped saving Shortcut for action without name " << action->text() << "!";
0669             continue;
0670         }
0671 
0672         // Write the shortcut
0673         if (isShortcutsConfigurable(action)) {
0674             bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
0675             bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action));
0676             // If we're using a global config or this setting
0677             //  differs from the default, then we want to write.
0678             KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
0679 
0680             // Honor the configIsGlobal() setting
0681             if (configIsGlobal()) {
0682                 flags |= KConfigGroup::Global;
0683             }
0684 
0685             if (writeAll || !bSameAsDefault) {
0686                 // We are instructed to write all shortcuts or the shortcut is
0687                 // not set to its default value. Write it
0688                 QString s = QKeySequence::listToString(action->shortcuts());
0689                 if (s.isEmpty()) {
0690                     s = QStringLiteral("none");
0691                 }
0692                 qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s;
0693                 config->writeEntry(actionName, s, flags);
0694 
0695             } else if (bConfigHasAction) {
0696                 // Otherwise, this key is the same as default but exists in
0697                 // config file. Remove it.
0698                 qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default";
0699                 config->deleteEntry(actionName, flags);
0700             }
0701         }
0702     }
0703 
0704     config->sync();
0705 }
0706 
0707 void KActionCollection::slotActionTriggered()
0708 {
0709     QAction *action = qobject_cast<QAction *>(sender());
0710     if (action) {
0711         Q_EMIT actionTriggered(action);
0712     }
0713 }
0714 
0715 void KActionCollection::slotActionHovered()
0716 {
0717     QAction *action = qobject_cast<QAction *>(sender());
0718     if (action) {
0719         Q_EMIT actionHovered(action);
0720     }
0721 }
0722 
0723 // The downcast from a QObject to a QAction triggers UBSan
0724 // but we're only comparing pointers, so UBSan shouldn't check vptrs
0725 // Similar to https://github.com/itsBelinda/plog/pull/1/files
0726 #if defined(__clang__) || __GNUC__ >= 8
0727 __attribute__((no_sanitize("vptr")))
0728 #endif
0729 void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj)
0730 {
0731     // obj isn't really a QAction anymore. So make sure we don't do fancy stuff
0732     // with it.
0733     QAction *action = static_cast<QAction *>(obj);
0734 
0735     if (!unlistAction(action)) {
0736         return;
0737     }
0738 
0739     Q_EMIT q->changed();
0740 }
0741 
0742 void KActionCollection::connectNotify(const QMetaMethod &signal)
0743 {
0744     if (d->connectHovered && d->connectTriggered) {
0745         return;
0746     }
0747 
0748     if (signal.methodSignature() == "actionHovered(QAction*)") {
0749         if (!d->connectHovered) {
0750             d->connectHovered = true;
0751             for (QAction *action : std::as_const(d->actions)) {
0752                 connect(action, &QAction::hovered, this, &KActionCollection::slotActionHovered);
0753             }
0754         }
0755 
0756     } else if (signal.methodSignature() == "actionTriggered(QAction*)") {
0757         if (!d->connectTriggered) {
0758             d->connectTriggered = true;
0759             for (QAction *action : std::as_const(d->actions)) {
0760                 connect(action, &QAction::triggered, this, &KActionCollection::slotActionTriggered);
0761             }
0762         }
0763     }
0764 
0765     QObject::connectNotify(signal);
0766 }
0767 
0768 const QList<KActionCollection *> &KActionCollection::allCollections()
0769 {
0770     return KActionCollectionPrivate::s_allCollections;
0771 }
0772 
0773 void KActionCollection::associateWidget(QWidget *widget) const
0774 {
0775     for (QAction *action : std::as_const(d->actions)) {
0776         if (!widget->actions().contains(action)) {
0777             widget->addAction(action);
0778         }
0779     }
0780 }
0781 
0782 void KActionCollection::addAssociatedWidget(QWidget *widget)
0783 {
0784     if (!d->associatedWidgets.contains(widget)) {
0785         widget->addActions(actions());
0786 
0787         d->associatedWidgets.append(widget);
0788         connect(widget, &QObject::destroyed, this, [this](QObject *obj) {
0789             d->_k_associatedWidgetDestroyed(obj);
0790         });
0791     }
0792 }
0793 
0794 void KActionCollection::removeAssociatedWidget(QWidget *widget)
0795 {
0796     for (QAction *action : std::as_const(d->actions)) {
0797         widget->removeAction(action);
0798     }
0799 
0800     d->associatedWidgets.removeAll(widget);
0801     disconnect(widget, &QObject::destroyed, this, nullptr);
0802 }
0803 
0804 QAction *KActionCollectionPrivate::unlistAction(QAction *action)
0805 {
0806     // ATTENTION:
0807     //   This method is called with an QObject formerly known as a QAction
0808     //   during _k_actionDestroyed(). So don't do fancy stuff here that needs a
0809     //   real QAction!
0810 
0811     // Get the index for the action
0812     int index = actions.indexOf(action);
0813 
0814     // Action not found.
0815     if (index == -1) {
0816         return nullptr;
0817     }
0818 
0819     // An action collection can't have the same action twice.
0820     Q_ASSERT(actions.indexOf(action, index + 1) == -1);
0821 
0822     // Get the actions name
0823     const QString name = action->objectName();
0824 
0825     // Remove the action
0826     actionByName.remove(name);
0827     actions.removeAt(index);
0828 
0829     // Remove the action from the categories. Should be only one
0830     const QList<KActionCategory *> categories = q->findChildren<KActionCategory *>();
0831     for (KActionCategory *category : categories) {
0832         category->unlistAction(action);
0833     }
0834 
0835     return action;
0836 }
0837 
0838 QList<QWidget *> KActionCollection::associatedWidgets() const
0839 {
0840     return d->associatedWidgets;
0841 }
0842 
0843 void KActionCollection::clearAssociatedWidgets()
0844 {
0845     for (QWidget *widget : std::as_const(d->associatedWidgets)) {
0846         for (QAction *action : std::as_const(d->actions)) {
0847             widget->removeAction(action);
0848         }
0849     }
0850 
0851     d->associatedWidgets.clear();
0852 }
0853 
0854 void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj)
0855 {
0856     associatedWidgets.removeAll(static_cast<QWidget *>(obj));
0857 }
0858 
0859 #include "moc_kactioncollection.cpp"