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>
36 #include <QJsonObject>
37 #include <QJsonDocument>
38 #include <QJsonParseError>
40 #include <QProcessEnvironment>
49 const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: ";
50 bool ShCutDbg(const QString& theString)
53 std::wcout << SHORTCUT_MGR_LOG_PREFIX << theString.toStdWString() << std::endl;
58 bool ShCutDbg(const char* src)
61 std::wcout << SHORTCUT_MGR_LOG_PREFIX << std::wstring(src, src + strlen(src)) << std::endl;
67 void Warning(const QString& theString)
69 std::wcout << theString.toStdWString() << std::endl;
71 void Warning(const char* src)
73 std::wcout << std::wstring(src, src + strlen(src)) << std::endl;
77 static const QKeySequence NO_KEYSEQUENCE = QKeySequence(QString(""));
78 static const QString NO_ACTION = QString("");
79 /** Separates tokens in action ID. */
80 static const QString TOKEN_SEPARATOR = QString("/");
81 /*static*/ const QString SUIT_ShortcutMgr::ROOT_MODULE_ID = QString("");
82 static const QString META_ACTION_PREFIX = QString("#");
84 /** Prefix of names of shortcut setting sections in preference files. */
85 static const QString SECTION_NAME_PREFIX = QString("shortcuts");
88 const QString DEFAULT_LANG = QString("en");
89 const QStringList LANG_PRIORITY_LIST = QStringList({DEFAULT_LANG, "fr"});
90 const QString LANG_SECTION = QString("language");
92 static const QString SECTION_NAME_ACTION_ASSET_FILE_PATHS = QString("action_assets");
97 * Uncomment this, to start collecting all shortcuts and action assets (1),
98 * from instances of QtxActions, if a shortcut or action assets are absent in resource/asset files.
100 * (1) Set required language in the application settings and run features of interest.
101 * For all actions from these features, their assets will be dumped to appropriate places in dump files.
103 * Content of dump files is appended on every run. Files are located in "<APP_DIR>/shortcut_mgr_dev/".
105 // #define SHORTCUT_MGR_DEVTOOLS
106 #ifdef SHORTCUT_MGR_DEVTOOLS
110 #include <QTextStream>
112 #include <functional>
114 #include <QDomDocument>
115 #include <QDomElement>
119 /*! \brief Generates XML files with appearing at runtime shortcuts,
120 using key sequences of QActions passed to the shortcut manager,
121 and JSON files with assets of QtxActions passed to the shortcut manager.
122 Content of these files can be easily copied to resource/asset files. */
126 DevTools() : myActionsWithInvalidIDsFile(nullptr) {};
127 DevTools(const DevTools&) = delete;
128 void operator=(const DevTools&) = delete;
133 for (const auto& fileNameAndPtrs : myXMLFilesAndDocs) {
134 delete fileNameAndPtrs.second.second;
135 delete fileNameAndPtrs.second.first;
138 for (const auto& fileNameAndPtrs : myJSONFilesAndDocs) {
139 delete fileNameAndPtrs.second.second;
140 delete fileNameAndPtrs.second.first;
144 static DevTools* get() {
145 if (!DevTools::instance)
146 DevTools::instance = new DevTools();
148 return DevTools::instance;
151 void collectShortcut(
152 const QString& theModuleID,
153 const QString& theInModuleActionID,
154 const QKeySequence& theKeySequence
156 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
157 auto& moduleShortcuts = myShortcutsOfMetaActions[theModuleID];
158 moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
160 const QString fileName = theModuleID + DevTools::SHORTCUTS_OF_META_SUFFIX;
161 const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + SUIT_ShortcutMgr::ROOT_MODULE_ID;
162 std::map<QString, std::map<QString, QString>> sections;
163 sections[sectionName] = moduleShortcuts;
164 writeToXMLFile(fileName, sections);
167 auto& moduleShortcuts = myShortcuts[theModuleID];
168 moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
170 const QString fileName = theModuleID + DevTools::SHORTCUTS_SUFFIX;
171 const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + theModuleID;
172 std::map<QString, std::map<QString, QString>> sections;
173 sections[sectionName] = moduleShortcuts;
174 writeToXMLFile(fileName, sections);
179 const QString& theModuleID,
180 const QString& theInModuleActionID,
181 const QString& theLang,
182 const QAction* theAction
184 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
185 QString actionID = SUIT_ShortcutMgr::makeActionID(SUIT_ShortcutMgr::ROOT_MODULE_ID, theInModuleActionID);
186 // { actionID, assets } []
187 auto& moduleAssets = myAssetsOfMetaActions[theModuleID];
189 auto& actionAssets = moduleAssets[actionID];
190 actionAssets.myLangDependentAssets[theLang].myName = theAction->text();
191 actionAssets.myLangDependentAssets[theLang].myToolTip = theAction->statusTip();
193 const QString fileName = theModuleID + DevTools::ASSETS_OF_META_SUFFIX;
194 writeToJSONFile(fileName, actionID, actionAssets);
197 QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID);
198 // { actionID, assets } []
199 auto& moduleAssets = myAssets[theModuleID];
201 auto& actionAssets = moduleAssets[actionID];
202 actionAssets.myLangDependentAssets[theLang].myName = theAction->text();
203 actionAssets.myLangDependentAssets[theLang].myToolTip = theAction->statusTip();
205 const QString fileName = theModuleID + DevTools::ASSETS_SUFFIX;
206 writeToJSONFile(fileName, actionID, actionAssets);
210 void collectShortcutAndAssets(const QtxAction* const theAction)
212 const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theAction->ID());
213 if (moduleIDAndActionID.second.isEmpty())
216 if (!SUIT_ShortcutMgr::get()->getShortcutContainer().hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second))
217 collectShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second, theAction->shortcut());
219 { // Collect action assets, if they are not provided in asset files.
220 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
222 Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
226 const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
230 const auto& assetsInResources = SUIT_ShortcutMgr::getActionAssetsFromResources(theAction->ID());
231 if (assetsInResources.first && assetsInResources.second.myLangDependentAssets.find(lang) != assetsInResources.second.myLangDependentAssets.end())
234 collectAssets(moduleIDAndActionID.first, moduleIDAndActionID.second, lang, theAction);
239 /*! Appends new entries to content of dump files. */
240 bool writeToXMLFile(const QString& theFileName, const std::map<QString, std::map<QString, QString>>& theSections)
243 Warning("DebugTools for SUIT_ShortcutMgr can't create XML - #QT_NO_DOM is defined.");
246 static const QString DOC_TAG = "document";
247 static const QString SECTION_TAG = "section";
248 static const QString PARAMETER_TAG = "parameter";
249 static const QString NAME_ATTR = "name";
250 static const QString VAL_ATTR = "value";
252 const auto itFileAndDoc = myXMLFilesAndDocs.find(theFileName);
253 if (itFileAndDoc == myXMLFilesAndDocs.end()) {
254 const QString fullPath = DevTools::SAVE_PATH + theFileName + ".xml";
255 if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) {
256 myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(nullptr, nullptr);
260 QFile* file = new QFile(fullPath);
261 if (!file->open(QFile::ReadWrite | QIODevice::Text)) {
263 myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(nullptr, nullptr);
267 QDomDocument* dom = new QDomDocument(DOC_TAG);
268 QTextStream instream(file);
269 dom->setContent(instream.readAll());
270 myXMLFilesAndDocs[theFileName] = std::pair<QFile*, QDomDocument*>(file, dom);
272 else if (itFileAndDoc->second.first == nullptr) {
276 const auto fileAndDom = myXMLFilesAndDocs[theFileName];
277 QFile* const file = fileAndDom.first;
278 QDomDocument* const dom = fileAndDom.second;
280 QDomElement doc = dom->documentElement();
282 *dom = QDomDocument(DOC_TAG);
283 doc = dom->createElement(DOC_TAG);
284 dom->appendChild(doc);
287 static const std::function<void(const std::map<QString, QString>&, QDomDocument&, QDomElement&)> mergeParamsToSection =
288 [&](const std::map<QString, QString>& parameters, QDomDocument& dom, QDomElement& sectionInDom)
290 for (const std::pair<QString, QString>& nameAndVal : parameters) {
291 const QString& paramName = nameAndVal.first;
292 const QString& paramVal = nameAndVal.second;
293 bool fileHasParam = false;
294 for (QDomElement paramInDom = sectionInDom.firstChildElement(PARAMETER_TAG); !paramInDom.isNull(); paramInDom = paramInDom.nextSiblingElement(PARAMETER_TAG)) {
295 const QString paramNameInDom = paramInDom.attribute(NAME_ATTR);
296 if (paramName == paramNameInDom) {
297 const QString paramValInDom = paramInDom.attribute(VAL_ATTR);
298 if (paramValInDom != paramVal) {
299 QDomElement replaceElement = dom.createElement(PARAMETER_TAG);
300 replaceElement.setAttribute(NAME_ATTR, paramName);
301 replaceElement.setAttribute(VAL_ATTR, paramVal);
302 sectionInDom.replaceChild(replaceElement, paramInDom);
310 QDomElement newParam = dom.createElement(PARAMETER_TAG);
311 newParam.setAttribute(NAME_ATTR, paramName);
312 newParam.setAttribute(VAL_ATTR, paramVal);
313 sectionInDom.insertAfter(newParam, sectionInDom.lastChildElement(PARAMETER_TAG));
319 for (const auto& sectionNameAndParams : theSections) {
320 const QString& sectionName = sectionNameAndParams.first;
321 const std::map<QString, QString>& parameters = sectionNameAndParams.second;
323 bool fileHasSection = false;
324 for (QDomElement sectionInDom = doc.firstChildElement(SECTION_TAG); !sectionInDom.isNull(); sectionInDom = sectionInDom.nextSiblingElement(SECTION_TAG)) {
325 QString sectionNameInDom = sectionInDom.attribute(NAME_ATTR);
326 if (sectionNameInDom == sectionName) {
327 mergeParamsToSection(parameters, *dom, sectionInDom);
328 fileHasSection = true;
333 if (!fileHasSection) {
334 QDomElement newSection = dom->createElement(SECTION_TAG);
335 newSection.setAttribute(NAME_ATTR, sectionName);
336 doc.insertAfter(newSection, doc.lastChildElement(SECTION_TAG));
337 mergeParamsToSection(parameters, *dom, newSection);
342 QTextStream outstream(file);
343 outstream << dom->toString();
349 /*! Appends new entries to content of dump files. */
350 bool writeToJSONFile(const QString& theFileName, const QString& theActionID, const SUIT_ActionAssets& theAssets)
352 const auto itFileAndDoc = myJSONFilesAndDocs.find(theFileName);
353 if (itFileAndDoc == myJSONFilesAndDocs.end()) {
354 const QString fullPath = DevTools::SAVE_PATH + theFileName + ".json";
355 if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) {
356 myJSONFilesAndDocs[theFileName] = std::pair<QFile*, QJsonDocument*>(nullptr, nullptr);
360 const bool fileExisted = QFileInfo::exists(fullPath);
361 QFile* file = new QFile(fullPath);
362 if (!file->open(QFile::ReadWrite | QIODevice::Text)) {
364 myJSONFilesAndDocs[theFileName] = std::pair<QFile*, QJsonDocument*>(nullptr, nullptr);
368 QJsonParseError jsonError;
369 QJsonDocument* document = new QJsonDocument(QJsonDocument::fromJson(file->readAll(), &jsonError));
370 if (jsonError.error != QJsonParseError::NoError && fileExisted) {
371 Warning("SUIT_ShortcutMgr: error during parsing of action asset dump file \"" + fullPath + "\"!");
374 myJSONFilesAndDocs[theFileName] = std::pair<QFile*, QJsonDocument*>(nullptr, nullptr);
378 if (!document->isObject()) {
379 document->setObject(QJsonObject());
381 QTextStream outstream(file);
382 outstream << document->toJson(QJsonDocument::Indented);
385 myJSONFilesAndDocs[theFileName] = std::pair<QFile*, QJsonDocument*>(file, document);
387 else if (itFileAndDoc->second.first == nullptr) {
391 const auto fileAndDoc = myJSONFilesAndDocs[theFileName];
392 QFile* const file = fileAndDoc.first;
393 QJsonDocument* const document = fileAndDoc.second;
395 QJsonObject rootJSON = document->object();
396 QJsonObject actionAssetsJSON = rootJSON[theActionID].toObject();
397 SUIT_ActionAssets actionAssets;
398 actionAssets.fromJSON(actionAssetsJSON);
399 actionAssets.merge(theAssets, true /*theOverride*/);
400 actionAssets.toJSON(actionAssetsJSON);
401 rootJSON[theActionID] = actionAssetsJSON;
402 document->setObject(rootJSON);
405 QTextStream outstream(file);
406 outstream << document->toJson(QJsonDocument::Indented);
412 void collectAssetsOfActionWithInvalidID(const QAction* const theAction)
414 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
416 Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!");
420 const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION);
424 if (!myActionsWithInvalidIDsFile) {
425 const QString fullPath = DevTools::SAVE_PATH + lang + DevTools::INVALID_ID_ACTIONS_SUFFIX + ".csv";
426 if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath()))
429 myActionsWithInvalidIDsFile = new QFile(fullPath);
430 if (!myActionsWithInvalidIDsFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
431 delete myActionsWithInvalidIDsFile;
432 myActionsWithInvalidIDsFile = nullptr;
436 QTextStream ostream(myActionsWithInvalidIDsFile);
437 ostream << "text\t" << "tool tip\t" << "status tip\t" << "key sequence\t" << "QtxAction?\t" << "ID\n";
441 QTextStream ostream(myActionsWithInvalidIDsFile);
442 const auto aQtxAction = qobject_cast<const QtxAction*>(theAction);
443 ostream << theAction->text() << "\t" << theAction->toolTip() << "\t" << theAction->statusTip() << "\t"
444 << theAction->shortcut().toString() << "\t" << (aQtxAction ? "yes\t" : "no\t") << (aQtxAction ? aQtxAction->ID() + "\n" : "\n");
448 static const QString SAVE_PATH;
449 static const QString SHORTCUTS_SUFFIX;
450 static const QString SHORTCUTS_OF_META_SUFFIX;
451 static const QString ASSETS_SUFFIX;
452 static const QString ASSETS_OF_META_SUFFIX;
453 static const QString INVALID_ID_ACTIONS_SUFFIX;
455 static DevTools* instance;
456 static const QString XML_SECTION_TOKENS_SEPARATOR;
458 /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
459 std::map<QString, std::map<QString, QString>> myShortcuts;
461 /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */
462 std::map<QString, std::map<QString, QString>> myShortcutsOfMetaActions;
464 /** { moduleID, { actionID, assets }[] }[] */
465 std::map<QString, std::map<QString, SUIT_ActionAssets>> myAssets;
467 /** { moduleID, { actionID, assets }[] }[] */
468 std::map<QString, std::map<QString, SUIT_ActionAssets>> myAssetsOfMetaActions;
471 // { filename, {file, domDoc} }[]
472 std::map<QString, std::pair<QFile*, QDomDocument*>> myXMLFilesAndDocs;
474 // { filename, {file, jsonDoc} }[]
475 std::map<QString, std::pair<QFile*, QJsonDocument*>> myJSONFilesAndDocs;
477 QFile* myActionsWithInvalidIDsFile;
479 /*static*/ DevTools* DevTools::instance = nullptr;
480 /*static*/ const QString DevTools::SAVE_PATH = "shortcut_mgr_dev/";
481 /*static*/ const QString DevTools::INVALID_ID_ACTIONS_SUFFIX = "_actions_with_invalid_IDs";
482 /*static*/ const QString DevTools::XML_SECTION_TOKENS_SEPARATOR = ":";
483 /*static*/ const QString DevTools::SHORTCUTS_SUFFIX = "_shortcuts";
484 /*static*/ const QString DevTools::SHORTCUTS_OF_META_SUFFIX = "_shortcuts_of_meta_actions";
485 /*static*/ const QString DevTools::ASSETS_SUFFIX = "_assets";
486 /*static*/ const QString DevTools::ASSETS_OF_META_SUFFIX = "_assets_of_meta_actions";
487 #endif // SHORTCUT_MGR_DEVTOOLS
491 SUIT_ShortcutContainer::SUIT_ShortcutContainer()
493 myShortcuts.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID, std::map<QKeySequence, QString>());
494 myShortcutsInversed.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID, std::map<QString, QKeySequence>());
497 std::set<QString> SUIT_ShortcutContainer::getIDsOfInterferingModules(const QString& theModuleID) const
499 std::set<QString> IDsOfInterferingModules;
500 if (theModuleID == SUIT_ShortcutMgr::ROOT_MODULE_ID) {
501 for (const auto& moduleIDAndShortcuts : myShortcuts) {
502 IDsOfInterferingModules.emplace(moduleIDAndShortcuts.first);
506 IDsOfInterferingModules.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID);
507 if (theModuleID != SUIT_ShortcutMgr::ROOT_MODULE_ID)
508 IDsOfInterferingModules.emplace(theModuleID);
510 return IDsOfInterferingModules;
513 std::set<QString> SUIT_ShortcutContainer::getIDsOfAllModules() const
515 std::set<QString> res;
516 for (const auto& moduleIDAndShortcuts : myShortcutsInversed) {
517 res.emplace(moduleIDAndShortcuts.first);
522 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::setShortcut(QString theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
524 if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) {
525 ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid module ID = \"" + theModuleID + "\".");
526 return std::set<std::pair<QString, QString>>();
529 if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) {
530 ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid in-module action ID = \"" + theInModuleActionID + "\".");
531 return std::set<std::pair<QString, QString>>();
534 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
535 theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
537 auto itModuleShortcuts = myShortcuts.find(theModuleID);
538 auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
539 if (itModuleShortcuts == myShortcuts.end()) {
540 itModuleShortcuts = myShortcuts.emplace(theModuleID, std::map<QKeySequence, QString>()).first;
541 itModuleShortcutsInversed = myShortcutsInversed.emplace(theModuleID, std::map<QString, QKeySequence>()).first;
544 std::map<QKeySequence, QString>& moduleShortcuts = itModuleShortcuts->second;
545 std::map<QString, QKeySequence>& moduleShortcutsInversed = itModuleShortcutsInversed->second;
547 if (theKeySequence.isEmpty()) {
550 auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
551 if (itShortcutInversed == moduleShortcutsInversed.end()) {
552 // No key sequence was mapped to the action earlier.
553 // Set disabled shortcut.
554 moduleShortcutsInversed.emplace(theInModuleActionID, NO_KEYSEQUENCE);
555 return std::set<std::pair<QString, QString>>();
557 else /* if keySequence was mapped to the action earlier. */ {
558 QKeySequence& keySequence = itShortcutInversed->second;
560 moduleShortcuts.erase(keySequence);
561 keySequence = NO_KEYSEQUENCE;
563 return std::set<std::pair<QString, QString>>();
567 { // Check if the shortcut is already set.
568 const auto itShortcut = moduleShortcuts.find(theKeySequence);
569 if (itShortcut != moduleShortcuts.end()) {
570 if (itShortcut->second == theInModuleActionID) {
571 // The shortcut was set earlier. Nothing to change.
572 return std::set<std::pair<QString, QString>>();
577 auto conflictingActionIDs = std::set<std::pair<QString, QString>>();
578 { // Look for conflicting shortcuts with the same key sequence from interfering modules.
579 std::set<QString> IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID);
580 for (const QString& IDOfInterferingModule : IDsOfInterferingModules) {
581 std::map<QKeySequence, QString>& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule);
582 auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence);
583 if (itConflictingShortcut != shortcutsOfInterferingModule.end()) {
584 const QString& conflictingActionID = itConflictingShortcut->second;
586 conflictingActionIDs.insert(std::pair<QString, QString>(IDOfInterferingModule, conflictingActionID));
589 // Disable conflicting shortcuts.
590 std::map<QString, QKeySequence>& shortcutsOfInterferingModuleInversed = myShortcutsInversed.at(IDOfInterferingModule);
591 shortcutsOfInterferingModuleInversed[conflictingActionID] = NO_KEYSEQUENCE;
592 shortcutsOfInterferingModule.erase(itConflictingShortcut);
597 if (!theOverride && !conflictingActionIDs.empty())
598 return conflictingActionIDs;
601 { // Ensure, that the module has only shortcut for the action ID.
602 auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
603 if (itShortcutInversed != moduleShortcutsInversed.end()) {
604 // Redefine key sequence for existing action.
606 QKeySequence& keySequence = itShortcutInversed->second;
608 moduleShortcuts.erase(keySequence);
609 moduleShortcuts[theKeySequence] = theInModuleActionID;
611 keySequence = theKeySequence;
613 else /* if the action has not been added earlier. */ {
614 moduleShortcuts[theKeySequence] = theInModuleActionID;
615 moduleShortcutsInversed[theInModuleActionID] = theKeySequence;
619 return conflictingActionIDs;
622 std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::getConflicts(
624 const QString& theInModuleActionID,
625 const QKeySequence& theKeySequence
628 if (theKeySequence.isEmpty())
629 return std::set<std::pair<QString, QString>>();
631 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
632 theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
634 { // Check if the shortcut is set.
635 const auto itModuleShortcuts = myShortcuts.find(theModuleID);
636 if (itModuleShortcuts != myShortcuts.end()) {
637 const std::map<QKeySequence, QString>& moduleShortcuts = itModuleShortcuts->second;
638 const auto itShortcut = moduleShortcuts.find(theKeySequence);
639 if (itShortcut != moduleShortcuts.end()) {
640 if (itShortcut->second == theInModuleActionID) {
641 // The shortcut is set => no conflicts.
642 return std::set<std::pair<QString, QString>>();
648 auto conflictingActionIDs = std::set<std::pair<QString, QString>>();
649 { // Look for conflicting shortcuts with the same key sequence from interfering modules.
650 std::set<QString> IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID);
651 for (const QString& IDOfInterferingModule : IDsOfInterferingModules) {
652 const std::map<QKeySequence, QString>& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule);
653 const auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence);
654 if (itConflictingShortcut != shortcutsOfInterferingModule.end()) {
655 const QString& conflictingActionID = itConflictingShortcut->second;
656 conflictingActionIDs.insert(std::pair<QString, QString>(IDOfInterferingModule, conflictingActionID));
660 return conflictingActionIDs;
663 const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID, const QString& theInModuleActionID) const
665 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
666 theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
668 const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
669 if (itModuleShortcutsInversed == myShortcutsInversed.end())
670 return NO_KEYSEQUENCE;
672 const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
673 const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
674 if (itShortcutInversed == moduleShortcutsInversed.end())
675 return NO_KEYSEQUENCE;
677 return itShortcutInversed->second;
680 bool SUIT_ShortcutContainer::hasShortcut(QString theModuleID, const QString& theInModuleActionID) const
682 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
683 theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
685 const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
686 if (itModuleShortcutsInversed == myShortcutsInversed.end())
689 const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second;
690 const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID);
691 if (itShortcutInversed == moduleShortcutsInversed.end())
697 const std::map<QString, QKeySequence>& SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID) const
699 static const std::map<QString, QKeySequence> EMPTY_RES;
700 const auto it = myShortcutsInversed.find(theModuleID);
701 if (it == myShortcutsInversed.end())
707 const std::map<QString, QKeySequence> SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID, const QString& theActionIDPrefix) const
709 const auto it = myShortcutsInversed.find(theModuleID);
710 if (it == myShortcutsInversed.end())
711 return std::map<QString, QKeySequence>();
713 std::map<QString, QKeySequence> shortcutsInversed;
714 for (const auto& existingShortcut : it->second) {
715 if (existingShortcut.first.startsWith(theActionIDPrefix))
716 shortcutsInversed[existingShortcut.first] = existingShortcut.second;
718 return shortcutsInversed;
721 QString SUIT_ShortcutContainer::toString() const
724 text += "Shortcuts inversed:\n";
725 for (auto it = myShortcutsInversed.begin(); it != myShortcutsInversed.end(); it++) {
726 const QString& moduleID = it->first;
727 const auto& moduleShortcuts = it->second;
728 text += (it == myShortcutsInversed.begin() ? "\"" : "\n\"") + moduleID + "\"";
729 for (const auto& shortcut : moduleShortcuts) {
730 text += "\n\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\"";
733 text += "\nShortcuts:\n";
734 for (auto it = myShortcuts.begin(); it != myShortcuts.end(); it++) {
735 const QString& moduleID = it->first;
736 const auto& moduleShortcuts = it->second;
737 text += (it == myShortcuts.begin() ? "\"" : "\n\"") + moduleID + "\"";
738 for (const auto& shortcut : moduleShortcuts) {
739 text += "\n\t\"" + shortcut.first.toString() + "\"\t\"" + shortcut.second + "\"";
745 /*static*/ const QString SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME = "name";
746 /*static*/ const QString SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP = "tooltip";
748 bool SUIT_ActionAssets::LangDependentAssets::fromJSON(const QJsonObject& theJsonObject)
750 myName = theJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME].toString();
751 myToolTip = theJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP].toString();
753 if (myName.isEmpty())
756 return !myName.isEmpty();
759 void SUIT_ActionAssets::LangDependentAssets::toJSON(QJsonObject& oJsonObject) const
761 oJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME] = myName;
762 oJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP] = myToolTip;
765 /*static*/ const QString SUIT_ActionAssets::STRUCT_ID = "SUIT_ActionAssets";
766 /*static*/ const QString SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS = "langDependentAssets";
767 /*static*/ const QString SUIT_ActionAssets::PROP_ID_ICON_PATH = "iconPath";
769 bool SUIT_ActionAssets::fromJSON(const QJsonObject& theJsonObject)
771 myLangDependentAssets.clear();
773 auto lda = SUIT_ActionAssets::LangDependentAssets();
774 const auto& langToLdaJson = theJsonObject[SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS].toObject();
775 for (const QString& lang : langToLdaJson.keys()) {
776 if (!lda.fromJSON(langToLdaJson[lang].toObject()))
779 myLangDependentAssets[lang] = lda;
782 myIconPath = theJsonObject[SUIT_ActionAssets::PROP_ID_ICON_PATH].toString();
784 return !myLangDependentAssets.empty();
787 void SUIT_ActionAssets::toJSON(QJsonObject& oJsonObject) const
789 auto langDependentAssetsJSON = QJsonObject();
791 auto langDependentAssetsItemJSON = QJsonObject();
792 for (const auto& langAndLDA : myLangDependentAssets) {
793 langAndLDA.second.toJSON(langDependentAssetsItemJSON);
794 langDependentAssetsJSON[langAndLDA.first] = langDependentAssetsItemJSON;
796 oJsonObject[SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS] = langDependentAssetsJSON;
798 oJsonObject[SUIT_ActionAssets::PROP_ID_ICON_PATH] = myIconPath;
801 QString SUIT_ActionAssets::toString() const
803 QJsonObject jsonObject;
805 return QString::fromStdString(QJsonDocument(jsonObject).toJson(QJsonDocument::Indented).toStdString());
808 QStringList SUIT_ActionAssets::getLangs() const
812 for (const auto& langAndAssets : myLangDependentAssets) {
813 langs.push_back(langAndAssets.first);
819 void SUIT_ActionAssets::clearAllLangsExcept(const QString& theLang)
821 for (auto it = myLangDependentAssets.begin(); it != myLangDependentAssets.end();) {
822 if (it->first == theLang)
825 it = myLangDependentAssets.erase(it);
829 void SUIT_ActionAssets::merge(const SUIT_ActionAssets& theOther, bool theOverride)
831 for (const auto& otherLangAndLDA : theOther.myLangDependentAssets) {
832 const QString& lang = otherLangAndLDA.first;
833 const auto& otherLDA = otherLangAndLDA.second;
834 auto& thisLDA = myLangDependentAssets[lang];
836 if (thisLDA.myName.isEmpty() || theOverride && !otherLDA.myName.isEmpty())
837 thisLDA.myName = otherLDA.myName;
839 if (thisLDA.myToolTip.isEmpty() || theOverride && !otherLDA.myToolTip.isEmpty())
840 thisLDA.myToolTip = otherLDA.myToolTip;
844 myIconPath = theOther.myIconPath;
847 std::map<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge(
848 const SUIT_ShortcutContainer& theOther,
850 bool theTreatAbsentIncomingAsDisabled
852 std::map<QString, std::map<QString, QKeySequence>> changesOfThis;
854 for (const auto& shortcutsInversedOfOtherPair : theOther.myShortcutsInversed) {
855 const QString& moduleIDOther = shortcutsInversedOfOtherPair.first;
856 const auto& shortcutsInversedOther = shortcutsInversedOfOtherPair.second;
857 for (const auto& shortcutInversedOther : shortcutsInversedOther) {
858 const QString& inModuleActionIDOther = shortcutInversedOther.first;
859 const QKeySequence& keySequenceOther = shortcutInversedOther.second;
861 if (hasShortcut(moduleIDOther, inModuleActionIDOther) && getKeySequence(moduleIDOther, inModuleActionIDOther) == keySequenceOther) {
864 else /* if this has no shortcut for the action or if this has a shortcut for the action, but the key sequence differs. */ {
865 const auto disabledActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, true);
866 changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
867 for (const auto& disabledActionOfThis : disabledActionsOfThis) {
868 changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = NO_KEYSEQUENCE;
872 else /* if (!theOverride) */ {
873 if (hasShortcut(moduleIDOther, inModuleActionIDOther))
876 const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false);
877 if (conflictingActionsOfThis.empty()) {
878 changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
880 else /* if this has no shortcut for the action, but the incoming key sequence conflicts with others shortcuts. */ {
881 changesOfThis[moduleIDOther][inModuleActionIDOther] = NO_KEYSEQUENCE;
888 if (theOverride && theTreatAbsentIncomingAsDisabled) {
889 // Disable existing shortcuts, if they are absent in theOther.
890 for (auto& shortcutsInversedPair : myShortcutsInversed) {
891 const QString& moduleID = shortcutsInversedPair.first;
892 auto& moduleShortcutsInversed = shortcutsInversedPair.second;
893 for (auto& inversedShortcut : moduleShortcutsInversed) {
894 if (theOther.hasShortcut(moduleID, inversedShortcut.first))
897 if (inversedShortcut.second.isEmpty())
898 continue; // Existing shortcut is already disabled.
900 auto itShortcutsPair = myShortcuts.find(moduleID);
901 if (itShortcutsPair == myShortcuts.end())
902 continue; // The check is an overhead in an error-free designed class, but let be just in case.
904 auto& moduleShortcuts = itShortcutsPair->second;
905 moduleShortcuts.erase(inversedShortcut.second);
906 inversedShortcut.second = NO_KEYSEQUENCE;
907 changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE;
912 return changesOfThis;
916 SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = nullptr;
918 SUIT_ShortcutMgr::SUIT_ShortcutMgr()
921 qApp->installEventFilter( this );
924 SUIT_ShortcutMgr::~SUIT_ShortcutMgr()
926 qApp->removeEventFilter( this );
929 /*static*/ void SUIT_ShortcutMgr::Init()
931 if( myShortcutMgr == nullptr) {
932 myShortcutMgr = new SUIT_ShortcutMgr();
933 myShortcutMgr->setShortcutsFromPreferences();
937 /*static*/ SUIT_ShortcutMgr* SUIT_ShortcutMgr::get()
940 return myShortcutMgr;
943 /*static*/ bool SUIT_ShortcutMgr::isKeySequenceValid(const QKeySequence& theKeySequence)
945 // TODO Perform check whether a key sequence is platform-compatible.
949 /*static*/ std::pair<bool, QKeySequence> SUIT_ShortcutMgr::toKeySequenceIfValid(const QString& theKeySequenceString)
951 auto res = std::pair<bool, QKeySequence>(false, QKeySequence());
954 res.second = QKeySequence::fromString(theKeySequenceString);
955 if (res.second.toString() != theKeySequenceString)
956 return std::pair<bool, QKeySequence>(false, QKeySequence());
958 if (!SUIT_ShortcutMgr::isKeySequenceValid(res.second))
959 return std::pair<bool, QKeySequence>(false, QKeySequence());
962 return std::pair<bool, QKeySequence>(false, QKeySequence());
969 /*static*/ bool SUIT_ShortcutMgr::isModuleIDValid(const QString& theModuleID)
971 if (theModuleID.contains(TOKEN_SEPARATOR))
974 if (theModuleID.simplified() != theModuleID)
980 /*static*/ bool SUIT_ShortcutMgr::isInModuleActionIDValid(const QString& theInModuleActionID)
982 QStringList tokens = theInModuleActionID.split(TOKEN_SEPARATOR);
983 for (QStringList::size_type i = 0; i < tokens.length(); i++) {
984 const QString simplifiedToken = tokens[i].simplified();
986 simplifiedToken.isEmpty() ||
987 simplifiedToken != tokens[i] ||
988 i == 0 && simplifiedToken == META_ACTION_PREFIX ||
989 i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
996 /*static*/ bool SUIT_ShortcutMgr::isInModuleMetaActionID(const QString& theInModuleActionID)
998 return theInModuleActionID.startsWith(META_ACTION_PREFIX);
1001 /*static*/ std::pair<QString, QString> SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(const QString& theActionID)
1003 QStringList tokens = theActionID.split(TOKEN_SEPARATOR);
1004 if (tokens.length() < 2)
1005 return std::pair<QString, QString>();
1007 auto res = std::pair<QString, QString>();
1009 if (tokens[0].simplified() != tokens[0])
1010 return std::pair<QString, QString>();
1012 res.first = tokens[0];
1015 for (QStringList::size_type i = 0; i < tokens.length(); i++) {
1016 const QString simplifiedToken = tokens[i].simplified();
1018 simplifiedToken.isEmpty() ||
1019 simplifiedToken != tokens[i] ||
1020 i == 0 && simplifiedToken == META_ACTION_PREFIX ||
1021 i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX)
1023 return std::pair<QString, QString>();
1025 res.second = tokens.join(TOKEN_SEPARATOR);
1030 /*static*/ bool SUIT_ShortcutMgr::isActionIDValid(const QString& theActionID)
1032 return !SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID).second.isEmpty();
1035 /*static*/ QString SUIT_ShortcutMgr::makeActionID(const QString& theModuleID, const QString& theInModuleActionID)
1037 if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID))
1040 if (!isInModuleActionIDValid(theInModuleActionID))
1043 return theModuleID + TOKEN_SEPARATOR + theInModuleActionID;
1046 /*static*/ void SUIT_ShortcutMgr::fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly)
1048 ShCutDbg() && ShCutDbg("Retrieving preferences from resources.");
1050 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1052 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1056 const auto resMgrWorkingModeBefore = resMgr->workingMode();
1058 resMgr->setWorkingMode(QtxResourceMgr::IgnoreUserValues);
1060 /** List of modules with invalid IDs. */
1061 QStringList invalidModuleIDs;
1063 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
1064 std::map<QString, std::list<std::pair<QString, QString>>> invalidShortcuts;
1067 * Shortcuts, which have not been set, because they are in conflict with previously parsed shortcuts.
1068 * { moduleID, {inModuleActionID, keySequence}[] }[] */
1069 std::map<QString, std::list<std::pair<QString, QKeySequence>>> conflicts;
1071 // Resource manager strips leading and trailing whitespaces from subsections' names.
1072 // And then it is not able to retrieve parametes from that subsections,
1073 // because parsed subsection names differ from the ones in resource file.
1074 // Anyway, it does not affect operability of ShortcutMgr.
1075 QStringList moduleIDs = resMgr->subSections(SECTION_NAME_PREFIX, true);
1077 if (moduleIDs.isEmpty())
1078 ShCutDbg("No discovered shortcut modules.");
1080 ShCutDbg("Discovered shortcut modules: \"" + moduleIDs.join("\", \"") + ".");
1082 moduleIDs.push_front(SUIT_ShortcutMgr::ROOT_MODULE_ID); // Resource manager filters out empty section suffices.
1083 moduleIDs.removeDuplicates();
1085 for (size_t i = 0; i < moduleIDs.size(); i++) {
1086 const auto& moduleID = moduleIDs[i];
1087 if (!SUIT_ShortcutMgr::isModuleIDValid(moduleID)) {
1088 invalidModuleIDs.push_back(moduleID);
1092 const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
1093 QStringList moduleActionIDs = resMgr->parameters(sectionName);
1095 for(const QString& inModuleActionID : moduleActionIDs) {
1096 QString keySequenceString = QString("");
1097 resMgr->value(sectionName, inModuleActionID, keySequenceString);
1098 const auto keySequence = SUIT_ShortcutMgr::toKeySequenceIfValid(keySequenceString);
1100 ShCutDbg() && ShCutDbg("Shortcut discovered: \"" + moduleID + "\"\t\"" + inModuleActionID + "\"\t\"" + keySequenceString + "\".");
1103 !SUIT_ShortcutMgr::isInModuleActionIDValid(inModuleActionID) ||
1104 !keySequence.first ||
1105 SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != SUIT_ShortcutMgr::ROOT_MODULE_ID
1107 std::list<std::pair<QString, QString>>& moduleInvalidShortcuts = invalidShortcuts[moduleID];
1108 moduleInvalidShortcuts.push_back(std::pair<QString, QString>(inModuleActionID, keySequenceString));
1112 const auto shortcutConflicts = theContainer.setShortcut(moduleID, inModuleActionID, keySequence.second, false /*override*/);
1113 if (!shortcutConflicts.empty()) {
1114 auto& moduleConflicts = conflicts[moduleID];
1115 moduleConflicts.push_back(std::pair<QString, QKeySequence>(inModuleActionID, keySequence.second));
1120 if (!invalidModuleIDs.isEmpty() || !invalidShortcuts.empty() || !conflicts.empty())
1121 { // Prepare report and show warning.
1123 if (!invalidModuleIDs.isEmpty()) {
1124 report += tr("Invalid module IDs") + ":";
1125 for (const QString& invalidModuleID : invalidModuleIDs) {
1126 report += "\n\t\"" + invalidModuleID + "\"" ;
1130 if (!invalidShortcuts.empty()) {
1131 if (!report.isEmpty())
1134 report += tr("Invalid shortcuts") + ":";
1135 for (const auto& moduleAndShortcuts : invalidShortcuts) {
1136 report += "\n\t\"" + moduleAndShortcuts.first + "\"";
1137 const std::list<std::pair<QString, QString>>& moduleShortcuts = moduleAndShortcuts.second;
1138 for (const auto& shortcut : moduleShortcuts) {
1139 report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second + "\"";
1144 if (!conflicts.empty()) {
1145 if (!report.isEmpty())
1148 report += tr("These shortcuts have not been set to theContainer, because they conflict with previously parsed ones") + ":";
1149 for (const auto& moduleAndShortcuts : conflicts) {
1150 report += "\n\t\"" + moduleAndShortcuts.first + "\"";
1152 const std::list<std::pair<QString, QKeySequence>>& moduleShortcuts = moduleAndShortcuts.second;
1153 for (const auto& shortcut : moduleShortcuts) {
1154 report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\"";
1161 const auto text = tr("Invalid shortcuts in preferences");
1162 const auto informativeText = tr("Fix the following entries in the preference files manually");
1163 if (!theDefaultOnly) {
1164 // If user preferences are accounted, show warning in UI.
1165 SUIT_Application* app = SUIT_Session::session()->activeApplication();
1166 if (app && app->desktop()) {
1167 // Is not compiled without cast or with static_cast<QWidget*>.
1168 QMessageBox msgBox((QWidget*)app->desktop());
1169 msgBox.setIcon(QMessageBox::Warning);
1170 msgBox.setTextFormat(Qt::RichText);
1171 msgBox.setText("<b>" + text + "</b>");
1172 msgBox.setInformativeText(informativeText + ":");
1173 msgBox.setWindowFlags(Qt::WindowType::Popup);
1174 msgBox.setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
1175 msgBox.setDetailedText(report);
1176 msgBox.setStandardButtons(QMessageBox::Ok);
1177 msgBox.setDefaultButton(QMessageBox::Ok);
1178 msgBox.setMinimumWidth(600);
1182 Warning(text + ". " + informativeText + ":\n" + report);
1186 resMgr->setWorkingMode(resMgrWorkingModeBefore);
1188 ShCutDbg() && ShCutDbg("theContainer holds following shortcuts:\n" + theContainer.toString());
1191 QString substituteBashVars(const QString& theString)
1193 QString res = theString;
1194 const auto env = QProcessEnvironment::systemEnvironment();
1196 QRegExp rx("\\$\\{([^\\}]+)\\}"); // Match substrings enclosed by "${" and "}".
1197 rx.setMinimal(true); // Set search to non-greedy.
1198 while((pos = rx.indexIn(res, pos)) != -1) {
1199 QString capture = rx.cap(1);
1200 QString subst = env.value(capture);
1201 ShCutDbg("capture = " + capture);
1202 ShCutDbg("subst = " + subst);
1203 res.replace("${" + capture + "}", subst);
1204 pos += rx.matchedLength();
1209 QString substitutePowerShellVars(const QString& theString)
1211 QString res = theString;
1213 QRegExp rx("%([^%]+)%"); // Match substrings enclosed by "%".
1214 rx.setMinimal(true); // Set search to non-greedy.
1215 while((pos = rx.indexIn(res, pos)) != -1) {
1216 QString capture = rx.cap(1);
1217 QString subst = Qtx::getenv(capture.toUtf8().constData());
1218 ShCutDbg("capture = " + capture);
1219 ShCutDbg("subst = " + subst);
1220 res.replace("%" + capture + "%", subst);
1221 pos += rx.matchedLength();
1226 QString substituteVars(const QString& theString)
1228 QString str = substituteBashVars(theString);
1229 return substitutePowerShellVars(str);
1232 /*static*/ std::pair<bool, SUIT_ActionAssets> SUIT_ShortcutMgr::getActionAssetsFromResources(const QString& theActionID)
1234 auto res = std::pair<bool, SUIT_ActionAssets>(false, SUIT_ActionAssets());
1236 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1238 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1242 QStringList actionAssetFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ASSET_FILE_PATHS);
1243 for (const QString& actionAssetFilePath : actionAssetFilePaths) {
1244 const QString path = substituteVars(actionAssetFilePath);
1245 QFile actionAssetFile(path);
1246 if (!actionAssetFile.open(QIODevice::ReadOnly)) {
1247 Warning("SUIT_ShortcutMgr can't open action asset file \"" + path + "\"!");
1251 QJsonParseError jsonError;
1252 QJsonDocument document = QJsonDocument::fromJson(actionAssetFile.readAll(), &jsonError);
1253 actionAssetFile.close();
1254 if(jsonError.error != QJsonParseError::NoError) {
1255 Warning("SUIT_ShortcutMgr: error during parsing of action asset file \"" + path + "\"!");
1259 if(!document.isObject()) {
1260 Warning("SUIT_ShortcutMgr: empty action asset file \"" + path + "\"!");
1264 QJsonObject object = document.object();
1265 if (object.keys().indexOf(theActionID) == -1)
1268 SUIT_ActionAssets actionAssets;
1269 if (!actionAssets.fromJSON(object[theActionID].toObject())) {
1270 ShCutDbg("Action asset file \"" + path + "\" contains invalid action assets with ID \"" + theActionID + "\".");
1274 res.second.merge(actionAssets, true);
1282 /*static*/ QString SUIT_ShortcutMgr::getLang()
1284 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1286 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1287 return DEFAULT_LANG;
1290 return resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1294 void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction)
1296 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1297 const QString& moduleID = moduleIDAndActionID.first;
1298 const QString& inModuleActionID = moduleIDAndActionID.second;
1300 if (inModuleActionID.isEmpty()) {
1301 ShCutDbg() && ShCutDbg("Attempt to register an action \"" + theAction->toolTip() + "\" with invalid ID \"" + theActionID + "\".");
1302 if (theAction->shortcut() != NO_KEYSEQUENCE)
1303 theAction->setShortcut(NO_KEYSEQUENCE);
1308 { // If an action with the same memory address was registered earlier,
1309 // clear all data about it to start registering procedure from scratch.
1310 auto itPreviousModuleAndActionID = myActionIDs.find(theAction);
1311 if (itPreviousModuleAndActionID != myActionIDs.end()) {
1312 // Clear the data from myActions.
1313 const auto& previousModuleAndActionID = itPreviousModuleAndActionID->second;
1314 auto itActions = myActions.find(previousModuleAndActionID.first);
1315 if (itActions != myActions.end()) {
1316 std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1317 auto itModuleActions = moduleActions.find(previousModuleAndActionID.second);
1318 if (itModuleActions != moduleActions.end()) {
1319 std::set<QAction*>& registeredActions = itModuleActions->second;
1320 registeredActions.erase(theAction);
1324 myActionIDs.erase(itPreviousModuleAndActionID);
1328 auto itActions = myActions.find(moduleID);
1329 if (itActions == myActions.end()) {
1330 itActions = myActions.emplace(moduleID, std::map<QString, std::set<QAction*>>()).first;
1333 std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1334 auto itModuleActions = moduleActions.find(inModuleActionID);
1335 if (itModuleActions != moduleActions.end()) {
1336 std::set<QAction*>& registeredActions = itModuleActions->second;
1337 const bool actionIsNew = registeredActions.emplace(theAction).second;
1339 myActionIDs[theAction] = moduleIDAndActionID;
1342 std::set<QAction*>& registeredActions = moduleActions[inModuleActionID];
1343 registeredActions.emplace(theAction);
1344 myActionIDs[theAction] = moduleIDAndActionID;
1347 connect(theAction, SIGNAL(destroyed(QObject*)), this, SLOT (onActionDestroyed(QObject*)));
1349 if (myShortcutContainer.hasShortcut(moduleID, inModuleActionID)) {
1350 const QKeySequence& keySequence = getKeySequence(moduleID, inModuleActionID);
1351 theAction->setShortcut(keySequence);
1355 "Action with ID \"" +
1356 (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? SUIT_ShortcutMgr::ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) +
1357 "\" is not added to default resource files."
1359 auto conflicts = myShortcutContainer.setShortcut(moduleID, inModuleActionID, theAction->shortcut(), false);
1360 if (!conflicts.empty())
1361 theAction->setShortcut(NO_KEYSEQUENCE); // Unbind any key sequence, if it was bound outside of the class and interferes with other shortcuts.
1365 void SUIT_ShortcutMgr::registerAction(QtxAction* theAction)
1367 registerAction(theAction->ID(), theAction);
1370 std::set<QAction*> SUIT_ShortcutMgr::getActions(const QString& theModuleID, const QString& theInModuleActionID) const
1372 if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
1373 std::set<QAction*> actions;
1374 for (const auto& actionAndID : myActionIDs) {
1375 if (actionAndID.second.second == theInModuleActionID)
1376 actions.emplace(actionAndID.first);
1381 const auto itActions = myActions.find(theModuleID);
1382 if (itActions == myActions.end())
1383 return std::set<QAction*>();
1385 const std::map<QString, std::set<QAction*>>& moduleActions = itActions->second;
1386 const auto itModuleActions = moduleActions.find(theInModuleActionID);
1387 if (itModuleActions == moduleActions.end())
1388 return std::set<QAction*>();
1390 return itModuleActions->second;
1394 std::pair<QString, QString> SUIT_ShortcutMgr::getModuleIDAndInModuleID(const QAction* theAction) const {
1395 const auto it = myActionIDs.find(const_cast<QAction*>(theAction));
1396 if (it == myActionIDs.end())
1397 return std::pair<QString, QString>();
1402 bool SUIT_ShortcutMgr::hasAction(const QAction* theAction) const
1404 return myActionIDs.find(const_cast<QAction*>(theAction)) != myActionIDs.end();
1407 QString SUIT_ShortcutMgr::getActionID(const QAction* theAction) const
1409 const auto it = myActionIDs.find(const_cast<QAction*>(theAction));
1410 if (it == myActionIDs.end())
1413 return SUIT_ShortcutMgr::makeActionID(it->second.first, it->second.second);
1416 void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable) const
1418 const auto itModuleActions = myActions.find(theModuleID);
1419 if (itModuleActions == myActions.end())
1422 SUIT_Application* app = SUIT_Session::session()->activeApplication();
1426 const std::map<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
1427 for (const auto& idAndActions : moduleActions) {
1428 const std::set<QAction*>& actions = idAndActions.second;
1429 for (QAction* const action : actions) {
1430 if (action->parentWidget() == (QWidget*)app->desktop()) // Is not compiled without cast or with static_cast<QWidget*>.
1431 action->setEnabled(theEnable);
1436 void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1438 SUIT_Application* app = SUIT_Session::session()->activeApplication();
1442 for (const std::pair<QAction*, std::pair<QString, QString>>& actionAndID : myActionIDs) {
1443 QAction* const action = actionAndID.first;
1444 // Is not compiled without cast or with static_cast<QWidget*>.
1445 if (action->parentWidget() == (QWidget*)app->desktop()) {
1446 const QString& inModuleActionID = actionAndID.second.second;
1447 if (inModuleActionID.startsWith(theInModuleActionIDPrefix))
1448 action->setEnabled(theEnable);
1453 void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const
1455 setActionsWithPrefixInIDEnabled(theInModuleActionIDPrefix, theEnable);
1458 void SUIT_ShortcutMgr::rebindActionsToKeySequences() const
1460 ShCutDbg() && ShCutDbg("SUIT_ShortcutMgr::rebindActionsToKeySequences()");
1461 for (const std::pair<QAction*, std::pair<QString, QString>>& actionAndID : myActionIDs) {
1462 actionAndID.first->setShortcut(getKeySequence(actionAndID.second.first, actionAndID.second.second));
1466 void SUIT_ShortcutMgr::updateShortcuts() const
1468 rebindActionsToKeySequences();
1471 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride)
1473 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID);
1474 const QString& moduleID = moduleIDAndActionID.first;
1475 const QString& inModuleActionID = moduleIDAndActionID.second;
1477 if (inModuleActionID.isEmpty()) {
1478 ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid action ID \"" + theActionID + "\".");
1479 return std::set<std::pair<QString, QString>>();
1482 return setShortcutNoIDChecks(moduleID, inModuleActionID, theKeySequence, theOverride);
1485 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
1487 if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) {
1488 ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid module ID \"" + theModuleID + "\".");
1489 return std::set<std::pair<QString, QString>>();
1492 if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) {
1493 ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid in-module action ID \"" + theInModuleActionID + "\".");
1494 return std::set<std::pair<QString, QString>>();
1497 return setShortcutNoIDChecks(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1500 const SUIT_ShortcutContainer& SUIT_ShortcutMgr::getShortcutContainer() const
1502 return myShortcutContainer;
1505 void SUIT_ShortcutMgr::mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride, bool theTreatAbsentIncomingAsDisabled)
1507 ShCutDbg() && ShCutDbg("ShortcutMgr merges shortcut container...");
1508 const auto changes = myShortcutContainer.merge(theContainer, theOverride, theTreatAbsentIncomingAsDisabled);
1509 ShCutDbg() && ShCutDbg("ShortcutMgr keeps following shortcuts:\n" + myShortcutContainer.toString());
1511 // Turn off hotkeys for disabled shortcuts.
1512 for (const auto& moduleIDAndChanges : changes) {
1513 const QString& moduleID = moduleIDAndChanges.first;
1514 const auto& moduleChanges = moduleIDAndChanges.second;
1515 for (const std::pair<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
1516 if (modifiedShortcut.second == NO_KEYSEQUENCE) {
1517 const std::set<QAction*> actions = getActions(moduleID, modifiedShortcut.first);
1518 for (QAction* const action : actions) {
1519 action->setShortcut(NO_KEYSEQUENCE);
1525 // Turn on hotkeys for enabled shortcuts.
1526 for (const auto& moduleIDAndChanges : changes) {
1527 const QString& moduleID = moduleIDAndChanges.first;
1528 const auto& moduleChanges = moduleIDAndChanges.second;
1529 for (const std::pair<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
1530 if (modifiedShortcut.second != NO_KEYSEQUENCE) {
1531 const std::set<QAction*> actions = getActions(moduleID, modifiedShortcut.first);
1532 for (QAction* const action : actions) {
1533 action->setShortcut(modifiedShortcut.second);
1539 SUIT_ShortcutMgr::saveShortcutsToPreferences(changes);
1542 QKeySequence SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const
1544 return myShortcutContainer.getKeySequence(theModuleID, theInModuleActionID);
1547 const std::map<QString, QKeySequence>& SUIT_ShortcutMgr::getModuleShortcutsInversed(const QString& theModuleID) const
1549 return myShortcutContainer.getModuleShortcutsInversed(theModuleID);
1552 std::set<QString> SUIT_ShortcutMgr::getShortcutModuleIDs() const
1554 return myShortcutContainer.getIDsOfAllModules();
1557 std::set<QString> SUIT_ShortcutMgr::getIDsOfInterferingModules(const QString& theModuleID) const
1559 return myShortcutContainer.getIDsOfInterferingModules(theModuleID);
1562 std::shared_ptr<const SUIT_ActionAssets> SUIT_ShortcutMgr::getModuleAssets(const QString& theModuleID) const
1564 const auto itModuleAssets = myModuleAssets.find(theModuleID);
1565 if (itModuleAssets == myModuleAssets.end()) {
1566 auto assets = std::shared_ptr<SUIT_ActionAssets>(new SUIT_ActionAssets());
1567 auto lda = SUIT_ActionAssets::LangDependentAssets();
1568 lda.myName = theModuleID; // At least something meaningful.
1570 assets->myLangDependentAssets.emplace(SUIT_ShortcutMgr::getLang(), lda);
1573 return itModuleAssets->second;
1576 QString SUIT_ShortcutMgr::getModuleName(const QString& theModuleID, const QString& theLang) const
1578 const auto assets = getModuleAssets(theModuleID);
1579 const auto& ldaMap = assets->myLangDependentAssets;
1583 auto itLang = ldaMap.find(theLang.isEmpty() ? SUIT_ShortcutMgr::getLang() : theLang);
1584 if (itLang == ldaMap.end())
1585 itLang = ldaMap.begin(); // Get name in any language.
1587 const auto& name = itLang->second.myName;
1588 return name.isEmpty() ? theModuleID : name;
1591 std::shared_ptr<const SUIT_ActionAssets> SUIT_ShortcutMgr::getActionAssets(const QString& theModuleID, const QString& theInModuleActionID) const
1593 const QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID);
1594 if (actionID.isEmpty()) {
1595 ShCutDbg() && ShCutDbg("Can't get action assets: either/both module ID \"" + theModuleID + "\" or/and in-module action ID \"" + theInModuleActionID + "\" is/are invalid.");
1596 return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
1598 return getActionAssets(actionID);
1601 std::shared_ptr<const SUIT_ActionAssets> SUIT_ShortcutMgr::getActionAssets(const QString& theActionID) const
1603 const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID);
1604 const QString& moduleID = moduleIDAndActionID.first;
1605 const QString& inModuleActionID = moduleIDAndActionID.second;
1607 if (inModuleActionID.isEmpty()) {
1608 ShCutDbg() && ShCutDbg("Attempt to get assets of an action with invalid ID \"" + theActionID + "\".");
1609 return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
1612 const auto itModuleActionAssets = myActionAssets.find(moduleID);
1613 if (itModuleActionAssets == myActionAssets.end())
1614 return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
1616 const auto moduleActionAssets = itModuleActionAssets->second;
1617 const auto itActionAssets = moduleActionAssets.find(inModuleActionID);
1618 if (itActionAssets == moduleActionAssets.end())
1619 return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
1621 return itActionAssets->second;
1625 QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang) const
1627 const QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID);
1628 if (actionID.isEmpty()) {
1629 ShCutDbg() && ShCutDbg("Can't get action name: either/both module ID \"" + theModuleID + "\" or/and in-module action ID \"" + theInModuleActionID + "\" is/are invalid.");
1633 const auto itModuleActionAssets = myActionAssets.find(theModuleID);
1634 if (itModuleActionAssets == myActionAssets.end())
1637 const auto moduleActionAssets = itModuleActionAssets->second;
1638 const auto itActionAssets = moduleActionAssets.find(theInModuleActionID);
1639 if (itActionAssets != moduleActionAssets.end() && !itActionAssets->second->myLangDependentAssets.empty()) {
1640 const auto& ldaMap = itActionAssets->second->myLangDependentAssets;
1642 return theInModuleActionID;
1644 auto itLang = ldaMap.find(theLang.isEmpty() ? SUIT_ShortcutMgr::getLang() : theLang);
1645 if (itLang == ldaMap.end())
1646 itLang = ldaMap.begin(); // Get name in any language.
1648 const auto& name = itLang->second.myName;
1649 return name.isEmpty() ? theInModuleActionID : name;
1651 else /* if action assets have not been loaded. */ {
1652 // Try to get action->text() and use it as a name.
1654 // Pitfall of the approach: at the time this code block is called, the action may not exist.
1655 // Moreover, an action with such an ID may not even have been created at the time of calling this method.
1656 // Thus, even buffering of assets of every action ever created at runtime does not guarantee,
1657 // that the assets will be available at any point in the life of the application,
1658 // unless the assets are added to dedicated section in an asset file.
1660 const auto actions = getActions(theModuleID, theInModuleActionID);
1661 for (const auto& action : actions) {
1662 if (!action->text().isEmpty())
1663 return action->text();
1665 return theInModuleActionID;
1669 void SUIT_ShortcutMgr::onActionDestroyed(QObject* theObject)
1671 QAction* action = static_cast<QAction*>(theObject);
1673 auto itID = myActionIDs.find(action);
1674 if (itID == myActionIDs.end())
1677 const QString& moduleID = itID->second.first;
1678 const QString& inModuleActionID = itID->second.second;
1680 auto itModuleActions = myActions.find(moduleID);
1681 if (itModuleActions != myActions.end()) {
1682 std::map<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
1683 auto itActions = moduleActions.find(inModuleActionID);
1684 if (itActions != moduleActions.end()) {
1685 std::set<QAction*>& actions = itActions->second;
1686 actions.erase(action);
1690 myActionIDs.erase(itID);
1693 bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent)
1696 if (theEvent->type() == QEvent::ActionAdded) {
1697 auto anActionEvent = static_cast<QActionEvent*>(theEvent);
1699 QtxAction* aQtxAction = qobject_cast<QtxAction*>(anActionEvent->action());
1701 #ifdef SHORTCUT_MGR_DBG
1703 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID());
1704 if (moduleIDAndActionID.second.isEmpty())
1705 ShCutDbg("ActionAdded event, but ID of the action is invalid. Action name = \"" + aQtxAction->toolTip() + "\", ID = \"" + aQtxAction->ID() + "\".");
1706 else if (!myShortcutContainer.hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second))
1707 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 + "\".");
1709 #endif//SHORTCUT_MGR_DBG
1710 #ifdef SHORTCUT_MGR_DEVTOOLS
1712 DevTools::get()->collectShortcutAndAssets(aQtxAction);
1713 const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID());
1714 if (moduleIDAndActionID.second.isEmpty())
1715 DevTools::get()->collectAssetsOfActionWithInvalidID(aQtxAction);
1717 #endif//SHORTCUT_MGR_DEVTOOLS
1718 registerAction(aQtxAction);
1721 QAction* aQAction = qobject_cast<QAction*>(anActionEvent->action());
1722 #ifdef SHORTCUT_MGR_DEVTOOLS
1724 DevTools::get()->collectAssetsOfActionWithInvalidID(aQAction);
1725 #endif//SHORTCUT_MGR_DEVTOOLS
1726 if (aQAction && aQAction->shortcut() != NO_KEYSEQUENCE) {
1727 #ifdef SHORTCUT_MGR_DBG
1728 ShCutDbg("ActionAdded event, but the added action is not QtxAction and bound to non-empty key sequence. name: \"" + aQAction->toolTip() + "\".");
1729 #endif//SHORTCUT_MGR_DBG
1730 // Since non-QtxAction has no ID, it is impossible to properly manage its shortcut.
1731 // And the shortcut may interfere with managed ones.
1732 aQAction->setShortcut(NO_KEYSEQUENCE);
1738 return QObject::eventFilter(theObject, theEvent);
1741 std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride)
1743 std::set<std::pair<QString, QString>> disabledShortcutsIDs = myShortcutContainer.setShortcut(theModuleID, theInModuleActionID, theKeySequence, theOverride);
1745 if (theOverride || disabledShortcutsIDs.empty()) {
1746 // Bind actions to corresponding modified key sequences. Save changes to preferences.
1748 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
1749 std::map<QString, std::map<QString, QKeySequence>> modifiedShortcuts;
1751 for (const auto& moduleIDAndActionID : disabledShortcutsIDs) {
1752 // Unbind actions of disabled shortcuts.
1754 const QString& moduleID = moduleIDAndActionID.first;
1755 const QString& inModuleActionID = moduleIDAndActionID.second;
1757 std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[moduleID];
1758 modifiedModuleShortcuts[inModuleActionID] = NO_KEYSEQUENCE;
1760 const std::set<QAction*> actions = getActions(moduleID, inModuleActionID);
1761 for (QAction* const action : actions) {
1762 action->setShortcut(NO_KEYSEQUENCE);
1766 { // Bind actions to theKeySequence.
1767 std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[theModuleID];
1768 modifiedModuleShortcuts[theInModuleActionID] = theKeySequence;
1770 const std::set<QAction*> actions = getActions(theModuleID, theInModuleActionID);
1771 for (QAction* const action : actions) {
1772 action->setShortcut(theKeySequence);
1776 SUIT_ShortcutMgr::saveShortcutsToPreferences(modifiedShortcuts);
1779 return disabledShortcutsIDs;
1782 void SUIT_ShortcutMgr::setShortcutsFromPreferences()
1784 ShCutDbg() && ShCutDbg("ShortcutMgr is initializing...");
1786 SUIT_ShortcutContainer container;
1787 SUIT_ShortcutMgr::fillContainerFromPreferences(container, false /*theDefaultOnly*/);
1788 mergeShortcutContainer(container, true /*theOverrde*/, false /*theTreatAbsentIncomingAsDisabled*/);
1789 setAssetsFromResources();
1791 ShCutDbg() && ShCutDbg("ShortcutMgr has been initialized.");
1794 /*static*/ void SUIT_ShortcutMgr::saveShortcutsToPreferences(const std::map<QString, std::map<QString, QKeySequence>>& theShortcutsInversed)
1796 ShCutDbg() && ShCutDbg("Saving preferences to resources.");
1798 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1800 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1804 for (const auto& moduleIDAndShortcutsInversed : theShortcutsInversed) {
1805 const auto& moduleID = moduleIDAndShortcutsInversed.first;
1806 const auto& moduleShortcutsInversed = moduleIDAndShortcutsInversed.second;
1807 for (const auto& shortcutInversed : moduleShortcutsInversed) {
1808 if (shortcutInversed.first.isEmpty()) {
1809 ShCutDbg("Attempt to serialize a shortcut with empty action ID.");
1813 const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID;
1814 resMgr->setValue(sectionName, shortcutInversed.first, shortcutInversed.second.toString());
1816 ShCutDbg() && ShCutDbg("Saving shortcut: \"" + moduleID + "\"\t\"" + shortcutInversed.first + "\"\t\"" + shortcutInversed.second.toString() + "\"");
1821 void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
1823 ShCutDbg() && ShCutDbg("Retrieving action assets.");
1825 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1827 Warning("SUIT_ShortcutMgr can't retrieve resource manager!");
1831 if (theLanguage.isEmpty())
1832 theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG);
1834 QStringList langPriorityList = LANG_PRIORITY_LIST;
1835 langPriorityList.push_front(theLanguage);
1836 langPriorityList.removeDuplicates();
1838 QStringList actionAssetFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ASSET_FILE_PATHS);
1839 #ifdef SHORTCUT_MGR_DBG
1840 ShCutDbg("Asset files: " + actionAssetFilePaths.join(", ") + ".");
1842 for (const QString& actionAssetFilePath : actionAssetFilePaths) {
1843 const QString path = substituteVars(actionAssetFilePath);
1844 QFile actionAssetFile(path);
1845 if (!actionAssetFile.open(QIODevice::ReadOnly)) {
1846 Warning("SUIT_ShortcutMgr can't open action asset file \"" + path + "\"!");
1850 QJsonParseError jsonError;
1851 QJsonDocument document = QJsonDocument::fromJson(actionAssetFile.readAll(), &jsonError);
1852 actionAssetFile.close();
1853 if(jsonError.error != QJsonParseError::NoError) {
1854 Warning("SUIT_ShortcutMgr: error during parsing of action asset file \"" + path + "\"!");
1858 if(document.isObject()) {
1859 QJsonObject object = document.object();
1860 SUIT_ActionAssets actionAssets;
1861 for (const QString& actionID : object.keys()) {
1862 const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(actionID);
1863 const QString& moduleID = moduleIDAndActionID.first;
1864 const QString& inModuleActionID = moduleIDAndActionID.second;
1866 if (inModuleActionID.isEmpty()) {
1867 ShCutDbg("Action asset file \"" + path + "\" contains invalid action ID \"" + actionID + "\".");
1871 if (!actionAssets.fromJSON(object[actionID].toObject())) {
1872 ShCutDbg("Action asset file \"" + path + "\" contains invalid action assets with ID \"" + actionID + "\".");
1876 const bool nameInCurLangExists = actionAssets.myLangDependentAssets.find(theLanguage) != actionAssets.myLangDependentAssets.end();
1877 if (nameInCurLangExists) {
1878 actionAssets.clearAllLangsExcept(theLanguage);
1881 bool nameInLinguaFrancaExists = false;
1882 QString usedLanguage = QString();
1883 for (int i = 1; i < langPriorityList.length(); i++) {
1884 nameInLinguaFrancaExists = actionAssets.myLangDependentAssets.find(langPriorityList[i]) != actionAssets.myLangDependentAssets.end();
1885 if (nameInLinguaFrancaExists) {
1886 usedLanguage = langPriorityList[i];
1887 actionAssets.clearAllLangsExcept(usedLanguage);
1892 #ifdef SHORTCUT_MGR_DBG
1893 if (nameInLinguaFrancaExists)
1894 ShCutDbg("Can't find assets for action with ID \"" + actionID + "\" at current (" + theLanguage + ") language. Assets in " + usedLanguage + " is used for the action." );
1896 ShCutDbg("Can't find assets for action with ID \"" + actionID + "\". Tried " + langPriorityList.join(", ") + " languages." );
1902 auto& moduleActionAssets = myActionAssets[moduleID];
1903 auto itAssets = moduleActionAssets.find(inModuleActionID);
1904 if (itAssets == moduleActionAssets.end()) {
1905 auto pAssets = std::shared_ptr<SUIT_ActionAssets>(new SUIT_ActionAssets(actionAssets));
1906 itAssets = moduleActionAssets.emplace(inModuleActionID, pAssets).first;
1909 itAssets->second->merge(actionAssets, true);
1911 const auto& assets = itAssets->second;
1912 if (!assets->myIconPath.isEmpty() && assets->myIcon.isNull())
1913 assets->myIcon = QIcon(substituteVars(assets->myIconPath));
1918 #ifdef SHORTCUT_MGR_DBG
1919 ShCutDbg("Parsed assets: ");
1921 for (const auto& moduleIDAndAssets : myActionAssets) {
1922 for (const auto& actionIDAndAssets : moduleIDAndAssets.second) {
1923 actionIDAndAssets.second->toJSON(object);
1924 QJsonDocument doc(object);
1925 QString strJson = doc.toJson(QJsonDocument::Indented);
1926 const QString actionID = SUIT_ShortcutMgr::makeActionID(moduleIDAndAssets.first, actionIDAndAssets.first);
1927 ShCutDbg(actionID + " : " + strJson);
1932 // Fill myModuleAssets.
1933 for (const auto& moduleID : myShortcutContainer.getIDsOfAllModules()) {
1934 const auto assets = std::shared_ptr<SUIT_ActionAssets>(new SUIT_ActionAssets());
1935 auto& lda = assets->myLangDependentAssets[DEFAULT_LANG];
1937 if (moduleID == SUIT_ShortcutMgr::ROOT_MODULE_ID) {
1938 lda.myName = tr("General");
1942 if (resMgr->value("resources", "LightApp", dirPath)) {
1943 assets->myIconPath = dirPath + (!dirPath.isEmpty() && dirPath[dirPath.length() - 1] == "/" ? "" : "/") + "icon_default.png";
1944 assets->myIcon = QIcon(substituteVars(assets->myIconPath));
1949 QString moduleName = moduleID;
1950 resMgr->value(moduleID, "name", moduleName);
1951 lda.myName = moduleName;
1953 resMgr->value(moduleID, "description", lda.myToolTip);
1958 if (resMgr->value("resources", moduleID, dirPath) && resMgr->value(moduleID, "icon", fileName)) {
1959 assets->myIconPath = dirPath + (!dirPath.isEmpty() && dirPath[dirPath.length() - 1] == "/" ? "" : "/") + fileName;
1960 assets->myIcon = QIcon(substituteVars(assets->myIconPath));
1965 myModuleAssets.emplace(moduleID, std::move(assets));
1971 SUIT_SentenceMatcher::SUIT_SentenceMatcher()
1973 myUseExactWordOrder = false;
1974 myUseFuzzyWords = true;
1975 myIsCaseSensitive = false;
1978 void SUIT_SentenceMatcher::setUseExactWordOrder(bool theOn)
1980 if (myUseExactWordOrder == theOn)
1983 myUseExactWordOrder = theOn;
1985 myPermutatedSentences.clear();
1986 myFuzzyPermutatedSentences.clear();
1990 if (myPermutatedSentences.isEmpty())
1991 SUIT_SentenceMatcher::makePermutatedSentences(myWords, myPermutatedSentences);
1993 if (myUseFuzzyWords && myFuzzyPermutatedSentences.isEmpty())
1994 SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
1997 void SUIT_SentenceMatcher::setUseFuzzyWords(bool theOn)
1999 if (myUseFuzzyWords == theOn)
2002 myUseFuzzyWords = theOn;
2003 if (myWords.isEmpty() || !theOn) {
2004 myFuzzyWords.clear();
2005 myFuzzyPermutatedSentences.clear();
2009 myFuzzyWords.clear();
2010 SUIT_SentenceMatcher::makeFuzzyWords(myWords, myFuzzyWords);
2012 if (!myUseExactWordOrder) {
2013 myFuzzyPermutatedSentences.clear();
2014 SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
2018 void SUIT_SentenceMatcher::setCaseSensitive(bool theOn)
2020 myIsCaseSensitive = theOn;
2023 void SUIT_SentenceMatcher::setQuery(QString theQuery)
2025 theQuery = theQuery.simplified();
2026 if (theQuery == myQuery)
2030 myWords = theQuery.split(" ", QString::SkipEmptyParts);
2032 { // Set permutated sentences.
2033 myPermutatedSentences.clear();
2034 if (!myUseExactWordOrder)
2035 SUIT_SentenceMatcher::makePermutatedSentences(myWords, myPermutatedSentences);
2038 // Set fuzzy words and sentences.
2039 myFuzzyWords.clear();
2040 myFuzzyPermutatedSentences.clear();
2042 if (myUseFuzzyWords) {
2043 SUIT_SentenceMatcher::makeFuzzyWords(myWords, myFuzzyWords);
2044 if (!myUseExactWordOrder)
2045 SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
2049 double SUIT_SentenceMatcher::match(const QString& theInputString) const
2052 if (myUseExactWordOrder) {
2053 n = SUIT_SentenceMatcher::match(theInputString, myWords, myIsCaseSensitive);
2054 if (n != theInputString.length() && myUseFuzzyWords) {
2055 const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyWords, myIsCaseSensitive);
2060 else /* if match with permutated query sentences */ {
2061 n = SUIT_SentenceMatcher::match(theInputString, myPermutatedSentences, myIsCaseSensitive);
2062 if (n != theInputString.length() && myUseFuzzyWords) {
2063 const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyPermutatedSentences, myIsCaseSensitive);
2070 return std::numeric_limits<double>::infinity();
2072 const auto strLength = theInputString.length() > myQuery.length() ? theInputString.length() : myQuery.length();
2075 return 0; // Exact match or almost exact.
2077 return double(strLength - n);
2080 QString SUIT_SentenceMatcher::toString() const
2082 QString res = QString("myUseExactWordOrder: ") + (myUseExactWordOrder ? "true" : "false") + ";\n";
2083 res += QString("myUseFuzzyWords: ") + (myUseFuzzyWords ? "true" : "false") + ";\n";
2084 res += QString("myIsCaseSensitive: ") + (myIsCaseSensitive ? "true" : "false") + ";\n";
2085 res += QString("myQuery: ") + myQuery + ";\n";
2086 res += QString("myWords: ") + myWords.join(", ") + ";\n";
2087 res += QString("myFuzzyWords: ") + myFuzzyWords.join(", ") + ";\n";
2089 res += "myPermutatedSentences:\n";
2090 for (const auto& sentence : myPermutatedSentences) {
2091 res += "\t" + sentence.join(", ") + ";\n";
2094 res += "myFuzzyPermutatedSentences:\n";
2095 for (const auto& sentence : myFuzzyPermutatedSentences) {
2096 res += "\t" + sentence.join(", ") + ";\n";
2103 /*static*/ bool SUIT_SentenceMatcher::makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences)
2105 theSentences.clear();
2106 theSentences.push_back(theWords);
2107 QStringList nextPerm = theWords;
2108 QStringList prevPerm = theWords;
2110 bool hasNextPerm = true;
2111 bool hasPrevPerm = true;
2113 while (hasNextPerm || hasPrevPerm) {
2115 hasNextPerm = std::next_permutation(nextPerm.begin(), nextPerm.end());
2117 if (hasNextPerm && !theSentences.contains(nextPerm))
2118 theSentences.push_back(nextPerm);
2121 hasPrevPerm = std::prev_permutation(prevPerm.begin(), prevPerm.end());
2123 if (hasPrevPerm && !theSentences.contains(prevPerm))
2124 theSentences.push_back(prevPerm);
2127 return theSentences.size() > 1;
2130 /*static*/ void SUIT_SentenceMatcher::makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords)
2132 theFuzzyWords.clear();
2133 for (const QString& word : theWords) {
2135 for (int i = 0; i < word.size(); i++) {
2136 fuzzyWord += word[i];
2137 fuzzyWord += "\\w*";
2139 theFuzzyWords.push_back(fuzzyWord);
2143 /*static*/ int SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive)
2145 const QRegExp regExp("^" + theSentence.join("\\w*\\W+"), theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
2146 regExp.indexIn(theInputString);
2147 const int matchMetrics = regExp.matchedLength();
2148 return matchMetrics > 0 ? matchMetrics : 0;
2151 /*static*/ int SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive)
2154 for (const QStringList& sentence : theSentences) {
2155 const int matchMetrics = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, sentence, theCaseSensitive);
2156 if (matchMetrics > res) {
2158 if (res == theInputString.length())
2165 /*static*/ int SUIT_SentenceMatcher::matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive)
2168 for (const QString& word : theWords) {
2169 const auto regExp = QRegExp(word, theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
2170 regExp.indexIn(theInputString);
2171 const int matchMetrics = regExp.matchedLength();
2172 // The same input word can be counted multiple times. Nobody cares.
2173 if (matchMetrics > 0)
2174 res += matchMetrics;
2179 /*static*/ int SUIT_SentenceMatcher::match(
2180 const QString& theInputString,
2181 const QStringList& theSentence,
2182 bool theCaseSensitive
2184 int res = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, theSentence, theCaseSensitive);
2185 if (res == theInputString.length())
2188 const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentence, theCaseSensitive);
2189 if (matchMetrics > res)
2195 /*static*/ int SUIT_SentenceMatcher::match(
2196 const QString& theInputString,
2197 const QList<QStringList>& theSentences,
2198 bool theCaseSensitive
2200 int res = SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(theInputString, theSentences, theCaseSensitive);
2201 if (res == theInputString.length())
2204 if (theSentences.size() > 0) {
2205 const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentences[0], theCaseSensitive);
2206 if (matchMetrics > res)
2214 SUIT_ActionSearcher::AssetsAndSearchData::AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets, double theMatchMetrics)
2215 : myAssets(theAssets), myMatchMetrics(theMatchMetrics)
2217 if (theMatchMetrics < 0) {
2218 myMatchMetrics = std::numeric_limits<double>::infinity();
2219 ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
2223 void SUIT_ActionSearcher::AssetsAndSearchData::setMatchMetrics(double theMatchMetrics)
2225 if (theMatchMetrics < 0) {
2226 myMatchMetrics = std::numeric_limits<double>::infinity();
2227 ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
2231 myMatchMetrics = theMatchMetrics;
2234 void SUIT_ActionSearcher::AssetsAndSearchData::toJSON(QJsonObject& oJsonObject) const
2236 oJsonObject["myMatchMetrics"] = myMatchMetrics;
2239 QJsonObject assetsJSON;
2240 myAssets->toJSON(assetsJSON);
2241 oJsonObject["myAssets"] = assetsJSON;
2245 QString SUIT_ActionSearcher::AssetsAndSearchData::toString() const
2249 QJsonDocument doc(json);
2250 return QString(doc.toJson(QJsonDocument::Indented));
2253 SUIT_ActionSearcher::SUIT_ActionSearcher()
2255 myIncludedModuleIDs = { SUIT_ShortcutMgr::ROOT_MODULE_ID };
2256 myIncludeDisabledActions = false;
2257 myFieldsToMatch = { SUIT_ActionSearcher::MatchField::Name, SUIT_ActionSearcher::MatchField::ToolTip };
2258 myMatcher.setCaseSensitive(false);
2259 myMatcher.setUseExactWordOrder(false);
2260 myMatcher.setUseFuzzyWords(true);
2263 bool SUIT_ActionSearcher::setIncludedModuleIDs(std::set<QString> theIncludedModuleIDs)
2265 ShCutDbg("SUIT_ActionSearcher::setIncludedModuleIDs");
2267 if (myIncludedModuleIDs == theIncludedModuleIDs)
2270 myIncludedModuleIDs = theIncludedModuleIDs;
2273 // Erase search results from excluded modules. Erase IDs of modules, which are already in search results, from theIncludedModuleIDs.
2274 for (auto itFound = mySearchResults.begin(); itFound != mySearchResults.end(); ) {
2275 const auto itModuleID = theIncludedModuleIDs.find(itFound->first);
2276 if (itModuleID == theIncludedModuleIDs.end()) {
2277 itFound = mySearchResults.erase(itFound);
2282 theIncludedModuleIDs.erase(itModuleID);
2286 // Filter assets of added modules.
2287 const auto& allAssets = SUIT_ShortcutMgr::get()->getActionAssets();
2288 for (const auto& moduleIDAndAssets : allAssets) {
2289 const QString& moduleID = moduleIDAndAssets.first;
2290 const auto& actionIDsAndAssets = moduleIDAndAssets.second;
2291 if (theIncludedModuleIDs.find(moduleID) == theIncludedModuleIDs.end())
2294 for (const auto& actionIDAndAssets : actionIDsAndAssets) {
2295 const QString& inModuleActionID = actionIDAndAssets.first;
2296 const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
2297 if (matchMetrics < std::numeric_limits<double>::infinity()) {
2298 mySearchResults[moduleID][inModuleActionID] = SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics);
2304 ShCutDbg() && ShCutDbg(toString());
2309 bool SUIT_ActionSearcher::includeDisabledActions(bool theOn)
2311 ShCutDbg("SUIT_ActionSearcher::includeDisabledActions");
2313 if (myIncludeDisabledActions == theOn)
2316 myIncludeDisabledActions = theOn;
2319 if (myIncludeDisabledActions)
2320 res = extendResults();
2322 res = filterResults().first;
2324 ShCutDbg() && ShCutDbg(toString());
2328 bool SUIT_ActionSearcher::setFieldsToMatch(const std::set<SUIT_ActionSearcher::MatchField>& theFields)
2330 if (myFieldsToMatch == theFields)
2333 if (theFields.empty()) {
2334 myFieldsToMatch = theFields;
2335 mySearchResults.clear();
2339 bool narrows = true;
2340 for (const SUIT_ActionSearcher::MatchField field : theFields) {
2341 if (myFieldsToMatch.find(field) == myFieldsToMatch.end()) {
2347 bool extends = true;
2348 for (const SUIT_ActionSearcher::MatchField field : myFieldsToMatch) {
2349 if (theFields.find(field) == theFields.end()) {
2355 myFieldsToMatch = theFields;
2359 res = filterResults().first;
2361 res = extendResults();
2363 res = filter().first;
2365 ShCutDbg() && ShCutDbg(toString());
2369 bool SUIT_ActionSearcher::setCaseSensitive(bool theOn)
2371 if (myMatcher.isCaseSensitive() == theOn)
2374 myMatcher.setCaseSensitive(theOn);
2378 res = filterResults().first;
2380 res = extendResults();
2382 ShCutDbg() && ShCutDbg(toString());
2386 bool SUIT_ActionSearcher::setQuery(const QString& theQuery)
2388 ShCutDbg("SUIT_ActionSearcher::setQuery");
2390 if (theQuery.simplified() == myMatcher.getQuery().simplified())
2393 myMatcher.setQuery(theQuery);
2394 bool res = filter().first;
2395 ShCutDbg() && ShCutDbg(toString());
2399 const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& SUIT_ActionSearcher::getSearchResults() const
2401 return mySearchResults;
2404 std::pair<bool, bool> SUIT_ActionSearcher::filter()
2406 ShCutDbg("SUIT_ActionSearcher::filter()");
2408 auto res = std::pair<bool, bool>(false, false);
2410 for (const auto& moduleIDAndAssets : SUIT_ShortcutMgr::get()->getActionAssets()) {
2411 const auto& moduleID = moduleIDAndAssets.first;
2412 if (myIncludedModuleIDs.find(moduleID) == myIncludedModuleIDs.end())
2415 const auto& actionIDsAndAssets = moduleIDAndAssets.second;
2417 auto itFoundModuleIDAndAssets = mySearchResults.find(moduleID);
2418 for (const auto& actionIDAndAssets : actionIDsAndAssets) {
2419 const QString& inModuleActionID = actionIDAndAssets.first;
2421 if (itFoundModuleIDAndAssets != mySearchResults.end()) {
2422 auto& foundActionIDsAndAssets = itFoundModuleIDAndAssets->second;
2423 auto itFoundActionIDAndAssets = foundActionIDsAndAssets.find(inModuleActionID);
2424 if (itFoundActionIDAndAssets != foundActionIDsAndAssets.end()) {
2425 // Action is already in search results.
2426 SUIT_ActionSearcher::AssetsAndSearchData& aAndD = itFoundActionIDAndAssets->second;
2427 const double matchMetrics = matchAction(moduleID, inModuleActionID, aAndD.myAssets);
2428 if (matchMetrics < std::numeric_limits<double>::infinity()) {
2429 if (matchMetrics != aAndD.matchMetrics()) {
2430 aAndD.setMatchMetrics(matchMetrics);
2434 else /* if n == 0 */ {
2435 foundActionIDsAndAssets.erase(itFoundActionIDAndAssets);
2442 const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
2443 if (matchMetrics < std::numeric_limits<double>::infinity()) {
2444 if (itFoundModuleIDAndAssets == mySearchResults.end())
2445 itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
2447 itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
2456 std::pair<bool, bool> SUIT_ActionSearcher::filterResults()
2458 auto res = std::pair<bool, bool>(false, false);
2460 for (auto itFoundModuleIDAndAssets = mySearchResults.begin(); itFoundModuleIDAndAssets != mySearchResults.end(); ) {
2461 const QString& moduleID = itFoundModuleIDAndAssets->first;
2462 auto& actionIDsAndAssets = itFoundModuleIDAndAssets->second;
2463 for (auto itActionIDAndAssets = actionIDsAndAssets.begin(); itActionIDAndAssets != actionIDsAndAssets.end(); ) {
2464 const QString& inModuleActionID = itActionIDAndAssets->first;
2465 SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = itActionIDAndAssets->second;
2466 const double matchMetrics = matchAction(moduleID, inModuleActionID, assetsAndSearchData.myAssets);
2467 if (matchMetrics == std::numeric_limits<double>::infinity()) {
2468 itActionIDAndAssets = actionIDsAndAssets.erase(itActionIDAndAssets);
2472 if (assetsAndSearchData.matchMetrics() != matchMetrics) {
2473 assetsAndSearchData.setMatchMetrics(matchMetrics);
2476 itActionIDAndAssets++;
2480 if (actionIDsAndAssets.empty())
2481 itFoundModuleIDAndAssets = mySearchResults.erase(itFoundModuleIDAndAssets);
2483 itFoundModuleIDAndAssets++;
2489 bool SUIT_ActionSearcher::extendResults()
2491 ShCutDbg("SUIT_ActionSearcher::extendResults()");
2494 for (const auto& moduleIDAndAssets : SUIT_ShortcutMgr::get()->getActionAssets()) {
2495 const auto& moduleID = moduleIDAndAssets.first;
2496 if (myIncludedModuleIDs.find(moduleID) == myIncludedModuleIDs.end())
2499 const auto& actionIDsAndAssets = moduleIDAndAssets.second;
2501 auto itFoundModuleIDAndAssets = mySearchResults.find(moduleID);
2502 for (const auto& actionIDAndAssets : actionIDsAndAssets) {
2503 const QString& inModuleActionID = actionIDAndAssets.first;
2505 if (itFoundModuleIDAndAssets != mySearchResults.end()) {
2506 const auto& foundActionIDsAndAssets = itFoundModuleIDAndAssets->second;
2507 if (foundActionIDsAndAssets.find(inModuleActionID) != foundActionIDsAndAssets.end())
2508 continue; // Action is already in search results.
2511 ShCutDbg() && ShCutDbg("SUIT_ActionSearcher::extendResults(): " + moduleID + "/" + inModuleActionID + "." );
2512 const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
2513 if (matchMetrics < std::numeric_limits<double>::infinity()) {
2514 ShCutDbg("SUIT_ActionSearcher::extendResults(): match, metrics = " + QString::fromStdString(std::to_string(matchMetrics)));
2515 if (itFoundModuleIDAndAssets == mySearchResults.end())
2516 itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
2518 itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
2526 double SUIT_ActionSearcher::matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets)
2529 ShCutDbg("WARNING: SUIT_ActionSearcher::matchAction: theAssets is nullptr.");
2530 return std::numeric_limits<double>::infinity();
2533 if (!myIncludeDisabledActions) {
2534 const auto& actions = SUIT_ShortcutMgr::get()->getActions(theModuleID, theInModuleActionID);
2535 const bool actionEnabled = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); } ) != actions.end();
2537 return std::numeric_limits<double>::infinity();
2540 double res = std::numeric_limits<double>::infinity();
2542 for (const auto& langAndLDA : theAssets->myLangDependentAssets) {
2543 if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ToolTip) != myFieldsToMatch.end()) {
2544 const double matchMetrics = myMatcher.match(langAndLDA.second.myToolTip);
2545 if (matchMetrics < res)
2549 if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::Name) != myFieldsToMatch.end()) {
2550 const double matchMetrics = myMatcher.match(langAndLDA.second.myName);
2551 if (matchMetrics < res)
2556 if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ID) != myFieldsToMatch.end()) {
2557 const double matchMetrics = myMatcher.match(SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID));
2558 if (matchMetrics < res)
2562 if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::KeySequence) != myFieldsToMatch.end()) {
2563 const QString keySequence = SUIT_ShortcutMgr::get()->getKeySequence(theModuleID, theInModuleActionID).toString();
2564 const double matchMetrics = myMatcher.match(keySequence);
2565 if (matchMetrics < res)
2572 QString SUIT_ActionSearcher::toString() const
2576 res += "myMatcher: {\n";
2577 res += myMatcher.toString();
2580 res += "myIncludedModuleIDs: ";
2581 for (const QString& moduleID : myIncludedModuleIDs) {
2582 res += moduleID + ", ";
2586 res += QString("myIncludeDisabledActions: ") + (myIncludeDisabledActions ? "true" : "false") + ";\n";
2588 res += "myFieldsToMatch: ";
2589 for (const auto& field : myFieldsToMatch) {
2590 res += QString::number(int(field)) + ", ";
2594 res += "mySearchResults:\n";
2595 for (const auto& moduleIDAndAssets : mySearchResults ) {
2596 res += "\tModule ID: " + moduleIDAndAssets.first + ":\n";
2597 for (const auto& actionIDAndAssets : moduleIDAndAssets.second) {
2598 const auto& assetsAndSearchData = actionIDAndAssets.second;
2599 res += "\t\tAction ID: " + actionIDAndAssets.first + ": {";
2600 res += "\t\t: " + actionIDAndAssets.second.toString();