Krita Source Code Documentation
Loading...
Searching...
No Matches
kactioncollection.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
3 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
4 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
5 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
6 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
7 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
8 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
9 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
10 SPDX-FileCopyrightText: 2005-2007 Hamish Rodda <rodda@kde.org>
11
12 SPDX-License-Identifier: LGPL-2.0-only
13*/
14
15#include "kactioncollection.h"
16#include "config-xmlgui.h"
17#include "kactioncategory.h"
18#include "kxmlguiclient.h"
19#include "kxmlguifactory.h"
20#include "kis_action_registry.h"
21
22#include <kconfiggroup.h>
23#include <ksharedconfig.h>
24
25#include <QDebug>
26#include <QDomDocument>
27#include <QSet>
28#include <QGuiApplication>
29#include <QMap>
30#include <QList>
31#include <QAction>
32#include <QMetaMethod>
33#include <QTextStream>
34#include <QWidget>
35
36#include <stdio.h>
37
38#if defined(KCONFIG_BEFORE_5_24)
39# define authorizeAction authorizeKAction
40#endif
41
43{
44public:
47 configGroup(QStringLiteral("Shortcuts")),
48 q(0)
49
50 {
51 }
52
53 void setComponentForAction(QAction *action)
54 {
55 Q_UNUSED(action);
56 }
57
59
60 void _k_associatedWidgetDestroyed(QObject *obj);
61 void _k_actionDestroyed(QObject *obj);
62
64
67
70 QAction *unlistAction(QObject *);
71
72 QMap<QString, QAction *> actionByName;
74
76
77 QString configGroup;
78
79 bool connectTriggered {false};
80 bool connectHovered {false};
81
83
85};
86
88
89KisKActionCollection::KisKActionCollection(QObject *parent, const QString &cName)
90 : QObject(parent)
92{
93 d->q = this;
95
96 setComponentName(cName);
97}
98
100 : QObject(0)
102{
103 d->q = this;
105
106 d->m_parentGUIClient = parent;
107 d->m_componentName = parent->componentName();
108}
109
116
117
119{
120 return this->findChildren<KisKActionCategory *>();
121}
122
124 KisKActionCategory *category = 0;
125 foreach (KisKActionCategory *c, categories()) {
126 if (c->text() == name) {
127 category = c;
128 }
129 }
130
131 if (category == 0) {
132 category = new KisKActionCategory(name, this);
133 }
134 return category;
135}
136
137
139{
140 d->actionByName.clear();
141 qDeleteAll(d->actions);
142 d->actions.clear();
143}
144
145QAction *KisKActionCollection::action(const QString &name) const
146{
147 QAction *action = 0L;
148
149 if (!name.isEmpty()) {
150 action = d->actionByName.value(name);
151 }
152
153 return action;
154}
155
156QAction *KisKActionCollection::action(int index) const
157{
158 // ### investigate if any apps use this at all
159 return actions().value(index);
160}
161
163{
164 return d->actions.count();
165}
166
168{
169 return count() == 0;
170}
171
173{
174 if (count() > 0) {
175 // Its component name is part of an action's signature in the context of
176 // global shortcuts and the semantics of changing an existing action's
177 // signature are, as it seems, impossible to get right.
178 // As of now this only matters for global shortcuts. We could
179 // thus relax the requirement and only refuse to change the component data
180 // if we have actions with global shortcuts in this collection.
181 qWarning() << "this does not work on a KisKActionCollection containing actions!";
182 }
183
184 if (!cName.isEmpty()) {
185 d->m_componentName = cName;
186 } else {
187 d->m_componentName = QCoreApplication::applicationName();
188 }
189}
190
192{
193 return d->m_componentName;
194}
195
196void KisKActionCollection::setComponentDisplayName(const QString &displayName)
197{
198 d->m_componentDisplayName = displayName;
199}
200
202{
203 if (!d->m_componentDisplayName.isEmpty()) {
205 }
206 if (!QGuiApplication::applicationDisplayName().isEmpty()) {
207 return QGuiApplication::applicationDisplayName();
208 }
209 return QCoreApplication::applicationName();
210}
211
216
221
223{
225 Q_FOREACH (QAction *action, d->actions)
226 if (!action->actionGroup()) {
227 ret.append(action);
228 }
229 return ret;
230}
231
233{
234 QSet<QActionGroup *> set;
235 Q_FOREACH (QAction *action, d->actions)
236 if (action->actionGroup()) {
237 set.insert(action->actionGroup());
238 }
239 return QList<QActionGroup*>(set.begin(), set.end());
240}
241
242QAction *KisKActionCollection::addCategorizedAction(const QString &name, QAction *action, const QString &categoryName)
243{
244 return getCategory(categoryName)->addAction(name, action);
245}
246
247QAction *KisKActionCollection::addAction(const QString &name, QAction *action)
248{
249 if (!action) {
250 return action;
251 }
252
253 const QString objectName = action->objectName();
254 QString indexName = name;
255
256 if (indexName.isEmpty()) {
257 // No name provided. Use the objectName.
258 indexName = objectName;
259
260 } else {
261 // Set the new name
262 action->setObjectName(indexName);
263 }
264
265 // No name provided and the action had no name. Make one up. This will not
266 // work when trying to save shortcuts.
267 if (indexName.isEmpty()) {
268 QTextStream(&indexName) << (void *)action;
269
270 action->setObjectName(indexName);
271 }
272
273 // From now on the objectName has to have a value. Else we cannot safely
274 // remove actions.
275 Q_ASSERT(!action->objectName().isEmpty());
276
277 // look if we already have THIS action under THIS name ;)
278 if (d->actionByName.value(indexName, 0) == action) {
279 // This is not a multi map!
280 Q_ASSERT(d->actionByName.count(indexName) == 1);
281 return action;
282 }
283
284 // Check if we have another action under this name
285 if (QAction *oldAction = d->actionByName.value(indexName)) {
286 takeAction(oldAction);
287 }
288
289 // Check if we have this action under a different name.
290 // Not using takeAction because we don't want to remove it from categories,
291 // and because it has the new name already.
292 const int oldIndex = d->actions.indexOf(action);
293 if (oldIndex != -1) {
294 d->actionByName.remove(d->actionByName.key(action));
295 d->actions.removeAt(oldIndex);
296 }
297
298 // Add action to our lists.
299 d->actionByName.insert(indexName, action);
300 d->actions.append(action);
301
302 Q_FOREACH (QWidget *widget, d->associatedWidgets) {
303 widget->addAction(action);
304 }
305
306 connect(action, SIGNAL(destroyed(QObject*)), SLOT(_k_actionDestroyed(QObject*)));
307
309
310 if (d->connectHovered) {
311 connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
312 }
313
314 if (d->connectTriggered) {
315 connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));
316 }
317
318 Q_EMIT inserted(action);
319 return action;
320}
321
323{
324 Q_FOREACH (QAction *action, actions) {
325 addAction(action->objectName(), action);
326 }
327}
328
330{
331 delete takeAction(action);
332}
333
334QAction *KisKActionCollection::takeAction(QAction *action)
335{
336 if (!d->unlistAction(action)) {
337 return 0;
338 }
339
340 // Remove the action from all widgets
341 Q_FOREACH (QWidget *widget, d->associatedWidgets) {
342 widget->removeAction(action);
343 }
344
345 action->disconnect(this);
346
347 return action;
348}
349
350QAction *KisKActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
351{
352 QAction *action = KStandardAction::create(actionType, receiver, member, this);
353 return action;
354}
355
357 const QObject *receiver, const char *member)
358{
359 // pass 0 as parent, because if the parent is a KisKActionCollection KStandardAction::create automatically
360 // adds the action to it under the default name. We would trigger the
361 // warning about renaming the action then.
362 QAction *action = KStandardAction::create(actionType, receiver, member, 0);
363 // Give it a parent for gc.
364 action->setParent(this);
365 // Remove the name to get rid of the "rename action" warning above
366 action->setObjectName(name);
367 // And now add it with the desired name.
368 return addAction(name, action);
369}
370
371QAction *KisKActionCollection::addAction(const QString &name, const QObject *receiver, const char *member)
372{
373 QAction *a = new QAction(this);
374 if (receiver && member) {
375 connect(a, SIGNAL(triggered(bool)), receiver, member);
376 }
377 return addAction(name, a);
378}
379
380QKeySequence KisKActionCollection::defaultShortcut(QAction *action) const
381{
383 return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
384}
385
387{
388 return action->property("defaultShortcuts").value<QList<QKeySequence> >();
389}
390
391void KisKActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
392{
394}
395
397{
398 action->setShortcuts(shortcuts);
399 action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts));
400}
401
403{
404 // Considered as true by default
405 const QVariant value = action->property("isShortcutConfigurable");
406 return value.isValid() ? value.toBool() : true;
407}
408
409void KisKActionCollection::setShortcutsConfigurable(QAction *action, bool configurable)
410{
411 action->setProperty("isShortcutConfigurable", configurable);
412}
413
415{
416 return d->configGroup;
417}
418
419void KisKActionCollection::setConfigGroup(const QString &group)
420{
421 d->configGroup = group;
422}
423
425{
426 auto actionRegistry = KisActionRegistry::instance();
427
428 for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
429 it != d->actionByName.constEnd(); ++it) {
430 actionRegistry->updateShortcut(it.key(), it.value());
431 }
432}
433
434
436{
437 auto ar = KisActionRegistry::instance();
438 ar->loadCustomShortcuts();
439
440 for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
441 it != d->actionByName.constEnd(); ++it) {
442 QAction *action = it.value();
443 if (!action) {
444 continue;
445 }
446
448 QString actionName = it.key();
449 ar->updateShortcut(actionName, action);
450 }
451 }
452}
453
454
456{
457 const KisKXMLGUIClient *kxmlguiClient = q->parentGUIClient();
458 // return false if there is no KisKXMLGUIClient
459 if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) {
460 return false;
461 }
462
463
464 QString attrShortcut = QStringLiteral("shortcut");
465
466 // Read XML file
467 QString sXml(KisKXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentName()));
468 QDomDocument doc;
469 doc.setContent(sXml);
470
471 // Process XML data
472
473 // Get hold of ActionProperties tag
474 QDomElement elem = KisKXMLGUIFactory::actionPropertiesElement(doc);
475
476 // now, iterate through our actions
477 for (QMap<QString, QAction *>::ConstIterator it = actionByName.constBegin();
478 it != actionByName.constEnd(); ++it) {
479 QAction *action = it.value();
480 if (!action) {
481 continue;
482 }
483
484 QString actionName = it.key();
485
486 // If the action name starts with unnamed- spit out a warning and ignore
487 // it. That name will change at will and will break loading writing
488 if (actionName.startsWith(QLatin1String("unnamed-"))) {
489 qCritical() << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!";
490 continue;
491 }
492
493 bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action));
494
495 // now see if this element already exists
496 // and create it if necessary (unless bSameAsDefault)
497 QDomElement act_elem = KisKXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault);
498 if (act_elem.isNull()) {
499 continue;
500 }
501
502 if (bSameAsDefault) {
503 act_elem.removeAttribute(attrShortcut);
504 if (act_elem.attributes().count() == 1) {
505 elem.removeChild(act_elem);
506 }
507 } else {
508 act_elem.setAttribute(attrShortcut, QKeySequence::listToString(action->shortcuts()));
509 }
510 }
511
512 // Write back to XML file
514 return true;
515}
516
517void KisKActionCollection::writeSettings(KConfigGroup *config,
518 bool writeScheme,
519 QAction *oneAction) const
520{
521 // If the caller didn't provide a config group we try to save the KisKXMLGUI
522 // Configuration file. (This will work if the parentGUI was set and has a
523 // valid configuration file.)
524 if (config == 0 && d->writeKisKXMLGUIConfigFile()) {
525 return;
526 }
527
528
529 KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
530 if (!config) {
531 config = &cg;
532 }
533
534 QList<QAction *> writeActions;
535 if (oneAction) {
536 writeActions.append(oneAction);
537 } else {
538 writeActions = actions();
539 }
540
541 for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
542 it != d->actionByName.constEnd(); ++it) {
543
544 QAction *action = it.value();
545 if (!action) {
546 continue;
547 }
548
549 QString actionName = it.key();
550
551 // If the action name starts with unnamed- spit out a warning and ignore
552 // it. That name will change at will and will break loading writing
553 if (actionName.startsWith(QLatin1String("unnamed-"))) {
554 qCritical() << "Skipped saving shortcut for action without name " \
555 << action->text() << "!";
556 continue;
557 }
558
559 // Write the shortcut
561 bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
562 bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action));
563 // If the current shortcut differs from the default, we want to write.
564
565 KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
566
567 if (writeScheme || !bSameAsDefault) {
568 // We are instructed to write all shortcuts or the shortcut is
569 // not set to its default value. Write it
570 QString s = QKeySequence::listToString(action->shortcuts());
571 if (s.isEmpty()) {
572 s = QStringLiteral("none");
573 }
574 config->writeEntry(actionName, s, flags);
575 } else if (bConfigHasAction) {
576 // This key is the same as default but exists in config file.
577 // Remove it.
578 config->deleteEntry(actionName, flags);
579 }
580 }
581 }
582
583 config->sync();
584}
585
587{
588 QAction *action = qobject_cast<QAction *>(sender());
589 if (action) {
590 Q_EMIT actionTriggered(action);
591 }
592}
593
598
600{
601 QAction *action = qobject_cast<QAction *>(sender());
602 if (action) {
604 Q_EMIT actionHovered(action);
605 }
606}
607
612
613void KisKActionCollection::connectNotify(const QMetaMethod &signal)
614{
616 return;
617 }
618
619 if (signal.methodSignature() == "actionHighlighted(QAction*)" ||
620 signal.methodSignature() == "actionHovered(QAction*)") {
621 if (!d->connectHovered) {
622 d->connectHovered = true;
623 Q_FOREACH (QAction *action, actions()) {
624 connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
625 }
626 }
627
628 } else if (signal.methodSignature() == "actionTriggered(QAction*)") {
629 if (!d->connectTriggered) {
630 d->connectTriggered = true;
631 Q_FOREACH (QAction *action, actions()) {
632 connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));
633 }
634 }
635 }
636
637 QObject::connectNotify(signal);
638}
639
644
645void KisKActionCollection::associateWidget(QWidget *widget) const
646{
647 Q_FOREACH (QAction *action, actions()) {
648 if (!widget->actions().contains(action)) {
649 widget->addAction(action);
650 }
651 }
652}
653
655{
656 if (!d->associatedWidgets.contains(widget)) {
657 widget->addActions(actions());
658
659 d->associatedWidgets.append(widget);
660 connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*)));
661 }
662}
663
665{
666 Q_FOREACH (QAction *action, actions()) {
667 widget->removeAction(action);
668 }
669
670 d->associatedWidgets.removeAll(widget);
671 disconnect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*)));
672}
673
675{
676 // ATTENTION:
677 // This method is called with an QObject formerly known as a QAction
678 // during _k_actionDestroyed(). So don't do fancy stuff here that needs a
679 // real QAction!
680
681 // Get the index for the action
682 const auto indexOf = [&](const QObject *action, int from = 0) -> int {
683 for (int i = from; i < actions.size(); i++) {
684 if (actions[i] == action) {
685 return i;
686 }
687 }
688 return -1;
689 };
690
691 int index = indexOf(action);
692
693 // Action not found.
694 if (index == -1) {
695 return 0;
696 }
697
698 // An action collection can't have the same action twice.
699 Q_ASSERT(indexOf(action, index + 1) == -1);
700
701 // Get the actions name
702 const QString name = action->objectName();
703
704 // Remove the action
705 actionByName.remove(name);
706 // Retrieve the typed QObject* pointer
707 // ATTENTION: THIS ACTION IS PARTIALLY DESTROYED!
708 QAction *tmp = actions.at(index);
709 actions.removeAt(index);
710
711 // Remove the action from the categories. Should be only one
712 QList<KisKActionCategory *> categories = q->findChildren<KisKActionCategory *>();
713 Q_FOREACH (KisKActionCategory *category, categories) {
714 category->unlistAction(tmp);
715 }
716
717 return tmp;
718}
719
724
726{
727 Q_FOREACH (QWidget *widget, d->associatedWidgets)
728 Q_FOREACH (QAction *action, actions()) {
729 widget->removeAction(action);
730 }
731
732 d->associatedWidgets.clear();
733}
734
736{
737 associatedWidgets.removeAll(static_cast<QWidget *>(obj));
738}
739
740#include "moc_kactioncollection.cpp"
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static KisActionRegistry * instance()
void unlistAction(QAction *action)
QAction * addAction(const QString &name, QAction *action)
const KisKXMLGUIClient * m_parentGUIClient
void _k_actionDestroyed(QObject *obj)
QAction * unlistAction(QObject *)
QMap< QString, QAction * > actionByName
void _k_associatedWidgetDestroyed(QObject *obj)
void setComponentForAction(QAction *action)
static QList< KisKActionCollection * > s_allCollections
QList< QWidget * > associatedWidgets
A container for a set of QAction objects.
bool isShortcutsConfigurable(QAction *action) const
Q_INVOKABLE void setDefaultShortcuts(QAction *action, const QList< QKeySequence > &shortcuts)
void inserted(QAction *action)
QList< QKeySequence > defaultShortcuts(QAction *action) const
void setComponentDisplayName(const QString &displayName)
QAction * takeAction(QAction *action)
QT_MOC_COMPAT void actionHighlighted(QAction *action)
QList< KisKActionCategory * > categories() const
QString componentName() const
void setConfigGroup(const QString &group)
QList< QWidget * > associatedWidgets() const
void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
Q_INVOKABLE QAction * addAction(const QString &name, QAction *action)
const QList< QAction * > actionsWithoutGroup() const
const QList< QActionGroup * > actionGroups() const
QString componentDisplayName() const
const KisKXMLGUIClient * parentGUIClient() const
virtual void slotActionTriggered()
KisKActionCollection(QObject *parent, const QString &cName=QString())
KisKActionCategory * getCategory(const QString &categoryName)
void associateWidget(QWidget *widget) const
class KisKActionCollectionPrivate *const d
void addActions(const QList< QAction * > &actions)
QKeySequence defaultShortcut(QAction *action) const
void removeAssociatedWidget(QWidget *widget)
static const QList< KisKActionCollection * > & allCollections()
void writeSettings(KConfigGroup *config=0, bool writeScheme=false, QAction *oneAction=0) const
Q_INVOKABLE QAction * addCategorizedAction(const QString &name, QAction *action, const QString &categoryName)
void setComponentName(const QString &componentName)
void setShortcutsConfigurable(QAction *action, bool configurable)
void actionTriggered(QAction *action)
QAction * action(int index) const
void connectNotify(const QMetaMethod &signal) override
Overridden to perform connections when someone wants to know whether an action was highlighted or tri...
void removeAction(QAction *action)
QList< QAction * > actions() const
virtual QT_MOC_COMPAT void slotActionHighlighted()
void addAssociatedWidget(QWidget *widget)
void actionHovered(QAction *action)
virtual QString xmlFile() const
virtual QString localXMLFile() const
static QDomElement actionPropertiesElement(QDomDocument &doc)
static bool saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &componentName=QString())
static QString readConfigFile(const QString &filename, const QString &componentName=QString())
static QDomElement findActionByName(QDomElement &elem, const QString &sName, bool create)
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)