File indexing completed on 2024-09-08 12:23:20
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"