Salome HOME
[bos #35160][EDF](2023-T1) Keyboard shortcuts.
[modules/gui.git] / src / SUIT / SUIT_ShortcutMgr.cxx
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 #include "SUIT_ShortcutMgr.h"
24
25 #include "SUIT_Session.h"
26 #include "SUIT_ResourceMgr.h"
27 #include "SUIT_MessageBox.h"
28
29 #include <QAction>
30 #include <QtxAction.h>
31
32 #include <QApplication>
33 #include <QActionEvent>
34 #include <QKeySequence>
35 #include <QDebug>
36
37 #include <list>
38
39
40 #include <iostream>
41 #include <string>
42 const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: ";
43 bool ShCutDbg(const QString& theString)
44 {
45   if (ShCutDbg()) {
46     std::wcout << SHORTCUT_MGR_LOG_PREFIX << theString.toStdWString() << std::endl;
47     return true;
48   }
49   return false;
50 }
51 bool ShCutDbg(const char* src)
52 {
53   if (ShCutDbg()) {
54     std::wcout << SHORTCUT_MGR_LOG_PREFIX << std::wstring(src, src + strlen(src)) << std::endl;
55     return true;
56   }
57   return false;
58 }
59
60 void Warning(const QString& theString)
61 {
62   std::wcout << theString.toStdWString() << std::endl;
63 }
64 void Warning(const char* src)
65 {
66   std::wcout << std::wstring(src, src + strlen(src)) << std::endl;
67 }
68
69
70 static const QKeySequence NO_KEYSEQUENCE = QKeySequence(QString(""));
71 static const QString NO_ACTION = QString("");
72 /** Separates tokens in action ID. */
73 static const QString TOKEN_SEPARATOR = QString("/");
74 static const QString ROOT_MODULE_ID = QString("");
75 static const QString META_ACTION_PREFIX = QString("#");
76
77 /** Prefix of names of shortcut setting sections in preference files. */
78 static const QString SECTION_NAME_PREFIX = QString("shortcuts");
79
80
81 const QString DEFAULT_LANG = QString("en");
82 const QStringList LANG_PRIORITY_LIST = QStringList({DEFAULT_LANG, "fr"});
83 const QString LANG_SECTION = QString("language");
84
85 /** Prefix of names of sections in preference files with shortcut actions' names. */
86 static const QString SECTION_SHORTCUT_NAMES_PREFIX = QString("shortcut_translations");
87
88
89
90 /**
91  * Uncomment this, to start collecting all shortcuts and action name translations (1),
92  * from instances of QtxActions, if a shortcut or name translation is absent in resource files.
93  *
94  * (1) Set required language in the application settings and run features of interest.
95  * For all actions from these features, their statusTip()s will be dumped to appropriate places in dump files.
96  *
97  * Content of dump files is appended on every run. Files are located in "<APP_DIR>/shortcut_mgr_dev/".
98 */
99 // #define SHORTCUT_MGR_DEVTOOLS
100 #ifdef SHORTCUT_MGR_DEVTOOLS
101 #include <QDir>
102 #include <QFile>
103 #include <QTextStream>
104 #include "QtxMap.h"
105 #include <functional>
106 #ifndef QT_NO_DOM
107 #include <QDomDocument>
108 #include <QDomElement>
109 #include <QDomNode>
110 #endif // QT_NO_DOM
111
112 /*! \brief Generates XML files with appearing in runtime shortcuts,
113     using key sequences of QActions passed to the shortcut manager,
114     and translations, using QAction::statusTip(), of QtxActions passed to the shortcut manager.
115     Content of these files can be easily copied to resource files. */
116 class DevTools
117 {
118 private:
119   DevTools() : myActionsWithInvalidIDsFile(nullptr) {};
120   DevTools(const DevTools&) = delete;
121   void operator=(const DevTools&) = delete;
122
123 public:
124   ~DevTools()
125   {
126     for (const auto& fileNameAndPtrs : myXMLFilesAndDocs) {
127       delete fileNameAndPtrs.second.second;
128       delete fileNameAndPtrs.second.first;
129     }
130   }
131
132   static DevTools* get() {
133     if (!DevTools::instance)
134       DevTools::instance = new DevTools();
135
136     return DevTools::instance;
137   }
138
139   void collectShortcut(
140     const QString& theModuleID,
141     const QString& theInModuleActionID,
142     const QKeySequence& theKeySequence
143   ) {
144     if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
145       auto& moduleShortcuts = myShortcutsOfMetaActions[theModuleID];
146       moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
147
148       const QString fileName = theModuleID + DevTools::SHORTCUTS_OF_META_SUFFIX;
149       const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + ROOT_MODULE_ID;
150       std::map<QString, std::map<QString, QString>> sections;
151       sections[sectionName] = moduleShortcuts;
152       writeToXMLFile(fileName, sections);
153     }
154     else {
155       auto& moduleShortcuts = myShortcuts[theModuleID];
156       moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
157
158       const QString fileName = theModuleID + DevTools::SHORTCUTS_SUFFIX;
159       const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + theModuleID;
160       std::map<QString, std::map<QString, QString>> sections;
161       sections[sectionName] = moduleShortcuts;
162       writeToXMLFile(fileName, sections);
163     }
164   }
165
166   void collectTranslation(
167     const QString& theModuleID,
168     const QString& theInModuleActionID,
169     const QString& theLang,
170     const QString& theActionName
171   ) {
172     if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
173       QString actionID = SUIT_ShortcutMgr::makeActionID(ROOT_MODULE_ID, theInModuleActionID);
174       // { actionID, {lang, actionName}[] } []
175       auto& moduleTranslations = myTranslationsOfMetaActions[theModuleID];
176
177       // {lang, actionName}[]
178       auto& actionTranslations = moduleTranslations[actionID];
179       actionTranslations[theLang] = theActionName;
180
181       const QString fileName = theModuleID + DevTools::TRANSLATIONS_OF_META_SUFFIX;
182       std::map<QString, std::map<QString, QString>> sections;
183       for (auto itAction = moduleTranslations.begin(); itAction != moduleTranslations.end(); itAction++) {
184         const QString sectionName = SECTION_SHORTCUT_NAMES_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + ROOT_MODULE_ID;
185
186         // {lang, actionName}[]
187         std::map<QString, QString>& actionTranslations = itAction->second;
188         std::map<QString, QString>& section = sections[sectionName];
189         for (auto itTranslation = actionTranslations.begin(); itTranslation != actionTranslations.end(); itTranslation++) {
190           section[itTranslation->first] = itTranslation->second;
191         }
192       }
193       writeToXMLFile(fileName, sections);
194     }
195     else {
196       QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID);
197       // { actionID, {lang, actionName}[] } []
198       auto& moduleTranslations = myTranslations[theModuleID];
199       // {lang, actionName}[]
200       auto& actionTranslations = moduleTranslations[actionID];
201       actionTranslations[theLang] = theActionName;
202
203       const QString fileName = theModuleID + DevTools::TRANSLATIONS_SUFFIX;
204       std::map<QString, std::map<QString, QString>> sections;
205       for (auto itAction = moduleTranslations.begin(); itAction != moduleTranslations.end(); itAction++) {
206         const QString sectionName = SECTION_SHORTCUT_NAMES_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + itAction->first;
207
208         // {lang, actionName}[]
209         std::map<QString, QString>& actionTranslations = itAction->second;
210         std::map<QString, QString>& section = sections[sectionName];
211         for (auto itTranslation = actionTranslations.begin(); itTranslation != actionTranslations.end(); itTranslation++) {
212           section[itTranslation->first] = itTranslation->second;
213         }
214       }
215       writeToXMLFile(fileName, sections);
216     }
217   }
218
219   void collectShortcutAndTranslation(const QtxAction* const theAction)
220   {
221     const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theAction->ID());
222     if (moduleIDAndActionID.second.isEmpty())
223       return;
224
225     if (!SUIT_ShortcutMgr::get()->getShortcutContainer().hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second))
226       collectShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second, theAction->shortcut());
227
228     { // Collect action name (translation) in current language, if it is not provided in resource files.
229       SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
230       if (!resMgr) {
231         Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
232         return;
233       }
234
235       const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
236       if (lang.isEmpty())
237         return;
238
239       if (SUIT_ShortcutMgr::getActionNameFromResources(theAction->ID(), lang).first)
240         return;
241
242       collectTranslation(moduleIDAndActionID.first, moduleIDAndActionID.second, lang, theAction->statusTip());
243     }
244   }
245
246 private:
247   /*! Appends new entries to content of dump files. */
248   bool writeToXMLFile(const QString& theFileName, const std::map<QString, std::map<QString, QString>>& theSections)
249   {
250 #ifdef QT_NO_DOM
251   Warning("DebugTools for SUIT_ShortcutMgr can't create XML - #QT_NO_DOM is defined.");
252   return false;
253 #else QT_NO_DOM
254     static const QString DOC_TAG = "document";
255     static const QString SECTION_TAG = "section";
256     static const QString PARAMETER_TAG = "parameter";
257     static const QString NAME_ATTR = "name";
258     static const QString VAL_ATTR = "value";
259
260     const auto itFileAndDoc = myXMLFilesAndDocs.find(theFileName);
261     if (itFileAndDoc == myXMLFilesAndDocs.end()) {
262       const QString fullPath = DevTools::SAVE_PATH + theFileName + ".xml";
263       if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) {
264         myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(nullptr, nullptr);
265         return false;
266       }
267
268       QFile* file = new QFile(fullPath);
269       if (!file->open(QFile::ReadWrite | QIODevice::Text)) {
270         delete file;
271         myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(nullptr, nullptr);
272         return false;
273       }
274
275       QDomDocument* dom = new QDomDocument(DOC_TAG);
276       QTextStream instream(file);
277       dom->setContent(instream.readAll());
278       myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(file, dom);
279     }
280     else if (itFileAndDoc->second.first == nullptr) {
281       return false;
282     }
283
284     const auto fileAndDom = myXMLFilesAndDocs[theFileName];
285     QFile* const file = fileAndDom.first;
286     QDomDocument* const dom = fileAndDom.second;
287
288     QDomElement doc = dom->documentElement();
289     if (doc.isNull()) {
290       *dom = QDomDocument(DOC_TAG);
291       doc = dom->createElement(DOC_TAG);
292       dom->appendChild(doc);
293     }
294
295     static const std::function<void(const std::map<QString, QString>&, QDomDocument&, QDomElement&)> mergeParamsToSection =
296     [&](const std::map<QString, QString>& parameters, QDomDocument& dom, QDomElement& sectionInDom)
297     {
298       for (const std::pair<QString, QString>& nameAndVal : parameters) {
299         const QString& paramName = nameAndVal.first;
300         const QString& paramVal = nameAndVal.second;
301         bool fileHasParam = false;
302         for (QDomElement paramInDom = sectionInDom.firstChildElement(PARAMETER_TAG); !paramInDom.isNull(); paramInDom = paramInDom.nextSiblingElement(PARAMETER_TAG)) {
303           const QString paramNameInDom = paramInDom.attribute(NAME_ATTR);
304           if (paramName == paramNameInDom) {
305             const QString paramValInDom = paramInDom.attribute(VAL_ATTR);
306             if (paramValInDom != paramVal) {
307               QDomElement replaceElement = dom.createElement(PARAMETER_TAG);
308               replaceElement.setAttribute(NAME_ATTR, paramName);
309               replaceElement.setAttribute(VAL_ATTR, paramVal);
310               sectionInDom.replaceChild(replaceElement, paramInDom);
311             }
312
313             fileHasParam = true;
314             break;
315           }
316         }
317         if (!fileHasParam) {
318           QDomElement newParam = dom.createElement(PARAMETER_TAG);
319           newParam.setAttribute(NAME_ATTR, paramName);
320           newParam.setAttribute(VAL_ATTR, paramVal);
321           sectionInDom.insertAfter(newParam, sectionInDom.lastChildElement(PARAMETER_TAG));
322         }
323       }
324       return;
325     };
326
327     for (const auto& sectionNameAndParams : theSections) {
328       const QString& sectionName = sectionNameAndParams.first;
329       const std::map<QString, QString>& parameters = sectionNameAndParams.second;
330
331       bool fileHasSection = false;
332       for (QDomElement sectionInDom = doc.firstChildElement(SECTION_TAG); !sectionInDom.isNull(); sectionInDom = sectionInDom.nextSiblingElement(SECTION_TAG)) {
333         QString sectionNameInDom = sectionInDom.attribute(NAME_ATTR);
334         if (sectionNameInDom == sectionName) {
335           mergeParamsToSection(parameters, *dom, sectionInDom);
336           fileHasSection = true;
337           break;
338         }
339       }
340
341       if (!fileHasSection) {
342         QDomElement newSection = dom->createElement(SECTION_TAG);
343         newSection.setAttribute(NAME_ATTR, sectionName);
344         doc.insertAfter(newSection, doc.lastChildElement(SECTION_TAG));
345         mergeParamsToSection(parameters, *dom, newSection);
346       }
347     }
348
349     file->resize(0);
350     QTextStream outstream(file);
351     outstream << dom->toString();
352
353     return true;
354 #endif // QT_NO_DOM
355   }
356
357 public:
358   void collectAssetsOfActionWithInvalidID(const QAction* const theAction)
359   {
360     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
361     if (!resMgr) {
362       Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
363       return;
364     }
365
366     const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
367     if (lang.isEmpty())
368       return;
369
370     if (!myActionsWithInvalidIDsFile) {
371       const QString fullPath = DevTools::SAVE_PATH + lang + DevTools::INVALID_ID_ACTIONS_SUFFIX + ".csv";
372       if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath()))
373         return;
374
375       myActionsWithInvalidIDsFile = new QFile(fullPath);
376       if (!myActionsWithInvalidIDsFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
377         delete myActionsWithInvalidIDsFile;
378         myActionsWithInvalidIDsFile = nullptr;
379         return;
380       }
381
382       QTextStream ostream(myActionsWithInvalidIDsFile);
383       ostream << "text\t" << "tool tip\t" << "status tip\t" << "key sequence\t" << "QtxAction?\t" << "ID\n";
384       ostream.flush();
385     }
386
387     QTextStream ostream(myActionsWithInvalidIDsFile);
388     const auto aQtxAction = qobject_cast<const QtxAction*>(theAction);
389     ostream << theAction->text() << "\t" << theAction->toolTip() << "\t" << theAction->statusTip() << "\t"
390     << theAction->shortcut().toString() << "\t" << (aQtxAction ? "yes\t" : "no\t") << (aQtxAction ? aQtxAction->ID() + "\n" : "\n");
391     ostream.flush();
392   }
393
394   static const QString SAVE_PATH;
395   static const QString SHORTCUTS_SUFFIX;
396   static const QString SHORTCUTS_OF_META_SUFFIX;
397   static const QString TRANSLATIONS_SUFFIX;
398   static const QString TRANSLATIONS_OF_META_SUFFIX;
399   static const QString INVALID_ID_ACTIONS_SUFFIX;
400
401   static DevTools* instance;
402   static const QString XML_SECTION_TOKENS_SEPARATOR;
403
404   /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
405   std::map<QString, std::map<QString, QString>> myShortcuts;
406
407   /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
408   std::map<QString, std::map<QString, QString>> myShortcutsOfMetaActions;
409
410   /** { moduleID, { actionID, {language, actionName} }[] }[] */
411   std::map<QString, std::map<QString, std::map<QString, QString>>> myTranslations;
412
413   /** { moduleID, { actionID, {language, actionName} }[] }[] */
414   std::map<QString, std::map<QString, std::map<QString, QString>>> myTranslationsOfMetaActions;
415
416 #ifndef QT_NO_DOM
417   // { filename, {file, domDoc} }[]
418   std::map<QString, std::pair<QFile*, QDomDocument*>> myXMLFilesAndDocs;
419 #endif // QT_NO_DOM
420
421   QFile* myActionsWithInvalidIDsFile;
422 };
423 /*static*/ DevTools* DevTools::instance = nullptr;
424 /*static*/ const QString DevTools::SAVE_PATH = "shortcut_mgr_dev/";
425 /*static*/ const QString DevTools::SHORTCUTS_SUFFIX = "_shortcuts";
426 /*static*/ const QString DevTools::SHORTCUTS_OF_META_SUFFIX = "_shortcuts_of_meta_actions";
427 /*static*/ const QString DevTools::TRANSLATIONS_SUFFIX = "_translations";
428 /*static*/ const QString DevTools::TRANSLATIONS_OF_META_SUFFIX = "_translations_of_meta_actions";
429 /*static*/ const QString DevTools::XML_SECTION_TOKENS_SEPARATOR = ":";
430 /*static*/ const QString DevTools::INVALID_ID_ACTIONS_SUFFIX = "_actions_with_invalid_IDs";
431 #endif // SHORTCUT_MGR_DEVTOOLS
432
433
434
435 SUIT_ShortcutContainer::SUIT_ShortcutContainer()
436 {
437   myShortcuts.emplace(ROOT_MODULE_ID, std::map<QKeySequence, QString>());
438   myShortcutsInversed.emplace(ROOT_MODULE_ID, std::map<QString, QKeySequence>());
439 }
440
441 std::set<QString> SUIT_ShortcutContainer::getIDsOfInterferingModules(const QString& theModuleID) const
442 {
443   std::set<QString> IDsOfInterferingModules;
444   if (theModuleID == ROOT_MODULE_ID) {
445     for (const auto& moduleIDAndShortcuts : myShortcuts) {
446       IDsOfInterferingModules.emplace(moduleIDAndShortcuts.first);
447     }
448   }
449   else {
450     IDsOfInterferingModules.emplace(ROOT_MODULE_ID);
451     if (theModuleID != ROOT_MODULE_ID)
452       IDsOfInterferingModules.emplace(theModuleID);
453   }
454   return IDsOfInterferingModules;
455 }
456
457 std::set<QString> SUIT_ShortcutContainer::getIDsOfAllModules() const
458 {
459   std::set<QString> res;
460   for (const auto& moduleIDAndShortcuts : myShortcutsInversed) {
461     res.emplace(moduleIDAndShortcuts.first);
462   }
463   return res;
464 }
465
466 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::setShortcut(QString theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
467 {
468   if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) {
469     ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid module ID = \"" + theModuleID + "\".");
470     return std::set<std::pair<QString, QString>>();
471   }
472
473   if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) {
474     ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid in-module action ID = \"" + theInModuleActionID + "\".");
475     return std::set<std::pair<QString, QString>>();
476   }
477
478   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
479     theModuleID = ROOT_MODULE_ID;
480
481   auto itModuleShortcuts = myShortcuts.find(theModuleID);
482   auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
483   if (itModuleShortcuts == myShortcuts.end()) {
484     itModuleShortcuts = myShortcuts.emplace(theModuleID, std::map<QKeySequence, QString>()).first;
485     itModuleShortcutsInversed = myShortcutsInversed.emplace(theModuleID, std::map<QString, QKeySequence>()).first;
486   }
487
488   std::map<QKeySequence, QString>& moduleShortcuts = itModuleShortcuts->second;
489   std::map<QString, QKeySequence>& moduleShortcutsInversed = itModuleShortcutsInversed->second;
490
491   if (theKeySequence.isEmpty()) {
492     // Disable shortcut.
493
494     auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
495     if (itShortcutInversed == moduleShortcutsInversed.end()) {
496       // No key sequence was mapped to the action earlier.
497       // Set disabled shortcut.
498       moduleShortcutsInversed.emplace(theInModuleActionID, NO_KEYSEQUENCE);
499       return std::set<std::pair<QString, QString>>();
500     }
501     else /* if keySequence was mapped to the action earlier. */ {
502       QKeySequence& keySequence = itShortcutInversed->second;
503
504       moduleShortcuts.erase(keySequence);
505       keySequence = NO_KEYSEQUENCE;
506
507       return std::set<std::pair<QString, QString>>();
508     }
509   }
510
511   { // Check if the shortcut is already set.
512     const auto itShortcut = moduleShortcuts.find(theKeySequence);
513     if (itShortcut != moduleShortcuts.end()) {
514       if (itShortcut->second == theInModuleActionID) {
515         // The shortcut was set earlier. Nothing to change.
516         return std::set<std::pair<QString, QString>>();
517       }
518     }
519   }
520
521   auto conflictingActionIDs = std::set<std::pair<QString, QString>>();
522   { // Look for conflicting shortcuts with the same key sequence from interfering modules.
523     std::set<QString> IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID);
524     for (const QString& IDOfInterferingModule : IDsOfInterferingModules) {
525       std::map<QKeySequence, QString>& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule);
526       auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence);
527       if (itConflictingShortcut != shortcutsOfInterferingModule.end()) {
528         const QString& conflictingActionID = itConflictingShortcut->second;
529
530         conflictingActionIDs.insert(std::pair<QString, QString>(IDOfInterferingModule, conflictingActionID));
531
532         if (theOverride) {
533           // Disable conflicting shortcuts.
534           std::map<QString, QKeySequence>& shortcutsOfInterferingModuleInversed = myShortcutsInversed.at(IDOfInterferingModule);
535           shortcutsOfInterferingModuleInversed[conflictingActionID] = NO_KEYSEQUENCE;
536           shortcutsOfInterferingModule.erase(itConflictingShortcut);
537         }
538       }
539     }
540
541     if (!theOverride && !conflictingActionIDs.empty())
542       return conflictingActionIDs;
543   }
544
545   { // Ensure, that the module has only shortcut for the action ID.
546     auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
547     if (itShortcutInversed != moduleShortcutsInversed.end()) {
548       // Redefine key sequence for existing action.
549
550       QKeySequence& keySequence = itShortcutInversed->second;
551
552       moduleShortcuts.erase(keySequence);
553       moduleShortcuts[theKeySequence] = theInModuleActionID;
554
555       keySequence = theKeySequence;
556     }
557     else /* if the action has not been added earlier. */ {
558       moduleShortcuts[theKeySequence] = theInModuleActionID;
559       moduleShortcutsInversed[theInModuleActionID] = theKeySequence;
560     }
561   }
562
563   return conflictingActionIDs;
564 }
565
566 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::getConflicts(
567   QString theModuleID,
568   const QString& theInModuleActionID,
569   const QKeySequence& theKeySequence
570 ) const
571 {
572   if (theKeySequence.isEmpty())
573     return std::set<std::pair<QString, QString>>();
574
575   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
576     theModuleID = ROOT_MODULE_ID;
577
578   { // Check if the shortcut is set.
579     const auto itModuleShortcuts = myShortcuts.find(theModuleID);
580     if (itModuleShortcuts != myShortcuts.end()) {
581       const std::map<QKeySequence, QString>& moduleShortcuts = itModuleShortcuts->second;
582       const auto itShortcut = moduleShortcuts.find(theKeySequence);
583       if (itShortcut != moduleShortcuts.end()) {
584         if (itShortcut->second == theInModuleActionID) {
585           // The shortcut is set => no conflicts.
586           return std::set<std::pair<QString, QString>>();
587         }
588       }
589     }
590   }
591
592   auto conflictingActionIDs = std::set<std::pair<QString, QString>>();
593   { // Look for conflicting shortcuts with the same key sequence from interfering modules.
594     std::set<QString> IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID);
595     for (const QString& IDOfInterferingModule : IDsOfInterferingModules) {
596       const std::map<QKeySequence, QString>& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule);
597       const auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence);
598       if (itConflictingShortcut != shortcutsOfInterferingModule.end()) {
599         const QString& conflictingActionID = itConflictingShortcut->second;
600         conflictingActionIDs.insert(std::pair<QString, QString>(IDOfInterferingModule, conflictingActionID));
601       }
602     }
603   }
604   return conflictingActionIDs;
605 }
606
607 const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID, const QString& theInModuleActionID) const
608 {
609   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
610     theModuleID = ROOT_MODULE_ID;
611
612   const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
613   if (itModuleShortcutsInversed == myShortcutsInversed.end())
614     return NO_KEYSEQUENCE;
615
616   const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
617   const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
618   if (itShortcutInversed == moduleShortcutsInversed.end())
619     return NO_KEYSEQUENCE;
620
621   return itShortcutInversed->second;
622 }
623
624 bool SUIT_ShortcutContainer::hasShortcut(QString theModuleID, const QString& theInModuleActionID) const
625 {
626   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
627     theModuleID = ROOT_MODULE_ID;
628
629   const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
630   if (itModuleShortcutsInversed == myShortcutsInversed.end())
631     return false;
632
633   const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
634   const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
635   if (itShortcutInversed == moduleShortcutsInversed.end())
636     return false;
637
638   return true;
639 }
640
641 const std::map<QString, QKeySequence>& SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID) const
642 {
643   static const std::map<QString, QKeySequence> EMPTY_RES;
644   const auto it = myShortcutsInversed.find(theModuleID);
645   if (it == myShortcutsInversed.end())
646     return EMPTY_RES;
647
648   return it->second;
649 }
650
651 const std::map<QString, QKeySequence> SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID, const QString& theActionIDPrefix) const
652 {
653   const auto it = myShortcutsInversed.find(theModuleID);
654   if (it == myShortcutsInversed.end())
655     return std::map<QString, QKeySequence>();
656
657   std::map<QString, QKeySequence> shortcutsInversed;
658   for (const auto& existingShortcut : it->second) {
659     if (existingShortcut.first.startsWith(theActionIDPrefix))
660       shortcutsInversed[existingShortcut.first] = existingShortcut.second;
661   }
662   return shortcutsInversed;
663 }
664
665 QString SUIT_ShortcutContainer::toString() const
666 {
667   QString text;
668   text += "Shortcuts inversed:\n";
669   for (auto it = myShortcutsInversed.begin(); it != myShortcutsInversed.end(); it++) {
670     const QString& moduleID = it->first;
671     const auto& moduleShortcuts = it->second;
672     text += (it == myShortcutsInversed.begin() ? "\"" : "\n\"")  + moduleID + "\"";
673     for (const auto& shortcut : moduleShortcuts) {
674       text += "\n\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\"";
675     }
676   }
677   text += "\nShortcuts:\n";
678   for (auto it = myShortcuts.begin(); it != myShortcuts.end(); it++) {
679     const QString& moduleID = it->first;
680     const auto& moduleShortcuts = it->second;
681     text += (it == myShortcuts.begin() ? "\"" : "\n\"")  + moduleID + "\"";
682     for (const auto& shortcut : moduleShortcuts) {
683       text += "\n\t\"" + shortcut.first.toString() + "\"\t\"" + shortcut.second + "\"";
684     }
685   }
686   return text;
687 }
688
689 std::map<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge(
690   const SUIT_ShortcutContainer& theOther,
691   bool theOverride,
692   bool theTreatAbsentIncomingAsDisabled
693 ) {
694   std::map<QString, std::map<QString, QKeySequence>> changesOfThis;
695
696   for (const auto& shortcutsInversedOfOtherPair : theOther.myShortcutsInversed) {
697     const QString& moduleIDOther = shortcutsInversedOfOtherPair.first;
698     const auto& shortcutsInversedOther = shortcutsInversedOfOtherPair.second;
699     for (const auto& shortcutInversedOther : shortcutsInversedOther) {
700       const QString& inModuleActionIDOther = shortcutInversedOther.first;
701       const QKeySequence& keySequenceOther = shortcutInversedOther.second;
702       if (theOverride) {
703         if (hasShortcut(moduleIDOther, inModuleActionIDOther) && getKeySequence(moduleIDOther, inModuleActionIDOther) == keySequenceOther) {
704           continue;
705         }
706         else /* if this has no shortcut for the action  or  if this has a shortcut for the action, but the key sequence differs. */ {
707           const auto disabledActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, true);
708           changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
709           for (const auto& disabledActionOfThis : disabledActionsOfThis) {
710             changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = NO_KEYSEQUENCE;
711           }
712         }
713       }
714       else /* if (!theOverride) */ {
715         if (hasShortcut(moduleIDOther, inModuleActionIDOther))
716           continue;
717         else {
718           const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false);
719           if (conflictingActionsOfThis.empty()) {
720             changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
721           }
722           else /* if this has no shortcut for the action, but the incoming key sequence conflicts with others shortcuts. */ {
723             changesOfThis[moduleIDOther][inModuleActionIDOther] = NO_KEYSEQUENCE;
724           }
725         }
726       }
727     }
728   }
729
730   if (theOverride && theTreatAbsentIncomingAsDisabled) {
731     // Disable existing shortcuts, if they are absent in theOther.
732     for (auto& shortcutsInversedPair : myShortcutsInversed) {
733       const QString& moduleID = shortcutsInversedPair.first;
734       auto& moduleShortcutsInversed = shortcutsInversedPair.second;
735       for (auto& inversedShortcut : moduleShortcutsInversed) {
736         if (theOther.hasShortcut(moduleID, inversedShortcut.first))
737           continue;
738
739         if (inversedShortcut.second.isEmpty())
740           continue; // Existing shortcut is already disabled.
741
742         auto itShortcutsPair = myShortcuts.find(moduleID);
743         if (itShortcutsPair == myShortcuts.end())
744           continue; // The check is an overhead in an error-free designed class, but let be just in case.
745
746         auto& moduleShortcuts = itShortcutsPair->second;
747         moduleShortcuts.erase(inversedShortcut.second);
748         inversedShortcut.second = NO_KEYSEQUENCE;
749         changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE;
750       }
751     }
752   }
753
754   return changesOfThis;
755 }
756
757
758 SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = nullptr;
759
760 SUIT_ShortcutMgr::SUIT_ShortcutMgr()
761 : QObject()
762 {
763   qApp->installEventFilter( this );
764 }
765
766 SUIT_ShortcutMgr::~SUIT_ShortcutMgr()
767 {
768   qApp->removeEventFilter( this );
769 }
770
771 /*static*/ void SUIT_ShortcutMgr::Init()
772 {
773   if( myShortcutMgr == nullptr) {
774     myShortcutMgr = new SUIT_ShortcutMgr();
775     myShortcutMgr->setShortcutsFromPreferences();
776   }
777 }
778
779 /*static*/ SUIT_ShortcutMgr* SUIT_ShortcutMgr::get()
780 {
781   Init();
782   return myShortcutMgr;
783 }
784
785 /*static*/ bool SUIT_ShortcutMgr::isKeySequenceValid(const QKeySequence& theKeySequence)
786 {
787   // TODO Perform check whether a key sequence is platform-compatible.
788   return true;
789 }
790
791 /*static*/ std::pair<bool, QKeySequence> SUIT_ShortcutMgr::toKeySequenceIfValid(const QString& theKeySequenceString)
792 {
793   auto res = std::pair<bool, QKeySequence>(false, QKeySequence());
794
795   try {
796     res.second = QKeySequence::fromString(theKeySequenceString);
797     if (res.second.toString() != theKeySequenceString)
798       return std::pair<bool, QKeySequence>(false, QKeySequence());
799
800     if (!SUIT_ShortcutMgr::isKeySequenceValid(res.second))
801       return std::pair<bool, QKeySequence>(false, QKeySequence());
802   }
803   catch (...) {
804     return std::pair<bool, QKeySequence>(false, QKeySequence());
805   }
806
807   res.first = true;
808   return res;
809 }
810
811 /*static*/ bool SUIT_ShortcutMgr::isModuleIDValid(const QString& theModuleID)
812 {
813   if (theModuleID.contains(TOKEN_SEPARATOR))
814     return false;
815
816   if (theModuleID.simplified() != theModuleID)
817     return false;
818
819   return true;
820 }
821
822 /*static*/ bool SUIT_ShortcutMgr::isInModuleActionIDValid(const QString& theInModuleActionID)
823 {
824   QStringList tokens = theInModuleActionID.split(TOKEN_SEPARATOR);
825    for (QStringList::size_type i = 0; i < tokens.length(); i++) {
826     const QString simplifiedToken = tokens[i].simplified();
827     if (
828       simplifiedToken.isEmpty() ||
829       simplifiedToken != tokens[i] ||
830       i == 0 && simplifiedToken == META_ACTION_PREFIX ||
831       i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
832     )
833       return false;
834   }
835   return true;
836 }
837
838 /*static*/ bool SUIT_ShortcutMgr::isInModuleMetaActionID(const QString& theInModuleActionID)
839 {
840   return theInModuleActionID.startsWith(META_ACTION_PREFIX);
841 }
842
843 /*static*/ std::pair<QString, QString> SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(const QString& theActionID)
844 {
845   QStringList tokens = theActionID.split(TOKEN_SEPARATOR);
846   if (tokens.length() < 2)
847     return std::pair<QString, QString>();
848
849   auto res = std::pair<QString, QString>();
850
851   if (tokens[0].simplified() != tokens[0])
852     return std::pair<QString, QString>();
853
854   res.first = tokens[0];
855   tokens.pop_front();
856
857   for (QStringList::size_type i = 0; i < tokens.length(); i++) {
858     const QString simplifiedToken = tokens[i].simplified();
859     if (
860       simplifiedToken.isEmpty() ||
861       simplifiedToken != tokens[i] ||
862       i == 0 && simplifiedToken == META_ACTION_PREFIX ||
863       i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
864     )
865       return std::pair<QString, QString>();
866   }
867   res.second = tokens.join(TOKEN_SEPARATOR);
868
869   return res;
870 }
871
872 /*static*/ bool SUIT_ShortcutMgr::isActionIDValid(const QString& theActionID)
873 {
874   return !SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID).second.isEmpty();
875 }
876
877 /*static*/ QString SUIT_ShortcutMgr::makeActionID(const QString& theModuleID, const QString& theInModuleActionID)
878 {
879   if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID))
880     return QString();
881
882   if (!isInModuleActionIDValid(theInModuleActionID))
883     return QString();
884
885   return theModuleID + TOKEN_SEPARATOR + theInModuleActionID;
886 }
887
888 /*static*/ void SUIT_ShortcutMgr::fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly)
889 {
890   ShCutDbg() && ShCutDbg("Retrieving preferences from resources.");
891
892   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
893   if (!resMgr) {
894     Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
895     return;
896   }
897
898   const auto resMgrWorkingModeBefore = resMgr->workingMode();
899   if (theDefaultOnly)
900     resMgr->setWorkingMode(QtxResourceMgr::IgnoreUserValues);
901
902   /** List of modules with invalid IDs. */
903   QStringList invalidModuleIDs;
904
905   /** { moduleID, {inModuleActionID, keySequence}[] }[] */
906   std::map<QString, std::list<std::pair<QString, QString>>> invalidShortcuts;
907
908   /**
909    * Shortcuts, which have not been set, because they are in conflict with previously parsed shortcuts.
910    * { moduleID, {inModuleActionID, keySequence}[] }[] */
911   std::map<QString, std::list<std::pair<QString, QKeySequence>>> conflicts;
912
913   // Resource manager strips leading and trailing whitespaces from subsections' names.
914   // And then it is not able to retrieve parametes from that subsections,
915   // because parsed subsection names differ from the ones in resource file.
916   // Anyway, it does not affect operability of ShortcutMgr.
917   QStringList moduleIDs = resMgr->subSections(SECTION_NAME_PREFIX, true);
918   if (ShCutDbg()) {
919     if (moduleIDs.isEmpty())
920       ShCutDbg("No discovered shortcut modules.");
921     else
922       ShCutDbg("Discovered shortcut modules: \"" + moduleIDs.join("\", \"") + ".");
923   }
924   moduleIDs.push_front(ROOT_MODULE_ID); // Resource manager filters out empty section suffices.
925   moduleIDs.removeDuplicates();
926
927   for (size_t i = 0; i < moduleIDs.size(); i++) {
928     const auto& moduleID = moduleIDs[i];
929     if (!SUIT_ShortcutMgr::isModuleIDValid(moduleID)) {
930       invalidModuleIDs.push_back(moduleID);
931       continue;
932     }
933
934     const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
935     QStringList moduleActionIDs = resMgr->parameters(sectionName);
936
937     for(const QString& inModuleActionID : moduleActionIDs) {
938       QString keySequenceString = QString("");
939       resMgr->value(sectionName, inModuleActionID, keySequenceString);
940       const auto keySequence = SUIT_ShortcutMgr::toKeySequenceIfValid(keySequenceString);
941
942       ShCutDbg() && ShCutDbg("Shortcut discovered: \"" + moduleID + "\"\t\"" + inModuleActionID + "\"\t\"" + keySequenceString + "\".");
943
944       if (
945         !SUIT_ShortcutMgr::isInModuleActionIDValid(inModuleActionID) ||
946         !keySequence.first ||
947         SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != ROOT_MODULE_ID
948       ) {
949         std::list<std::pair<QString, QString>>& moduleInvalidShortcuts = invalidShortcuts[moduleID];
950         moduleInvalidShortcuts.push_back(std::pair<QString, QString>(inModuleActionID, keySequenceString));
951         continue;
952       }
953
954       const auto shortcutConflicts = theContainer.setShortcut(moduleID, inModuleActionID, keySequence.second, false /*override*/);
955       if (!shortcutConflicts.empty()) {
956         auto& moduleConflicts = conflicts[moduleID];
957         moduleConflicts.push_back(std::pair<QString, QKeySequence>(inModuleActionID, keySequence.second));
958       }
959     }
960   }
961
962   if (!invalidModuleIDs.isEmpty() || !invalidShortcuts.empty() || !conflicts.empty())
963   { // Prepare report and show warning.
964     QString report;
965     if (!invalidModuleIDs.isEmpty()) {
966       report += tr("Invalid module IDs") + ":";
967       for (const QString& invalidModuleID : invalidModuleIDs) {
968         report += "\n\t\"" + invalidModuleID + "\"" ;
969       }
970     }
971
972     if (!invalidShortcuts.empty()) {
973       if (!report.isEmpty())
974         report += "\n\n";
975
976       report += tr("Invalid shortcuts") + ":";
977       for (const auto& moduleAndShortcuts : invalidShortcuts) {
978         report += "\n\t\"" + moduleAndShortcuts.first + "\"";
979         const std::list<std::pair<QString, QString>>& moduleShortcuts = moduleAndShortcuts.second;
980         for (const auto& shortcut : moduleShortcuts) {
981           report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second + "\"";
982         }
983       }
984     }
985
986     if (!conflicts.empty()) {
987       if (!report.isEmpty())
988         report += "\n\n";
989
990       report += tr("These shortcuts have not been set to theContainer, because they conflict with previously parsed ones") + ":";
991       for (const auto& moduleAndShortcuts : conflicts) {
992         report += "\n\t\"" + moduleAndShortcuts.first + "\"";
993
994         const std::list<std::pair<QString, QKeySequence>>& moduleShortcuts = moduleAndShortcuts.second;
995         for (const auto& shortcut : moduleShortcuts) {
996           report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\"";
997         }
998       }
999     }
1000
1001     report += "\n.";
1002
1003     const auto text = tr("Invalid shortcuts in preferences");
1004     const auto informativeText = tr("Fix the following entries in the preference files manually");
1005     if (!theDefaultOnly) {
1006       // If user preferences are accounted, show warning in UI.
1007       SUIT_Application* app = SUIT_Session::session()->activeApplication();
1008       if (app && app->desktop()) {
1009         // Is not compiled without cast or with static_cast<QWidget*>.
1010         QMessageBox msgBox((QWidget*)app->desktop());
1011         msgBox.setIcon(QMessageBox::Warning);
1012         msgBox.setTextFormat(Qt::RichText);
1013         msgBox.setText("<b>" + text + "</b>");
1014         msgBox.setInformativeText(informativeText + ":");
1015         msgBox.setWindowFlags(Qt::WindowType::Popup);
1016         msgBox.setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
1017         msgBox.setDetailedText(report);
1018         msgBox.setStandardButtons(QMessageBox::Ok);
1019         msgBox.setDefaultButton(QMessageBox::Ok);
1020         msgBox.setMinimumWidth(600);
1021         msgBox.exec();
1022       }
1023     }
1024     Warning(text + ". " + informativeText + ":\n" + report);
1025   }
1026
1027   if (theDefaultOnly)
1028     resMgr->setWorkingMode(resMgrWorkingModeBefore);
1029
1030   ShCutDbg() && ShCutDbg("theContainer holds following shortcuts:\n" + theContainer.toString());
1031 }
1032
1033 /*static*/ std::pair<bool, QString> SUIT_ShortcutMgr::getActionNameFromResources(const QString& theActionID, QString theLanguage)
1034 {
1035   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1036   if (!resMgr) {
1037     Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1038     return std::pair<bool, QString>(false, QString());
1039   }
1040
1041   if (theLanguage.isEmpty())
1042     theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
1043
1044   if (theLanguage.isEmpty())
1045     return std::pair<bool, QString>(false, QString());
1046
1047   QStringList actionIDs = resMgr->subSections(SECTION_SHORTCUT_NAMES_PREFIX, false);
1048   if (actionIDs.indexOf(theActionID) == -1)
1049     return std::pair<bool, QString>(false, QString());
1050
1051   const QString sectionName = SECTION_SHORTCUT_NAMES_PREFIX + resMgr->sectionsToken() + theActionID;
1052   QStringList availableActionNameLangs = resMgr->parameters(sectionName);
1053   if (availableActionNameLangs.indexOf(theLanguage) == -1)
1054     return std::pair<bool, QString>(false, QString());
1055
1056   QString actionName;
1057   const bool nameInCurLangExists = resMgr->value(sectionName, theLanguage, actionName);
1058
1059   if (!nameInCurLangExists)
1060     return std::pair<bool, QString>(false, QString());
1061
1062   return std::pair<bool, QString>(true, actionName);
1063 }
1064
1065
1066 void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction)
1067 {
1068   const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1069   const QString& moduleID = moduleIDAndActionID.first;
1070   const QString& inModuleActionID = moduleIDAndActionID.second;
1071
1072   if (inModuleActionID.isEmpty()) {
1073     ShCutDbg() && ShCutDbg("Attempt to register an action \"" + theAction->toolTip() + "\" with invalid ID \"" + theActionID + "\".");
1074     if (theAction->shortcut() != NO_KEYSEQUENCE)
1075       theAction->setShortcut(NO_KEYSEQUENCE);
1076
1077     return;
1078   }
1079
1080   { // If an action with the same memory address was registered earlier,
1081     // clear all data about it to start registering procedure from scratch.
1082     auto itPreviousModuleAndActionID = myActionIDs.find(theAction);
1083     if (itPreviousModuleAndActionID != myActionIDs.end()) {
1084       // Clear the data from myActions.
1085       const auto& previousModuleAndActionID = itPreviousModuleAndActionID->second;
1086       auto itActions = myActions.find(previousModuleAndActionID.first);
1087       if (itActions != myActions.end()) {
1088         std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1089         auto itModuleActions = moduleActions.find(previousModuleAndActionID.second);
1090         if (itModuleActions != moduleActions.end()) {
1091           std::set<QAction*>& registeredActions = itModuleActions->second;
1092           registeredActions.erase(theAction);
1093         }
1094       }
1095
1096       myActionIDs.erase(itPreviousModuleAndActionID);
1097     }
1098   }
1099
1100   auto itActions = myActions.find(moduleID);
1101   if (itActions == myActions.end()) {
1102     itActions = myActions.emplace(moduleID, std::map<QString, std::set<QAction*>>()).first;
1103   }
1104
1105   std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1106   auto itModuleActions = moduleActions.find(inModuleActionID);
1107   if (itModuleActions != moduleActions.end()) {
1108     std::set<QAction*>& registeredActions = itModuleActions->second;
1109     const bool actionIsNew = registeredActions.emplace(theAction).second;
1110     if (actionIsNew)
1111       myActionIDs[theAction] = moduleIDAndActionID;
1112   }
1113   else {
1114     std::set<QAction*>& registeredActions = moduleActions[inModuleActionID];
1115     registeredActions.emplace(theAction);
1116     myActionIDs[theAction] = moduleIDAndActionID;
1117   }
1118
1119   connect(theAction, SIGNAL(destroyed(QObject*)), this, SLOT (onActionDestroyed(QObject*)));
1120
1121   if (myShortcutContainer.hasShortcut(moduleID, inModuleActionID)) {
1122     const QKeySequence& keySequence = getKeySequence(moduleID, inModuleActionID);
1123     theAction->setShortcut(keySequence);
1124   }
1125   else {
1126     ShCutDbg(
1127       "Action with ID \"" +
1128       (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) +
1129       "\" is not added to default resource files."
1130     );
1131     auto conflicts = myShortcutContainer.setShortcut(moduleID, inModuleActionID, theAction->shortcut(), false);
1132     if (!conflicts.empty())
1133       theAction->setShortcut(NO_KEYSEQUENCE); // Unbind any key sequence, if it was bound outside of the class and interferes with other shortcuts.
1134   }
1135 }
1136
1137 void SUIT_ShortcutMgr::registerAction(QtxAction* theAction)
1138 {
1139   registerAction(theAction->ID(), theAction);
1140 }
1141
1142 std::set<QAction*> SUIT_ShortcutMgr::getActions(const QString& theModuleID, const QString& theInModuleActionID) const
1143 {
1144   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
1145     std::set<QAction*> actions;
1146     for (const auto& actionAndID : myActionIDs) {
1147       if (actionAndID.second.second == theInModuleActionID)
1148         actions.emplace(actionAndID.first);
1149     }
1150     return actions;
1151   }
1152   else {
1153     const auto itActions = myActions.find(theModuleID);
1154     if (itActions == myActions.end())
1155       return std::set<QAction*>();
1156
1157     const std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1158     const auto itModuleActions = moduleActions.find(theInModuleActionID);
1159     if (itModuleActions == moduleActions.end())
1160       return std::set<QAction*>();
1161
1162     return itModuleActions->second;
1163   }
1164 }
1165
1166 std::pair<QString, QString> SUIT_ShortcutMgr::getModuleIDAndInModuleID(const QAction* theAction) const {
1167   const auto it = myActionIDs.find(const_cast<QAction*>(theAction));
1168   if (it == myActionIDs.end())
1169     return std::pair<QString, QString>();
1170
1171   return it->second;
1172 }
1173
1174 bool SUIT_ShortcutMgr::hasAction(const QAction* theAction) const
1175 {
1176   return myActionIDs.find(const_cast<QAction*>(theAction)) != myActionIDs.end();
1177 }
1178
1179 QString SUIT_ShortcutMgr::getActionID(const QAction* theAction) const
1180 {
1181   const auto it = myActionIDs.find(const_cast<QAction*>(theAction));
1182   if (it == myActionIDs.end())
1183     return QString();
1184
1185   return SUIT_ShortcutMgr::makeActionID(it->second.first, it->second.second);
1186 }
1187
1188 void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable) const
1189 {
1190   const auto itModuleActions = myActions.find(theModuleID);
1191   if (itModuleActions == myActions.end())
1192     return;
1193
1194   SUIT_Application* app = SUIT_Session::session()->activeApplication();
1195   if (!app)
1196     return;
1197
1198   const std::map<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
1199   for (const auto& idAndActions : moduleActions) {
1200     const std::set<QAction*>& actions = idAndActions.second;
1201     for (QAction* const action : actions) {
1202       if (action->parentWidget() == (QWidget*)app->desktop()) // Is not compiled without cast or with static_cast<QWidget*>.
1203         action->setEnabled(theEnable);
1204     }
1205   }
1206 }
1207
1208 void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1209 {
1210   SUIT_Application* app = SUIT_Session::session()->activeApplication();
1211   if (!app)
1212     return;
1213
1214   for (const std::pair<QAction*, std::pair<QString, QString>>& actionAndID : myActionIDs) {
1215     QAction* const action = actionAndID.first;
1216     // Is not compiled without cast or with static_cast<QWidget*>.
1217     if (action->parentWidget() == (QWidget*)app->desktop()) {
1218       const QString& inModuleActionID = actionAndID.second.second;
1219       if (inModuleActionID.startsWith(theInModuleActionIDPrefix))
1220         action->setEnabled(theEnable);
1221     }
1222   }
1223 }
1224
1225 void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1226 {
1227   setActionsWithPrefixInIDEnabled(theInModuleActionIDPrefix, theEnable);
1228 }
1229
1230 void SUIT_ShortcutMgr::rebindActionsToKeySequences() const
1231 {
1232   ShCutDbg() && ShCutDbg("SUIT_ShortcutMgr::rebindActionsToKeySequences()");
1233   for (const std::pair<QAction*, std::pair<QString, QString>>& actionAndID : myActionIDs) {
1234     actionAndID.first->setShortcut(getKeySequence(actionAndID.second.first, actionAndID.second.second));
1235   }
1236 }
1237
1238 void SUIT_ShortcutMgr::updateShortcuts() const
1239 {
1240   rebindActionsToKeySequences();
1241 }
1242
1243 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride)
1244 {
1245   const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1246   const QString& moduleID = moduleIDAndActionID.first;
1247   const QString& inModuleActionID = moduleIDAndActionID.second;
1248
1249   if (inModuleActionID.isEmpty()) {
1250     ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid action ID \"" + theActionID + "\".");
1251     return std::set<std::pair<QString, QString>>();
1252   }
1253
1254   return setShortcutNoIDChecks(moduleID, inModuleActionID, theKeySequence, theOverride);
1255 }
1256
1257 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
1258 {
1259   if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) {
1260     ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid module ID \"" + theModuleID + "\".");
1261     return std::set<std::pair<QString, QString>>();
1262   }
1263
1264   if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) {
1265     ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid in-module action ID \"" + theInModuleActionID + "\".");
1266     return std::set<std::pair<QString, QString>>();
1267   }
1268
1269   return setShortcutNoIDChecks(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1270 }
1271
1272 const SUIT_ShortcutContainer& SUIT_ShortcutMgr::getShortcutContainer() const
1273 {
1274   return myShortcutContainer;
1275 }
1276
1277 void SUIT_ShortcutMgr::mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride, bool theTreatAbsentIncomingAsDisabled)
1278 {
1279   ShCutDbg() && ShCutDbg("ShortcutMgr merges shortcut container...");
1280   const auto changes = myShortcutContainer.merge(theContainer, theOverride, theTreatAbsentIncomingAsDisabled);
1281   ShCutDbg() && ShCutDbg("ShortcutMgr keeps following shortcuts:\n" + myShortcutContainer.toString());
1282
1283   // Turn off hotkeys for disabled shortcuts.
1284   for (const auto& moduleIDAndChanges : changes) {
1285     const QString& moduleID = moduleIDAndChanges.first;
1286     const auto& moduleChanges = moduleIDAndChanges.second;
1287     for (const std::pair<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
1288       if (modifiedShortcut.second == NO_KEYSEQUENCE) {
1289         const std::set<QAction*> actions = getActions(moduleID, modifiedShortcut.first);
1290         for (QAction* const action : actions) {
1291           action->setShortcut(NO_KEYSEQUENCE);
1292         }
1293       }
1294     }
1295   }
1296
1297   // Turn on hotkeys for enabled shortcuts.
1298   for (const auto& moduleIDAndChanges : changes) {
1299     const QString& moduleID = moduleIDAndChanges.first;
1300     const auto& moduleChanges = moduleIDAndChanges.second;
1301     for (const std::pair<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
1302       if (modifiedShortcut.second != NO_KEYSEQUENCE) {
1303         const std::set<QAction*> actions = getActions(moduleID, modifiedShortcut.first);
1304         for (QAction* const action : actions) {
1305           action->setShortcut(modifiedShortcut.second);
1306         }
1307       }
1308     }
1309   }
1310
1311   SUIT_ShortcutMgr::saveShortcutsToPreferences(changes);
1312 }
1313
1314 QKeySequence SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const
1315 {
1316   return myShortcutContainer.getKeySequence(theModuleID, theInModuleActionID);
1317 }
1318
1319 const std::map<QString, QKeySequence>& SUIT_ShortcutMgr::getModuleShortcutsInversed(const QString& theModuleID) const
1320 {
1321   return myShortcutContainer.getModuleShortcutsInversed(theModuleID);
1322 }
1323
1324 std::set<QString> SUIT_ShortcutMgr::getShortcutModuleIDs() const
1325 {
1326   return myShortcutContainer.getIDsOfAllModules();
1327 }
1328
1329 std::set<QString> SUIT_ShortcutMgr::getIDsOfInterferingModules(const QString& theModuleID) const
1330 {
1331   return myShortcutContainer.getIDsOfInterferingModules(theModuleID);
1332 }
1333
1334 QString SUIT_ShortcutMgr::getModuleName(const QString& theModuleID) const
1335 {
1336   return theModuleID == ROOT_MODULE_ID ? tr("General") : theModuleID;
1337
1338   /*
1339   // TODO ? The SUIT_ShortcutMgr should be renamed and moved to CAM folder.
1340   // Because the CAM_Application class is the closest to SUIT_Application in the inheritance hierarchy,
1341   // who has concept of application module. Enabling this chunk of code, due to the presence of CAM_Application,
1342   // requires to heap up cyclic dependencies in compilation units.
1343   // Or it is reasond to add like-action-name-resources to preference files.
1344
1345   SUIT_Application* aSUIT_Application = SUIT_Session::session()->activeApplication();
1346   const auto aCAM_Application = dynamic_cast<CAM_Application*>(aSUIT_Application);
1347   if (aCAM_Application)
1348     return aCAM_Application->moduleTitle(theModuleID);
1349   */
1350
1351   // At least something meaningful.
1352   return theModuleID;
1353 }
1354
1355 QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QString& theInModuleActionID) const
1356 {
1357   const QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID);
1358   if (actionID.isEmpty()) {
1359     ShCutDbg() && ShCutDbg("Can't get action name: either/both module ID \"" + theModuleID + "\" or/and in-module action ID \"" + theInModuleActionID + "\" is/are invalid.");
1360     return actionID;
1361   }
1362   return getActionName(actionID);
1363 }
1364
1365 QString SUIT_ShortcutMgr::getActionName(const QString& theActionID) const
1366 {
1367   const auto moduleAndInModuleActionIDs = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID);
1368   if (moduleAndInModuleActionIDs.second.isEmpty()) {
1369     ShCutDbg() && ShCutDbg("Can't get action name using invalid action ID \"" + theActionID + "\".");
1370     return QString();
1371   }
1372
1373   const auto itActionNames = myActionNames.find(theActionID);
1374   if (itActionNames != myActionNames.end() && !itActionNames->second.empty()) {
1375     QString lang;
1376     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1377     if (!resMgr) {
1378       Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1379       lang = DEFAULT_LANG;
1380     }
1381     else {
1382       lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1383     }
1384
1385     QStringList langPriorityList = LANG_PRIORITY_LIST;
1386     langPriorityList.push_front(lang);
1387     langPriorityList.removeDuplicates();
1388
1389     const std::map<QString, QString>& translations = itActionNames->second;
1390     for (const QString& lang : langPriorityList) {
1391       const auto itTranslation = translations.find(lang);
1392       if (itTranslation != translations.end()) {
1393         return itTranslation->second;
1394       }
1395     }
1396     return translations.begin()->second;
1397   }
1398   else /* if action ID has no loaded name in any language. */ {
1399     // Try to get action->toolTip() and use it as a name.
1400
1401     // Pitfall of the approach: at the time this code block is called, the action may not exist.
1402     // Moreover, an action with such an ID may not even have been created at the time of calling this method.
1403     // Thus, even buffering of names of every action ever created at runtime does not guarantee,
1404     // that the name will be available at any point in the life of the application,
1405     // unless the name is added to dedicated section in a preference file.
1406
1407     const QString& moduleID = moduleAndInModuleActionIDs.first;
1408     const QString& inModuleActionID = moduleAndInModuleActionIDs.second;
1409
1410     if (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID)) {
1411       for (const auto& actionAndID : myActionIDs) {
1412         if (actionAndID.second.second == inModuleActionID)
1413           return actionAndID.first->toolTip();
1414       }
1415       return inModuleActionID;
1416     }
1417     else {
1418       const auto itModuleActions = myActions.find(moduleID);
1419       if (itModuleActions == myActions.end())
1420         return inModuleActionID;
1421
1422       const std::map<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
1423       const auto itActions = moduleActions.find(inModuleActionID);
1424       if (itActions == moduleActions.end())
1425         return inModuleActionID;
1426
1427       const std::set<QAction*>& actions = itActions->second;
1428       if (actions.empty())
1429         return inModuleActionID;
1430
1431       return (*actions.begin())->toolTip();
1432     }
1433   }
1434 }
1435
1436 void SUIT_ShortcutMgr::onActionDestroyed(QObject* theObject)
1437 {
1438   QAction* action = static_cast<QAction*>(theObject);
1439
1440   auto itID = myActionIDs.find(action);
1441   if (itID == myActionIDs.end())
1442     return;
1443
1444   const QString& moduleID = itID->second.first;
1445   const QString& inModuleActionID = itID->second.second;
1446
1447   auto itModuleActions = myActions.find(moduleID);
1448   if (itModuleActions != myActions.end()) {
1449     std::map<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
1450     auto itActions = moduleActions.find(inModuleActionID);
1451     if (itActions != moduleActions.end()) {
1452       std::set<QAction*>& actions = itActions->second;
1453       actions.erase(action);
1454     }
1455   }
1456
1457   myActionIDs.erase(itID);
1458 }
1459
1460 bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent)
1461 {
1462   if (theEvent) {
1463     if (theEvent->type() == QEvent::ActionAdded) {
1464       auto anActionEvent = static_cast<QActionEvent*>(theEvent);
1465
1466       QtxAction* aQtxAction = qobject_cast<QtxAction*>(anActionEvent->action());
1467       if (aQtxAction) {
1468 #ifdef SHORTCUT_MGR_DBG
1469         {
1470           const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID());
1471           if (moduleIDAndActionID.second.isEmpty())
1472             ShCutDbg("ActionAdded event, but ID of the action is invalid. Action name = \"" + aQtxAction->toolTip() + "\", ID = \"" + aQtxAction->ID() + "\".");
1473           else if (!myShortcutContainer.hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second))
1474             ShCutDbg("ActionAdded event, but shortcut container has no shortcut for the action. It is ok, if preference files has not been parsed yet. Action ID = \"" + moduleIDAndActionID.second + "\".");
1475         }
1476 #endif//SHORTCUT_MGR_DBG
1477 #ifdef SHORTCUT_MGR_DEVTOOLS
1478         {
1479           DevTools::get()->collectShortcutAndTranslation(aQtxAction);
1480           const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID());
1481           if (moduleIDAndActionID.second.isEmpty())
1482             DevTools::get()->collectAssetsOfActionWithInvalidID(aQtxAction);
1483         }
1484 #endif//SHORTCUT_MGR_DEVTOOLS
1485               registerAction(aQtxAction);
1486       }
1487       else {
1488         QAction* aQAction = qobject_cast<QAction*>(anActionEvent->action());
1489 #ifdef SHORTCUT_MGR_DEVTOOLS
1490         if (aQAction)
1491           DevTools::get()->collectAssetsOfActionWithInvalidID(aQAction);
1492 #endif//SHORTCUT_MGR_DEVTOOLS
1493         if (aQAction && aQAction->shortcut() != NO_KEYSEQUENCE) {
1494 #ifdef SHORTCUT_MGR_DBG
1495           ShCutDbg("ActionAdded event, but the added action is not QtxAction and bound to non-empty key sequence. name: \"" + aQAction->toolTip() + "\".");
1496 #endif//SHORTCUT_MGR_DBG
1497           // Since non-QtxAction has no ID, it is impossible to properly manage its shortcut.
1498           // And the shortcut may interfere with managed ones.
1499           aQAction->setShortcut(NO_KEYSEQUENCE);
1500         }
1501       }
1502     }
1503   }
1504
1505   return QObject::eventFilter(theObject, theEvent);
1506 }
1507
1508 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
1509 {
1510   std::set<std::pair<QString, QString>> disabledShortcutsIDs = myShortcutContainer.setShortcut(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1511
1512   if (theOverride || disabledShortcutsIDs.empty()) {
1513     // Bind actions to corresponding modified key sequences. Save changes to preferences.
1514
1515     /** { moduleID, {inModuleActionID, keySequence}[] }[] */
1516     std::map<QString, std::map<QString, QKeySequence>> modifiedShortcuts;
1517
1518     for (const auto& moduleIDAndActionID : disabledShortcutsIDs) {
1519       // Unbind actions of disabled shortcuts.
1520
1521       const QString& moduleID = moduleIDAndActionID.first;
1522       const QString& inModuleActionID = moduleIDAndActionID.second;
1523
1524       std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[moduleID];
1525       modifiedModuleShortcuts[inModuleActionID] = NO_KEYSEQUENCE;
1526
1527       const std::set<QAction*> actions = getActions(moduleID, inModuleActionID);
1528       for (QAction* const action : actions) {
1529         action->setShortcut(NO_KEYSEQUENCE);
1530       }
1531     }
1532
1533     { // Bind actions to theKeySequence.
1534       std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[theModuleID];
1535       modifiedModuleShortcuts[theInModuleActionID] = theKeySequence;
1536
1537       const std::set<QAction*> actions = getActions(theModuleID, theInModuleActionID);
1538       for (QAction* const action : actions) {
1539         action->setShortcut(theKeySequence);
1540       }
1541     }
1542
1543     SUIT_ShortcutMgr::saveShortcutsToPreferences(modifiedShortcuts);
1544   }
1545
1546   return disabledShortcutsIDs;
1547 }
1548
1549 void SUIT_ShortcutMgr::setShortcutsFromPreferences()
1550 {
1551   ShCutDbg() && ShCutDbg("ShortcutMgr is initializing...");
1552
1553   SUIT_ShortcutContainer container;
1554   SUIT_ShortcutMgr::fillContainerFromPreferences(container, false /*theDefaultOnly*/);
1555   mergeShortcutContainer(container, true /*theOverrde*/, false /*theTreatAbsentIncomingAsDisabled*/);
1556   setActionNamesFromResources();
1557
1558   ShCutDbg() && ShCutDbg("ShortcutMgr has been initialized.");
1559 }
1560
1561 /*static*/ void SUIT_ShortcutMgr::saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed)
1562 {
1563   ShCutDbg() && ShCutDbg("Saving preferences to resources.");
1564
1565   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1566   if (!resMgr) {
1567     Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1568     return;
1569   }
1570
1571   for (const auto& moduleIDAndShortcutsInversed : theShortcutsInversed) {
1572     const auto& moduleID = moduleIDAndShortcutsInversed.first;
1573     const auto& moduleShortcutsInversed = moduleIDAndShortcutsInversed.second;
1574     for (const auto& shortcutInversed : moduleShortcutsInversed) {
1575       if (shortcutInversed.first.isEmpty()) {
1576         ShCutDbg("Attempt to serialize a shortcut with empty action ID.");
1577         continue;
1578       }
1579
1580       const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
1581       resMgr->setValue(sectionName, shortcutInversed.first, shortcutInversed.second.toString());
1582
1583       ShCutDbg() && ShCutDbg("Saving shortcut: \"" + moduleID + "\"\t\"" + shortcutInversed.first + "\"\t\"" + shortcutInversed.second.toString() + "\"");
1584     }
1585   }
1586 }
1587
1588 void SUIT_ShortcutMgr::setActionNamesFromResources(QString theLanguage)
1589 {
1590   ShCutDbg() && ShCutDbg("Retrieving action names (translations) from preference files.");
1591
1592   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1593   if (!resMgr) {
1594     Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1595     return;
1596   }
1597
1598   if (theLanguage.isEmpty())
1599     theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1600
1601   QStringList langPriorityList = LANG_PRIORITY_LIST;
1602   langPriorityList.push_front(theLanguage);
1603   langPriorityList.removeDuplicates();
1604
1605   QStringList actionIDs = resMgr->subSections(SECTION_SHORTCUT_NAMES_PREFIX, false);
1606   for (const QString& actionID : actionIDs) {
1607     // {language, actionName}[]
1608     std::map<QString, QString>& actionTranslations = myActionNames[actionID];
1609     const QString sectionName = SECTION_SHORTCUT_NAMES_PREFIX + resMgr->sectionsToken() + actionID;
1610     QStringList availableActionNameLangs = resMgr->parameters(sectionName);
1611
1612     QString actionName = actionID;
1613     const bool nameInCurLangExists = resMgr->value(sectionName, theLanguage, actionName);
1614     if (nameInCurLangExists) {
1615       actionTranslations[theLanguage] = actionName;
1616     }
1617     else {
1618       bool nameInlinguaFrancaExists = false;
1619       QString usedLanguage = QString();
1620       for (int i = 1; i < langPriorityList.length(); i++) {
1621         nameInlinguaFrancaExists = resMgr->value(sectionName, langPriorityList[i], actionName);
1622         if (nameInlinguaFrancaExists) {
1623           usedLanguage = langPriorityList[i];
1624           break;
1625         }
1626       }
1627       actionTranslations[theLanguage] = actionName;
1628
1629 #ifdef SHORTCUT_MGR_DBG
1630       if (nameInlinguaFrancaExists)
1631         ShCutDbg("Can't find in preference files a name for action with ID \"" + actionID + "\" at current (" + theLanguage + ") language. " + usedLanguage + " is used for the name." );
1632       else
1633         ShCutDbg("Can't find in preference files a name for action with ID \"" + actionID + "\". Also tried " + langPriorityList.join(", ") + " languages." );
1634 #endif
1635     }
1636   }
1637 }