1 // Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
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.
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.
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
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 #include "SUIT_ShortcutMgr.h"
25 #include "SUIT_Session.h"
26 #include "SUIT_ResourceMgr.h"
27 #include "SUIT_MessageBox.h"
30 #include <QtxAction.h>
32 #include <QApplication>
33 #include <QActionEvent>
34 #include <QKeySequence>
42 const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: ";
43 bool ShCutDbg(const QString& theString)
46 std::wcout << SHORTCUT_MGR_LOG_PREFIX << theString.toStdWString() << std::endl;
51 bool ShCutDbg(const char* src)
54 std::wcout << SHORTCUT_MGR_LOG_PREFIX << std::wstring(src, src + strlen(src)) << std::endl;
60 void Warning(const QString& theString)
62 std::wcout << theString.toStdWString() << std::endl;
64 void Warning(const char* src)
66 std::wcout << std::wstring(src, src + strlen(src)) << std::endl;
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("#");
77 /** Prefix of names of shortcut setting sections in preference files. */
78 static const QString SECTION_NAME_PREFIX = QString("shortcuts");
81 const QString DEFAULT_LANG = QString("en");
82 const QStringList LANG_PRIORITY_LIST = QStringList({DEFAULT_LANG, "fr"});
83 const QString LANG_SECTION = QString("language");
85 /** Prefix of names of sections in preference files with shortcut actions' names. */
86 static const QString SECTION_SHORTCUT_NAMES_PREFIX = QString("shortcut_translations");
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.
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.
97 * Content of dump files is appended on every run. Files are located in "<APP_DIR>/shortcut_mgr_dev/".
99 // #define SHORTCUT_MGR_DEVTOOLS
100 #ifdef SHORTCUT_MGR_DEVTOOLS
103 #include <QTextStream>
105 #include <functional>
107 #include <QDomDocument>
108 #include <QDomElement>
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. */
119 DevTools() : myActionsWithInvalidIDsFile(nullptr) {};
120 DevTools(const DevTools&) = delete;
121 void operator=(const DevTools&) = delete;
126 for (const auto& fileNameAndPtrs : myXMLFilesAndDocs) {
127 delete fileNameAndPtrs.second.second;
128 delete fileNameAndPtrs.second.first;
132 static DevTools* get() {
133 if (!DevTools::instance)
134 DevTools::instance = new DevTools();
136 return DevTools::instance;
139 void collectShortcut(
140 const QString& theModuleID,
141 const QString& theInModuleActionID,
142 const QKeySequence& theKeySequence
144 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
145 auto& moduleShortcuts = myShortcutsOfMetaActions[theModuleID];
146 moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
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);
155 auto& moduleShortcuts = myShortcuts[theModuleID];
156 moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
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);
166 void collectTranslation(
167 const QString& theModuleID,
168 const QString& theInModuleActionID,
169 const QString& theLang,
170 const QString& theActionName
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];
177 // {lang, actionName}[]
178 auto& actionTranslations = moduleTranslations[actionID];
179 actionTranslations[theLang] = theActionName;
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;
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;
193 writeToXMLFile(fileName, sections);
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;
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;
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;
215 writeToXMLFile(fileName, sections);
219 void collectShortcutAndTranslation(const QtxAction* const theAction)
221 const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theAction->ID());
222 if (moduleIDAndActionID.second.isEmpty())
225 if (!SUIT_ShortcutMgr::get()->getShortcutContainer().hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second))
226 collectShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second, theAction->shortcut());
228 { // Collect action name (translation) in current language, if it is not provided in resource files.
229 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
231 Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
235 const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
239 if (SUIT_ShortcutMgr::getActionNameFromResources(theAction->ID(), lang).first)
242 collectTranslation(moduleIDAndActionID.first, moduleIDAndActionID.second, lang, theAction->statusTip());
247 /*! Appends new entries to content of dump files. */
248 bool writeToXMLFile(const QString& theFileName, const std::map<QString, std::map<QString, QString>>& theSections)
251 Warning("DebugTools for SUIT_ShortcutMgr can't create XML - #QT_NO_DOM is defined.");
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";
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);
268 QFile* file = new QFile(fullPath);
269 if (!file->open(QFile::ReadWrite | QIODevice::Text)) {
271 myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(nullptr, nullptr);
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);
280 else if (itFileAndDoc->second.first == nullptr) {
284 const auto fileAndDom = myXMLFilesAndDocs[theFileName];
285 QFile* const file = fileAndDom.first;
286 QDomDocument* const dom = fileAndDom.second;
288 QDomElement doc = dom->documentElement();
290 *dom = QDomDocument(DOC_TAG);
291 doc = dom->createElement(DOC_TAG);
292 dom->appendChild(doc);
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)
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);
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));
327 for (const auto& sectionNameAndParams : theSections) {
328 const QString& sectionName = sectionNameAndParams.first;
329 const std::map<QString, QString>& parameters = sectionNameAndParams.second;
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;
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);
350 QTextStream outstream(file);
351 outstream << dom->toString();
358 void collectAssetsOfActionWithInvalidID(const QAction* const theAction)
360 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
362 Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
366 const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
370 if (!myActionsWithInvalidIDsFile) {
371 const QString fullPath = DevTools::SAVE_PATH + lang + DevTools::INVALID_ID_ACTIONS_SUFFIX + ".csv";
372 if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath()))
375 myActionsWithInvalidIDsFile = new QFile(fullPath);
376 if (!myActionsWithInvalidIDsFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
377 delete myActionsWithInvalidIDsFile;
378 myActionsWithInvalidIDsFile = nullptr;
382 QTextStream ostream(myActionsWithInvalidIDsFile);
383 ostream << "text\t" << "tool tip\t" << "status tip\t" << "key sequence\t" << "QtxAction?\t" << "ID\n";
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");
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;
401 static DevTools* instance;
402 static const QString XML_SECTION_TOKENS_SEPARATOR;
404 /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
405 std::map<QString, std::map<QString, QString>> myShortcuts;
407 /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
408 std::map<QString, std::map<QString, QString>> myShortcutsOfMetaActions;
410 /** { moduleID, { actionID, {language, actionName} }[] }[] */
411 std::map<QString, std::map<QString, std::map<QString, QString>>> myTranslations;
413 /** { moduleID, { actionID, {language, actionName} }[] }[] */
414 std::map<QString, std::map<QString, std::map<QString, QString>>> myTranslationsOfMetaActions;
417 // { filename, {file, domDoc} }[]
418 std::map<QString, std::pair<QFile*, QDomDocument*>> myXMLFilesAndDocs;
421 QFile* myActionsWithInvalidIDsFile;
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
435 SUIT_ShortcutContainer::SUIT_ShortcutContainer()
437 myShortcuts.emplace(ROOT_MODULE_ID, std::map<QKeySequence, QString>());
438 myShortcutsInversed.emplace(ROOT_MODULE_ID, std::map<QString, QKeySequence>());
441 std::set<QString> SUIT_ShortcutContainer::getIDsOfInterferingModules(const QString& theModuleID) const
443 std::set<QString> IDsOfInterferingModules;
444 if (theModuleID == ROOT_MODULE_ID) {
445 for (const auto& moduleIDAndShortcuts : myShortcuts) {
446 IDsOfInterferingModules.emplace(moduleIDAndShortcuts.first);
450 IDsOfInterferingModules.emplace(ROOT_MODULE_ID);
451 if (theModuleID != ROOT_MODULE_ID)
452 IDsOfInterferingModules.emplace(theModuleID);
454 return IDsOfInterferingModules;
457 std::set<QString> SUIT_ShortcutContainer::getIDsOfAllModules() const
459 std::set<QString> res;
460 for (const auto& moduleIDAndShortcuts : myShortcutsInversed) {
461 res.emplace(moduleIDAndShortcuts.first);
466 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::setShortcut(QString theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
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>>();
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>>();
478 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
479 theModuleID = ROOT_MODULE_ID;
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;
488 std::map<QKeySequence, QString>& moduleShortcuts = itModuleShortcuts->second;
489 std::map<QString, QKeySequence>& moduleShortcutsInversed = itModuleShortcutsInversed->second;
491 if (theKeySequence.isEmpty()) {
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>>();
501 else /* if keySequence was mapped to the action earlier. */ {
502 QKeySequence& keySequence = itShortcutInversed->second;
504 moduleShortcuts.erase(keySequence);
505 keySequence = NO_KEYSEQUENCE;
507 return std::set<std::pair<QString, QString>>();
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>>();
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;
530 conflictingActionIDs.insert(std::pair<QString, QString>(IDOfInterferingModule, conflictingActionID));
533 // Disable conflicting shortcuts.
534 std::map<QString, QKeySequence>& shortcutsOfInterferingModuleInversed = myShortcutsInversed.at(IDOfInterferingModule);
535 shortcutsOfInterferingModuleInversed[conflictingActionID] = NO_KEYSEQUENCE;
536 shortcutsOfInterferingModule.erase(itConflictingShortcut);
541 if (!theOverride && !conflictingActionIDs.empty())
542 return conflictingActionIDs;
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.
550 QKeySequence& keySequence = itShortcutInversed->second;
552 moduleShortcuts.erase(keySequence);
553 moduleShortcuts[theKeySequence] = theInModuleActionID;
555 keySequence = theKeySequence;
557 else /* if the action has not been added earlier. */ {
558 moduleShortcuts[theKeySequence] = theInModuleActionID;
559 moduleShortcutsInversed[theInModuleActionID] = theKeySequence;
563 return conflictingActionIDs;
566 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::getConflicts(
568 const QString& theInModuleActionID,
569 const QKeySequence& theKeySequence
572 if (theKeySequence.isEmpty())
573 return std::set<std::pair<QString, QString>>();
575 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
576 theModuleID = ROOT_MODULE_ID;
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>>();
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));
604 return conflictingActionIDs;
607 const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID, const QString& theInModuleActionID) const
609 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
610 theModuleID = ROOT_MODULE_ID;
612 const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
613 if (itModuleShortcutsInversed == myShortcutsInversed.end())
614 return NO_KEYSEQUENCE;
616 const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
617 const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
618 if (itShortcutInversed == moduleShortcutsInversed.end())
619 return NO_KEYSEQUENCE;
621 return itShortcutInversed->second;
624 bool SUIT_ShortcutContainer::hasShortcut(QString theModuleID, const QString& theInModuleActionID) const
626 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
627 theModuleID = ROOT_MODULE_ID;
629 const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
630 if (itModuleShortcutsInversed == myShortcutsInversed.end())
633 const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
634 const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
635 if (itShortcutInversed == moduleShortcutsInversed.end())
641 const std::map<QString, QKeySequence>& SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID) const
643 static const std::map<QString, QKeySequence> EMPTY_RES;
644 const auto it = myShortcutsInversed.find(theModuleID);
645 if (it == myShortcutsInversed.end())
651 const std::map<QString, QKeySequence> SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID, const QString& theActionIDPrefix) const
653 const auto it = myShortcutsInversed.find(theModuleID);
654 if (it == myShortcutsInversed.end())
655 return std::map<QString, QKeySequence>();
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;
662 return shortcutsInversed;
665 QString SUIT_ShortcutContainer::toString() const
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() + "\"";
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 + "\"";
689 std::map<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge(
690 const SUIT_ShortcutContainer& theOther,
692 bool theTreatAbsentIncomingAsDisabled
694 std::map<QString, std::map<QString, QKeySequence>> changesOfThis;
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;
703 if (hasShortcut(moduleIDOther, inModuleActionIDOther) && getKeySequence(moduleIDOther, inModuleActionIDOther) == keySequenceOther) {
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;
714 else /* if (!theOverride) */ {
715 if (hasShortcut(moduleIDOther, inModuleActionIDOther))
718 const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false);
719 if (conflictingActionsOfThis.empty()) {
720 changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
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;
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))
739 if (inversedShortcut.second.isEmpty())
740 continue; // Existing shortcut is already disabled.
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.
746 auto& moduleShortcuts = itShortcutsPair->second;
747 moduleShortcuts.erase(inversedShortcut.second);
748 inversedShortcut.second = NO_KEYSEQUENCE;
749 changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE;
754 return changesOfThis;
758 SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = nullptr;
760 SUIT_ShortcutMgr::SUIT_ShortcutMgr()
763 qApp->installEventFilter( this );
766 SUIT_ShortcutMgr::~SUIT_ShortcutMgr()
768 qApp->removeEventFilter( this );
771 /*static*/ void SUIT_ShortcutMgr::Init()
773 if( myShortcutMgr == nullptr) {
774 myShortcutMgr = new SUIT_ShortcutMgr();
775 myShortcutMgr->setShortcutsFromPreferences();
779 /*static*/ SUIT_ShortcutMgr* SUIT_ShortcutMgr::get()
782 return myShortcutMgr;
785 /*static*/ bool SUIT_ShortcutMgr::isKeySequenceValid(const QKeySequence& theKeySequence)
787 // TODO Perform check whether a key sequence is platform-compatible.
791 /*static*/ std::pair<bool, QKeySequence> SUIT_ShortcutMgr::toKeySequenceIfValid(const QString& theKeySequenceString)
793 auto res = std::pair<bool, QKeySequence>(false, QKeySequence());
796 res.second = QKeySequence::fromString(theKeySequenceString);
797 if (res.second.toString() != theKeySequenceString)
798 return std::pair<bool, QKeySequence>(false, QKeySequence());
800 if (!SUIT_ShortcutMgr::isKeySequenceValid(res.second))
801 return std::pair<bool, QKeySequence>(false, QKeySequence());
804 return std::pair<bool, QKeySequence>(false, QKeySequence());
811 /*static*/ bool SUIT_ShortcutMgr::isModuleIDValid(const QString& theModuleID)
813 if (theModuleID.contains(TOKEN_SEPARATOR))
816 if (theModuleID.simplified() != theModuleID)
822 /*static*/ bool SUIT_ShortcutMgr::isInModuleActionIDValid(const QString& theInModuleActionID)
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();
828 simplifiedToken.isEmpty() ||
829 simplifiedToken != tokens[i] ||
830 i == 0 && simplifiedToken == META_ACTION_PREFIX ||
831 i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
838 /*static*/ bool SUIT_ShortcutMgr::isInModuleMetaActionID(const QString& theInModuleActionID)
840 return theInModuleActionID.startsWith(META_ACTION_PREFIX);
843 /*static*/ std::pair<QString, QString> SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(const QString& theActionID)
845 QStringList tokens = theActionID.split(TOKEN_SEPARATOR);
846 if (tokens.length() < 2)
847 return std::pair<QString, QString>();
849 auto res = std::pair<QString, QString>();
851 if (tokens[0].simplified() != tokens[0])
852 return std::pair<QString, QString>();
854 res.first = tokens[0];
857 for (QStringList::size_type i = 0; i < tokens.length(); i++) {
858 const QString simplifiedToken = tokens[i].simplified();
860 simplifiedToken.isEmpty() ||
861 simplifiedToken != tokens[i] ||
862 i == 0 && simplifiedToken == META_ACTION_PREFIX ||
863 i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
865 return std::pair<QString, QString>();
867 res.second = tokens.join(TOKEN_SEPARATOR);
872 /*static*/ bool SUIT_ShortcutMgr::isActionIDValid(const QString& theActionID)
874 return !SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID).second.isEmpty();
877 /*static*/ QString SUIT_ShortcutMgr::makeActionID(const QString& theModuleID, const QString& theInModuleActionID)
879 if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID))
882 if (!isInModuleActionIDValid(theInModuleActionID))
885 return theModuleID + TOKEN_SEPARATOR + theInModuleActionID;
888 /*static*/ void SUIT_ShortcutMgr::fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly)
890 ShCutDbg() && ShCutDbg("Retrieving preferences from resources.");
892 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
894 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
898 const auto resMgrWorkingModeBefore = resMgr->workingMode();
900 resMgr->setWorkingMode(QtxResourceMgr::IgnoreUserValues);
902 /** List of modules with invalid IDs. */
903 QStringList invalidModuleIDs;
905 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
906 std::map<QString, std::list<std::pair<QString, QString>>> invalidShortcuts;
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;
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);
919 if (moduleIDs.isEmpty())
920 ShCutDbg("No discovered shortcut modules.");
922 ShCutDbg("Discovered shortcut modules: \"" + moduleIDs.join("\", \"") + ".");
924 moduleIDs.push_front(ROOT_MODULE_ID); // Resource manager filters out empty section suffices.
925 moduleIDs.removeDuplicates();
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);
934 const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
935 QStringList moduleActionIDs = resMgr->parameters(sectionName);
937 for(const QString& inModuleActionID : moduleActionIDs) {
938 QString keySequenceString = QString("");
939 resMgr->value(sectionName, inModuleActionID, keySequenceString);
940 const auto keySequence = SUIT_ShortcutMgr::toKeySequenceIfValid(keySequenceString);
942 ShCutDbg() && ShCutDbg("Shortcut discovered: \"" + moduleID + "\"\t\"" + inModuleActionID + "\"\t\"" + keySequenceString + "\".");
945 !SUIT_ShortcutMgr::isInModuleActionIDValid(inModuleActionID) ||
946 !keySequence.first ||
947 SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != ROOT_MODULE_ID
949 std::list<std::pair<QString, QString>>& moduleInvalidShortcuts = invalidShortcuts[moduleID];
950 moduleInvalidShortcuts.push_back(std::pair<QString, QString>(inModuleActionID, keySequenceString));
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));
962 if (!invalidModuleIDs.isEmpty() || !invalidShortcuts.empty() || !conflicts.empty())
963 { // Prepare report and show warning.
965 if (!invalidModuleIDs.isEmpty()) {
966 report += tr("Invalid module IDs") + ":";
967 for (const QString& invalidModuleID : invalidModuleIDs) {
968 report += "\n\t\"" + invalidModuleID + "\"" ;
972 if (!invalidShortcuts.empty()) {
973 if (!report.isEmpty())
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 + "\"";
986 if (!conflicts.empty()) {
987 if (!report.isEmpty())
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 + "\"";
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() + "\"";
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);
1024 Warning(text + ". " + informativeText + ":\n" + report);
1028 resMgr->setWorkingMode(resMgrWorkingModeBefore);
1030 ShCutDbg() && ShCutDbg("theContainer holds following shortcuts:\n" + theContainer.toString());
1033 /*static*/ std::pair<bool, QString> SUIT_ShortcutMgr::getActionNameFromResources(const QString& theActionID, QString theLanguage)
1035 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1037 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1038 return std::pair<bool, QString>(false, QString());
1041 if (theLanguage.isEmpty())
1042 theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
1044 if (theLanguage.isEmpty())
1045 return std::pair<bool, QString>(false, QString());
1047 QStringList actionIDs = resMgr->subSections(SECTION_SHORTCUT_NAMES_PREFIX, false);
1048 if (actionIDs.indexOf(theActionID) == -1)
1049 return std::pair<bool, QString>(false, QString());
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());
1057 const bool nameInCurLangExists = resMgr->value(sectionName, theLanguage, actionName);
1059 if (!nameInCurLangExists)
1060 return std::pair<bool, QString>(false, QString());
1062 return std::pair<bool, QString>(true, actionName);
1066 void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction)
1068 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1069 const QString& moduleID = moduleIDAndActionID.first;
1070 const QString& inModuleActionID = moduleIDAndActionID.second;
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);
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);
1096 myActionIDs.erase(itPreviousModuleAndActionID);
1100 auto itActions = myActions.find(moduleID);
1101 if (itActions == myActions.end()) {
1102 itActions = myActions.emplace(moduleID, std::map<QString, std::set<QAction*>>()).first;
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;
1111 myActionIDs[theAction] = moduleIDAndActionID;
1114 std::set<QAction*>& registeredActions = moduleActions[inModuleActionID];
1115 registeredActions.emplace(theAction);
1116 myActionIDs[theAction] = moduleIDAndActionID;
1119 connect(theAction, SIGNAL(destroyed(QObject*)), this, SLOT (onActionDestroyed(QObject*)));
1121 if (myShortcutContainer.hasShortcut(moduleID, inModuleActionID)) {
1122 const QKeySequence& keySequence = getKeySequence(moduleID, inModuleActionID);
1123 theAction->setShortcut(keySequence);
1127 "Action with ID \"" +
1128 (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) +
1129 "\" is not added to default resource files."
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.
1137 void SUIT_ShortcutMgr::registerAction(QtxAction* theAction)
1139 registerAction(theAction->ID(), theAction);
1142 std::set<QAction*> SUIT_ShortcutMgr::getActions(const QString& theModuleID, const QString& theInModuleActionID) const
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);
1153 const auto itActions = myActions.find(theModuleID);
1154 if (itActions == myActions.end())
1155 return std::set<QAction*>();
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*>();
1162 return itModuleActions->second;
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>();
1174 bool SUIT_ShortcutMgr::hasAction(const QAction* theAction) const
1176 return myActionIDs.find(const_cast<QAction*>(theAction)) != myActionIDs.end();
1179 QString SUIT_ShortcutMgr::getActionID(const QAction* theAction) const
1181 const auto it = myActionIDs.find(const_cast<QAction*>(theAction));
1182 if (it == myActionIDs.end())
1185 return SUIT_ShortcutMgr::makeActionID(it->second.first, it->second.second);
1188 void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable) const
1190 const auto itModuleActions = myActions.find(theModuleID);
1191 if (itModuleActions == myActions.end())
1194 SUIT_Application* app = SUIT_Session::session()->activeApplication();
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);
1208 void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1210 SUIT_Application* app = SUIT_Session::session()->activeApplication();
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);
1225 void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1227 setActionsWithPrefixInIDEnabled(theInModuleActionIDPrefix, theEnable);
1230 void SUIT_ShortcutMgr::rebindActionsToKeySequences() const
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));
1238 void SUIT_ShortcutMgr::updateShortcuts() const
1240 rebindActionsToKeySequences();
1243 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride)
1245 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1246 const QString& moduleID = moduleIDAndActionID.first;
1247 const QString& inModuleActionID = moduleIDAndActionID.second;
1249 if (inModuleActionID.isEmpty()) {
1250 ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid action ID \"" + theActionID + "\".");
1251 return std::set<std::pair<QString, QString>>();
1254 return setShortcutNoIDChecks(moduleID, inModuleActionID, theKeySequence, theOverride);
1257 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
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>>();
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>>();
1269 return setShortcutNoIDChecks(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1272 const SUIT_ShortcutContainer& SUIT_ShortcutMgr::getShortcutContainer() const
1274 return myShortcutContainer;
1277 void SUIT_ShortcutMgr::mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride, bool theTreatAbsentIncomingAsDisabled)
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());
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);
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);
1311 SUIT_ShortcutMgr::saveShortcutsToPreferences(changes);
1314 QKeySequence SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const
1316 return myShortcutContainer.getKeySequence(theModuleID, theInModuleActionID);
1319 const std::map<QString, QKeySequence>& SUIT_ShortcutMgr::getModuleShortcutsInversed(const QString& theModuleID) const
1321 return myShortcutContainer.getModuleShortcutsInversed(theModuleID);
1324 std::set<QString> SUIT_ShortcutMgr::getShortcutModuleIDs() const
1326 return myShortcutContainer.getIDsOfAllModules();
1329 std::set<QString> SUIT_ShortcutMgr::getIDsOfInterferingModules(const QString& theModuleID) const
1331 return myShortcutContainer.getIDsOfInterferingModules(theModuleID);
1334 QString SUIT_ShortcutMgr::getModuleName(const QString& theModuleID) const
1336 return theModuleID == ROOT_MODULE_ID ? tr("General") : theModuleID;
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.
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);
1351 // At least something meaningful.
1355 QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QString& theInModuleActionID) const
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.");
1362 return getActionName(actionID);
1365 QString SUIT_ShortcutMgr::getActionName(const QString& theActionID) const
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 + "\".");
1373 const auto itActionNames = myActionNames.find(theActionID);
1374 if (itActionNames != myActionNames.end() && !itActionNames->second.empty()) {
1376 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1378 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1379 lang = DEFAULT_LANG;
1382 lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1385 QStringList langPriorityList = LANG_PRIORITY_LIST;
1386 langPriorityList.push_front(lang);
1387 langPriorityList.removeDuplicates();
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;
1396 return translations.begin()->second;
1398 else /* if action ID has no loaded name in any language. */ {
1399 // Try to get action->toolTip() and use it as a name.
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.
1407 const QString& moduleID = moduleAndInModuleActionIDs.first;
1408 const QString& inModuleActionID = moduleAndInModuleActionIDs.second;
1410 if (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID)) {
1411 for (const auto& actionAndID : myActionIDs) {
1412 if (actionAndID.second.second == inModuleActionID)
1413 return actionAndID.first->toolTip();
1415 return inModuleActionID;
1418 const auto itModuleActions = myActions.find(moduleID);
1419 if (itModuleActions == myActions.end())
1420 return inModuleActionID;
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;
1427 const std::set<QAction*>& actions = itActions->second;
1428 if (actions.empty())
1429 return inModuleActionID;
1431 return (*actions.begin())->toolTip();
1436 void SUIT_ShortcutMgr::onActionDestroyed(QObject* theObject)
1438 QAction* action = static_cast<QAction*>(theObject);
1440 auto itID = myActionIDs.find(action);
1441 if (itID == myActionIDs.end())
1444 const QString& moduleID = itID->second.first;
1445 const QString& inModuleActionID = itID->second.second;
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);
1457 myActionIDs.erase(itID);
1460 bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent)
1463 if (theEvent->type() == QEvent::ActionAdded) {
1464 auto anActionEvent = static_cast<QActionEvent*>(theEvent);
1466 QtxAction* aQtxAction = qobject_cast<QtxAction*>(anActionEvent->action());
1468 #ifdef SHORTCUT_MGR_DBG
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 + "\".");
1476 #endif//SHORTCUT_MGR_DBG
1477 #ifdef SHORTCUT_MGR_DEVTOOLS
1479 DevTools::get()->collectShortcutAndTranslation(aQtxAction);
1480 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID());
1481 if (moduleIDAndActionID.second.isEmpty())
1482 DevTools::get()->collectAssetsOfActionWithInvalidID(aQtxAction);
1484 #endif//SHORTCUT_MGR_DEVTOOLS
1485 registerAction(aQtxAction);
1488 QAction* aQAction = qobject_cast<QAction*>(anActionEvent->action());
1489 #ifdef SHORTCUT_MGR_DEVTOOLS
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);
1505 return QObject::eventFilter(theObject, theEvent);
1508 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
1510 std::set<std::pair<QString, QString>> disabledShortcutsIDs = myShortcutContainer.setShortcut(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1512 if (theOverride || disabledShortcutsIDs.empty()) {
1513 // Bind actions to corresponding modified key sequences. Save changes to preferences.
1515 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
1516 std::map<QString, std::map<QString, QKeySequence>> modifiedShortcuts;
1518 for (const auto& moduleIDAndActionID : disabledShortcutsIDs) {
1519 // Unbind actions of disabled shortcuts.
1521 const QString& moduleID = moduleIDAndActionID.first;
1522 const QString& inModuleActionID = moduleIDAndActionID.second;
1524 std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[moduleID];
1525 modifiedModuleShortcuts[inModuleActionID] = NO_KEYSEQUENCE;
1527 const std::set<QAction*> actions = getActions(moduleID, inModuleActionID);
1528 for (QAction* const action : actions) {
1529 action->setShortcut(NO_KEYSEQUENCE);
1533 { // Bind actions to theKeySequence.
1534 std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[theModuleID];
1535 modifiedModuleShortcuts[theInModuleActionID] = theKeySequence;
1537 const std::set<QAction*> actions = getActions(theModuleID, theInModuleActionID);
1538 for (QAction* const action : actions) {
1539 action->setShortcut(theKeySequence);
1543 SUIT_ShortcutMgr::saveShortcutsToPreferences(modifiedShortcuts);
1546 return disabledShortcutsIDs;
1549 void SUIT_ShortcutMgr::setShortcutsFromPreferences()
1551 ShCutDbg() && ShCutDbg("ShortcutMgr is initializing...");
1553 SUIT_ShortcutContainer container;
1554 SUIT_ShortcutMgr::fillContainerFromPreferences(container, false /*theDefaultOnly*/);
1555 mergeShortcutContainer(container, true /*theOverrde*/, false /*theTreatAbsentIncomingAsDisabled*/);
1556 setActionNamesFromResources();
1558 ShCutDbg() && ShCutDbg("ShortcutMgr has been initialized.");
1561 /*static*/ void SUIT_ShortcutMgr::saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed)
1563 ShCutDbg() && ShCutDbg("Saving preferences to resources.");
1565 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1567 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
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.");
1580 const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
1581 resMgr->setValue(sectionName, shortcutInversed.first, shortcutInversed.second.toString());
1583 ShCutDbg() && ShCutDbg("Saving shortcut: \"" + moduleID + "\"\t\"" + shortcutInversed.first + "\"\t\"" + shortcutInversed.second.toString() + "\"");
1588 void SUIT_ShortcutMgr::setActionNamesFromResources(QString theLanguage)
1590 ShCutDbg() && ShCutDbg("Retrieving action names (translations) from preference files.");
1592 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1594 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1598 if (theLanguage.isEmpty())
1599 theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1601 QStringList langPriorityList = LANG_PRIORITY_LIST;
1602 langPriorityList.push_front(theLanguage);
1603 langPriorityList.removeDuplicates();
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);
1612 QString actionName = actionID;
1613 const bool nameInCurLangExists = resMgr->value(sectionName, theLanguage, actionName);
1614 if (nameInCurLangExists) {
1615 actionTranslations[theLanguage] = actionName;
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];
1627 actionTranslations[theLanguage] = actionName;
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." );
1633 ShCutDbg("Can't find in preference files a name for action with ID \"" + actionID + "\". Also tried " + langPriorityList.join(", ") + " languages." );