QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsconfigureshortcutsdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsconfigureshortcutsdialog.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 
17 
18 #include "qgsshortcutsmanager.h"
19 #include "qgslogger.h"
20 #include "qgssettings.h"
21 #include "qgsgui.h"
22 
23 #include <QKeyEvent>
24 #include <QKeySequence>
25 #include <QMessageBox>
26 #include <QShortcut>
27 #include <QDomDocument>
28 #include <QFileDialog>
29 #include <QTextStream>
30 #include <QDebug>
31 
33  : QDialog( parent )
34  , mManager( manager )
35 {
36  setupUi( this );
38  connect( mLeFilter, &QgsFilterLineEdit::textChanged, this, &QgsConfigureShortcutsDialog::mLeFilter_textChanged );
39 
40  if ( !mManager )
41  mManager = QgsGui::shortcutsManager();
42 
43  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsConfigureShortcutsDialog::showHelp ); // Vérifier nommage des boutons
44  connect( btnChangeShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::changeShortcut );
45  connect( btnResetShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::resetShortcut );
46  connect( btnSetNoShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::setNoShortcut );
47  connect( btnSaveShortcuts, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::saveShortcuts );
48  connect( btnLoadShortcuts, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::loadShortcuts );
49 
50  connect( treeActions, &QTreeWidget::currentItemChanged,
51  this, &QgsConfigureShortcutsDialog::actionChanged );
52 
53  populateActions();
54 }
55 
56 void QgsConfigureShortcutsDialog::populateActions()
57 {
58  QList<QObject *> objects = mManager->listAll();
59 
60  QList<QTreeWidgetItem *> items;
61  items.reserve( objects.count() );
62  const auto constObjects = objects;
63  for ( QObject *obj : constObjects )
64  {
65  QString actionText;
66  QString sequence;
67  QIcon icon;
68 
69  if ( QAction *action = qobject_cast< QAction * >( obj ) )
70  {
71  actionText = action->text();
72  actionText.remove( '&' ); // remove the accelerator
73  sequence = action->shortcut().toString( QKeySequence::NativeText );
74  icon = action->icon();
75  }
76  else if ( QShortcut *shortcut = qobject_cast< QShortcut * >( obj ) )
77  {
78  actionText = shortcut->whatsThis();
79  sequence = shortcut->key().toString( QKeySequence::NativeText );
80  icon = shortcut->property( "Icon" ).value<QIcon>();
81  }
82  else
83  {
84  continue;
85  }
86 
87  if ( actionText.isEmpty() )
88  {
89  continue;
90  }
91 
92  QStringList lst;
93  lst << actionText << sequence;
94  QTreeWidgetItem *item = new QTreeWidgetItem( lst );
95  item->setIcon( 0, icon );
96  item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
97  items.append( item );
98  }
99 
100  treeActions->addTopLevelItems( items );
101 
102  // make sure everything's visible and sorted
103  treeActions->resizeColumnToContents( 0 );
104  treeActions->sortItems( 0, Qt::AscendingOrder );
105 
106  actionChanged( treeActions->currentItem(), nullptr );
107 }
108 
109 void QgsConfigureShortcutsDialog::saveShortcuts()
110 {
111  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Shortcuts" ), QDir::homePath(),
112  tr( "XML file" ) + " (*.xml);;" + tr( "All files" ) + " (*)" );
113 
114  if ( fileName.isEmpty() )
115  return;
116 
117  // ensure the user never omitted the extension from the file name
118  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
119  {
120  fileName += QLatin1String( ".xml" );
121  }
122 
123  QFile file( fileName );
124  if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
125  {
126  QMessageBox::warning( this, tr( "Saving Shortcuts" ),
127  tr( "Cannot write file %1:\n%2." )
128  .arg( fileName,
129  file.errorString() ) );
130  return;
131  }
132 
133  QgsSettings settings;
134 
135  QDomDocument doc( QStringLiteral( "shortcuts" ) );
136  QDomElement root = doc.createElement( QStringLiteral( "qgsshortcuts" ) );
137  root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0" ) );
138  root.setAttribute( QStringLiteral( "locale" ), settings.value( QStringLiteral( "locale/userLocale" ), "en_US" ).toString() );
139  doc.appendChild( root );
140 
141  settings.beginGroup( mManager->settingsPath() );
142  QStringList keys = settings.childKeys();
143 
144  QString actionText;
145  QString actionShortcut;
146 
147  for ( int i = 0; i < keys.count(); ++i )
148  {
149  actionText = keys[ i ];
150  actionShortcut = settings.value( actionText, "" ).toString();
151 
152  QDomElement el = doc.createElement( QStringLiteral( "act" ) );
153  el.setAttribute( QStringLiteral( "name" ), actionText );
154  el.setAttribute( QStringLiteral( "shortcut" ), actionShortcut );
155  root.appendChild( el );
156  }
157 
158  QTextStream out( &file );
159  doc.save( out, 4 );
160 }
161 
162 void QgsConfigureShortcutsDialog::loadShortcuts()
163 {
164  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Shortcuts" ), QDir::homePath(),
165  tr( "XML file" ) + " (*.xml);;" + tr( "All files" ) + " (*)" );
166 
167  if ( fileName.isEmpty() )
168  {
169  return;
170  }
171 
172  QFile file( fileName );
173  if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
174  {
175  QMessageBox::warning( this, tr( "Loading Shortcuts" ),
176  tr( "Cannot read file %1:\n%2." )
177  .arg( fileName,
178  file.errorString() ) );
179  return;
180  }
181 
182  QDomDocument doc;
183  QString errorStr;
184  int errorLine;
185  int errorColumn;
186 
187  if ( !doc.setContent( &file, true, &errorStr, &errorLine, &errorColumn ) )
188  {
189  QMessageBox::information( this, tr( "Loading Shortcuts" ),
190  tr( "Parse error at line %1, column %2:\n%3" )
191  .arg( errorLine )
192  .arg( errorColumn )
193  .arg( errorStr ) );
194  return;
195  }
196 
197  QDomElement root = doc.documentElement();
198  if ( root.tagName() != QLatin1String( "qgsshortcuts" ) )
199  {
200  QMessageBox::information( this, tr( "Loading Shortcuts" ),
201  tr( "The file is not an shortcuts exchange file." ) );
202  return;
203  }
204 
205  QgsSettings settings;
206  QString currentLocale;
207 
208  bool localeOverrideFlag = settings.value( QStringLiteral( "locale/overrideFlag" ), false ).toBool();
209  if ( localeOverrideFlag )
210  {
211  currentLocale = settings.value( QStringLiteral( "locale/userLocale" ), "en_US" ).toString();
212  }
213  else // use QGIS locale
214  {
215  currentLocale = QLocale().name();
216  }
217 
218  if ( root.attribute( QStringLiteral( "locale" ) ) != currentLocale )
219  {
220  QMessageBox::information( this, tr( "Loading Shortcuts" ),
221  tr( "The file contains shortcuts created with different locale, so you can't use it." ) );
222  return;
223  }
224 
225  QString actionName;
226  QString actionShortcut;
227 
228  QDomElement child = root.firstChildElement();
229  while ( !child.isNull() )
230  {
231  actionName = child.attribute( QStringLiteral( "name" ) );
232  actionShortcut = child.attribute( QStringLiteral( "shortcut" ) );
233  mManager->setKeySequence( actionName, actionShortcut );
234 
235  child = child.nextSiblingElement();
236  }
237 
238  treeActions->clear();
239  populateActions();
240 }
241 
242 void QgsConfigureShortcutsDialog::changeShortcut()
243 {
244  setFocus(); // make sure we have focus
245  setGettingShortcut( true );
246 }
247 
248 void QgsConfigureShortcutsDialog::resetShortcut()
249 {
250  QObject *object = currentObject();
251  QString sequence = mManager->objectDefaultKeySequence( object );
252  setCurrentActionShortcut( sequence );
253 }
254 
255 void QgsConfigureShortcutsDialog::setNoShortcut()
256 {
257  setCurrentActionShortcut( QKeySequence() );
258 }
259 
260 QAction *QgsConfigureShortcutsDialog::currentAction()
261 {
262  return qobject_cast<QAction *>( currentObject() );
263 }
264 
265 QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
266 {
267  return qobject_cast<QShortcut *>( currentObject() );
268 }
269 
270 void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
271 {
272  Q_UNUSED( current )
273  Q_UNUSED( previous )
274  // cancel previous shortcut setting (if any)
275  setGettingShortcut( false );
276 
277  QString shortcut;
278  QKeySequence sequence;
279  if ( QAction *action = currentAction() )
280  {
281  // show which one is the default action
282  shortcut = mManager->defaultKeySequence( action );
283  sequence = action->shortcut();
284  }
285  else if ( QShortcut *object = currentShortcut() )
286  {
287  // show which one is the default action
288  shortcut = mManager->defaultKeySequence( object );
289  sequence = object->key();
290  }
291  else
292  {
293  return;
294  }
295 
296  if ( shortcut.isEmpty() )
297  shortcut = tr( "None" );
298  btnResetShortcut->setText( tr( "Set default (%1)" ).arg( shortcut ) );
299 
300  // if there's no shortcut, disable set none
301  btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
302  // if the shortcut is default, disable set default
303  btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
304 }
305 
307 {
308  if ( !mGettingShortcut )
309  {
310  QDialog::keyPressEvent( event );
311  return;
312  }
313 
314  int key = event->key();
315  switch ( key )
316  {
317  // modifiers
318  case Qt::Key_Meta:
319  mModifiers |= Qt::META;
320  updateShortcutText();
321  break;
322  case Qt::Key_Alt:
323  mModifiers |= Qt::ALT;
324  updateShortcutText();
325  break;
326  case Qt::Key_Control:
327  mModifiers |= Qt::CTRL;
328  updateShortcutText();
329  break;
330  case Qt::Key_Shift:
331  mModifiers |= Qt::SHIFT;
332  updateShortcutText();
333  break;
334 
335  // escape aborts the acquisition of shortcut
336  case Qt::Key_Escape:
337  setGettingShortcut( false );
338  break;
339 
340  default:
341  mKey = key;
342  updateShortcutText();
343  }
344 }
345 
347 {
348  if ( !mGettingShortcut )
349  {
350  QDialog::keyReleaseEvent( event );
351  return;
352  }
353 
354  int key = event->key();
355  switch ( key )
356  {
357  // modifiers
358  case Qt::Key_Meta:
359  mModifiers &= ~Qt::META;
360  updateShortcutText();
361  break;
362  case Qt::Key_Alt:
363  mModifiers &= ~Qt::ALT;
364  updateShortcutText();
365  break;
366  case Qt::Key_Control:
367  mModifiers &= ~Qt::CTRL;
368  updateShortcutText();
369  break;
370  case Qt::Key_Shift:
371  mModifiers &= ~Qt::SHIFT;
372  updateShortcutText();
373  break;
374 
375  case Qt::Key_Escape:
376  break;
377 
378  default:
379  {
380  // an ordinary key - set it with modifiers as a shortcut
381  setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
382  setGettingShortcut( false );
383  }
384  }
385 }
386 
387 QObject *QgsConfigureShortcutsDialog::currentObject()
388 {
389  if ( !treeActions->currentItem() )
390  return nullptr;
391 
392  QObject *object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
393  return object;
394 }
395 
396 void QgsConfigureShortcutsDialog::updateShortcutText()
397 {
398  // update text of the button so that user can see what has typed already
399  QKeySequence s( mModifiers + mKey );
400  btnChangeShortcut->setText( tr( "Input: " ) + s.toString( QKeySequence::NativeText ) );
401 }
402 
403 void QgsConfigureShortcutsDialog::setGettingShortcut( bool getting )
404 {
405  mModifiers = 0;
406  mKey = 0;
407  mGettingShortcut = getting;
408  if ( !getting )
409  {
410  btnChangeShortcut->setChecked( false );
411  btnChangeShortcut->setText( tr( "Change" ) );
412  }
413  else
414  {
415  updateShortcutText();
416  }
417 }
418 
419 void QgsConfigureShortcutsDialog::setCurrentActionShortcut( const QKeySequence &s )
420 {
421  QObject *object = currentObject();
422  if ( !object )
423  return;
424 
425  // first check whether this action is not taken already
426  QObject *otherObject = mManager->objectForSequence( s );
427  if ( otherObject == object )
428  return;
429 
430  if ( otherObject )
431  {
432  QString otherText;
433  if ( QAction *otherAction = qobject_cast< QAction * >( otherObject ) )
434  {
435  otherText = otherAction->text();
436  otherText.remove( '&' ); // remove the accelerator
437  }
438  else if ( QShortcut *otherShortcut = qobject_cast< QShortcut * >( otherObject ) )
439  {
440  otherText = otherShortcut->whatsThis();
441  }
442 
443  int res = QMessageBox::question( this, tr( "Change Shortcut" ),
444  tr( "This shortcut is already assigned to action %1. Reassign?" ).arg( otherText ),
445  QMessageBox::Yes | QMessageBox::No );
446 
447  if ( res != QMessageBox::Yes )
448  return;
449 
450  // reset action of the conflicting other action!
451  mManager->setObjectKeySequence( otherObject, QString() );
452  QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
453  if ( !items.isEmpty() ) // there should be exactly one
454  items[0]->setText( 1, QString() );
455  }
456 
457  // update manager
458  mManager->setObjectKeySequence( object, s.toString( QKeySequence::NativeText ) );
459 
460  // update gui
461  treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
462 
463  actionChanged( treeActions->currentItem(), nullptr );
464 }
465 
466 void QgsConfigureShortcutsDialog::mLeFilter_textChanged( const QString &text )
467 {
468  for ( int i = 0; i < treeActions->topLevelItemCount(); i++ )
469  {
470  QTreeWidgetItem *item = treeActions->topLevelItem( i );
471  if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
472  {
473  item->setHidden( true );
474  }
475  else
476  {
477  item->setHidden( false );
478  }
479  }
480 }
481 
482 void QgsConfigureShortcutsDialog::showHelp()
483 {
484  QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#keyboard-shortcuts" ) );
485 }
QgsConfigureShortcutsDialog(QWidget *parent=nullptr, QgsShortcutsManager *manager=nullptr)
Constructor for QgsConfigureShortcutsDialog.
void keyReleaseEvent(QKeyEvent *event) override
void keyPressEvent(QKeyEvent *event) override
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition: qgsgui.cpp:101
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:156
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
Definition: qgssettings.cpp:87
QStringList childKeys() const
Returns a list of all top-level keys that can be read using the QSettings object.
Shortcuts manager is a class that contains a list of QActions and QShortcuts that have been registere...
QString settingsPath() const
Returns the root settings path used to store shortcut customization.
bool setKeySequence(const QString &name, const QString &sequence)
Modifies an action or shortcut's key sequence.
QList< QObject * > listAll() const
Returns a list of both actions and shortcuts in the manager.
QString objectDefaultKeySequence(QObject *object) const
Returns the default sequence for an object (either a QAction or QShortcut).
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.
QObject * objectForSequence(const QKeySequence &sequence) const
Returns the object (QAction or QShortcut) matching the specified key sequence,.