QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
qgsshortcutsmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsshortcutsmanager.cpp
3 ---------------------
4 begin : May 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsshortcutsmanager.h"
17
18#include "qgsapplication.h"
19#include "qgslogger.h"
20#include "qgssettings.h"
21
22#include <QPalette>
23#include <QRegularExpression>
24#include <QShortcut>
25#include <QString>
26#include <QWidgetAction>
27
28#include "moc_qgsshortcutsmanager.cpp"
29
30using namespace Qt::StringLiterals;
31
32QgsShortcutsManager::QgsShortcutsManager( QObject *parent, const QString &settingsRoot )
33 : QObject( parent )
34 , mSettingsPath( settingsRoot )
35{
36 // Register common actions
37 auto registerCommonAction = [this]( CommonAction commonAction, const QIcon &icon, const QString &text, const QString &toolTip, const QString &sequence, const QString &objectName, const QString &section ) {
38 QAction *action = new QAction( icon, text, this );
39 action->setToolTip( toolTip );
40 setObjectName( objectName );
41 // We do not want these actions to be enabled, they are just there to be able to change
42 // the shortcuts in the Shortcuts Manager.
43 action->setEnabled( false );
44 action->setProperty( "commonAction", static_cast< int >( commonAction ) );
45 registerAction( action, sequence, section );
46 mCommonActions.insert( static_cast< int >( commonAction ), action );
47 };
48
49#ifdef Q_OS_WIN
50 registerCommonAction( CommonAction::CodeToggleComment, QgsApplication::getThemeIcon( u"console/iconCommentEditorConsole.svg"_s, QgsApplication::palette().color( QPalette::ColorRole::WindowText ) ), tr( "Toggle Comment" ), tr( "Toggle comment" ), u"Ctrl+:"_s, u"mEditorToggleComment"_s, u"Editor"_s );
51#else
52 registerCommonAction( CommonAction::CodeToggleComment, QgsApplication::getThemeIcon( u"console/iconCommentEditorConsole.svg"_s, QgsApplication::palette().color( QPalette::ColorRole::WindowText ) ), tr( "Toggle Comment" ), tr( "Toggle comment" ), u"Ctrl+/"_s, u"mEditorToggleComment"_s, u"Editor"_s );
53#endif
54 registerCommonAction( CommonAction::CodeReformat, QgsApplication::getThemeIcon( u"console/iconFormatCode.svg"_s ), tr( "Reformat Code" ), tr( "Reformat code" ), u"Ctrl+Alt+F"_s, u"mEditorReformatCode"_s, u"Editor"_s );
55 registerCommonAction( CommonAction::CodeRunScript, QgsApplication::getThemeIcon( u"mActionStart.svg"_s ), tr( "Run Script" ), tr( "Run entire script" ), u"Ctrl+Shift+E"_s, u"mEditorRunScript"_s, u"Editor"_s );
56 registerCommonAction( CommonAction::CodeRunSelection, QgsApplication::getThemeIcon( u"mActionRunSelected.svg"_s ), tr( "Run Selection" ), tr( "Run selected part of script" ), u"Ctrl+E"_s, u"mEditorRunSelection"_s, u"Editor"_s );
57}
58
60{
61 // delete all common actions BEFORE this object is destroyed -- they have a lambda connection which
62 // we do NOT want to be triggered during the qt child object cleanup which will occur after this destructor
63 const QHash< int, QAction * > commonActionsToCleanup = std::move( mCommonActions );
64 for ( auto it = commonActionsToCleanup.constBegin(); it != commonActionsToCleanup.constEnd(); ++it )
65 {
66 delete it.value();
67 }
68}
69
70void QgsShortcutsManager::registerAllChildren( QObject *object, bool recursive, const QString &section )
71{
72 registerAllChildActions( object, recursive, section );
73 registerAllChildShortcuts( object, recursive, section );
74}
75
76void QgsShortcutsManager::registerAllChildActions( QObject *object, bool recursive, const QString &section )
77{
78 const QList<QObject *> children = object->children();
79 for ( QObject *child : children )
80 {
81 if ( QAction *a = qobject_cast<QAction *>( child ) )
82 {
83 registerAction( a, a->shortcut().toString( QKeySequence::NativeText ), section );
84 }
85 else if ( recursive )
86 {
87 const QString newSection = child->objectName().isEmpty() ? section : section + child->objectName() + "/";
88 registerAllChildActions( child, recursive, newSection );
89 }
90 }
91}
92
93void QgsShortcutsManager::registerAllChildShortcuts( QObject *object, bool recursive, const QString &section )
94{
95 const QList<QObject *> children = object->children();
96 for ( QObject *child : children )
97 {
98 if ( QShortcut *s = qobject_cast<QShortcut *>( child ) )
99 {
100 registerShortcut( s, s->key().toString( QKeySequence::NativeText ), section );
101 }
102 else if ( recursive )
103 {
104 const QString newSection = child->objectName().isEmpty() ? section : section + child->objectName() + "/";
105 registerAllChildShortcuts( child, recursive, newSection );
106 }
107 }
108}
109
110bool QgsShortcutsManager::registerAction( QAction *action, const QString &defaultSequence, const QString &section )
111{
112 if ( qobject_cast<QWidgetAction *>( action ) )
113 return false;
114
115 if ( mActions.contains( action ) )
116 return false; // already registered
117
118 // if using a debug build, warn on duplicate or non-compliant actions
119 if ( action->text().isEmpty() && action->objectName().isEmpty() )
120 {
121#ifdef QGISDEBUG
122 QgsLogger::warning( u"Action has no text set: %1"_s.arg( action->objectName() ) );
123#endif
124 return false;
125 }
126
127 QString key = action->objectName().isEmpty() ? action->text() : action->objectName();
128 key.remove( '&' ); // remove the accelerator
129
130#ifdef QGISDEBUG
131 if ( actionByName( key ) || shortcutByName( key ) )
132 QgsLogger::warning( u"Duplicate shortcut registered: %1"_s.arg( key ) );
133#endif
134
135 const QString settingKey = mSettingsPath + ( section.isEmpty() || section.endsWith( '/'_L1 ) ? section : section + u"/"_s ) + key;
136
137 mActions.insert( action, { defaultSequence, settingKey } );
138 connect( action, &QObject::destroyed, this, [action, this]() { actionDestroyed( action ); } );
139
140 // load overridden value from settings
141 const QgsSettings settings;
142 const QString sequence = settings.value( settingKey, defaultSequence ).toString();
143
144 action->setShortcut( sequence );
145 if ( !action->toolTip().isEmpty() )
146 {
147 action->setToolTip( formatActionToolTip( action->toolTip() ) );
148 updateActionToolTip( action, sequence );
149 }
150
151 return true;
152}
153
155{
156 const auto it = mCommonActions.constFind( static_cast< int >( commonAction ) );
157 if ( it == mCommonActions.constEnd() )
158 return;
159
160 // copy properties from common action
161 action->setText( it.value()->text() );
162 action->setToolTip( it.value()->toolTip() );
163 action->setShortcut( it.value()->shortcut() );
164
165 mLinkedCommonActions.insert( action, commonAction );
166 connect( action, &QObject::destroyed, this, [action, this]() { actionDestroyed( action ); } );
167}
168
169bool QgsShortcutsManager::registerShortcut( QShortcut *shortcut, const QString &defaultSequence, const QString &section )
170{
171#ifdef QGISDEBUG
172 // if using a debug build, warn on duplicate or non-compliant actions
173 if ( shortcut->objectName().isEmpty() )
174 QgsLogger::warning( u"Shortcut has no object name set: %1"_s.arg( shortcut->key().toString() ) );
175 else if ( actionByName( shortcut->objectName() ) || shortcutByName( shortcut->objectName() ) )
176 QgsLogger::warning( u"Duplicate shortcut registered: %1"_s.arg( shortcut->objectName() ) );
177#endif
178
179 const QString settingKey = mSettingsPath + ( section.isEmpty() || section.endsWith( '/'_L1 ) ? section : section + u"/"_s ) + shortcut->objectName();
180
181 mShortcuts.insert( shortcut, { defaultSequence, settingKey } );
182 connect( shortcut, &QObject::destroyed, this, [shortcut, this]() { shortcutDestroyed( shortcut ); } );
183
184 // load overridden value from settings
185 const QgsSettings settings;
186 const QString keySequence = settings.value( settingKey, defaultSequence ).toString();
187
188 shortcut->setKey( keySequence );
189
190 return true;
191}
192
194{
195 if ( !mActions.contains( action ) )
196 return false;
197
198 mActions.remove( action );
199 return true;
200}
201
203{
204 if ( !mShortcuts.contains( shortcut ) )
205 return false;
206
207 mShortcuts.remove( shortcut );
208 return true;
209}
210
211QList<QAction *> QgsShortcutsManager::listActions() const
212{
213 return mActions.keys();
214}
215
216QList<QShortcut *> QgsShortcutsManager::listShortcuts() const
217{
218 return mShortcuts.keys();
219}
220
221QList<QObject *> QgsShortcutsManager::listAll() const
222{
223 QList<QObject *> list;
224 ActionsHash::const_iterator actionIt = mActions.constBegin();
225 for ( ; actionIt != mActions.constEnd(); ++actionIt )
226 {
227 list << actionIt.key();
228 }
229 ShortcutsHash::const_iterator shortcutIt = mShortcuts.constBegin();
230 for ( ; shortcutIt != mShortcuts.constEnd(); ++shortcutIt )
231 {
232 list << shortcutIt.key();
233 }
234 return list;
235}
236
237QString QgsShortcutsManager::objectDefaultKeySequence( QObject *object ) const
238{
239 if ( QAction *action = qobject_cast<QAction *>( object ) )
240 return defaultKeySequence( action );
241 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( object ) )
242 return defaultKeySequence( shortcut );
243 else
244 return QString();
245}
246
247QString QgsShortcutsManager::defaultKeySequence( QAction *action ) const
248{
249 return mActions.value( action ).first;
250}
251
252QString QgsShortcutsManager::defaultKeySequence( QShortcut *shortcut ) const
253{
254 return mShortcuts.value( shortcut ).first;
255}
256
257bool QgsShortcutsManager::setKeySequence( const QString &name, const QString &sequence )
258{
259 if ( QAction *action = actionByName( name ) )
260 return setKeySequence( action, sequence );
261 else if ( QShortcut *shortcut = shortcutByName( name ) )
262 return setKeySequence( shortcut, sequence );
263 else
264 return false;
265}
266
267bool QgsShortcutsManager::setObjectKeySequence( QObject *object, const QString &sequence )
268{
269 if ( QAction *action = qobject_cast<QAction *>( object ) )
270 return setKeySequence( action, sequence );
271 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( object ) )
272 return setKeySequence( shortcut, sequence );
273 else
274 return false;
275}
276
277bool QgsShortcutsManager::setKeySequence( QAction *action, const QString &sequence )
278{
279 if ( !mActions.contains( action ) )
280 {
281 return false;
282 }
283 action->setShortcut( sequence );
284 this->updateActionToolTip( action, sequence );
285
286 if ( action->property( "commonAction" ).isValid() )
287 {
288 // if the key sequence for a common action is changed, update all QActions currently linked
289 // to that common action
290 const CommonAction commonAction = static_cast< CommonAction >( action->property( "commonAction" ).toInt() );
291 for ( auto it = mLinkedCommonActions.constBegin(); it != mLinkedCommonActions.constEnd(); ++it )
292 {
293 if ( it.value() == commonAction )
294 {
295 it.key()->setShortcut( action->shortcut() );
296 it.key()->setToolTip( action->toolTip() );
297 }
298 }
299 }
300
301 const QString settingKey = mActions[action].second;
302
303 // save to settings
304 QgsSettings settings;
305 settings.setValue( settingKey, sequence );
306 return true;
307}
308
309bool QgsShortcutsManager::setKeySequence( QShortcut *shortcut, const QString &sequence )
310{
311 if ( !mShortcuts.contains( shortcut ) )
312 {
313 return false;
314 }
315 shortcut->setKey( sequence );
316
317 const QString settingKey = mShortcuts[shortcut].second;
318
319 // save to settings
320 QgsSettings settings;
321 settings.setValue( settingKey, sequence );
322 return true;
323}
324
325QObject *QgsShortcutsManager::objectForSequence( const QKeySequence &sequence ) const
326{
327 if ( QAction *action = actionForSequence( sequence ) )
328 return action;
329 else if ( QShortcut *shortcut = shortcutForSequence( sequence ) )
330 return shortcut;
331 else
332 return nullptr;
333}
334
335QAction *QgsShortcutsManager::actionForSequence( const QKeySequence &sequence ) const
336{
337 if ( sequence.isEmpty() )
338 return nullptr;
339
340 for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
341 {
342 if ( it.key()->shortcut() == sequence )
343 return it.key();
344 }
345
346 return nullptr;
347}
348
349QShortcut *QgsShortcutsManager::shortcutForSequence( const QKeySequence &sequence ) const
350{
351 if ( sequence.isEmpty() )
352 return nullptr;
353
354 for ( ShortcutsHash::const_iterator it = mShortcuts.constBegin(); it != mShortcuts.constEnd(); ++it )
355 {
356 if ( it.key()->key() == sequence )
357 return it.key();
358 }
359
360 return nullptr;
361}
362
364{
365 const auto it = mCommonActions.constFind( static_cast< int >( action ) );
366 if ( it == mCommonActions.constEnd() )
367 return QKeySequence();
368
369 return it.value()->shortcut();
370}
371
372QAction *QgsShortcutsManager::actionByName( const QString &name ) const
373{
374 for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
375 {
376 if ( it.key()->objectName() == name )
377 return it.key();
378 }
379 for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
380 {
381 QString key = it.key()->text();
382 key.remove( '&' ); // remove the accelerator
383 if ( key == name )
384 return it.key();
385 }
386
387 return nullptr;
388}
389
390QShortcut *QgsShortcutsManager::shortcutByName( const QString &name ) const
391{
392 for ( ShortcutsHash::const_iterator it = mShortcuts.constBegin(); it != mShortcuts.constEnd(); ++it )
393 {
394 if ( it.key()->objectName() == name )
395 return it.key();
396 }
397
398 return nullptr;
399}
400
401void QgsShortcutsManager::actionDestroyed( QAction *action )
402{
403 mActions.remove( action );
404 mLinkedCommonActions.remove( action );
405}
406
407QString QgsShortcutsManager::objectSettingKey( QObject *object ) const
408{
409 if ( auto action = qobject_cast<QAction *>( object ) )
410 {
411 return mActions.value( action ).second;
412 }
413 else if ( auto shortcut = qobject_cast<QShortcut *>( object ) )
414 {
415 return mShortcuts.value( shortcut ).second;
416 }
417 return QString();
418}
419
420QObject *QgsShortcutsManager::objectForSettingKey( const QString &settingKey ) const
421{
422 for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
423 {
424 if ( it.value().second == settingKey )
425 return it.key();
426 }
427 for ( ShortcutsHash::const_iterator it = mShortcuts.constBegin(); it != mShortcuts.constEnd(); ++it )
428 {
429 if ( it.value().second == settingKey )
430 return it.key();
431 }
432 return nullptr;
433}
434
435void QgsShortcutsManager::shortcutDestroyed( QShortcut *shortcut )
436{
437 mShortcuts.remove( shortcut );
438}
439
440QString QgsShortcutsManager::formatActionToolTip( const QString &toolTip )
441{
442 if ( toolTip.isEmpty() )
443 return QString();
444
445 const QStringList parts = toolTip.split( '\n' );
446 QString formatted = u"<b>%1</b>"_s.arg( parts.at( 0 ) );
447 if ( parts.count() > 1 )
448 {
449 for ( int i = 1; i < parts.count(); ++i )
450 formatted += u"<p>%1</p>"_s.arg( parts.at( i ) );
451 }
452
453 return formatted;
454}
455
456void QgsShortcutsManager::updateActionToolTip( QAction *action, const QString &sequence )
457{
458 QString current = action->toolTip();
459 const thread_local QRegularExpression rx( u"\\s*\\((.*)\\)"_s );
460 // Look for the last occurrence of text inside parentheses
461 QRegularExpressionMatch match;
462 if ( current.lastIndexOf( rx, -1, &match ) != -1 )
463 {
464 // Check if it is a valid QKeySequence
465 const QStringList parts = QKeySequence( match.captured( 1 ) ).toString().split( "," );
466 if ( std::all_of( parts.constBegin(), parts.constEnd(), []( const QString &part ) { return !part.trimmed().isEmpty(); } ) )
467 {
468 current = current.remove( match.capturedStart( 0 ), match.capturedLength( 0 ) );
469 }
470 }
471
472 if ( !sequence.isEmpty() )
473 {
474 action->setToolTip( current + " (" + sequence + ")" );
475 }
476 else
477 {
478 action->setToolTip( current );
479 }
480}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static void warning(const QString &msg)
Goes to qWarning.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool setKeySequence(const QString &name, const QString &sequence)
Modifies an action or shortcut's key sequence.
bool registerShortcut(QShortcut *shortcut, const QString &defaultSequence=QString(), const QString &section=QString())
Registers a QShortcut with the manager so the shortcut can be configured in GUI.
QList< QObject * > listAll() const
Returns a list of both actions and shortcuts in the manager.
void registerAllChildActions(QObject *object, bool recursive=false, const QString &section=QString())
Automatically registers all QActions which are children of the passed object.
QObject * objectForSettingKey(const QString &name) const
Returns the QShortcut or QAction matching the the full setting key Return nullptr if the key was not ...
void registerAllChildShortcuts(QObject *object, bool recursive=false, const QString &section=QString())
Automatically registers all QShortcuts which are children of the passed object.
QgsShortcutsManager(QObject *parent=nullptr, const QString &settingsRoot="/shortcuts/")
Constructor for QgsShortcutsManager.
QString objectDefaultKeySequence(QObject *object) const
Returns the default sequence for an object (either a QAction or QShortcut).
QList< QShortcut * > listShortcuts() const
Returns a list of shortcuts in the manager.
CommonAction
Contains common actions which are used across a variety of classes.
@ CodeRunSelection
Run selection from script.
@ CodeToggleComment
Toggle code comments.
bool unregisterShortcut(QShortcut *shortcut)
Removes a shortcut from the manager.
QString defaultKeySequence(QAction *action) const
Returns the default sequence for an action.
bool setObjectKeySequence(QObject *object, const QString &sequence)
Modifies an object's (either a QAction or a QShortcut) key sequence.
bool registerAction(QAction *action, const QString &defaultShortcut=QString(), const QString &section=QString())
Registers an action with the manager so the shortcut can be configured in GUI.
void registerAllChildren(QObject *object, bool recursive=false, const QString &section=QString())
Automatically registers all QActions and QShortcuts which are children of the passed object.
QShortcut * shortcutByName(const QString &name) const
Returns a shortcut by its name, or nullptr if nothing found.
QAction * actionByName(const QString &name) const
Returns an action by its name, or nullptr if nothing found.
QShortcut * shortcutForSequence(const QKeySequence &sequence) const
Returns the shortcut which is associated for a key sequence, or nullptr if no shortcut is associated.
QKeySequence sequenceForCommonAction(CommonAction action) const
Returns the key sequence which is associated with a common action, or an empty sequence if no shortcu...
QList< QAction * > listActions() const
Returns a list of all actions in the manager.
QString objectSettingKey(QObject *object) const
Returns the full settings key matching the QShortcut or QAction Return an empty QString if the QObjec...
QAction * actionForSequence(const QKeySequence &sequence) const
Returns the action which is associated for a shortcut sequence, or nullptr if no action is associated...
void initializeCommonAction(QAction *action, CommonAction commonAction)
Initializes an action as a common action.
bool unregisterAction(QAction *action)
Removes an action from the manager.
QObject * objectForSequence(const QKeySequence &sequence) const
Returns the object (QAction or QShortcut) matching the specified key sequence,.