17#include "moc_qgsconfigureshortcutsdialog.cpp"
27#include <QKeySequence>
30#include <QDomDocument>
36#include <QTextDocument>
39#include <QTextTableFormat>
40#include <QTextTableCellFormat>
41#include <QTextCharFormat>
50 mSaveMenu =
new QMenu(
this );
51 mSaveUserShortcuts =
new QAction( tr(
"Save User Shortcuts…" ),
this );
52 mSaveMenu->addAction( mSaveUserShortcuts );
53 connect( mSaveUserShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(
false ); } );
55 mSaveAllShortcuts =
new QAction( tr(
"Save All Shortcuts…" ),
this );
56 mSaveMenu->addAction( mSaveAllShortcuts );
57 connect( mSaveAllShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(); } );
59 mSaveAsPdf =
new QAction( tr(
"Save as PDF…" ),
this );
60 mSaveMenu->addAction( mSaveAsPdf );
61 connect( mSaveAsPdf, &QAction::triggered,
this, &QgsConfigureShortcutsDialog::saveShortcutsPdf );
63 btnSaveShortcuts->setMenu( mSaveMenu );
65 connect( mLeFilter, &QgsFilterLineEdit::textChanged,
this, &QgsConfigureShortcutsDialog::mLeFilter_textChanged );
70 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsConfigureShortcutsDialog::showHelp );
71 connect( btnChangeShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::changeShortcut );
72 connect( btnResetShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::resetShortcut );
73 connect( btnSetNoShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::setNoShortcut );
74 connect( btnLoadShortcuts, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::loadShortcuts );
76 connect( treeActions, &QTreeWidget::currentItemChanged,
this, &QgsConfigureShortcutsDialog::actionChanged );
81void QgsConfigureShortcutsDialog::populateActions()
83 const QList<QObject *> objects = mManager->
listAll();
85 QList<QTreeWidgetItem *> items;
86 items.reserve( objects.count() );
87 const auto constObjects = objects;
88 for ( QObject *obj : constObjects )
95 if ( QAction *action = qobject_cast<QAction *>( obj ) )
97 actionText = action->text();
98 actionText.remove(
'&' );
99 sequence = action->shortcut().toString( QKeySequence::NativeText );
100 icon = action->icon();
102 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
104 actionText = shortcut->whatsThis();
105 sequence = shortcut->key().toString( QKeySequence::NativeText );
106 icon = shortcut->property(
"Icon" ).value<QIcon>();
113 if ( actionText.isEmpty() )
119 lst << actionText << sequence;
120 QTreeWidgetItem *item =
new QTreeWidgetItem( lst );
121 item->setIcon( 0, icon );
122 item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
123 item->setToolTip( 0, settingKey );
124 items.append( item );
127 treeActions->addTopLevelItems( items );
130 treeActions->resizeColumnToContents( 0 );
131 treeActions->sortItems( 0, Qt::AscendingOrder );
133 actionChanged( treeActions->currentItem(),
nullptr );
136void QgsConfigureShortcutsDialog::saveShortcuts(
bool saveAll )
138 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
143 if ( fileName.isEmpty() )
147 if ( !fileName.endsWith( QLatin1String(
".xml" ), Qt::CaseInsensitive ) )
149 fileName += QLatin1String(
".xml" );
152 QFile file( fileName );
153 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
155 QMessageBox::warning(
this, tr(
"Saving Shortcuts" ), tr(
"Cannot write file %1:\n%2." ).arg( fileName, file.errorString() ) );
161 QDomDocument doc( QStringLiteral(
"shortcuts" ) );
162 QDomElement root = doc.createElement( QStringLiteral(
"qgsshortcuts" ) );
163 root.setAttribute( QStringLiteral(
"version" ), QStringLiteral(
"1.1" ) );
165 doc.appendChild( root );
167 const QList<QObject *> objects = mManager->
listAll();
168 for ( QObject *obj : objects )
171 QString actionShortcut;
172 QString actionSettingKey;
173 QKeySequence sequence;
175 if ( QAction *action = qobject_cast<QAction *>( obj ) )
177 actionText = action->text().remove(
'&' );
178 actionShortcut = action->shortcut().toString( QKeySequence::NativeText );
181 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
183 actionText = shortcut->whatsThis();
184 actionShortcut = shortcut->key().toString( QKeySequence::NativeText );
194 if ( actionSettingKey.isEmpty() )
200 if ( !saveAll && sequence == QKeySequence( actionShortcut ) )
205 QDomElement el = doc.createElement( QStringLiteral(
"action" ) );
206 el.setAttribute( QStringLiteral(
"name" ), actionText );
207 el.setAttribute( QStringLiteral(
"shortcut" ), actionShortcut );
208 el.setAttribute( QStringLiteral(
"setting" ), actionSettingKey );
209 root.appendChild( el );
212 QTextStream out( &file );
216void QgsConfigureShortcutsDialog::loadShortcuts()
218 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
220 if ( fileName.isEmpty() )
225 QFile file( fileName );
226 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
228 QMessageBox::warning(
this, tr(
"Loading Shortcuts" ), tr(
"Cannot read file %1:\n%2." ).arg( fileName, file.errorString() ) );
237 if ( !doc.setContent( &file,
true, &errorStr, &errorLine, &errorColumn ) )
239 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"Parse error at line %1, column %2:\n%3" ).arg( errorLine ).arg( errorColumn ).arg( errorStr ) );
243 const QDomElement root = doc.documentElement();
244 if ( root.tagName() != QLatin1String(
"qgsshortcuts" ) )
246 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file is not an shortcuts exchange file." ) );
250 QString currentLocale;
253 if ( localeOverrideFlag )
259 currentLocale = QLocale().name();
262 const QString versionStr = root.attribute( QStringLiteral(
"version" ) );
265 if ( root.attribute( QStringLiteral(
"locale" ) ) != currentLocale )
269 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so you can't use it." ) );
274 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so some shortcuts may not work." ) );
279 QString actionShortcut;
280 QString actionSettingKey;
282 QDomElement child = root.firstChildElement();
283 ActionOnExisting actionOnExisting = ActionOnExisting::Ask;
284 while ( !child.isNull() )
286 actionShortcut = child.attribute( QStringLiteral(
"shortcut" ) );
287 QKeySequence actionShortcutSequence( actionShortcut );
288 QString previousText;
292 actionName = child.attribute( QStringLiteral(
"name" ) );
295 if ( previousShortcut && previousShortcut->objectName() != actionName )
297 previousText = previousShortcut->whatsThis();
299 else if ( previousAction && previousAction->objectName() != actionName )
301 previousText = previousAction->text().remove(
'&' );
303 if ( !previousText.isEmpty() )
306 if ( QAction *action = mManager->
actionByName( actionName ) )
308 text = action->text().remove(
'&' );
310 else if ( QShortcut *shortcut = mManager->
shortcutByName( actionName ) )
312 text = shortcut->whatsThis();
315 if ( actionOnExisting == ActionOnExisting::Ask )
317 const int res = QMessageBox::question(
this, tr(
"Load Shortcut" ), tr(
"Shortcut %1 is already assigned to action %2. Reassign to %3?" ).arg( actionShortcut, previousText, text ), QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll );
318 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
320 if ( res == QMessageBox::NoToAll )
322 actionOnExisting = ActionOnExisting::SkipAll;
324 child = child.nextSiblingElement();
327 if ( res == QMessageBox::YesToAll )
329 actionOnExisting = ActionOnExisting::ReassignAll;
332 else if ( actionOnExisting == ActionOnExisting::SkipAll )
334 child = child.nextSiblingElement();
337 mManager->
setObjectKeySequence( previousAction ? qobject_cast<QObject *>( previousAction ) : qobject_cast<QObject *>( previousShortcut ), QString() );
343 actionSettingKey = child.attribute( QStringLiteral(
"setting" ) );
348 if ( previousObj && previousObj != obj )
350 if ( QAction *previousAction = qobject_cast<QAction *>( previousObj ) )
352 previousText = previousAction->text().remove(
'&' );
354 else if ( QShortcut *previousShortcut = qobject_cast<QShortcut *>( previousObj ) )
356 previousText = previousShortcut->whatsThis();
360 if ( !previousText.isEmpty() )
363 if ( QAction *action = qobject_cast<QAction *>( obj ) )
365 text = action->text().remove(
'&' );
367 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
369 text = shortcut->whatsThis();
372 if ( actionOnExisting == ActionOnExisting::Ask )
374 const int res = QMessageBox::question(
this, tr(
"Load Shortcut" ), tr(
"Shortcut %1 is already assigned to action %2. Reassign to %3?" ).arg( actionShortcut, previousText, text ), QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll );
375 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
377 if ( res == QMessageBox::NoToAll )
379 actionOnExisting = ActionOnExisting::SkipAll;
381 child = child.nextSiblingElement();
384 if ( res == QMessageBox::YesToAll )
386 actionOnExisting = ActionOnExisting::ReassignAll;
389 else if ( actionOnExisting == ActionOnExisting::SkipAll )
391 child = child.nextSiblingElement();
400 child = child.nextSiblingElement();
403 treeActions->clear();
407void QgsConfigureShortcutsDialog::changeShortcut()
410 setGettingShortcut(
true );
413void QgsConfigureShortcutsDialog::resetShortcut()
415 QObject *
object = currentObject();
417 setCurrentActionShortcut( sequence );
420void QgsConfigureShortcutsDialog::setNoShortcut()
422 setCurrentActionShortcut( QKeySequence() );
425QAction *QgsConfigureShortcutsDialog::currentAction()
427 return qobject_cast<QAction *>( currentObject() );
430QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
432 return qobject_cast<QShortcut *>( currentObject() );
435void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
440 setGettingShortcut(
false );
443 QKeySequence sequence;
444 if ( QAction *action = currentAction() )
448 sequence = action->shortcut();
450 else if ( QShortcut *
object = currentShortcut() )
454 sequence =
object->key();
461 if ( shortcut.isEmpty() )
462 shortcut = tr(
"None" );
463 btnResetShortcut->setText( tr(
"Set default (%1)" ).arg( shortcut ) );
466 btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
468 btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
473 if ( !mGettingShortcut )
475 QDialog::keyPressEvent( event );
479 const int key =
event->key();
484 mModifiers |= Qt::META;
485 updateShortcutText();
488 mModifiers |= Qt::ALT;
489 updateShortcutText();
491 case Qt::Key_Control:
492 mModifiers |= Qt::CTRL;
493 updateShortcutText();
496 mModifiers |= Qt::SHIFT;
497 updateShortcutText();
502 setGettingShortcut(
false );
507 updateShortcutText();
513 if ( !mGettingShortcut )
515 QDialog::keyReleaseEvent( event );
519 const int key =
event->key();
524 mModifiers &= ~Qt::META;
525 updateShortcutText();
528 mModifiers &= ~Qt::ALT;
529 updateShortcutText();
531 case Qt::Key_Control:
532 mModifiers &= ~Qt::CTRL;
533 updateShortcutText();
536 mModifiers &= ~Qt::SHIFT;
537 updateShortcutText();
546 setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
547 setGettingShortcut(
false );
552QObject *QgsConfigureShortcutsDialog::currentObject()
554 if ( !treeActions->currentItem() )
557 QObject *
object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
561void QgsConfigureShortcutsDialog::updateShortcutText()
564 const QKeySequence s( mModifiers + mKey );
565 btnChangeShortcut->setText( tr(
"Input: " ) + s.toString( QKeySequence::NativeText ) );
568void QgsConfigureShortcutsDialog::setGettingShortcut(
bool getting )
572 mGettingShortcut = getting;
575 btnChangeShortcut->setChecked(
false );
576 btnChangeShortcut->setText( tr(
"Change" ) );
580 updateShortcutText();
584void QgsConfigureShortcutsDialog::setCurrentActionShortcut(
const QKeySequence &s )
586 QObject *
object = currentObject();
592 if ( otherObject ==
object )
598 if ( QAction *otherAction = qobject_cast<QAction *>( otherObject ) )
600 otherText = otherAction->text();
601 otherText.remove(
'&' );
603 else if ( QShortcut *otherShortcut = qobject_cast<QShortcut *>( otherObject ) )
605 otherText = otherShortcut->whatsThis();
608 const int res = QMessageBox::question(
this, tr(
"Change Shortcut" ), tr(
"This shortcut is already assigned to action %1. Reassign?" ).arg( otherText ), QMessageBox::Yes | QMessageBox::No );
610 if ( res != QMessageBox::Yes )
615 QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
616 if ( !items.isEmpty() )
617 items[0]->setText( 1, QString() );
624 treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
626 actionChanged( treeActions->currentItem(),
nullptr );
629void QgsConfigureShortcutsDialog::mLeFilter_textChanged(
const QString &text )
631 for (
int i = 0; i < treeActions->topLevelItemCount(); i++ )
633 QTreeWidgetItem *item = treeActions->topLevelItem( i );
634 if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
636 item->setHidden(
true );
640 item->setHidden(
false );
645void QgsConfigureShortcutsDialog::showHelp()
647 QgsHelp::openHelp( QStringLiteral(
"introduction/qgis_configuration.html#shortcuts" ) );
650void QgsConfigureShortcutsDialog::saveShortcutsPdf()
652 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"PDF file" ) +
" (*.pdf);;" + tr(
"All files" ) +
" (*)" );
657 if ( fileName.isEmpty() )
660 if ( !fileName.endsWith( QLatin1String(
".pdf" ), Qt::CaseInsensitive ) )
662 fileName += QLatin1String(
".pdf" );
665 QTextDocument *document =
new QTextDocument;
666 QTextCursor cursor( document );
668 QTextTableFormat tableFormat;
669 tableFormat.setBorder( 0 );
670 tableFormat.setCellSpacing( 0 );
671 tableFormat.setCellPadding( 4 );
672 tableFormat.setHeaderRowCount( 1 );
674 QVector<QTextLength> constraints;
675 constraints << QTextLength( QTextLength::PercentageLength, 5 );
676 constraints << QTextLength( QTextLength::PercentageLength, 80 );
677 constraints << QTextLength( QTextLength::PercentageLength, 15 );
678 tableFormat.setColumnWidthConstraints( constraints );
680 QTextTableCellFormat headerFormat;
681 headerFormat.setFontWeight( QFont::Bold );
682 headerFormat.setBottomPadding( 4 );
684 QTextCharFormat rowFormat;
685 rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
687 QTextCharFormat altRowFormat;
688 altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
689 altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
692 QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
693 table->mergeCells( 0, 0, 1, 2 );
694 QTextCursor
c = table->cellAt( row, 0 ).firstCursorPosition();
695 c.setCharFormat( headerFormat );
696 c.insertText( tr(
"Action" ) );
697 c = table->cellAt( row, 2 ).firstCursorPosition();
698 c.setCharFormat( headerFormat );
699 c.insertText( tr(
"Shortcut" ) );
701 const QList<QObject *> objects = mManager->
listAll();
702 for ( QObject *obj : objects )
708 if ( QAction *action = qobject_cast<QAction *>( obj ) )
710 actionText = action->text().remove(
'&' );
711 sequence = action->shortcut().toString( QKeySequence::NativeText );
712 icon = action->icon();
714 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
716 actionText = shortcut->whatsThis();
717 sequence = shortcut->key().toString( QKeySequence::NativeText );
718 icon = shortcut->property(
"Icon" ).value<QIcon>();
726 if ( actionText.isEmpty() || sequence.isEmpty() )
732 table->appendRows( 1 );
736 table->cellAt( row, 0 ).setFormat( altRowFormat );
737 table->cellAt( row, 1 ).setFormat( altRowFormat );
738 table->cellAt( row, 2 ).setFormat( altRowFormat );
742 table->cellAt( row, 0 ).setFormat( rowFormat );
743 table->cellAt( row, 1 ).setFormat( rowFormat );
744 table->cellAt( row, 2 ).setFormat( rowFormat );
747 if ( !icon.isNull() )
749 c = table->cellAt( row, 0 ).firstCursorPosition();
750 c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
752 table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
753 table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
756 QPdfWriter pdfWriter( fileName );
757 pdfWriter.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
758 document->setPageSize( QSizeF( pdfWriter.pageLayout().fullRect( QPageLayout::Point ).size() ) );
759 document->print( &pdfWriter );
static const QgsSettingsEntryBool * settingsLocaleOverrideFlag
Settings entry locale override flag.
static const QgsSettingsEntryString * settingsLocaleUserLocale
Settings entry locale user locale.
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
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...
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Describes the version of a project.
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.
QString key(const QString &dynamicKeyPart=QString()) const
Returns settings entry key.
Stores settings for use within QGIS.
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.
QObject * objectForSettingKey(const QString &name) const
Returns the QShortcut or QAction matching the the full setting key Return nullptr if the key was not ...
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.
QShortcut * shortcutByName(const QString &name) const
Returns a shortcut by its name, or nullptr if nothing found.
QAction * actionByName(const QString &name) const
Returns an action by its name, or nullptr if nothing found.
QShortcut * shortcutForSequence(const QKeySequence &sequence) const
Returns the shortcut which is associated for a key sequence, or nullptr if no shortcut is associated.
QString objectSettingKey(QObject *object) const
Returns the full settings key matching the QShortcut or QAction Return an empty QString if the QObjec...
QAction * actionForSequence(const QKeySequence &sequence) const
Returns the action which is associated for a shortcut sequence, or nullptr if no action is associated...
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