Kirigami-addons

kirigamiactioncollection.cpp
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 "kirigamiactioncollection.h"
17
18#include <KAuthorized>
19#include <KConfigGroup>
20#include <KSharedConfig>
21#include "debug.h"
22
23#include <QGuiApplication>
24#include <QList>
25#include <QMap>
26#include <QMetaMethod>
27#include <QSet>
28
29#include <cstdio>
30
31class KirigamiActionCollectionPrivate
32{
33public:
34 KirigamiActionCollectionPrivate(KirigamiActionCollection *qq)
35 : q(qq)
36 , configIsGlobal(false)
37 , connectTriggered(false)
38 , connectHovered(false)
39
40 {
41 }
42
43 static QList<KirigamiActionCollection *> s_allCollections;
44
45 void _k_associatedWidgetDestroyed(QObject *obj);
46 void _k_actionDestroyed(QObject *obj);
47
48 QString m_componentName;
49 QString m_componentDisplayName;
50
51 //! Remove a action from our internal bookkeeping. Returns a nullptr if the
52 //! action doesn't belong to us.
53 QAction *unlistAction(QAction *);
54
55 QMap<QString, QAction *> actionByName;
56 QList<QAction *> actions;
57
58 KirigamiActionCollection *q = nullptr;
59
60 QString configGroup{QStringLiteral("Shortcuts")};
61 bool configIsGlobal : 1;
62
63 bool connectTriggered : 1;
64 bool connectHovered : 1;
65};
66
67QList<KirigamiActionCollection *> KirigamiActionCollectionPrivate::s_allCollections;
68
70 : QObject(parent)
71 , d(new KirigamiActionCollectionPrivate(this))
72{
73 setObjectName(cName);
74 KirigamiActionCollectionPrivate::s_allCollections.append(this);
75}
76
78{
79 KirigamiActionCollectionPrivate::s_allCollections.removeAll(this);
80}
81
83{
84 d->actionByName.clear();
85 qDeleteAll(d->actions);
86 d->actions.clear();
87}
88
90{
91 QAction *action = nullptr;
92
93 if (!name.isEmpty()) {
94 action = d->actionByName.value(name);
95 }
96
97 return action;
98}
99
101{
102 // ### investigate if any apps use this at all
103 return actions().value(index);
104}
105
107{
108 return d->actions.count();
109}
110
112{
113 return count() == 0;
114}
115
117{
118 if (!cName.isEmpty()) {
119 d->m_componentName = cName;
120 } else {
121 d->m_componentName = QCoreApplication::applicationName();
122 }
123}
124
126{
127 return d->m_componentName;
128}
129
131{
132 d->m_componentDisplayName = displayName;
133}
134
136{
137 if (!d->m_componentDisplayName.isEmpty()) {
138 return d->m_componentDisplayName;
139 }
142 }
144}
145
147{
148 return d->actions;
149}
150
152{
154 for (QAction *action : std::as_const(d->actions)) {
155 if (!action->actionGroup()) {
156 ret.append(action);
157 }
158 }
159 return ret;
160}
161
163{
165 for (QAction *action : std::as_const(d->actions)) {
166 if (action->actionGroup()) {
167 set.insert(action->actionGroup());
168 }
169 }
170 return set.values();
171}
172
174{
175 if (!action) {
176 return action;
177 }
178
180 QString indexName = name;
181
182 if (indexName.isEmpty()) {
183 // No name provided. Use the objectName.
184 indexName = objectName;
185
186 } else {
187 // A name was provided. Check against objectName.
188 if ((!objectName.isEmpty()) && (objectName != indexName)) {
189 // The user specified a new name and the action already has a
190 // different one. The objectName is used for saving shortcut
191 // settings to disk. Both for local and global shortcuts.
192 qCDebug(BASEAPP_LOG) << "Registering action " << objectName << " under new name " << indexName;
193 // If there is a global shortcuts it's a very bad idea.
194 }
195
196 // Set the new name
197 action->setObjectName(indexName);
198 }
199
200 // No name provided and the action had no name. Make one up. This will not
201 // work when trying to save shortcuts. Both local and global shortcuts.
202 if (indexName.isEmpty()) {
203 indexName = QString::asprintf("unnamed-%p", (void *)action);
204 action->setObjectName(indexName);
205 }
206
207 // From now on the objectName has to have a value. Else we cannot safely
208 // remove actions.
209 Q_ASSERT(!action->objectName().isEmpty());
210
211 // look if we already have THIS action under THIS name ;)
212 if (d->actionByName.value(indexName, nullptr) == action) {
213 // This is not a multi map!
214 Q_ASSERT(d->actionByName.count(indexName) == 1);
215 return action;
216 }
217
218 if (!KAuthorized::authorizeAction(indexName)) {
219 // Disable this action
220 action->setEnabled(false);
221 action->setVisible(false);
222 action->blockSignals(true);
223 }
224
225 // Check if we have another action under this name
226 if (QAction *oldAction = d->actionByName.value(indexName)) {
227 takeAction(oldAction);
228 }
229
230 // Check if we have this action under a different name.
231 // Not using takeAction because we don't want to remove it from categories,
232 // and because it has the new name already.
233 const int oldIndex = d->actions.indexOf(action);
234 if (oldIndex != -1) {
235 d->actionByName.remove(d->actionByName.key(action));
236 d->actions.removeAt(oldIndex);
237 }
238
239 // Add action to our lists.
240 d->actionByName.insert(indexName, action);
241 d->actions.append(action);
242
243 connect(action, &QObject::destroyed, this, [this](QObject *obj) {
244 d->_k_actionDestroyed(obj);
245 });
246
247 if (d->connectHovered) {
248 connect(action, &QAction::hovered, this, &KirigamiActionCollection::slotActionHovered);
249 }
250
251 if (d->connectTriggered) {
252 connect(action, &QAction::triggered, this, &KirigamiActionCollection::slotActionTriggered);
253 }
254
256 Q_EMIT changed();
257 return action;
258}
259
266
271
273{
274 if (!d->unlistAction(action)) {
275 return nullptr;
276 }
277
278 action->disconnect(this);
279
280 Q_EMIT changed();
281 return action;
282}
283
285{
287 return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
288}
289
294
299
301{
302 action->setShortcuts(shortcuts);
303 action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts));
304}
305
307{
308 // Considered as true by default
309 const QVariant value = action->property("isShortcutConfigurable");
310 return value.isValid() ? value.toBool() : true;
311}
312
314{
315 action->setProperty("isShortcutConfigurable", configurable);
316}
317
318QString KirigamiActionCollection::configGroup() const
319{
320 return d->configGroup;
321}
322
324{
325 d->configGroup = group;
326}
327
328bool KirigamiActionCollection::configIsGlobal() const
329{
330 return d->configIsGlobal;
331}
332
334{
335 d->configIsGlobal = global;
336}
337
339{
340 KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
341 if (!config) {
342 config = &cg;
343 }
344
345 if (!config->exists()) {
346 return;
347 }
348
349 for (auto it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
350 QAction *action = it.value();
351 if (!action) {
352 continue;
353 }
354
356 const QString &actionName = it.key();
357 QString entry = config->readEntry(actionName, QString());
358 if (!entry.isEmpty()) {
360 } else {
362 }
363 }
364 }
365
366 // qCDebug(BASEAPP_LOG) << " done";
367}
368
369void KirigamiActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const
370{
371 KConfigGroup cg(KSharedConfig::openConfig(), configGroup());
372 if (!config) {
373 config = &cg;
374 }
375
376 QList<QAction *> writeActions;
377 if (oneAction) {
378 writeActions.append(oneAction);
379 } else {
380 writeActions = actions();
381 }
382
383 for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) {
384 QAction *action = it.value();
385 if (!action) {
386 continue;
387 }
388
389 const QString &actionName = it.key();
390
391 // If the action name starts with unnamed- spit out a warning and ignore
392 // it. That name will change at will and will break loading writing
393 if (actionName.startsWith(QLatin1String("unnamed-"))) {
394 qCCritical(BASEAPP_LOG) << "Skipped saving Shortcut for action without name " << action->text() << "!";
395 continue;
396 }
397
398 // Write the shortcut
400 bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
401 bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action));
402 // If we're using a global config or this setting
403 // differs from the default, then we want to write.
405
406 // Honor the configIsGlobal() setting
407 if (configIsGlobal()) {
408 flags |= KConfigGroup::Global;
409 }
410
411 if (writeAll || !bSameAsDefault) {
412 // We are instructed to write all shortcuts or the shortcut is
413 // not set to its default value. Write it
415 if (s.isEmpty()) {
416 s = QStringLiteral("none");
417 }
418 qCDebug(BASEAPP_LOG) << "\twriting " << actionName << " = " << s;
419 config->writeEntry(actionName, s, flags);
420 } else if (bConfigHasAction) {
421 // Otherwise, this key is the same as default but exists in
422 // config file. Remove it.
423 qCDebug(BASEAPP_LOG) << "\tremoving " << actionName << " because == default";
424 config->deleteEntry(actionName, flags);
425 }
426 }
427 }
428
429 config->sync();
430}
431
432void KirigamiActionCollection::slotActionTriggered()
433{
435 if (action) {
437 }
438}
439
440void KirigamiActionCollection::slotActionHovered()
441{
443 if (action) {
445 }
446}
447
448// The downcast from a QObject to a QAction triggers UBSan
449// but we're only comparing pointers, so UBSan shouldn't check vptrs
450// Similar to https://github.com/itsBelinda/plog/pull/1/files
451#if defined(__clang__) || __GNUC__ >= 8
452__attribute__((no_sanitize("vptr")))
453#endif
454void KirigamiActionCollectionPrivate::_k_actionDestroyed(QObject *obj)
455{
456 // obj isn't really a QAction anymore. So make sure we don't do fancy stuff
457 // with it.
458 QAction *action = static_cast<QAction *>(obj);
459
460 if (!unlistAction(action)) {
461 return;
462 }
463
464 Q_EMIT q->changed();
465}
466
468{
469 if (d->connectHovered && d->connectTriggered) {
470 return;
471 }
472
473 if (signal.methodSignature() == "actionHovered(QAction*)") {
474 if (!d->connectHovered) {
475 d->connectHovered = true;
476 for (QAction *action : std::as_const(d->actions)) {
477 connect(action, &QAction::hovered, this, &KirigamiActionCollection::slotActionHovered);
478 }
479 }
480
481 } else if (signal.methodSignature() == "actionTriggered(QAction*)") {
482 if (!d->connectTriggered) {
483 d->connectTriggered = true;
484 for (QAction *action : std::as_const(d->actions)) {
485 connect(action, &QAction::triggered, this, &KirigamiActionCollection::slotActionTriggered);
486 }
487 }
488 }
489
491}
492
494{
495 return KirigamiActionCollectionPrivate::s_allCollections;
496}
497
498QAction *KirigamiActionCollectionPrivate::unlistAction(QAction *action)
499{
500 // ATTENTION:
501 // This method is called with an QObject formerly known as a QAction
502 // during _k_actionDestroyed(). So don't do fancy stuff here that needs a
503 // real QAction!
504
505 // Get the index for the action
506 int index = actions.indexOf(action);
507
508 // Action not found.
509 if (index == -1) {
510 return nullptr;
511 }
512
513 // An action collection can't have the same action twice.
514 Q_ASSERT(actions.indexOf(action, index + 1) == -1);
515
516 // Get the actions name
517 const QString name = action->objectName();
518
519 // Remove the action
520 actionByName.remove(name);
521 actions.removeAt(index);
522
523 return action;
524}
525
526#include "moc_kirigamiactioncollection.cpp"
static Q_INVOKABLE bool authorizeAction(const QString &action)
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool exists() const
bool sync() override
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
A container for a set of QAction objects.
void changed()
Emitted when an action has been inserted into, or removed from, this action collection.
void inserted(QAction *action)
Indicates that action was inserted into this action collection.
~KirigamiActionCollection() override
Destructor.
void actionTriggered(QAction *action)
Indicates that action was triggered.
void writeSettings(KConfigGroup *config=nullptr, bool writeDefaults=false, QAction *oneAction=nullptr) const
Write the current configurable key associations to config.
static QList< QKeySequence > defaultShortcuts(QAction *action)
Get the default shortcuts for the given action.
void connectNotify(const QMetaMethod &signal) override
Overridden to perform connections when someone wants to know whether an action was highlighted or tri...
QAction * takeAction(QAction *action)
Removes an action from the collection.
void removeAction(QAction *action)
Removes an action from the collection and deletes it.
KirigamiActionCollection(QObject *parent, const QString &cName=QString())
Constructor.
void setComponentDisplayName(const QString &displayName)
Set the component display name associated with this action collection.
QString componentName() const
The component name with which this class is associated.
int count() const
Returns the number of actions in the collection.
const QList< QAction * > actionsWithoutGroup() const
Returns the list of QActions without an QAction::actionGroup() which belong to this action collection...
static void setShortcutsConfigurable(QAction *action, bool configurable)
Indicate whether the user may configure the action's shortcuts.
static void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
Set the default shortcut for the given action.
static const QList< KirigamiActionCollection * > & allCollections()
Access the list of all action collections in existence for this app.
bool isEmpty() const
Returns whether the action collection is empty or not.
static bool isShortcutsConfigurable(QAction *action)
Returns true if the given action's shortcuts may be configured by the user.
Q_INVOKABLE QAction * addAction(const QString &name, QAction *action)
Add an action under the given name to the collection.
void setComponentName(const QString &componentName)
Set the componentName associated with this action collection.
void readSettings(KConfigGroup *config=nullptr)
Read all key associations from config.
void actionHovered(QAction *action)
Indicates that action was hovered.
void addActions(const QList< QAction * > &actions)
Adds a list of actions to the collection.
void setConfigGroup(const QString &group)
Sets group as the KConfig group with which settings will be loaded and saved.
void setConfigGlobal(bool global)
Set whether this action collection's configuration should be global to KDE ( true ),...
static Q_INVOKABLE void setDefaultShortcuts(QAction *action, const QList< QKeySequence > &shortcuts)
Set the default shortcuts for the given action.
QList< QAction * > actions() const
Returns the list of QActions which belong to this action collection.
QAction * action(int index) const
Return the QAction* at position index in the action collection.
QString componentDisplayName() const
The display name for the associated component.
const QList< QActionGroup * > actionGroups() const
Returns the list of all QActionGroups associated with actions in this action collection.
void clear()
Clears the entire action collection, deleting all actions.
static QKeySequence defaultShortcut(QAction *action)
Get the default primary shortcut for the given action.
QString name(GameStandardAction id)
QActionGroup * actionGroup() const const
void setEnabled(bool)
void hovered()
void setShortcuts(QKeySequence::StandardKey key)
QList< QKeySequence > shortcuts() const const
void triggered(bool checked)
void setVisible(bool)
QList< QKeySequence > listFromString(const QString &str, SequenceFormat format)
QString listToString(const QList< QKeySequence > &list, SequenceFormat format)
void append(QList< T > &&value)
T & first()
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
qsizetype removeAll(const AT &t)
void removeAt(qsizetype i)
T value(qsizetype i) const const
ConstIterator
size_type remove(const Key &key)
QByteArray methodSignature() const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual void connectNotify(const QMetaMethod &signal)
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
QObject * sender() const const
void setObjectName(QAnyStringView name)
bool setProperty(const char *name, QVariant &&value)
iterator insert(const T &value)
QList< T > values() const const
QString asprintf(const char *cformat,...)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QVariant fromValue(T &&value)
bool isValid() const const
bool toBool() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:33:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.