#include "SUIT.h"
#include <QObject>
-#include <QMultiMap>
+#include <QString>
+#include <map>
+#include <set>
+#include <utility>
+class QAction;
class QtxAction;
-
class QKeySequence;
#if defined WIN32
#pragma warning( disable: 4251 )
#endif
+// Define SHORTCUT_MGR_DBG to enable SUIT_ShortcutMgr debug logging.
+// #define SHORTCUT_MGR_DBG
+/*! \returns true, if SUIT_ShortcutMgr debug logging is enabled. */
+SUIT_EXPORT extern inline bool ShCutDbg() {
+#ifdef SHORTCUT_MGR_DBG
+ return true;
+#else
+ return false;
+#endif
+}
+/*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */
+SUIT_EXPORT extern bool ShCutDbg(const QString& theString);
+/*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */
+SUIT_EXPORT extern bool ShCutDbg(const char* theString);
+
+
+/*!
+ \class SUIT_ShortcutContainer
+ \brief Provides means to keep and edit shortcuts in compliance with the application logics.
+ \ref See SUIT_ShortcutMgr for details.
+*/
+class SUIT_EXPORT SUIT_ShortcutContainer
+{
+public:
+ SUIT_ShortcutContainer();
+
+ /*! \returns IDs of modules, which interfere with the module:
+ if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
+ std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
+
+ std::set<QString> getIDsOfAllModules() const;
+
+ /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts.
+ Redefining a key sequence for the action, if theKeySequence does not conflict with other shortcuts, is not considered as a conflict.
+ \param theModuleID The method has no effect if theModuleID is invalid. \ref See SUIT_ShortcutMgr::isModuleIDValid(const QString&) for details.
+ \param theInModuleActionID The method has no effect if theInModuleActionID is invalid. \ref See SUIT_ShortcutMgr::isInModuleActionIDValid(const QString&).
+ If theInModuleActionID is meta-action ID, the shortcut is set to root module, and theModuleID is ignored.
+ \param theKeySequence Empty theKeySequence does not cause conflicts, in this case
+ a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence.
+ \param theOverride If true, conflicting shortcuts are disabled.
+ \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false,
+ otherwise set of actions (without incoming one), whose shortcuts have been disabled. */
+ std::set<std::pair<QString, QString>> setShortcut(
+ QString theModuleID,
+ const QString& theInModuleActionID,
+ const QKeySequence& theKeySequence,
+ bool theOverride
+ );
+
+ /*! \brief Checks for conflicts. Existence of a shortcut with another key sequence for the action,
+ if theKeySequence does not conflict with other shortcuts, is not considered as a conflict.
+ \param theInModuleActionID If theInModuleActionID is meta-action ID, the shortcut is looked for in root module, and theModuleID is ignored.
+ \param theKeySequence Empty theKeySequence does not have conflicts.
+ \returns {moduleID, inModuleActionID}[] - Set of conflicting actions. */
+ std::set<std::pair<QString, QString>> getConflicts(
+ QString theModuleID,
+ const QString& theInModuleActionID,
+ const QKeySequence& theKeySequence
+ ) const;
+
+ /*! \returns empty key sequence if shortcut for the action is not set.
+ \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/
+ const QKeySequence& getKeySequence(QString theModuleID, const QString& theInModuleActionID) const;
+
+ /*! \returns true, if shortcut for the action is set (even if the mapped key sequence is empty).
+ \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/
+ bool hasShortcut(QString theModuleID, const QString& theInModuleActionID) const;
+
+ /*! \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */
+ const std::map<QString, QKeySequence>& getModuleShortcutsInversed(const QString& theModuleID) const;
+
+ /*! \brief Seeks for shortcuts in the module with in-module action IDs, which start with theInModuleActionIDPrefix.
+ \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */
+ const std::map<QString, QKeySequence> getModuleShortcutsInversed(const QString& theModuleID, const QString& theInModuleActionIDPrefix) const;
+
+ /*! \brief Merges shortcuts of theOther into this.
+ \param theOverride if true, overrides conflicting shortcuts.
+ If false, and this has no shortcut for an incoming action, and the incoming shortcut conflicts
+ with an existing shortcut, disabled shortcut for the incoming action is set.
+ \param theTreatAbsentIncomingAsDisabled If theOverride == false, theTreatAbsentIncomingAsDisabled is ignored.
+ If theOverride and theTreatAbsentIncomingAsDisabled, and theOther has no shortcut for an action, which exists in this,
+ the existing shortcut in this is set disabled.
+ \returns { moduleID, { inModuleActionID, keySequence }[] }[] - Modiified shortcuts inversed. */
+ std::map<QString, std::map<QString, QKeySequence>> merge(
+ const SUIT_ShortcutContainer& theOther,
+ bool theOverride,
+ bool theTreatAbsentIncomingAsDisabled = false
+ );
+
+ /*! \brief Generates human-readable text representation of content. */
+ QString toString() const;
+
+private:
+ /** { moduleID, { keySequence, inModuleActionID }[] }[]. keySequence can not be empty.
+ * Can not contain entries like { <non-root module ID>, { keySequence, <meta-action ID> } }. */
+ std::map<QString, std::map<QKeySequence, QString>> myShortcuts;
+
+ /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty.
+ * Can not contain entries like { <non-root module ID>, { <meta-action ID>, keySequence } }. */
+ std::map<QString, std::map<QString, QKeySequence>> myShortcutsInversed;
+};
+
+
/*!
\class SUIT_ShortcutMgr
- \brief Class which manages shortcuts customization.
+ \brief Handles action shortcut customization.
+
+ Register actions under action IDs. Set shortcuts, which are [action ID]<->[key sequence] mappings.
+ Every time an action is registered or a shorcut is set, if there are an action and a shortcut,
+ which are mapped to the same action ID, the action is bound to the key sequence of the shortcut.
+ Action IDs are also used to (de)serialize shortcut settings.
+ Several QActions may be registered under the same ID.
+
+ Most of actions are intercepted on creation in SUIT_ShortcutMgr:eventFilter(QObject* theObject, QEvent* theEvent).
+ If an intercepted action is instance of QtxAction, it is registered automatically.
+ Since non-QtxActions have no member ID(), SUIT_ShortcutMgr is unable to register them automatically
+ in SUIT_ShortcutMgr::eventFilter(). Thus, every non-QtxAction should be
+ passed to SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction).
+
+ Action ID is application-unique must be composed as <moduleID>/<inModuleActionID>.
+ If an action belongs to a desktop or is available even if no module is active (e.g. 'Save As'),
+ use empty string as <moduleID>. Let's call such actions as root actions.
+
+ There is a need to keep multiple actions, which do the same from user' perspective,
+ bound to the same key sequence. E.g. 'Front view'. Let's call such set of actions as meta-action.
+ Actions of a meta-action may belong to different modules, and/or there may be several actions
+ of the same meta-action in the same module. <inModuleActionID> of all members of a meta-action
+ must be the same and start with "#".
+ Meta-action is root action when it comes to checking for conflicts.
+ Shortcuts of meta-actions are (de)serialized to the same section of preference files as root shortcuts.
+
+ <inModuleActionID> can contain several "/". Go to \ref isInModuleActionIDValid(const QString&) for details.
+ You can refer to multiple actions, whose <inModuleActionID> starts with the prefix.
+
+ Only one module can be active at instance. So a key sequence must be unique within a joined temporary table of
+ root and active module shortcuts. An action is allowed to be bound with only key sequence.
+
+ WARNING!
+ Avoid assigning shortcuts to instances of QAction and all its descendants directly.
+ (1) Key sequence being bound directly to any registered/intercepted action with valid ID,
+ if the key sequence does not conflict with shortcuts kept by SUIT_ShortcutMgr,
+ is added to the manager and appears in user preference file. If it does conflict,
+ it is reassigned with a key sequence from preference files or
+ disabled and added to user preference files (if the files have no shortcut for the action).
+ (2) Key sequences being bound directly to non-QtxAction instances are disabled.
*/
-class SUIT_EXPORT SUIT_ShortcutMgr: public QObject
+class SUIT_EXPORT SUIT_ShortcutMgr: public QObject
{
Q_OBJECT
-public:
- static void Init();
- static SUIT_ShortcutMgr* getShortcutMgr();
- void setSectionEnabled( const QString&, const bool = true );
- void updateShortcuts();
+private:
+ SUIT_ShortcutMgr();
+ SUIT_ShortcutMgr(const SUIT_ShortcutMgr&) = delete;
+ SUIT_ShortcutMgr& operator=(const SUIT_ShortcutMgr&) = delete;
protected:
- SUIT_ShortcutMgr();
virtual ~SUIT_ShortcutMgr();
+public:
+ /*! \brief Create new singleton-instance of shortcut manager, if it has not been created. */
+ static void Init();
+
+ static SUIT_ShortcutMgr* get();
+
+ /*! \brief Checks whether the theKeySequence is platform-compatible. */
+ static bool isKeySequenceValid(const QKeySequence& theKeySequence);
+
+ /*! \returns {false, _ } if theKeySequenceString is invalid. */
+ static std::pair<bool, QKeySequence> toKeySequenceIfValid(const QString& theKeySequenceString);
+
+ /*! \brief Valid module ID does not contain "/" and equals to result of QString(theModuleID).simplified().
+ Empty module ID is valid - it is root module ID. */
+ static bool isModuleIDValid(const QString& theModuleID);
+
+ /*! \brief Valid in-module action ID may consist of several tokens, separated by "/":
+ <token_0>/<token_1>...<token_N>/<token_N-1>.
+ Each <token> must be non-empty and be equal to QString(<token>).simplified().
+ Empty or "#" in-module action ID is not valid. */
+ static bool isInModuleActionIDValid(const QString& theInModuleActionID);
+
+ /*! \returns true, is theInModuleActionID starts with "#". */
+ static bool isInModuleMetaActionID(const QString& theInModuleActionID);
+
+ /*! \brief Extracts module ID and in-module action ID from application-unique action ID.
+ The theActionID must be composed as <moduleID>/<inModuleActionID>.
+ \returns { _ , "" }, if theActionID is invalid. */
+ static std::pair<QString, QString> splitIntoModuleIDAndInModuleID(const QString& theActionID);
+
+ /*! See \ref splitIntoModuleIDAndInModuleID(const QString&). */
+ static bool isActionIDValid(const QString& theActionID);
+
+ /*! \brief Creates application-unique action ID. Reverse to splitIntoModuleIDAndInModuleID.
+ \returns Emppty string, if either theModuleID or theInModuleActionID is invalid*/
+ static QString makeActionID(const QString& theModuleID, const QString& theInModuleActionID);
+
+ /*! \brief Sets all shortcuts from preferences to theContainer. Incoming shortcuts override existing ones.
+ If the container has shortcut for an action, which is absent in preferences, and the existing shortcut
+ does not conflict with incoming ones, it is untouched.
+ See \ref setShortcutsFromPreferences() for details.
+ \param theDefaultOnly If true, user preferences are ignored and only default preferences are used. */
+ static void fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly);
+
+ /*! \brief Checks the resource manager directly.
+ \returns {nameExists, name}. */
+ static std::pair<bool, QString> getActionNameFromResources(const QString& theActionID, QString theLanguage = QString());
+
+
+ /*! \brief Add theAction to map of managed actions. */
+ void registerAction(const QString& theActionID, QAction* theAction);
+
+ /*! \brief Add theAction to map of managed actions. QtxAction::ID() is used as action ID. */
+ void registerAction(QtxAction* theAction);
+
+ /*! \brief Get registered actions. If theInModuleActionID is meta-action ID, seeks in all modules. */
+ std::set<QAction*> getActions(const QString& theModuleID, const QString& theInModuleActionID) const;
+
+ /*! \brief Get module ID and in-module-ID of theAction.
+ \returns { _ , "" } if theAction is not registered. */
+ std::pair<QString, QString> getModuleIDAndInModuleID(const QAction* theAction) const;
+
+ /*! Returns true if theAction is registered. */
+ bool hasAction(const QAction* theAction) const;
+
+ /*! \brief Get action ID of theActon.
+ \returns empty string if theAction is not registered. */
+ QString getActionID(const QAction* theAction) const;
+
+ /*! \brief Enables/disable actions of the module.
+ Only those actions are affected, whose parent widget is active desktop. */
+ void setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable = true) const;
+
+ /*! \brief Enables/disables all registered actions whose in-module action ID begins with theInModuleActionIDPrefix.
+ Only those actions are affected, whose parent widget is active desktop. */
+ void setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
+
+ [[deprecated("Use setActionsWithPrefixInIDEnabled(const QString&, bool) instead.")]]
+ void setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
+
+ /*! \brief For all registered actions binds key sequences from myShortcutContainer. */
+ void rebindActionsToKeySequences() const;
+
+ [[deprecated("Use rebindActionsToKeySequences() instead.")]]
+ void updateShortcuts() const;
+
+ /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts.
+ Then binds key sequences with corresponding registered actions. Saves changes to preferences.
+
+ Redefining a key sequence for the action, if the key sequence does not conflict with other shortcuts, is not considered as a conflict.
+ \param theInModuleActionID The method has no effect if theInModuleActionID is empty.
+ \param theKeySequence Empty theKeySequence does not cause conflicts, in this case
+ a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence.
+ \param theOverride If true, conflicting shortcuts are disabled.
+ \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false,
+ otherwise set of actions (without incoming one), whose shortcuts have been disabled. */
+ std::set<std::pair<QString, QString>> setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride = false);
+ std::set<std::pair<QString, QString>> setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride = false);
+
+ const SUIT_ShortcutContainer& getShortcutContainer() const;
+
+ /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
+ void mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride = true, bool theTreatAbsentIncomingAsDisabled = false);
+
+ /*! \brief Get a key sequence mapped to the action. */
+ QKeySequence getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const;
+
+ /*! \returns {inModuleActionID, keySequence}[] */
+ const std::map<QString, QKeySequence>& getModuleShortcutsInversed(const QString& theModuleID) const;
+
+ /*! \returns All module IDs, which were added to myShortcutContainer. */
+ std::set<QString> getShortcutModuleIDs() const;
+
+ /*! \returns IDs of modules, which interfere with the module:
+ if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
+ std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
+
+ /*! \brief Retrieves module name translated to appropriate language. */
+ QString getModuleName(const QString& theModuleID) const;
+
+ /*! \brief Retrieves at runtime action name translated to appropriate language,
+ if the language was loaded using \ref setActionNamesFromResources(QString). */
+ QString getActionName(const QString& theModuleID, const QString& theInModuleActionID) const;
+
+ /*! \brief Retrieves at runtime action name translated to appropriate language,
+ if the language was loaded using \ref setActionNamesFromResources(QString). */
+ QString getActionName(const QString& theActionID) const;
+
private slots:
- void onActionDestroyed( QObject* );
+ /*!
+ \brief Called when the corresponding action is destroyed.
+ Removes destroyed action from maps of registered actions. Preserves shortcut.
+ \param theObject action being destroyed.
+ */
+ void onActionDestroyed(QObject* theObject);
private:
- virtual bool eventFilter( QObject* o, QEvent* e );
+ /*! \brief Overrides QObject::eventFilter().
+ If theEvent is QEvent::ActionAdded and the action is instance of QtxAction, registers it. */
+ virtual bool eventFilter(QObject* theObject, QEvent* theEvent);
+
+ /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
+ std::set<std::pair<QString, QString>> setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride);
+
+ /*! \brief Set shortcuts from preference files. The method is intended to be called before any calls to setShortcut() or mergeShortcutContainer().
+ Definition of this method assumes, that shortcut settings are serialized as prerefence entries {name=<inModuleActionID>, val=<keySequence>}
+ in dedicated section for each module, with names of sections being composed as "shortcuts:<moduleID>".
- void processAction( QtxAction* );
- QKeySequence getShortcutByActionName( const QString& ) const;
+ E.g. in case of XML file it may look like this:
+ <!--
+ <section name="<module ID>">
+ ...
+ <parameter name="<in-module action ID>" value="key sequence">
+ ...
+ </section>
+ -->
+ <section name="shortcuts:">
+ <parameter name="TOT_DESK_FILE_NEW" value="Ctrl+N"/>
+ <parameter name="TOT_DESK_FILE_OPEN" value="Ctrl+O"/>
+ <parameter name="#General/Show object(s)" value="Ctrl+Alt+S"/>
+ <parameter name="#General/Hide object(s)" value="Ctrl+Alt+H"/>
+ <parameter name="#Viewers/Back view" value="Ctrl+Alt+B"/>
+ <parameter name="#Viewers/Front view" value="Ctrl+Alt+F"/>
+ </section>
+ <section name="shortcuts:GEOM">
+ <parameter name="Isolines/Increase number" value="Meta+I"/>
+ <parameter name="Isolines/Decrease number" value="Meta+D"/>
+ <parameter name="Transparency/Increase" value="Meta+Y"/>
+ <parameter name="Transparency/Decrease" value="Meta+T"/>
+ </section>
+
+ Empty inModuleActionIDs are ignored.
+
+ nb! For any theQtxAction you wish user be able to assign it to a shortcut,
+ add theQtxAction.ID() to default resource files (you can map it to no key sequence).*/
+ void setShortcutsFromPreferences();
+
+ /*! \brief Writes shortcuts to preference files.
+ \param theShortcuts { moduleID, { inModuleActionID, keySequence }[] }[]. Empty inModuleActionIDs are ignored. */
+ static void saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed);
+
+ /*! Fills myActionNames from preference files in theLanguage.
+ \param theLanguage If default, fills names in current language.
+ If a name in requested language is not found, seeks for the name EN in and then in FR.
+
+ In case if resource file is XML, it should look like this:
+ <!--
+ <section name="<action ID>"> Note, that apllication-unique must be typed, not in-module action ID.
+ ...
+ <parameter name="<language>" value="action name">
+ ...
+ </section>
+ -->
+ <section name="shortcut_translations:/#Viewers/Reset view">
+ <parameter name="en" value="Reset View Point"/>
+ <parameter name="fr" value="Restaurer le point de vue"/>
+ <parameter name="ja" value="ビューのポイントを復元します。"/>
+ </section>
+ <section name="shortcut_translations:GEOM/Isolines/Increase number">
+ <parameter name="en" value="Increase number of isolines"/>
+ <parameter name="fr" value="Augmenter le nombre d'isolignes"/>
+ <parameter name="ja" value="等値線の数を増やす"/>
+ </section>
+ */
+ void setActionNamesFromResources(QString theLanguage = QString());
private:
static SUIT_ShortcutMgr* myShortcutMgr;
- QMultiMap<QString, QtxAction*> myShortcutActions;
+
+ /** { moduleID, { inModuleActionID, action[] }[] }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
+ std::map<QString, std::map<QString, std::set<QAction*>>> myActions;
+
+ /** { action, { moduleID, inModuleActionID } }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
+ std::map<QAction*, std::pair<QString, QString>> myActionIDs; // To maintain uniqueness of actions and effectively retrieve IDs of registered actions.
+
+ /** Can not contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
+ SUIT_ShortcutContainer myShortcutContainer;
+
+ /* nb!
+ Sets of moduleIDs and inModuleActionIDs are equal for myActions and myActionIDs.
+ Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer.
+ */
+
+ /** { actionID, {language, actionName} }[] */
+ std::map<QString, std::map<QString, QString>> myActionNames;
};
#if defined WIN32