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