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 );
85void QgsConfigureShortcutsDialog::populateActions()
87 const QList<QObject *> objects = mManager->
listAll();
89 QList<QTreeWidgetItem *> items;
90 items.reserve( objects.count() );
91 const auto constObjects = objects;
92 for ( QObject *obj : constObjects )
99 if ( QAction *action = qobject_cast<QAction *>( obj ) )
101 actionText = action->text();
102 actionText.remove(
'&' );
103 sequence = action->shortcut().toString( QKeySequence::NativeText );
104 icon = action->icon();
106 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
108 actionText = shortcut->whatsThis();
109 sequence = shortcut->key().toString( QKeySequence::NativeText );
110 icon = shortcut->property(
"Icon" ).value<QIcon>();
117 if ( actionText.isEmpty() )
123 lst << actionText << sequence;
124 QTreeWidgetItem *item =
new QTreeWidgetItem( lst );
125 item->setIcon( 0, icon );
126 item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
127 item->setToolTip( 0, settingKey );
128 items.append( item );
131 treeActions->addTopLevelItems( items );
134 treeActions->resizeColumnToContents( 0 );
135 treeActions->sortItems( 0, Qt::AscendingOrder );
137 actionChanged( treeActions->currentItem(),
nullptr );
140void QgsConfigureShortcutsDialog::saveShortcuts(
bool saveAll )
142 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
147 if ( fileName.isEmpty() )
151 if ( !fileName.endsWith(
".xml"_L1, Qt::CaseInsensitive ) )
153 fileName +=
".xml"_L1;
156 QFile file( fileName );
157 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
159 QMessageBox::warning(
this, tr(
"Saving Shortcuts" ), tr(
"Cannot write file %1:\n%2." ).arg( fileName, file.errorString() ) );
163 QgsSettings settings;
165 QDomDocument doc( u
"shortcuts"_s );
166 QDomElement root = doc.createElement( u
"qgsshortcuts"_s );
167 root.setAttribute( u
"version"_s, u
"1.1"_s );
169 doc.appendChild( root );
171 const QList<QObject *> objects = mManager->listAll();
172 for ( QObject *obj : objects )
175 QString actionShortcut;
176 QString actionSettingKey;
177 QKeySequence sequence;
179 if ( QAction *action = qobject_cast<QAction *>( obj ) )
181 actionText = action->text().remove(
'&' );
182 actionShortcut = action->shortcut().toString( QKeySequence::NativeText );
183 sequence = mManager->defaultKeySequence( action );
185 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
187 actionText = shortcut->whatsThis();
188 actionShortcut = shortcut->key().toString( QKeySequence::NativeText );
189 sequence = mManager->defaultKeySequence( shortcut );
196 actionSettingKey = mManager->objectSettingKey( obj );
198 if ( actionSettingKey.isEmpty() )
204 if ( !saveAll && sequence == QKeySequence( actionShortcut ) )
209 QDomElement el = doc.createElement( u
"action"_s );
210 el.setAttribute( u
"name"_s, actionText );
211 el.setAttribute( u
"shortcut"_s, actionShortcut );
212 el.setAttribute( u
"setting"_s, actionSettingKey );
213 root.appendChild( el );
216 QTextStream out( &file );
220void QgsConfigureShortcutsDialog::loadShortcuts()
222 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Shortcuts" ), QDir::homePath(), tr(
"XML file" ) +
" (*.xml);;" + tr(
"All files" ) +
" (*)" );
224 if ( fileName.isEmpty() )
229 QFile file( fileName );
230 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
232 QMessageBox::warning(
this, tr(
"Loading Shortcuts" ), tr(
"Cannot read file %1:\n%2." ).arg( fileName, file.errorString() ) );
241 if ( !doc.setContent( &file,
true, &errorStr, &errorLine, &errorColumn ) )
243 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"Parse error at line %1, column %2:\n%3" ).arg( errorLine ).arg( errorColumn ).arg( errorStr ) );
247 const QDomElement root = doc.documentElement();
248 if ( root.tagName() !=
"qgsshortcuts"_L1 )
250 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file is not an shortcuts exchange file." ) );
254 QString currentLocale;
257 if ( localeOverrideFlag )
263 currentLocale = QLocale().name();
266 const QString versionStr = root.attribute( u
"version"_s );
267 const QgsProjectVersion version( versionStr );
269 if ( root.attribute( u
"locale"_s ) != currentLocale )
271 if ( version < QgsProjectVersion( u
"1.1"_s ) )
273 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so you can't use it." ) );
278 QMessageBox::information(
this, tr(
"Loading Shortcuts" ), tr(
"The file contains shortcuts created with different locale, so some shortcuts may not work." ) );
283 QString actionShortcut;
284 QString actionSettingKey;
286 QDomElement child = root.firstChildElement();
287 ActionOnExisting actionOnExisting = ActionOnExisting::Ask;
288 while ( !child.isNull() )
290 actionShortcut = child.attribute( u
"shortcut"_s );
291 QKeySequence actionShortcutSequence( actionShortcut );
292 QString previousText;
294 if ( version < QgsProjectVersion( u
"1.1"_s ) )
296 actionName = child.attribute( u
"name"_s );
297 QShortcut *previousShortcut = mManager->shortcutForSequence( actionShortcutSequence );
298 QAction *previousAction = mManager->actionForSequence( actionShortcutSequence );
299 if ( previousShortcut && previousShortcut->objectName() != actionName )
301 previousText = previousShortcut->whatsThis();
303 else if ( previousAction && previousAction->objectName() != actionName )
305 previousText = previousAction->text().remove(
'&' );
307 if ( !previousText.isEmpty() )
310 if ( QAction *action = mManager->actionByName( actionName ) )
312 text = action->text().remove(
'&' );
314 else if ( QShortcut *shortcut = mManager->shortcutByName( actionName ) )
316 text = shortcut->whatsThis();
319 if ( actionOnExisting == ActionOnExisting::Ask )
321 const int res = QMessageBox::
322 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 );
323 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
325 if ( res == QMessageBox::NoToAll )
327 actionOnExisting = ActionOnExisting::SkipAll;
329 child = child.nextSiblingElement();
332 if ( res == QMessageBox::YesToAll )
334 actionOnExisting = ActionOnExisting::ReassignAll;
337 else if ( actionOnExisting == ActionOnExisting::SkipAll )
339 child = child.nextSiblingElement();
342 mManager->setObjectKeySequence( previousAction ? qobject_cast<QObject *>( previousAction ) : qobject_cast<QObject *>( previousShortcut ), QString() );
344 mManager->setKeySequence( actionName, actionShortcut );
348 actionSettingKey = child.attribute( u
"setting"_s );
349 QObject *obj = mManager->objectForSettingKey( actionSettingKey );
352 QObject *previousObj = mManager->objectForSequence( actionShortcutSequence );
353 if ( previousObj && previousObj != obj )
355 if ( QAction *previousAction = qobject_cast<QAction *>( previousObj ) )
357 previousText = previousAction->text().remove(
'&' );
359 else if ( QShortcut *previousShortcut = qobject_cast<QShortcut *>( previousObj ) )
361 previousText = previousShortcut->whatsThis();
365 if ( !previousText.isEmpty() )
368 if ( QAction *action = qobject_cast<QAction *>( obj ) )
370 text = action->text().remove(
'&' );
372 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
374 text = shortcut->whatsThis();
377 if ( actionOnExisting == ActionOnExisting::Ask )
379 const int res = QMessageBox::
380 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 );
381 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
383 if ( res == QMessageBox::NoToAll )
385 actionOnExisting = ActionOnExisting::SkipAll;
387 child = child.nextSiblingElement();
390 if ( res == QMessageBox::YesToAll )
392 actionOnExisting = ActionOnExisting::ReassignAll;
395 else if ( actionOnExisting == ActionOnExisting::SkipAll )
397 child = child.nextSiblingElement();
400 mManager->setObjectKeySequence( previousObj, QString() );
402 mManager->setObjectKeySequence( obj, actionShortcut );
406 child = child.nextSiblingElement();
409 treeActions->clear();
413void QgsConfigureShortcutsDialog::changeShortcut()
416 setGettingShortcut(
true );
419void QgsConfigureShortcutsDialog::resetShortcut()
421 QObject *
object = currentObject();
422 const QString sequence = mManager->objectDefaultKeySequence(
object );
423 setCurrentActionShortcut( sequence );
426void QgsConfigureShortcutsDialog::setNoShortcut()
428 setCurrentActionShortcut( QKeySequence() );
431QAction *QgsConfigureShortcutsDialog::currentAction()
433 return qobject_cast<QAction *>( currentObject() );
436QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
438 return qobject_cast<QShortcut *>( currentObject() );
441void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
446 setGettingShortcut(
false );
449 QKeySequence sequence;
450 if ( QAction *action = currentAction() )
453 shortcut = mManager->defaultKeySequence( action );
454 sequence = action->shortcut();
456 else if ( QShortcut *
object = currentShortcut() )
459 shortcut = mManager->defaultKeySequence(
object );
460 sequence =
object->key();
467 if ( shortcut.isEmpty() )
468 shortcut = tr(
"None" );
469 btnResetShortcut->setText( tr(
"Set default (%1)" ).arg( shortcut ) );
472 btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
474 btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
479 if ( !mGettingShortcut )
481 QDialog::keyPressEvent( event );
485 const int key =
event->key();
490 mModifiers |= Qt::META;
491 updateShortcutText();
494 mModifiers |= Qt::ALT;
495 updateShortcutText();
497 case Qt::Key_Control:
498 mModifiers |= Qt::CTRL;
499 updateShortcutText();
502 mModifiers |= Qt::SHIFT;
503 updateShortcutText();
508 setGettingShortcut(
false );
513 updateShortcutText();
519 if ( !mGettingShortcut )
521 QDialog::keyReleaseEvent( event );
525 const int key =
event->key();
530 mModifiers &= ~Qt::META;
531 updateShortcutText();
534 mModifiers &= ~Qt::ALT;
535 updateShortcutText();
537 case Qt::Key_Control:
538 mModifiers &= ~Qt::CTRL;
539 updateShortcutText();
542 mModifiers &= ~Qt::SHIFT;
543 updateShortcutText();
552 setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
553 setGettingShortcut(
false );
558QObject *QgsConfigureShortcutsDialog::currentObject()
560 if ( !treeActions->currentItem() )
563 QObject *
object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
567void QgsConfigureShortcutsDialog::updateShortcutText()
570 const QKeySequence s( mModifiers + mKey );
571 btnChangeShortcut->setText( tr(
"Input: " ) + s.toString( QKeySequence::NativeText ) );
574void QgsConfigureShortcutsDialog::setGettingShortcut(
bool getting )
578 mGettingShortcut = getting;
581 btnChangeShortcut->setChecked(
false );
582 btnChangeShortcut->setText( tr(
"Change" ) );
586 updateShortcutText();
590void QgsConfigureShortcutsDialog::setCurrentActionShortcut(
const QKeySequence &s )
592 QObject *
object = currentObject();
597 QObject *otherObject = mManager->objectForSequence( s );
598 if ( otherObject ==
object )
604 if ( QAction *otherAction = qobject_cast<QAction *>( otherObject ) )
606 otherText = otherAction->text();
607 otherText.remove(
'&' );
609 else if ( QShortcut *otherShortcut = qobject_cast<QShortcut *>( otherObject ) )
611 otherText = otherShortcut->whatsThis();
614 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 );
616 if ( res != QMessageBox::Yes )
620 mManager->setObjectKeySequence( otherObject, QString() );
621 QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
622 if ( !items.isEmpty() )
623 items[0]->setText( 1, QString() );
627 mManager->setObjectKeySequence(
object, s.toString( QKeySequence::NativeText ) );
630 treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
632 actionChanged( treeActions->currentItem(),
nullptr );
635void QgsConfigureShortcutsDialog::mLeFilter_textChanged(
const QString &text )
637 for (
int i = 0; i < treeActions->topLevelItemCount(); i++ )
639 QTreeWidgetItem *item = treeActions->topLevelItem( i );
640 if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
642 item->setHidden(
true );
646 item->setHidden(
false );
651void QgsConfigureShortcutsDialog::showHelp()
656void QgsConfigureShortcutsDialog::saveShortcutsPdf()
658 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"PDF file" ) +
" (*.pdf);;" + tr(
"All files" ) +
" (*)" );
663 if ( fileName.isEmpty() )
666 if ( !fileName.endsWith(
".pdf"_L1, Qt::CaseInsensitive ) )
668 fileName +=
".pdf"_L1;
671 QTextDocument *document =
new QTextDocument;
672 QTextCursor cursor( document );
674 QTextTableFormat tableFormat;
675 tableFormat.setBorder( 0 );
676 tableFormat.setCellSpacing( 0 );
677 tableFormat.setCellPadding( 4 );
678 tableFormat.setHeaderRowCount( 1 );
680 QVector<QTextLength> constraints;
681 constraints << QTextLength( QTextLength::PercentageLength, 5 );
682 constraints << QTextLength( QTextLength::PercentageLength, 80 );
683 constraints << QTextLength( QTextLength::PercentageLength, 15 );
684 tableFormat.setColumnWidthConstraints( constraints );
686 QTextTableCellFormat headerFormat;
687 headerFormat.setFontWeight( QFont::Bold );
688 headerFormat.setBottomPadding( 4 );
690 QTextCharFormat rowFormat;
691 rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
693 QTextCharFormat altRowFormat;
694 altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
695 altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
698 QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
699 table->mergeCells( 0, 0, 1, 2 );
700 QTextCursor
c = table->cellAt( row, 0 ).firstCursorPosition();
701 c.setCharFormat( headerFormat );
702 c.insertText( tr(
"Action" ) );
703 c = table->cellAt( row, 2 ).firstCursorPosition();
704 c.setCharFormat( headerFormat );
705 c.insertText( tr(
"Shortcut" ) );
707 const QList<QObject *> objects = mManager->listAll();
708 for ( QObject *obj : objects )
714 if ( QAction *action = qobject_cast<QAction *>( obj ) )
716 actionText = action->text().remove(
'&' );
717 sequence = action->shortcut().toString( QKeySequence::NativeText );
718 icon = action->icon();
720 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
722 actionText = shortcut->whatsThis();
723 sequence = shortcut->key().toString( QKeySequence::NativeText );
724 icon = shortcut->property(
"Icon" ).value<QIcon>();
732 if ( actionText.isEmpty() || sequence.isEmpty() )
738 table->appendRows( 1 );
742 table->cellAt( row, 0 ).setFormat( altRowFormat );
743 table->cellAt( row, 1 ).setFormat( altRowFormat );
744 table->cellAt( row, 2 ).setFormat( altRowFormat );
748 table->cellAt( row, 0 ).setFormat( rowFormat );
749 table->cellAt( row, 1 ).setFormat( rowFormat );
750 table->cellAt( row, 2 ).setFormat( rowFormat );
753 if ( !icon.isNull() )
755 c = table->cellAt( row, 0 ).firstCursorPosition();
756 c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
758 table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
759 table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
762 QPdfWriter pdfWriter( fileName );
763 pdfWriter.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
764 document->setPageSize( QSizeF( pdfWriter.pageLayout().fullRect( QPageLayout::Point ).size() ) );
765 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