QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
80void 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
133void 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
210void 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 {
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
289void QgsConfigureShortcutsDialog::changeShortcut()
290{
291 setFocus(); // make sure we have focus
292 setGettingShortcut( true );
293}
294
295void QgsConfigureShortcutsDialog::resetShortcut()
296{
297 QObject *object = currentObject();
298 const QString sequence = mManager->objectDefaultKeySequence( object );
299 setCurrentActionShortcut( sequence );
300}
301
302void QgsConfigureShortcutsDialog::setNoShortcut()
303{
304 setCurrentActionShortcut( QKeySequence() );
305}
306
307QAction *QgsConfigureShortcutsDialog::currentAction()
308{
309 return qobject_cast<QAction *>( currentObject() );
310}
311
312QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
313{
314 return qobject_cast<QShortcut *>( currentObject() );
315}
316
317void 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
434QObject *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
443void 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
450void 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
466void 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
513void 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
529void QgsConfigureShortcutsDialog::showHelp()
530{
531 QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#keyboard-shortcuts" ) );
532}
533
534void 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.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
641 printer.setOutputFileName( fileName );
642 document->setPageSize( QSizeF( printer.pageRect( QPrinter::DevicePixel ).size() ) );
643
644 document->print( &printer );
645}
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:113
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:178
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:38
QString key(const QString &dynamicKeyPart=QString()) const
Returns settings entry key.
T valueWithDefaultOverride(const T &defaultValueOverride, const QString &dynamicKeyPart=QString()) const
Returns the settings value with a defaultValueOverride and with an optional dynamicKeyPart.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
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.
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