]> SALOME platform Git repositories - modules/gui.git/blob - src/SUIT/SUIT_ShortcutMgr.h
Salome HOME
[bos #42871] Clipping plane remains applied after being deleted
[modules/gui.git] / src / SUIT / SUIT_ShortcutMgr.h
1 // Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 #ifndef SUIT_SHORTCUTMGR_H
24 #define SUIT_SHORTCUTMGR_H
25
26 #include "SUIT.h"
27
28 #include <QObject>
29 #include <QString>
30 #include <QKeySequence>
31 #include <QStringList>
32 #include <QIcon>
33 #include <map>
34 #include <set>
35 #include <memory>
36 #include <utility>
37 #include <limits>
38
39 class QAction;
40 class QtxAction;
41 class QJsonObject;
42
43 #if defined WIN32
44 #pragma warning( disable: 4251 )
45 #endif
46
47 // Define SHORTCUT_MGR_DBG to enable SUIT_ShortcutMgr debug logging.
48 // #define SHORTCUT_MGR_DBG
49 /*! \returns true, if SUIT_ShortcutMgr debug logging is enabled. */
50 SUIT_EXPORT extern inline bool ShCutDbg() {
51 #ifdef SHORTCUT_MGR_DBG
52   return true;
53 #else
54   return false;
55 #endif
56 }
57 /*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */
58 SUIT_EXPORT extern bool ShCutDbg(const QString& theString);
59 /*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */
60 SUIT_EXPORT extern bool ShCutDbg(const char* theString);
61
62
63 /*!
64   \class SUIT_ShortcutContainer
65   \brief Provides means to keep and edit shortcuts in compliance with the application logics.
66   \ref See SUIT_ShortcutMgr for details.
67 */
68 class SUIT_EXPORT SUIT_ShortcutContainer
69 {
70 public:
71   SUIT_ShortcutContainer();
72
73   /*! \returns IDs of modules, which interfere with the module:
74   if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
75   std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
76
77   std::set<QString> getIDsOfAllModules() const;
78
79   /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts.
80   Redefining a key sequence for the action, if theKeySequence does not conflict with other shortcuts, is not considered as a conflict.
81   \param theModuleID The method has no effect if theModuleID is invalid. \ref See SUIT_ShortcutMgr::isModuleIDValid(const QString&) for details.
82   \param theInModuleActionID The method has no effect if theInModuleActionID is invalid. \ref See SUIT_ShortcutMgr::isInModuleActionIDValid(const QString&).
83   If theInModuleActionID is meta-action ID, the shortcut is set to root module, and theModuleID is ignored.
84   \param theKeySequence Empty theKeySequence does not cause conflicts, in this case
85   a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence.
86   \param theOverride If true, conflicting shortcuts are disabled.
87   \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false,
88   otherwise set of actions (without incoming one), whose shortcuts have been disabled. */
89   std::set<std::pair<QString, QString>> setShortcut(
90     QString theModuleID,
91     const QString& theInModuleActionID,
92     const QKeySequence& theKeySequence,
93     bool theOverride
94   );
95
96   /*! \brief Checks for conflicts. Existence of a shortcut with another key sequence for the action,
97   if theKeySequence does not conflict with other shortcuts, is not considered as a conflict.
98   \param theInModuleActionID If theInModuleActionID is meta-action ID, the shortcut is looked for in root module, and theModuleID is ignored.
99   \param theKeySequence Empty theKeySequence does not have conflicts.
100   \returns {moduleID, inModuleActionID}[] - Set of conflicting actions. */
101   std::set<std::pair<QString, QString>> getConflicts(
102     QString theModuleID,
103     const QString& theInModuleActionID,
104     const QKeySequence& theKeySequence
105   ) const;
106
107   /*! \returns empty key sequence if shortcut for the action is not set.
108   \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/
109   const QKeySequence& getKeySequence(QString theModuleID, const QString& theInModuleActionID) const;
110
111   /*! \returns true, if shortcut for the action is set (even if the mapped key sequence is empty).
112    \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/
113   bool hasShortcut(QString theModuleID, const QString& theInModuleActionID) const;
114
115   /*! \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */
116   const std::map<QString, QKeySequence>& getModuleShortcutsInversed(const QString& theModuleID) const;
117
118   /*! \brief Seeks for shortcuts in the module with in-module action IDs, which start with theInModuleActionIDPrefix.
119   \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */
120   const std::map<QString, QKeySequence> getModuleShortcutsInversed(const QString& theModuleID, const QString& theInModuleActionIDPrefix) const;
121
122   /*! \brief Merges shortcuts of theOther into this.
123   \param theOverride if true, overrides conflicting shortcuts.
124   If false, and this has no shortcut for an incoming action, and the incoming shortcut conflicts
125   with an existing shortcut, disabled shortcut for the incoming action is set.
126   \param theTreatAbsentIncomingAsDisabled If theOverride == false, theTreatAbsentIncomingAsDisabled is ignored.
127   If theOverride and theTreatAbsentIncomingAsDisabled, and theOther has no shortcut for an action, which exists in this,
128   the existing shortcut in this is set disabled.
129     \returns { moduleID, { inModuleActionID, {oldKeySequence, newKeySequence} }[] }[] - Modiified shortcuts inversed. */
130   std::map<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>> merge(
131     const SUIT_ShortcutContainer& theOther,
132     bool theOverride,
133     bool theTreatAbsentIncomingAsDisabled = false
134   );
135
136   bool hasKeySequence(const QString& theModuleID, const QKeySequence& theKeySequence) const;
137
138   /*! \brief Generates human-readable text representation of content. */
139   QString toString() const;
140
141 private:
142   /** { moduleID, { keySequence, inModuleActionID }[] }[]. keySequence can not be empty.
143    * Can not contain entries like { <non-root module ID>, { keySequence, <meta-action ID> } }. */
144   std::map<QString, std::map<QKeySequence, QString>> myShortcuts;
145
146   /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty.
147    * Can not contain entries like { <non-root module ID>, { <meta-action ID>, keySequence } }. */
148   std::map<QString, std::map<QString, QKeySequence>> myShortcutsInversed;
149 };
150
151
152 /*! \brief GUI-related assets. */
153 struct SUIT_EXPORT SUIT_ActionAssets
154 {
155   struct LangDependentAssets
156   {
157     static const QString PROP_ID_NAME;
158     static const QString PROP_ID_TOOLTIP;
159
160     bool fromJSON(const QJsonObject& theJsonObject);
161     void toJSON(QJsonObject& oJsonObject) const;
162
163     QString myName;
164     QString myToolTip;
165   };
166
167   static const QString STRUCT_ID;
168   static const QString PROP_ID_LANG_DEPENDENT_ASSETS;
169   static const QString PROP_ID_ICON_PATH;
170
171   bool fromJSON(const QJsonObject& theJsonObject);
172   void toJSON(QJsonObject& oJsonObject) const;
173   QString toString() const;
174
175   QStringList getLangs() const;
176   void clearAllLangsExcept(const QString& theLang);
177
178   /*! \param theOverride If true, values of theOther override conflicting values of this. */
179   void merge(const SUIT_ActionAssets& theOther, bool theOverride);
180
181   std::map<QString, LangDependentAssets> myLangDependentAssets;
182   QString myIconPath;
183
184   /*! Is not serialized. */
185   QIcon myIcon;
186 };
187
188
189 /*!
190   \class SUIT_ShortcutMgr
191   \brief Handles action shortcut customization.
192
193   IDENTIFIED ACTIONS/SHORTCUTS
194
195   Register actions under action IDs. Set shortcuts, which are [action ID]<->[key sequence] mappings.
196   Every time an action is registered or a shorcut is set, if there are an action and a shortcut,
197   which are mapped to the same action ID, the action is bound to the key sequence of the shortcut.
198   Action IDs are also used to (de)serialize shortcut settings.
199   Several QActions may be registered under the same ID.
200
201   Most of actions are intercepted on creation in SUIT_ShortcutMgr:eventFilter(QObject* theObject, QEvent* theEvent).
202   If an intercepted action is instance of QtxAction, it is registered automatically.
203   Since non-QtxActions have no member ID(), SUIT_ShortcutMgr is unable to register them automatically
204   in SUIT_ShortcutMgr::eventFilter(). Thus, every non-QtxAction should be
205   passed to SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction).
206
207   Action ID is application-unique must be composed as <moduleID>/<inModuleActionID>.
208   If an action belongs to a desktop or is available even if no module is active (e.g. 'Save As'),
209   use empty string as <moduleID>. Let's call such actions as root actions.
210
211   There is a need to keep multiple actions, which do the same from user' perspective,
212   bound to the same key sequence. E.g. 'Front view'. Let's call such set of actions as meta-action.
213   Actions of a meta-action may belong to different modules, and/or there may be several actions
214   of the same meta-action in the same module. <inModuleActionID> of all members of a meta-action
215   must be the same and start with "#".
216   Meta-action is root action when it comes to checking for conflicts.
217   Shortcuts of meta-actions are (de)serialized to the same section of preference files as root shortcuts.
218
219   <inModuleActionID> can contain several "/". Go to \ref isInModuleActionIDValid(const QString&) for details.
220   You can refer to multiple actions, whose <inModuleActionID> starts with the prefix.
221
222   Only one module can be active at instance. So a key sequence must be unique within a joined temporary table of
223   root and active module shortcuts. An action is allowed to be bound with only key sequence.
224
225   ANONYMOUS ACTIONS/SHORTCUTS
226
227   Actions without action IDs or with invalid ones are called anonymous actions.
228   All anonymous actions with non-empty shortcut key sequences are registered by SUIT_ShortcutMgr.
229   If a shortcut for an anonymous action clashes with a shortcut for an action with defined ID (identified action/shortcut),
230   the shortcut for the anonymous action is disabled, but [the anonymous action, the hard-coded key sequence] pair
231   remains within the SUIT_ShortcutMgr. If user redefines key sequences for identified actions,
232   and the clash is gone, SUIT_ShortcutMgr enables back the shortcut for the anonymous action.
233
234   WARNING!
235   Avoid assigning shortcuts to instances of QAction and all its descendants directly.
236   (1) Key sequence being bound directly to any registered/intercepted action with valid ID,
237       if the key sequence does not conflict with shortcuts kept by SUIT_ShortcutMgr,
238       is added to the manager and appears in user preference file. If it does conflict,
239       it is reassigned with a key sequence from preference files or
240       disabled and added to user preference files (if the files have no shortcut for the action).
241   (2) It is not possible to reassign key sequences for anonymous actions using the Shortcut Editor GUI.
242       It is not possible to always warn user, if a key sequence, he assigns to an identified action,
243       disables an anonymous shortcut, because SUIT_ShortcutMgr has no data about anonymous actions until they appear in runtime.
244       To prevent the user from relying on such warnings, they are completely disabled.
245 */
246 class SUIT_EXPORT SUIT_ShortcutMgr: public QObject
247 {
248   Q_OBJECT
249
250 private:
251   SUIT_ShortcutMgr();
252   SUIT_ShortcutMgr(const SUIT_ShortcutMgr&) = delete;
253   SUIT_ShortcutMgr& operator=(const SUIT_ShortcutMgr&) = delete;
254
255 protected:
256   virtual ~SUIT_ShortcutMgr();
257
258 public:
259   static const QString ROOT_MODULE_ID;
260
261   /*! \brief Create new singleton-instance of shortcut manager, if it has not been created. */
262   static void Init();
263
264   static SUIT_ShortcutMgr* get();
265
266   /*! \brief Checks whether the theKeySequence is platform-compatible. */
267   static bool isKeySequenceValid(const QKeySequence& theKeySequence);
268
269   /*! \returns {false, _ } if  theKeySequenceString is invalid. */
270   static std::pair<bool, QKeySequence> toKeySequenceIfValid(const QString& theKeySequenceString);
271
272   /*! \brief Valid module ID does not contain "/" and equals to result of QString(theModuleID).simplified().
273   Empty module ID is valid - it is root module ID. */
274   static bool isModuleIDValid(const QString& theModuleID);
275
276   /*! \brief Valid in-module action ID may consist of several tokens, separated by "/":
277   <token_0>/<token_1>...<token_N>/<token_N-1>.
278   Each <token> must be non-empty and be equal to QString(<token>).simplified().
279   Empty or "#" in-module action ID is not valid. */
280   static bool isInModuleActionIDValid(const QString& theInModuleActionID);
281
282   /*! \returns true, is theInModuleActionID starts with "#". */
283   static bool isInModuleMetaActionID(const QString& theInModuleActionID);
284
285   /*! \brief Extracts module ID and in-module action ID from application-unique action ID.
286   The theActionID must be composed as <moduleID>/<inModuleActionID>.
287   \returns { _ , "" }, if theActionID is invalid. */
288   static std::pair<QString, QString> splitIntoModuleIDAndInModuleID(const QString& theActionID);
289
290   /*! See \ref splitIntoModuleIDAndInModuleID(const QString&). */
291   static bool isActionIDValid(const QString& theActionID);
292
293   /*! \brief Creates application-unique action ID. Reverse to splitIntoModuleIDAndInModuleID.
294   \returns Emppty string, if either theModuleID or theInModuleActionID is invalid*/
295   static QString makeActionID(const QString& theModuleID, const QString& theInModuleActionID);
296
297   /*! \brief Sets all shortcuts from preferences to theContainer. Incoming shortcuts override existing ones.
298   If the container has shortcut for an action, which is absent in preferences, and the existing shortcut
299   does not conflict with incoming ones, it is untouched.
300   See \ref setShortcutsFromPreferences() for details.
301   \param theDefaultOnly If true, user preferences are ignored and only default preferences are used. */
302   static void fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly);
303
304   /*! \brief Checks the resource manager directly.
305   \returns {assetsExist, assets}. */
306   static std::pair<bool, SUIT_ActionAssets> getActionAssetsFromResources(const QString& theActionID);
307
308   /*! \returns Language, which is set in resource manager. */
309   static QString getLang();
310
311
312   /*! \brief Add theAction to map of managed actions. */
313   void registerAction(const QString& theActionID, QAction* theAction);
314
315   /*! \brief Add theAction to map of managed actions. QtxAction::ID() is used as action ID. */
316   void registerAction(QtxAction* theAction);
317
318   /*! \brief Get registered actions. If theInModuleActionID is meta-action ID, seeks in all modules. */
319   std::set<QAction*> getActions(const QString& theModuleID, const QString& theInModuleActionID) const;
320
321   /*! \brief Get module ID and in-module-ID of theAction.
322   \returns { _ , "" } if theAction is not registered. */
323   std::pair<QString, QString> getModuleIDAndInModuleID(const QAction* theAction) const;
324
325   /*! Returns true if theAction is registered. */
326   bool hasAction(const QAction* theAction) const;
327
328   /*! \brief Get action ID of theActon.
329   \returns empty string if theAction is not registered. */
330   QString getActionID(const QAction* theAction) const;
331
332   /*! \brief Enables/disable actions of the module.
333   Only those actions are affected, whose parent widget is active desktop. */
334   void setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable = true) const;
335
336   [[deprecated("Use setActionsOfModuleEnabled(const QString&, bool) instead.")]]
337   void setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
338
339   /*! \brief Enables/disables all registered actions whose in-module action ID begins with theInModuleActionIDPrefix.
340   Only those actions are affected, whose parent widget is active desktop. */
341   void setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
342
343   /*! \brief For all registered actions binds key sequences from myShortcutContainer. */
344   void rebindActionsToKeySequences() const;
345
346   [[deprecated("Use rebindActionsToKeySequences() instead.")]]
347   void updateShortcuts() const;
348
349   /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts.
350   Then binds key sequences with corresponding registered actions. Saves changes to preferences.
351
352   Redefining a key sequence for the action, if the key sequence does not conflict with other shortcuts, is not considered as a conflict.
353   \param theInModuleActionID The method has no effect if theInModuleActionID is empty.
354   \param theKeySequence Empty theKeySequence does not cause conflicts, in this case
355   a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence.
356   \param theOverride If true, conflicting shortcuts are disabled.
357   \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false,
358   otherwise set of actions (without incoming one), whose shortcuts have been disabled. */
359   std::set<std::pair<QString, QString>> setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride = false);
360   std::set<std::pair<QString, QString>> setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride = false);
361
362   const SUIT_ShortcutContainer& getShortcutContainer() const;
363
364   /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
365   void mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride = true, bool theTreatAbsentIncomingAsDisabled = false);
366
367   /*! \brief Get a key sequence mapped to the action. */
368   const QKeySequence& getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const;
369
370   /*! \returns {inModuleActionID, keySequence}[] */
371   const std::map<QString, QKeySequence>& getModuleShortcutsInversed(const QString& theModuleID) const;
372
373   /*! \returns All module IDs, which were added to myShortcutContainer. */
374   std::set<QString> getShortcutModuleIDs() const;
375
376   /*! \returns IDs of modules, which interfere with the module:
377   if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
378   std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
379
380   /*! \returns assets, which describe module's header, not its content. */
381   std::shared_ptr<const SUIT_ActionAssets> getModuleAssets(const QString& theModuleID) const;
382
383   /*! \returns assets, which describe modules' headers, not their content. */
384   std::map<QString, std::shared_ptr<SUIT_ActionAssets>> getModuleAssets() const { return myModuleAssets; }
385
386   /*! \brief Retrieves module name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
387   QString getModuleName(const QString& theModuleID, const QString& theLang = "") const;
388
389   std::shared_ptr<const SUIT_ActionAssets> getActionAssets(const QString& theModuleID, const QString& theInModuleActionID) const;
390
391   std::shared_ptr<const SUIT_ActionAssets> getActionAssets(const QString& theActionID) const;
392
393   std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> getActionAssets() const { return myActionAssets; }
394
395   /*! \brief Retrieves action name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
396   QString getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang = "") const;
397
398 private slots:
399   /*!
400   \brief Called when the corresponding action is destroyed.
401   Removes destroyed action from maps of registered actions. Preserves shortcut.
402   \param theObject action being destroyed.
403   */
404   void onActionDestroyed(QObject* theObject);
405
406   void onAnonymousActionDestroyed(QObject* theObject);
407
408 private:
409   /*! \brief Overrides QObject::eventFilter().
410   If theEvent is QEvent::ActionAdded and the action is instance of QtxAction, registers it. */
411   virtual bool eventFilter(QObject* theObject, QEvent* theEvent);
412
413   /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
414   std::set<std::pair<QString, QString>> setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride);
415
416   /*! \brief Set shortcuts from preference files. The method is intended to be called before any calls to setShortcut() or mergeShortcutContainer().
417   Definition of this method assumes, that shortcut settings are serialized as prerefence entries {name=<inModuleActionID>, val=<keySequence>}
418   in dedicated section for each module, with names of sections being composed as "shortcuts:<moduleID>".
419
420   E.g. in case of XML file it may look like this:
421   <!--
422   <section name="<module ID>">
423     ...
424     <parameter name="<in-module action ID>" value="key sequence">
425     ...
426   </section>
427   -->
428   <section name="shortcuts:">
429     <parameter name="TOT_DESK_FILE_NEW" value="Ctrl+N"/>
430     <parameter name="TOT_DESK_FILE_OPEN" value="Ctrl+O"/>
431     <parameter name="#General/Show object(s)" value="Ctrl+Alt+S"/>
432     <parameter name="#General/Hide object(s)" value="Ctrl+Alt+H"/>
433     <parameter name="#Viewers/Back view" value="Ctrl+Alt+B"/>
434     <parameter name="#Viewers/Front view" value="Ctrl+Alt+F"/>
435   </section>
436    <section name="shortcuts:GEOM">
437     <parameter name="Isolines/Increase number" value="Meta+I"/>
438     <parameter name="Isolines/Decrease number" value="Meta+D"/>
439     <parameter name="Transparency/Increase" value="Meta+Y"/>
440     <parameter name="Transparency/Decrease" value="Meta+T"/>
441   </section>
442
443   Empty inModuleActionIDs are ignored.
444
445   nb! For any theQtxAction you wish user be able to assign it to a shortcut,
446   add theQtxAction.ID() to default resource files (you can map it to no key sequence).*/
447   void setShortcutsFromPreferences();
448
449   /*! \brief Writes shortcuts to preference files.
450   \param theShortcuts { moduleID, { inModuleActionID, keySequence }[] }[]. Empty inModuleActionIDs are ignored. */
451   static void saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed);
452
453   /*! \brief Writes shortcuts to preference files.
454   \param theShortcuts { moduleID, { inModuleActionID, {oldKeySequence, newKeySequence} }[] }[]. Empty inModuleActionIDs are ignored.
455   OldKeySequences are ignored. */
456   static void saveShortcutsToPreferences(const std::map<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>>& theShortcutsInversed);
457
458   /*! Fills myActionAssets from asset files in theLanguage.
459   \param theLanguage If default, fills assets in current language.
460   If an asset in requested language is not found, seeks for the asset EN in and then in FR.
461
462   Asset files must be structured like this:
463   {
464     ...
465     actionID : {
466       "langDependentAssets": {
467         ...
468         lang: {
469           "name": name,
470           "tooltip": tooltip
471         },
472         ...
473       },
474       "iconPath": iconPath
475     },
476     ...
477   }
478   */
479   void setAssetsFromResources(QString theLanguage = QString());
480
481   void registerAnonymousShortcut(QAction* const theAction);
482   void enableAnonymousShortcutsClashingWith(const QString& theModuleID, const bool theEnable) const;
483   void enableAnonymousShortcutsClashingWith(const QKeySequence& theKeySequence, bool theEnable) const;
484   QString anonymousShortcutsToString() const;
485
486 private:
487   static SUIT_ShortcutMgr* myShortcutMgr;
488
489   /** { moduleID, { inModuleActionID, action[] }[] }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
490   std::map<QString, std::map<QString, std::set<QAction*>>> myActions;
491
492   /** { action, { moduleID, inModuleActionID } }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
493   std::map<QAction*, std::pair<QString, QString>> myActionIDs; // To maintain uniqueness of actions and effectively retrieve IDs of registered actions.
494
495   /** Can not contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
496   SUIT_ShortcutContainer myShortcutContainer;
497
498   /* nb!
499   Sets of moduleIDs and inModuleActionIDs are equal for myActions and myActionIDs.
500   Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer.
501   */
502
503   /** { moduleID, {inModuleActionID, assets}[] }[] */
504   std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> myActionAssets;
505
506   /** {moduleID, assets}[] */
507   mutable std::map<QString, std::shared_ptr<SUIT_ActionAssets>> myModuleAssets;
508
509   mutable std::set<QString> myActiveModuleIDs;
510
511   /** Actions without IDs, but with hard-coded non-empty key sequences.
512   * Shortcuts, defined in preferences, override shortcuts of anonymous actions - if an active module has a preference shortcut,
513   * anonymous shortcuts with the same key sequence are disabled. If the root module has a preference shortcut, which
514   * is in clash with anonymous shortcuts, clashing anonymous actions are always disabled. */
515   std::map<QAction*, QKeySequence> myAnonymousShortcuts;
516
517   std::map<QKeySequence, std::set<QAction*>> myAnonymousShortcutsInverse;
518 };
519
520
521 /*!
522   \class SUIT_SentenceMatcher
523   \brief Approximate string matcher, treats strings as sentences composed of words.
524 */
525 class SUIT_EXPORT SUIT_SentenceMatcher
526 {
527 public:
528   /*! Default config:
529     Exact word order = false;
530     Fuzzy words = true;
531     Case sensitive = false;
532     Query = ""; // matches nothing.
533   */
534   SUIT_SentenceMatcher();
535
536   void setUseExactWordOrder(bool theOn);
537   void setUseFuzzyWords(bool theOn);
538   void setCaseSensitive(bool theOn);
539   inline bool isCaseSensitive() const { return myIsCaseSensitive; };
540
541   /*! \param theQuery should not be regex. */
542   void setQuery(QString theQuery);
543
544   inline const QString& getQuery() const { return myQuery; };
545
546   /*! \returns match metrics. The metrics >= 0. INF means mismatch.
547   The class is unable to differentiate exact match with some approximate matches! */
548   double match(const QString& theInputString) const;
549
550   /** \brief For debug. */
551   QString toString() const;
552
553 private:
554   static bool makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences);
555   static void makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords);
556
557   /*! \returns number of characters in matched words. The number >= 0. */
558   static int matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive);
559   /*! \returns number of characters in matched words. The number >= 0. */
560   static int matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive);
561
562   /*! \returns number of characters in matched words. The number >= 0. */
563   static int matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive);
564
565   /*! \returns number of characters in matched words. The number >= 0. */
566   static int match(
567     const QString& theInputString,
568     const QStringList& theSentence,
569     bool theCaseSensitive
570   );
571
572   /*! \returns number of characters in matched words. The number >= 0. */
573   static int match(
574     const QString& theInputString,
575     const QList<QStringList>& theSentences,
576     bool theCaseSensitive
577   );
578
579   bool myUseExactWordOrder; // If false, try to match with sentences, composed of query's words in different orders.
580   bool myUseFuzzyWords; // Try to match with sentences, composed of query's truncated words.
581   bool myIsCaseSensitive;
582   QString myQuery;
583
584   QStringList myWords; // It is also original search sentence.
585   QList<QStringList> myPermutatedSentences;
586
587   QStringList myFuzzyWords; // Regexes.
588   QList<QStringList> myFuzzyPermutatedSentences;
589 };
590
591
592 /*!
593   \class SUIT_ActionSearcher
594   \brief Searches in data, provided in action asset files and shortcut preferences.
595 */
596 class SUIT_EXPORT SUIT_ActionSearcher
597 {
598 public:
599   enum MatchField {
600     ID,
601     Name,
602     ToolTip,
603     KeySequence
604   };
605
606   class AssetsAndSearchData
607   {
608   public:
609     AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets = nullptr, double theMatchMetrics = std::numeric_limits<double>::infinity());
610
611     void setMatchMetrics(double theMatchMetrics);
612     double matchMetrics() const { return myMatchMetrics; };
613
614     std::shared_ptr<const SUIT_ActionAssets> myAssets;
615
616     void toJSON(QJsonObject& oJsonObject) const;
617     QString toString() const;
618
619   private:
620     /*! \brief Ideally it should be number of weighted character permutations. Now it is just a number of characters in unmatched words. */
621     double myMatchMetrics;
622   };
623
624   /*! Default config:
625       Included modules' IDs = { ROOT_MODULE_ID };
626       Include disabled actions = false;
627       Fields to match = { Name, Tooltip };
628       Case sensitive = false;
629       Fuzzy matching = true;
630       Query = ""; // matches everything.
631   */
632   SUIT_ActionSearcher();
633   SUIT_ActionSearcher(const SUIT_ActionSearcher&) = delete;
634   SUIT_ActionSearcher& operator=(const SUIT_ActionSearcher&) = delete;
635   virtual ~SUIT_ActionSearcher() = default;
636
637   /*! \returns true, if set of results is changed. */
638   bool setIncludedModuleIDs(std::set<QString> theIncludedModuleIDs);
639
640   /*! \returns true, if set of results is changed. */
641   bool includeDisabledActions(bool theOn);
642   inline bool areDisabledActionsIncluded() const {return myIncludeDisabledActions;};
643
644   /*! \returns true, if set of results is changed. */
645   bool setFieldsToMatch(const std::set<SUIT_ActionSearcher::MatchField>& theFields);
646
647   /*! \returns true, if set of results is changed. */
648   bool setCaseSensitive(bool theOn);
649
650   /*! \returns true, if set of results is changed. */
651   bool setQuery(const QString& theQuery);
652   inline const QString& getQuery() const {return myMatcher.getQuery();};
653
654   const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& getSearchResults() const;
655
656
657 private:
658   /*! \brief Applies filter to all actions, provided in asset files for SUIT_ShortcutMgr.
659   \returns { true, _ } if set of results is changed; { _ , true } if matching metrics is changed for at least one result. */
660   std::pair<bool, bool> filter();
661
662   /*! \brief Applies filter to search results only.
663   \returns { true, _ } if set of results is shrunk; { _ , true } if matching metrics is changed for at least one result. */
664   std::pair<bool, bool> filterResults();
665
666   /*! \brief Applies filter only to actions, which are not in search results.
667   \returns True, if set of results is extended. */
668   bool extendResults();
669
670   double matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets);
671
672   QString toString() const;
673
674
675   std::set<QString> myIncludedModuleIDs;
676   bool myIncludeDisabledActions;
677
678   std::set<SUIT_ActionSearcher::MatchField> myFieldsToMatch;
679   SUIT_SentenceMatcher myMatcher;
680
681   /* { moduleID, {inModuleActionID, assetsAndSearchData}[] }[]. */
682   std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>> mySearchResults;
683 };
684
685
686 #if defined WIN32
687 #pragma warning( default: 4251 )
688 #endif
689
690 #endif