]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
[bos #42109][CEA] (2024) Import med and show python console shortcuts does not work...
authordish <dmitrii.shvydkoi@opencascade.com>
Wed, 3 Jul 2024 10:47:00 +0000 (10:47 +0000)
committerdish <dmitrii.shvydkoi@opencascade.com>
Wed, 3 Jul 2024 10:47:00 +0000 (10:47 +0000)
Anonymous shortcuts are not disabled, unless they are in clash with preference shortcuts.

src/LightApp/LightApp_Application.cxx
src/LightApp/LightApp_Module.cxx
src/SUIT/SUIT_ShortcutMgr.cxx
src/SUIT/SUIT_ShortcutMgr.h
src/SalomeApp/SalomeApp_Application.cxx

index a83faafc4d04fff81308ba8d08fcc9b1935b9868..5c17af98d5c4c4b6d0c260551f95cf3f367e85dc 100644 (file)
@@ -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
index fe3abbf30febaacc0e2e4e784f4ec0301ee30ef4..903a037ca57daae93f00e0411e03b01742c9eb22 100644 (file)
@@ -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" );
index 1204d32f806c23d3b8cc003f024814a4a4cad59c..95cede6d265675eef3e0245d998bf4e70a525aaa 100644 (file)
@@ -46,6 +46,7 @@
 
 #include <iostream>
 #include <string>
+#include <sstream>
 const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: ";
 bool ShCutDbg(const QString& theString)
 {
@@ -718,6 +719,16 @@ const std::map<QString, QKeySequence> 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<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge(
+std::map<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>> SUIT_ShortcutContainer::merge(
   const SUIT_ShortcutContainer& theOther,
   bool theOverride,
   bool theTreatAbsentIncomingAsDisabled
 ) {
-  std::map<QString, std::map<QString, QKeySequence>> changesOfThis;
+  std::map<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>> 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<QString, std::map<QString, QKeySequence>> 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<QKeySequence, QKeySequence>(keySequenceThis, keySequenceOther);
+          changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair<QKeySequence, QKeySequence>(copyOfThisBeforeMerge.getKeySequence(moduleIDOther, inModuleActionIDOther), keySequenceOther);
           for (const auto& disabledActionOfThis : disabledActionsOfThis) {
-            changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = NO_KEYSEQUENCE;
+            changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = std::pair<QKeySequence, QKeySequence>(keySequenceOther, NO_KEYSEQUENCE);
           }
         }
       }
@@ -875,10 +890,10 @@ std::map<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge
         else {
           const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false);
           if (conflictingActionsOfThis.empty()) {
-            changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther;
+            changesOfThis[moduleIDOther][inModuleActionIDOther] = std::pair<QKeySequence, QKeySequence>(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<QKeySequence, QKeySequence>(NO_KEYSEQUENCE, NO_KEYSEQUENCE);
           }
         }
       }
@@ -903,8 +918,8 @@ std::map<QString, std::map<QString, QKeySequence>> SUIT_ShortcutContainer::merge
 
         auto& moduleShortcuts = itShortcutsPair->second;
         moduleShortcuts.erase(inversedShortcut.second);
+        changesOfThis[moduleID][inversedShortcut.first] = std::pair<QKeySequence, QKeySequence>(inversedShortcut.second, NO_KEYSEQUENCE);
         inversedShortcut.second = NO_KEYSEQUENCE;
-        changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE;
       }
     }
   }
@@ -916,7 +931,7 @@ std::map<QString, std::map<QString, QKeySequence>> 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<QString, std::set<QAction*>>& moduleActions = itModuleActions->second;
   for (const auto& idAndActions : moduleActions) {
     const std::set<QAction*>& 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<QString, std::pair<QKeySequence, QKeySequence>>& 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<QKeySequence, std::map<QString, bool>> 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<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
-      if (modifiedShortcut.second == NO_KEYSEQUENCE) {
+    for (const std::pair<QString, std::pair<QKeySequence, QKeySequence>>& modifiedShortcut : moduleChanges) {
+      if (modifiedShortcut.second.second == NO_KEYSEQUENCE) {
         const std::set<QAction*> 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<QString, QKeySequence>& modifiedShortcut : moduleChanges) {
-      if (modifiedShortcut.second != NO_KEYSEQUENCE) {
+    for (const std::pair<QString, std::pair<QKeySequence, QKeySequence>>& modifiedShortcut : moduleChanges) {
+      if (modifiedShortcut.second.second != NO_KEYSEQUENCE) {
         const std::set<QAction*> 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<QAction*>(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<QAction*>& 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<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcutNoIDChecks(co
     /** { moduleID, {inModuleActionID, keySequence}[] }[] */
     std::map<QString, std::map<QString, QKeySequence>> modifiedShortcuts;
 
+    /** { moduleID, isTheKeySequenceBound }[] */
+    std::map<QString, bool> modifiedBoundStates;
+
     for (const auto& moduleIDAndActionID : disabledShortcutsIDs) {
       // Unbind actions of disabled shortcuts.
 
       const QString& moduleID = moduleIDAndActionID.first;
       const QString& inModuleActionID = moduleIDAndActionID.second;
 
-      std::map<QString, QKeySequence>& modifiedModuleShortcuts = modifiedShortcuts[moduleID];
-      modifiedModuleShortcuts[inModuleActionID] = NO_KEYSEQUENCE;
+      modifiedShortcuts[moduleID][inModuleActionID] = NO_KEYSEQUENCE;
+      modifiedBoundStates[moduleID] = false;
 
       const std::set<QAction*> actions = getActions(moduleID, inModuleActionID);
       for (QAction* const action : actions) {
@@ -1764,8 +1858,13 @@ std::set<std::pair<QString, QString>> SUIT_ShortcutMgr::setShortcutNoIDChecks(co
     }
 
     { // Bind actions to theKeySequence.
-      std::map<QString, QKeySequence>& 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<QAction*> actions = getActions(theModuleID, theInModuleActionID);
       for (QAction* const action : actions) {
@@ -1773,6 +1872,18 @@ std::set<std::pair<QString, QString>> 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<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>>& 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<QAction*>& 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<QAction*>& 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<QAction*>& 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()
index 95707d25f51b39ec59a2fbc14c4b3adc26092394..4c2244438a033d36f5f2df2ffd2698ca1434b502 100644 (file)
@@ -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<QString, std::map<QString, QKeySequence>> merge(
+    \returns { moduleID, { inModuleActionID, {oldKeySequence, newKeySequence} }[] }[] - Modiified shortcuts inversed. */
+  std::map<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>> 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<QString, QKeySequence>& 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<QString, std::map<QString, QKeySequence>>& 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<QString, std::map<QString, std::pair<QKeySequence, QKeySequence>>>& 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<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> myActionAssets;
 
-  /* {moduleID, assets}[] */
+  /** {moduleID, assets}[] */
   mutable std::map<QString, std::shared_ptr<SUIT_ActionAssets>> myModuleAssets;
+
+  mutable std::set<QString> 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<QAction*, QKeySequence> myAnonymousShortcuts;
+
+  std::map<QKeySequence, std::set<QAction*>> myAnonymousShortcutsInverse;
 };
 
 
index 6a5847fd431e1be8e175032219eb191ac1281e78..89130d41825641eb60a710068137c9999da9d117 100644 (file)
@@ -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 )