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