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