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