From aa6c4e6039636c0ad4b2d0756e7c6aa35a677b2f Mon Sep 17 00:00:00 2001 From: dish Date: Wed, 3 Jul 2024 10:47:00 +0000 Subject: [PATCH] [bos #42109][CEA] (2024) Import med and show python console shortcuts does not work anymore. Anonymous shortcuts are not disabled, unless they are in clash with preference shortcuts. --- src/LightApp/LightApp_Application.cxx | 4 +- src/LightApp/LightApp_Module.cxx | 4 +- src/SUIT/SUIT_ShortcutMgr.cxx | 323 +++++++++++++++++++++--- src/SUIT/SUIT_ShortcutMgr.h | 58 ++++- src/SalomeApp/SalomeApp_Application.cxx | 4 +- 5 files changed, 337 insertions(+), 56 deletions(-) diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index a83faafc4..5c17af98d 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -2576,7 +2576,7 @@ QWidget* LightApp_Application::createWindow( const int flag ) ob->treeView()->header()->setSectionResizeMode(SUIT_DataObject::VisibilityId, QHeaderView::Fixed); ob->treeView()->header()->moveSection(SUIT_DataObject::NameId,SUIT_DataObject::VisibilityId); ob->treeView()->setColumnWidth(SUIT_DataObject::VisibilityId, VISIBILITY_COLUMN_WIDTH); - ob->setProperty( "shortcut", QKeySequence( "Alt+Shift+O" ) ); + ob->setProperty( "shortcut", QKeySequence( "Ctrl+Shift+O" ) ); wid = ob; ob->connectPopupRequest( this, SLOT( onConnectPopupRequest( SUIT_PopupClient*, QContextMenuEvent* ) ) ); } @@ -2596,7 +2596,7 @@ QWidget* LightApp_Application::createWindow( const int flag ) pyCons->setFont( resMgr->fontValue( "PyConsole", "font" ) ); pyCons->setIsShowBanner( resMgr->booleanValue( "PyConsole", "show_banner", true ) ); pyCons->setAutoCompletion( resMgr->booleanValue( "PyConsole", "auto_completion", true ) ); - pyCons->setProperty( "shortcut", QKeySequence( "Alt+Shift+P" ) ); + pyCons->setProperty( "shortcut", QKeySequence( "Ctrl+Shift+P" ) ); wid = pyCons; } #endif diff --git a/src/LightApp/LightApp_Module.cxx b/src/LightApp/LightApp_Module.cxx index fe3abbf30..903a037ca 100644 --- a/src/LightApp/LightApp_Module.cxx +++ b/src/LightApp/LightApp_Module.cxx @@ -242,7 +242,7 @@ bool LightApp_Module::activateModule( SUIT_Study* study ) if ( action(myErase) ) action(myErase)->setEnabled(true); - application()->shortcutMgr()->setActionsWithPrefixInIDEnabled( moduleName() ); + application()->shortcutMgr()->setActionsOfModuleEnabled( name() ); /* BUG 0020498 : The Entry column is always shown at module activation The registration of column is moved into LightApp_Application @@ -282,7 +282,7 @@ bool LightApp_Module::deactivateModule( SUIT_Study* study ) if ( action(myErase) ) action(myErase)->setEnabled(false); - application()->shortcutMgr()->setActionsWithPrefixInIDEnabled( moduleName(), false ); + application()->shortcutMgr()->setActionsOfModuleEnabled( name(), false ); /* BUG 0020498 : The Entry column is always shown at module activation QString EntryCol = QObject::tr( "ENTRY_COLUMN" ); diff --git a/src/SUIT/SUIT_ShortcutMgr.cxx b/src/SUIT/SUIT_ShortcutMgr.cxx index 1204d32f8..95cede6d2 100644 --- a/src/SUIT/SUIT_ShortcutMgr.cxx +++ b/src/SUIT/SUIT_ShortcutMgr.cxx @@ -46,6 +46,7 @@ #include #include +#include const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: "; bool ShCutDbg(const QString& theString) { @@ -718,6 +719,16 @@ const std::map SUIT_ShortcutContainer::getModuleShortcuts return shortcutsInversed; } +bool SUIT_ShortcutContainer::hasKeySequence(const QString& theModuleID, const QKeySequence& theKeySequence) const +{ + const auto itModuleShortcuts = myShortcuts.find(theModuleID); + if (itModuleShortcuts == myShortcuts.end()) + return false; + + const auto& moduleShortcuts = itModuleShortcuts->second; + return moduleShortcuts.find(theKeySequence) != moduleShortcuts.end(); +} + QString SUIT_ShortcutContainer::toString() const { QString text; @@ -844,12 +855,14 @@ void SUIT_ActionAssets::merge(const SUIT_ActionAssets& theOther, bool theOverrid myIconPath = theOther.myIconPath; } -std::map> SUIT_ShortcutContainer::merge( +std::map>> SUIT_ShortcutContainer::merge( const SUIT_ShortcutContainer& theOther, bool theOverride, bool theTreatAbsentIncomingAsDisabled ) { - std::map> changesOfThis; + std::map>> changesOfThis; + + const SUIT_ShortcutContainer copyOfThisBeforeMerge = *this; // TODO Get rid of whole container copying. for (const auto& shortcutsInversedOfOtherPair : theOther.myShortcutsInversed) { const QString& moduleIDOther = shortcutsInversedOfOtherPair.first; @@ -857,15 +870,17 @@ std::map> SUIT_ShortcutContainer::merge for (const auto& shortcutInversedOther : shortcutsInversedOther) { const QString& inModuleActionIDOther = shortcutInversedOther.first; const QKeySequence& keySequenceOther = shortcutInversedOther.second; + const QKeySequence& keySequenceThis = getKeySequence(moduleIDOther, inModuleActionIDOther); if (theOverride) { - if (hasShortcut(moduleIDOther, inModuleActionIDOther) && getKeySequence(moduleIDOther, inModuleActionIDOther) == keySequenceOther) { + if (hasShortcut(moduleIDOther, inModuleActionIDOther) && keySequenceThis == keySequenceOther) { continue; } else /* if this has no shortcut for the action or if this has a shortcut for the action, but the key sequence differs. */ { const auto disabledActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, true); - changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther; + //changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair(keySequenceThis, keySequenceOther); + changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair(copyOfThisBeforeMerge.getKeySequence(moduleIDOther, inModuleActionIDOther), keySequenceOther); for (const auto& disabledActionOfThis : disabledActionsOfThis) { - changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = NO_KEYSEQUENCE; + changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = std::pair(keySequenceOther, NO_KEYSEQUENCE); } } } @@ -875,10 +890,10 @@ std::map> SUIT_ShortcutContainer::merge else { const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false); if (conflictingActionsOfThis.empty()) { - changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther; + changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair(NO_KEYSEQUENCE, keySequenceOther); } else /* if this has no shortcut for the action, but the incoming key sequence conflicts with others shortcuts. */ { - changesOfThis[moduleIDOther][inModuleActionIDOther] = NO_KEYSEQUENCE; + changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair(NO_KEYSEQUENCE, NO_KEYSEQUENCE); } } } @@ -903,8 +918,8 @@ std::map> SUIT_ShortcutContainer::merge auto& moduleShortcuts = itShortcutsPair->second; moduleShortcuts.erase(inversedShortcut.second); + changesOfThis[moduleID][inversedShortcut.first] = std::pair(inversedShortcut.second, NO_KEYSEQUENCE); inversedShortcut.second = NO_KEYSEQUENCE; - changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE; } } } @@ -916,7 +931,7 @@ std::map> SUIT_ShortcutContainer::merge SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = nullptr; SUIT_ShortcutMgr::SUIT_ShortcutMgr() -: QObject() +: QObject(), myActiveModuleIDs({SUIT_ShortcutMgr::ROOT_MODULE_ID}) { qApp->installEventFilter( this ); } @@ -1298,10 +1313,8 @@ void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAc const QString& inModuleActionID = moduleIDAndActionID.second; if (inModuleActionID.isEmpty()) { - ShCutDbg() && ShCutDbg("Attempt to register an action \"" + theAction->toolTip() + "\" with invalid ID \"" + theActionID + "\"."); - if (theAction->shortcut() != NO_KEYSEQUENCE) - theAction->setShortcut(NO_KEYSEQUENCE); - + ShCutDbg() && ShCutDbg("Attempt to register an action \"" + theAction->toolTip() + "\" with invalid ID \"" + theActionID + "\". The action is treated as anonymous."); + registerAnonymousShortcut(theAction); return; } @@ -1415,14 +1428,30 @@ QString SUIT_ShortcutMgr::getActionID(const QAction* theAction) const void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable) const { - const auto itModuleActions = myActions.find(theModuleID); - if (itModuleActions == myActions.end()) - return; +#ifdef SHORTCUT_MGR_DBG + if (theEnable) + ShCutDbg("Enable actions of module \"" + theModuleID + "\"."); + else + ShCutDbg("Disable actions of module \"" + theModuleID + "\"."); + + ShCutDbg("Anonymous actions before: " + anonymousShortcutsToString()); +#endif//SHORTCUT_MGR_DBG + + if (theEnable) + myActiveModuleIDs.emplace(theModuleID); + else + myActiveModuleIDs.erase(theModuleID); + + enableAnonymousShortcutsClashingWith(theModuleID, !theEnable); SUIT_Application* app = SUIT_Session::session()->activeApplication(); if (!app) return; + const auto itModuleActions = myActions.find(theModuleID); + if (itModuleActions == myActions.end()) + return; + const std::map>& moduleActions = itModuleActions->second; for (const auto& idAndActions : moduleActions) { const std::set& actions = idAndActions.second; @@ -1431,6 +1460,15 @@ void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, con action->setEnabled(theEnable); } } + + #ifdef SHORTCUT_MGR_DBG + ShCutDbg("Anonymous actions after: " + anonymousShortcutsToString()); + #endif//SHORTCUT_MGR_DBG +} + +void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const +{ + setActionsOfModuleEnabled(theInModuleActionIDPrefix, theEnable); } void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const @@ -1450,11 +1488,6 @@ void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModul } } -void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const -{ - setActionsWithPrefixInIDEnabled(theInModuleActionIDPrefix, theEnable); -} - void SUIT_ShortcutMgr::rebindActionsToKeySequences() const { ShCutDbg() && ShCutDbg("SUIT_ShortcutMgr::rebindActionsToKeySequences()"); @@ -1508,38 +1541,79 @@ void SUIT_ShortcutMgr::mergeShortcutContainer(const SUIT_ShortcutContainer& theC const auto changes = myShortcutContainer.merge(theContainer, theOverride, theTreatAbsentIncomingAsDisabled); ShCutDbg() && ShCutDbg("ShortcutMgr keeps following shortcuts:\n" + myShortcutContainer.toString()); + { +#ifdef SHORTCUT_MGR_DBG + QString changesStr = "Changes:\n"; + for (const auto& moduleIDAndChanges : changes) { + const QString& moduleID = moduleIDAndChanges.first; + const auto& moduleChanges = moduleIDAndChanges.second; + changesStr += "\t\"" + moduleID + "\":\n"; + for (const std::pair>& modifiedShortcut : moduleChanges) { + changesStr += "\t\t\"" + modifiedShortcut.first + "\": " + modifiedShortcut.second.first.toString() + "->" + modifiedShortcut.second.second.toString() + ";\n"; + } + } + ShCutDbg(changesStr); +#endif//SHORTCUT_MGR_DBG + } + + /** { keySequence, {moduleID, isTheKeySequenceBound}[] }[] */ + std::map> modifiedBoundStates; + // Turn off hotkeys for disabled shortcuts. for (const auto& moduleIDAndChanges : changes) { const QString& moduleID = moduleIDAndChanges.first; const auto& moduleChanges = moduleIDAndChanges.second; - for (const std::pair& modifiedShortcut : moduleChanges) { - if (modifiedShortcut.second == NO_KEYSEQUENCE) { + for (const std::pair>& modifiedShortcut : moduleChanges) { + if (modifiedShortcut.second.second == NO_KEYSEQUENCE) { const std::set actions = getActions(moduleID, modifiedShortcut.first); for (QAction* const action : actions) { action->setShortcut(NO_KEYSEQUENCE); } } + + if (modifiedShortcut.second.first != NO_KEYSEQUENCE) + modifiedBoundStates[modifiedShortcut.second.first][moduleID] = false; } } // Turn on hotkeys for enabled shortcuts. for (const auto& moduleIDAndChanges : changes) { const QString& moduleID = moduleIDAndChanges.first; + const auto& moduleChanges = moduleIDAndChanges.second; - for (const std::pair& modifiedShortcut : moduleChanges) { - if (modifiedShortcut.second != NO_KEYSEQUENCE) { + for (const std::pair>& modifiedShortcut : moduleChanges) { + if (modifiedShortcut.second.second != NO_KEYSEQUENCE) { const std::set actions = getActions(moduleID, modifiedShortcut.first); for (QAction* const action : actions) { - action->setShortcut(modifiedShortcut.second); + action->setShortcut(modifiedShortcut.second.second); } + + modifiedBoundStates[modifiedShortcut.second.second][moduleID] = true; + } + } + } + + // Update anonymous shortcuts. + for (const auto& ksAndBoundStates : modifiedBoundStates) { + const auto& keySequence = ksAndBoundStates.first; + const auto& boundStates = ksAndBoundStates.second; + + bool ksIsBoundToAnActionInActiveModule = false; + for (const auto& moduleIDAndBoundState : boundStates) { + const QString& moduleID = moduleIDAndBoundState.first; + const bool keySequenceIsBound = moduleIDAndBoundState.second; + if (keySequenceIsBound && myActiveModuleIDs.find(moduleID) != myActiveModuleIDs.end()) { + ksIsBoundToAnActionInActiveModule = true; + break; } } + enableAnonymousShortcutsClashingWith(keySequence, !ksIsBoundToAnActionInActiveModule); } SUIT_ShortcutMgr::saveShortcutsToPreferences(changes); } -QKeySequence SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const +const QKeySequence& SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const { return myShortcutContainer.getKeySequence(theModuleID, theInModuleActionID); } @@ -1690,6 +1764,29 @@ void SUIT_ShortcutMgr::onActionDestroyed(QObject* theObject) myActionIDs.erase(itID); } +void SUIT_ShortcutMgr::onAnonymousActionDestroyed(QObject* theObject) +{ + QAction* action = static_cast(theObject); + + const auto itShortcut = myAnonymousShortcuts.find(action); + if (itShortcut == myAnonymousShortcuts.end()) + return; + + const auto& keySequence = itShortcut->second; + + const auto itShortcutsInverse = myAnonymousShortcutsInverse.find(keySequence); + if (itShortcutsInverse == myAnonymousShortcutsInverse.end()) { + ShCutDbg("Faulty logics of anonymous action destruction."); + return; + } + std::set& actions = itShortcutsInverse->second; + actions.erase(action); + if (actions.empty()) + myAnonymousShortcutsInverse.erase(itShortcutsInverse); + + myAnonymousShortcuts.erase(itShortcut); +} + bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent) { if (theEvent) { @@ -1723,14 +1820,8 @@ bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent) if (aQAction) DevTools::get()->collectAssetsOfActionWithInvalidID(aQAction); #endif//SHORTCUT_MGR_DEVTOOLS - if (aQAction && aQAction->shortcut() != NO_KEYSEQUENCE) { -#ifdef SHORTCUT_MGR_DBG - ShCutDbg("ActionAdded event, but the added action is not QtxAction and bound to non-empty key sequence. name: \"" + aQAction->toolTip() + "\"."); -#endif//SHORTCUT_MGR_DBG - // Since non-QtxAction has no ID, it is impossible to properly manage its shortcut. - // And the shortcut may interfere with managed ones. - aQAction->setShortcut(NO_KEYSEQUENCE); - } + if (aQAction) + registerAnonymousShortcut(aQAction); } } } @@ -1748,14 +1839,17 @@ std::set> SUIT_ShortcutMgr::setShortcutNoIDChecks(co /** { moduleID, {inModuleActionID, keySequence}[] }[] */ std::map> modifiedShortcuts; + /** { moduleID, isTheKeySequenceBound }[] */ + std::map modifiedBoundStates; + for (const auto& moduleIDAndActionID : disabledShortcutsIDs) { // Unbind actions of disabled shortcuts. const QString& moduleID = moduleIDAndActionID.first; const QString& inModuleActionID = moduleIDAndActionID.second; - std::map& modifiedModuleShortcuts = modifiedShortcuts[moduleID]; - modifiedModuleShortcuts[inModuleActionID] = NO_KEYSEQUENCE; + modifiedShortcuts[moduleID][inModuleActionID] = NO_KEYSEQUENCE; + modifiedBoundStates[moduleID] = false; const std::set actions = getActions(moduleID, inModuleActionID); for (QAction* const action : actions) { @@ -1764,8 +1858,13 @@ std::set> SUIT_ShortcutMgr::setShortcutNoIDChecks(co } { // Bind actions to theKeySequence. - std::map& modifiedModuleShortcuts = modifiedShortcuts[theModuleID]; - modifiedModuleShortcuts[theInModuleActionID] = theKeySequence; + modifiedShortcuts[theModuleID][theInModuleActionID] = theKeySequence; + + const auto it = modifiedBoundStates.find(theModuleID); + if (it == modifiedBoundStates.end()) + modifiedBoundStates[theModuleID] = true; + else + modifiedBoundStates.erase(it); const std::set actions = getActions(theModuleID, theInModuleActionID); for (QAction* const action : actions) { @@ -1773,6 +1872,18 @@ std::set> SUIT_ShortcutMgr::setShortcutNoIDChecks(co } } + // Update anonymous shortcuts. + bool ksIsBoundToAnActionInActiveModule = false; + for (const auto& moduleIDAndBoundState : modifiedBoundStates) { + const QString& moduleID = moduleIDAndBoundState.first; + const bool keySequenceIsBound = moduleIDAndBoundState.second; + if (keySequenceIsBound && myActiveModuleIDs.find(moduleID) != myActiveModuleIDs.end()) { + ksIsBoundToAnActionInActiveModule = true; + break; + } + } + enableAnonymousShortcutsClashingWith(theKeySequence, !ksIsBoundToAnActionInActiveModule); + SUIT_ShortcutMgr::saveShortcutsToPreferences(modifiedShortcuts); } @@ -1785,7 +1896,7 @@ void SUIT_ShortcutMgr::setShortcutsFromPreferences() SUIT_ShortcutContainer container; SUIT_ShortcutMgr::fillContainerFromPreferences(container, false /*theDefaultOnly*/); - mergeShortcutContainer(container, true /*theOverrde*/, false /*theTreatAbsentIncomingAsDisabled*/); + mergeShortcutContainer(container, true /*theOverride*/, false /*theTreatAbsentIncomingAsDisabled*/); setAssetsFromResources(); ShCutDbg() && ShCutDbg("ShortcutMgr has been initialized."); @@ -1818,6 +1929,33 @@ void SUIT_ShortcutMgr::setShortcutsFromPreferences() } } +/*static*/ void SUIT_ShortcutMgr::saveShortcutsToPreferences(const std::map>>& theShortcutsInversed) +{ + ShCutDbg() && ShCutDbg("Saving preferences to resources."); + + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("SUIT_ShortcutMgr can't retrieve resource manager!"); + return; + } + + for (const auto& moduleIDAndShortcutsInversed : theShortcutsInversed) { + const auto& moduleID = moduleIDAndShortcutsInversed.first; + const auto& moduleShortcutsInversed = moduleIDAndShortcutsInversed.second; + for (const auto& shortcutInversed : moduleShortcutsInversed) { + if (shortcutInversed.first.isEmpty()) { + ShCutDbg("Attempt to serialize a shortcut with empty action ID."); + continue; + } + + const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID; + resMgr->setValue(sectionName, shortcutInversed.first, shortcutInversed.second.second.toString()); + + ShCutDbg() && ShCutDbg("Saving shortcut: \"" + moduleID + "\"\t\"" + shortcutInversed.first + "\"\t\"" + shortcutInversed.second.second.toString() + "\""); + } + } +} + void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage) { ShCutDbg() && ShCutDbg("Retrieving action assets."); @@ -1966,6 +2104,111 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage) } } +void SUIT_ShortcutMgr::registerAnonymousShortcut(QAction* const theAction) +{ + const auto itAnSh = myAnonymousShortcuts.find(theAction); + if (itAnSh != myAnonymousShortcuts.end()) { +#ifdef SHORTCUT_MGR_DBG + ShCutDbg("Registered anonymous shortcut is registered again."); +#endif//SHORTCUT_MGR_DBG + onAnonymousActionDestroyed(theAction); + } + + const QKeySequence keySequence = theAction->shortcut(); + + if (keySequence == NO_KEYSEQUENCE) + return; + +#ifdef SHORTCUT_MGR_DBG + ShCutDbg("Anonymous shortcut is registered: \"" + theAction->toolTip() + "\" - " + keySequence.toString() + "."); +#endif//SHORTCUT_MGR_DBG + + for (const auto& activeModuleID : myActiveModuleIDs) { + if (myShortcutContainer.hasKeySequence(activeModuleID, keySequence)) { + theAction->setShortcut(NO_KEYSEQUENCE); + break; + } + } + + myAnonymousShortcuts[theAction] = keySequence; + myAnonymousShortcutsInverse[keySequence].emplace(theAction); + + connect(theAction, SIGNAL(destroyed(QObject*)), this, SLOT (onAnonymousActionDestroyed(QObject*))); +} + +void SUIT_ShortcutMgr::enableAnonymousShortcutsClashingWith(const QString& theModuleID, const bool theEnable) const +{ + for (const auto& ksAndActions : myAnonymousShortcutsInverse) { + const QKeySequence& keySequence = ksAndActions.first; + + if (!myShortcutContainer.hasKeySequence(theModuleID, keySequence)) + continue; + + const std::set& actions = ksAndActions.second; + if (theEnable) { + for (const auto& action : actions) + action->setShortcut(keySequence); + } + else { + for (const auto& action : actions) + action->setShortcut(NO_KEYSEQUENCE); + } + } +} + +void SUIT_ShortcutMgr::enableAnonymousShortcutsClashingWith(const QKeySequence& theKeySequence, bool theEnable) const +{ + const auto itShortcutsInverse = myAnonymousShortcutsInverse.find(theKeySequence); + if (itShortcutsInverse == myAnonymousShortcutsInverse.end()) + return; + + const std::set& actions = itShortcutsInverse->second; + if (theEnable) { + for (const auto& action : actions) + action->setShortcut(theKeySequence); + } + else { + for (const auto& action : actions) + action->setShortcut(NO_KEYSEQUENCE); + } +} + +QString SUIT_ShortcutMgr::anonymousShortcutsToString() const +{ + QString res; + res += "myAnonymousShortcutsInverse: {\n"; + for (const auto& ksAndActions : myAnonymousShortcutsInverse) { + const QKeySequence& ks = ksAndActions.first; + const std::set& actions = ksAndActions.second; + res += "\t" + ks.toString() + ":"; + for (const auto& action : actions) { + std::ostringstream ss; + ss << (void const *)action; + std::string addressStr = ss.str(); + res += " " + QString::fromStdString(addressStr); + } + res += ";\n"; + } + res += "};\n\n"; + + res += "myAnonymousShortcuts: {\n"; + for (const auto& shortcut : myAnonymousShortcuts) { + const auto& action = shortcut.first; + const QKeySequence& ks = shortcut.second; + + std::ostringstream ss; + ss << (void const *)action; + std::string addressStr = ss.str(); + res += "\t" + QString::fromStdString(addressStr) + ": {\n"; + res += "\t\tRegistered key sequence: " + ks.toString() + ";\n"; + res += "\t\tBound key sequence: " + action->shortcut().toString() + ";\n"; + res += "\t};\n"; + } + res += "};\n\n"; + + return res; +} + SUIT_SentenceMatcher::SUIT_SentenceMatcher() diff --git a/src/SUIT/SUIT_ShortcutMgr.h b/src/SUIT/SUIT_ShortcutMgr.h index 95707d25f..4c2244438 100644 --- a/src/SUIT/SUIT_ShortcutMgr.h +++ b/src/SUIT/SUIT_ShortcutMgr.h @@ -45,7 +45,7 @@ class QJsonObject; #endif // Define SHORTCUT_MGR_DBG to enable SUIT_ShortcutMgr debug logging. -// #define SHORTCUT_MGR_DBG +#define SHORTCUT_MGR_DBG /*! \returns true, if SUIT_ShortcutMgr debug logging is enabled. */ SUIT_EXPORT extern inline bool ShCutDbg() { #ifdef SHORTCUT_MGR_DBG @@ -126,13 +126,15 @@ public: \param theTreatAbsentIncomingAsDisabled If theOverride == false, theTreatAbsentIncomingAsDisabled is ignored. If theOverride and theTreatAbsentIncomingAsDisabled, and theOther has no shortcut for an action, which exists in this, the existing shortcut in this is set disabled. - \returns { moduleID, { inModuleActionID, keySequence }[] }[] - Modiified shortcuts inversed. */ - std::map> merge( + \returns { moduleID, { inModuleActionID, {oldKeySequence, newKeySequence} }[] }[] - Modiified shortcuts inversed. */ + std::map>> merge( const SUIT_ShortcutContainer& theOther, bool theOverride, bool theTreatAbsentIncomingAsDisabled = false ); + bool hasKeySequence(const QString& theModuleID, const QKeySequence& theKeySequence) const; + /*! \brief Generates human-readable text representation of content. */ QString toString() const; @@ -188,6 +190,8 @@ struct SUIT_EXPORT SUIT_ActionAssets \class SUIT_ShortcutMgr \brief Handles action shortcut customization. + IDENTIFIED ACTIONS/SHORTCUTS + Register actions under action IDs. Set shortcuts, which are [action ID]<->[key sequence] mappings. Every time an action is registered or a shorcut is set, if there are an action and a shortcut, which are mapped to the same action ID, the action is bound to the key sequence of the shortcut. @@ -218,6 +222,15 @@ struct SUIT_EXPORT SUIT_ActionAssets Only one module can be active at instance. So a key sequence must be unique within a joined temporary table of root and active module shortcuts. An action is allowed to be bound with only key sequence. + ANONYMOUS ACTIONS/SHORTCUTS + + Actions without action IDs or with invalid ones are called anonymous actions. + All anonymous actions with non-empty shortcut key sequences are registered by SUIT_ShortcutMgr. + If a shortcut for an anonymous action clashes with a shortcut for an action with defined ID (identified action/shortcut), + the shortcut for the anonymous action is disabled, but [the anonymous action, the hard-coded key sequence] pair + remains within the SUIT_ShortcutMgr. If user redefines key sequences for identified actions, + and the clash is gone, SUIT_ShortcutMgr enables back the shortcut for the anonymous action. + WARNING! Avoid assigning shortcuts to instances of QAction and all its descendants directly. (1) Key sequence being bound directly to any registered/intercepted action with valid ID, @@ -225,7 +238,10 @@ struct SUIT_EXPORT SUIT_ActionAssets is added to the manager and appears in user preference file. If it does conflict, it is reassigned with a key sequence from preference files or disabled and added to user preference files (if the files have no shortcut for the action). - (2) Key sequences being bound directly to non-QtxAction instances are disabled. + (2) It is not possible to reassign key sequences for anonymous actions using the Shortcut Editor GUI. + It is not possible to always warn user, if a key sequence, he assigns to an identified action, + disables an anonymous shortcut, because SUIT_ShortcutMgr has no data about anonymous actions until they appear in runtime. + To prevent the user from relying on such warnings, they are completely disabled. */ class SUIT_EXPORT SUIT_ShortcutMgr: public QObject { @@ -317,13 +333,13 @@ public: Only those actions are affected, whose parent widget is active desktop. */ void setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable = true) const; + [[deprecated("Use setActionsOfModuleEnabled(const QString&, bool) instead.")]] + void setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const; + /*! \brief Enables/disables all registered actions whose in-module action ID begins with theInModuleActionIDPrefix. Only those actions are affected, whose parent widget is active desktop. */ void setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const; - [[deprecated("Use setActionsWithPrefixInIDEnabled(const QString&, bool) instead.")]] - void setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable = true) const; - /*! \brief For all registered actions binds key sequences from myShortcutContainer. */ void rebindActionsToKeySequences() const; @@ -349,7 +365,7 @@ public: void mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride = true, bool theTreatAbsentIncomingAsDisabled = false); /*! \brief Get a key sequence mapped to the action. */ - QKeySequence getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const; + const QKeySequence& getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const; /*! \returns {inModuleActionID, keySequence}[] */ const std::map& getModuleShortcutsInversed(const QString& theModuleID) const; @@ -387,6 +403,8 @@ private slots: */ void onActionDestroyed(QObject* theObject); + void onAnonymousActionDestroyed(QObject* theObject); + private: /*! \brief Overrides QObject::eventFilter(). If theEvent is QEvent::ActionAdded and the action is instance of QtxAction, registers it. */ @@ -432,6 +450,11 @@ private: \param theShortcuts { moduleID, { inModuleActionID, keySequence }[] }[]. Empty inModuleActionIDs are ignored. */ static void saveShortcutsToPreferences(const std::map>& theShortcutsInversed); + /*! \brief Writes shortcuts to preference files. + \param theShortcuts { moduleID, { inModuleActionID, {oldKeySequence, newKeySequence} }[] }[]. Empty inModuleActionIDs are ignored. + OldKeySequences are ignored. */ + static void saveShortcutsToPreferences(const std::map>>& theShortcutsInversed); + /*! Fills myActionAssets from asset files in theLanguage. \param theLanguage If default, fills assets in current language. If an asset in requested language is not found, seeks for the asset EN in and then in FR. @@ -455,6 +478,11 @@ private: */ void setAssetsFromResources(QString theLanguage = QString()); + void registerAnonymousShortcut(QAction* const theAction); + void enableAnonymousShortcutsClashingWith(const QString& theModuleID, const bool theEnable) const; + void enableAnonymousShortcutsClashingWith(const QKeySequence& theKeySequence, bool theEnable) const; + QString anonymousShortcutsToString() const; + private: static SUIT_ShortcutMgr* myShortcutMgr; @@ -472,11 +500,21 @@ private: Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer. */ - /* { moduleID, {inModuleActionID, assets}[] }[] */ + /** { moduleID, {inModuleActionID, assets}[] }[] */ std::map>> myActionAssets; - /* {moduleID, assets}[] */ + /** {moduleID, assets}[] */ mutable std::map> myModuleAssets; + + mutable std::set myActiveModuleIDs; + + /** Actions without IDs, but with hard-coded non-empty key sequences. + * Shortcuts, defined in preferences, override shortcuts of anonymous actions - if an active module has a preference shortcut, + * anonymous shortcuts with the same key sequence are disabled. If the root module has a preference shortcut, which + * is in clash with anonymous shortcuts, clashing anonymous actions are always disabled. */ + std::map myAnonymousShortcuts; + + std::map> myAnonymousShortcutsInverse; }; diff --git a/src/SalomeApp/SalomeApp_Application.cxx b/src/SalomeApp/SalomeApp_Application.cxx index 6a5847fd4..89130d418 100644 --- a/src/SalomeApp/SalomeApp_Application.cxx +++ b/src/SalomeApp/SalomeApp_Application.cxx @@ -1116,7 +1116,7 @@ QWidget* SalomeApp_Application::createWindow( const int flag ) ob->setAutoSizeFirstColumn(autoSizeFirst); ob->setAutoSizeColumns(autoSize); ob->setResizeOnExpandItem(resizeOnExpandItem); - ob->setProperty( "shortcut", QKeySequence( "Alt+Shift+O" ) ); + ob->setProperty( "shortcut", QKeySequence( "Ctrl+Shift+O" ) ); for ( int i = SalomeApp_DataObject::EntryId; i < SalomeApp_DataObject::LastId; i++ ) { @@ -1151,7 +1151,7 @@ QWidget* SalomeApp_Application::createWindow( const int flag ) pyCons->setFont(resourceMgr()->fontValue( "PyConsole", "font" )); pyCons->setIsShowBanner(resourceMgr()->booleanValue( "PyConsole", "show_banner", true )); pyCons->setAutoCompletion( resMgr->booleanValue( "PyConsole", "auto_completion", true ) ); - pyCons->setProperty( "shortcut", QKeySequence( "Alt+Shift+P" ) ); + pyCons->setProperty( "shortcut", QKeySequence( "Ctrl+Shift+P" ) ); wid = pyCons; } else if ( flag == WT_NoteBook ) -- 2.39.2