26#include <QDomDocument>
29#include <QKeySequence>
35#include <QTextCharFormat>
37#include <QTextDocument>
40#include <QTextTableCellFormat>
41#include <QTextTableFormat>
43#include "moc_qgsconfigureshortcutsdialog.cpp"
45using namespace Qt::StringLiterals;
54 mSaveMenu =
new QMenu(
this );
55 mSaveUserShortcuts =
new QAction( tr(
"Save User Shortcuts…" ),
this );
56 mSaveMenu->addAction( mSaveUserShortcuts );
57 connect( mSaveUserShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(
false ); } );
59 mSaveAllShortcuts =
new QAction( tr(
"Save All Shortcuts…" ),
this );
60 mSaveMenu->addAction( mSaveAllShortcuts );
61 connect( mSaveAllShortcuts, &QAction::triggered,
this, [
this] { saveShortcuts(); } );
63 mSaveAsPdf =
new QAction( tr(
"Save as PDF…" ),
this );
64 mSaveMenu->addAction( mSaveAsPdf );
65 connect( mSaveAsPdf, &QAction::triggered,
this, &QgsConfigureShortcutsDialog::saveShortcutsPdf );
67 btnSaveShortcuts->setMenu( mSaveMenu );
69 connect( mLeFilter, &QgsFilterLineEdit::textChanged,
this, &QgsConfigureShortcutsDialog::mLeFilter_textChanged );
74 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsConfigureShortcutsDialog::showHelp );
75 connect( btnChangeShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::changeShortcut );
76 connect( btnResetShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::resetShortcut );
77 connect( btnSetNoShortcut, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::setNoShortcut );
78 connect( btnLoadShortcuts, &QAbstractButton::clicked,
this, &QgsConfigureShortcutsDialog::loadShortcuts );
80 connect( treeActions, &QTreeWidget::currentItemChanged,
this, &QgsConfigureShortcutsDialog::actionChanged );
87 mLeFilter->setText( filterText );
90void QgsConfigureShortcutsDialog::populateActions()
92 const QList<QObject *> objects = mManager->
listAll();
94 QList<QTreeWidgetItem *> items;
95 items.reserve( objects.count() );
96 const auto constObjects = objects;
97 for ( QObject *obj : constObjects )
104 if ( QAction *action = qobject_cast<QAction *>( obj ) )
106 actionText = action->text();
107 actionText.remove(
'&' );
108 sequence = action->shortcut().toString( QKeySequence::NativeText );
109 icon = action->icon();
111 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
113 actionText = shortcut->whatsThis();
114 sequence = shortcut->key().toString( QKeySequence::NativeText );
115 icon = shortcut->property(
"Icon" ).value<QIcon>();
122 if ( actionText.isEmpty() )
128 lst << actionText << sequence;
129 QTreeWidgetItem *item =
new QTreeWidgetItem( lst );
130 item->setIcon( 0, icon );
131 item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
132 item->setToolTip( 0, settingKey );
133 items.append( item );
136 treeActions->addTopLevelItems( items );
139 treeActions->resizeColumnToContents( 0 );
140 treeActions->sortItems( 0, Qt::AscendingOrder );
142 actionChanged( treeActions->currentItem(),
nullptr );
145void QgsConfigureShortcutsDialog::saveShortcuts(
bool saveAll )
147 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
152 if ( fileName.isEmpty() )
156 if ( !fileName.endsWith(
".xml"_L1, Qt::CaseInsensitive ) )
158 fileName +=
".xml"_L1;
161 QFile file( fileName );
162 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
164 QMessageBox::warning(
this, tr(
"Saving Shortcuts" ), tr(
"Cannot write file %1:\n%2." ).arg( fileName, file.errorString() ) );
168 QgsSettings settings;
170 QDomDocument doc( u
"shortcuts"_s );
171 QDomElement root = doc.createElement( u
"qgsshortcuts"_s );
172 root.setAttribute( u
"version"_s, u
"1.1"_s );
174 doc.appendChild( root );
176 const QList<QObject *> objects = mManager->listAll();
177 for ( QObject *obj : objects )
180 QString actionShortcut;
181 QString actionSettingKey;
182 QKeySequence sequence;
184 if ( QAction *action = qobject_cast<QAction *>( obj ) )
186 actionText = action->text().remove(
'&' );
187 actionShortcut = action->shortcut().toString( QKeySequence::NativeText );
188 sequence = mManager->defaultKeySequence( action );
190 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
192 actionText = shortcut->whatsThis();
193 actionShortcut = shortcut->key().toString( QKeySequence::NativeText );
194 sequence = mManager->defaultKeySequence( shortcut );
201 actionSettingKey = mManager->objectSettingKey( obj );
203 if ( actionSettingKey.isEmpty() )
209 if ( !saveAll && sequence == QKeySequence( actionShortcut ) )
214 QDomElement el = doc.createElement( u
"action"_s );
215 el.setAttribute( u
"name"_s, actionText );
216 el.setAttribute( u
"shortcut"_s, actionShortcut );
217 el.setAttribute( u
"setting"_s, actionSettingKey );
218 root.appendChild( el );
221 QTextStream out( &file );
225void QgsConfigureShortcutsDialog::loadShortcuts()
227 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
229 if ( fileName.isEmpty() )
234 QFile file( fileName );
235 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
237 QMessageBox::warning(
this, tr(
"Loading Shortcuts" ), tr(
"Cannot read file %1:\n%2." ).arg( fileName, file.errorString() ) );
246 if ( !doc.setContent( &file,
true, &errorStr, &errorLine, &errorColumn ) )
248 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"Parse error at line %1, column %2:\n%3" ).arg( errorLine ).arg( errorColumn ).arg( errorStr ) );
252 const QDomElement root = doc.documentElement();
253 if ( root.tagName() !=
"qgsshortcuts"_L1 )
255 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file is not an shortcuts exchange file." ) );
259 QString currentLocale;
262 if ( localeOverrideFlag )
268 currentLocale = QLocale().name();
271 const QString versionStr = root.attribute( u
"version"_s );
272 const QgsProjectVersion version( versionStr );
274 if ( root.attribute( u
"locale"_s ) != currentLocale )
276 if ( version < QgsProjectVersion( u
"1.1"_s ) )
278 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so you can't use it." ) );
283 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so some shortcuts may not work." ) );
288 QString actionShortcut;
289 QString actionSettingKey;
291 QDomElement child = root.firstChildElement();
292 ActionOnExisting actionOnExisting = ActionOnExisting::Ask;
293 while ( !child.isNull() )
295 actionShortcut = child.attribute( u
"shortcut"_s );
296 QKeySequence actionShortcutSequence( actionShortcut );
297 QString previousText;
299 if ( version < QgsProjectVersion( u
"1.1"_s ) )
301 actionName = child.attribute( u
"name"_s );
302 QShortcut *previousShortcut = mManager->shortcutForSequence( actionShortcutSequence );
303 QAction *previousAction = mManager->actionForSequence( actionShortcutSequence );
304 if ( previousShortcut && previousShortcut->objectName() != actionName )
306 previousText = previousShortcut->whatsThis();
308 else if ( previousAction && previousAction->objectName() != actionName )
310 previousText = previousAction->text().remove(
'&' );
312 if ( !previousText.isEmpty() )
315 if ( QAction *action = mManager->actionByName( actionName ) )
317 text = action->text().remove(
'&' );
319 else if ( QShortcut *shortcut = mManager->shortcutByName( actionName ) )
321 text = shortcut->whatsThis();
324 if ( actionOnExisting == ActionOnExisting::Ask )
326 const int res = QMessageBox::
327 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 );
328 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
330 if ( res == QMessageBox::NoToAll )
332 actionOnExisting = ActionOnExisting::SkipAll;
334 child = child.nextSiblingElement();
337 if ( res == QMessageBox::YesToAll )
339 actionOnExisting = ActionOnExisting::ReassignAll;
342 else if ( actionOnExisting == ActionOnExisting::SkipAll )
344 child = child.nextSiblingElement();
347 mManager->setObjectKeySequence( previousAction ? qobject_cast<QObject *>( previousAction ) : qobject_cast<QObject *>( previousShortcut ), QString() );
349 mManager->setKeySequence( actionName, actionShortcut );
353 actionSettingKey = child.attribute( u
"setting"_s );
354 QObject *obj = mManager->objectForSettingKey( actionSettingKey );
357 QObject *previousObj = mManager->objectForSequence( actionShortcutSequence );
358 if ( previousObj && previousObj != obj )
360 if ( QAction *previousAction = qobject_cast<QAction *>( previousObj ) )
362 previousText = previousAction->text().remove(
'&' );
364 else if ( QShortcut *previousShortcut = qobject_cast<QShortcut *>( previousObj ) )
366 previousText = previousShortcut->whatsThis();
370 if ( !previousText.isEmpty() )
373 if ( QAction *action = qobject_cast<QAction *>( obj ) )
375 text = action->text().remove(
'&' );
377 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
379 text = shortcut->whatsThis();
382 if ( actionOnExisting == ActionOnExisting::Ask )
384 const int res = QMessageBox::
385 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 );
386 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
388 if ( res == QMessageBox::NoToAll )
390 actionOnExisting = ActionOnExisting::SkipAll;
392 child = child.nextSiblingElement();
395 if ( res == QMessageBox::YesToAll )
397 actionOnExisting = ActionOnExisting::ReassignAll;
400 else if ( actionOnExisting == ActionOnExisting::SkipAll )
402 child = child.nextSiblingElement();
405 mManager->setObjectKeySequence( previousObj, QString() );
407 mManager->setObjectKeySequence( obj, actionShortcut );
411 child = child.nextSiblingElement();
414 treeActions->clear();
418void QgsConfigureShortcutsDialog::changeShortcut()
421 setGettingShortcut(
true );
424void QgsConfigureShortcutsDialog::resetShortcut()
426 QObject *
object = currentObject();
427 const QString sequence = mManager->objectDefaultKeySequence(
object );
428 setCurrentActionShortcut( sequence );
431void QgsConfigureShortcutsDialog::setNoShortcut()
433 setCurrentActionShortcut( QKeySequence() );
436QAction *QgsConfigureShortcutsDialog::currentAction()
438 return qobject_cast<QAction *>( currentObject() );
441QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
443 return qobject_cast<QShortcut *>( currentObject() );
446void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
451 setGettingShortcut(
false );
454 QKeySequence sequence;
455 if ( QAction *action = currentAction() )
458 shortcut = mManager->defaultKeySequence( action );
459 sequence = action->shortcut();
461 else if ( QShortcut *
object = currentShortcut() )
464 shortcut = mManager->defaultKeySequence(
object );
465 sequence =
object->key();
472 if ( shortcut.isEmpty() )
473 shortcut = tr(
"None" );
474 btnResetShortcut->setText( tr(
"Set default (%1)" ).arg( shortcut ) );
477 btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
479 btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
484 if ( !mGettingShortcut )
486 QDialog::keyPressEvent( event );
490 const int key =
event->key();
495 mModifiers.setFlag( Qt::KeyboardModifier::MetaModifier );
496 updateShortcutText();
499 mModifiers.setFlag( Qt::KeyboardModifier::AltModifier );
500 updateShortcutText();
502 case Qt::Key_Control:
503 mModifiers.setFlag( Qt::KeyboardModifier::ControlModifier );
504 updateShortcutText();
507 mModifiers.setFlag( Qt::KeyboardModifier::ShiftModifier );
508 updateShortcutText();
513 setGettingShortcut(
false );
518 updateShortcutText();
524 if ( !mGettingShortcut )
526 QDialog::keyReleaseEvent( event );
530 const int key =
event->key();
535 mModifiers.setFlag( Qt::KeyboardModifier::MetaModifier,
false );
536 updateShortcutText();
539 mModifiers.setFlag( Qt::KeyboardModifier::AltModifier,
false );
540 updateShortcutText();
542 case Qt::Key_Control:
543 mModifiers.setFlag( Qt::KeyboardModifier::ControlModifier,
false );
544 updateShortcutText();
547 mModifiers.setFlag( Qt::KeyboardModifier::ShiftModifier,
false );
548 updateShortcutText();
557 setCurrentActionShortcut( QKeySequence(
static_cast< int >( mModifiers ) + mKey ) );
558 setGettingShortcut(
false );
563QObject *QgsConfigureShortcutsDialog::currentObject()
565 if ( !treeActions->currentItem() )
568 QObject *
object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
572void QgsConfigureShortcutsDialog::updateShortcutText()
575 const QKeySequence s(
static_cast< int >( mModifiers ) + mKey );
576 btnChangeShortcut->setText( tr(
"Input: " ) + s.toString( QKeySequence::NativeText ) );
579void QgsConfigureShortcutsDialog::setGettingShortcut(
bool getting )
581 mModifiers = Qt::KeyboardModifiers();
583 mGettingShortcut = getting;
586 btnChangeShortcut->setChecked(
false );
587 btnChangeShortcut->setText( tr(
"Change" ) );
591 updateShortcutText();
595void QgsConfigureShortcutsDialog::setCurrentActionShortcut(
const QKeySequence &s )
597 QObject *
object = currentObject();
602 QObject *otherObject = mManager->objectForSequence( s );
603 if ( otherObject ==
object )
609 if ( QAction *otherAction = qobject_cast<QAction *>( otherObject ) )
611 otherText = otherAction->text();
612 otherText.remove(
'&' );
614 else if ( QShortcut *otherShortcut = qobject_cast<QShortcut *>( otherObject ) )
616 otherText = otherShortcut->whatsThis();
619 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 );
621 if ( res != QMessageBox::Yes )
625 mManager->setObjectKeySequence( otherObject, QString() );
626 QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
627 if ( !items.isEmpty() )
628 items[0]->setText( 1, QString() );
632 mManager->setObjectKeySequence(
object, s.toString( QKeySequence::NativeText ) );
635 treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
637 actionChanged( treeActions->currentItem(),
nullptr );
640void QgsConfigureShortcutsDialog::mLeFilter_textChanged(
const QString &text )
642 for (
int i = 0; i < treeActions->topLevelItemCount(); i++ )
644 QTreeWidgetItem *item = treeActions->topLevelItem( i );
645 if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
647 item->setHidden(
true );
651 item->setHidden(
false );
656void QgsConfigureShortcutsDialog::showHelp()
661void QgsConfigureShortcutsDialog::saveShortcutsPdf()
663 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"PDF file" ) +
" (*.pdf);;" + tr(
"All files" ) +
" (*)" );
668 if ( fileName.isEmpty() )
671 if ( !fileName.endsWith(
".pdf"_L1, Qt::CaseInsensitive ) )
673 fileName +=
".pdf"_L1;
676 QTextDocument *document =
new QTextDocument;
677 QTextCursor cursor( document );
679 QTextTableFormat tableFormat;
680 tableFormat.setBorder( 0 );
681 tableFormat.setCellSpacing( 0 );
682 tableFormat.setCellPadding( 4 );
683 tableFormat.setHeaderRowCount( 1 );
685 QVector<QTextLength> constraints;
686 constraints << QTextLength( QTextLength::PercentageLength, 5 );
687 constraints << QTextLength( QTextLength::PercentageLength, 80 );
688 constraints << QTextLength( QTextLength::PercentageLength, 15 );
689 tableFormat.setColumnWidthConstraints( constraints );
691 QTextTableCellFormat headerFormat;
692 headerFormat.setFontWeight( QFont::Bold );
693 headerFormat.setBottomPadding( 4 );
695 QTextCharFormat rowFormat;
696 rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
698 QTextCharFormat altRowFormat;
699 altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
700 altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
703 QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
704 table->mergeCells( 0, 0, 1, 2 );
705 QTextCursor
c = table->cellAt( row, 0 ).firstCursorPosition();
706 c.setCharFormat( headerFormat );
707 c.insertText( tr(
"Action" ) );
708 c = table->cellAt( row, 2 ).firstCursorPosition();
709 c.setCharFormat( headerFormat );
710 c.insertText( tr(
"Shortcut" ) );
712 const QList<QObject *> objects = mManager->listAll();
713 for ( QObject *obj : objects )
719 if ( QAction *action = qobject_cast<QAction *>( obj ) )
721 actionText = action->text().remove(
'&' );
722 sequence = action->shortcut().toString( QKeySequence::NativeText );
723 icon = action->icon();
725 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
727 actionText = shortcut->whatsThis();
728 sequence = shortcut->key().toString( QKeySequence::NativeText );
729 icon = shortcut->property(
"Icon" ).value<QIcon>();
737 if ( actionText.isEmpty() || sequence.isEmpty() )
743 table->appendRows( 1 );
747 table->cellAt( row, 0 ).setFormat( altRowFormat );
748 table->cellAt( row, 1 ).setFormat( altRowFormat );
749 table->cellAt( row, 2 ).setFormat( altRowFormat );
753 table->cellAt( row, 0 ).setFormat( rowFormat );
754 table->cellAt( row, 1 ).setFormat( rowFormat );
755 table->cellAt( row, 2 ).setFormat( rowFormat );
758 if ( !icon.isNull() )
760 c = table->cellAt( row, 0 ).firstCursorPosition();
761 c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
763 table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
764 table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
767 QPdfWriter pdfWriter( fileName );
768 pdfWriter.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
769 document->setPageSize( QSizeF( pdfWriter.pageLayout().fullRect( QPageLayout::Point ).size() ) );
770 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