26#include <QDomDocument>
29#include <QKeySequence>
34#include <QTextCharFormat>
36#include <QTextDocument>
39#include <QTextTableCellFormat>
40#include <QTextTableFormat>
42#include "moc_qgsconfigureshortcutsdialog.cpp"
51 mSaveMenu =
new QMenu(
this );
52 mSaveUserShortcuts =
new QAction( tr(
"Save User Shortcuts…" ),
this );
53 mSaveMenu->addAction( mSaveUserShortcuts );
54 connect( mSaveUserShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(
false ); } );
56 mSaveAllShortcuts =
new QAction( tr(
"Save All Shortcuts…" ),
this );
57 mSaveMenu->addAction( mSaveAllShortcuts );
58 connect( mSaveAllShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(); } );
60 mSaveAsPdf =
new QAction( tr(
"Save as PDF…" ),
this );
61 mSaveMenu->addAction( mSaveAsPdf );
62 connect( mSaveAsPdf, &QAction::triggered,
this, &QgsConfigureShortcutsDialog::saveShortcutsPdf );
64 btnSaveShortcuts->setMenu( mSaveMenu );
66 connect( mLeFilter, &QgsFilterLineEdit::textChanged,
this, &QgsConfigureShortcutsDialog::mLeFilter_textChanged );
71 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsConfigureShortcutsDialog::showHelp );
72 connect( btnChangeShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::changeShortcut );
73 connect( btnResetShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::resetShortcut );
74 connect( btnSetNoShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::setNoShortcut );
75 connect( btnLoadShortcuts, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::loadShortcuts );
77 connect( treeActions, &QTreeWidget::currentItemChanged,
this, &QgsConfigureShortcutsDialog::actionChanged );
82void QgsConfigureShortcutsDialog::populateActions()
84 const QList<QObject *> objects = mManager->
listAll();
86 QList<QTreeWidgetItem *> items;
87 items.reserve( objects.count() );
88 const auto constObjects = objects;
89 for ( QObject *obj : constObjects )
96 if ( QAction *action = qobject_cast<QAction *>( obj ) )
98 actionText = action->text();
99 actionText.remove(
'&' );
100 sequence = action->shortcut().toString( QKeySequence::NativeText );
101 icon = action->icon();
103 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
105 actionText = shortcut->whatsThis();
106 sequence = shortcut->key().toString( QKeySequence::NativeText );
107 icon = shortcut->property(
"Icon" ).value<QIcon>();
114 if ( actionText.isEmpty() )
120 lst << actionText << sequence;
121 QTreeWidgetItem *item =
new QTreeWidgetItem( lst );
122 item->setIcon( 0, icon );
123 item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
124 item->setToolTip( 0, settingKey );
125 items.append( item );
128 treeActions->addTopLevelItems( items );
131 treeActions->resizeColumnToContents( 0 );
132 treeActions->sortItems( 0, Qt::AscendingOrder );
134 actionChanged( treeActions->currentItem(),
nullptr );
137void QgsConfigureShortcutsDialog::saveShortcuts(
bool saveAll )
139 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
144 if ( fileName.isEmpty() )
148 if ( !fileName.endsWith( QLatin1String(
".xml" ), Qt::CaseInsensitive ) )
150 fileName += QLatin1String(
".xml" );
153 QFile file( fileName );
154 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
156 QMessageBox::warning(
this, tr(
"Saving Shortcuts" ), tr(
"Cannot write file %1:\n%2." ).arg( fileName, file.errorString() ) );
160 QgsSettings settings;
162 QDomDocument doc( QStringLiteral(
"shortcuts" ) );
163 QDomElement root = doc.createElement( QStringLiteral(
"qgsshortcuts" ) );
164 root.setAttribute( QStringLiteral(
"version" ), QStringLiteral(
"1.1" ) );
166 doc.appendChild( root );
168 const QList<QObject *> objects = mManager->listAll();
169 for ( QObject *obj : objects )
172 QString actionShortcut;
173 QString actionSettingKey;
174 QKeySequence sequence;
176 if ( QAction *action = qobject_cast<QAction *>( obj ) )
178 actionText = action->text().remove(
'&' );
179 actionShortcut = action->shortcut().toString( QKeySequence::NativeText );
180 sequence = mManager->defaultKeySequence( action );
182 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
184 actionText = shortcut->whatsThis();
185 actionShortcut = shortcut->key().toString( QKeySequence::NativeText );
186 sequence = mManager->defaultKeySequence( shortcut );
193 actionSettingKey = mManager->objectSettingKey( obj );
195 if ( actionSettingKey.isEmpty() )
201 if ( !saveAll && sequence == QKeySequence( actionShortcut ) )
206 QDomElement el = doc.createElement( QStringLiteral(
"action" ) );
207 el.setAttribute( QStringLiteral(
"name" ), actionText );
208 el.setAttribute( QStringLiteral(
"shortcut" ), actionShortcut );
209 el.setAttribute( QStringLiteral(
"setting" ), actionSettingKey );
210 root.appendChild( el );
213 QTextStream out( &file );
217void QgsConfigureShortcutsDialog::loadShortcuts()
219 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
221 if ( fileName.isEmpty() )
226 QFile file( fileName );
227 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
229 QMessageBox::warning(
this, tr(
"Loading Shortcuts" ), tr(
"Cannot read file %1:\n%2." ).arg( fileName, file.errorString() ) );
238 if ( !doc.setContent( &file,
true, &errorStr, &errorLine, &errorColumn ) )
240 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"Parse error at line %1, column %2:\n%3" ).arg( errorLine ).arg( errorColumn ).arg( errorStr ) );
244 const QDomElement root = doc.documentElement();
245 if ( root.tagName() != QLatin1String(
"qgsshortcuts" ) )
247 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file is not an shortcuts exchange file." ) );
251 QString currentLocale;
254 if ( localeOverrideFlag )
260 currentLocale = QLocale().name();
263 const QString versionStr = root.attribute( QStringLiteral(
"version" ) );
264 const QgsProjectVersion version( versionStr );
266 if ( root.attribute( QStringLiteral(
"locale" ) ) != currentLocale )
268 if ( version < QgsProjectVersion( QStringLiteral(
"1.1" ) ) )
270 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so you can't use it." ) );
275 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so some shortcuts may not work." ) );
280 QString actionShortcut;
281 QString actionSettingKey;
283 QDomElement child = root.firstChildElement();
284 ActionOnExisting actionOnExisting = ActionOnExisting::Ask;
285 while ( !child.isNull() )
287 actionShortcut = child.attribute( QStringLiteral(
"shortcut" ) );
288 QKeySequence actionShortcutSequence( actionShortcut );
289 QString previousText;
291 if ( version < QgsProjectVersion( QStringLiteral(
"1.1" ) ) )
293 actionName = child.attribute( QStringLiteral(
"name" ) );
294 QShortcut *previousShortcut = mManager->shortcutForSequence( actionShortcutSequence );
295 QAction *previousAction = mManager->actionForSequence( actionShortcutSequence );
296 if ( previousShortcut && previousShortcut->objectName() != actionName )
298 previousText = previousShortcut->whatsThis();
300 else if ( previousAction && previousAction->objectName() != actionName )
302 previousText = previousAction->text().remove(
'&' );
304 if ( !previousText.isEmpty() )
307 if ( QAction *action = mManager->actionByName( actionName ) )
309 text = action->text().remove(
'&' );
311 else if ( QShortcut *shortcut = mManager->shortcutByName( actionName ) )
313 text = shortcut->whatsThis();
316 if ( actionOnExisting == ActionOnExisting::Ask )
318 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 );
319 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
321 if ( res == QMessageBox::NoToAll )
323 actionOnExisting = ActionOnExisting::SkipAll;
325 child = child.nextSiblingElement();
328 if ( res == QMessageBox::YesToAll )
330 actionOnExisting = ActionOnExisting::ReassignAll;
333 else if ( actionOnExisting == ActionOnExisting::SkipAll )
335 child = child.nextSiblingElement();
338 mManager->setObjectKeySequence( previousAction ? qobject_cast<QObject *>( previousAction ) : qobject_cast<QObject *>( previousShortcut ), QString() );
340 mManager->setKeySequence( actionName, actionShortcut );
344 actionSettingKey = child.attribute( QStringLiteral(
"setting" ) );
345 QObject *obj = mManager->objectForSettingKey( actionSettingKey );
348 QObject *previousObj = mManager->objectForSequence( actionShortcutSequence );
349 if ( previousObj && previousObj != obj )
351 if ( QAction *previousAction = qobject_cast<QAction *>( previousObj ) )
353 previousText = previousAction->text().remove(
'&' );
355 else if ( QShortcut *previousShortcut = qobject_cast<QShortcut *>( previousObj ) )
357 previousText = previousShortcut->whatsThis();
361 if ( !previousText.isEmpty() )
364 if ( QAction *action = qobject_cast<QAction *>( obj ) )
366 text = action->text().remove(
'&' );
368 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
370 text = shortcut->whatsThis();
373 if ( actionOnExisting == ActionOnExisting::Ask )
375 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 );
376 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
378 if ( res == QMessageBox::NoToAll )
380 actionOnExisting = ActionOnExisting::SkipAll;
382 child = child.nextSiblingElement();
385 if ( res == QMessageBox::YesToAll )
387 actionOnExisting = ActionOnExisting::ReassignAll;
390 else if ( actionOnExisting == ActionOnExisting::SkipAll )
392 child = child.nextSiblingElement();
395 mManager->setObjectKeySequence( previousObj, QString() );
397 mManager->setObjectKeySequence( obj, actionShortcut );
401 child = child.nextSiblingElement();
404 treeActions->clear();
408void QgsConfigureShortcutsDialog::changeShortcut()
411 setGettingShortcut(
true );
414void QgsConfigureShortcutsDialog::resetShortcut()
416 QObject *
object = currentObject();
417 const QString sequence = mManager->objectDefaultKeySequence(
object );
418 setCurrentActionShortcut( sequence );
421void QgsConfigureShortcutsDialog::setNoShortcut()
423 setCurrentActionShortcut( QKeySequence() );
426QAction *QgsConfigureShortcutsDialog::currentAction()
428 return qobject_cast<QAction *>( currentObject() );
431QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
433 return qobject_cast<QShortcut *>( currentObject() );
436void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
441 setGettingShortcut(
false );
444 QKeySequence sequence;
445 if ( QAction *action = currentAction() )
448 shortcut = mManager->defaultKeySequence( action );
449 sequence = action->shortcut();
451 else if ( QShortcut *
object = currentShortcut() )
454 shortcut = mManager->defaultKeySequence(
object );
455 sequence =
object->key();
462 if ( shortcut.isEmpty() )
463 shortcut = tr(
"None" );
464 btnResetShortcut->setText( tr(
"Set default (%1)" ).arg( shortcut ) );
467 btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
469 btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
474 if ( !mGettingShortcut )
476 QDialog::keyPressEvent( event );
480 const int key =
event->key();
485 mModifiers |= Qt::META;
486 updateShortcutText();
489 mModifiers |= Qt::ALT;
490 updateShortcutText();
492 case Qt::Key_Control:
493 mModifiers |= Qt::CTRL;
494 updateShortcutText();
497 mModifiers |= Qt::SHIFT;
498 updateShortcutText();
503 setGettingShortcut(
false );
508 updateShortcutText();
514 if ( !mGettingShortcut )
516 QDialog::keyReleaseEvent( event );
520 const int key =
event->key();
525 mModifiers &= ~Qt::META;
526 updateShortcutText();
529 mModifiers &= ~Qt::ALT;
530 updateShortcutText();
532 case Qt::Key_Control:
533 mModifiers &= ~Qt::CTRL;
534 updateShortcutText();
537 mModifiers &= ~Qt::SHIFT;
538 updateShortcutText();
547 setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
548 setGettingShortcut(
false );
553QObject *QgsConfigureShortcutsDialog::currentObject()
555 if ( !treeActions->currentItem() )
558 QObject *
object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
562void QgsConfigureShortcutsDialog::updateShortcutText()
565 const QKeySequence s( mModifiers + mKey );
566 btnChangeShortcut->setText( tr(
"Input: " ) + s.toString( QKeySequence::NativeText ) );
569void QgsConfigureShortcutsDialog::setGettingShortcut(
bool getting )
573 mGettingShortcut = getting;
576 btnChangeShortcut->setChecked(
false );
577 btnChangeShortcut->setText( tr(
"Change" ) );
581 updateShortcutText();
585void QgsConfigureShortcutsDialog::setCurrentActionShortcut(
const QKeySequence &s )
587 QObject *
object = currentObject();
592 QObject *otherObject = mManager->objectForSequence( s );
593 if ( otherObject ==
object )
599 if ( QAction *otherAction = qobject_cast<QAction *>( otherObject ) )
601 otherText = otherAction->text();
602 otherText.remove(
'&' );
604 else if ( QShortcut *otherShortcut = qobject_cast<QShortcut *>( otherObject ) )
606 otherText = otherShortcut->whatsThis();
609 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 );
611 if ( res != QMessageBox::Yes )
615 mManager->setObjectKeySequence( otherObject, QString() );
616 QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
617 if ( !items.isEmpty() )
618 items[0]->setText( 1, QString() );
622 mManager->setObjectKeySequence(
object, s.toString( QKeySequence::NativeText ) );
625 treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
627 actionChanged( treeActions->currentItem(),
nullptr );
630void QgsConfigureShortcutsDialog::mLeFilter_textChanged(
const QString &text )
632 for (
int i = 0; i < treeActions->topLevelItemCount(); i++ )
634 QTreeWidgetItem *item = treeActions->topLevelItem( i );
635 if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
637 item->setHidden(
true );
641 item->setHidden(
false );
646void QgsConfigureShortcutsDialog::showHelp()
648 QgsHelp::openHelp( QStringLiteral(
"introduction/qgis_configuration.html#shortcuts" ) );
651void QgsConfigureShortcutsDialog::saveShortcutsPdf()
653 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"PDF file" ) +
" (*.pdf);;" + tr(
"All files" ) +
" (*)" );
658 if ( fileName.isEmpty() )
661 if ( !fileName.endsWith( QLatin1String(
".pdf" ), Qt::CaseInsensitive ) )
663 fileName += QLatin1String(
".pdf" );
666 QTextDocument *document =
new QTextDocument;
667 QTextCursor cursor( document );
669 QTextTableFormat tableFormat;
670 tableFormat.setBorder( 0 );
671 tableFormat.setCellSpacing( 0 );
672 tableFormat.setCellPadding( 4 );
673 tableFormat.setHeaderRowCount( 1 );
675 QVector<QTextLength> constraints;
676 constraints << QTextLength( QTextLength::PercentageLength, 5 );
677 constraints << QTextLength( QTextLength::PercentageLength, 80 );
678 constraints << QTextLength( QTextLength::PercentageLength, 15 );
679 tableFormat.setColumnWidthConstraints( constraints );
681 QTextTableCellFormat headerFormat;
682 headerFormat.setFontWeight( QFont::Bold );
683 headerFormat.setBottomPadding( 4 );
685 QTextCharFormat rowFormat;
686 rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
688 QTextCharFormat altRowFormat;
689 altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
690 altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
693 QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
694 table->mergeCells( 0, 0, 1, 2 );
695 QTextCursor
c = table->cellAt( row, 0 ).firstCursorPosition();
696 c.setCharFormat( headerFormat );
697 c.insertText( tr(
"Action" ) );
698 c = table->cellAt( row, 2 ).firstCursorPosition();
699 c.setCharFormat( headerFormat );
700 c.insertText( tr(
"Shortcut" ) );
702 const QList<QObject *> objects = mManager->listAll();
703 for ( QObject *obj : objects )
709 if ( QAction *action = qobject_cast<QAction *>( obj ) )
711 actionText = action->text().remove(
'&' );
712 sequence = action->shortcut().toString( QKeySequence::NativeText );
713 icon = action->icon();
715 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
717 actionText = shortcut->whatsThis();
718 sequence = shortcut->key().toString( QKeySequence::NativeText );
719 icon = shortcut->property(
"Icon" ).value<QIcon>();
727 if ( actionText.isEmpty() || sequence.isEmpty() )
733 table->appendRows( 1 );
737 table->cellAt( row, 0 ).setFormat( altRowFormat );
738 table->cellAt( row, 1 ).setFormat( altRowFormat );
739 table->cellAt( row, 2 ).setFormat( altRowFormat );
743 table->cellAt( row, 0 ).setFormat( rowFormat );
744 table->cellAt( row, 1 ).setFormat( rowFormat );
745 table->cellAt( row, 2 ).setFormat( rowFormat );
748 if ( !icon.isNull() )
750 c = table->cellAt( row, 0 ).firstCursorPosition();
751 c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
753 table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
754 table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
757 QPdfWriter pdfWriter( fileName );
758 pdfWriter.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
759 document->setPageSize( QSizeF( pdfWriter.pageLayout().fullRect( QPageLayout::Point ).size() ) );
760 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.
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.
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...
QList< QObject * > listAll() const
Returns a list of both actions and shortcuts in the manager.
QString objectSettingKey(QObject *object) const
Returns the full settings key matching the QShortcut or QAction Return an empty QString if the QObjec...
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