File indexing completed on 2024-03-24 04:02:23
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"