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