QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 "qgsapplication.h"
20 #include "qgslogger.h"
21 #include "qgssettings.h"
22 #include "qgsgui.h"
23 
24 #include <QKeyEvent>
25 #include <QKeySequence>
26 #include <QMessageBox>
27 #include <QShortcut>
28 #include <QDomDocument>
29 #include <QFileDialog>
30 #include <QTextStream>
31 #include <QMenu>
32 #include <QAction>
33 #include <QPrinter>
34 #include <QTextDocument>
35 #include <QTextCursor>
36 #include <QTextTable>
37 #include <QTextTableFormat>
38 #include <QTextTableCellFormat>
39 #include <QTextCharFormat>
40 
42  : QDialog( parent )
43  , mManager( manager )
44 {
45  setupUi( this );
47 
48  mSaveMenu = new QMenu( this );
49  mSaveUserShortcuts = new QAction( tr( "Save User Shortcuts…" ), this );
50  mSaveMenu->addAction( mSaveUserShortcuts );
51  connect( mSaveUserShortcuts, &QAction::triggered, this, [this] { saveShortcuts( false ); } );
52 
53  mSaveAllShortcuts = new QAction( tr( "Save All Shortcuts…" ), this );
54  mSaveMenu->addAction( mSaveAllShortcuts );
55  connect( mSaveAllShortcuts, &QAction::triggered, this, [this] { saveShortcuts(); } );
56 
57  mSaveAsPdf = new QAction( tr( "Save as PDF…" ), this );
58  mSaveMenu->addAction( mSaveAsPdf );
59  connect( mSaveAsPdf, &QAction::triggered, this, &QgsConfigureShortcutsDialog::saveShortcutsPdf );
60 
61  btnSaveShortcuts->setMenu( mSaveMenu );
62 
63  connect( mLeFilter, &QgsFilterLineEdit::textChanged, this, &QgsConfigureShortcutsDialog::mLeFilter_textChanged );
64 
65  if ( !mManager )
66  mManager = QgsGui::shortcutsManager();
67 
68  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsConfigureShortcutsDialog::showHelp ); // Vérifier nommage des boutons
69  connect( btnChangeShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::changeShortcut );
70  connect( btnResetShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::resetShortcut );
71  connect( btnSetNoShortcut, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::setNoShortcut );
72  connect( btnLoadShortcuts, &QAbstractButton::clicked, this, &QgsConfigureShortcutsDialog::loadShortcuts );
73 
74  connect( treeActions, &QTreeWidget::currentItemChanged,
75  this, &QgsConfigureShortcutsDialog::actionChanged );
76 
77  populateActions();
78 }
79 
80 void QgsConfigureShortcutsDialog::populateActions()
81 {
82  const QList<QObject *> objects = mManager->listAll();
83 
84  QList<QTreeWidgetItem *> items;
85  items.reserve( objects.count() );
86  const auto constObjects = objects;
87  for ( QObject *obj : constObjects )
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( QKeySequence::NativeText );
98  icon = action->icon();
99  }
100  else if ( QShortcut *shortcut = qobject_cast< QShortcut * >( obj ) )
101  {
102  actionText = shortcut->whatsThis();
103  sequence = shortcut->key().toString( QKeySequence::NativeText );
104  icon = shortcut->property( "Icon" ).value<QIcon>();
105  }
106  else
107  {
108  continue;
109  }
110 
111  if ( actionText.isEmpty() )
112  {
113  continue;
114  }
115 
116  QStringList lst;
117  lst << actionText << sequence;
118  QTreeWidgetItem *item = new QTreeWidgetItem( lst );
119  item->setIcon( 0, icon );
120  item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
121  items.append( item );
122  }
123 
124  treeActions->addTopLevelItems( items );
125 
126  // make sure everything's visible and sorted
127  treeActions->resizeColumnToContents( 0 );
128  treeActions->sortItems( 0, Qt::AscendingOrder );
129 
130  actionChanged( treeActions->currentItem(), nullptr );
131 }
132 
133 void QgsConfigureShortcutsDialog::saveShortcuts( bool saveAll )
134 {
135  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Shortcuts" ), QDir::homePath(),
136  tr( "XML file" ) + " (*.xml);;" + tr( "All files" ) + " (*)" );
137 
138  if ( fileName.isEmpty() )
139  return;
140 
141  // ensure the user never omitted the extension from the file name
142  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
143  {
144  fileName += QLatin1String( ".xml" );
145  }
146 
147  QFile file( fileName );
148  if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
149  {
150  QMessageBox::warning( this, tr( "Saving Shortcuts" ),
151  tr( "Cannot write file %1:\n%2." )
152  .arg( fileName,
153  file.errorString() ) );
154  return;
155  }
156 
157  QgsSettings settings;
158 
159  QDomDocument doc( QStringLiteral( "shortcuts" ) );
160  QDomElement root = doc.createElement( QStringLiteral( "qgsshortcuts" ) );
161  root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0" ) );
162  root.setAttribute( QStringLiteral( "locale" ), settings.value( QgsApplication::settingsLocaleUserLocale.key(), "en_US" ).toString() );
163  doc.appendChild( root );
164 
165  const QList<QObject *> objects = mManager->listAll();
166  for ( QObject *obj : objects )
167  {
168  QString actionText;
169  QString actionShortcut;
170  QKeySequence sequence;
171 
172  if ( QAction *action = qobject_cast< QAction * >( obj ) )
173  {
174  actionText = action->text().remove( '&' );
175  actionShortcut = action->shortcut().toString( QKeySequence::NativeText );
176  sequence = mManager->defaultKeySequence( action );
177  }
178  else if ( QShortcut *shortcut = qobject_cast< QShortcut * >( obj ) )
179  {
180  actionText = shortcut->whatsThis();
181  actionShortcut = shortcut->key().toString( QKeySequence::NativeText );
182  sequence = mManager->defaultKeySequence( shortcut );
183  }
184  else
185  {
186  continue;
187  }
188 
189  if ( actionText.isEmpty() || actionShortcut.isEmpty() )
190  {
191  continue;
192  }
193 
194  // skip unchanged shortcuts if only user-definied were requested
195  if ( !saveAll && sequence == QKeySequence( actionShortcut ) )
196  {
197  continue;
198  }
199 
200  QDomElement el = doc.createElement( QStringLiteral( "action" ) );
201  el.setAttribute( QStringLiteral( "name" ), actionText );
202  el.setAttribute( QStringLiteral( "shortcut" ), actionShortcut );
203  root.appendChild( el );
204  }
205 
206  QTextStream out( &file );
207  doc.save( out, 4 );
208 }
209 
210 void QgsConfigureShortcutsDialog::loadShortcuts()
211 {
212  const QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Shortcuts" ), QDir::homePath(),
213  tr( "XML file" ) + " (*.xml);;" + tr( "All files" ) + " (*)" );
214 
215  if ( fileName.isEmpty() )
216  {
217  return;
218  }
219 
220  QFile file( fileName );
221  if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
222  {
223  QMessageBox::warning( this, tr( "Loading Shortcuts" ),
224  tr( "Cannot read file %1:\n%2." )
225  .arg( fileName,
226  file.errorString() ) );
227  return;
228  }
229 
230  QDomDocument doc;
231  QString errorStr;
232  int errorLine;
233  int errorColumn;
234 
235  if ( !doc.setContent( &file, true, &errorStr, &errorLine, &errorColumn ) )
236  {
237  QMessageBox::information( this, tr( "Loading Shortcuts" ),
238  tr( "Parse error at line %1, column %2:\n%3" )
239  .arg( errorLine )
240  .arg( errorColumn )
241  .arg( errorStr ) );
242  return;
243  }
244 
245  const QDomElement root = doc.documentElement();
246  if ( root.tagName() != QLatin1String( "qgsshortcuts" ) )
247  {
248  QMessageBox::information( this, tr( "Loading Shortcuts" ),
249  tr( "The file is not an shortcuts exchange file." ) );
250  return;
251  }
252 
253  QString currentLocale;
254 
255  const bool localeOverrideFlag = QgsApplication::settingsLocaleOverrideFlag.value();
256  if ( localeOverrideFlag )
257  {
258  currentLocale = QgsApplication::settingsLocaleUserLocale.value( QString(), true, "en_US" );
259  }
260  else // use QGIS locale
261  {
262  currentLocale = QLocale().name();
263  }
264 
265  if ( root.attribute( QStringLiteral( "locale" ) ) != currentLocale )
266  {
267  QMessageBox::information( this, tr( "Loading Shortcuts" ),
268  tr( "The file contains shortcuts created with different locale, so you can't use it." ) );
269  return;
270  }
271 
272  QString actionName;
273  QString actionShortcut;
274 
275  QDomElement child = root.firstChildElement();
276  while ( !child.isNull() )
277  {
278  actionName = child.attribute( QStringLiteral( "name" ) );
279  actionShortcut = child.attribute( QStringLiteral( "shortcut" ) );
280  mManager->setKeySequence( actionName, actionShortcut );
281 
282  child = child.nextSiblingElement();
283  }
284 
285  treeActions->clear();
286  populateActions();
287 }
288 
289 void QgsConfigureShortcutsDialog::changeShortcut()
290 {
291  setFocus(); // make sure we have focus
292  setGettingShortcut( true );
293 }
294 
295 void QgsConfigureShortcutsDialog::resetShortcut()
296 {
297  QObject *object = currentObject();
298  const QString sequence = mManager->objectDefaultKeySequence( object );
299  setCurrentActionShortcut( sequence );
300 }
301 
302 void QgsConfigureShortcutsDialog::setNoShortcut()
303 {
304  setCurrentActionShortcut( QKeySequence() );
305 }
306 
307 QAction *QgsConfigureShortcutsDialog::currentAction()
308 {
309  return qobject_cast<QAction *>( currentObject() );
310 }
311 
312 QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
313 {
314  return qobject_cast<QShortcut *>( currentObject() );
315 }
316 
317 void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
318 {
319  Q_UNUSED( current )
320  Q_UNUSED( previous )
321  // cancel previous shortcut setting (if any)
322  setGettingShortcut( false );
323 
324  QString shortcut;
325  QKeySequence sequence;
326  if ( QAction *action = currentAction() )
327  {
328  // show which one is the default action
329  shortcut = mManager->defaultKeySequence( action );
330  sequence = action->shortcut();
331  }
332  else if ( QShortcut *object = currentShortcut() )
333  {
334  // show which one is the default action
335  shortcut = mManager->defaultKeySequence( object );
336  sequence = object->key();
337  }
338  else
339  {
340  return;
341  }
342 
343  if ( shortcut.isEmpty() )
344  shortcut = tr( "None" );
345  btnResetShortcut->setText( tr( "Set default (%1)" ).arg( shortcut ) );
346 
347  // if there's no shortcut, disable set none
348  btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
349  // if the shortcut is default, disable set default
350  btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
351 }
352 
354 {
355  if ( !mGettingShortcut )
356  {
357  QDialog::keyPressEvent( event );
358  return;
359  }
360 
361  const int key = event->key();
362  switch ( key )
363  {
364  // modifiers
365  case Qt::Key_Meta:
366  mModifiers |= Qt::META;
367  updateShortcutText();
368  break;
369  case Qt::Key_Alt:
370  mModifiers |= Qt::ALT;
371  updateShortcutText();
372  break;
373  case Qt::Key_Control:
374  mModifiers |= Qt::CTRL;
375  updateShortcutText();
376  break;
377  case Qt::Key_Shift:
378  mModifiers |= Qt::SHIFT;
379  updateShortcutText();
380  break;
381 
382  // escape aborts the acquisition of shortcut
383  case Qt::Key_Escape:
384  setGettingShortcut( false );
385  break;
386 
387  default:
388  mKey = key;
389  updateShortcutText();
390  }
391 }
392 
394 {
395  if ( !mGettingShortcut )
396  {
397  QDialog::keyReleaseEvent( event );
398  return;
399  }
400 
401  const int key = event->key();
402  switch ( key )
403  {
404  // modifiers
405  case Qt::Key_Meta:
406  mModifiers &= ~Qt::META;
407  updateShortcutText();
408  break;
409  case Qt::Key_Alt:
410  mModifiers &= ~Qt::ALT;
411  updateShortcutText();
412  break;
413  case Qt::Key_Control:
414  mModifiers &= ~Qt::CTRL;
415  updateShortcutText();
416  break;
417  case Qt::Key_Shift:
418  mModifiers &= ~Qt::SHIFT;
419  updateShortcutText();
420  break;
421 
422  case Qt::Key_Escape:
423  break;
424 
425  default:
426  {
427  // an ordinary key - set it with modifiers as a shortcut
428  setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
429  setGettingShortcut( false );
430  }
431  }
432 }
433 
434 QObject *QgsConfigureShortcutsDialog::currentObject()
435 {
436  if ( !treeActions->currentItem() )
437  return nullptr;
438 
439  QObject *object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
440  return object;
441 }
442 
443 void QgsConfigureShortcutsDialog::updateShortcutText()
444 {
445  // update text of the button so that user can see what has typed already
446  const QKeySequence s( mModifiers + mKey );
447  btnChangeShortcut->setText( tr( "Input: " ) + s.toString( QKeySequence::NativeText ) );
448 }
449 
450 void QgsConfigureShortcutsDialog::setGettingShortcut( bool getting )
451 {
452  mModifiers = 0;
453  mKey = 0;
454  mGettingShortcut = getting;
455  if ( !getting )
456  {
457  btnChangeShortcut->setChecked( false );
458  btnChangeShortcut->setText( tr( "Change" ) );
459  }
460  else
461  {
462  updateShortcutText();
463  }
464 }
465 
466 void QgsConfigureShortcutsDialog::setCurrentActionShortcut( const QKeySequence &s )
467 {
468  QObject *object = currentObject();
469  if ( !object )
470  return;
471 
472  // first check whether this action is not taken already
473  QObject *otherObject = mManager->objectForSequence( s );
474  if ( otherObject == object )
475  return;
476 
477  if ( otherObject )
478  {
479  QString otherText;
480  if ( QAction *otherAction = qobject_cast< QAction * >( otherObject ) )
481  {
482  otherText = otherAction->text();
483  otherText.remove( '&' ); // remove the accelerator
484  }
485  else if ( QShortcut *otherShortcut = qobject_cast< QShortcut * >( otherObject ) )
486  {
487  otherText = otherShortcut->whatsThis();
488  }
489 
490  const int res = QMessageBox::question( this, tr( "Change Shortcut" ),
491  tr( "This shortcut is already assigned to action %1. Reassign?" ).arg( otherText ),
492  QMessageBox::Yes | QMessageBox::No );
493 
494  if ( res != QMessageBox::Yes )
495  return;
496 
497  // reset action of the conflicting other action!
498  mManager->setObjectKeySequence( otherObject, QString() );
499  QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
500  if ( !items.isEmpty() ) // there should be exactly one
501  items[0]->setText( 1, QString() );
502  }
503 
504  // update manager
505  mManager->setObjectKeySequence( object, s.toString( QKeySequence::NativeText ) );
506 
507  // update gui
508  treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
509 
510  actionChanged( treeActions->currentItem(), nullptr );
511 }
512 
513 void QgsConfigureShortcutsDialog::mLeFilter_textChanged( const QString &text )
514 {
515  for ( int i = 0; i < treeActions->topLevelItemCount(); i++ )
516  {
517  QTreeWidgetItem *item = treeActions->topLevelItem( i );
518  if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
519  {
520  item->setHidden( true );
521  }
522  else
523  {
524  item->setHidden( false );
525  }
526  }
527 }
528 
529 void QgsConfigureShortcutsDialog::showHelp()
530 {
531  QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#keyboard-shortcuts" ) );
532 }
533 
534 void QgsConfigureShortcutsDialog::saveShortcutsPdf()
535 {
536  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Shortcuts" ), QDir::homePath(),
537  tr( "PDF file" ) + " (*.pdf);;" + tr( "All files" ) + " (*)" );
538 
539  if ( fileName.isEmpty() )
540  return;
541 
542  if ( !fileName.endsWith( QLatin1String( ".pdf" ), Qt::CaseInsensitive ) )
543  {
544  fileName += QLatin1String( ".pdf" );
545  }
546 
547  QTextDocument *document = new QTextDocument;
548  QTextCursor cursor( document );
549 
550  QTextTableFormat tableFormat;
551  tableFormat.setBorder( 0 );
552  tableFormat.setCellSpacing( 0 );
553  tableFormat.setCellPadding( 4 );
554  tableFormat.setHeaderRowCount( 1 );
555 
556  QVector<QTextLength> constraints;
557  constraints << QTextLength( QTextLength::PercentageLength, 5 );
558  constraints << QTextLength( QTextLength::PercentageLength, 80 );
559  constraints << QTextLength( QTextLength::PercentageLength, 15 );
560  tableFormat.setColumnWidthConstraints( constraints );
561 
562  QTextTableCellFormat headerFormat;
563  headerFormat.setFontWeight( QFont::Bold );
564  headerFormat.setBottomPadding( 4 );
565 
566  QTextCharFormat rowFormat;
567  rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
568 
569  QTextCharFormat altRowFormat;
570  altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
571  altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
572 
573  int row = 0;
574  QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
575  table->mergeCells( 0, 0, 1, 2 );
576  QTextCursor c = table->cellAt( row, 0 ).firstCursorPosition();
577  c.setCharFormat( headerFormat );
578  c.insertText( tr( "Action" ) );
579  c = table->cellAt( row, 2 ).firstCursorPosition();
580  c.setCharFormat( headerFormat );
581  c.insertText( tr( "Shortcut" ) );
582 
583  const QList<QObject *> objects = mManager->listAll();
584  for ( QObject *obj : objects )
585  {
586  QString actionText;
587  QString sequence;
588  QIcon icon;
589 
590  if ( QAction *action = qobject_cast< QAction * >( obj ) )
591  {
592  actionText = action->text().remove( '&' );
593  sequence = action->shortcut().toString( QKeySequence::NativeText );
594  icon = action->icon();
595  }
596  else if ( QShortcut *shortcut = qobject_cast< QShortcut * >( obj ) )
597  {
598  actionText = shortcut->whatsThis();
599  sequence = shortcut->key().toString( QKeySequence::NativeText );
600  icon = shortcut->property( "Icon" ).value<QIcon>();
601  }
602  else
603  {
604  continue;
605  }
606 
607  // skip actions without shortcut and name
608  if ( actionText.isEmpty() || sequence.isEmpty() )
609  {
610  continue;
611  }
612 
613  row += 1;
614  table->appendRows( 1 );
615 
616  if ( row % 2 )
617  {
618  table->cellAt( row, 0 ).setFormat( altRowFormat );
619  table->cellAt( row, 1 ).setFormat( altRowFormat );
620  table->cellAt( row, 2 ).setFormat( altRowFormat );
621  }
622  else
623  {
624  table->cellAt( row, 0 ).setFormat( rowFormat );
625  table->cellAt( row, 1 ).setFormat( rowFormat );
626  table->cellAt( row, 2 ).setFormat( rowFormat );
627  }
628 
629  if ( !icon.isNull() )
630  {
631  c = table->cellAt( row, 0 ).firstCursorPosition();
632  c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
633  }
634  table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
635  table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
636  }
637 
638  QPrinter printer( QPrinter::ScreenResolution );
639  printer.setOutputFormat( QPrinter::PdfFormat );
640  printer.setPaperSize( QPrinter::A4 );
641  printer.setPageOrientation( QPageLayout::Portrait );
642  printer.setPageMargins( QMarginsF( 20, 10, 10, 10 ), QPageLayout::Millimeter );
643  printer.setOutputFileName( fileName );
644  document->setPageSize( QSizeF( printer.pageRect().size() ) );
645  document->print( &printer );
646 }
static const QgsSettingsEntryBool settingsLocaleOverrideFlag
Settings entry locale override flag.
static const QgsSettingsEntryString settingsLocaleUserLocale
Settings entry locale user locale.
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:108
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:168
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
Shortcuts manager is a class that contains a list of QActions and QShortcuts that have been registere...
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,.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c