Salome HOME
[bos #40644][CEA](2024-T1) Feature search.
[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 <QStringList>
31 #include <QIcon>
32 #include <map>
33 #include <set>
34 #include <memory>
35 #include <utility>
36 #include <limits>
37
38 class QAction;
39 class QtxAction;
40 class QKeySequence;
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, keySequence }[] }[] - Modiified shortcuts inversed. */
130   std::map<QString, std::map<QString, QKeySequence>> merge(
131     const SUIT_ShortcutContainer& theOther,
132     bool theOverride,
133     bool theTreatAbsentIncomingAsDisabled = false
134   );
135
136   /*! \brief Generates human-readable text representation of content. */
137   QString toString() const;
138
139 private:
140   /** { moduleID, { keySequence, inModuleActionID }[] }[]. keySequence can not be empty.
141    * Can not contain entries like { <non-root module ID>, { keySequence, <meta-action ID> } }. */
142   std::map<QString, std::map<QKeySequence, QString>> myShortcuts;
143
144   /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty.
145    * Can not contain entries like { <non-root module ID>, { <meta-action ID>, keySequence } }. */
146   std::map<QString, std::map<QString, QKeySequence>> myShortcutsInversed;
147 };
148
149
150 /*! \brief GUI-related assets. */
151 struct SUIT_EXPORT SUIT_ActionAssets
152 {
153   struct LangDependentAssets
154   {
155     static const QString PROP_ID_NAME;
156     static const QString PROP_ID_TOOLTIP;
157
158     bool fromJSON(const QJsonObject& theJsonObject);
159     void toJSON(QJsonObject& oJsonObject) const;
160
161     QString myName;
162     QString myToolTip;
163   };
164
165   static const QString STRUCT_ID;
166   static const QString PROP_ID_LANG_DEPENDENT_ASSETS;
167   static const QString PROP_ID_ICON_PATH;
168
169   bool fromJSON(const QJsonObject& theJsonObject);
170   void toJSON(QJsonObject& oJsonObject) const;
171   QString toString() const;
172
173   QStringList getLangs() const;
174   void clearAllLangsExcept(const QString& theLang);
175
176   /*! \param theOverride If true, values of theOther override conflicting values of this. */
177   void merge(const SUIT_ActionAssets& theOther, bool theOverride);
178
179   std::map<QString, LangDependentAssets> myLangDependentAssets;
180   QString myIconPath;
181
182   /*! Is not serialized. */
183   QIcon myIcon;
184 };
185
186
187 /*!
188   \class SUIT_ShortcutMgr
189   \brief Handles action shortcut customization.
190
191   Register actions under action IDs. Set shortcuts, which are [action ID]<->[key sequence] mappings.
192   Every time an action is registered or a shorcut is set, if there are an action and a shortcut,
193   which are mapped to the same action ID, the action is bound to the key sequence of the shortcut.
194   Action IDs are also used to (de)serialize shortcut settings.
195   Several QActions may be registered under the same ID.
196
197   Most of actions are intercepted on creation in SUIT_ShortcutMgr:eventFilter(QObject* theObject, QEvent* theEvent).
198   If an intercepted action is instance of QtxAction, it is registered automatically.
199   Since non-QtxActions have no member ID(), SUIT_ShortcutMgr is unable to register them automatically
200   in SUIT_ShortcutMgr::eventFilter(). Thus, every non-QtxAction should be
201   passed to SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction).
202
203   Action ID is application-unique must be composed as <moduleID>/<inModuleActionID>.
204   If an action belongs to a desktop or is available even if no module is active (e.g. 'Save As'),
205   use empty string as <moduleID>. Let's call such actions as root actions.
206
207   There is a need to keep multiple actions, which do the same from user' perspective,
208   bound to the same key sequence. E.g. 'Front view'. Let's call such set of actions as meta-action.
209   Actions of a meta-action may belong to different modules, and/or there may be several actions
210   of the same meta-action in the same module. <inModuleActionID> of all members of a meta-action
211   must be the same and start with "#".
212   Meta-action is root action when it comes to checking for conflicts.
213   Shortcuts of meta-actions are (de)serialized to the same section of preference files as root shortcuts.
214
215   <inModuleActionID> can contain several "/". Go to \ref isInModuleActionIDValid(const QString&) for details.
216   You can refer to multiple actions, whose <inModuleActionID> starts with the prefix.
217
218   Only one module can be active at instance. So a key sequence must be unique within a joined temporary table of
219   root and active module shortcuts. An action is allowed to be bound with only key sequence.
220
221   WARNING!
222   Avoid assigning shortcuts to instances of QAction and all its descendants directly.
223   (1) Key sequence being bound directly to any registered/intercepted action with valid ID,
224       if the key sequence does not conflict with shortcuts kept by SUIT_ShortcutMgr,
225       is added to the manager and appears in user preference file. If it does conflict,
226       it is reassigned with a key sequence from preference files or
227       disabled and added to user preference files (if the files have no shortcut for the action).
228   (2) Key sequences being bound directly to non-QtxAction instances are disabled.
229 */
230 class SUIT_EXPORT SUIT_ShortcutMgr: public QObject
231 {
232   Q_OBJECT
233
234 private:
235   SUIT_ShortcutMgr();
236   SUIT_ShortcutMgr(const SUIT_ShortcutMgr&) = delete;
237   SUIT_ShortcutMgr& operator=(const SUIT_ShortcutMgr&) = delete;
238
239 protected:
240   virtual ~SUIT_ShortcutMgr();
241
242 public:
243   static const QString ROOT_MODULE_ID;
244
245   /*! \brief Create new singleton-instance of shortcut manager, if it has not been created. */
246   static void Init();
247
248   static SUIT_ShortcutMgr* get();
249
250   /*! \brief Checks whether the theKeySequence is platform-compatible. */
251   static bool isKeySequenceValid(const QKeySequence& theKeySequence);
252
253   /*! \returns {false, _ } if  theKeySequenceString is invalid. */
254   static std::pair<bool, QKeySequence> toKeySequenceIfValid(const QString& theKeySequenceString);
255
256   /*! \brief Valid module ID does not contain "/" and equals to result of QString(theModuleID).simplified().
257   Empty module ID is valid - it is root module ID. */
258   static bool isModuleIDValid(const QString& theModuleID);
259
260   /*! \brief Valid in-module action ID may consist of several tokens, separated by "/":
261   <token_0>/<token_1>...<token_N>/<token_N-1>.
262   Each <token> must be non-empty and be equal to QString(<token>).simplified().
263   Empty or "#" in-module action ID is not valid. */
264   static bool isInModuleActionIDValid(const QString& theInModuleActionID);
265
266   /*! \returns true, is theInModuleActionID starts with "#". */
267   static bool isInModuleMetaActionID(const QString& theInModuleActionID);
268
269   /*! \brief Extracts module ID and in-module action ID from application-unique action ID.
270   The theActionID must be composed as <moduleID>/<inModuleActionID>.
271   \returns { _ , "" }, if theActionID is invalid. */
272   static std::pair<QString, QString> splitIntoModuleIDAndInModuleID(const QString& theActionID);
273
274   /*! See \ref splitIntoModuleIDAndInModuleID(const QString&). */
275   static bool isActionIDValid(const QString& theActionID);
276
277   /*! \brief Creates application-unique action ID. Reverse to splitIntoModuleIDAndInModuleID.
278   \returns Emppty string, if either theModuleID or theInModuleActionID is invalid*/
279   static QString makeActionID(const QString& theModuleID, const QString& theInModuleActionID);
280
281   /*! \brief Sets all shortcuts from preferences to theContainer. Incoming shortcuts override existing ones.
282   If the container has shortcut for an action, which is absent in preferences, and the existing shortcut
283   does not conflict with incoming ones, it is untouched.
284   See \ref setShortcutsFromPreferences() for details.
285   \param theDefaultOnly If true, user preferences are ignored and only default preferences are used. */
286   static void fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly);
287
288   /*! \brief Checks the resource manager directly.
289   \returns {assetsExist, assets}. */
290   static std::pair<bool, SUIT_ActionAssets> getActionAssetsFromResources(const QString& theActionID);
291
292   /*! \returns Language, which is set in resource manager. */
293   static QString getLang();
294
295
296   /*! \brief Add theAction to map of managed actions. */
297   void registerAction(const QString& theActionID, QAction* theAction);
298
299   /*! \brief Add theAction to map of managed actions. QtxAction::ID() is used as action ID. */
300   void registerAction(QtxAction* theAction);
301
302   /*! \brief Get registered actions. If theInModuleActionID is meta-action ID, seeks in all modules. */
303   std::set<QAction*> getActions(const QString& theModuleID, const QString& theInModuleActionID) const;
304
305   /*! \brief Get module ID and in-module-ID of theAction.
306   \returns { _ , "" } if theAction is not registered. */
307   std::pair<QString, QString> getModuleIDAndInModuleID(const QAction* theAction) const;
308
309   /*! Returns true if theAction is registered. */
310   bool hasAction(const QAction* theAction) const;
311
312   /*! \brief Get action ID of theActon.
313   \returns empty string if theAction is not registered. */
314   QString getActionID(const QAction* theAction) const;
315
316   /*! \brief Enables/disable actions of the module.
317   Only those actions are affected, whose parent widget is active desktop. */
318   void setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable = true) const;
319
320   /*! \brief Enables/disables all registered actions whose in-module action ID begins with theInModuleActionIDPrefix.
321   Only those actions are affected, whose parent widget is active desktop. */
322   void setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
323
324   [[deprecated("Use setActionsWithPrefixInIDEnabled(const QString&, bool) instead.")]]
325   void setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const;
326
327   /*! \brief For all registered actions binds key sequences from myShortcutContainer. */
328   void rebindActionsToKeySequences() const;
329
330   [[deprecated("Use rebindActionsToKeySequences() instead.")]]
331   void updateShortcuts() const;
332
333   /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts.
334   Then binds key sequences with corresponding registered actions. Saves changes to preferences.
335
336   Redefining a key sequence for the action, if the key sequence does not conflict with other shortcuts, is not considered as a conflict.
337   \param theInModuleActionID The method has no effect if theInModuleActionID is empty.
338   \param theKeySequence Empty theKeySequence does not cause conflicts, in this case
339   a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence.
340   \param theOverride If true, conflicting shortcuts are disabled.
341   \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false,
342   otherwise set of actions (without incoming one), whose shortcuts have been disabled. */
343   std::set<std::pair<QString, QString>> setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride = false);
344   std::set<std::pair<QString, QString>> setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride = false);
345
346   const SUIT_ShortcutContainer& getShortcutContainer() const;
347
348   /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
349   void mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride = true, bool theTreatAbsentIncomingAsDisabled = false);
350
351   /*! \brief Get a key sequence mapped to the action. */
352   QKeySequence getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const;
353
354   /*! \returns {inModuleActionID, keySequence}[] */
355   const std::map<QString, QKeySequence>& getModuleShortcutsInversed(const QString& theModuleID) const;
356
357   /*! \returns All module IDs, which were added to myShortcutContainer. */
358   std::set<QString> getShortcutModuleIDs() const;
359
360   /*! \returns IDs of modules, which interfere with the module:
361   if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
362   std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
363
364   /*! \returns assets, which describe module's header, not its content. */
365   std::shared_ptr<const SUIT_ActionAssets> getModuleAssets(const QString& theModuleID) const;
366
367   /*! \returns assets, which describe modules' headers, not their content. */
368   std::map<QString, std::shared_ptr<SUIT_ActionAssets>> getModuleAssets() const { return myModuleAssets; }
369
370   /*! \brief Retrieves module name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
371   QString getModuleName(const QString& theModuleID, const QString& theLang = "") const;
372
373   std::shared_ptr<const SUIT_ActionAssets> getActionAssets(const QString& theModuleID, const QString& theInModuleActionID) const;
374
375   std::shared_ptr<const SUIT_ActionAssets> getActionAssets(const QString& theActionID) const;
376
377   std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> getActionAssets() const { return myActionAssets; }
378
379   /*! \brief Retrieves action name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
380   QString getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang = "") const;
381
382 private slots:
383   /*!
384   \brief Called when the corresponding action is destroyed.
385   Removes destroyed action from maps of registered actions. Preserves shortcut.
386   \param theObject action being destroyed.
387   */
388   void onActionDestroyed(QObject* theObject);
389
390 private:
391   /*! \brief Overrides QObject::eventFilter().
392   If theEvent is QEvent::ActionAdded and the action is instance of QtxAction, registers it. */
393   virtual bool eventFilter(QObject* theObject, QEvent* theEvent);
394
395   /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */
396   std::set<std::pair<QString, QString>> setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride);
397
398   /*! \brief Set shortcuts from preference files. The method is intended to be called before any calls to setShortcut() or mergeShortcutContainer().
399   Definition of this method assumes, that shortcut settings are serialized as prerefence entries {name=<inModuleActionID>, val=<keySequence>}
400   in dedicated section for each module, with names of sections being composed as "shortcuts:<moduleID>".
401
402   E.g. in case of XML file it may look like this:
403   <!--
404   <section name="<module ID>">
405     ...
406     <parameter name="<in-module action ID>" value="key sequence">
407     ...
408   </section>
409   -->
410   <section name="shortcuts:">
411     <parameter name="TOT_DESK_FILE_NEW" value="Ctrl+N"/>
412     <parameter name="TOT_DESK_FILE_OPEN" value="Ctrl+O"/>
413     <parameter name="#General/Show object(s)" value="Ctrl+Alt+S"/>
414     <parameter name="#General/Hide object(s)" value="Ctrl+Alt+H"/>
415     <parameter name="#Viewers/Back view" value="Ctrl+Alt+B"/>
416     <parameter name="#Viewers/Front view" value="Ctrl+Alt+F"/>
417   </section>
418    <section name="shortcuts:GEOM">
419     <parameter name="Isolines/Increase number" value="Meta+I"/>
420     <parameter name="Isolines/Decrease number" value="Meta+D"/>
421     <parameter name="Transparency/Increase" value="Meta+Y"/>
422     <parameter name="Transparency/Decrease" value="Meta+T"/>
423   </section>
424
425   Empty inModuleActionIDs are ignored.
426
427   nb! For any theQtxAction you wish user be able to assign it to a shortcut,
428   add theQtxAction.ID() to default resource files (you can map it to no key sequence).*/
429   void setShortcutsFromPreferences();
430
431   /*! \brief Writes shortcuts to preference files.
432   \param theShortcuts { moduleID, { inModuleActionID, keySequence }[] }[]. Empty inModuleActionIDs are ignored. */
433   static void saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed);
434
435   /*! Fills myActionAssets from asset files in theLanguage.
436   \param theLanguage If default, fills assets in current language.
437   If an asset in requested language is not found, seeks for the asset EN in and then in FR.
438
439   Asset files must be structured like this:
440   {
441     ...
442     actionID : {
443       "langDependentAssets": {
444         ...
445         lang: {
446           "name": name,
447           "tooltip": tooltip
448         },
449         ...
450       },
451       "iconPath": iconPath
452     },
453     ...
454   }
455   */
456   void setAssetsFromResources(QString theLanguage = QString());
457
458 private:
459   static SUIT_ShortcutMgr* myShortcutMgr;
460
461   /** { moduleID, { inModuleActionID, action[] }[] }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
462   std::map<QString, std::map<QString, std::set<QAction*>>> myActions;
463
464   /** { action, { moduleID, inModuleActionID } }[]. May contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
465   std::map<QAction*, std::pair<QString, QString>> myActionIDs; // To maintain uniqueness of actions and effectively retrieve IDs of registered actions.
466
467   /** Can not contain entries like { <non-root module ID>, { <meta-action ID>, actions[] } }. */
468   SUIT_ShortcutContainer myShortcutContainer;
469
470   /* nb!
471   Sets of moduleIDs and inModuleActionIDs are equal for myActions and myActionIDs.
472   Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer.
473   */
474
475   /* { moduleID, {inModuleActionID, assets}[] }[] */
476   std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> myActionAssets;
477
478   /* {moduleID, assets}[] */
479   mutable std::map<QString, std::shared_ptr<SUIT_ActionAssets>> myModuleAssets;
480 };
481
482
483 /*!
484   \class SUIT_SentenceMatcher
485   \brief Approximate string matcher, treats strings as sentences composed of words.
486 */
487 class SUIT_EXPORT SUIT_SentenceMatcher
488 {
489 public:
490   /*! Default config:
491     Exact word order = false;
492     Fuzzy words = true;
493     Case sensitive = false;
494     Query = ""; // matches nothing.
495   */
496   SUIT_SentenceMatcher();
497
498   void setUseExactWordOrder(bool theOn);
499   void setUseFuzzyWords(bool theOn);
500   void setCaseSensitive(bool theOn);
501   inline bool isCaseSensitive() const { return myIsCaseSensitive; };
502
503   /*! \param theQuery should not be regex. */
504   void setQuery(QString theQuery);
505
506   inline const QString& getQuery() const { return myQuery; };
507
508   /*! \returns match metrics. The metrics >= 0. INF means mismatch.
509   The class is unable to differentiate exact match with some approximate matches! */
510   double match(const QString& theInputString) const;
511
512   /** \brief For debug. */
513   QString toString() const;
514
515 private:
516   static bool makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences);
517   static void makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords);
518
519   /*! \returns number of characters in matched words. The number >= 0. */
520   static int matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive);
521   /*! \returns number of characters in matched words. The number >= 0. */
522   static int matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive);
523
524   /*! \returns number of characters in matched words. The number >= 0. */
525   static int matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive);
526
527   /*! \returns number of characters in matched words. The number >= 0. */
528   static int match(
529     const QString& theInputString,
530     const QStringList& theSentence,
531     bool theCaseSensitive
532   );
533
534   /*! \returns number of characters in matched words. The number >= 0. */
535   static int match(
536     const QString& theInputString,
537     const QList<QStringList>& theSentences,
538     bool theCaseSensitive
539   );
540
541   bool myUseExactWordOrder; // If false, try to match with sentences, composed of query's words in different orders.
542   bool myUseFuzzyWords; // Try to match with sentences, composed of query's truncated words.
543   bool myIsCaseSensitive;
544   QString myQuery;
545
546   QStringList myWords; // It is also original search sentence.
547   QList<QStringList> myPermutatedSentences;
548
549   QStringList myFuzzyWords; // Regexes.
550   QList<QStringList> myFuzzyPermutatedSentences;
551 };
552
553
554 /*!
555   \class SUIT_ActionSearcher
556   \brief Searches in data, provided in action asset files and shortcut preferences.
557 */
558 class SUIT_EXPORT SUIT_ActionSearcher
559 {
560 public:
561   enum MatchField {
562     ID,
563     Name,
564     ToolTip,
565     KeySequence
566   };
567
568   class AssetsAndSearchData
569   {
570   public:
571     AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets = nullptr, double theMatchMetrics = std::numeric_limits<double>::infinity());
572
573     void setMatchMetrics(double theMatchMetrics);
574     double matchMetrics() const { return myMatchMetrics; };
575
576     std::shared_ptr<const SUIT_ActionAssets> myAssets;
577
578     void toJSON(QJsonObject& oJsonObject) const;
579     QString toString() const;
580
581   private:
582     /*! \brief Ideally it should be number of weighted character permutations. Now it is just a number of characters in unmatched words. */
583     double myMatchMetrics;
584   };
585
586   /*! Default config:
587       Included modules' IDs = { ROOT_MODULE_ID };
588       Include disabled actions = false;
589       Fields to match = { Name, Tooltip };
590       Case sensitive = false;
591       Fuzzy matching = true;
592       Query = ""; // matches everything.
593   */
594   SUIT_ActionSearcher();
595   SUIT_ActionSearcher(const SUIT_ActionSearcher&) = delete;
596   SUIT_ActionSearcher& operator=(const SUIT_ActionSearcher&) = delete;
597   virtual ~SUIT_ActionSearcher() = default;
598
599   /*! \returns true, if set of results is changed. */
600   bool setIncludedModuleIDs(std::set<QString> theIncludedModuleIDs);
601
602   /*! \returns true, if set of results is changed. */
603   bool includeDisabledActions(bool theOn);
604   inline bool areDisabledActionsIncluded() const {return myIncludeDisabledActions;};
605
606   /*! \returns true, if set of results is changed. */
607   bool setFieldsToMatch(const std::set<SUIT_ActionSearcher::MatchField>& theFields);
608
609   /*! \returns true, if set of results is changed. */
610   bool setCaseSensitive(bool theOn);
611
612   /*! \returns true, if set of results is changed. */
613   bool setQuery(const QString& theQuery);
614   inline const QString& getQuery() const {return myMatcher.getQuery();};
615
616   const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& getSearchResults() const;
617
618
619 private:
620   /*! \brief Applies filter to all actions, provided in asset files for SUIT_ShortcutMgr.
621   \returns { true, _ } if set of results is changed; { _ , true } if matching metrics is changed for at least one result. */
622   std::pair<bool, bool> filter();
623
624   /*! \brief Applies filter to search results only.
625   \returns { true, _ } if set of results is shrunk; { _ , true } if matching metrics is changed for at least one result. */
626   std::pair<bool, bool> filterResults();
627
628   /*! \brief Applies filter only to actions, which are not in search results.
629   \returns True, if set of results is extended. */
630   bool extendResults();
631
632   double matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets);
633
634   QString toString() const;
635
636
637   std::set<QString> myIncludedModuleIDs;
638   bool myIncludeDisabledActions;
639
640   std::set<SUIT_ActionSearcher::MatchField> myFieldsToMatch;
641   SUIT_SentenceMatcher myMatcher;
642
643   /* { moduleID, {inModuleActionID, assetsAndSearchData}[] }[]. */
644   std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>> mySearchResults;
645 };
646
647
648 #if defined WIN32
649 #pragma warning( default: 4251 )
650 #endif
651
652 #endif