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::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 );
322 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
324 if ( res == QMessageBox::NoToAll )
326 actionOnExisting = ActionOnExisting::SkipAll;
328 child = child.nextSiblingElement();
331 if ( res == QMessageBox::YesToAll )
333 actionOnExisting = ActionOnExisting::ReassignAll;
336 else if ( actionOnExisting == ActionOnExisting::SkipAll )
338 child = child.nextSiblingElement();
341 mManager->setObjectKeySequence( previousAction ? qobject_cast<QObject *>( previousAction ) : qobject_cast<QObject *>( previousShortcut ), QString() );
343 mManager->setKeySequence( actionName, actionShortcut );
347 actionSettingKey = child.attribute( u
"setting"_s );
348 QObject *obj = mManager->objectForSettingKey( actionSettingKey );
351 QObject *previousObj = mManager->objectForSequence( actionShortcutSequence );
352 if ( previousObj && previousObj != obj )
354 if ( QAction *previousAction = qobject_cast<QAction *>( previousObj ) )
356 previousText = previousAction->text().remove(
'&' );
358 else if ( QShortcut *previousShortcut = qobject_cast<QShortcut *>( previousObj ) )
360 previousText = previousShortcut->whatsThis();
364 if ( !previousText.isEmpty() )
367 if ( QAction *action = qobject_cast<QAction *>( obj ) )
369 text = action->text().remove(
'&' );
371 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
373 text = shortcut->whatsThis();
376 if ( actionOnExisting == ActionOnExisting::Ask )
378 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 );
379 if ( res == QMessageBox::No || res == QMessageBox::NoToAll )
381 if ( res == QMessageBox::NoToAll )
383 actionOnExisting = ActionOnExisting::SkipAll;
385 child = child.nextSiblingElement();
388 if ( res == QMessageBox::YesToAll )
390 actionOnExisting = ActionOnExisting::ReassignAll;
393 else if ( actionOnExisting == ActionOnExisting::SkipAll )
395 child = child.nextSiblingElement();
398 mManager->setObjectKeySequence( previousObj, QString() );
400 mManager->setObjectKeySequence( obj, actionShortcut );
404 child = child.nextSiblingElement();
407 treeActions->clear();
411void QgsConfigureShortcutsDialog::changeShortcut()
414 setGettingShortcut(
true );
417void QgsConfigureShortcutsDialog::resetShortcut()
419 QObject *
object = currentObject();
420 const QString sequence = mManager->objectDefaultKeySequence(
object );
421 setCurrentActionShortcut( sequence );
424void QgsConfigureShortcutsDialog::setNoShortcut()
426 setCurrentActionShortcut( QKeySequence() );
429QAction *QgsConfigureShortcutsDialog::currentAction()
431 return qobject_cast<QAction *>( currentObject() );
434QShortcut *QgsConfigureShortcutsDialog::currentShortcut()
436 return qobject_cast<QShortcut *>( currentObject() );
439void QgsConfigureShortcutsDialog::actionChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
444 setGettingShortcut(
false );
447 QKeySequence sequence;
448 if ( QAction *action = currentAction() )
451 shortcut = mManager->defaultKeySequence( action );
452 sequence = action->shortcut();
454 else if ( QShortcut *
object = currentShortcut() )
457 shortcut = mManager->defaultKeySequence(
object );
458 sequence =
object->key();
465 if ( shortcut.isEmpty() )
466 shortcut = tr(
"None" );
467 btnResetShortcut->setText( tr(
"Set default (%1)" ).arg( shortcut ) );
470 btnSetNoShortcut->setEnabled( !sequence.isEmpty() );
472 btnResetShortcut->setEnabled( sequence != QKeySequence( shortcut ) );
477 if ( !mGettingShortcut )
479 QDialog::keyPressEvent( event );
483 const int key =
event->key();
488 mModifiers |= Qt::META;
489 updateShortcutText();
492 mModifiers |= Qt::ALT;
493 updateShortcutText();
495 case Qt::Key_Control:
496 mModifiers |= Qt::CTRL;
497 updateShortcutText();
500 mModifiers |= Qt::SHIFT;
501 updateShortcutText();
506 setGettingShortcut(
false );
511 updateShortcutText();
517 if ( !mGettingShortcut )
519 QDialog::keyReleaseEvent( event );
523 const int key =
event->key();
528 mModifiers &= ~Qt::META;
529 updateShortcutText();
532 mModifiers &= ~Qt::ALT;
533 updateShortcutText();
535 case Qt::Key_Control:
536 mModifiers &= ~Qt::CTRL;
537 updateShortcutText();
540 mModifiers &= ~Qt::SHIFT;
541 updateShortcutText();
550 setCurrentActionShortcut( QKeySequence( mModifiers + mKey ) );
551 setGettingShortcut(
false );
556QObject *QgsConfigureShortcutsDialog::currentObject()
558 if ( !treeActions->currentItem() )
561 QObject *
object = treeActions->currentItem()->data( 0, Qt::UserRole ).value<QObject *>();
565void QgsConfigureShortcutsDialog::updateShortcutText()
568 const QKeySequence s( mModifiers + mKey );
569 btnChangeShortcut->setText( tr(
"Input: " ) + s.toString( QKeySequence::NativeText ) );
572void QgsConfigureShortcutsDialog::setGettingShortcut(
bool getting )
576 mGettingShortcut = getting;
579 btnChangeShortcut->setChecked(
false );
580 btnChangeShortcut->setText( tr(
"Change" ) );
584 updateShortcutText();
588void QgsConfigureShortcutsDialog::setCurrentActionShortcut(
const QKeySequence &s )
590 QObject *
object = currentObject();
595 QObject *otherObject = mManager->objectForSequence( s );
596 if ( otherObject ==
object )
602 if ( QAction *otherAction = qobject_cast<QAction *>( otherObject ) )
604 otherText = otherAction->text();
605 otherText.remove(
'&' );
607 else if ( QShortcut *otherShortcut = qobject_cast<QShortcut *>( otherObject ) )
609 otherText = otherShortcut->whatsThis();
612 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 );
614 if ( res != QMessageBox::Yes )
618 mManager->setObjectKeySequence( otherObject, QString() );
619 QList<QTreeWidgetItem *> items = treeActions->findItems( otherText, Qt::MatchExactly );
620 if ( !items.isEmpty() )
621 items[0]->setText( 1, QString() );
625 mManager->setObjectKeySequence(
object, s.toString( QKeySequence::NativeText ) );
628 treeActions->currentItem()->setText( 1, s.toString( QKeySequence::NativeText ) );
630 actionChanged( treeActions->currentItem(),
nullptr );
633void QgsConfigureShortcutsDialog::mLeFilter_textChanged(
const QString &text )
635 for (
int i = 0; i < treeActions->topLevelItemCount(); i++ )
637 QTreeWidgetItem *item = treeActions->topLevelItem( i );
638 if ( !item->text( 0 ).contains( text, Qt::CaseInsensitive ) && !item->text( 1 ).contains( text, Qt::CaseInsensitive ) )
640 item->setHidden(
true );
644 item->setHidden(
false );
649void QgsConfigureShortcutsDialog::showHelp()
654void QgsConfigureShortcutsDialog::saveShortcutsPdf()
656 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Shortcuts" ), QDir::homePath(), tr(
"PDF file" ) +
" (*.pdf);;" + tr(
"All files" ) +
" (*)" );
661 if ( fileName.isEmpty() )
664 if ( !fileName.endsWith(
".pdf"_L1, Qt::CaseInsensitive ) )
666 fileName +=
".pdf"_L1;
669 QTextDocument *document =
new QTextDocument;
670 QTextCursor cursor( document );
672 QTextTableFormat tableFormat;
673 tableFormat.setBorder( 0 );
674 tableFormat.setCellSpacing( 0 );
675 tableFormat.setCellPadding( 4 );
676 tableFormat.setHeaderRowCount( 1 );
678 QVector<QTextLength> constraints;
679 constraints << QTextLength( QTextLength::PercentageLength, 5 );
680 constraints << QTextLength( QTextLength::PercentageLength, 80 );
681 constraints << QTextLength( QTextLength::PercentageLength, 15 );
682 tableFormat.setColumnWidthConstraints( constraints );
684 QTextTableCellFormat headerFormat;
685 headerFormat.setFontWeight( QFont::Bold );
686 headerFormat.setBottomPadding( 4 );
688 QTextCharFormat rowFormat;
689 rowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
691 QTextCharFormat altRowFormat;
692 altRowFormat.setBackground( QBrush( QColor( 238, 238, 236 ) ) );
693 altRowFormat.setVerticalAlignment( QTextCharFormat::AlignMiddle );
696 QTextTable *table = cursor.insertTable( 1, 3, tableFormat );
697 table->mergeCells( 0, 0, 1, 2 );
698 QTextCursor
c = table->cellAt( row, 0 ).firstCursorPosition();
699 c.setCharFormat( headerFormat );
700 c.insertText( tr(
"Action" ) );
701 c = table->cellAt( row, 2 ).firstCursorPosition();
702 c.setCharFormat( headerFormat );
703 c.insertText( tr(
"Shortcut" ) );
705 const QList<QObject *> objects = mManager->listAll();
706 for ( QObject *obj : objects )
712 if ( QAction *action = qobject_cast<QAction *>( obj ) )
714 actionText = action->text().remove(
'&' );
715 sequence = action->shortcut().toString( QKeySequence::NativeText );
716 icon = action->icon();
718 else if ( QShortcut *shortcut = qobject_cast<QShortcut *>( obj ) )
720 actionText = shortcut->whatsThis();
721 sequence = shortcut->key().toString( QKeySequence::NativeText );
722 icon = shortcut->property(
"Icon" ).value<QIcon>();
730 if ( actionText.isEmpty() || sequence.isEmpty() )
736 table->appendRows( 1 );
740 table->cellAt( row, 0 ).setFormat( altRowFormat );
741 table->cellAt( row, 1 ).setFormat( altRowFormat );
742 table->cellAt( row, 2 ).setFormat( altRowFormat );
746 table->cellAt( row, 0 ).setFormat( rowFormat );
747 table->cellAt( row, 1 ).setFormat( rowFormat );
748 table->cellAt( row, 2 ).setFormat( rowFormat );
751 if ( !icon.isNull() )
753 c = table->cellAt( row, 0 ).firstCursorPosition();
754 c.insertImage( icon.pixmap( QSize( 24, 24 ) ).toImage() );
756 table->cellAt( row, 1 ).firstCursorPosition().insertText( actionText );
757 table->cellAt( row, 2 ).firstCursorPosition().insertText( sequence );
760 QPdfWriter pdfWriter( fileName );
761 pdfWriter.setPageLayout( QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, QMarginsF( 20, 10, 10, 10 ) ) );
762 document->setPageSize( QSizeF( pdfWriter.pageLayout().fullRect( QPageLayout::Point ).size() ) );
763 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