Composite setting key for QAction & QShortcuts keyboad shortcuts

This commit is contained in:
Yoann Quenach de Quivillic 2023-03-23 22:54:24 +01:00 committed by Nyall Dawson
parent b148ff1051
commit b35d5fd15d
4 changed files with 188 additions and 85 deletions

View File

@ -36,25 +36,27 @@ Constructor for QgsShortcutsManager.
taken to not register actions which conflict with the built in QGIS actions.
%End
void registerAllChildren( QObject *object, bool recursive = false );
void registerAllChildren( QObject *object, bool recursive = false, const QString &section = QString() );
%Docstring
Automatically registers all QActions and QShortcuts which are children of the
passed object.
:param object: parent object containing actions and shortcuts to register
:param recursive: set to ``True`` to recursively add child actions and shortcuts
:param section: Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
.. seealso:: :py:func:`registerAllChildActions`
.. seealso:: :py:func:`registerAllChildShortcuts`
%End
void registerAllChildActions( QObject *object, bool recursive = false );
void registerAllChildActions( QObject *object, bool recursive = false, const QString &section = QString() );
%Docstring
Automatically registers all QActions which are children of the passed object.
:param object: parent object containing actions to register
:param recursive: set to ``True`` to recursively add child actions
:param section: Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
.. seealso:: :py:func:`registerAction`
@ -63,12 +65,13 @@ Automatically registers all QActions which are children of the passed object.
.. seealso:: :py:func:`registerAllChildShortcuts`
%End
void registerAllChildShortcuts( QObject *object, bool recursive = false );
void registerAllChildShortcuts( QObject *object, bool recursive = false, const QString &section = QString() );
%Docstring
Automatically registers all QShortcuts which are children of the passed object.
:param object: parent object containing shortcuts to register
:param recursive: set to ``True`` to recursively add child shortcuts
:param section: Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
.. seealso:: :py:func:`registerShortcut`
@ -77,13 +80,14 @@ Automatically registers all QShortcuts which are children of the passed object.
.. seealso:: :py:func:`registerAllChildActions`
%End
bool registerAction( QAction *action, const QString &defaultShortcut = QString() );
bool registerAction( QAction *action, const QString &defaultShortcut = QString(), const QString &section = QString() );
%Docstring
Registers an action with the manager so the shortcut can be configured in GUI.
:param action: action to register. The action must have a unique text string for
identification.
:param defaultShortcut: default key sequence for action
:param section: Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
:return: ``True`` if action was successfully registered
@ -94,13 +98,14 @@ Registers an action with the manager so the shortcut can be configured in GUI.
.. seealso:: :py:func:`registerAllChildActions`
%End
bool registerShortcut( QShortcut *shortcut, const QString &defaultSequence = QString() );
bool registerShortcut( QShortcut *shortcut, const QString &defaultSequence = QString(), const QString &section = QString() );
%Docstring
Registers a QShortcut with the manager so the shortcut can be configured in GUI.
:param shortcut: QShortcut to register. The shortcut must have a unique QObject.objectName() for
identification.
:param defaultSequence: default key sequence for shortcut
:param section: Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
:return: ``True`` if shortcut was successfully registered
@ -291,6 +296,22 @@ Returns a shortcut by its name, or ``None`` if nothing found
QString settingsPath() const;
%Docstring
Returns the root settings path used to store shortcut customization.
%End
QString objectSettingKey( QObject *object ) const;
%Docstring
Returns the full settings key matching the QShortcut or QAction
Return an empty QString if the QObject is not registered
.. versionadded:: 3.30
%End
QObject *objectForSettingKey( const QString &name ) const;
%Docstring
Returns the QShortcut or QAction matching the the full setting key
Return None if the key was not found
.. versionadded:: 3.30
%End
};

View File

@ -89,6 +89,7 @@ void QgsConfigureShortcutsDialog::populateActions()
QString actionText;
QString sequence;
QIcon icon;
const QString settingKey = mManager->objectSettingKey( obj );
if ( QAction *action = qobject_cast< QAction * >( obj ) )
{
@ -118,6 +119,7 @@ void QgsConfigureShortcutsDialog::populateActions()
QTreeWidgetItem *item = new QTreeWidgetItem( lst );
item->setIcon( 0, icon );
item->setData( 0, Qt::UserRole, QVariant::fromValue( obj ) );
item->setToolTip( 0, settingKey );
items.append( item );
}
@ -158,7 +160,7 @@ void QgsConfigureShortcutsDialog::saveShortcuts( bool saveAll )
QDomDocument doc( QStringLiteral( "shortcuts" ) );
QDomElement root = doc.createElement( QStringLiteral( "qgsshortcuts" ) );
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0" ) );
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1" ) );
root.setAttribute( QStringLiteral( "locale" ), settings.value( QgsApplication::settingsLocaleUserLocale->key(), "en_US" ).toString() );
doc.appendChild( root );
@ -167,6 +169,7 @@ void QgsConfigureShortcutsDialog::saveShortcuts( bool saveAll )
{
QString actionText;
QString actionShortcut;
QString actionSettingKey;
QKeySequence sequence;
if ( QAction *action = qobject_cast< QAction * >( obj ) )
@ -186,7 +189,9 @@ void QgsConfigureShortcutsDialog::saveShortcuts( bool saveAll )
continue;
}
if ( actionText.isEmpty() || actionShortcut.isEmpty() )
actionSettingKey = mManager->objectSettingKey( obj );
if ( actionSettingKey.isEmpty() )
{
continue;
}
@ -200,6 +205,7 @@ void QgsConfigureShortcutsDialog::saveShortcuts( bool saveAll )
QDomElement el = doc.createElement( QStringLiteral( "action" ) );
el.setAttribute( QStringLiteral( "name" ), actionText );
el.setAttribute( QStringLiteral( "shortcut" ), actionShortcut );
el.setAttribute( QStringLiteral( "setting" ), actionSettingKey );
root.appendChild( el );
}
@ -227,7 +233,7 @@ void QgsConfigureShortcutsDialog::loadShortcuts()
return;
}
QDomDocument doc;
QDomDocument doc;
QString errorStr;
int errorLine;
int errorColumn;
@ -262,22 +268,44 @@ void QgsConfigureShortcutsDialog::loadShortcuts()
currentLocale = QLocale().name();
}
const QString version = root.attribute( QStringLiteral( "version" ) );
if ( root.attribute( QStringLiteral( "locale" ) ) != currentLocale )
{
QMessageBox::information( this, tr( "Loading Shortcuts" ),
tr( "The file contains shortcuts created with different locale, so you can't use it." ) );
return;
if ( version < "1.1" )
{
QMessageBox::information( this, tr( "Loading Shortcuts" ),
tr( "The file contains shortcuts created with different locale, so you can't use it." ) );
return;
}
else // From version 1.1, if objectName is not empty, it is used as key.
{
QMessageBox::information( this, tr( "Loading Shortcuts" ),
tr( "The file contains shortcuts created with different locale, so some shortcuts may not work." ) );
}
}
QString actionName;
QString actionShortcut;
QString actionSettingKey;
QDomElement child = root.firstChildElement();
while ( !child.isNull() )
{
actionName = child.attribute( QStringLiteral( "name" ) );
actionShortcut = child.attribute( QStringLiteral( "shortcut" ) );
mManager->setKeySequence( actionName, actionShortcut );
if ( version < "1.1" )
{
actionName = child.attribute( QStringLiteral( "name" ) );
mManager->setKeySequence( actionName, actionShortcut );
}
else
{
actionSettingKey = child.attribute( QStringLiteral( "setting" ) );
QObject *obj = mManager->objectForSettingKey( actionSettingKey );
if ( obj )
mManager->setObjectKeySequence( obj, actionShortcut );
}
child = child.nextSiblingElement();
}

View File

@ -27,87 +27,79 @@ QgsShortcutsManager::QgsShortcutsManager( QObject *parent, const QString &settin
{
}
void QgsShortcutsManager::registerAllChildren( QObject *object, bool recursive )
void QgsShortcutsManager::registerAllChildren( QObject *object, bool recursive, const QString &section )
{
registerAllChildActions( object, recursive );
registerAllChildShortcuts( object, recursive );
registerAllChildActions( object, recursive, section );
registerAllChildShortcuts( object, recursive, section );
}
void QgsShortcutsManager::registerAllChildActions( QObject *object, bool recursive )
void QgsShortcutsManager::registerAllChildActions( QObject *object, bool recursive, const QString &section )
{
if ( recursive )
const QList<QObject *> children = object->children();
for ( QObject *child : children )
{
const QList< QAction * > actions = object->findChildren< QAction * >();
for ( QAction *a : actions )
if ( QAction *a = qobject_cast< QAction * >( child ) )
{
registerAction( a, a->shortcut().toString( QKeySequence::NativeText ) );
registerAction( a, a->shortcut().toString( QKeySequence::NativeText ), section );
}
}
else
{
const QList< QObject *> children = object->children();
for ( QObject *child : children )
else if ( recursive )
{
if ( QAction *a = qobject_cast<QAction *>( child ) )
{
registerAction( a, a->shortcut().toString( QKeySequence::NativeText ) );
}
const QString newSection = child->objectName().isEmpty() ? section : section + child->objectName() + "/";
registerAllChildActions( child, recursive, newSection );
}
}
}
void QgsShortcutsManager::registerAllChildShortcuts( QObject *object, bool recursive )
void QgsShortcutsManager::registerAllChildShortcuts( QObject *object, bool recursive, const QString &section )
{
if ( recursive )
const QList<QObject *> children = object->children();
for ( QObject *child : children )
{
const QList< QShortcut * > shortcuts = object->findChildren< QShortcut * >();
const auto constShortcuts = shortcuts;
for ( QShortcut *s : constShortcuts )
if ( QShortcut *s = qobject_cast< QShortcut * >( child ) )
{
registerShortcut( s, s->key().toString( QKeySequence::NativeText ) );
registerShortcut( s, s->key().toString( QKeySequence::NativeText ), section );
}
}
else
{
const auto constChildren = object->children();
for ( QObject *child : constChildren )
else if ( recursive )
{
if ( QShortcut *s = qobject_cast<QShortcut *>( child ) )
{
registerShortcut( s, s->key().toString( QKeySequence::NativeText ) );
}
const QString newSection = child->objectName().isEmpty() ? section : section + child->objectName() + "/";
registerAllChildShortcuts( child, recursive, newSection );
}
}
}
bool QgsShortcutsManager::registerAction( QAction *action, const QString &defaultSequence )
bool QgsShortcutsManager::registerAction( QAction *action, const QString &defaultSequence, const QString &section )
{
if ( qobject_cast< QWidgetAction * >( action ) )
if ( qobject_cast<QWidgetAction *>( action ) )
return false;
if ( mActions.contains( action ) )
return false; // already registered
#ifdef QGISDEBUG
// if using a debug build, warn on duplicate or non-compliant actions
if ( action->text().isEmpty() )
if ( action->text().isEmpty() && action->objectName().isEmpty() )
{
#ifdef QGISDEBUG
QgsLogger::warning( QStringLiteral( "Action has no text set: %1" ).arg( action->objectName() ) );
#endif
return false;
}
else if ( actionByName( action->text() ) || shortcutByName( action->text() ) )
QgsLogger::warning( QStringLiteral( "Duplicate shortcut registered: %1" ).arg( action->text() ) );
QString key = action->objectName().isEmpty() ? action->text() : action->objectName();
key.remove( '&' ); // remove the accelerator
#ifdef QGISDEBUG
if ( actionByName( key ) || shortcutByName( key ) )
QgsLogger::warning( QStringLiteral( "Duplicate shortcut registered: %1" ).arg( key ) );
#endif
mActions.insert( action, defaultSequence );
connect( action, &QObject::destroyed, this, [action, this]() { actionDestroyed( action ); } );
const QString settingKey = mSettingsPath + section + key;
QString actionText = action->text();
actionText.remove( '&' ); // remove the accelerator
mActions.insert( action, {defaultSequence, settingKey} );
connect( action, &QObject::destroyed, this, [action, this]() { actionDestroyed( action ); } );
// load overridden value from settings
const QgsSettings settings;
const QString sequence = settings.value( mSettingsPath + actionText, defaultSequence ).toString();
const QString sequence = settings.value( settingKey, defaultSequence ).toString();
action->setShortcut( sequence );
if ( !action->toolTip().isEmpty() )
@ -127,7 +119,7 @@ bool QgsShortcutsManager::registerAction( QAction *action, const QString &defaul
return true;
}
bool QgsShortcutsManager::registerShortcut( QShortcut *shortcut, const QString &defaultSequence )
bool QgsShortcutsManager::registerShortcut( QShortcut *shortcut, const QString &defaultSequence, const QString &section )
{
#ifdef QGISDEBUG
// if using a debug build, warn on duplicate or non-compliant actions
@ -137,14 +129,14 @@ bool QgsShortcutsManager::registerShortcut( QShortcut *shortcut, const QString &
QgsLogger::warning( QStringLiteral( "Duplicate shortcut registered: %1" ).arg( shortcut->objectName() ) );
#endif
mShortcuts.insert( shortcut, defaultSequence );
connect( shortcut, &QObject::destroyed, this, [shortcut, this]() { shortcutDestroyed( shortcut ); } );
const QString settingKey = mSettingsPath + section + shortcut->objectName();
const QString shortcutName = shortcut->objectName();
mShortcuts.insert( shortcut, {defaultSequence, settingKey} );
connect( shortcut, &QObject::destroyed, this, [shortcut, this]() { shortcutDestroyed( shortcut ); } );
// load overridden value from settings
const QgsSettings settings;
const QString keySequence = settings.value( mSettingsPath + shortcutName, defaultSequence ).toString();
const QString keySequence = settings.value( settingKey, defaultSequence ).toString();
shortcut->setKey( keySequence );
@ -181,7 +173,7 @@ QList<QShortcut *> QgsShortcutsManager::listShortcuts() const
QList<QObject *> QgsShortcutsManager::listAll() const
{
QList< QObject * > list;
QList<QObject *> list;
ActionsHash::const_iterator actionIt = mActions.constBegin();
for ( ; actionIt != mActions.constEnd(); ++actionIt )
{
@ -207,12 +199,12 @@ QString QgsShortcutsManager::objectDefaultKeySequence( QObject *object ) const
QString QgsShortcutsManager::defaultKeySequence( QAction *action ) const
{
return mActions.value( action, QString() );
return mActions.value( action ).first;
}
QString QgsShortcutsManager::defaultKeySequence( QShortcut *shortcut ) const
{
return mShortcuts.value( shortcut, QString() );
return mShortcuts.value( shortcut ).first;
}
bool QgsShortcutsManager::setKeySequence( const QString &name, const QString &sequence )
@ -237,27 +229,34 @@ bool QgsShortcutsManager::setObjectKeySequence( QObject *object, const QString &
bool QgsShortcutsManager::setKeySequence( QAction *action, const QString &sequence )
{
if ( !mActions.contains( action ) )
{
return false;
}
action->setShortcut( sequence );
this->updateActionToolTip( action, sequence );
QString actionText = action->text();
actionText.remove( '&' ); // remove the accelerator
const QString settingKey = mActions[action].second;
// save to settings
QgsSettings settings;
settings.setValue( mSettingsPath + actionText, sequence );
settings.setValue( settingKey, sequence );
return true;
}
bool QgsShortcutsManager::setKeySequence( QShortcut *shortcut, const QString &sequence )
{
if ( !mShortcuts.contains( shortcut ) )
{
return false;
}
shortcut->setKey( sequence );
const QString shortcutText = shortcut->objectName();
const QString settingKey = mShortcuts[shortcut].second;
// save to settings
QgsSettings settings;
settings.setValue( mSettingsPath + shortcutText, sequence );
settings.setValue( settingKey, sequence );
return true;
}
@ -303,7 +302,14 @@ QAction *QgsShortcutsManager::actionByName( const QString &name ) const
{
for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
{
if ( it.key()->text() == name )
if ( it.key()->objectName() == name )
return it.key();
}
for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
{
QString key = it.key()->text();
key.remove( '&' ); // remove the accelerator
if ( key == name )
return it.key();
}
@ -326,6 +332,34 @@ void QgsShortcutsManager::actionDestroyed( QAction *action )
mActions.remove( action );
}
QString QgsShortcutsManager::objectSettingKey( QObject *object ) const
{
if ( auto action = qobject_cast< QAction * >( object ) )
{
return mActions.value( action ).second;
}
else if ( auto shortcut = qobject_cast< QShortcut * >( object ) )
{
return mShortcuts.value( shortcut ).second;
}
return QString();
}
QObject *QgsShortcutsManager::objectForSettingKey( const QString &settingKey ) const
{
for ( ActionsHash::const_iterator it = mActions.constBegin(); it != mActions.constEnd(); ++it )
{
if ( it.value().second == settingKey )
return it.key();
}
for ( ShortcutsHash::const_iterator it = mShortcuts.constBegin(); it != mShortcuts.constEnd(); ++it )
{
if ( it.value().second == settingKey )
return it.key();
}
return nullptr;
}
void QgsShortcutsManager::shortcutDestroyed( QShortcut *shortcut )
{
mShortcuts.remove( shortcut );

View File

@ -54,53 +54,58 @@ class GUI_EXPORT QgsShortcutsManager : public QObject
* passed object.
* \param object parent object containing actions and shortcuts to register
* \param recursive set to TRUE to recursively add child actions and shortcuts
* \param section Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
* \see registerAllChildActions()
* \see registerAllChildShortcuts()
*/
void registerAllChildren( QObject *object, bool recursive = false );
void registerAllChildren( QObject *object, bool recursive = false, const QString &section = QString() );
/**
* Automatically registers all QActions which are children of the passed object.
* \param object parent object containing actions to register
* \param recursive set to TRUE to recursively add child actions
* \param section Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
* \see registerAction()
* \see registerAllChildren()
* \see registerAllChildShortcuts()
*/
void registerAllChildActions( QObject *object, bool recursive = false );
void registerAllChildActions( QObject *object, bool recursive = false, const QString &section = QString() );
/**
* Automatically registers all QShortcuts which are children of the passed object.
* \param object parent object containing shortcuts to register
* \param recursive set to TRUE to recursively add child shortcuts
* \param section Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
* \see registerShortcut()
* \see registerAllChildren()
* \see registerAllChildActions()
*/
void registerAllChildShortcuts( QObject *object, bool recursive = false );
void registerAllChildShortcuts( QObject *object, bool recursive = false, const QString &section = QString() );
/**
* Registers an action with the manager so the shortcut can be configured in GUI.
* \param action action to register. The action must have a unique text string for
* identification.
* \param defaultShortcut default key sequence for action
* \param section Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
* \returns TRUE if action was successfully registered
* \see registerShortcut()
* \see unregisterAction()
* \see registerAllChildActions()
*/
bool registerAction( QAction *action, const QString &defaultShortcut = QString() );
bool registerAction( QAction *action, const QString &defaultShortcut = QString(), const QString &section = QString() );
/**
* Registers a QShortcut with the manager so the shortcut can be configured in GUI.
* \param shortcut QShortcut to register. The shortcut must have a unique QObject::objectName() for
* identification.
* \param defaultSequence default key sequence for shortcut
* \returns TRUE if shortcut was successfully registered
* \see registerAction()
* \see registerAllChildShortcuts()
*/
bool registerShortcut( QShortcut *shortcut, const QString &defaultSequence = QString() );
* \param shortcut QShortcut to register. The shortcut must have a unique QObject::objectName() for
* identification.
* \param defaultSequence default key sequence for shortcut
* \param section Allows disambiguating shortcuts with the same objectName (since QGIS 3.32)
* \returns TRUE if shortcut was successfully registered
* \see registerAction()
* \see registerAllChildShortcuts()
*/
bool registerShortcut( QShortcut *shortcut, const QString &defaultSequence = QString(), const QString &section = QString() );
/**
* Removes an action from the manager.
@ -242,15 +247,30 @@ class GUI_EXPORT QgsShortcutsManager : public QObject
//! Returns the root settings path used to store shortcut customization.
QString settingsPath() const { return mSettingsPath; }
/**
* Returns the full settings key matching the QShortcut or QAction
* Return an empty QString if the QObject is not registered
*
* \since QGIS 3.30
*/
QString objectSettingKey( QObject *object ) const;
/**
* Returns the QShortcut or QAction matching the the full setting key
* Return nullptr if the key was not found
*
* \since QGIS 3.30
*/
QObject *objectForSettingKey( const QString &name ) const;
private slots:
void actionDestroyed( QAction *action );
void shortcutDestroyed( QShortcut *shortcut );
private:
typedef QHash< QAction *, QString > ActionsHash;
typedef QHash< QShortcut *, QString > ShortcutsHash;
typedef QHash<QAction *, QPair<QString, QString>> ActionsHash;
typedef QHash<QShortcut *, QPair<QString, QString>> ShortcutsHash;
ActionsHash mActions;
ShortcutsHash mShortcuts;