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