From: dish Date: Mon, 4 Dec 2023 15:34:45 +0000 (+0000) Subject: [bos #35160][EDF](2023-T1) Keyboard shortcuts. X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=ae6282bdfa8bc68a8dc66fceaba130ff0a28461e;p=modules%2Fgui.git [bos #35160][EDF](2023-T1) Keyboard shortcuts. Shortcut Mgr is now actually responsible for managing shortcuts: it is solely responsible and capable to dynamically bind actions with hotkey combinations and (de)serialize shortcut preference changes using SUIT_ResourceMgr. Add SUIT_ShortcutContainer, which ensures that there are no conflicts between shortcuts. Added the ability to change shortcut preferences through the GUI. Add a development tool to grab all shortcuts of created at runtime QtxActions . The tool also grabs tooltips of the actions for further dynamic translation from actionID to actionName in runtime before actions with these ID are created. Both grabbed shortcuts and translations are dumped in XML files with identical to project-conventional resource files formatting. --- diff --git a/doc/salome/gui/input/setting_preferences.rst b/doc/salome/gui/input/setting_preferences.rst index 4e892e81e..0eaa7a258 100644 --- a/doc/salome/gui/input/setting_preferences.rst +++ b/doc/salome/gui/input/setting_preferences.rst @@ -1,4 +1,4 @@ -.. _setting_preferences_page: +.. _setting_preferences_page: ******************* Setting Preferences @@ -13,7 +13,7 @@ The **Preferences** dialog box consists of two parts: - Available preferences sections are listed in the left part of the dialog box; usually this list contains "SALOME" item that specifies general application preferences and a separate section for each SALOME module available in the current session. - The tabbed widget at the right side of the dialog box provides controls that can be used to customize the preferences. -.. note:: The preferences for modules become accessible only after explicit loading of these modules. Until then the dialog box will show the corresponding warning message. +.. note:: The preferences for modules become accessible only after explicit loading of these modules. Until then the dialog box will show the corresponding warning message. More detailed information about preferences for certain modules can be found in the User's guide of the corresponding module. @@ -23,7 +23,7 @@ application look-n-feel and common functionality. If the study has just been started and other modules have not been loaded yet, it will be possible to change only the settings which refer to -the whole GUI SALOME session. +the whole GUI SALOME session. General Preferences ################### @@ -40,11 +40,11 @@ General Preferences - **Show splash screen at start-up** - allows showing or hiding the splash screen at start-up. - **Opaque resize** - force opaque resize mode for viewers area (tabbed workspace). Clear this checkbox for less perfomant workstations. - - **Drop-down buttons in toolbars for action groups** - when checked, the action groups are represented in the viewer toolbars as a single drop-down button, switchable by the user. Otherwise, all the buttons from the action groups are displayed in the toolbar. + - **Drop-down buttons in toolbars for action groups** - when checked, the action groups are represented in the viewer toolbars as a single drop-down button, switchable by the user. Otherwise, all the buttons from the action groups are displayed in the toolbar. - **Study Properties** - - **Multi file save** - if checked in, your study will be saved in several HDF files (one basic HDF file which will store the main information about the saved study and several other files for the data created by each component used during the study session). Opening of this study requires that **ALL** saved files should be stored in the **SAME** directory. If you would like to copy your saved study in another directory or machine, you should copy all stored files. Otherwise, if you try to open this study, some data will be lost and it will lead to invalid functioning of the SALOME platform. + - **Multi file save** - if checked in, your study will be saved in several HDF files (one basic HDF file which will store the main information about the saved study and several other files for the data created by each component used during the study session). Opening of this study requires that **ALL** saved files should be stored in the **SAME** directory. If you would like to copy your saved study in another directory or machine, you should copy all stored files. Otherwise, if you try to open this study, some data will be lost and it will lead to invalid functioning of the SALOME platform. - **ASCII save** - if checked in, your study will be saved in ASCII format file (or files). - **Automatic loading of light modules when opening study** - if checked in, Light Modules of the current study will be automatically loaded at the next study opening, allowing completion of object browser. - **Store positions of windows** - if checked in, positions of windows will be saved in a special file at the end of the current session and then restored for a new session. @@ -55,7 +55,7 @@ General Preferences - **Multi file python dump** - allows to generate multiple files (separately for each component) for dumping of a study to a python script. If the option is disabled, the study is dumped to a single python script. - **Save GUI state in python dump** - if this option is switched on, the Python script resulting from Dump Python operation will include commands related to the GUI state. -- **External browser** - allows to define what browser will be used to show SALOME reference manuals: internal (built-in SALOME browser) or external (IE, Netscape, Mozilla, ...). In addition, it is possible to specify +- **External browser** - allows to define what browser will be used to show SALOME reference manuals: internal (built-in SALOME browser) or external (IE, Netscape, Mozilla, ...). In addition, it is possible to specify - **Application** - this option allows you to set an external browser (IE, Netscape) which will be used for viewing SALOME reference manuals. By default, Mozilla is used. - **Parameters** - additional parameters required for launching of the external browser (if applicable). @@ -65,13 +65,13 @@ General Preferences - **Font** - allows quickly setting the parameters (style, size, face) of the :ref:`font_color_dlg` used in embedded Python console. - **Show banner** - this option allows to show/hide the Python banner on top of the console window. -- **Show MRU items** - allows to define the maximum **Number** of items in **Most Recently Used** list and the **Link type**: +- **Show MRU items** - allows to define the maximum **Number** of items in **Most Recently Used** list and the **Link type**: - - **Long** - shows the full path to the file. + - **Long** - shows the full path to the file. - **Short** - shows the file name only. - **Auto** - shows full paths to the files only if some files from different locations have the same name. -- **Full-screen** +- **Full-screen** - **Hide object browser and viewers toolbars** - switches automatic hiding of Object Browser and OCC viewer toolbar in full-screen mode. @@ -135,10 +135,10 @@ OCC 3D Viewer Preferences - **Enable V-Sync** - activates vertical synchronization. - **Enable quad-buffer support** - allows quad-buffered rendering. - .. note:: + .. note:: It is neccessary to enable quad-buffered stereoscopic rendering manually in graphic driver settings. SALOME does not do it automatically. - .. note:: + .. note:: All existing OCC 3D views should be re-created for quad-buffer support. - **Background** - specifies the default background for the viewers, separately for (for more details, refer to the :ref:`viewer_background` page"): @@ -220,7 +220,7 @@ VTK 3D Viewer Preferences - Checkerboard - Split View Port Horizontal - .. note:: + .. note:: The stereo pair type selected in this combobox is applied for all existing VTK 3D views with stereo rendering already turned on within the same SALOME GUI session. It is not possible to use different stereo modes in several VTK 3D views at the same time within the same SALOME GUI session. - **Anaglyph filter** - specifies the format of anaglyph stereo pair: @@ -231,9 +231,9 @@ VTK 3D Viewer Preferences - **Enable quad-buffer support** - allows quad-buffered rendering. - .. note:: + .. note:: It is neccessary to enable quad-buffered stereoscopic rendering manually in graphic driver settings. SALOME does not do it automatically. - .. note:: + .. note:: All existing VTK 3D views should be re-created for quad-buffer support. - **Selection** @@ -241,7 +241,7 @@ VTK 3D Viewer Preferences - **Preselection** - allows to choose among three possible preselection modes: - **Standard** - this mode works quickly, by checking only bounding boxes of objects. It does not deal with the order of actors in the view or with their data (points/ cells). - - **Dynamic** - works directly with cells of actors, which provides the exact area of preselection. This mode is much more slower. + - **Dynamic** - works directly with cells of actors, which provides the exact area of preselection. This mode is much more slower. - **Disabled** - switches off the preselection. - **Enable selection** - switches selection on/off. @@ -250,13 +250,13 @@ VTK 3D Viewer Preferences - **Decrease Speed Increment** - decreases by 1 the speed increment used for the keyboard (same as [-] key). - **Increase Speed Increment** - increase by 1 the speed increment used for the keyboard (same as [+] key). - - **Dominant / combined switch** - toggles button to switch to dominant or combined movements. + - **Dominant / combined switch** - toggles button to switch to dominant or combined movements. - **AVI Recording** - **Mode** - allows to choose from two recording regimes: - - **Recording all displayed frames** - records exactly at the FPS rate specified by the user. + - **Recording all displayed frames** - records exactly at the FPS rate specified by the user. - **Recording at a given FPS** - records only when the contents of the viewer change (some activity is performed). In the AVI file non-recorded images are substituted with the copies of the latest recorded image, which produces files with lower quality but requires less system resources. - **FPS** - allows to define the FPS (frames per second) rate for the clip. Set greater value for better quality. @@ -278,15 +278,15 @@ Plot 2D Viewer Preferences :align: center - **Background color** - this submenu allows to select the background color. Click on the colored line to access to the :ref:`select_color_and_font_page` dialog box. -- **Selection color** - this submenu allows to select the color of selected object in the viewer. +- **Selection color** - this submenu allows to select the color of selected object in the viewer. - **Viewer** - allows specifying the properties of the Plot 2D Viewer. - **Curve Type** - this allows to set the representation of graphs in your presentations. You can see only **Points**, points connected with **Lines** or points connected with smooth **Splines**. - **Marker Size** - this submenu allows you to set the size of markers in your graphs. - **Horizontal** and **Vertical axis scale** - this submenus allow you to set the scale for vertical and horizontal axes. It can be either **Linear** or **Logarithmic**. Note that the **Logarithmic** scale can be used only if the minimum value of corresponding component (abscissa or ordinate) of all points displayed in the viewer is greater than zero. If this condition is not met, the scale is switched to **Linear** automatically, even if it is set to **Logarithmic**. - - **Deviation marker color** - this submenu allows to select the color of the deviation marker. - - **Deviation marker line width** allows to define line width of the deviation marker. - - **Deviation marker tick size** allows to define size of the upper and lower horizontal lines of the deviation marker. + - **Deviation marker color** - this submenu allows to select the color of the deviation marker. + - **Deviation marker line width** allows to define line width of the deviation marker. + - **Deviation marker tick size** allows to define size of the upper and lower horizontal lines of the deviation marker. - **Legend** - allows specifying the properties of the legend. @@ -294,8 +294,8 @@ Plot 2D Viewer Preferences - **Legend Position** - this submenu allows to set the default position of the legend, it can be located to the left, to the right, on top or on bottom of the graph. - **Symbol type** you can select the type of legend item symbol from "Marker on line" or "Marker above line" - **Legend font** - this allows to set type and face for the font of Legend item. - - **Legend font color** - this allows to select the color of the font of the legend item. - - **Highlighted legend font color** - this submenu allows to select the color of the font of the selected legend item. + - **Legend font color** - this allows to select the color of the font of the legend item. + - **Highlighted legend font color** - this submenu allows to select the color of the font of the selected legend item. .. _default_python_preferences: @@ -305,7 +305,7 @@ Python Viewer Preferences .. image:: ../images/pref_salome_pythonviewer.png :align: center -.. note:: +.. note:: The following settings are default and will be applied only for newly created Python viewers. Customization of already opened viewers can be done using local :ref:`custom_python_preferences` called by clicking on the corresponding icon of :ref:`python_viewer_page`. - **Font settings** allows setting font variant, size and style. @@ -368,13 +368,13 @@ Object Browser Preferences - **Default columns** - these checkboxes allow to display or hide **Value**, **Entry**, **IOR** and **Reference entry** columns in the Object Browser. -Shortcuts Preferences +Shortcut Preferences ##################### .. image:: ../images/pref_salome_shortcuts.png :align: center -- **Shortcuts settings** widget allows to define custom shortcuts for various operations. To change keyboard sequence for a certain action - select the action and press the custom keys combination. +- **Shortcut settings** widget allows to define custom shortcuts for various operations. To change keyboard sequence for a certain action - select the action and press the custom keys combination. :ref:`select_color_and_font_page`: "Font and color preferences" @@ -383,7 +383,7 @@ are most oftenly used types of user settings. When you change settings (click **OK** or **Apply** button) each module receives the notification about what preferences are changed. You can also click **Defaults** button to restore default preferences or **Close** -button to quit the dialog box without any changes. +button to quit the dialog box without any changes. **Import** button allows loading specific user file containing preferences from any location at the computer through a standard @@ -400,7 +400,7 @@ separate preferences file for each SALOME version in use. Preferences are saved to the file in the end of the working session and restored at the application start-up. -.. note:: +.. note:: The preferences you set will be default preferences for all **new** objects, but they are not retroactive and do not automatically apply to the existing objects. diff --git a/doc/salome/gui/locale/fr/LC_MESSAGES/setting_preferences.po b/doc/salome/gui/locale/fr/LC_MESSAGES/setting_preferences.po index 81d695f41..b5adda1b9 100644 --- a/doc/salome/gui/locale/fr/LC_MESSAGES/setting_preferences.po +++ b/doc/salome/gui/locale/fr/LC_MESSAGES/setting_preferences.po @@ -1175,7 +1175,7 @@ msgstr "" # e081f044dacf408690aa128eca010f21 #: ../../../../../../SRC/GUI_SRC/doc/salome/gui/input/setting_preferences.rst:357 msgid "" -"**Shortcuts settings** widget allows to define custom shortcuts for " +"**Shortcut settings** widget allows to define custom shortcuts for " "various operations. To change keyboard sequence for a certain action - " "select the action and press the custom keys combination." msgstr "" diff --git a/src/CAM/CAM_Module.cxx b/src/CAM/CAM_Module.cxx index 79b911de5..a88c8e721 100644 --- a/src/CAM/CAM_Module.cxx +++ b/src/CAM/CAM_Module.cxx @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -640,7 +641,7 @@ int CAM_Module::createMenu( const QString& subMenu, const int menu, { if ( !menuMgr() ) return -1; - + return menuMgr()->insert( subMenu, menu, group, id, idx, menuObj ); } @@ -704,7 +705,7 @@ int CAM_Module::createMenu( QAction* a, const int menu, const int id, const int { if ( !a || !menuMgr() ) return -1; - + ActionMgrLocker lock( menuMgr(), !myMenuShown ); int regId = registerAction( id, a ); @@ -754,7 +755,7 @@ int CAM_Module::createMenu( QAction* a, const QString& menu, const int id, const if ( !myMenuShown ) setMenuShown( a, false ); - + return intId != -1 ? regId : -1; } @@ -932,6 +933,11 @@ void CAM_Module::setToolShown( const int id, const bool on ) setToolShown( action( id ), on ); } +QString CAM_Module::makeActionID(const QString& theInModuleActionID) const +{ + return SUIT_ShortcutMgr::makeActionID(name(), theInModuleActionID); +} + /*! \brief Get action by specified \a id. \param id action ID @@ -978,18 +984,19 @@ int CAM_Module::actionId( const QAction* a ) const \param icon action icon \param menu menu text \param tip status bar tip - \param key keyboard accelerator + \param key will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true, the action will be toggled \param reciever action activation signal receiver object \param member action activation signal receiver slot + \param inModuleActionID module-unique action ID */ QAction* CAM_Module::createAction( const int id, const QString& text, const QIcon& icon, const QString& menu, const QString& tip, const int key, QObject* parent, const bool toggle, QObject* reciever, - const char* member, const QString& shortcutAction ) + const char* member, const QString& inModuleActionID ) { - return createAction( id, text, icon, menu, tip, QKeySequence(key), parent, toggle, reciever, member, shortcutAction ); + return createAction( id, text, icon, menu, tip, QKeySequence(key), parent, toggle, reciever, member, inModuleActionID ); } /*! @@ -1005,18 +1012,20 @@ QAction* CAM_Module::createAction( const int id, const QString& text, const QIco \param icon action icon \param menu menu text \param tip status bar tip - \param key keyboard accelerator + \param key will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true, the action will be toggled \param reciever action activation signal receiver object \param member action activation signal receiver slot + \param inModuleActionID module-unique action ID */ QAction* CAM_Module::createAction( const int id, const QString& text, const QIcon& icon, const QString& menu, const QString& tip, const QKeySequence& key, QObject* parent, const bool toggle, QObject* reciever, - const char* member, const QString& shortcutAction ) + const char* member, const QString& inModuleActionID ) { - QtxAction* a = new QtxAction( text, icon, menu, key, parent, toggle, shortcutAction ); + const QString actionID = makeActionID(inModuleActionID); + QtxAction* a = new QtxAction( text, icon, menu, key, parent, toggle, actionID ); a->setStatusTip( tip ); connect( a, SIGNAL( triggered( bool ) ), this, SLOT( moduleActionActivated() ), Qt::UniqueConnection ); diff --git a/src/CAM/CAM_Module.h b/src/CAM/CAM_Module.h index 7956b982f..20df84831 100644 --- a/src/CAM/CAM_Module.h +++ b/src/CAM/CAM_Module.h @@ -98,6 +98,8 @@ public: QtxActionMenuMgr* menuMgr() const; QtxActionToolMgr* toolMgr() const; + QString makeActionID(const QString& theInModuleActionID) const; + virtual QAction* action( const int ) const; virtual int actionId( const QAction* ) const; virtual QAction* createAction( const int, const QString&, const QIcon&, const QString&, @@ -125,7 +127,7 @@ public: virtual void logAction( QAction* ); bool isActionLoggingEnabled() const; void setActionLoggingEnabled( bool ); - + static QAction* separator(); public slots: @@ -144,7 +146,7 @@ public slots: private slots: void onInfoChanged( QString ); -protected: +protected: virtual bool isSelectionCompatible(); virtual CAM_DataModel* createDataModel(); @@ -170,7 +172,7 @@ private: QMap myActionMap; //!< menu actions bool myMenuShown; //!< menu shown flag bool myToolShown; //!< tool shown flag - bool myActionLoggingEnabled; //!< action logging enabled + bool myActionLoggingEnabled; //!< action logging enabled friend class CAM_Application; }; diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index 4152f174b..c897f7930 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -642,21 +642,14 @@ LightApp_SelectionMgr* LightApp_Application::selectionMgr() const /*!Creat action "New window" for certain type of viewer:*/ void LightApp_Application::createActionForViewer( const int id, const int parentId, - const QString& suffix, - const int accel ) + const QString& suffix ) { QString vtlt = tr( QString( "NEW_WINDOW_%1" ).arg( suffix ).toLatin1().constData() ); QString tip = tr( "CREATING_NEW_WINDOW" ).arg( vtlt.remove( "&" ) ); - QAction* a = createAction( id, // menu action id - tip, // status tip - QIcon(), // icon - vtlt, // menu text - tip, // tooltip - accel, // shortcut - desktop(), // parent - false, // toggle flag - this, // receiver - SLOT( onNewWindow() ) ); // slot + QAction* a = createAction(id, desktop(), false /*toggle*/, "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_" + suffix, + tip, vtlt, tip, QIcon(), + this, SLOT(onNewWindow())); + createMenu( a, parentId, -1 ); } @@ -672,7 +665,7 @@ void LightApp_Application::createActions() // Preferences createAction( PreferencesId, tr( "TOT_DESK_PREFERENCES" ), QIcon(), tr( "MEN_DESK_PREFERENCES" ), tr( "PRP_DESK_PREFERENCES" ), - Qt::CTRL+Qt::Key_P, desk, false, this, SLOT( onPreferences() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onPreferences() ), "/PRP_DESK_PREFERENCES" ); // Help menu @@ -800,6 +793,11 @@ void LightApp_Application::createActions() createAction( CloseId, tr( "TOT_CLOSE" ), QIcon(), tr( "MEN_DESK_CLOSE" ), tr( "PRP_CLOSE" ), Qt::CTRL+Qt::Key_F4, desk, false, this, SLOT( onCloseWindow() ) ); + + createAction( CloseId, desk, false /*toggle*/, "/PRP_CLOSE", + tr( "TOT_CLOSE" ), tr( "MEN_DESK_CLOSE" ), tr( "PRP_CLOSE" ), QIcon(), + this, SLOT( onCloseWindow() ) ); + createAction( CloseAllId, tr( "TOT_CLOSE_ALL" ), QIcon(), tr( "MEN_DESK_CLOSE_ALL" ), tr( "PRP_CLOSE_ALL" ), 0, desk, false, this, SLOT( onCloseAllWindow() ) ); createAction( GroupAllId, tr( "TOT_GROUP_ALL" ), QIcon(), tr( "MEN_DESK_GROUP_ALL" ), tr( "PRP_GROUP_ALL" ), @@ -811,38 +809,40 @@ void LightApp_Application::createActions() createMenu( separator(), windowMenu, -1, 0 ); #ifndef DISABLE_GLVIEWER - createActionForViewer( NewGLViewId, newWinMenu, QString::number( 0 ), Qt::ALT+Qt::Key_G ); + createActionForViewer( NewGLViewId, newWinMenu, QString::number( 0 ) ); #endif #ifndef DISABLE_PLOT2DVIEWER - createActionForViewer( NewPlot2dId, newWinMenu, QString::number( 1 ), Qt::ALT+Qt::Key_P ); + createActionForViewer( NewPlot2dId, newWinMenu, QString::number( 1 ) ); #endif #ifndef DISABLE_OCCVIEWER - createActionForViewer( NewOCCViewId, newWinMenu, QString::number( 2 ), Qt::ALT+Qt::Key_O ); + createActionForViewer( NewOCCViewId, newWinMenu, QString::number( 2 ) ); #endif #ifndef DISABLE_VTKVIEWER - createActionForViewer( NewVTKViewId, newWinMenu, QString::number( 3 ), Qt::ALT+Qt::Key_K ); + createActionForViewer( NewVTKViewId, newWinMenu, QString::number( 3 ) ); #endif #ifndef DISABLE_QXGRAPHVIEWER - createActionForViewer( NewQxSceneViewId, newWinMenu, QString::number( 4 ), Qt::ALT+Qt::Key_S ); + createActionForViewer( NewQxSceneViewId, newWinMenu, QString::number( 4 ) ); #endif #ifndef DISABLE_GRAPHICSVIEW - createActionForViewer( NewGraphicsViewId, newWinMenu, QString::number( 5 ), Qt::ALT+Qt::Key_C ); + createActionForViewer( NewGraphicsViewId, newWinMenu, QString::number( 5 ) ); #endif #ifndef DISABLE_PVVIEWER - createActionForViewer( NewPVViewId, newWinMenu, QString::number( 6 ), Qt::ALT+Qt::Key_A ); + createActionForViewer( NewPVViewId, newWinMenu, QString::number( 6 ) ); #endif #ifndef DISABLE_PYVIEWER - createActionForViewer( NewPyViewerId, newWinMenu, QString::number( 7 ), Qt::ALT+Qt::Key_Y ); + createActionForViewer( NewPyViewerId, newWinMenu, QString::number( 7 ) ); #endif #ifndef DISABLE_PV3DVIEWER - createActionForViewer( NewPV3DViewId, newWinMenu, QString::number( 8 ), Qt::ALT+Qt::Key_3 ); + createActionForViewer( NewPV3DViewId, newWinMenu, QString::number( 8 ) ); #endif #ifndef DISABLE_QTVIEWER createActionForViewer( NewQtViewId, newWinMenu, QString::number( 9 ), Qt::ALT+Qt::Key_Q ); #endif - createAction( RenameId, tr( "TOT_RENAME" ), QIcon(), tr( "MEN_DESK_RENAME" ), tr( "PRP_RENAME" ), - Qt::ALT+Qt::SHIFT+Qt::Key_R, desk, false, this, SLOT( onRenameWindow() ) ); + createAction( RenameId, desk, false /*toggle*/, "/PRP_RENAME", + tr( "TOT_RENAME" ), tr( "MEN_DESK_RENAME" ), tr( "PRP_RENAME" ), QIcon(), + this, SLOT( onRenameWindow() ) ); + createMenu( RenameId, windowMenu, -1 ); int fileMenu = createMenu( tr( "MEN_DESK_FILE" ), -1 ); @@ -859,7 +859,7 @@ void LightApp_Application::createActions() #endif // USE_SALOME_STYLE createAction( FullScreenId, tr( "TOT_FULLSCREEN" ), QIcon(), tr( "MEN_DESK_FULLSCREEN" ), tr( "PRP_FULLSCREEN" ), - Qt::Key_F11, desk, false, this, SLOT( onFullScreen() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onFullScreen() ), "/PRP_FULLSCREEN" ); int viewMenu = createMenu( tr( "MEN_DESK_VIEW" ), -1 ); @@ -1236,7 +1236,7 @@ void LightApp_Application::onExtRemoving(const QString& title) MESSAGE("Removing of an extension was cancelled"); return; // cancelled } - + if (activeStudy() && activeStudy()->isModified() && !onSaveDoc()) { // doc is not saved, or saving cancelled @@ -2470,7 +2470,7 @@ void LightApp_Application::showPreferences( const QStringList& path ) resourceMgr()->save(); // Update shortcuts - shortcutMgr()->updateShortcuts(); + shortcutMgr()->rebindActionsToKeySequences(); } delete prefDlg; @@ -3588,11 +3588,7 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref ) // .. "Shortcuts" preferences tab <> int shortcutTab = pref->addPreference( tr( "PREF_TAB_SHORTCUTS" ), salomeCat ); - // ... "Shortcuts settings" group <> - int shortcutGroup = pref->addPreference( tr( "PREF_GROUP_SHORTCUTS" ), shortcutTab ); - pref->addPreference( tr( "" ), shortcutGroup, - LightApp_Preferences::ShortcutTree, "shortcuts" ); - // ... "Shortcuts settings" group <> + pref->addPreference( tr( "" ), shortcutTab, LightApp_Preferences::ShortcutTree, "shortcuts" ); // .. "Shortcuts" preferences tab <> // . Top-level "SALOME" preferences group <> diff --git a/src/LightApp/LightApp_Application.h b/src/LightApp/LightApp_Application.h index 1bfb1345e..3b02baca1 100644 --- a/src/LightApp/LightApp_Application.h +++ b/src/LightApp/LightApp_Application.h @@ -224,8 +224,7 @@ protected: virtual void customize(); virtual void createActionForViewer( const int id, const int parentId, - const QString& suffix, - const int accel ); + const QString& suffix ); virtual SUIT_Study* createNewStudy(); virtual QWidget* createWindow( const int ); virtual void defaultWindows( QMap& ) const; diff --git a/src/LightApp/LightApp_Module.cxx b/src/LightApp/LightApp_Module.cxx index 9b6bd7558..fe3abbf30 100644 --- a/src/LightApp/LightApp_Module.cxx +++ b/src/LightApp/LightApp_Module.cxx @@ -159,7 +159,7 @@ void LightApp_Module::contextMenuPopup( const QString& client, QMenu* menu, QStr /*!Update object browser. * For updating model or whole object browser use update() method can be used. */ -void LightApp_Module::updateObjBrowser( bool theIsUpdateDataModel, +void LightApp_Module::updateObjBrowser( bool theIsUpdateDataModel, SUIT_DataObject* theDataObject ) { if (!getApp()->objectBrowser()) @@ -242,7 +242,7 @@ bool LightApp_Module::activateModule( SUIT_Study* study ) if ( action(myErase) ) action(myErase)->setEnabled(true); - application()->shortcutMgr()->setSectionEnabled( moduleName() ); + application()->shortcutMgr()->setActionsWithPrefixInIDEnabled( moduleName() ); /* BUG 0020498 : The Entry column is always shown at module activation The registration of column is moved into LightApp_Application @@ -282,8 +282,8 @@ bool LightApp_Module::deactivateModule( SUIT_Study* study ) if ( action(myErase) ) action(myErase)->setEnabled(false); - application()->shortcutMgr()->setSectionEnabled( moduleName(), false ); - + application()->shortcutMgr()->setActionsWithPrefixInIDEnabled( moduleName(), false ); + /* BUG 0020498 : The Entry column is always shown at module activation QString EntryCol = QObject::tr( "ENTRY_COLUMN" ); LightApp_DataModel* m = dynamic_cast( dataModel() ); @@ -302,9 +302,9 @@ bool LightApp_Module::deactivateModule( SUIT_Study* study ) void LightApp_Module::studyClosed( SUIT_Study* theStudy ) { CAM_Module::studyClosed( theStudy ); - + myIsFirstActivate = true; - + LightApp_Application* app = dynamic_cast(application()); if ( app ) { SUIT_DataBrowser* ob = app->objectBrowser(); @@ -452,12 +452,12 @@ QtxPopupMgr* LightApp_Module::popupMgr() QPixmap p; SUIT_Desktop* d = application()->desktop(); - - QAction + + QAction *disp = createAction( -1, tr( "TOP_SHOW" ), p, tr( "MEN_SHOW" ), tr( "STB_SHOW" ), - 0, d, false, this, SLOT( onShowHide() ), QString("General:Show object(s)") ), + 0, d, false, this, SLOT( onShowHide() ), QString("#General/Object(s)/Show") ), *erase = createAction( -1, tr( "TOP_HIDE" ), p, tr( "MEN_HIDE" ), tr( "STB_HIDE" ), - 0, d, false, this, SLOT( onShowHide() ) , QString("General:Hide object(s)") ), + 0, d, false, this, SLOT( onShowHide() ) , QString("#General/Object(s)/Hide") ), *dispOnly = createAction( -1, tr( "TOP_DISPLAY_ONLY" ), p, tr( "MEN_DISPLAY_ONLY" ), tr( "STB_DISPLAY_ONLY" ), 0, d, false, this, SLOT( onShowHide() ) ), *eraseAll = createAction( -1, tr( "TOP_ERASE_ALL" ), p, tr( "MEN_ERASE_ALL" ), tr( "STB_ERASE_ALL" ), @@ -467,7 +467,7 @@ QtxPopupMgr* LightApp_Module::popupMgr() myDisplayOnly = actionId( dispOnly ); myEraseAll = actionId( eraseAll ); - myPopupMgr->insert( disp, -1, 0 ); + myPopupMgr->insert( disp, -1, 0 ); myPopupMgr->insert( erase, -1, 0 ); myPopupMgr->insert( dispOnly, -1, 0 ); myPopupMgr->insert( eraseAll, -1, 0 ); @@ -616,7 +616,7 @@ void LightApp_Module::startOperation( const int id ) * * Creates operation with given id. You should not call this method, it will be called * automatically from startOperation. You may redefine this method in concrete module to -* create operations. +* create operations. */ LightApp_Operation* LightApp_Module::createOperation( const int id ) const { @@ -711,7 +711,7 @@ void LightApp_Module::onViewManagerRemoved( SUIT_ViewManager* ) /*! \brief Returns instance of operation by its id; if there is no operation corresponding to this id, null pointer is returned - \param id - operation id + \param id - operation id \return operation instance */ LightApp_Operation* LightApp_Module::operation( const int id ) const @@ -725,7 +725,7 @@ LightApp_Operation* LightApp_Module::operation( const int id ) const bool LightApp_Module::reusableOperation( const int /*id*/ ) { return true; -} +} /*! virtual method @@ -816,7 +816,7 @@ void LightApp_Module::updateModuleVisibilityState() // update visibility state of objects LightApp_Application* app = dynamic_cast(SUIT_Session::session()->activeApplication()); if ( !app ) return; - + SUIT_DataBrowser* ob = app->objectBrowser(); if ( !ob || !ob->model() ) return; @@ -834,9 +834,9 @@ void LightApp_Module::updateModuleVisibilityState() SUIT_DataObject* rootObj = ob->root(); if ( !rootObj ) return; - + DataObjectList listObj = rootObj->children( true ); - + SUIT_ViewModel* vmod = 0; if ( SUIT_ViewManager* vman = app->activeViewManager() ) vmod = vman->getViewModel(); @@ -861,15 +861,15 @@ void LightApp_Module::onObjectClicked( SUIT_DataObject* theObject, int theColumn LightApp_DataObject* lo = dynamic_cast( theObject ); if ( !lo ) return; - + // detect action index (from LightApp level) int id = -1; - + if ( study->visibilityState( lo->entry() ) == Qtx::ShownState ) id = myErase; else if ( study->visibilityState( lo->entry() ) == Qtx::HiddenState ) id = myDisplay; - + if ( id != -1 ) startOperation( id ); } diff --git a/src/LightApp/LightApp_PreferencesDlg.cxx b/src/LightApp/LightApp_PreferencesDlg.cxx index c747e034c..44de2ffa4 100644 --- a/src/LightApp/LightApp_PreferencesDlg.cxx +++ b/src/LightApp/LightApp_PreferencesDlg.cxx @@ -150,7 +150,7 @@ void LightApp_PreferencesDlg::onDefault() if ( myPrefs && myPrefs->resourceMgr() ) { QtxResourceMgr::WorkingMode prev = myPrefs->resourceMgr()->setWorkingMode( QtxResourceMgr::IgnoreUserValues ); - myPrefs->retrieve(); + myPrefs->retrieveDefault(); myPrefs->resourceMgr()->setWorkingMode( prev ); } emit defaultPressed(); diff --git a/src/LightApp/resources/LightApp.xml b/src/LightApp/resources/LightApp.xml index 3fa9fb424..90062e08a 100644 --- a/src/LightApp/resources/LightApp.xml +++ b/src/LightApp/resources/LightApp.xml @@ -148,7 +148,7 @@ - + @@ -250,28 +250,55 @@ - -
- +
+
-
- - -
-
- - - - - - - -
-
- - - - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/LightApp/resources/LightApp_msg_en.ts b/src/LightApp/resources/LightApp_msg_en.ts index f17c70c5a..90c371e24 100644 --- a/src/LightApp/resources/LightApp_msg_en.ts +++ b/src/LightApp/resources/LightApp_msg_en.ts @@ -1097,7 +1097,7 @@ File does not exist PREF_GROUP_SHORTCUTS - Shortcuts settings + Shortcut settings PREF_GROUP_FULL_SCREEN diff --git a/src/OCCViewer/OCCViewer_ViewWindow.cxx b/src/OCCViewer/OCCViewer_ViewWindow.cxx index 480c7cbe8..3a5336bee 100644 --- a/src/OCCViewer/OCCViewer_ViewWindow.cxx +++ b/src/OCCViewer/OCCViewer_ViewWindow.cxx @@ -242,7 +242,7 @@ const char* imageCrossCursor[] = { "................................", "................................"}; - + /*! \brief Constructor \param theDesktop main window of application @@ -1285,42 +1285,42 @@ void OCCViewer_ViewWindow::createActions() // Projections aAction = new QtxAction(tr("MNU_FRONT_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_FRONT" ) ), - tr( "MNU_FRONT_VIEW" ), 0, this, false, "Viewers:Front view"); + tr( "MNU_FRONT_VIEW" ), 0, this, false, "/#Viewers/View/Set X-"); aAction->setStatusTip(tr("DSC_FRONT_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onFrontView())); this->addAction(aAction); toolMgr()->registerAction( aAction, FrontId ); aAction = new QtxAction(tr("MNU_BACK_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_BACK" ) ), - tr( "MNU_BACK_VIEW" ), 0, this, false, "Viewers:Back view"); + tr( "MNU_BACK_VIEW" ), 0, this, false, "/#Viewers/View/Set X+"); aAction->setStatusTip(tr("DSC_BACK_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onBackView())); this->addAction(aAction); toolMgr()->registerAction( aAction, BackId ); aAction = new QtxAction(tr("MNU_TOP_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_TOP" ) ), - tr( "MNU_TOP_VIEW" ), 0, this, false, "Viewers:Top view"); + tr( "MNU_TOP_VIEW" ), 0, this, false, "/#Viewers/View/Set Z-"); aAction->setStatusTip(tr("DSC_TOP_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onTopView())); this->addAction(aAction); toolMgr()->registerAction( aAction, TopId ); aAction = new QtxAction(tr("MNU_BOTTOM_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_BOTTOM" ) ), - tr( "MNU_BOTTOM_VIEW" ), 0, this, false, "Viewers:Bottom view"); + tr( "MNU_BOTTOM_VIEW" ), 0, this, false, "/#Viewers/View/Set Z+"); aAction->setStatusTip(tr("DSC_BOTTOM_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onBottomView())); this->addAction(aAction); toolMgr()->registerAction( aAction, BottomId ); aAction = new QtxAction(tr("MNU_LEFT_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_LEFT" ) ), - tr( "MNU_LEFT_VIEW" ), 0, this, false, "Viewers:Left view"); + tr( "MNU_LEFT_VIEW" ), 0, this, false, "/#Viewers/View/Set Y+"); aAction->setStatusTip(tr("DSC_LEFT_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onLeftView())); this->addAction(aAction); toolMgr()->registerAction( aAction, LeftId ); aAction = new QtxAction(tr("MNU_RIGHT_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_RIGHT" ) ), - tr( "MNU_RIGHT_VIEW" ), 0, this, false, "Viewers:Right view"); + tr( "MNU_RIGHT_VIEW" ), 0, this, false, "/#Viewers/View/Set Y-"); aAction->setStatusTip(tr("DSC_RIGHT_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onRightView())); this->addAction(aAction); @@ -1328,7 +1328,7 @@ void OCCViewer_ViewWindow::createActions() // rotate anticlockwise aAction = new QtxAction(tr("MNU_ANTICLOCKWISE_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_ANTICLOCKWISE" ) ), - tr( "MNU_ANTICLOCKWISE_VIEW" ), 0, this, false, "Viewers:Rotate anticlockwise"); + tr( "MNU_ANTICLOCKWISE_VIEW" ), 0, this, false, "/#Viewers/View/Rotate anticlockwise"); aAction->setStatusTip(tr("DSC_ANTICLOCKWISE_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onAntiClockWiseView())); this->addAction(aAction); @@ -1336,7 +1336,7 @@ void OCCViewer_ViewWindow::createActions() // rotate clockwise aAction = new QtxAction(tr("MNU_CLOCKWISE_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_CLOCKWISE" ) ), - tr( "MNU_CLOCKWISE_VIEW" ), 0, this, false, "Viewers:Rotate clockwise"); + tr( "MNU_CLOCKWISE_VIEW" ), 0, this, false, "/#Viewers/View/Rotate clockwise"); aAction->setStatusTip(tr("DSC_CLOCKWISE_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onClockWiseView())); this->addAction(aAction); @@ -1371,10 +1371,10 @@ void OCCViewer_ViewWindow::createActions() aProjectionGroup->addAction( toolMgr()->action( OrthographicId ) ); aProjectionGroup->addAction( toolMgr()->action( PerspectiveId ) ); connect(aProjectionGroup, SIGNAL(triggered(QAction*)), this, SLOT(onProjectionType(QAction*))); - + // Reset aAction = new QtxAction(tr("MNU_RESET_VIEW"), aResMgr->loadPixmap( "OCCViewer", tr( "ICON_OCCVIEWER_VIEW_RESET" ) ), - tr( "MNU_RESET_VIEW" ), 0, this, false, "Viewers:Reset view"); + tr( "MNU_RESET_VIEW" ), 0, this, false, "/#Viewers/View/Reset"); aAction->setStatusTip(tr("DSC_RESET_VIEW")); connect(aAction, SIGNAL(triggered()), this, SLOT(onResetView())); this->addAction(aAction); @@ -1665,7 +1665,7 @@ void OCCViewer_ViewWindow::onFrontView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Xpos); onViewFitAll(); } @@ -1683,7 +1683,7 @@ void OCCViewer_ViewWindow::onBackView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Xneg); onViewFitAll(); } @@ -1701,7 +1701,7 @@ void OCCViewer_ViewWindow::onTopView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Zpos); onViewFitAll(); } @@ -1719,7 +1719,7 @@ void OCCViewer_ViewWindow::onBottomView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Zneg); onViewFitAll(); } @@ -1737,7 +1737,7 @@ void OCCViewer_ViewWindow::onLeftView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Yneg); onViewFitAll(); } @@ -1755,7 +1755,7 @@ void OCCViewer_ViewWindow::onRightView() Handle(V3d_View) aView3d = myViewPort->getView(); if (myAutomaticZoom) { - if ( !aView3d.IsNull() ) + if ( !aView3d.IsNull() ) aView3d->SetProj (V3d_Ypos); onViewFitAll(); } @@ -2420,7 +2420,7 @@ QImage OCCViewer_ViewWindow::dumpView() Image_PixMap aPix; view->ToPixMap(aPix, aWidth, aHeight, Graphic3d_BT_RGB); - + QImage anImage( aWidth, aHeight, QImage::Format_ARGB32 ); for ( int i = 0; i < aWidth; i++ ) { for ( int j = 0; j < aHeight; j++ ) { @@ -2429,7 +2429,7 @@ QImage OCCViewer_ViewWindow::dumpView() anImage.setPixelColor( i, j, color ); } } - + if ( aPix.IsTopDown() ) anImage = anImage.mirrored(); return anImage; @@ -3660,7 +3660,7 @@ void OCCViewer_ViewWindow::synchronize( SUIT_ViewWindow* theView ) aProps.getPosition( aPosition[0], aPosition[1], aPosition[2] ); aProps.getViewUp( anUpDir[0], anUpDir[1], anUpDir[2] ); aProps.getAxialScale( anAxialScale[0], anAxialScale[1], anAxialScale[2] ); - + try { aDestView->SetAt( aFocalPoint[0], aFocalPoint[1], aFocalPoint[2] ); aDestView->SetEye( aPosition[0], aPosition[1], aPosition[2] ); @@ -3670,7 +3670,7 @@ void OCCViewer_ViewWindow::synchronize( SUIT_ViewWindow* theView ) getViewPort()->setAxialScale( anAxialScale[0], anAxialScale[1], anAxialScale[2] ); aDestView->SetImmediateUpdate( Standard_True ); aDestView->Redraw(); - } + } catch (Standard_Failure&) { } diff --git a/src/Qtx/CMakeLists.txt b/src/Qtx/CMakeLists.txt index 571509786..f95706ef1 100644 --- a/src/Qtx/CMakeLists.txt +++ b/src/Qtx/CMakeLists.txt @@ -22,7 +22,10 @@ INCLUDE(UseQtExt) # --- options --- # additional include directories -INCLUDE_DIRECTORIES(${QT_INCLUDES}) +INCLUDE_DIRECTORIES( + ${QT_INCLUDES} + ${PROJECT_SOURCE_DIR}/src/SUIT +) # additional preprocessor / compiler flags ADD_DEFINITIONS(${QT_DEFINITIONS}) diff --git a/src/Qtx/Qtx.qrc b/src/Qtx/Qtx.qrc index e5017a8eb..dc31318e3 100644 --- a/src/Qtx/Qtx.qrc +++ b/src/Qtx/Qtx.qrc @@ -32,6 +32,8 @@ images/open.png images/close.png images/appicon.png + images/shortcut_disable.svg + images/shortcut_restore.svg - + diff --git a/src/Qtx/QtxAction.cxx b/src/Qtx/QtxAction.cxx index 83dd376f7..004207143 100644 --- a/src/Qtx/QtxAction.cxx +++ b/src/Qtx/QtxAction.cxx @@ -54,32 +54,30 @@ private: \class QtxAction \brief Generic action class. - The class QtxAction inherits QWidgetAction class and can be used + The class QtxAction inherits QWidgetAction class and can be used as base class when implementing any custom menu/toolbar actions. - It is necessary to subclass from QtxAction and redefine virtual + It is necessary to subclass from QtxAction and redefine virtual callback methods addedTo(), removedFrom() (which are called automatically when the action is added to the widget and removed from it) to customize - the action behavior. + the action behavior. */ /*! \brief Constructor. - Creates an action owned by \a parent. + Creates an action owned by \a parent. Parameter \a toggle can be used to make the action checkable. - Parameter \a shortcutAction can be used to assign the shortcut from - preferences. This parameter value corresponds to shortcut action identifier - in shortcut preferences. + Parameter \a ID is used to de(serialize) shortcut settings. \param parent parent object \param toggle if \c true the action will be a toggle action - \param shortcutAction shortcut action identifier + \param ID shortcut action identifier */ -QtxAction::QtxAction( QObject* parent, bool toggle, const QString& shortcutAction ) +QtxAction::QtxAction( QObject* parent, bool toggle, const QString& ID ) : QWidgetAction( parent ) { setCheckable( toggle ); - setShortcutActionName(shortcutAction); + setID(ID); QApplication::instance()->installEventFilter( this ); } @@ -88,30 +86,55 @@ QtxAction::QtxAction( QObject* parent, bool toggle, const QString& shortcutActio \brief Constructor. Creates an action owned by \a parent. Parameters \a text, - \a icon, \a menuText and \a accel specify the action's attributes. + \a menuText and \a ID specify the action's attributes. Parameter \a toggle can be used to make the action checkable. - Parameter \a shortcutAction can be used to assign the shortcut from - preferences. This parameter value corresponds to shortcut action identifier - in shortcut preferences. + Parameter \a ID is used to de(serialize) shortcut settings. + + \param parent parent object + \param toggle if \c true the action is a toggle action + \param ID shortcut action identifier + \param toolTip tooltip text + \param text menu text +*/ +QtxAction::QtxAction( QObject* parent, bool toggle, const QString& ID, + const QString& toolTip, const QString& text, const QIcon& icon ) +: QWidgetAction( parent ) +{ + setCheckable( toggle ); + setID(ID); + setToolTip( toolTip ); + setText( text ); + setIcon( icon ); + + QApplication::instance()->installEventFilter( this ); +} + +/*! + \brief Constructor. + + Creates an action owned by \a parent. Parameters \a text, + \a icon, \a menuText and \a ID specify the action's attributes. + Parameter \a toggle can be used to make the action checkable. + Parameter \a ID is used to de(serialize) shortcut settings. \param text tooltip text \param icon iconset \param menuText menu text - \param accel shortcut key sequence + \param accel will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true the action will be a toggle action - \param shortcutAction shortcut action identifier + \param ID shortcut action identifier */ -QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& menuText, - int accel, QObject* parent, bool toggle, const QString& shortcutAction ) +QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& menuText, + int accel, QObject* parent, bool toggle, const QString& ID ) : QWidgetAction( parent ) { setIcon( icon ); setText( menuText ); setToolTip( text ); - setShortcut( accel ); setCheckable( toggle ); - setShortcutActionName(shortcutAction); + setID(ID); + setShortcut(accel); QApplication::instance()->installEventFilter( this ); } @@ -120,30 +143,28 @@ QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& men \brief Constructor. Creates an action owned by \a parent. Parameters \a text, - \a icon, \a menuText and \a accel specify the action's attributes. + \a icon, \a menuText and \a ID specify the action's attributes. Parameter \a toggle can be used to make the action checkable. - Parameter \a shortcutAction can be used to assign the shortcut from - preferences. This parameter value corresponds to shortcut action identifier - in shortcut preferences. + Parameter \a ID is used to de(serialize) shortcut settings. \param text tooltip text \param icon iconset \param menuText menu text - \param accel shortcut key sequence + \param accel will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true the action will be a toggle action - \param shortcutAction shortcut action identifier + \param ID shortcut action identifier */ -QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& menuText, - const QKeySequence& accel, QObject* parent, bool toggle, const QString& shortcutAction ) +QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& menuText, + const QKeySequence& accel, QObject* parent, bool toggle, const QString& ID ) : QWidgetAction( parent ) { setIcon( icon ); setText( menuText ); setToolTip( text ); - setShortcut( accel ); setCheckable( toggle ); - setShortcutActionName(shortcutAction); + setID(ID); + setShortcut(accel); QApplication::instance()->installEventFilter( this ); } @@ -152,29 +173,27 @@ QtxAction::QtxAction( const QString& text, const QIcon& icon, const QString& men \brief Constructor. Creates an action owned by \a parent. Parameters \a text, - \a menuText and \a accel specify the action's attributes. + \a menuText and \a ID specify the action's attributes. Parameter \a toggle can be used to make the action checkable. - Parameter \a shortcutAction can be used to assign the shortcut from - preferences. This parameter value corresponds to shortcut action identifier - in shortcut preferences. + Parameter \a ID is used to de(serialize) shortcut settings. \param text tooltip text \param menuText menu text - \param accel shortcut key sequence + \param accel will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true the action is a toggle action - \param shortcutAction shortcut action identifier + \param ID shortcut action identifier */ QtxAction::QtxAction( const QString& text, const QString& menuText, - int accel, QObject* parent, bool toggle, const QString& shortcutAction ) + int accel, QObject* parent, bool toggle, const QString& ID ) : QWidgetAction( parent ) { setText( menuText ); setToolTip( text ); - setShortcut( accel ); setCheckable( toggle ); - setShortcutActionName(shortcutAction); - + setID(ID); + setShortcut(accel); + QApplication::instance()->installEventFilter( this ); } @@ -182,29 +201,27 @@ QtxAction::QtxAction( const QString& text, const QString& menuText, \brief Constructor. Creates an action owned by \a parent. Parameters \a text, - \a menuText and \a accel specify the action's attributes. + \a menuText and \a ID specify the action's attributes. Parameter \a toggle can be used to make the action checkable. - Parameter \a shortcutAction can be used to assign the shortcut from - preferences. This parameter value corresponds to shortcut action identifier - in shortcut preferences. + Parameter \a ID is used to de(serialize) shortcut settings. \param text tooltip text \param menuText menu text - \param accel shortcut key sequence + \param accel will be disabled by SUIT_ShortcutMgr! \param parent parent object \param toggle if \c true the action is a toggle action - \param shortcutAction shortcut action identifier + \param ID shortcut action identifier */ QtxAction::QtxAction( const QString& text, const QString& menuText, - const QKeySequence& accel, QObject* parent, bool toggle, const QString& shortcutAction ) + const QKeySequence& accel, QObject* parent, bool toggle, const QString& ID ) : QWidgetAction( parent ) { setText( menuText ); setToolTip( text ); - setShortcut( accel ); setCheckable( toggle ); - setShortcutActionName(shortcutAction); - + setID(ID); + setShortcut(accel); + QApplication::instance()->installEventFilter( this ); } @@ -217,8 +234,8 @@ QtxAction::~QtxAction() /*! \brief Customize action events. - - Sends a notification event to the action when it is added to + + Sends a notification event to the action when it is added to the widget or removed from it in order to perform custom processing. \param o object @@ -238,11 +255,29 @@ bool QtxAction::eventFilter( QObject* o, QEvent* e ) return QWidgetAction::eventFilter( o, e ); } +QString QtxAction::shortcutActionName() const +{ + return myID; +} + +void QtxAction::setShortcutActionName( const QString& shortcutAction ) +{ + myID = shortcutAction; +} + +const QString& QtxAction::ID() const { + return myID; +} + +void QtxAction::setID( const QString& theID) { + myID = theID; +} + /*! \brief Called when the action is added to the widget. - This method can be redefined in the subclasses to customize - the action behavior. Base implementation does nothing. + This method can be redefined in the subclasses to customize + the action behavior. Base implementation does nothing. \param w widget (should be menu or toolbar) \sa removedFrom() @@ -266,7 +301,7 @@ void QtxAction::removedFrom( QWidget* /*w*/ ) /*! \brief Process notification events. - + Calls addedTo() method when the action is added to the widget and removedFrom() when it is removed from the widget in order to perform custom processing. @@ -285,27 +320,3 @@ void QtxAction::customEvent( QEvent* e ) else removedFrom( ae->widget() ); } - -/*! - \brief Return shortcut action name for the action. - - \return shortcut action name - \sa setShortcutActionName() -*/ -QString QtxAction::shortcutActionName() const -{ - return myShortcutActionName; -} - -/*! - \brief Set shortcut action name to the action. - - Shortcut action name is used for shortcuts customization. - - \param shortcutAction shortcut action name - \sa shortcutActionName() -*/ -void QtxAction::setShortcutActionName( const QString& shortcutAction ) -{ - myShortcutActionName = shortcutAction; -} diff --git a/src/Qtx/QtxAction.h b/src/Qtx/QtxAction.h index cb36f3f64..a286a6ab6 100644 --- a/src/Qtx/QtxAction.h +++ b/src/Qtx/QtxAction.h @@ -29,8 +29,7 @@ #include "Qtx.h" #include - -class QIcon; +#include #ifdef WIN32 #pragma warning ( disable:4251 ) @@ -44,17 +43,25 @@ class QTX_EXPORT QtxAction : public QWidgetAction public: QtxAction( QObject* = 0, bool = false, const QString& = QString() ); - QtxAction( const QString&, const QString&, int, QObject*, bool = false, const QString& = QString() ); + QtxAction( QObject*, bool, const QString&, const QString&, const QString& = QString(), const QIcon& = QIcon()); + QtxAction( const QString&, const QString&, int , QObject*, bool = false, const QString& = QString() ); QtxAction( const QString&, const QString&, const QKeySequence&, QObject*, bool = false, const QString& = QString() ); - QtxAction( const QString&, const QIcon&, const QString&, int, QObject*, bool = false, const QString& = QString() ); + QtxAction( const QString&, const QIcon&, const QString&, int , QObject*, bool = false, const QString& = QString() ); QtxAction( const QString&, const QIcon&, const QString&, const QKeySequence&, QObject*, bool = false, const QString& = QString() ); virtual ~QtxAction(); virtual bool eventFilter( QObject*, QEvent* ); + [[deprecated("Use ID() instead.")]] QString shortcutActionName() const; + + [[deprecated("Use setID() instead.")]] void setShortcutActionName( const QString& ); + /** Unique for the entire application ID. */ + const QString& ID() const; + void setID( const QString& theID); + protected: virtual void addedTo( QWidget* ); virtual void removedFrom( QWidget* ); @@ -62,7 +69,7 @@ protected: virtual void customEvent( QEvent* ); private: - QString myShortcutActionName; + QString myID; }; #ifdef WIN32 diff --git a/src/Qtx/QtxPagePrefMgr.cxx b/src/Qtx/QtxPagePrefMgr.cxx index d0d861ed0..b81bcfb19 100644 --- a/src/Qtx/QtxPagePrefMgr.cxx +++ b/src/Qtx/QtxPagePrefMgr.cxx @@ -34,6 +34,8 @@ #include "QtxBackgroundTool.h" #include "QtxResourceMgr.h" +#include "SUIT_ShortcutMgr.h" + #include #include #include @@ -2216,7 +2218,7 @@ void QtxPagePrefEditItem::setDecimals( const int dec ) { if ( myDecimals == dec ) return; - + myDecimals = dec; updateEditor(); } @@ -2237,10 +2239,10 @@ int QtxPagePrefEditItem::echoMode() const \sa echoMode() */ void QtxPagePrefEditItem::setEchoMode( const int echo ) -{ +{ if ( myEchoMode == echo ) return; - + myEchoMode = echo; updateEditor(); } @@ -2329,7 +2331,7 @@ void QtxPagePrefEditItem::updateEditor() default: myEditor->setEchoMode(QLineEdit::Normal); } - + QValidator* val = 0; switch ( inputType() ) { @@ -2370,14 +2372,14 @@ QtxPagePrefSliderItem::QtxPagePrefSliderItem( const QString& title, QtxPreferenc { setControl( mySlider = new QSlider( Qt::Horizontal ) ); widget()->layout()->addWidget( myLabel = new QLabel( ) ); - + setMinimum( 0 ); setMaximum( 0 ); setSingleStep( 1 ); setPageStep( 1 ); - + mySlider->setTickPosition( QSlider::TicksBothSides ); - + connect (mySlider, SIGNAL(valueChanged(int)), this, SLOT(setIcon(int))); updateSlider(); } @@ -2488,17 +2490,17 @@ void QtxPagePrefSliderItem::setMaximum( const int& max ) */ void QtxPagePrefSliderItem::setIcons( const QList& icns ) { - if ( icns.isEmpty() ) + if ( icns.isEmpty() ) return; myIcons = icns; - - QSize maxsize(0, 0); + + QSize maxsize(0, 0); for ( QList::const_iterator it = myIcons.begin(); it != myIcons.end(); ++it ) { if ( (*it).isNull() ) continue; maxsize = maxsize.expandedTo( (*it).availableSizes().first() ); } myLabel->setFixedSize( maxsize ); - + updateSlider(); } @@ -2597,13 +2599,13 @@ void QtxPagePrefSliderItem::updateSlider() control()->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); mySlider->setFocusPolicy(Qt::StrongFocus); - + mySlider->setValue( val ); setSingleStep( stp ); setPageStep( ptp ); setMinimum( min ); setMaximum( max ); - + myLabel->setVisible( !myIcons.empty() ); widget()->layout()->setSpacing( !myIcons.empty() ? 6 : 0 ); } @@ -2781,7 +2783,7 @@ void QtxPagePrefSelectItem::setNumbers( const QList& ids ) for ( QList::const_iterator it = ids.begin(); it != ids.end(); ++it, i++ ) { if ( i >= mySelector->count() ) mySelector->addItem(QString("") ); - + mySelector->setId( i, *it ); } } @@ -2831,7 +2833,7 @@ void QtxPagePrefSelectItem::store() void QtxPagePrefSelectItem::retrieve() { QString txt = getString(); - + // try to find via the id int idx = mySelector->index( txt ); if ( idx < 0 ) @@ -3539,7 +3541,7 @@ void QtxPagePrefColorItem::retrieve() \brief GUI implementation of the resources item to store a bi-color value. The main color is specified explicitly. The secondary color is calculated - by changing "value" and "saturation" parameters of the main color in the + by changing "value" and "saturation" parameters of the main color in the HSV notation using specified delta. */ @@ -3700,10 +3702,10 @@ void QtxPagePrefFontItem::setFeatures( const int f ) } /*! - \brief Specifies whether widget works in Native or Custom mode. Native mode - is intended for working with system fonts. Custom mode is intended for - working with manually defined set of fonts. Set of custom fonts can be - specified with setFonts() method + \brief Specifies whether widget works in Native or Custom mode. Native mode + is intended for working with system fonts. Custom mode is intended for + working with manually defined set of fonts. Set of custom fonts can be + specified with setFonts() method \param mode mode from QtxFontEdit::Mode enumeration \sa mode() */ @@ -3723,7 +3725,7 @@ int QtxPagePrefFontItem::mode() const } /*! - \brief Sets list of custom fonts. + \brief Sets list of custom fonts. This method is intended for working in Custom mode only. \param fams list of families \sa fonts(), setMode() @@ -3734,7 +3736,7 @@ void QtxPagePrefFontItem::setFonts( const QStringList& fams ) } /*! - \brief Gets list of custom fonts + \brief Gets list of custom fonts \return list of families \sa setFonts(), setMode() */ @@ -3744,8 +3746,8 @@ QStringList QtxPagePrefFontItem::fonts() const } /*! - \brief Sets list of available font sizes. - This method is intended for working in Custom mode only. The list of sizes can + \brief Sets list of available font sizes. + This method is intended for working in Custom mode only. The list of sizes can be empty. In this case system generate listof size automatically from 8 till 72. \param sizes list of sizes \sa sizes(), setMode() @@ -3756,7 +3758,7 @@ void QtxPagePrefFontItem::setSizes( const QList& sizes ) } /*! - \brief Gets list of custom fonts + \brief Gets list of custom fonts \return list of families \sa setFonts(), setMode() */ @@ -4454,119 +4456,68 @@ void QtxPagePrefDateTimeItem::updateDateTime() myDateTime->setDisplayFormat( dispFmt ); } -/*! - \brief Constructor. - \param title preference item title - \param parent parent preference item - \param sect resource file section associated with the preference item - \param param resource file parameter associated with the preference item -*/ -QtxPagePrefShortcutBtnsItem::QtxPagePrefShortcutBtnsItem( const QString& title, QtxPreferenceItem* parent, const QString& sect, - const QString& param ): QtxPageNamedPrefItem( title, parent, sect, param ) -{ - setControl( myShortcut = new QtxShortcutEdit() ); -} - -/*! - \brief Destructor. -*/ -QtxPagePrefShortcutBtnsItem::~QtxPagePrefShortcutBtnsItem() -{ -} /*! - \brief Store preference item to the resource manager. - \sa retrieve() + \brief Creates preference item for editing of key bindings + \param theParent parent preference item. Must not be nullptr. */ -void QtxPagePrefShortcutBtnsItem::store() +QtxPagePrefShortcutTreeItem::QtxPagePrefShortcutTreeItem(QtxPreferenceItem* theParent) + : QtxPagePrefItem(QString(), theParent) { - setString( myShortcut->shortcut().toString() ); -} + auto container = std::shared_ptr(); + const auto itContainers = QtxPagePrefShortcutTreeItem::shortcutContainers.find(rootItem()); + if (itContainers == QtxPagePrefShortcutTreeItem::shortcutContainers.end()) { + container.reset(new SUIT_ShortcutContainer()); + QtxPagePrefShortcutTreeItem::shortcutContainers.emplace(rootItem(), container); + } + else { + container = itContainers->second.lock(); + if (!container) { + container.reset(new SUIT_ShortcutContainer()); + itContainers->second = container; + } + } -/*! - \brief Retrieve preference item from the resource manager. - \sa store() -*/ -void QtxPagePrefShortcutBtnsItem::retrieve() -{ - myShortcut->setShortcut( QKeySequence::fromString( getString() ) ); + QtxShortcutTree* tree = new QtxShortcutTree(container); + tree->myModuleIDs = SUIT_ShortcutMgr::get()->getShortcutModuleIDs(); + setWidget(tree); } /*! - \brief Constructor. - - Creates preference item for editing of key bindings - - \param title preference item title - \param parent parent preference item - \param sect resource file section associated with the preference item - \param param resource file parameter associated with the preference item + \brief Retrieves shortcut preferences from ShortcutMgr. + Updates UI of controlling widget. + \sa store() */ -QtxPagePrefShortcutTreeItem::QtxPagePrefShortcutTreeItem( const QString& title, QtxPreferenceItem* parent, const QString& sect, - const QString& /*param*/ ): QtxPageNamedPrefItem( title, parent, sect, "" ) +void QtxPagePrefShortcutTreeItem::retrieve() { - mySection = sect; - - myShortcutTree = new QtxShortcutTree(); - - // Retrieve shortcuts common sections from resources - QtxResourceMgr* resMgr = resourceMgr(); - if ( resMgr ){ - QString generalSections = resourceMgr()->stringValue( "shortcuts_settings", "general_sections", QString() ); - QStringList sectionsList = generalSections.split( ";", QString::SkipEmptyParts ); - myShortcutTree->setGeneralSections( sectionsList ); - } - - setControl( myShortcutTree ); + static_cast(widget())->setShortcutsFromManager(); } /*! - \brief Destructor. -*/ -QtxPagePrefShortcutTreeItem::~QtxPagePrefShortcutTreeItem() -{ -} - -/*! - \brief Retrieve preference item from the resource manager. + \brief Retrieves shortcut preferences from resource files, ignoring user preferences. + Updates UI of controlling widget. \sa store() */ -void QtxPagePrefShortcutTreeItem::retrieve() +void QtxPagePrefShortcutTreeItem::retrieveDefault() { - QtxResourceMgr* resMgr = resourceMgr(); - if ( resMgr ){ - QStringList secLst = resMgr->subSections( mySection, false ); - ShortcutMap aMap; QStringList paramLst; - for( int i = 0; i < secLst.size(); i++ ) { - paramLst = resMgr->parameters( QStringList() << mySection << secLst.at( i ) ); - for( int j = 0; j < paramLst.size(); j++ ) - resMgr->value( mySection + resMgr->sectionsToken() + secLst.at( i ), paramLst.at( j ),aMap[ paramLst.at( j ) ], false ); - myShortcutTree->setBindings( secLst.at( i ), aMap ); - aMap.clear(); - } - } + static_cast(widget())->setDefaultShortcuts(); } - + /*! - \brief Store preference item to the resource manager. + \brief Applies modified shortcut preferences to ShortcutMgr. + Updates UI of controlling widget. + And ShortcutMgr, in turn, serilizes shortcut preferences using the resource manager. \sa retrieve() */ void QtxPagePrefShortcutTreeItem::store() { - QStringList lst = myShortcutTree->sections(); - QString aSection; - QtxResourceMgr* resMgr = resourceMgr(); - - if ( resMgr ) { - for( int i = 0; i < lst.size(); i++ ) { - ShortcutMap* aMap( myShortcutTree->bindings( lst.at( i ) ) ); - aSection = mySection + resMgr->sectionsToken() + lst.at( i ); - for( ShortcutMap::const_iterator it = aMap->constBegin(); it != aMap->constEnd(); ++it ) - resMgr->setValue( aSection, it.key(), it.value() ); - } - } + static_cast(widget())->applyChangesToShortcutMgr(); } +/*static*/ std::map> QtxPagePrefShortcutTreeItem::shortcutContainers = +std::map>(); + + /*! \class QtxPagePrefBackgroundItem \brief GUI implementation of the resources item to store background data. @@ -4576,7 +4527,7 @@ void QtxPagePrefShortcutTreeItem::store() - texture image file - simple two-color gradient - complex custom gradient (NOT IMPLEMENTED YET) - + Allowed background modes can be specified using setModeAllowed() method. Texture modes can be enabled/disabled using setTextureModeAllowed() method. Also, showing texture controls can be enabled/disabled by means of @@ -4863,9 +4814,9 @@ void QtxPagePrefBackgroundItem::setOptionValue( const QString& name, const QVari /*! \brief Constructor. - Creates preference item of user defined widget. The item widget has to be inherited from + Creates preference item of user defined widget. The item widget has to be inherited from QtxUserDefinedContent class. An instance of this class has to be installed into the item - with help of setContent method. Methods optionValue and setOptionValue use pointer on the + with help of setContent method. Methods optionValue and setOptionValue use pointer on the content widget an qint64 value. \param parent parent preference item @@ -4874,7 +4825,7 @@ QtxUserDefinedItem::QtxUserDefinedItem( QtxPreferenceItem* parent ) : QtxPageNamedPrefItem(QString(), parent), myContent(0) { } - + /*! \brief Lets to Store preferences for content instance \sa retrieve() @@ -4912,7 +4863,7 @@ QVariant QtxUserDefinedItem::optionValue( const QString& theName ) const /*! * \brief Sets option value - * \param theName is a string "content" + * \param theName is a string "content" * \param theVal is a pointer on QtxUserDefinedContent class instance represented as qint64 value */ void QtxUserDefinedItem::setOptionValue( const QString& theName, const QVariant& theVal) @@ -4924,14 +4875,14 @@ void QtxUserDefinedItem::setOptionValue( const QString& theName, const QVariant& } else QtxPreferenceItem::setOptionValue( theName, theVal ); } - + /*! - * \brief Defines content of the property item as a Widget which has to be inherited from + * \brief Defines content of the property item as a Widget which has to be inherited from * QtxUserDefinedContent class. * \param theContent is an QtxUserDefinedContent class instance. */ -void QtxUserDefinedItem::setContent( QtxUserDefinedContent* theContent ) -{ - myContent = theContent; +void QtxUserDefinedItem::setContent( QtxUserDefinedContent* theContent ) +{ + myContent = theContent; setControl(myContent); } diff --git a/src/Qtx/QtxPagePrefMgr.h b/src/Qtx/QtxPagePrefMgr.h index 534173481..b2b854b42 100644 --- a/src/Qtx/QtxPagePrefMgr.h +++ b/src/Qtx/QtxPagePrefMgr.h @@ -33,13 +33,15 @@ #include #include +#include +#include + class QtxGridBox; class QtxFontEdit; class QtxGroupBox; class QtxComboBox; class QtxColorButton; class QtxBiColorTool; -class QtxShortcutEdit; class QtxShortcutTree; class QtxBackgroundTool; @@ -409,7 +411,7 @@ public: int decimals() const; void setDecimals( const int ); - + int echoMode() const; void setEchoMode( const int ); @@ -443,7 +445,7 @@ public: int pageStep() const; int minimum() const; int maximum() const; - QList icons() const; + QList icons() const; void setSingleStep( const int& ); void setPageStep( const int& ); @@ -741,31 +743,35 @@ private: QDateTimeEdit* myDateTime; }; -class QTX_EXPORT QtxPagePrefShortcutBtnsItem : public QtxPageNamedPrefItem -{ -public: - QtxPagePrefShortcutBtnsItem( const QString&, QtxPreferenceItem* = 0, - const QString& = QString(), const QString& = QString() ); - virtual ~QtxPagePrefShortcutBtnsItem(); - virtual void store(); - virtual void retrieve(); -private: - QtxShortcutEdit* myShortcut; -}; +class SUIT_ShortcutContainer; + -class QTX_EXPORT QtxPagePrefShortcutTreeItem : public QtxPageNamedPrefItem +class QTX_EXPORT QtxPagePrefShortcutTreeItem : public QtxPagePrefItem { public: - QtxPagePrefShortcutTreeItem( const QString&, QtxPreferenceItem* = 0, - const QString& = QString(), const QString& = QString() ); - virtual ~QtxPagePrefShortcutTreeItem(); - virtual void store(); + QtxPagePrefShortcutTreeItem(QtxPreferenceItem* theParent); + virtual ~QtxPagePrefShortcutTreeItem() = default; + virtual void retrieve(); - + virtual void retrieveDefault(); + virtual void store(); + private: QtxShortcutTree* myShortcutTree; - QString mySection; + + // { root item (preference window), shortcut container of synchronized trees (widgets within the same window) } + static std::map> shortcutContainers; + /** Why is this? + * Every QtxPagePrefMgr is eventually a preference window. Each preference window has button "Apply". + * When the button is pressed, all descendants of the QtxPagePrefMgr store changes they carry into preferences. + * The pitfall with shortcut trees is as follows: made in independent shortcut trees, changes may conflict, + * and merge of such changes is ambiguous. And the solution is to keep shortcut trees within the same window + * synchronized - all changes being made in a tree of a synchronized bundle are projected to other trees from the bundle + * without interacting with SUIT_ShortcutMgr. + * + * Every time shortcut preferences stored to the ShortcutMgr, all instances of QtxShortcutTree are updated. + */ }; class QTX_EXPORT QtxPagePrefBackgroundItem : public QObject, public QtxPageNamedPrefItem diff --git a/src/Qtx/QtxPreferenceMgr.cxx b/src/Qtx/QtxPreferenceMgr.cxx index 84fd552a6..3090568c7 100644 --- a/src/Qtx/QtxPreferenceMgr.cxx +++ b/src/Qtx/QtxPreferenceMgr.cxx @@ -122,7 +122,7 @@ void QtxPreferenceItem::Updater::customEvent( QEvent* /*e*/ ) \class QtxPreferenceItem \brief Base class for implementing of all the preference items. - To implement any specific preference item, cubclass from the + To implement any specific preference item, subclass from the QtxPreferenceItem and redefine store() and retrieve() methods. */ @@ -258,7 +258,7 @@ void QtxPreferenceItem::appendItem( QtxPreferenceItem* item ) /*! \brief Insert child preference item before specified item. - If the before item is 0 then new item is appended. + If the before item is 0, then new item is appended. Removes (if necessary) the item from the previous parent. @@ -511,6 +511,14 @@ void QtxPreferenceItem::setRestartRequired( const bool on ) \sa store() */ +/*! \brief Restore preference item (for example, from the resource file, ignoring user preferences). + If not overridden, it is equivalent to \ref retrieve(). +*/ +void QtxPreferenceItem::retrieveDefault() +{ + retrieve(); +} + /*! \brief Get the value of the associated resource file setting. \return associated resource file setting value @@ -996,6 +1004,17 @@ void QtxPreferenceMgr::retrieve() (*it)->retrieve(); } +/*! + \brief Retrieve all preference items from the resource manager, ignoring user preferences. + \sa store() +*/ +void QtxPreferenceMgr::retrieveDefault() +{ + QList items = childItems( true ); + for ( QList::iterator it = items.begin(); it != items.end(); ++it ) + (*it)->retrieveDefault(); +} + /*! \brief Dumps all values to the backup container. \sa fromBackup() diff --git a/src/Qtx/QtxPreferenceMgr.h b/src/Qtx/QtxPreferenceMgr.h index db4c6f95c..94512dc2e 100644 --- a/src/Qtx/QtxPreferenceMgr.h +++ b/src/Qtx/QtxPreferenceMgr.h @@ -83,6 +83,7 @@ public: virtual void store() = 0; virtual void retrieve() = 0; + virtual void retrieveDefault(); QString resourceValue() const; void setResourceValue( const QString& ); @@ -160,6 +161,7 @@ public: virtual void store(); virtual void retrieve(); + virtual void retrieveDefault(); virtual void update(); diff --git a/src/Qtx/QtxShortcutEdit.cxx b/src/Qtx/QtxShortcutEdit.cxx index 6cf00f287..fe845b574 100644 --- a/src/Qtx/QtxShortcutEdit.cxx +++ b/src/Qtx/QtxShortcutEdit.cxx @@ -22,383 +22,862 @@ #include #include #include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include #include #include +#include + +#include + #define COLUMN_SIZE 500 -static const char* delete_icon[] = { -"16 16 3 1", -"` c #810000", -" c none", -"# c #ffffff", -" ", -" ", -" ``# ``# ", -" ````# ``# ", -" ````# ``# ", -" ```# `# ", -" `````# ", -" ```# ", -" `````# ", -" ```# ``# ", -" ```# ``# ", -" ```# `# ", -" ```# `# ", -" `# `# ", -" ", -" " -}; -/*! - \brief Constructor - \param parent parent widget -*/ -QtxShortcutEdit::QtxShortcutEdit( QWidget* parent ) -: QFrame( parent ) +QtxKeySequenceEdit::QtxKeySequenceEdit(QWidget* parent) +: QFrame(parent) { initialize(); - myShortcut->installEventFilter(this); + myKeySequenceLineEdit->installEventFilter(this); } -/*! - \brief Destructor -*/ -QtxShortcutEdit::~QtxShortcutEdit() +/*! \brief Set a key sequence to edit. */ +void QtxKeySequenceEdit::setConfirmedKeySequence(const QKeySequence& theKeySequence) { + myConfirmedKeySequenceString = theKeySequence.toString(); + myKeySequenceLineEdit->setText(myConfirmedKeySequenceString); + myPrevKeySequenceString = myConfirmedKeySequenceString; } -/*! - \brief Sets custom shortcut - \param seq a key sequence describes a combination of keys - \sa shortcut() -*/ -void QtxShortcutEdit::setShortcut( const QKeySequence& seq ) +void QtxKeySequenceEdit::setEditedKeySequence(const QKeySequence& theKeySequence) { - QString txt = seq.toString(); - myPrevShortcutText = txt; - myShortcut->setText( txt ); + const QString keySequenceString = theKeySequence.toString(); + myKeySequenceLineEdit->setText(keySequenceString); + myPrevKeySequenceString = keySequenceString; } -/*! - \brief Gets custom shortcut - \return a key sequence describes a combination of keys - \sa setShortcut() -*/ -QKeySequence QtxShortcutEdit::shortcut() +QKeySequence QtxKeySequenceEdit::editedKeySequence() const +{ + return QKeySequence::fromString(myKeySequenceLineEdit->text()); +} + +/*! \returns true, if the edited key sequence differs from confirmed one. */ +bool QtxKeySequenceEdit::isKeySequenceModified() const +{ + return QKeySequence(myConfirmedKeySequenceString) != editedKeySequence(); +} + +/*! \brief Set confirmed key sequence to line editor. */ +void QtxKeySequenceEdit::restoreKeySequence() { - return QKeySequence::fromString( myShortcut->text() ); + myKeySequenceLineEdit->setText(myConfirmedKeySequenceString); + myPrevKeySequenceString = myConfirmedKeySequenceString; } /*! - \brief Gets the key sequence from keys that were pressed + \brief Gets the key sequence from keys that were pressed \param e a key event - \return a string representation of the key sequence + \returns a string representation of the key sequence */ -QString QtxShortcutEdit::parseEvent( QKeyEvent* e ) +/*static*/ QString QtxKeySequenceEdit::parseEvent(QKeyEvent* e) { bool isShiftPressed = e->modifiers() & Qt::ShiftModifier; bool isControlPressed = e->modifiers() & Qt::ControlModifier; bool isAltPressed = e->modifiers() & Qt::AltModifier; bool isMetaPressed = e->modifiers() & Qt::MetaModifier; - bool isModifiersPressed = isShiftPressed || isControlPressed || isAltPressed || isMetaPressed; + bool isModifiersPressed = isControlPressed || isAltPressed || isMetaPressed; // Do not treat Shift alone as a modifier! int result=0; - if( isControlPressed ) + if(isControlPressed) result += Qt::CTRL; - if( isAltPressed ) + if(isAltPressed) result += Qt::ALT; - if( isShiftPressed ) + if(isShiftPressed) result += Qt::SHIFT; - if( isMetaPressed ) + if(isMetaPressed) result += Qt::META; int aKey = e->key(); - if ( ( isValidKey( aKey ) && isModifiersPressed ) || ( ( aKey >= Qt::Key_F1 ) && ( aKey <= Qt::Key_F12 ) ) ) + if ((isValidKey(aKey) && isModifiersPressed) || ((aKey >= Qt::Key_F1) && (aKey <= Qt::Key_F12))) result += aKey; - return QKeySequence( result ).toString(); + return QKeySequence(result).toString(); } /*! \brief Check if the key event contains a 'valid' key - \param aKey the code of the key - \return \c true if the key is 'valid' + \param theKey the code of the key + \returns \c true if the key is 'valid' */ - -bool QtxShortcutEdit::isValidKey( int aKey ) +/*static*/ bool QtxKeySequenceEdit::isValidKey(int theKey) { - if ( aKey == Qt::Key_Underscore || aKey == Qt::Key_Escape || - ( aKey >= Qt::Key_Backspace && aKey <= Qt::Key_Delete ) || - ( aKey >= Qt::Key_Home && aKey <= Qt::Key_PageDown ) || - ( aKey >= Qt::Key_F1 && aKey <= Qt::Key_F12 ) || - ( aKey >= Qt::Key_Space && aKey <= Qt::Key_Asterisk ) || - ( aKey >= Qt::Key_Comma && aKey <= Qt::Key_Question ) || - ( aKey >= Qt::Key_A && aKey <= Qt::Key_AsciiTilde ) ) + if ( theKey == Qt::Key_Underscore || theKey == Qt::Key_Escape || + ( theKey >= Qt::Key_Backspace && theKey <= Qt::Key_Delete ) || + ( theKey >= Qt::Key_Home && theKey <= Qt::Key_PageDown ) || + ( theKey >= Qt::Key_F1 && theKey <= Qt::Key_F12 ) || + ( theKey >= Qt::Key_Space && theKey <= Qt::Key_Asterisk ) || + ( theKey >= Qt::Key_Comma && theKey <= Qt::Key_Question ) || + ( theKey >= Qt::Key_A && theKey <= Qt::Key_AsciiTilde ) ) return true; return false; } -/*! - \brief Called when "Clear" button is clicked. -*/ -void QtxShortcutEdit::onCliked() +/*! \brief Called when "Clear" button is clicked. */ +void QtxKeySequenceEdit::onClear() { - myShortcut->setText( "" ); + myKeySequenceLineEdit->setText(""); + myPrevKeySequenceString = ""; + emit editingFinished(); } -/*! - \brief Called when myShortcut loses focus. -*/ -void QtxShortcutEdit::onEditingFinished() +/*! \brief Called when myKeySequenceLineEdit loses focus. */ +void QtxKeySequenceEdit::onEditingFinished() { - if ( myShortcut->text().endsWith("+") ) - myShortcut->setText( myPrevShortcutText ); + if (myKeySequenceLineEdit->text().endsWith("+")) + myKeySequenceLineEdit->setText(myPrevKeySequenceString); + else + myPrevKeySequenceString = myKeySequenceLineEdit->text(); + emit editingFinished(); } /*! \brief Custom event filter. \param obj event receiver object \param event event - \return \c true if further event processing should be stopped + \returns \c true if further event processing should be stopped */ -bool QtxShortcutEdit::eventFilter(QObject* obj, QEvent* event) -{ - if ( obj == myShortcut ) { - if (event->type() == QEvent::KeyPress ) { - QKeyEvent* keyEvent = static_cast(event); - QString text = parseEvent( keyEvent ); - if ( keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace ) - onCliked(); - if ( text != "" ) - myShortcut->setText( text ); +bool QtxKeySequenceEdit::eventFilter(QObject* theObject, QEvent* theEvent) +{ + if (theObject == myKeySequenceLineEdit) { + if (theEvent->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(theEvent); + QString text = parseEvent(keyEvent); + if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace) + myKeySequenceLineEdit->setText(""); + if (!text.isEmpty()) + myKeySequenceLineEdit->setText(text); + + emit editingStarted(); return true; } - if ( event->type() == QEvent::KeyRelease ) { - if ( myShortcut->text().endsWith("+") ) - myShortcut->setText( myPrevShortcutText ); - else myPrevShortcutText = myShortcut->text(); - + if (theEvent->type() == QEvent::KeyRelease) { + onEditingFinished(); return true; } - } + } return false; } /* \brief Perform internal intialization. */ -void QtxShortcutEdit::initialize() +void QtxKeySequenceEdit::initialize() { - myPrevShortcutText = QString(); + static const int PIXMAP_SIZE = 30; QHBoxLayout* base = new QHBoxLayout( this ); - base->setMargin( 0 ); - base->setSpacing( 5 ); + base->setMargin(0); + base->setSpacing(5); + + base->addWidget(myKeySequenceLineEdit = new QLineEdit(this)); + setFocusProxy(myKeySequenceLineEdit); + + QToolButton* clearBtn = new QToolButton(); + auto clearPixmap = QPixmap(":/images/shortcut_disable.svg"); + clearPixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation); + clearBtn->setIcon(clearPixmap); + clearBtn->setToolTip(tr("Disable shortcut.")); + base->addWidget(clearBtn); + + QToolButton* restoreBtn = new QToolButton(); + auto restorePixmap = QPixmap(":/images/shortcut_restore.svg"); + restorePixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation); + restoreBtn->setIcon(restorePixmap); + restoreBtn->setToolTip(tr("Restore the currently applied key sequence.")); + base->addWidget(restoreBtn); + + myKeySequenceLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + clearBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + restoreBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear())); + connect(restoreBtn, SIGNAL(clicked()), this, SIGNAL(restoreFromShortcutMgrClicked())); + connect(myKeySequenceLineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished())); +} - base->addWidget( myShortcut = new QLineEdit( this ) ); - QToolButton* deleteBtn = new QToolButton(); - deleteBtn->setIcon( QPixmap( delete_icon ) ); - base->addWidget( deleteBtn ); - - myShortcut->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); - deleteBtn->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); +/*! \param theParent must not be nullptr. */ +QtxEditKeySequenceDialog::QtxEditKeySequenceDialog(QtxShortcutTree* theParent) +: QDialog(theParent) +{ + setMinimumWidth(500); + setWindowTitle(tr("Change key sequence")); + QVBoxLayout* layout = new QVBoxLayout(this); + myActionName = new QLabel(this); + myActionName->setTextFormat(Qt::RichText); + myKeySequenceEdit = new QtxKeySequenceEdit(this); + myTextEdit = new QTextEdit(this); + layout->addWidget(myActionName); + layout->addWidget(myKeySequenceEdit); + layout->addWidget(myTextEdit); + myActionName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + myKeySequenceEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + myTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + myTextEdit->setReadOnly(true); + myTextEdit->setAcceptRichText(true); + myTextEdit->setPlaceholderText(tr("No conflicts.")); + setFocusProxy(myKeySequenceEdit); + + QHBoxLayout* buttonLayout = new QHBoxLayout(this); + layout->addLayout(buttonLayout); + QPushButton* confirmButton = new QPushButton(tr("Confirm"), this); + confirmButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + cancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + buttonLayout->addStretch(); + buttonLayout->addWidget(confirmButton); + buttonLayout->addWidget(cancelButton); + + connect(myKeySequenceEdit, SIGNAL(editingStarted()), this, SLOT(onEditingStarted())); + connect(myKeySequenceEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished())); + connect(myKeySequenceEdit, SIGNAL(restoreFromShortcutMgrClicked()), this, SLOT(onRestoreFromShortcutMgr())); + connect(confirmButton, SIGNAL(clicked()), this, SLOT(onConfirm())); + connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); +} - connect( deleteBtn, SIGNAL( clicked() ), this, SLOT( onCliked() ) ); - connect( myShortcut, SIGNAL( editingFinished() ), this, SLOT( onEditingFinished() ) ); +void QtxEditKeySequenceDialog::setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID) +{ + myModuleID = theModuleID; + myInModuleActionID = theInModuleActionID; } -/*! - \brief Constructor - \param parent parent widget -*/ -QtxShortcutTree::QtxShortcutTree( QWidget * parent ) : QTreeWidget( parent ) +const QString& QtxEditKeySequenceDialog::moduleID() const { return myModuleID; } +const QString& QtxEditKeySequenceDialog::inModuleActionID() const { return myInModuleActionID; } + +void QtxEditKeySequenceDialog::setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip) +{ + myActionName->setText("" + theModuleName + "  " + theActionName); + myActionName->setToolTip(theActionToolTip); +} + +void QtxEditKeySequenceDialog::setConfirmedKeySequence(const QKeySequence& theSequence) +{ + myKeySequenceEdit->setConfirmedKeySequence(theSequence); +} + +QKeySequence QtxEditKeySequenceDialog::editedKeySequence() const +{ + return myKeySequenceEdit->editedKeySequence(); +} + +int QtxEditKeySequenceDialog::exec() +{ + myKeySequenceEdit->setFocus(Qt::ActiveWindowFocusReason); + return QDialog::exec(); +} + +void QtxEditKeySequenceDialog::onEditingStarted() +{ + myTextEdit->setEnabled(false); +} + +void QtxEditKeySequenceDialog::onEditingFinished() { - setColumnCount( 2 ); - setSelectionMode( QAbstractItemView::SingleSelection ); - setColumnWidth ( 0, COLUMN_SIZE); + updateConflictsMessage(); +} + +void QtxEditKeySequenceDialog::onRestoreFromShortcutMgr() +{ + const auto shortcutMgr = SUIT_ShortcutMgr::get(); + myKeySequenceEdit->setEditedKeySequence(shortcutMgr->getKeySequence(myModuleID, myInModuleActionID)); + updateConflictsMessage(); +} + +/*! Updates message with list of actions, whose shortcuts will be disabled on Confirm. */ +void QtxEditKeySequenceDialog::updateConflictsMessage() +{ + myTextEdit->setEnabled(true); + QTextDocument* doc = myTextEdit->document(); + if (!doc) { + doc = new QTextDocument(myTextEdit); + myTextEdit->setDocument(doc); + } + + if (!myKeySequenceEdit->isKeySequenceModified()) { + doc->clear(); + return; + } + + const QKeySequence newKeySequence = editedKeySequence(); + + const auto shortcutTree = static_cast(parentWidget()); + /** {moduleID, inModuleActionID}[] */ + std::set> conflicts = shortcutTree->shortcutContainer()->getConflicts(myModuleID, myInModuleActionID, newKeySequence); + if (!conflicts.empty()) { + const auto shortcutMgr = SUIT_ShortcutMgr::get(); + + QString report = "" + tr("These shortcuts will be disabled on confirm:") + ""; + { + report += "
    "; + for (const auto& conflict : conflicts) { + const QString conflictingModuleName = shortcutMgr->getModuleName(conflict.first); + const QString conflictingActionName = shortcutMgr->getActionName(conflict.first, conflict.second); + report += "
  • " + conflictingModuleName + "  " + conflictingActionName + "
  • "; + } + report += "
"; + } + doc->setHtml(report); + } + else /* if no conflicts */ { + doc->clear(); + } +} + +void QtxEditKeySequenceDialog::onConfirm() +{ + if (myKeySequenceEdit->isKeySequenceModified()) + accept(); + else + reject(); +} + + +/*! \brief Compensates lack of std::distance(), which is introduced in C++17. +\returns -1, if theIt does not belong to the */ +template +size_t indexOf( + const Container& theContainer, + const typename Container::iterator& theIt +) { + auto it = theContainer.begin(); + size_t distance = 0; + while (it != theContainer.end()) { + if (it == theIt) + return distance; + + it++; + distance++; + } + return -1; +} + + +/*! \param theContainer Share the same container between several trees, +to edit them synchronously even without exchange of changes with SUIT_ShortcutMgr. +Pass nullptr to create non-synchronized tree. */ +QtxShortcutTree::QtxShortcutTree( + std::shared_ptr theContainer, + QWidget* theParent +) : QTreeWidget(theParent), +myShortcutContainer(theContainer ? theContainer : std::shared_ptr(new SUIT_ShortcutContainer())), +mySortKey(QtxShortcutTree::SortKey::Name), mySortOrder(QtxShortcutTree::SortOrder::Ascending) +{ + setColumnCount(2); + setSelectionMode(QAbstractItemView::SingleSelection); + setColumnWidth(0, COLUMN_SIZE); + setSortingEnabled(false); // Items are sorted in the same way, as in ShortcutContainer. + header()->setSectionResizeMode(QHeaderView::Interactive); + { + QMap labelMap; + labelMap[QtxShortcutTree::ElementIdx::Name] = tr("Action"); + labelMap[QtxShortcutTree::ElementIdx::KeySequence] = tr("Key sequence"); + setHeaderLabels(labelMap.values()); + } + setExpandsOnDoubleClick(false); // Open shortcut editor on double click instead. setSortingEnabled(false); - headerItem()->setHidden ( true ); + setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + myEditDialog = new QtxEditKeySequenceDialog(this); this->installEventFilter(this); - connect( this, SIGNAL( currentItemChanged ( QTreeWidgetItem*, QTreeWidgetItem* ) ), this, SLOT( onCurrentItemChanged ( QTreeWidgetItem*, QTreeWidgetItem* ) ) ); + connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int))); + QtxShortcutTree::instances[myShortcutContainer.get()].emplace(this); } -/*! - \brief Destructor -*/ -QtxShortcutTree::~QtxShortcutTree(){} +QtxShortcutTree::~QtxShortcutTree() +{ + QtxShortcutTree::instances[myShortcutContainer.get()].erase(this); + if (QtxShortcutTree::instances[myShortcutContainer.get()].empty()) + QtxShortcutTree::instances.erase(myShortcutContainer.get()); +} -/*! - \brief Custom event filter. - \param obj event receiver object - \param event event - \return \c true if further event processing should be stopped -*/ -bool QtxShortcutTree::eventFilter(QObject* /*obj*/, QEvent* event) -{ - if ( currentItem() && currentItem()->isSelected() ) { - - if (event->type() == QEvent::KeyPress ) { - QKeyEvent* keyEvent = static_cast(event); - QString text = QtxShortcutEdit::parseEvent( keyEvent ); - if ( keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace ) - currentItem()->setText( 1, "" ); - if ( text != "" ) { - if ( text.endsWith( "+" ) || checkUniqueness( currentItem(), text ) ) - currentItem()->setText( 1, text ); +/*! \brief Copies shortcuts from ShortcutMgr. (Re)displays shortcuts of myModuleIDs. */ +void QtxShortcutTree::setShortcutsFromManager() +{ + const auto shortcutMgr = SUIT_ShortcutMgr::get(); + *myShortcutContainer = shortcutMgr->getShortcutContainer(); + // nb! ShortcutMgr never removes shortcuts from its container, only disables. + + updateItems(false /*theHighlightModified*/, true /*theUpdateSyncTrees*/); +} + +/*! \brief Copies shortcuts from resources, user files are not accounted. (Re)displays shortcuts of myModuleIDs. */ +void QtxShortcutTree::setDefaultShortcuts() +{ + SUIT_ShortcutContainer defaultShortcuts; + SUIT_ShortcutMgr::fillContainerFromPreferences(defaultShortcuts, true /*theDefaultOnly*/); + + myShortcutContainer->merge(defaultShortcuts, true /*theOverride*/, true /*theTreatAbsentIncomingAsDisabled*/); + // nb! SUIT_ShortcutContainer never erases shortcuts, only disables. + + updateItems(true /*theHighlightModified*/, true /*theUpdateSyncTrees*/); +} + +/*! \brief Applies pending changes to ShortcutMgr. Updates other instances of QtxShortcutTree. */ +void QtxShortcutTree::applyChangesToShortcutMgr() +{ + const auto mgr = SUIT_ShortcutMgr::get(); + mgr->mergeShortcutContainer(*myShortcutContainer); + + // Update non-synchronized with this instances. + for (const auto& containerAndSyncTrees : QtxShortcutTree::instances) { + if (containerAndSyncTrees.first == myShortcutContainer.get()) + continue; + + const std::set& syncTrees = containerAndSyncTrees.second; + const auto itFirstSyncTree = syncTrees.begin(); + if (itFirstSyncTree == syncTrees.end()) + continue; + + (*itFirstSyncTree)->setShortcutsFromManager(); + const auto editDialog = (*itFirstSyncTree)->myEditDialog; + editDialog->setConfirmedKeySequence(mgr->getShortcutContainer().getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID())); + editDialog->updateConflictsMessage(); + } +} + +std::shared_ptr QtxShortcutTree::shortcutContainer() const +{ + return myShortcutContainer; +} + +/*! \brief Does not sort modules. */ +void QtxShortcutTree::sort(QtxShortcutTree::SortKey theKey, QtxShortcutTree::SortOrder theOrder) +{ + if (theKey == mySortKey && theOrder == mySortOrder) + return; + + mySortKey == theKey; + mySortOrder = theOrder; + + for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) { + const auto moduleItem = static_cast(topLevelItem(moduleIdx)); + const auto sortedChildren = getSortedChildren(moduleItem); + moduleItem->takeChildren(); + + for (const auto childItem : sortedChildren) { + moduleItem->addChild(childItem); + } + } +} + +/*! \param If theUpdateSyncTrees, trees sharing the same shortcut container are updated. */ +void QtxShortcutTree::updateItems(bool theHighlightModified, bool theUpdateSyncTrees) +{ + const auto shortcutMgr = SUIT_ShortcutMgr::get(); + const QString lang = SUIT_ShortcutMgr::getLang(); + + for (const QString& moduleID : myModuleIDs) { + const auto& moduleShortcuts = myShortcutContainer->getModuleShortcutsInversed(moduleID); + if (moduleShortcuts.empty()) { + // Do not display empty module. + const auto moduleItemAndIdx = findModuleFolderItem(moduleID); + if (moduleItemAndIdx.second >= 0) + delete takeTopLevelItem(moduleItemAndIdx.second); + + continue; + } + + const auto moduleItemAndIdx = findModuleFolderItem(moduleID); + QtxShortcutTreeFolder* moduleItem = moduleItemAndIdx.first; + if (!moduleItem) { + moduleItem = new QtxShortcutTreeFolder(moduleID); + moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang); + addTopLevelItem(moduleItem); + moduleItem->setFlags(Qt::ItemIsEnabled); + + auto sortedChildren = getSortedChildren(moduleItem); + for (const auto& shortcut : moduleShortcuts) { + const QString& inModuleActionID = shortcut.first; + const QKeySequence& keySequence = shortcut.second; + const QString keySequenceString = keySequence.toString(); + + auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID); + if (!actionItem) { + ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\"."); + continue; + } + + actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang); + actionItem->setKeySequence(keySequenceString); + + if (theHighlightModified) { + const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID); + actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence); + } + + insertChild(moduleItem, sortedChildren, actionItem); } - return true; + + moduleItem->setExpanded(true); // Make tree expanded on first show. } - if ( event->type() == QEvent::KeyRelease ) { - if ( currentItem()->text( 1 ).endsWith( "+" ) ) - currentItem()->setText( 1, myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] ); - else myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] = currentItem()->text( 1 ); + else /* if the tree has the module-item */ { + for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) { + // Update exisiting items of a module. + QtxShortcutTreeAction* const childItem = static_cast(moduleItem->child(childIdx)); + const auto itShortcut = moduleShortcuts.find(childItem->myInModuleActionID); + if (itShortcut == moduleShortcuts.end()) { + // Shortcut of the item has been removed from myShortcutContainer - impossible. + continue; + } + const QKeySequence& newKeySequence = itShortcut->second; + const QString newKeySequenceString = newKeySequence.toString(); + if (childItem->keySequence() != newKeySequenceString) + childItem->setKeySequence(newKeySequenceString); + + if (theHighlightModified) { + const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID); + childItem->highlightKeySequenceAsModified(newKeySequence != appliedKeySequence); + } + else + childItem->highlightKeySequenceAsModified(false); + } - return true; - } - } - return false; + // Add new items if myShortcutContainer acquired new shortcuts, which may happen if a developer forgot + // to add shortcuts for registered actions to resource files. + if (moduleItem->childCount() < moduleShortcuts.size()) { + auto sortedChildren = getSortedChildren(moduleItem); + for (const auto& shortcut : moduleShortcuts) { + const QString& inModuleActionID = shortcut.first; + const auto predicate = [&inModuleActionID](const QtxShortcutTreeItem* const theItem) -> bool { + return static_cast(theItem)->myInModuleActionID == inModuleActionID; + }; + + if (std::find_if(sortedChildren.begin(), sortedChildren.end(), predicate) == sortedChildren.end()) { + const auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID); + if (!actionItem) { + ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\"."); + continue; + } + + const QKeySequence& keySequence = shortcut.second; + actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang); + actionItem->setKeySequence(keySequence.toString()); + + if (theHighlightModified) { + const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID); + actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence); + } + + insertChild(moduleItem, sortedChildren, actionItem); + } + } + } + } + } + + if (theUpdateSyncTrees) { + const std::set& syncTrees = QtxShortcutTree::instances[myShortcutContainer.get()]; + for (const auto syncTree: syncTrees) { + if (syncTree == this) + continue; + + syncTree->updateItems(theHighlightModified, false /*theUpdateSyncTrees*/); + const auto editDialog = syncTree->myEditDialog; + editDialog->setConfirmedKeySequence(myShortcutContainer->getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID())); + editDialog->updateConflictsMessage(); + } + } } -/*! - \brief Called when the current item changes. - \param cur the current item - \param prev the previous current item -*/ -void QtxShortcutTree::onCurrentItemChanged( QTreeWidgetItem* /*cur*/, QTreeWidgetItem* prev ) +/*! \returns Pointer and index of top-level item. +If the tree does not contain an item with theModuleID, returns {nullptr, -1}. */ +std::pair QtxShortcutTree::findModuleFolderItem(const QString& theModuleID) const { - if ( prev && prev->text( 1 ).endsWith( "+" ) ) - prev->setText( 1, myPrevBindings[ prev->parent()->text( 0 ) ][ prev->text( 0 ) ] ); + for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) { + QtxShortcutTreeFolder* moduleItem = static_cast(topLevelItem(moduleIdx)); + if (moduleItem->myModuleID == theModuleID) + return std::pair(moduleItem, moduleIdx); + } + return std::pair(nullptr, -1); } -/*! - \brief Set key bindings to the tree - \param title the name of top-level item - \param theShortcutMap map of key bindings -*/ -void QtxShortcutTree::setBindings( const QString& title, const ShortcutMap& theShortcutMap ) -{ - QTreeWidgetItem* item= new QTreeWidgetItem(); - QFont font = item->font(0); - font.setBold(true); - - if ( findItems( title, Qt::MatchFixedString ).isEmpty() ) { - item->setText( 0, title ); - item->setFont( 0, font ); - addTopLevelItem( item ); - item->setFlags( Qt::ItemIsEnabled ); - } else { - item = findItems( title, Qt::MatchFixedString ).first(); - item->takeChildren(); +/*! \returns Children of theParentItem being sorted according to current sort mode and order. */ +std::set> QtxShortcutTree::getSortedChildren(QtxShortcutTreeFolder* theParentItem) +{ + QList> sortSchema = QtxShortcutTree::DEFAULT_SORT_SCHEMA; + { + for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) { + if (itSameKey->first == mySortKey) { + sortSchema.erase(itSameKey); + break; + } + } + sortSchema.push_front(std::pair(mySortKey, mySortOrder)); } - for( ShortcutMap::const_iterator it = theShortcutMap.constBegin(); it != theShortcutMap.constEnd(); ++it ) - item->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) ); - myPrevBindings.insert( title, theShortcutMap); + + static const QCollator collator; + const std::function comparator = + [this, sortSchema, &collator](const QtxShortcutTreeItem* theItemA, const QtxShortcutTreeItem* theItemB) { + int res = 0; + for (const auto& keyAndOrder : sortSchema) { + int res = 0; + res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first)); + if (res != 0) + return keyAndOrder.second == QtxShortcutTree::SortOrder::Ascending ? res < 0 : res > 0; + } + return false; + }; + + std::set> sortedChildren(comparator); + for (int childIdx = 0; childIdx < theParentItem->childCount(); childIdx++) { + QtxShortcutTreeAction* const childItem = static_cast(theParentItem->child(childIdx)); + sortedChildren.emplace(childItem); + } + return sortedChildren; } -/*! - \brief Get all sections names. - \return list of section names -*/ -QStringList QtxShortcutTree::sections() const +/*! \brief Inserts theChildItem to theParentItem and theSortedChildren. +Does not check whether theSortedChildren are actually child items of theParentItem. +Does not check whether current item sort schema is same as one of theSortedChildren. */ +void QtxShortcutTree::insertChild( + QtxShortcutTreeFolder* theParentItem, + std::set>& theSortedChildren, + QtxShortcutTreeItem* theChildItem +) { + auto emplaceRes = theSortedChildren.emplace(theChildItem); + theParentItem->insertChild(indexOf(theSortedChildren, emplaceRes.first), theChildItem); +} + +void QtxShortcutTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx) +{ + { + QtxShortcutTreeItem* const item = static_cast(theItem); + // Do not react if folder-item is clicked. + if (item->type() != QtxShortcutTreeItem::Type::Action) + return; + } + + QtxShortcutTreeAction* const actionItem = static_cast(theItem); + + myEditDialog->setModuleAndActionID(actionItem->myModuleID, actionItem->myInModuleActionID); + QString actionToolTip = actionItem->toolTip(QtxShortcutTree::ElementIdx::Name); + actionToolTip.truncate(actionToolTip.lastIndexOf('\n') + 1); + myEditDialog->setModuleAndActionName( + static_cast(actionItem->parent())->name(), + actionItem->name(), + actionToolTip + ); + myEditDialog->setConfirmedKeySequence(QKeySequence::fromString(actionItem->keySequence())); + myEditDialog->updateConflictsMessage(); + const bool somethingChanged = myEditDialog->exec() == QDialog::Accepted; + + if (!somethingChanged) + return; + + const QKeySequence newKeySequence = myEditDialog->editedKeySequence(); + + /** { moduleID, inModuleActionID }[] */ + std::set> disabledActionIDs = myShortcutContainer->setShortcut(actionItem->myModuleID, actionItem->myInModuleActionID, newKeySequence, true /*override*/); + + /** { moduleID, {inModuleActionID, keySequence}[] }[] */ + std::map> changes; + changes[actionItem->myModuleID][actionItem->myInModuleActionID] = newKeySequence.toString(); + for (const auto moduleAndActionID : disabledActionIDs) { + changes[moduleAndActionID.first][moduleAndActionID.second] = QString(); + } + + // Set new key sequences to shortcut items. + for (const auto& moduleIDAndChanges : changes) { + const QString& moduleID = moduleIDAndChanges.first; + + const auto moduleItemAndIdx = findModuleFolderItem(moduleID); + const auto moduleItem = moduleItemAndIdx.first; + if (!moduleItem) + continue; + + /** {inModuleActionID, newKeySequence}[] */ + const std::map& moduleChanges = moduleIDAndChanges.second; + + // Go through module' shortcut items, and highlight those, whose key sequences differ from applied key sequences. + for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) { + QtxShortcutTreeAction* const childItem = static_cast(moduleItem->child(childIdx)); + const auto itChange = moduleChanges.find(childItem->myInModuleActionID); + if (itChange == moduleChanges.end()) { + // The shortcut has not been changed. + continue; + } + + childItem->setKeySequence(itChange->second); + + const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID); + childItem->highlightKeySequenceAsModified(QKeySequence::fromString(itChange->second) != appliedKeySequence); + } + } +} + +/*static*/ const QList> QtxShortcutTree::DEFAULT_SORT_SCHEMA = +{ + {QtxShortcutTree::SortKey::Name, QtxShortcutTree::SortOrder::Ascending}, + {QtxShortcutTree::SortKey::ToolTip, QtxShortcutTree::SortOrder::Ascending}, + {QtxShortcutTree::SortKey::KeySequence, QtxShortcutTree::SortOrder::Ascending}, + {QtxShortcutTree::SortKey::ID, QtxShortcutTree::SortOrder::Ascending} +}; + +/*static*/ std::map> QtxShortcutTree::instances = +std::map>(); + + + +QtxShortcutTreeItem::QtxShortcutTreeItem(const QString& theModuleID) +: QTreeWidgetItem(), myModuleID(theModuleID) +{ } + +QString QtxShortcutTreeItem::name() const +{ + return text(QtxShortcutTree::ElementIdx::Name); +} + + +QtxShortcutTreeFolder::QtxShortcutTreeFolder(const QString& theModuleID) +: QtxShortcutTreeItem(theModuleID) { - QStringList lst; - for( int i = 0; i < topLevelItemCount(); i++ ) - lst << topLevelItem( i )->text( 0 ); - return lst; + QFont f = font(QtxShortcutTree::ElementIdx::Name); + f.setBold(true); + setFont(QtxShortcutTree::ElementIdx::Name, f); + setText(QtxShortcutTree::ElementIdx::Name, theModuleID); } -ShortcutMap* QtxShortcutTree::bindings( const QString& sec ) const +void QtxShortcutTreeFolder::setAssets(std::shared_ptr theAssets, const QString& theLang) { - ShortcutMap* aMap = new ShortcutMap(); - QTreeWidgetItem* item = findItems( sec, Qt::MatchFixedString ).first(); - int nbChildren = item->childCount(); + if (!theAssets) + return; + + setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon); - for( int i = 0; i < nbChildren; i++ ) { - QTreeWidgetItem* child = item->child(i); - aMap->insert( child->text( 0 ), child->text( 1 ) ); + const auto& ldaMap = theAssets->myLangDependentAssets; + if (ldaMap.empty()) { + setText(QtxShortcutTree::ElementIdx::Name, myModuleID); + return; } - return aMap; + auto itLDA = ldaMap.find(theLang); + if (itLDA == ldaMap.end()) + itLDA = ldaMap.begin(); + + const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second; + const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName; + setText(QtxShortcutTree::ElementIdx::Name, name); } -void QtxShortcutTree::focusOutEvent ( QFocusEvent* event ) +QString QtxShortcutTreeFolder::getValue(QtxShortcutTree::SortKey theKey) const { - QWidget::focusOutEvent( event ); - if ( currentItem() && currentItem()->isSelected() ) - currentItem()->setText( 1, myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] ); + switch (theKey) { + case QtxShortcutTree::SortKey::ID: + return myModuleID; + case QtxShortcutTree::SortKey::Name: + return name(); + case QtxShortcutTree::SortKey::ToolTip: + return name(); + default: + return QString(); + } } -/*! - \brief Set the list of shortcuts general sections. - - Key combinations in general sections should not intersect - with any other key combinations. - \param sectionsList list of common section names -*/ -void QtxShortcutTree::setGeneralSections( const QStringList& sectionsList ) +QtxShortcutTreeAction::QtxShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID) +: QtxShortcutTreeItem(theModuleID), myInModuleActionID(theInModuleActionID) { - myGeneralSections = sectionsList; + setText(QtxShortcutTree::ElementIdx::Name, theInModuleActionID); + setToolTip( + QtxShortcutTree::ElementIdx::Name, + theInModuleActionID + (theInModuleActionID.at(theInModuleActionID.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.") + ); + setToolTip(QtxShortcutTree::ElementIdx::KeySequence, QtxShortcutTree::tr("Double click to edit key sequence.")); } -/*! - \brief Check uniqueness of the shortcut. - \param item current item of the shortcut tree - \param shortcut shortcut appointed for the current item - \return \c true if the given shortcut is allowed -*/ -bool QtxShortcutTree::checkUniqueness( QTreeWidgetItem* item, const QString& shortcut ) -{ - // List of sections to check shortcut intersections - QStringList sectionsList; - - // Current section - QString currentSection = currentItem()->parent()->text( 0 ); - - // If the current section is general - if ( myGeneralSections.contains(currentSection) ) { - sectionsList = sections(); - int currentSectionIndex = sectionsList.indexOf(currentSection); - sectionsList.move( currentSectionIndex, 0); - } - else { - sectionsList = myGeneralSections; - sectionsList.prepend(currentSection); +/*static*/ QtxShortcutTreeAction* QtxShortcutTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID) +{ + if (theInModuleActionID.isEmpty()) { + ShCutDbg("QtxShortcutTreeItem: attempt to create item with empty action ID."); + return nullptr; } - // Iterate on sections - QStringList::const_iterator it; - for( it = sectionsList.constBegin(); it != sectionsList.constEnd(); ++it ) { - QString section = *it; - - // Iterate on actual section - QTreeWidgetItem* sectionRoot = findItems( section, Qt::MatchFixedString ).first(); - int nbChildren = sectionRoot->childCount(); - - for( int i = 0; i < nbChildren; i++ ) { - QTreeWidgetItem* child = sectionRoot->child(i); - - if ( (child != item) && (shortcut == child->text( 1 )) ) { - bool res = QMessageBox::warning( parentWidget(), tr("Warning"), - tr("The \"%1\" shortcut has already used by the \"%2\" action.\n") - .arg(shortcut, section + ":" + child->text( 0 ) ) + - tr("Do you want to reassign it from that action to the current one?"), - QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes; - if (res) - child->setText( 1, "" ); - return res; - } - } + return new QtxShortcutTreeAction(theModuleID, theInModuleActionID); +} + +void QtxShortcutTreeAction::setAssets(std::shared_ptr theAssets, const QString& theLang) +{ + if (!theAssets) + return; + + setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon); + + const auto& ldaMap = theAssets->myLangDependentAssets; + if (ldaMap.empty()) { + setText(QtxShortcutTree::ElementIdx::Name, myInModuleActionID); + return; } - return true; + auto itLDA = ldaMap.find(theLang); + if (itLDA == ldaMap.end()) + itLDA = ldaMap.begin(); + + const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second; + const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName; + setText(QtxShortcutTree::ElementIdx::Name, name); + + const QString& actionToolTip = lda.myToolTip.isEmpty() ? name : lda.myToolTip; + setToolTip( + QtxShortcutTree::ElementIdx::Name, + actionToolTip + (actionToolTip.at(actionToolTip.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.") + ); } + +QString QtxShortcutTreeAction::getValue(QtxShortcutTree::SortKey theKey) const +{ + switch (theKey) { + case QtxShortcutTree::SortKey::ID: + return myInModuleActionID; + case QtxShortcutTree::SortKey::Name: + return name(); + case QtxShortcutTree::SortKey::ToolTip: + return toolTip(QtxShortcutTree::ElementIdx::Name); + case QtxShortcutTree::SortKey::KeySequence: + return keySequence(); + default: + return QString(); + } +} + +void QtxShortcutTreeAction::setKeySequence(const QString& theKeySequence) +{ + setText(QtxShortcutTree::ElementIdx::KeySequence, theKeySequence); +} + +QString QtxShortcutTreeAction::keySequence() const +{ + return text(QtxShortcutTree::ElementIdx::KeySequence); +} + +/*! \brief Highlights text at ElementIdx::KeySequence. */ +void QtxShortcutTreeAction::highlightKeySequenceAsModified(bool theHighlight) +{ + static const QBrush bgHighlitingBrush = QBrush(Qt::darkGreen); + static const QBrush fgHighlitingBrush = QBrush(Qt::white); + static const QBrush noBrush = QBrush(); + + setBackground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? bgHighlitingBrush : noBrush); + setForeground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? fgHighlitingBrush : noBrush); +} \ No newline at end of file diff --git a/src/Qtx/QtxShortcutEdit.h b/src/Qtx/QtxShortcutEdit.h index 231e65a9d..862c80d86 100644 --- a/src/Qtx/QtxShortcutEdit.h +++ b/src/Qtx/QtxShortcutEdit.h @@ -17,71 +17,249 @@ // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com // -#ifndef QTXSHORTCUTEDIT_H -#define QTXSHORTCUTEDIT_H +#ifndef QTXSHORTCUTTREE_H +#define QTXSHORTCUTTREE_H #include "Qtx.h" - +#include #include #include +#include "SUIT_ShortcutMgr.h" +#include +#include +#include +#include + class QLineEdit; +class QLabel; class QPushButton; class QTreeWidgetItem; -typedef QMap< QString, QString > ShortcutMap; - -class QTX_EXPORT QtxShortcutEdit : public QFrame +class QTX_EXPORT QtxKeySequenceEdit : public QFrame { Q_OBJECT public: - QtxShortcutEdit( QWidget* = 0 ); - virtual ~QtxShortcutEdit(); - void setShortcut( const QKeySequence& ); - QKeySequence shortcut(); - static QString parseEvent( QKeyEvent* ); - static bool isValidKey( int ); + QtxKeySequenceEdit(QWidget* = nullptr); + virtual ~QtxKeySequenceEdit() = default; + + void setConfirmedKeySequence(const QKeySequence&); + void setEditedKeySequence(const QKeySequence&); + QKeySequence editedKeySequence() const; + bool isKeySequenceModified() const; + void restoreKeySequence(); + + static QString parseEvent(QKeyEvent*); + static bool isValidKey(int); +signals: + void editingStarted(); + void editingFinished(); + void restoreFromShortcutMgrClicked(); private slots: - void onCliked(); + void onClear(); void onEditingFinished(); protected: - virtual bool eventFilter( QObject*, QEvent* ); + virtual bool eventFilter(QObject*, QEvent*); private: void initialize(); private: - QLineEdit* myShortcut; - QString myPrevShortcutText; + QLineEdit* myKeySequenceLineEdit; + QString myConfirmedKeySequenceString; + + // Last valid key sequence string from myKeySequenceLineEdit. + QString myPrevKeySequenceString; +}; + + +class QtxShortcutTree; +class QtxShortcutTreeItem; +class QtxShortcutTreeFolder; +class QtxShortcutTreeAction; +class QTextEdit; + + +class QTX_EXPORT QtxEditKeySequenceDialog : public QDialog +{ + Q_OBJECT + +public: + QtxEditKeySequenceDialog(QtxShortcutTree* theParent); + QtxEditKeySequenceDialog(const QtxEditKeySequenceDialog&) = delete; + QtxEditKeySequenceDialog& operator=(const QtxEditKeySequenceDialog&) = delete; + virtual ~QtxEditKeySequenceDialog() = default; + + void setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID); + const QString& moduleID() const; + const QString& inModuleActionID() const; + + void setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip = ""); + + void setConfirmedKeySequence(const QKeySequence& theSequence); + QKeySequence editedKeySequence() const; + + void updateConflictsMessage(); + + int exec(); + +private slots: + void onEditingStarted(); + void onEditingFinished(); + void onRestoreFromShortcutMgr(); + void onConfirm(); + +private: + QString myModuleID; + QString myInModuleActionID; + QLabel* myActionName; + QtxKeySequenceEdit* myKeySequenceEdit; + QTextEdit* myTextEdit; }; + class QTX_EXPORT QtxShortcutTree : public QTreeWidget { Q_OBJECT public: - QtxShortcutTree( QWidget * parent = 0 ); + enum ElementIdx { + Name = 0, + KeySequence = 1, // Empty, if item is folder item. + }; + + enum class SortKey { + ID, + Name, + ToolTip, + KeySequence, + }; + + enum class SortOrder { + Ascending, + Descending + }; + + QtxShortcutTree( + std::shared_ptr theContainer = std::shared_ptr(), + QWidget* theParent = nullptr + ); + QtxShortcutTree(const QtxShortcutTree&) = delete; + QtxShortcutTree& operator=(const QtxShortcutTree&) = delete; virtual ~QtxShortcutTree(); - void setBindings( const QString&, const ShortcutMap& ); - ShortcutMap* bindings( const QString& ) const; - QStringList sections() const; - void setGeneralSections( const QStringList& ); -protected: - virtual bool eventFilter( QObject*, QEvent* ); - virtual void focusOutEvent( QFocusEvent* ); - virtual bool checkUniqueness( QTreeWidgetItem*, const QString& ); + void setShortcutsFromManager(); + void setDefaultShortcuts(); + void applyChangesToShortcutMgr(); + + std::shared_ptr shortcutContainer() const; + + void sort(QtxShortcutTree::SortKey theKey, QtxShortcutTree::SortOrder theOrder); + +private: + void updateItems(bool theHighlightModified, bool theUpdateSyncTrees); + std::pair findModuleFolderItem(const QString& theModuleID) const; + + std::set> getSortedChildren(QtxShortcutTreeFolder* theParentItem); + + void insertChild( + QtxShortcutTreeFolder* theParentItem, + std::set>& theSortedChildren, + QtxShortcutTreeItem* theChildItem + ); private slots: - void onCurrentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* ); + void onItemDoubleClicked(QTreeWidgetItem* theWidgetItem, int theColIdx); + +public: + /** Keeps IDs of modules, which will are shown on setShortcutsFromManager(). */ + std::set myModuleIDs; + + static const QList> DEFAULT_SORT_SCHEMA; + +private: + /** Allows to modify plenty of shortcuts and then apply them to SUIT_ShortcutMgr as a batch. */ + const std::shared_ptr myShortcutContainer; + + QtxEditKeySequenceDialog* myEditDialog; + + QtxShortcutTree::SortKey mySortKey; + QtxShortcutTree::SortOrder mySortOrder; + + /** + * Ensures that, if several QtxShortcutTree instances coexist, + * all of them are updated when one of them applies pending changes to SUIT_ShortcutMgr. + * + * Sharing of SUIT_ShortcutContainer allows to keep some trees synchronized even without + * applying changes to SUIT_ShortcutMgr. Why? See QtxPagePrefShortcutTreeItem. + * + * Access is not synchronized in assumption, that all instances live in the same thread. + */ + static std::map> instances; +}; + + +class QtxShortcutTreeItem : public QTreeWidgetItem +{ +public: + enum Type { + Folder = 0, + Action = 1, + }; + +protected: + QtxShortcutTreeItem(const QString& theModuleID); + +public: + virtual ~QtxShortcutTreeItem() = default; + virtual QtxShortcutTreeItem::Type type() const = 0; + + virtual void setAssets(std::shared_ptr theAssets, const QString& theLang) = 0; + QString name() const; + + virtual QString getValue(QtxShortcutTree::SortKey theKey) const = 0; +public: + const QString myModuleID; +}; + + +class QtxShortcutTreeFolder : public QtxShortcutTreeItem +{ +public: + QtxShortcutTreeFolder(const QString& theModuleID); + virtual ~QtxShortcutTreeFolder() = default; + virtual QtxShortcutTreeItem::Type type() const { return QtxShortcutTreeItem::Type::Folder; }; + + virtual void setAssets(std::shared_ptr theAssets, const QString& theLang); + + virtual QString getValue(QtxShortcutTree::SortKey theKey) const; +}; + + +class QtxShortcutTreeAction : public QtxShortcutTreeItem +{ private: - QMap< QString, ShortcutMap > myPrevBindings; - QStringList myGeneralSections; + QtxShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID); + +public: + static QtxShortcutTreeAction* create(const QString& theModuleID, const QString& theInModuleActionID); + virtual ~QtxShortcutTreeAction() = default; + virtual QtxShortcutTreeItem::Type type() const { return QtxShortcutTreeItem::Type::Action; }; + + virtual void setAssets(std::shared_ptr theAssets, const QString& theLang); + + virtual QString getValue(QtxShortcutTree::SortKey theKey) const; + + void setKeySequence(const QString& theKeySequence); + QString keySequence() const; + void highlightKeySequenceAsModified(bool theHighlight); + + const QString myInModuleActionID; }; -#endif // QTXSHORTCUTEDIT_H +#endif // QTXSHORTCUTTREE_H diff --git a/src/Qtx/QtxWorkspaceAction.cxx b/src/Qtx/QtxWorkspaceAction.cxx index 4b37a3c75..0decdea84 100644 --- a/src/Qtx/QtxWorkspaceAction.cxx +++ b/src/Qtx/QtxWorkspaceAction.cxx @@ -78,8 +78,8 @@ QtxWorkspace* QtxWorkspaceAction::workspace() const /*! \brief Set actions to be visible in the menu. - - Actions, which IDs are set in \a flags parameter, will be shown in the + + Actions, which IDs are set in \a flags parameter, will be shown in the menu bar. Other actions will not be shown. \param flags ORed together actions flags @@ -164,17 +164,6 @@ QString QtxWorkspaceAction::statusTip( const int id ) const return txt; } -/*! - \brief Set keyboard accelerator for the specified action. - \param id menu action ID - \param a new keyboard accelerator -*/ -void QtxWorkspaceAction::setAccel( const int id, const int a ) -{ - if ( action( id ) ) - action( id )->setShortcut( a ); -} - /*! \brief Set menu item icon for the specified action. \param id menu action ID @@ -403,7 +392,7 @@ void QtxWorkspaceAction::activateItem( const int idx ) /*! \brief Called when menu item is activated by the user. - + Perform the corresponding action. \param id menu item identifier diff --git a/src/Qtx/QtxWorkspaceAction.h b/src/Qtx/QtxWorkspaceAction.h index 42e5f2ae3..ffa676c46 100644 --- a/src/Qtx/QtxWorkspaceAction.h +++ b/src/Qtx/QtxWorkspaceAction.h @@ -63,7 +63,6 @@ public: int accel( const int ) const; QString statusTip( const int ) const; - void setAccel( const int, const int ); void setIcon( const int, const QIcon& ); void setText( const int, const QString& ); void setStatusTip( const int, const QString& ); diff --git a/src/Qtx/QtxWorkstack.cxx b/src/Qtx/QtxWorkstack.cxx index 6a5ba33f5..c8a4f9ab4 100644 --- a/src/Qtx/QtxWorkstack.cxx +++ b/src/Qtx/QtxWorkstack.cxx @@ -1728,10 +1728,10 @@ QtxWorkstack::QtxWorkstack( QWidget* parent ) myWorkWin( 0 ), myWorkArea( 0 ) { - myActionsMap.insert( SplitVertical, new QtxAction( QString(), tr( "Split vertically" ), 0, this ) ); - myActionsMap.insert( SplitHorizontal, new QtxAction( QString(), tr( "Split horizontally" ), 0, this ) ); - myActionsMap.insert( Close, new QtxAction( QString(), tr( "Close" ), 0, this ) ); - myActionsMap.insert( Rename, new QtxAction( QString(), tr( "Rename" ), 0, this ) ); + myActionsMap.insert( SplitVertical, new QtxAction( this, false /*toggle*/, "/PRP_DESK_WINDOW_VSPLIT", tr( "Split vertically" ) ) ); + myActionsMap.insert( SplitHorizontal, new QtxAction( this, false /*toggle*/, "/PRP_DESK_WINDOW_HSPLIT", tr( "Split horizontally" ) ) ); + myActionsMap.insert( Close, new QtxAction( this, false /*toggle*/, tr( "Close" ) ) ); + myActionsMap.insert( Rename, new QtxAction( this, false /*toggle*/, tr( "Rename" ) ) ); connect( myActionsMap[SplitVertical], SIGNAL( triggered( bool ) ), this, SLOT( splitVertical() ) ); connect( myActionsMap[SplitHorizontal], SIGNAL( triggered( bool ) ), this, SLOT( splitHorizontal() ) ); @@ -2181,19 +2181,6 @@ void QtxWorkstack::SetRelativePosition( QWidget* wid, const Qt::Orientation o, } } -/*! - \brief Set accelerator key-combination for the action with specified \a id. - \param id action ID - \param accel action accelerator -*/ -void QtxWorkstack::setAccel( const int id, const int accel ) -{ - if ( !myActionsMap.contains( id ) ) - return; - - myActionsMap[id]->setShortcut( accel ); -} - /*! \brief Get the action's accelerator key-combination. \param id action ID diff --git a/src/Qtx/QtxWorkstack.h b/src/Qtx/QtxWorkstack.h index f9a192e80..d696d9e6c 100644 --- a/src/Qtx/QtxWorkstack.h +++ b/src/Qtx/QtxWorkstack.h @@ -96,7 +96,6 @@ public: void setActiveWindow( QWidget* ); int accel( const int ) const; - void setAccel( const int, const int ); QIcon icon( const int ) const; void setIcon( const int, const QIcon& ); @@ -112,7 +111,7 @@ public: QByteArray saveState( int ) const; bool restoreState( const QByteArray&, int ); - + void setOpaqueResize( bool = true ); bool opaqueResize() const; diff --git a/src/Qtx/QtxWorkstackAction.cxx b/src/Qtx/QtxWorkstackAction.cxx index 74913b2b1..e441a5e59 100644 --- a/src/Qtx/QtxWorkstackAction.cxx +++ b/src/Qtx/QtxWorkstackAction.cxx @@ -125,14 +125,14 @@ QtxWorkstackAction::QtxWorkstackAction( QtxWorkstack* ws, QObject* parent ) if ( myWorkstack ) insertAction( myWorkstack->action( QtxWorkstack::SplitVertical ), SplitVertical ); else - insertAction( new QtxAction( tr( "Split the active window on two vertical parts" ), - tr( "Split vertically" ), 0, this ), SplitVertical ); + insertAction( new QtxAction(this, false /*toggle*/, "/PRP_DESK_WINDOW_VSPLIT", + tr( "Split the active window on two vertical parts" ), tr( "Split vertically" )), SplitVertical); if ( myWorkstack ) insertAction( myWorkstack->action( QtxWorkstack::SplitHorizontal ), SplitHorizontal ); else - insertAction( new QtxAction( tr( "Split the active window on two horizontal parts" ), - tr( "Split horizontally" ), 0, this ), SplitHorizontal ); + insertAction( new QtxAction(this, false /*toggle*/, "/PRP_DESK_WINDOW_HSPLIT", + tr( "Split the active window on two horizontal parts" ), tr( "Split horizontally" )), SplitHorizontal); connect( this, SIGNAL( triggered( int ) ), this, SLOT( onTriggered( int ) ) ); @@ -249,17 +249,6 @@ QString QtxWorkstackAction::statusTip( const int id ) const return txt; } -/*! - \brief Set keyboard accelerator for the specified action. - \param id menu action ID - \param a new keyboard accelerator -*/ -void QtxWorkstackAction::setAccel( const int id, const int a ) -{ - if ( action( id ) ) - action( id )->setShortcut( a ); -} - /*! \brief Set menu item icon for the specified action. \param id menu action ID diff --git a/src/Qtx/QtxWorkstackAction.h b/src/Qtx/QtxWorkstackAction.h index e7313e6f5..d524afb39 100644 --- a/src/Qtx/QtxWorkstackAction.h +++ b/src/Qtx/QtxWorkstackAction.h @@ -67,7 +67,6 @@ public: int accel( const int ) const; QString statusTip( const int ) const; - void setAccel( const int, const int ); void setIcon( const int, const QIcon& ); void setText( const int, const QString& ); void setStatusTip( const int, const QString& ); diff --git a/src/Qtx/images/shortcut_disable.svg b/src/Qtx/images/shortcut_disable.svg new file mode 100644 index 000000000..cf661ff9f --- /dev/null +++ b/src/Qtx/images/shortcut_disable.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Qtx/images/shortcut_restore.svg b/src/Qtx/images/shortcut_restore.svg new file mode 100644 index 000000000..90dfc47d5 --- /dev/null +++ b/src/Qtx/images/shortcut_restore.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Qtx/resources/Qtx_msg_fr.ts b/src/Qtx/resources/Qtx_msg_fr.ts index d61dfe7fb..2ea62824e 100644 --- a/src/Qtx/resources/Qtx_msg_fr.ts +++ b/src/Qtx/resources/Qtx_msg_fr.ts @@ -495,4 +495,53 @@ %1 a été développé en utilisant %2
+ + QtxKeySequenceEdit + + Disable shortcut. + Désactivez le raccourci. + + + Restore the currently applied key sequence. + Restaurez la séquence de touches actuellement appliquée. + + + + QtxEditKeySequenceDialog + + Change key sequence + Modifier la séquence de touches + + + No conflicts. + Aucun conflit. + + + Confirm + Confirmer + + + Cancel + Annuler + + + These shortcuts will be disabled on confirm: + Ces raccourcis seront désactivés lors de la confirmation : + + + + QtxShortcutTree + + Action + Action + + + Key sequence + Séquence de touches + + + Double click to edit key sequence. + Double-cliquez pour modifier la séquence de touches. + + diff --git a/src/Qtx/resources/Qtx_msg_ja.ts b/src/Qtx/resources/Qtx_msg_ja.ts index 173c81282..36900402a 100644 --- a/src/Qtx/resources/Qtx_msg_ja.ts +++ b/src/Qtx/resources/Qtx_msg_ja.ts @@ -495,4 +495,53 @@ %1 は %2 を使用して開発されています。 + + QtxKeySequenceEdit + + Disable shortcut. + ショートカットを無効にします。 + + + Restore the currently applied key sequence. + 現在適用されているキー シーケンスを復元します。 + + + + QtxEditKeySequenceDialog + + Change key sequence + キーシーケンスを変更する + + + No conflicts. + 競合はありません。 + + + Confirm + 確認する + + + Cancel + キャンセル + + + These shortcuts will be disabled on confirm: + これらのショートカットは確認時に無効になります。 + + + + QtxShortcutTree + + Action + アクション + + + Key sequence + キーシーケンス + + + Double click to edit key sequence. + ダブルクリックしてキー シーケンスを編集します。 + + diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h index 8d9b2b42f..aaf59b449 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h @@ -82,7 +82,7 @@ enum MenuName { Preferences = 4, Tools = 5, Window = 6, - Help = 7 + Help = 7 }; enum { @@ -98,29 +98,28 @@ enum { #endif }; -enum { +enum { PT_Auto = LightApp_Preferences::Auto, PT_Space = LightApp_Preferences::Space, - PT_Bool = LightApp_Preferences::Bool, + PT_Bool = LightApp_Preferences::Bool, PT_Color = LightApp_Preferences::Color, - PT_String = LightApp_Preferences::String, - PT_Selector = LightApp_Preferences::Selector, - PT_DblSpin = LightApp_Preferences::DblSpin, - PT_IntSpin = LightApp_Preferences::IntSpin, - PT_Double = LightApp_Preferences::Double, - PT_Integer = LightApp_Preferences::Integer, - PT_GroupBox = LightApp_Preferences::GroupBox, - PT_Tab = LightApp_Preferences::Tab, - PT_Frame = LightApp_Preferences::Frame, - PT_Font = LightApp_Preferences::Font, - PT_DirList = LightApp_Preferences::DirList, - PT_File = LightApp_Preferences::File, - PT_Slider = LightApp_Preferences::Slider, - PT_Shortcut = LightApp_Preferences::Shortcut, - PT_ShortcutTree = LightApp_Preferences::ShortcutTree, - PT_BiColor = LightApp_Preferences::BiColor, - PT_Background = LightApp_Preferences::Background, - PT_UserDefined = LightApp_Preferences::UserDefined, + PT_String = LightApp_Preferences::String, + PT_Selector = LightApp_Preferences::Selector, + PT_DblSpin = LightApp_Preferences::DblSpin, + PT_IntSpin = LightApp_Preferences::IntSpin, + PT_Double = LightApp_Preferences::Double, + PT_Integer = LightApp_Preferences::Integer, + PT_GroupBox = LightApp_Preferences::GroupBox, + PT_Tab = LightApp_Preferences::Tab, + PT_Frame = LightApp_Preferences::Frame, + PT_Font = LightApp_Preferences::Font, + PT_DirList = LightApp_Preferences::DirList, + PT_File = LightApp_Preferences::File, + PT_Slider = LightApp_Preferences::Slider, + PT_ShortcutTree = LightApp_Preferences::ShortcutTree, + PT_BiColor = LightApp_Preferences::BiColor, + PT_Background = LightApp_Preferences::Background, + PT_UserDefined = LightApp_Preferences::UserDefined, }; class UserDefinedContent: public QWidget @@ -136,7 +135,7 @@ public: //! Orientation enum Orientation { - Horizontal = 0, //!< Horizontal orientation + Horizontal = 0, //!< Horizontal orientation Vertical = 1 //!< Vertical orientation }; @@ -161,11 +160,11 @@ enum ObjectType }; #endif -enum VisibilityState +enum VisibilityState { ShownState, //!< Object is shown in viewer HiddenState, //!< Object is hidden in viewer - UnpresentableState //!< Unpresentable object + UnpresentableState //!< Unpresentable object }; #ifndef DISABLE_PLOT2DVIEWER @@ -174,7 +173,7 @@ enum Axis { yRight = QwtPlot::yRight, xBottom = QwtPlot::xBottom, xTop = QwtPlot::xTop, -}; +}; #endif class SalomePyQt @@ -247,7 +246,7 @@ public: static void setColor( const QString&, const QColor& ); static QColor getColor( const QString& ); - static void setReference( const QString&, const QString& ); + static void setReference( const QString&, const QString& ); static QString getReference( const QString& ); static QIcon loadIcon( const QString&, const QString& ); @@ -267,23 +266,23 @@ public: static int createMenu( const QString&, const int = -1, const int = -1, const int = -1, const int = -1 ); - static int createMenu( const QString&, const QString& = QString(), + static int createMenu( const QString&, const QString& = QString(), const int = -1, const int = -1, const int = -1 ); static int createMenu( const int, const int = -1, const int = -1, const int = -1 ); - static int createMenu( const int, const QString& = QString(), + static int createMenu( const int, const QString& = QString(), const int = -1, const int = -1 ); - static int createMenu( QAction*, const int, const int = -1, + static int createMenu( QAction*, const int, const int = -1, const int = -1, const int = -1 ); - static int createMenu( QAction*, const QString&, const int = -1, + static int createMenu( QAction*, const QString&, const int = -1, const int = -1, const int = -1 ); static QAction* createSeparator(); static QAction* createAction( const int, const QString&, - const QString& = QString(), const QString& = QString(), + const QString& = QString(), const QString& = QString(), const QString& = QString(), const int = 0, const bool = false ); - + static QtxActionGroup* createActionGroup( const int, const bool = true ); static QAction* action( const int ); @@ -323,7 +322,7 @@ public: static void message( const QString&, bool = true ); static void clearMessages(); - + static QList getViews(); static int getActiveView(); static QString getViewType( const int ); diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip index 0c01923e8..5b8bb3759 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip @@ -72,7 +72,7 @@ enum MenuName { Preferences = 4, Tools = 5, Window = 6, - Help = 7 + Help = 7 }; enum WindowType { @@ -86,25 +86,24 @@ enum WindowType { WT_User }; -enum PrefType { +enum PrefType { PT_Auto, PT_Space, - PT_Bool, + PT_Bool, PT_Color, - PT_String, - PT_Selector, - PT_DblSpin, - PT_IntSpin, - PT_Double, - PT_Integer, - PT_GroupBox, - PT_Tab, - PT_Frame, - PT_Font, - PT_DirList, - PT_File, + PT_String, + PT_Selector, + PT_DblSpin, + PT_IntSpin, + PT_Double, + PT_Integer, + PT_GroupBox, + PT_Tab, + PT_Frame, + PT_Font, + PT_DirList, + PT_File, PT_Slider, - PT_Shortcut, PT_ShortcutTree, PT_BiColor, PT_Background, @@ -112,14 +111,14 @@ enum PrefType { }; enum Orientation { - Horizontal = 0, - Vertical = 1 + Horizontal = 0, + Vertical = 1 }; enum Action { - MoveWidget = 0, - LeaveWidget = 1, - SplitAt = 2 + MoveWidget = 0, + LeaveWidget = 1, + SplitAt = 2 }; class QtxAction : QWidgetAction @@ -264,11 +263,11 @@ public: virtual void retrieve(); }; -enum VisibilityState +enum VisibilityState { ShownState, HiddenState, - UnpresentableState + UnpresentableState }; class SalomePyQt @@ -334,7 +333,7 @@ public: static QString getFileName ( QWidget*, const QString&, const QStringList&, const QString&, bool ) /ReleaseGIL/ ; static QStringList getOpenFileNames ( QWidget*, const QString&, const QStringList&, const QString& ) /ReleaseGIL/ ; static QString getExistingDirectory( QWidget*, const QString&, const QString& ) /ReleaseGIL/ ; - + static void createRoot() /ReleaseGIL/ ; static QString createObject( const QString& = QString("") ) /ReleaseGIL/ ; static QString createObject( const QString&, @@ -357,7 +356,7 @@ public: static void setColor( const QString&, const QColor& ) /ReleaseGIL/ ; static QColor getColor( const QString& ) /ReleaseGIL/ ; - static void setReference( const QString& ,const QString& ) /ReleaseGIL/ ; + static void setReference( const QString& ,const QString& ) /ReleaseGIL/ ; static QString getReference( const QString& ) /ReleaseGIL/ ; static void removeObject(const QString& ) /ReleaseGIL/ ; @@ -381,20 +380,20 @@ public: static int createMenu( const QString&, const int, const int = -1, const int = -1, const int = -1 ) /ReleaseGIL/ ; - static int createMenu( const QString&, const QString&, + static int createMenu( const QString&, const QString&, const int = -1, const int = -1, const int = -1 ) /ReleaseGIL/ ; static int createMenu( const int, const int, const int = -1, const int = -1 ) /ReleaseGIL/ ; - static int createMenu( const int, const QString&, + static int createMenu( const int, const QString&, const int = -1, const int = -1 ) /ReleaseGIL/ ; - static int createMenu( QAction*, const int, const int = -1, + static int createMenu( QAction*, const int, const int = -1, const int = -1, const int = -1 ) /ReleaseGIL/ ; - static int createMenu( QAction*, const QString&, const int = -1, + static int createMenu( QAction*, const QString&, const int = -1, const int = -1, const int = -1 ) /ReleaseGIL/ ; static QAction* createSeparator() /ReleaseGIL/ ; - static QAction* createAction( const int, const QString&, - const QString& = QString(), const QString& = QString(), + static QAction* createAction( const int, const QString&, + const QString& = QString(), const QString& = QString(), const QString& = QString(), const int = 0, const bool = false ) /ReleaseGIL/ ; static QtxActionGroup* createActionGroup( const int, const bool = true ) /ReleaseGIL/ ; @@ -438,20 +437,20 @@ public: const QString& = QString(), const QString& = QString() ) /ReleaseGIL/ ; static QVariant preferenceProperty( const int, const QString& ) /ReleaseGIL/ ; - static void setPreferenceProperty( const int, + static void setPreferenceProperty( const int, const QString&, const QVariant& ) /ReleaseGIL/ ; - static void setPreferencePropertyWg( const int, - const QString&, + static void setPreferencePropertyWg( const int, + const QString&, UserDefinedContent* ) /ReleaseGIL/ ; - static void addPreferenceProperty( const int, - const QString&, - const int, + static void addPreferenceProperty( const int, + const QString&, + const int, const QVariant& ) /ReleaseGIL/ ; static void message( const QString&, bool = true ) /ReleaseGIL/ ; static void clearMessages() /ReleaseGIL/ ; - + static QList getViews() /ReleaseGIL/ ; static int getActiveView() /ReleaseGIL/ ; static QString getViewType( const int ) /ReleaseGIL/ ; @@ -471,12 +470,12 @@ public: static bool isViewVisible( const int id ) /ReleaseGIL/ ; static void setViewClosable( const int id, const bool ) /ReleaseGIL/ ; static bool isViewClosable( const int id ) /ReleaseGIL/ ; - + static bool groupAllViews() /ReleaseGIL/ ; static bool splitView( const int, Orientation, Action ) /ReleaseGIL/ ; static bool moveView( const int, const int, const bool ) /ReleaseGIL/ ; static QList neighbourViews( const int ) /ReleaseGIL/ ; - + %If (ENABLE_PLOT2D) // start Plot2d-related functionality static void displayCurve(const int, Plot2d_Curve*) /ReleaseGIL/ ; diff --git a/src/STD/STD_Application.cxx b/src/STD/STD_Application.cxx index ff9cbd203..6dceb529f 100644 --- a/src/STD/STD_Application.cxx +++ b/src/STD/STD_Application.cxx @@ -157,12 +157,12 @@ void STD_Application::createActions() createAction( FileNewId, tr( "TOT_DESK_FILE_NEW" ), resMgr->loadPixmap( "STD", tr( "ICON_FILE_NEW" ) ), tr( "MEN_DESK_FILE_NEW" ), tr( "PRP_DESK_FILE_NEW" ), - Qt::CTRL+Qt::Key_N, desk, false, this, SLOT( onNewDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onNewDoc() ), "/TOT_DESK_FILE_NEW" ); createAction( FileOpenId, tr( "TOT_DESK_FILE_OPEN" ), resMgr->loadPixmap( "STD", tr( "ICON_FILE_OPEN" ) ), tr( "MEN_DESK_FILE_OPEN" ), tr( "PRP_DESK_FILE_OPEN" ), - Qt::CTRL+Qt::Key_O, desk, false, this, SLOT( onOpenDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onOpenDoc() ), "/TOT_DESK_FILE_OPEN" ); createAction( FileReopenId, tr( "TOT_DESK_FILE_REOPEN" ), QIcon(), tr( "MEN_DESK_FILE_REOPEN" ), tr( "PRP_DESK_FILE_REOPEN" ), @@ -171,46 +171,46 @@ void STD_Application::createActions() createAction( FileCloseId, tr( "TOT_DESK_FILE_CLOSE" ), resMgr->loadPixmap( "STD", tr( "ICON_FILE_CLOSE" ) ), tr( "MEN_DESK_FILE_CLOSE" ), tr( "PRP_DESK_FILE_CLOSE" ), - Qt::CTRL+Qt::Key_W, desk, false, this, SLOT( onCloseDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onCloseDoc() ), "/TOT_DESK_FILE_CLOSE" ); //no need in this action for mono-study application as it is same as NewDoc action( FileCloseId )->setVisible( false ); createAction( FileExitId, tr( "TOT_DESK_FILE_EXIT" ), QIcon(), tr( "MEN_DESK_FILE_EXIT" ), tr( "PRP_DESK_FILE_EXIT" ), - Qt::CTRL+Qt::Key_Q, desk, false, this, SLOT( onExit() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onExit() ), "/TOT_DESK_FILE_EXIT" ); createAction( FileSaveId, tr( "TOT_DESK_FILE_SAVE" ), resMgr->loadPixmap( "STD", tr( "ICON_FILE_SAVE" ) ), tr( "MEN_DESK_FILE_SAVE" ), tr( "PRP_DESK_FILE_SAVE" ), - Qt::CTRL+Qt::Key_S, desk, false, this, SLOT( onSaveDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onSaveDoc() ), "/TOT_DESK_FILE_SAVE" ); createAction( FileSaveAsId, tr( "TOT_DESK_FILE_SAVEAS" ), QIcon(), tr( "MEN_DESK_FILE_SAVEAS" ), tr( "PRP_DESK_FILE_SAVEAS" ), - Qt::CTRL+Qt::SHIFT+Qt::Key_S, desk, false, this, SLOT( onSaveAsDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onSaveAsDoc() ), "/TOT_DESK_FILE_SAVEAS"); createAction( EditCopyId, tr( "TOT_DESK_EDIT_COPY" ), resMgr->loadPixmap( "STD", tr( "ICON_EDIT_COPY" ) ), tr( "MEN_DESK_EDIT_COPY" ), tr( "PRP_DESK_EDIT_COPY" ), - Qt::CTRL+Qt::Key_C, desk, false, this, SLOT( onCopy() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onCopy() ), "/#TOT_DESK_EDIT_COPY" ); createAction( EditPasteId, tr( "TOT_DESK_EDIT_PASTE" ), resMgr->loadPixmap( "STD", tr( "ICON_EDIT_PASTE" ) ), tr( "MEN_DESK_EDIT_PASTE" ), tr( "PRP_DESK_EDIT_PASTE" ), - Qt::CTRL+Qt::Key_V, desk, false, this, SLOT( onPaste() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onPaste() ), "/TOT_DESK_EDIT_PASTE" ); + + QAction* a = createAction( ViewStatusBarId, desk, true /*toggle*/, "/PRP_DESK_VIEW_STATUSBAR", + tr( "TOT_DESK_VIEW_STATUSBAR" ), tr( "MEN_DESK_VIEW_STATUSBAR" ), tr( "PRP_DESK_VIEW_STATUSBAR" )); - QAction* a = createAction( ViewStatusBarId, tr( "TOT_DESK_VIEW_STATUSBAR" ), - QIcon(), tr( "MEN_DESK_VIEW_STATUSBAR" ), - tr( "PRP_DESK_VIEW_STATUSBAR" ), Qt::ALT+Qt::SHIFT+Qt::Key_S, desk, true ); a->setChecked( desk->statusBar()->isVisibleTo( desk ) ); connect( a, SIGNAL( toggled( bool ) ), this, SLOT( onViewStatusBar( bool ) ) ); createAction( NewWindowId, tr( "TOT_DESK_NEWWINDOW" ), QIcon(), tr( "MEN_DESK_NEWWINDOW" ), tr( "PRP_DESK_NEWWINDOW" ), 0, desk ); - createAction( HelpAboutId, tr( "TOT_DESK_HELP_ABOUT" ), - resMgr->loadPixmap( "STD", tr( "ICON_DESK_ABOUT" ) ), - tr( "MEN_DESK_HELP_ABOUT" ), tr( "PRP_DESK_HELP_ABOUT" ), - Qt::ALT+Qt::SHIFT+Qt::Key_A, desk, false, this, SLOT( onHelpAbout() ) ); + createAction( HelpAboutId, desk, false /*toggle*/, "/PRP_DESK_HELP_ABOUT", + tr( "TOT_DESK_HELP_ABOUT" ), tr( "MEN_DESK_HELP_ABOUT" ), tr( "PRP_DESK_HELP_ABOUT" ), + resMgr->loadPixmap( "STD", tr( "ICON_DESK_ABOUT" ) ), + this, SLOT( onHelpAbout() ) ); QtxDockAction* dwa = new QtxDockAction( tr( "TOT_DOCKWINDOWS" ), tr( "MEN_DESK_VIEW_DOCKWINDOWS" ), desk ); @@ -233,7 +233,7 @@ void STD_Application::createActions() createMenu( FileNewId, fileMenu, 0 ); createMenu( FileOpenId, fileMenu, 0 ); - createMenu( FileReopenId, fileMenu, 0 ); + createMenu( FileReopenId, fileMenu, 0 ); createMenu( FileSaveId, fileMenu, 5 ); createMenu( FileSaveAsId, fileMenu, 5 ); createMenu( FileCloseId, fileMenu, 5 ); @@ -342,7 +342,7 @@ bool STD_Application::onOpenDoc( const QString& aName ) QApplication::setOverrideCursor( Qt::WaitCursor ); bool res = openAction( openChoice( aName ), aName ); - + QApplication::restoreOverrideCursor(); return res; @@ -375,7 +375,7 @@ bool STD_Application::onReopenDoc() // delete study delete study; study = 0; - + // post closing actions afterCloseDoc(); @@ -730,11 +730,11 @@ void STD_Application::updateCommandsStatus() /*! \brief Show notification with specified text and title. - + Notification will be automatically hidden after specified \a timeout (given in milliseconds). If \a timeout is zero, the notification is not automatically hidden; it can be only closed by the user manually. - + \param text - Notification text \param title - Notification title \param timeout - Timeout in milliseconds diff --git a/src/STD/STD_TabDesktop.cxx b/src/STD/STD_TabDesktop.cxx index ec674d5a2..8b64dca05 100644 --- a/src/STD/STD_TabDesktop.cxx +++ b/src/STD/STD_TabDesktop.cxx @@ -56,10 +56,6 @@ myWorkstackAction( 0 ) // But the workstack must occupy as much space as possible -- set Expanding for it. myWorkstack->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); - myWorkstack->setAccel( QtxWorkstack::SplitVertical, Qt::ALT + Qt::SHIFT + Qt::Key_V ); - myWorkstack->setAccel( QtxWorkstack::SplitHorizontal, Qt::ALT + Qt::SHIFT + Qt::Key_H ); - //myWorkstack->setAccel( QtxWorkstack::Close, Qt::CTRL + Qt::Key_F4 ); - SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); if ( resMgr ) { myWorkstack->setIcon( QtxWorkstack::SplitVertical, @@ -216,14 +212,12 @@ void STD_TabDesktop::createActions() resMgr->loadPixmap( "STD", tr( "ICON_DESK_WINDOW_HSPLIT" ) ) ); myWorkstackAction->setText( QtxWorkstackAction::SplitHorizontal, tr( "MEN_DESK_WINDOW_HSPLIT" ) ); myWorkstackAction->setStatusTip( QtxWorkstackAction::SplitHorizontal, tr( "PRP_DESK_WINDOW_HSPLIT" ) ); - myWorkstackAction->setAccel( QtxWorkstackAction::SplitHorizontal, Qt::ALT + Qt::SHIFT + Qt::Key_H ); // Split Vertical myWorkstackAction->setIcon( QtxWorkstackAction::SplitVertical, resMgr->loadPixmap( "STD", tr( "ICON_DESK_WINDOW_VSPLIT" ) ) ); myWorkstackAction->setText( QtxWorkstackAction::SplitVertical, tr( "MEN_DESK_WINDOW_VSPLIT" ) ); myWorkstackAction->setStatusTip( QtxWorkstackAction::SplitVertical, tr( "PRP_DESK_WINDOW_VSPLIT" ) ); - myWorkstackAction->setAccel( QtxWorkstackAction::SplitVertical, Qt::ALT + Qt::SHIFT + Qt::Key_V ); QAction* anArrangeViewsAction = myWorkstackAction->getArrangeViewsAction(); if( anArrangeViewsAction ) diff --git a/src/SUIT/CMakeLists.txt b/src/SUIT/CMakeLists.txt index 10c9f0079..6c7c3f6af 100644 --- a/src/SUIT/CMakeLists.txt +++ b/src/SUIT/CMakeLists.txt @@ -37,28 +37,28 @@ SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES} qtx ObjBrowser) # --- headers --- # header files / to be processed by moc -SET(_moc_HEADERS - SUIT_Accel.h - SUIT_ActionOperation.h - SUIT_Application.h - SUIT_AutoRotate.h - SUIT_DataBrowser.h - SUIT_DataObject.h - SUIT_Desktop.h - SUIT_FileDlg.h +SET(_moc_HEADERS + SUIT_Accel.h + SUIT_ActionOperation.h + SUIT_Application.h + SUIT_AutoRotate.h + SUIT_DataBrowser.h + SUIT_DataObject.h + SUIT_Desktop.h + SUIT_FileDlg.h SUIT_LicenseDlg.h SUIT_MessageBox.h - SUIT_Operation.h - SUIT_PopupClient.h - SUIT_PreferenceMgr.h - SUIT_SelectionMgr.h - SUIT_Session.h + SUIT_Operation.h + SUIT_PopupClient.h + SUIT_PreferenceMgr.h + SUIT_SelectionMgr.h + SUIT_Session.h SUIT_ShortcutMgr.h SUIT_Study.h - SUIT_TreeModel.h - SUIT_ViewManager.h - SUIT_ViewModel.h - SUIT_ViewWindow.h + SUIT_TreeModel.h + SUIT_ViewManager.h + SUIT_ViewModel.h + SUIT_ViewWindow.h ) # header files / no moc processing @@ -97,6 +97,7 @@ SET(_other_RESOURCES resources/icon_visibility_on.png resources/icon_visibility_off.png resources/view_sync.png + resources/action_assets.json ) # --- sources --- @@ -153,4 +154,3 @@ INSTALL(FILES ${suit_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS}) QT_INSTALL_TS_RESOURCES("${_ts_RESOURCES}" "${SALOME_GUI_INSTALL_RES_DATA}") INSTALL(FILES ${_other_RESOURCES} DESTINATION ${SALOME_GUI_INSTALL_RES_DATA}) - diff --git a/src/SUIT/SUIT_Application.cxx b/src/SUIT/SUIT_Application.cxx index fef1771b3..17eab9ab1 100644 --- a/src/SUIT/SUIT_Application.cxx +++ b/src/SUIT/SUIT_Application.cxx @@ -189,7 +189,7 @@ SUIT_ResourceMgr* SUIT_Application::resourceMgr() const */ SUIT_ShortcutMgr* SUIT_Application::shortcutMgr() const { - return SUIT_ShortcutMgr::getShortcutMgr(); + return SUIT_ShortcutMgr::get(); } #define DEFAULT_MESSAGE_DELAY 3000 @@ -639,13 +639,14 @@ QList SUIT_Application::actionIds() const \param toggle - if it is \c true the action will be a toggle action, otherwise it will be a command action \param reciever - object that contains slot \param member - slot to be called when action is activated + \param actionID - application-unique action ID. Required by SUIT_ShortcutMgr for shortcut customization. May be left blank. */ QAction* SUIT_Application::createAction( const int id, const QString& text, const QIcon& icon, const QString& menu, const QString& tip, const int key, - QObject* parent, const bool toggle, QObject* reciever, - const char* member, const QString& shortcutAction ) + QObject* parent, const bool toggle, QObject* reciever, + const char* member, const QString& actionID ) { - return createAction( id, text, icon, menu, tip, QKeySequence(key), parent, toggle, reciever, member, shortcutAction ); + return createAction( id, text, icon, menu, tip, QKeySequence(key), parent, toggle, reciever, member, actionID ); } /*! @@ -661,13 +662,14 @@ QAction* SUIT_Application::createAction( const int id, const QString& text, cons \param toggle - if it is TRUE the action will be a toggle action, otherwise it will be a command action \param reciever - object that contains slot \param member - slot to be called when action is activated + \param actionID - application-unique action ID. Required by SUIT_ShortcutMgr for shortcut customization. May be left blank. */ QAction* SUIT_Application::createAction( const int id, const QString& text, const QIcon& icon, const QString& menu, const QString& tip, const QKeySequence& key, - QObject* parent, const bool toggle, QObject* reciever, - const char* member, const QString& shortcutAction ) + QObject* parent, const bool toggle, QObject* reciever, + const char* member, const QString& actionID ) { - QtxAction* a = new QtxAction( text, icon, menu, key, parent, toggle, shortcutAction ); + QtxAction* a = new QtxAction( text, icon, menu, key, parent, toggle, actionID ); a->setStatusTip( tip ); if ( reciever && member ) @@ -678,6 +680,35 @@ QAction* SUIT_Application::createAction( const int id, const QString& text, cons return a; } +/*! + Creates action and registers it both in menu manager and tool manager + \return new instance of action + \param id proposed SUIT identificator + \param parent parent object + \param toggle if it is TRUE the action will be a toggle action, otherwise it will be a command action + \param actionID application-unique action ID. Required by SUIT_ShortcutMgr for shortcut customization. May be left blank. + \param toolTip + \param menuText can be later retrieved using QAction::text(); + \param statusTip + \param icon icon for toolbar + \param reciever object that contains slot + \param member slot to be called when action is activated +*/ +QAction* SUIT_Application::createAction( const int id, QObject* parent, const bool toggle, const QString& actionID, + const QString& toolTip, const QString& menuText, const QString& statusTip, const QIcon& icon, + QObject* reciever, const char* member ) +{ + QtxAction* a = new QtxAction( parent, toggle, actionID, toolTip, menuText, icon); + a->setStatusTip( statusTip ); + + if ( reciever && member ) + connect( a, SIGNAL( triggered( bool ) ), reciever, member ); + + registerAction( id, a ); + + return a; +} + /*! Registers action both in menu manager and tool manager \param id - proposed SUIT identificator (if it is -1, auto generated one is used) diff --git a/src/SUIT/SUIT_Application.h b/src/SUIT/SUIT_Application.h index 0a0aa1830..cdc07a971 100644 --- a/src/SUIT/SUIT_Application.h +++ b/src/SUIT/SUIT_Application.h @@ -27,8 +27,8 @@ #include #include +#include -class QIcon; class QLabel; class QString; class QAction; @@ -50,8 +50,8 @@ class SUIT_Study; */ /*! An Application is a class which defines application configuration and behaviour. - For example Application object defines what Viewers are used in this application, what auxilliary windows - are present, how user can dial with them. Also Application object defines an sertain type of data structure by + For example, Application object defines what Viewers are used in this application, what auxilliary windows + are present, how user can dial with them. Also Application object defines an certain type of data structure by holding of pointer on an instance of SUIT_Study class (which represents Document data structure). In other words Application defines type of sata structure, type of used Viewers, type of main GUI widget (Desktop), and other auxilliary tools. @@ -68,7 +68,7 @@ public: //! Returns main widget (Desktop) of the application (if it exists) virtual SUIT_Desktop* desktop(); - /*! Returns \c false if application can not be closed (because of non saved data for example). + /*! Returns \c false if application can not be closed (because of non saved data for example). This method called by SUIT_Session whin closing of application was requested. */ virtual bool isPossibleToClose( bool& ); @@ -93,7 +93,7 @@ public: //! Creates new empty Study if active Study = 0 virtual void createEmptyStudy(); - /*! Returns number of Studies. + /*! Returns number of Studies. * Must be redefined in Applications which support several studies for one Application instance. */ virtual int getNbStudies() const; @@ -101,11 +101,11 @@ public: SUIT_ShortcutMgr* shortcutMgr() const; - //! Puts the message to the status bar + //! Puts the message to the status bar void putInfo ( const QString&, const int = 0 ); //! Invokes application-specific "Open/Save File" dialog and returns the selected file name. - virtual QString getFileName( bool open, const QString& initial, const QString& filters, + virtual QString getFileName( bool open, const QString& initial, const QString& filters, const QString& caption, QWidget* parent ) = 0; //! Invokes application-specific "Select Directory" dialog and returns the selected directory name. @@ -156,7 +156,7 @@ protected: //! Creates a new Study instance. Must be redefined in new application according to its Study type. virtual SUIT_Study* createNewStudy(); virtual void setActiveStudy( SUIT_Study* ); - + /** @name Set menu shown functions*/ //@{ void setMenuShown( QAction*, const bool ); void setMenuShown( const int, const bool );//@} @@ -177,11 +177,14 @@ protected: QAction* createAction( const int, const QString&, const QIcon&, const QString&, const QString&, const int, QObject* = 0, const bool = false, QObject* = 0, const char* = 0, - const QString& = QString() ); + const QString& = QString() ); QAction* createAction( const int, const QString&, const QIcon&, const QString&, const QString&, const QKeySequence&, QObject* = 0, const bool = false, QObject* = 0, const char* = 0, - const QString& = QString() ); + const QString& = QString() ); + QAction* createAction( const int, QObject*, const bool, const QString&, + const QString&, const QString&, const QString&, const QIcon& = QIcon(), + QObject* = nullptr, const char* = nullptr); protected slots: virtual void onDesktopActivated(); diff --git a/src/SUIT/SUIT_PreferenceMgr.cxx b/src/SUIT/SUIT_PreferenceMgr.cxx index 0fd2e20e1..fa0618058 100644 --- a/src/SUIT/SUIT_PreferenceMgr.cxx +++ b/src/SUIT/SUIT_PreferenceMgr.cxx @@ -157,11 +157,8 @@ int SUIT_PreferenceMgr::addItem( const QString& title, const int pId, case DirList: item = new QtxPagePrefPathListItem( Qtx::PT_Directory, title, parent, sect, param ); break; - case Shortcut: - item = new QtxPagePrefShortcutBtnsItem( title, parent, sect, param ); - break; case ShortcutTree: - item = new QtxPagePrefShortcutTreeItem( title, parent, sect, param ); + item = new QtxPagePrefShortcutTreeItem( parent ); break; case BiColor: item = new QtxPagePrefBiColorItem( title, parent, sect, param ); diff --git a/src/SUIT/SUIT_PreferenceMgr.h b/src/SUIT/SUIT_PreferenceMgr.h index b47922521..515c3d18d 100644 --- a/src/SUIT/SUIT_PreferenceMgr.h +++ b/src/SUIT/SUIT_PreferenceMgr.h @@ -38,7 +38,7 @@ public: typedef enum { Auto, Space, Bool, Color, String, Selector, DblSpin, IntSpin, Double, Integer, GroupBox, Tab, Frame, Font, DirList, File, - Slider, Shortcut, ShortcutTree, BiColor, Background, Directory, + Slider, Shortcut, ShortcutTree, BiColor, Background, Directory, UserDefined = 1000 } PrefItemType; public: diff --git a/src/SUIT/SUIT_ShortcutMgr. ReadMe.md b/src/SUIT/SUIT_ShortcutMgr. ReadMe.md new file mode 100644 index 000000000..d0cc5adc8 --- /dev/null +++ b/src/SUIT/SUIT_ShortcutMgr. ReadMe.md @@ -0,0 +1,82 @@ + # ShortcutMgr ReadMe + +Hot keys must be considered as resources, being shared between all components of an application. E.g. it is unacceptable to have 'Close file' and 'Redo' actions being assigned to the same key sequence. When the SHAPER module is active, the application desktop is active too. The desktop has own hot keys, and they must not interfere with ones of SHAPER. Since the task implies granting users a right to assign shortcuts on their will, the application must track all assigned shortcuts of all modules, prevent intolerable user shortcut modifications and govern actual binding of QActions with key sequences. + +`SUIT_ShortcutMgr` handles shortcuts of SALOME desktop and all modules. It is solely responsible and capable to dynamically bind actions with key sequences and (de)serialize shortcut preferences using `SUIT_ResourceMgr`. `SUIT_ShortcutContainer` encapsulates logics of conflict detecting and resolving. `QtxShortcutTree` widget provides GUI to change shortcut preferences conveniently: it allows to remap plenty of shortcuts without applying, displays conflict-resolving dialog, highlights modifications until they are applied (saved into preference files). + +To (de)serialize shortcut preferences without dependence on language environment, shortcuts must be stored as pairs {action ID, key sequence}, where action IDs must be application-unique. + +Since desktop shortcuts may also be changed and interfere with shortcuts of modules, `QtxShortcutTree` should always display desktop shortcuts and shortcuts of all modules altogether, even if some modules are inactive. It means, that `QtxShortcutTree` must be fed not only with shortcut data {action ID, key sequence}[], but also with dictionaries {action ID, action name}[]. `QtxShortcutTree` also requires other action assets - tool tip and icon path. + +Assets of actions may be retrieved from instances of actions, but there is a pitfall: if a module has not been activated yet, its actions have not been initialized either. +Qt Linguist is no help in this case too. To retrieve an action name using `QObject::tr(actionID)`, the `tr(const char*)` method must be called with instance of the class, which is designated as a context for the actionID in *.ts files. And contexts are usually descendants of SUIT_Application and CAM_Module. Again, until a module instance is created, there is no way for `SUIT_ShortcutMgr` to get even a name of a context-class, which an action with an ID belongs to, without any additional data. Straightforward mechanism for loading of action assets in advance has been devised: for all actions, which are bound by default or may be bound by user to hotkeys, assets must be placed into asset files. People who do/refine localizations should keep this in mind and also process JSON files, which are referred in resource files in sections `
`. + +To alleviate process of composing resource and asset files, a development tool `DevTools` has been made. It grabs all shortcuts and assets (except icon path) of intercepted at runtime actions with valid IDs and dumps shortcuts into XML files with identical to project-conventional resource files structure and assets into properly-formatted JSON files. Run modules/features of interest, switch application language, and if IDs or names in the selected language of some actions of the module are not added yet to preference files, these dump files will be appended with new data – shortcuts and assets, if the last ones are provided in *.ts files. +The tool also logs assets of intercepted actions with invalid IDs. The intent is as follows: run modules/features of interest at several languages in exactly the same sequence of actions, paste content of resulting language-dedicated *.csv files to corresponding sheet of [“ShortcutMgr. Resource generator.xlsx”](../../tools/DevTools/ShortcutMgr/ShortcutMgr. Resource generator.xlsx). Then come up with proper IDs for the actions, type the action IDs and their module IDs into corresponding columns and take away ready resource and asset items. + +Shortcuts and assets for all actions of SHAPER and GEOM and those actions of desktop, which were bound (hardcoded) to non-empty key sequences, have been added to resource files. Now they are available for hot key remapping via GUI, no conflicts guaranteed. Any hardcoded shortcut is disabled, unless the same shortcut persists in resource files. + +## Possible conflicts between shortcuts of desktop and modules, except SHAPER and GEOM + +Most of remaining actions can not be made available for shortcut customization without further meticulous inspection of all other modules' code, devising and typing proper action IDs for all such occurrences. + +Since desktop shortcuts are allowed for remapping, user can end up with conflicting shortcuts of desktop actions and those actions of unprocessed modules (all, except SHAPER and GEOM), which are hardcoded to non-empty key sequences. + +Now actions with invalid IDs are disabled by `SUIT_ShortcutMgr` at runtime and not available for binding neither using UI, neither by editing of preference file. There are four approaches how to handle this: + +1. Keep as is: user can remap shortcuts of desktop, SHAPER and GEOM - no conflicts guaranteed, hardcoded (now means all) shortcuts of other modules are disabled. + +2. Go through unprocessed repositories and add valid ID in every occurrence of action creation. Or at least only for those invalid-ID actions, which are currently assigned non-empty key sequences. It should not be just dumb slapping of unique IDs: some actions comprise meta-action (see `SUIT_ShortcutMgr` class documentation) and must get the same ID, e.g. “Undo”, and some actions with similar names do absolutely unrelated things and must be given with different IDs. + +3. Comment the line in `SUIT_ShortcutMgr` code, which disables shortcuts of inavlid-ID actions. At that rate, users will get back all the shortcuts of unprocessed modules they have accustomed to, but since these actions can not be taken into account during conflict detection, +users may face with shortcut interference, if a desktop action is bound to the same key sequence as a hardcoded shortcut from unprocessed module. + +4. Forbid user to edit shortcuts of desktop and shortcuts of unprocessed modules. When another module will be processed, allow to modify its shortcuts. When all modules will be processed, make desktop shortcuts available for customization. + +## Issue with ampersand-shortcuts + +`QPushButton::QPushButton(const QString&)`, being fed with an action name with an ampersand preceding a char, automatically creates a shortcut, bound to *Alt+\* key sequence. + +E.g. `auto button = QPushButton(tr("&Help")` may create language-depdendent shortcuts. Suppose that \*_fr.ts file contains this: +``` + + &Help + A&ide + + +``` + +Then help menu has different shortcuts if the application is run in EN or FR. + +**A part of the issue** is that some ampersand-shortcuts interfere between each other. You can witness this in master-native application. Switch language to FR, run the app, click on the area, where viewers appear, and stroke *Alt+A*. You will get a Qt-generated notification *"Ambiguous shortcut detected"*, because *Alt+A* is bound to *"New ParaView window"* and to *"Affichage"* simultaneously. If you stroke the sequence again, *"Affichage"* menu expands, since the button tray, where the menu-button resides, has gained focus. + +**Another part of the issue** is that ampersand-shortcuts are not always intercepted by `SUIT_ShortcutMgr::eventFilter(_)`. Normally, if the manager catches an unregistered `QAction`, it disables the action' shortcut. But, non-intercepted ampersand-shortcut remains enabled, and, if user binds some action to the same key sequence in the shortcut editor, the editor is not able to detect conflict and prevent interfering binding. Then user faces with the *"Ambiguous shortcut detected"* notification or button-menu is opened instead of executing the action, mapped to the key sequence in shortcut editor. + +**The question is how to handle ampersand-shortcuts?** +1. Resolve conflicts between ampersand-shortcuts for all localizations. Prohibit binding actions to *Alt+\* key sequences in shortcut editor. +2. After every occurrence of `QPushButton` creation type something like this: +``` +// Occurrence +const auto helpButton = new QPushButton(tr("&Help"); +SUIT_ShortcutMgr::get()->registerButtonActions("/#AltHelp", *helpButton); + +---------------------------------------------------- +// SUIT_ShortcutMgr.cpp +void SUIT_ShortcutMgr::registerButtonActions(const QString& theActionID, const QPushButton& theButton) const { + for (QAction* const action : theButton.actions()) { + registerAction(theActionID, action); + } +} +---------------------------------------------------- +// Resource file +
+ +
+``` +Thus, ampersand-shortcuts will appear and be treated in shortcut editor as regular shortcuts. +If the second option is preferable, should different ampersand-shortcuts for every target language be placed in resource files? + +## Minor issues +1. `QtxShortcutTree` widget does not take the whole available height of preference window, it only takes as mush as its items require. +2. Selection of `QtxShortcutTree`' item shadows "modified" highlighter. Can be fixed by replacing base `QTreeWidget` of `QtxShortcutTree` with `QTreeView`, or may be by applying some style sheet. +3. `SUIT_ShortcutMgr` introduces concept of module, but the first module class is `CAM_Module` is introduced along with `CAM_Application`, which is descendant of `SUIT_Application`. diff --git a/src/SUIT/SUIT_ShortcutMgr.cxx b/src/SUIT/SUIT_ShortcutMgr.cxx index 41b6d8b2a..bfae01ec3 100644 --- a/src/SUIT/SUIT_ShortcutMgr.cxx +++ b/src/SUIT/SUIT_ShortcutMgr.cxx @@ -24,164 +24,1916 @@ #include "SUIT_Session.h" #include "SUIT_ResourceMgr.h" +#include "SUIT_MessageBox.h" +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = NULL; +#include -/*! - \brief Constructor + +#include +#include +const std::wstring SHORTCUT_MGR_LOG_PREFIX = L"SHORTCUT_MGR_DBG: "; +bool ShCutDbg(const QString& theString) +{ + if (ShCutDbg()) { + std::wcout << SHORTCUT_MGR_LOG_PREFIX << theString.toStdWString() << std::endl; + return true; + } + return false; +} +bool ShCutDbg(const char* src) +{ + if (ShCutDbg()) { + std::wcout << SHORTCUT_MGR_LOG_PREFIX << std::wstring(src, src + strlen(src)) << std::endl; + return true; + } + return false; +} + +void Warning(const QString& theString) +{ + std::wcout << theString.toStdWString() << std::endl; +} +void Warning(const char* src) +{ + std::wcout << std::wstring(src, src + strlen(src)) << std::endl; +} + + +static const QKeySequence NO_KEYSEQUENCE = QKeySequence(QString("")); +static const QString NO_ACTION = QString(""); +/** Separates tokens in action ID. */ +static const QString TOKEN_SEPARATOR = QString("/"); +static const QString ROOT_MODULE_ID = QString(""); +static const QString META_ACTION_PREFIX = QString("#"); + +/** Prefix of names of shortcut setting sections in preference files. */ +static const QString SECTION_NAME_PREFIX = QString("shortcuts"); + + +const QString DEFAULT_LANG = QString("en"); +const QStringList LANG_PRIORITY_LIST = QStringList({DEFAULT_LANG, "fr"}); +const QString LANG_SECTION = QString("language"); + +static const QString SECTION_NAME_ACTION_ASSET_FILE_PATHS = QString("action_assets"); + + + +/** + * Uncomment this, to start collecting all shortcuts and action assets (1), + * from instances of QtxActions, if a shortcut or action assets are absent in resource/asset files. + * + * (1) Set required language in the application settings and run features of interest. + * For all actions from these features, their assets will be dumped to appropriate places in dump files. + * + * Content of dump files is appended on every run. Files are located in "/shortcut_mgr_dev/". */ +// #define SHORTCUT_MGR_DEVTOOLS +#ifdef SHORTCUT_MGR_DEVTOOLS +#include +#include +#include +#include +#include "QtxMap.h" +#include +#ifndef QT_NO_DOM +#include +#include +#include +#endif // QT_NO_DOM + +/*! \brief Generates XML files with appearing at runtime shortcuts, + using key sequences of QActions passed to the shortcut manager, + and JSON files with assets of QtxActions passed to the shortcut manager. + Content of these files can be easily copied to resource/asset files. */ +class DevTools +{ +private: + DevTools() : myActionsWithInvalidIDsFile(nullptr) {}; + DevTools(const DevTools&) = delete; + void operator=(const DevTools&) = delete; + +public: + ~DevTools() + { + for (const auto& fileNameAndPtrs : myXMLFilesAndDocs) { + delete fileNameAndPtrs.second.second; + delete fileNameAndPtrs.second.first; + } + + for (const auto& fileNameAndPtrs : myJSONFilesAndDocs) { + delete fileNameAndPtrs.second.second; + delete fileNameAndPtrs.second.first; + } + } + + static DevTools* get() { + if (!DevTools::instance) + DevTools::instance = new DevTools(); + + return DevTools::instance; + } + + void collectShortcut( + const QString& theModuleID, + const QString& theInModuleActionID, + const QKeySequence& theKeySequence + ) { + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) { + auto& moduleShortcuts = myShortcutsOfMetaActions[theModuleID]; + moduleShortcuts[theInModuleActionID] = theKeySequence.toString(); + + const QString fileName = theModuleID + DevTools::SHORTCUTS_OF_META_SUFFIX; + const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + ROOT_MODULE_ID; + std::map> sections; + sections[sectionName] = moduleShortcuts; + writeToXMLFile(fileName, sections); + } + else { + auto& moduleShortcuts = myShortcuts[theModuleID]; + moduleShortcuts[theInModuleActionID] = theKeySequence.toString(); + + const QString fileName = theModuleID + DevTools::SHORTCUTS_SUFFIX; + const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + theModuleID; + std::map> sections; + sections[sectionName] = moduleShortcuts; + writeToXMLFile(fileName, sections); + } + } + + void collectAssets( + const QString& theModuleID, + const QString& theInModuleActionID, + const QString& theLang, + const QAction* theAction + ) { + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) { + QString actionID = SUIT_ShortcutMgr::makeActionID(ROOT_MODULE_ID, theInModuleActionID); + // { actionID, assets } [] + auto& moduleAssets = myAssetsOfMetaActions[theModuleID]; + + auto& actionAssets = moduleAssets[actionID]; + actionAssets.myLangDependentAssets[theLang].myName = theAction->text(); + actionAssets.myLangDependentAssets[theLang].myToolTip = theAction->statusTip(); + + const QString fileName = theModuleID + DevTools::ASSETS_OF_META_SUFFIX; + writeToJSONFile(fileName, actionID, actionAssets); + } + else { + QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID); + // { actionID, assets } [] + auto& moduleAssets = myAssets[theModuleID]; + + auto& actionAssets = moduleAssets[actionID]; + actionAssets.myLangDependentAssets[theLang].myName = theAction->text(); + actionAssets.myLangDependentAssets[theLang].myToolTip = theAction->statusTip(); + + const QString fileName = theModuleID + DevTools::ASSETS_SUFFIX; + writeToJSONFile(fileName, actionID, actionAssets); + } + } + + void collectShortcutAndAssets(const QtxAction* const theAction) + { + const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theAction->ID()); + if (moduleIDAndActionID.second.isEmpty()) + return; + + if (!SUIT_ShortcutMgr::get()->getShortcutContainer().hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second)) + collectShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second, theAction->shortcut()); + + { // Collect action assets, if they are not provided in asset files. + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!"); + return; + } + + const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION); + if (lang.isEmpty()) + return; + + const auto& assetsInResources = SUIT_ShortcutMgr::getActionAssetsFromResources(theAction->ID()); + if (assetsInResources.first && assetsInResources.second.myLangDependentAssets.find(lang) != assetsInResources.second.myLangDependentAssets.end()) + return; + + collectAssets(moduleIDAndActionID.first, moduleIDAndActionID.second, lang, theAction); + } + } + +private: + /*! Appends new entries to content of dump files. */ + bool writeToXMLFile(const QString& theFileName, const std::map>& theSections) + { +#ifdef QT_NO_DOM + Warning("DebugTools for SUIT_ShortcutMgr can't create XML - #QT_NO_DOM is defined."); + return false; +#else QT_NO_DOM + static const QString DOC_TAG = "document"; + static const QString SECTION_TAG = "section"; + static const QString PARAMETER_TAG = "parameter"; + static const QString NAME_ATTR = "name"; + static const QString VAL_ATTR = "value"; + + const auto itFileAndDoc = myXMLFilesAndDocs.find(theFileName); + if (itFileAndDoc == myXMLFilesAndDocs.end()) { + const QString fullPath = DevTools::SAVE_PATH + theFileName + ".xml"; + if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) { + myXMLFilesAndDocs[theFileName] = std::pair(nullptr, nullptr); + return false; + } + + QFile* file = new QFile(fullPath); + if (!file->open(QFile::ReadWrite | QIODevice::Text)) { + delete file; + myXMLFilesAndDocs[theFileName] = std::pair(nullptr, nullptr); + return false; + } + + QDomDocument* dom = new QDomDocument(DOC_TAG); + QTextStream instream(file); + dom->setContent(instream.readAll()); + myXMLFilesAndDocs[theFileName] = std::pair(file, dom); + } + else if (itFileAndDoc->second.first == nullptr) { + return false; + } + + const auto fileAndDom = myXMLFilesAndDocs[theFileName]; + QFile* const file = fileAndDom.first; + QDomDocument* const dom = fileAndDom.second; + + QDomElement doc = dom->documentElement(); + if (doc.isNull()) { + *dom = QDomDocument(DOC_TAG); + doc = dom->createElement(DOC_TAG); + dom->appendChild(doc); + } + + static const std::function&, QDomDocument&, QDomElement&)> mergeParamsToSection = + [&](const std::map& parameters, QDomDocument& dom, QDomElement& sectionInDom) + { + for (const std::pair& nameAndVal : parameters) { + const QString& paramName = nameAndVal.first; + const QString& paramVal = nameAndVal.second; + bool fileHasParam = false; + for (QDomElement paramInDom = sectionInDom.firstChildElement(PARAMETER_TAG); !paramInDom.isNull(); paramInDom = paramInDom.nextSiblingElement(PARAMETER_TAG)) { + const QString paramNameInDom = paramInDom.attribute(NAME_ATTR); + if (paramName == paramNameInDom) { + const QString paramValInDom = paramInDom.attribute(VAL_ATTR); + if (paramValInDom != paramVal) { + QDomElement replaceElement = dom.createElement(PARAMETER_TAG); + replaceElement.setAttribute(NAME_ATTR, paramName); + replaceElement.setAttribute(VAL_ATTR, paramVal); + sectionInDom.replaceChild(replaceElement, paramInDom); + } + + fileHasParam = true; + break; + } + } + if (!fileHasParam) { + QDomElement newParam = dom.createElement(PARAMETER_TAG); + newParam.setAttribute(NAME_ATTR, paramName); + newParam.setAttribute(VAL_ATTR, paramVal); + sectionInDom.insertAfter(newParam, sectionInDom.lastChildElement(PARAMETER_TAG)); + } + } + return; + }; + + for (const auto& sectionNameAndParams : theSections) { + const QString& sectionName = sectionNameAndParams.first; + const std::map& parameters = sectionNameAndParams.second; + + bool fileHasSection = false; + for (QDomElement sectionInDom = doc.firstChildElement(SECTION_TAG); !sectionInDom.isNull(); sectionInDom = sectionInDom.nextSiblingElement(SECTION_TAG)) { + QString sectionNameInDom = sectionInDom.attribute(NAME_ATTR); + if (sectionNameInDom == sectionName) { + mergeParamsToSection(parameters, *dom, sectionInDom); + fileHasSection = true; + break; + } + } + + if (!fileHasSection) { + QDomElement newSection = dom->createElement(SECTION_TAG); + newSection.setAttribute(NAME_ATTR, sectionName); + doc.insertAfter(newSection, doc.lastChildElement(SECTION_TAG)); + mergeParamsToSection(parameters, *dom, newSection); + } + } + + file->resize(0); + QTextStream outstream(file); + outstream << dom->toString(); + + return true; +#endif // QT_NO_DOM + } + + /*! Appends new entries to content of dump files. */ + bool writeToJSONFile(const QString& theFileName, const QString& theActionID, const SUIT_ActionAssets& theAssets) + { + const auto itFileAndDoc = myJSONFilesAndDocs.find(theFileName); + if (itFileAndDoc == myJSONFilesAndDocs.end()) { + const QString fullPath = DevTools::SAVE_PATH + theFileName + ".json"; + if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) { + myJSONFilesAndDocs[theFileName] = std::pair(nullptr, nullptr); + return false; + } + + const bool fileExisted = QFileInfo::exists(fullPath); + QFile* file = new QFile(fullPath); + if (!file->open(QFile::ReadWrite | QIODevice::Text)) { + delete file; + myJSONFilesAndDocs[theFileName] = std::pair(nullptr, nullptr); + return false; + } + + QJsonParseError jsonError; + QJsonDocument* document = new QJsonDocument(QJsonDocument::fromJson(file->readAll(), &jsonError)); + if (jsonError.error != QJsonParseError::NoError && fileExisted) { + Warning("SUIT_ShortcutMgr: error during parsing of action asset dump file \"" + fullPath + "\"!"); + delete file; + delete document; + myJSONFilesAndDocs[theFileName] = std::pair(nullptr, nullptr); + return false; + } + + if (!document->isObject()) { + document->setObject(QJsonObject()); + file->resize(0); + QTextStream outstream(file); + outstream << document->toJson(QJsonDocument::Indented); + } + + myJSONFilesAndDocs[theFileName] = std::pair(file, document); + } + else if (itFileAndDoc->second.first == nullptr) { + return false; + } + + const auto fileAndDoc = myJSONFilesAndDocs[theFileName]; + QFile* const file = fileAndDoc.first; + QJsonDocument* const document = fileAndDoc.second; + + QJsonObject rootJSON = document->object(); + QJsonObject actionAssetsJSON = rootJSON[theActionID].toObject(); + SUIT_ActionAssets actionAssets; + actionAssets.fromJSON(actionAssetsJSON); + actionAssets.merge(theAssets, true /*theOverride*/); + actionAssets.toJSON(actionAssetsJSON); + rootJSON[theActionID] = actionAssetsJSON; + document->setObject(rootJSON); + + file->resize(0); + QTextStream outstream(file); + outstream << document->toJson(QJsonDocument::Indented); + + return true; + } + +public: + void collectAssetsOfActionWithInvalidID(const QAction* const theAction) + { + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("DevTools for SUIT_ShortcutMgr can't retrieve resource manager!"); + return; + } + + const QString lang = resMgr->stringValue(LANG_SECTION, LANG_SECTION); + if (lang.isEmpty()) + return; + + if (!myActionsWithInvalidIDsFile) { + const QString fullPath = DevTools::SAVE_PATH + lang + DevTools::INVALID_ID_ACTIONS_SUFFIX + ".csv"; + if (!Qtx::mkDir(QFileInfo(fullPath).absolutePath())) + return; + + myActionsWithInvalidIDsFile = new QFile(fullPath); + if (!myActionsWithInvalidIDsFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + delete myActionsWithInvalidIDsFile; + myActionsWithInvalidIDsFile = nullptr; + return; + } + + QTextStream ostream(myActionsWithInvalidIDsFile); + ostream << "text\t" << "tool tip\t" << "status tip\t" << "key sequence\t" << "QtxAction?\t" << "ID\n"; + ostream.flush(); + } + + QTextStream ostream(myActionsWithInvalidIDsFile); + const auto aQtxAction = qobject_cast(theAction); + ostream << theAction->text() << "\t" << theAction->toolTip() << "\t" << theAction->statusTip() << "\t" + << theAction->shortcut().toString() << "\t" << (aQtxAction ? "yes\t" : "no\t") << (aQtxAction ? aQtxAction->ID() + "\n" : "\n"); + ostream.flush(); + } + + static const QString SAVE_PATH; + static const QString SHORTCUTS_SUFFIX; + static const QString SHORTCUTS_OF_META_SUFFIX; + static const QString ASSETS_SUFFIX; + static const QString ASSETS_OF_META_SUFFIX; + static const QString INVALID_ID_ACTIONS_SUFFIX; + + static DevTools* instance; + static const QString XML_SECTION_TOKENS_SEPARATOR; + + /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */ + std::map> myShortcuts; + + /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. */ + std::map> myShortcutsOfMetaActions; + + /** { moduleID, { actionID, assets }[] }[] */ + std::map> myAssets; + + /** { moduleID, { actionID, assets }[] }[] */ + std::map> myAssetsOfMetaActions; + +#ifndef QT_NO_DOM + // { filename, {file, domDoc} }[] + std::map> myXMLFilesAndDocs; +#endif // QT_NO_DOM + // { filename, {file, jsonDoc} }[] + std::map> myJSONFilesAndDocs; + + QFile* myActionsWithInvalidIDsFile; +}; +/*static*/ DevTools* DevTools::instance = nullptr; +/*static*/ const QString DevTools::SAVE_PATH = "shortcut_mgr_dev/"; +/*static*/ const QString DevTools::INVALID_ID_ACTIONS_SUFFIX = "_actions_with_invalid_IDs"; +/*static*/ const QString DevTools::XML_SECTION_TOKENS_SEPARATOR = ":"; +/*static*/ const QString DevTools::SHORTCUTS_SUFFIX = "_shortcuts"; +/*static*/ const QString DevTools::SHORTCUTS_OF_META_SUFFIX = "_shortcuts_of_meta_actions"; +/*static*/ const QString DevTools::ASSETS_SUFFIX = "_assets"; +/*static*/ const QString DevTools::ASSETS_OF_META_SUFFIX = "_assets_of_meta_actions"; +#endif // SHORTCUT_MGR_DEVTOOLS + + + +SUIT_ShortcutContainer::SUIT_ShortcutContainer() +{ + myShortcuts.emplace(ROOT_MODULE_ID, std::map()); + myShortcutsInversed.emplace(ROOT_MODULE_ID, std::map()); +} + +std::set SUIT_ShortcutContainer::getIDsOfInterferingModules(const QString& theModuleID) const +{ + std::set IDsOfInterferingModules; + if (theModuleID == ROOT_MODULE_ID) { + for (const auto& moduleIDAndShortcuts : myShortcuts) { + IDsOfInterferingModules.emplace(moduleIDAndShortcuts.first); + } + } + else { + IDsOfInterferingModules.emplace(ROOT_MODULE_ID); + if (theModuleID != ROOT_MODULE_ID) + IDsOfInterferingModules.emplace(theModuleID); + } + return IDsOfInterferingModules; +} + +std::set SUIT_ShortcutContainer::getIDsOfAllModules() const +{ + std::set res; + for (const auto& moduleIDAndShortcuts : myShortcutsInversed) { + res.emplace(moduleIDAndShortcuts.first); + } + return res; +} + +std::set> SUIT_ShortcutContainer::setShortcut(QString theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride) +{ + if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) { + ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid module ID = \"" + theModuleID + "\"."); + return std::set>(); + } + + if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) { + ShCutDbg() && ShCutDbg("Attempt to define a shortcut using invalid in-module action ID = \"" + theInModuleActionID + "\"."); + return std::set>(); + } + + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) + theModuleID = ROOT_MODULE_ID; + + auto itModuleShortcuts = myShortcuts.find(theModuleID); + auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID); + if (itModuleShortcuts == myShortcuts.end()) { + itModuleShortcuts = myShortcuts.emplace(theModuleID, std::map()).first; + itModuleShortcutsInversed = myShortcutsInversed.emplace(theModuleID, std::map()).first; + } + + std::map& moduleShortcuts = itModuleShortcuts->second; + std::map& moduleShortcutsInversed = itModuleShortcutsInversed->second; + + if (theKeySequence.isEmpty()) { + // Disable shortcut. + + auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID); + if (itShortcutInversed == moduleShortcutsInversed.end()) { + // No key sequence was mapped to the action earlier. + // Set disabled shortcut. + moduleShortcutsInversed.emplace(theInModuleActionID, NO_KEYSEQUENCE); + return std::set>(); + } + else /* if keySequence was mapped to the action earlier. */ { + QKeySequence& keySequence = itShortcutInversed->second; + + moduleShortcuts.erase(keySequence); + keySequence = NO_KEYSEQUENCE; + + return std::set>(); + } + } + + { // Check if the shortcut is already set. + const auto itShortcut = moduleShortcuts.find(theKeySequence); + if (itShortcut != moduleShortcuts.end()) { + if (itShortcut->second == theInModuleActionID) { + // The shortcut was set earlier. Nothing to change. + return std::set>(); + } + } + } + + auto conflictingActionIDs = std::set>(); + { // Look for conflicting shortcuts with the same key sequence from interfering modules. + std::set IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID); + for (const QString& IDOfInterferingModule : IDsOfInterferingModules) { + std::map& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule); + auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence); + if (itConflictingShortcut != shortcutsOfInterferingModule.end()) { + const QString& conflictingActionID = itConflictingShortcut->second; + + conflictingActionIDs.insert(std::pair(IDOfInterferingModule, conflictingActionID)); + + if (theOverride) { + // Disable conflicting shortcuts. + std::map& shortcutsOfInterferingModuleInversed = myShortcutsInversed.at(IDOfInterferingModule); + shortcutsOfInterferingModuleInversed[conflictingActionID] = NO_KEYSEQUENCE; + shortcutsOfInterferingModule.erase(itConflictingShortcut); + } + } + } + + if (!theOverride && !conflictingActionIDs.empty()) + return conflictingActionIDs; + } + + { // Ensure, that the module has only shortcut for the action ID. + auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID); + if (itShortcutInversed != moduleShortcutsInversed.end()) { + // Redefine key sequence for existing action. + + QKeySequence& keySequence = itShortcutInversed->second; + + moduleShortcuts.erase(keySequence); + moduleShortcuts[theKeySequence] = theInModuleActionID; + + keySequence = theKeySequence; + } + else /* if the action has not been added earlier. */ { + moduleShortcuts[theKeySequence] = theInModuleActionID; + moduleShortcutsInversed[theInModuleActionID] = theKeySequence; + } + } + + return conflictingActionIDs; +} + +std::set> SUIT_ShortcutContainer::getConflicts( + QString theModuleID, + const QString& theInModuleActionID, + const QKeySequence& theKeySequence +) const +{ + if (theKeySequence.isEmpty()) + return std::set>(); + + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) + theModuleID = ROOT_MODULE_ID; + + { // Check if the shortcut is set. + const auto itModuleShortcuts = myShortcuts.find(theModuleID); + if (itModuleShortcuts != myShortcuts.end()) { + const std::map& moduleShortcuts = itModuleShortcuts->second; + const auto itShortcut = moduleShortcuts.find(theKeySequence); + if (itShortcut != moduleShortcuts.end()) { + if (itShortcut->second == theInModuleActionID) { + // The shortcut is set => no conflicts. + return std::set>(); + } + } + } + } + + auto conflictingActionIDs = std::set>(); + { // Look for conflicting shortcuts with the same key sequence from interfering modules. + std::set IDsOfInterferingModules = getIDsOfInterferingModules(theModuleID); + for (const QString& IDOfInterferingModule : IDsOfInterferingModules) { + const std::map& shortcutsOfInterferingModule = myShortcuts.at(IDOfInterferingModule); + const auto itConflictingShortcut = shortcutsOfInterferingModule.find(theKeySequence); + if (itConflictingShortcut != shortcutsOfInterferingModule.end()) { + const QString& conflictingActionID = itConflictingShortcut->second; + conflictingActionIDs.insert(std::pair(IDOfInterferingModule, conflictingActionID)); + } + } + } + return conflictingActionIDs; +} + +const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID, const QString& theInModuleActionID) const +{ + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) + theModuleID = ROOT_MODULE_ID; + + const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID); + if (itModuleShortcutsInversed == myShortcutsInversed.end()) + return NO_KEYSEQUENCE; + + const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second; + const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID); + if (itShortcutInversed == moduleShortcutsInversed.end()) + return NO_KEYSEQUENCE; + + return itShortcutInversed->second; +} + +bool SUIT_ShortcutContainer::hasShortcut(QString theModuleID, const QString& theInModuleActionID) const +{ + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) + theModuleID = ROOT_MODULE_ID; + + const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID); + if (itModuleShortcutsInversed == myShortcutsInversed.end()) + return false; + + const auto& moduleShortcutsInversed = itModuleShortcutsInversed->second; + const auto itShortcutInversed = moduleShortcutsInversed.find(theInModuleActionID); + if (itShortcutInversed == moduleShortcutsInversed.end()) + return false; + + return true; +} + +const std::map& SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID) const +{ + static const std::map EMPTY_RES; + const auto it = myShortcutsInversed.find(theModuleID); + if (it == myShortcutsInversed.end()) + return EMPTY_RES; + + return it->second; +} + +const std::map SUIT_ShortcutContainer::getModuleShortcutsInversed(const QString& theModuleID, const QString& theActionIDPrefix) const +{ + const auto it = myShortcutsInversed.find(theModuleID); + if (it == myShortcutsInversed.end()) + return std::map(); + + std::map shortcutsInversed; + for (const auto& existingShortcut : it->second) { + if (existingShortcut.first.startsWith(theActionIDPrefix)) + shortcutsInversed[existingShortcut.first] = existingShortcut.second; + } + return shortcutsInversed; +} + +QString SUIT_ShortcutContainer::toString() const +{ + QString text; + text += "Shortcuts inversed:\n"; + for (auto it = myShortcutsInversed.begin(); it != myShortcutsInversed.end(); it++) { + const QString& moduleID = it->first; + const auto& moduleShortcuts = it->second; + text += (it == myShortcutsInversed.begin() ? "\"" : "\n\"") + moduleID + "\""; + for (const auto& shortcut : moduleShortcuts) { + text += "\n\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\""; + } + } + text += "\nShortcuts:\n"; + for (auto it = myShortcuts.begin(); it != myShortcuts.end(); it++) { + const QString& moduleID = it->first; + const auto& moduleShortcuts = it->second; + text += (it == myShortcuts.begin() ? "\"" : "\n\"") + moduleID + "\""; + for (const auto& shortcut : moduleShortcuts) { + text += "\n\t\"" + shortcut.first.toString() + "\"\t\"" + shortcut.second + "\""; + } + } + return text; +} + +/*static*/ const QString SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME = "name"; +/*static*/ const QString SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP = "tooltip"; + +bool SUIT_ActionAssets::LangDependentAssets::fromJSON(const QJsonObject& theJsonObject) +{ + myName = theJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME].toString(); + myToolTip = theJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP].toString(); + + if (myName.isEmpty()) + myName = myToolTip; + + return !myName.isEmpty(); +} + +void SUIT_ActionAssets::LangDependentAssets::toJSON(QJsonObject& oJsonObject) const +{ + oJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_NAME] = myName; + oJsonObject[SUIT_ActionAssets::LangDependentAssets::PROP_ID_TOOLTIP] = myToolTip; +} + +/*static*/ const QString SUIT_ActionAssets::STRUCT_ID = "SUIT_ActionAssets"; +/*static*/ const QString SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS = "langDependentAssets"; +/*static*/ const QString SUIT_ActionAssets::PROP_ID_ICON_PATH = "iconPath"; + +bool SUIT_ActionAssets::fromJSON(const QJsonObject& theJsonObject) +{ + myLangDependentAssets.clear(); + + auto lda = SUIT_ActionAssets::LangDependentAssets(); + const auto& langToLdaJson = theJsonObject[SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS].toObject(); + for (const QString& lang : langToLdaJson.keys()) { + if (!lda.fromJSON(langToLdaJson[lang].toObject())) + continue; + + myLangDependentAssets[lang] = lda; + } + + myIconPath = theJsonObject[SUIT_ActionAssets::PROP_ID_ICON_PATH].toString(); + + return !myLangDependentAssets.empty(); +} + +void SUIT_ActionAssets::toJSON(QJsonObject& oJsonObject) const +{ + auto langDependentAssetsJSON = QJsonObject(); + + auto langDependentAssetsItemJSON = QJsonObject(); + for (const auto& langAndLDA : myLangDependentAssets) { + langAndLDA.second.toJSON(langDependentAssetsItemJSON); + langDependentAssetsJSON[langAndLDA.first] = langDependentAssetsItemJSON; + } + oJsonObject[SUIT_ActionAssets::PROP_ID_LANG_DEPENDENT_ASSETS] = langDependentAssetsJSON; + + oJsonObject[SUIT_ActionAssets::PROP_ID_ICON_PATH] = myIconPath; +} + +QString SUIT_ActionAssets::toString() const +{ + QJsonObject jsonObject; + toJSON(jsonObject); + return QString::fromStdString(QJsonDocument(jsonObject).toJson(QJsonDocument::Indented).toStdString()); +} + +QStringList SUIT_ActionAssets::getLangs() const +{ + QStringList langs; + + for (const auto& langAndAssets : myLangDependentAssets) { + langs.push_back(langAndAssets.first); + } + + return langs; +} + +void SUIT_ActionAssets::clearAllLangsExcept(const QString& theLang) +{ + for (auto it = myLangDependentAssets.begin(); it != myLangDependentAssets.end();) { + if (it->first == theLang) + it++; + else + it = myLangDependentAssets.erase(it); + } +} + +void SUIT_ActionAssets::merge(const SUIT_ActionAssets& theOther, bool theOverride) +{ + for (const auto& otherLangAndLDA : theOther.myLangDependentAssets) { + const QString& lang = otherLangAndLDA.first; + const auto& otherLDA = otherLangAndLDA.second; + auto& thisLDA = myLangDependentAssets[lang]; + + if (thisLDA.myName.isEmpty() || theOverride && !otherLDA.myName.isEmpty()) + thisLDA.myName = otherLDA.myName; + + if (thisLDA.myToolTip.isEmpty() || theOverride && !otherLDA.myToolTip.isEmpty()) + thisLDA.myToolTip = otherLDA.myToolTip; + } + + if (theOverride) + myIconPath = theOther.myIconPath; +} + +std::map> SUIT_ShortcutContainer::merge( + const SUIT_ShortcutContainer& theOther, + bool theOverride, + bool theTreatAbsentIncomingAsDisabled +) { + std::map> changesOfThis; + + for (const auto& shortcutsInversedOfOtherPair : theOther.myShortcutsInversed) { + const QString& moduleIDOther = shortcutsInversedOfOtherPair.first; + const auto& shortcutsInversedOther = shortcutsInversedOfOtherPair.second; + for (const auto& shortcutInversedOther : shortcutsInversedOther) { + const QString& inModuleActionIDOther = shortcutInversedOther.first; + const QKeySequence& keySequenceOther = shortcutInversedOther.second; + if (theOverride) { + if (hasShortcut(moduleIDOther, inModuleActionIDOther) && getKeySequence(moduleIDOther, inModuleActionIDOther) == 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; + for (const auto& disabledActionOfThis : disabledActionsOfThis) { + changesOfThis[disabledActionOfThis.first][disabledActionOfThis.second] = NO_KEYSEQUENCE; + } + } + } + else /* if (!theOverride) */ { + if (hasShortcut(moduleIDOther, inModuleActionIDOther)) + continue; + else { + const auto conflictingActionsOfThis = setShortcut(moduleIDOther, inModuleActionIDOther, keySequenceOther, false); + if (conflictingActionsOfThis.empty()) { + changesOfThis[moduleIDOther][inModuleActionIDOther] = keySequenceOther; + } + else /* if this has no shortcut for the action, but the incoming key sequence conflicts with others shortcuts. */ { + changesOfThis[moduleIDOther][inModuleActionIDOther] = NO_KEYSEQUENCE; + } + } + } + } + } + + if (theOverride && theTreatAbsentIncomingAsDisabled) { + // Disable existing shortcuts, if they are absent in theOther. + for (auto& shortcutsInversedPair : myShortcutsInversed) { + const QString& moduleID = shortcutsInversedPair.first; + auto& moduleShortcutsInversed = shortcutsInversedPair.second; + for (auto& inversedShortcut : moduleShortcutsInversed) { + if (theOther.hasShortcut(moduleID, inversedShortcut.first)) + continue; + + if (inversedShortcut.second.isEmpty()) + continue; // Existing shortcut is already disabled. + + auto itShortcutsPair = myShortcuts.find(moduleID); + if (itShortcutsPair == myShortcuts.end()) + continue; // The check is an overhead in an error-free designed class, but let be just in case. + + auto& moduleShortcuts = itShortcutsPair->second; + moduleShortcuts.erase(inversedShortcut.second); + inversedShortcut.second = NO_KEYSEQUENCE; + changesOfThis[moduleID][inversedShortcut.first] = NO_KEYSEQUENCE; + } + } + } + + return changesOfThis; +} + + +SUIT_ShortcutMgr* SUIT_ShortcutMgr::myShortcutMgr = nullptr; + SUIT_ShortcutMgr::SUIT_ShortcutMgr() : QObject() { - qApp->installEventFilter( this ); + qApp->installEventFilter( this ); +} + +SUIT_ShortcutMgr::~SUIT_ShortcutMgr() +{ + qApp->removeEventFilter( this ); +} + +/*static*/ void SUIT_ShortcutMgr::Init() +{ + if( myShortcutMgr == nullptr) { + myShortcutMgr = new SUIT_ShortcutMgr(); + myShortcutMgr->setShortcutsFromPreferences(); + } +} + +/*static*/ SUIT_ShortcutMgr* SUIT_ShortcutMgr::get() +{ + Init(); + return myShortcutMgr; +} + +/*static*/ bool SUIT_ShortcutMgr::isKeySequenceValid(const QKeySequence& theKeySequence) +{ + // TODO Perform check whether a key sequence is platform-compatible. + return true; +} + +/*static*/ std::pair SUIT_ShortcutMgr::toKeySequenceIfValid(const QString& theKeySequenceString) +{ + auto res = std::pair(false, QKeySequence()); + + try { + res.second = QKeySequence::fromString(theKeySequenceString); + if (res.second.toString() != theKeySequenceString) + return std::pair(false, QKeySequence()); + + if (!SUIT_ShortcutMgr::isKeySequenceValid(res.second)) + return std::pair(false, QKeySequence()); + } + catch (...) { + return std::pair(false, QKeySequence()); + } + + res.first = true; + return res; +} + +/*static*/ bool SUIT_ShortcutMgr::isModuleIDValid(const QString& theModuleID) +{ + if (theModuleID.contains(TOKEN_SEPARATOR)) + return false; + + if (theModuleID.simplified() != theModuleID) + return false; + + return true; +} + +/*static*/ bool SUIT_ShortcutMgr::isInModuleActionIDValid(const QString& theInModuleActionID) +{ + QStringList tokens = theInModuleActionID.split(TOKEN_SEPARATOR); + for (QStringList::size_type i = 0; i < tokens.length(); i++) { + const QString simplifiedToken = tokens[i].simplified(); + if ( + simplifiedToken.isEmpty() || + simplifiedToken != tokens[i] || + i == 0 && simplifiedToken == META_ACTION_PREFIX || + i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX) + ) + return false; + } + return true; +} + +/*static*/ bool SUIT_ShortcutMgr::isInModuleMetaActionID(const QString& theInModuleActionID) +{ + return theInModuleActionID.startsWith(META_ACTION_PREFIX); +} + +/*static*/ std::pair SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(const QString& theActionID) +{ + QStringList tokens = theActionID.split(TOKEN_SEPARATOR); + if (tokens.length() < 2) + return std::pair(); + + auto res = std::pair(); + + if (tokens[0].simplified() != tokens[0]) + return std::pair(); + + res.first = tokens[0]; + tokens.pop_front(); + + for (QStringList::size_type i = 0; i < tokens.length(); i++) { + const QString simplifiedToken = tokens[i].simplified(); + if ( + simplifiedToken.isEmpty() || + simplifiedToken != tokens[i] || + i == 0 && simplifiedToken == META_ACTION_PREFIX || + i != 0 && simplifiedToken.startsWith(META_ACTION_PREFIX) + ) + return std::pair(); + } + res.second = tokens.join(TOKEN_SEPARATOR); + + return res; +} + +/*static*/ bool SUIT_ShortcutMgr::isActionIDValid(const QString& theActionID) +{ + return !SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID).second.isEmpty(); +} + +/*static*/ QString SUIT_ShortcutMgr::makeActionID(const QString& theModuleID, const QString& theInModuleActionID) +{ + if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) + return QString(); + + if (!isInModuleActionIDValid(theInModuleActionID)) + return QString(); + + return theModuleID + TOKEN_SEPARATOR + theInModuleActionID; } -/*! - \brief Destructor -*/ -SUIT_ShortcutMgr::~SUIT_ShortcutMgr() +/*static*/ void SUIT_ShortcutMgr::fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly) { - qApp->removeEventFilter( this ); + ShCutDbg() && ShCutDbg("Retrieving preferences from resources."); + + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("SUIT_ShortcutMgr can't retrieve resource manager!"); + return; + } + + const auto resMgrWorkingModeBefore = resMgr->workingMode(); + if (theDefaultOnly) + resMgr->setWorkingMode(QtxResourceMgr::IgnoreUserValues); + + /** List of modules with invalid IDs. */ + QStringList invalidModuleIDs; + + /** { moduleID, {inModuleActionID, keySequence}[] }[] */ + std::map>> invalidShortcuts; + + /** + * Shortcuts, which have not been set, because they are in conflict with previously parsed shortcuts. + * { moduleID, {inModuleActionID, keySequence}[] }[] */ + std::map>> conflicts; + + // Resource manager strips leading and trailing whitespaces from subsections' names. + // And then it is not able to retrieve parametes from that subsections, + // because parsed subsection names differ from the ones in resource file. + // Anyway, it does not affect operability of ShortcutMgr. + QStringList moduleIDs = resMgr->subSections(SECTION_NAME_PREFIX, true); + if (ShCutDbg()) { + if (moduleIDs.isEmpty()) + ShCutDbg("No discovered shortcut modules."); + else + ShCutDbg("Discovered shortcut modules: \"" + moduleIDs.join("\", \"") + "."); + } + moduleIDs.push_front(ROOT_MODULE_ID); // Resource manager filters out empty section suffices. + moduleIDs.removeDuplicates(); + + for (size_t i = 0; i < moduleIDs.size(); i++) { + const auto& moduleID = moduleIDs[i]; + if (!SUIT_ShortcutMgr::isModuleIDValid(moduleID)) { + invalidModuleIDs.push_back(moduleID); + continue; + } + + const QString sectionName = SECTION_NAME_PREFIX + resMgr->sectionsToken() + moduleID; + QStringList moduleActionIDs = resMgr->parameters(sectionName); + + for(const QString& inModuleActionID : moduleActionIDs) { + QString keySequenceString = QString(""); + resMgr->value(sectionName, inModuleActionID, keySequenceString); + const auto keySequence = SUIT_ShortcutMgr::toKeySequenceIfValid(keySequenceString); + + ShCutDbg() && ShCutDbg("Shortcut discovered: \"" + moduleID + "\"\t\"" + inModuleActionID + "\"\t\"" + keySequenceString + "\"."); + + if ( + !SUIT_ShortcutMgr::isInModuleActionIDValid(inModuleActionID) || + !keySequence.first || + SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != ROOT_MODULE_ID + ) { + std::list>& moduleInvalidShortcuts = invalidShortcuts[moduleID]; + moduleInvalidShortcuts.push_back(std::pair(inModuleActionID, keySequenceString)); + continue; + } + + const auto shortcutConflicts = theContainer.setShortcut(moduleID, inModuleActionID, keySequence.second, false /*override*/); + if (!shortcutConflicts.empty()) { + auto& moduleConflicts = conflicts[moduleID]; + moduleConflicts.push_back(std::pair(inModuleActionID, keySequence.second)); + } + } + } + + if (!invalidModuleIDs.isEmpty() || !invalidShortcuts.empty() || !conflicts.empty()) + { // Prepare report and show warning. + QString report; + if (!invalidModuleIDs.isEmpty()) { + report += tr("Invalid module IDs") + ":"; + for (const QString& invalidModuleID : invalidModuleIDs) { + report += "\n\t\"" + invalidModuleID + "\"" ; + } + } + + if (!invalidShortcuts.empty()) { + if (!report.isEmpty()) + report += "\n\n"; + + report += tr("Invalid shortcuts") + ":"; + for (const auto& moduleAndShortcuts : invalidShortcuts) { + report += "\n\t\"" + moduleAndShortcuts.first + "\""; + const std::list>& moduleShortcuts = moduleAndShortcuts.second; + for (const auto& shortcut : moduleShortcuts) { + report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second + "\""; + } + } + } + + if (!conflicts.empty()) { + if (!report.isEmpty()) + report += "\n\n"; + + report += tr("These shortcuts have not been set to theContainer, because they conflict with previously parsed ones") + ":"; + for (const auto& moduleAndShortcuts : conflicts) { + report += "\n\t\"" + moduleAndShortcuts.first + "\""; + + const std::list>& moduleShortcuts = moduleAndShortcuts.second; + for (const auto& shortcut : moduleShortcuts) { + report += "\n\t\t\"" + shortcut.first + "\"\t\"" + shortcut.second.toString() + "\""; + } + } + } + + report += "\n."; + + const auto text = tr("Invalid shortcuts in preferences"); + const auto informativeText = tr("Fix the following entries in the preference files manually"); + if (!theDefaultOnly) { + // If user preferences are accounted, show warning in UI. + SUIT_Application* app = SUIT_Session::session()->activeApplication(); + if (app && app->desktop()) { + // Is not compiled without cast or with static_cast. + QMessageBox msgBox((QWidget*)app->desktop()); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setTextFormat(Qt::RichText); + msgBox.setText("" + text + ""); + msgBox.setInformativeText(informativeText + ":"); + msgBox.setWindowFlags(Qt::WindowType::Popup); + msgBox.setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + msgBox.setDetailedText(report); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setMinimumWidth(600); + msgBox.exec(); + } + } + Warning(text + ". " + informativeText + ":\n" + report); + } + + if (theDefaultOnly) + resMgr->setWorkingMode(resMgrWorkingModeBefore); + + ShCutDbg() && ShCutDbg("theContainer holds following shortcuts:\n" + theContainer.toString()); } -/*! - \brief Create new instance of shortcut manager. -*/ -void SUIT_ShortcutMgr::Init() +QString substituteBashVars(const QString& theString) { - if( myShortcutMgr==NULL ) - myShortcutMgr = new SUIT_ShortcutMgr(); + QString res = theString; + const auto env = QProcessEnvironment::systemEnvironment(); + int pos = 0; + QRegExp rx("\\$\\{([^\\}]+)\\}"); // Match substrings enclosed by "${" and "}". + rx.setMinimal(true); // Set search to non-greedy. + while((pos = rx.indexIn(res, pos)) != -1) { + QString capture = rx.cap(1); + QString subst = env.value(capture); + ShCutDbg("capture = " + capture); + ShCutDbg("subst = " + subst); + res.replace("${" + capture + "}", subst); + pos += rx.matchedLength(); + } + return res; } -/*! - \brief Return shortcut manager. -*/ -SUIT_ShortcutMgr* SUIT_ShortcutMgr::getShortcutMgr() +QString substitutePowerShellVars(const QString& theString) { - Init(); - - return myShortcutMgr; + QString res = theString; + int pos = 0; + QRegExp rx("%([^%]+)%"); // Match substrings enclosed by "%". + rx.setMinimal(true); // Set search to non-greedy. + while((pos = rx.indexIn(res, pos)) != -1) { + QString capture = rx.cap(1); + QString subst = Qtx::getenv(capture.toUtf8().constData()); + ShCutDbg("capture = " + capture); + ShCutDbg("subst = " + subst); + res.replace("%" + capture + "%", subst); + pos += rx.matchedLength(); + } + return res; } -/*! - \brief Custom event filter for qapplication . - - Redefined from QObject::eventFilter(); -*/ -bool SUIT_ShortcutMgr::eventFilter( QObject* o, QEvent* e ) +QString substituteVars(const QString& theString) { - if ( e->type() == QEvent::ActionAdded ) { - QActionEvent* anActionEvent = (QActionEvent*)e; - if (anActionEvent) { - QtxAction* anAction = qobject_cast( anActionEvent->action() ); - if ( anAction ) - processAction( anAction ); + QString str = substituteBashVars(theString); + return substitutePowerShellVars(str); +} + +/*static*/ std::pair SUIT_ShortcutMgr::getActionAssetsFromResources(const QString& theActionID) +{ + auto res = std::pair(false, SUIT_ActionAssets()); + + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("SUIT_ShortcutMgr can't retrieve resource manager!"); + return res; + } + + QStringList actionAssetFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ASSET_FILE_PATHS); + for (const QString& actionAssetFilePath : actionAssetFilePaths) { + const QString path = substituteVars(actionAssetFilePath); + QFile actionAssetFile(path); + if (!actionAssetFile.open(QIODevice::ReadOnly)) { + Warning("SUIT_ShortcutMgr can't open action asset file \"" + path + "\"!"); + continue; + } + + QJsonParseError jsonError; + QJsonDocument document = QJsonDocument::fromJson(actionAssetFile.readAll(), &jsonError); + actionAssetFile.close(); + if(jsonError.error != QJsonParseError::NoError) { + Warning("SUIT_ShortcutMgr: error during parsing of action asset file \"" + path + "\"!"); + continue; + } + + if(!document.isObject()) { + Warning("SUIT_ShortcutMgr: empty action asset file \"" + path + "\"!"); + continue; + } + + QJsonObject object = document.object(); + if (object.keys().indexOf(theActionID) == -1) + continue; + + SUIT_ActionAssets actionAssets; + if (!actionAssets.fromJSON(object[theActionID].toObject())) { + ShCutDbg("Action asset file \"" + path + "\" contains invalid action assets with ID \"" + theActionID + "\"."); + continue; } + + res.second.merge(actionAssets, true); } - return QObject::eventFilter( o, e ); + res.first = true; + return res; } -/*! - \brief Return key sequence for shortcut action name. - \param actionName name of shortcut action in preferences - \return key sequence defined in preferences or empty sequence -*/ -QKeySequence SUIT_ShortcutMgr::getShortcutByActionName( const QString& actionName ) const + +/*static*/ QString SUIT_ShortcutMgr::getLang() { SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("SUIT_ShortcutMgr can't retrieve resource manager!"); + return DEFAULT_LANG; + } + + return resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG); +} + + +void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction) +{ + const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID); + const QString& moduleID = moduleIDAndActionID.first; + 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); + + return; + } + + { // If an action with the same memory address was registered earlier, + // clear all data about it to start registering procedure from scratch. + auto itPreviousModuleAndActionID = myActionIDs.find(theAction); + if (itPreviousModuleAndActionID != myActionIDs.end()) { + // Clear the data from myActions. + const auto& previousModuleAndActionID = itPreviousModuleAndActionID->second; + auto itActions = myActions.find(previousModuleAndActionID.first); + if (itActions != myActions.end()) { + std::map>& moduleActions = itActions->second; + auto itModuleActions = moduleActions.find(previousModuleAndActionID.second); + if (itModuleActions != moduleActions.end()) { + std::set& registeredActions = itModuleActions->second; + registeredActions.erase(theAction); + } + } + + myActionIDs.erase(itPreviousModuleAndActionID); + } + } + + auto itActions = myActions.find(moduleID); + if (itActions == myActions.end()) { + itActions = myActions.emplace(moduleID, std::map>()).first; + } - QString section = actionName.section( resMgr->sectionsToken(), 0, 0 ); - section.prepend( QString("shortcuts") + resMgr->sectionsToken() ); - QString parameter = actionName.section( resMgr->sectionsToken(), 1, 1 ); + std::map>& moduleActions = itActions->second; + auto itModuleActions = moduleActions.find(inModuleActionID); + if (itModuleActions != moduleActions.end()) { + std::set& registeredActions = itModuleActions->second; + const bool actionIsNew = registeredActions.emplace(theAction).second; + if (actionIsNew) + myActionIDs[theAction] = moduleIDAndActionID; + } + else { + std::set& registeredActions = moduleActions[inModuleActionID]; + registeredActions.emplace(theAction); + myActionIDs[theAction] = moduleIDAndActionID; + } - QString shortcutValue; - bool hasValue = resMgr->value( section, parameter, shortcutValue, false ); + connect(theAction, SIGNAL(destroyed(QObject*)), this, SLOT (onActionDestroyed(QObject*))); - if ( !hasValue ) - return QKeySequence(); + if (myShortcutContainer.hasShortcut(moduleID, inModuleActionID)) { + const QKeySequence& keySequence = getKeySequence(moduleID, inModuleActionID); + theAction->setShortcut(keySequence); + } + else { + ShCutDbg( + "Action with ID \"" + + (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) + + "\" is not added to default resource files." + ); + auto conflicts = myShortcutContainer.setShortcut(moduleID, inModuleActionID, theAction->shortcut(), false); + if (!conflicts.empty()) + theAction->setShortcut(NO_KEYSEQUENCE); // Unbind any key sequence, if it was bound outside of the class and interferes with other shortcuts. + } +} - return QKeySequence::fromString( shortcutValue ); +void SUIT_ShortcutMgr::registerAction(QtxAction* theAction) +{ + registerAction(theAction->ID(), theAction); } -/*! - \brief Set shortcut to the given action if the shortcut is defined. - \param action action to process - */ -void SUIT_ShortcutMgr::processAction( QtxAction* action ) +std::set SUIT_ShortcutMgr::getActions(const QString& theModuleID, const QString& theInModuleActionID) const { - QString shortcutActionName = action->shortcutActionName(); - - if ( !shortcutActionName.isEmpty() ) { - // Add action to the actions map - if ( !myShortcutActions.contains( shortcutActionName, action ) ) { - myShortcutActions.insert( shortcutActionName, action ); - connect( action, SIGNAL( destroyed( QObject* ) ), - this, SLOT ( onActionDestroyed( QObject* ) ) ); + if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) { + std::set actions; + for (const auto& actionAndID : myActionIDs) { + if (actionAndID.second.second == theInModuleActionID) + actions.emplace(actionAndID.first); } + return actions; + } + else { + const auto itActions = myActions.find(theModuleID); + if (itActions == myActions.end()) + return std::set(); + + const std::map>& moduleActions = itActions->second; + const auto itModuleActions = moduleActions.find(theInModuleActionID); + if (itModuleActions == moduleActions.end()) + return std::set(); - QKeySequence keySeq = getShortcutByActionName( shortcutActionName ); - action->setShortcut( keySeq ); + return itModuleActions->second; } } -/*! - \brief Enable/disable a shortcuts section. +std::pair SUIT_ShortcutMgr::getModuleIDAndInModuleID(const QAction* theAction) const { + const auto it = myActionIDs.find(const_cast(theAction)); + if (it == myActionIDs.end()) + return std::pair(); - Enables or disables actions which belong to the given shortcuts section. - Only actions which have an active desktop as a parent widget - are taken into account. + return it->second; +} - \param section shorcuts section - \param on if \c true - action will be enabled, otherwise - disabled -*/ -void SUIT_ShortcutMgr::setSectionEnabled( const QString& section, const bool on ) -{ - QMap::ConstIterator it; - for ( it = myShortcutActions.constBegin(); it != myShortcutActions.constEnd(); ++it ) { - QtxAction* action = it.value(); - QString shortcutActionName = action->shortcutActionName(); - QString actionSection = shortcutActionName.section( ":", 0, 0 ); - if ( actionSection == section ) { - // Check if the action parent widget equals to the active desktop - SUIT_Application* app = SUIT_Session::session()->activeApplication(); - if ( !app ) - return; - if ( action->parentWidget() == (QWidget*)app->desktop() ) - action->setEnabled( on ); +bool SUIT_ShortcutMgr::hasAction(const QAction* theAction) const +{ + return myActionIDs.find(const_cast(theAction)) != myActionIDs.end(); +} + +QString SUIT_ShortcutMgr::getActionID(const QAction* theAction) const +{ + const auto it = myActionIDs.find(const_cast(theAction)); + if (it == myActionIDs.end()) + return QString(); + + return SUIT_ShortcutMgr::makeActionID(it->second.first, it->second.second); +} + +void SUIT_ShortcutMgr::setActionsOfModuleEnabled(const QString& theModuleID, const bool theEnable) const +{ + const auto itModuleActions = myActions.find(theModuleID); + if (itModuleActions == myActions.end()) + return; + + SUIT_Application* app = SUIT_Session::session()->activeApplication(); + if (!app) + return; + + const std::map>& moduleActions = itModuleActions->second; + for (const auto& idAndActions : moduleActions) { + const std::set& actions = idAndActions.second; + for (QAction* const action : actions) { + if (action->parentWidget() == (QWidget*)app->desktop()) // Is not compiled without cast or with static_cast. + action->setEnabled(theEnable); } } } -/*! - \brief Update shortcuts from preferences. -*/ -void SUIT_ShortcutMgr::updateShortcuts() +void SUIT_ShortcutMgr::setActionsWithPrefixInIDEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const { - QMap::ConstIterator it; - for ( it = myShortcutActions.constBegin(); it != myShortcutActions.constEnd(); ++it ) { - QtxAction* action = it.value(); - QKeySequence keySeq = getShortcutByActionName( action->shortcutActionName() ); - action->setShortcut( keySeq ); + SUIT_Application* app = SUIT_Session::session()->activeApplication(); + if (!app) + return; + + for (const std::pair>& actionAndID : myActionIDs) { + QAction* const action = actionAndID.first; + // Is not compiled without cast or with static_cast. + if (action->parentWidget() == (QWidget*)app->desktop()) { + const QString& inModuleActionID = actionAndID.second.second; + if (inModuleActionID.startsWith(theInModuleActionIDPrefix)) + action->setEnabled(theEnable); + } } } -/*! - \brief Called when the corresponding action is destroyed. - - Removes destroyed action from the actions list. +void SUIT_ShortcutMgr::setSectionEnabled(const QString& theInModuleActionIDPrefix, bool theEnable) const +{ + setActionsWithPrefixInIDEnabled(theInModuleActionIDPrefix, theEnable); +} - \param obj action being destroyed -*/ -void SUIT_ShortcutMgr::onActionDestroyed( QObject* obj ) +void SUIT_ShortcutMgr::rebindActionsToKeySequences() const +{ + ShCutDbg() && ShCutDbg("SUIT_ShortcutMgr::rebindActionsToKeySequences()"); + for (const std::pair>& actionAndID : myActionIDs) { + actionAndID.first->setShortcut(getKeySequence(actionAndID.second.first, actionAndID.second.second)); + } +} + +void SUIT_ShortcutMgr::updateShortcuts() const +{ + rebindActionsToKeySequences(); +} + +std::set> SUIT_ShortcutMgr::setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride) +{ + const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(theActionID); + const QString& moduleID = moduleIDAndActionID.first; + const QString& inModuleActionID = moduleIDAndActionID.second; + + if (inModuleActionID.isEmpty()) { + ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid action ID \"" + theActionID + "\"."); + return std::set>(); + } + + return setShortcutNoIDChecks(moduleID, inModuleActionID, theKeySequence, theOverride); +} + +std::set> SUIT_ShortcutMgr::setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride) +{ + if (!SUIT_ShortcutMgr::isModuleIDValid(theModuleID)) { + ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid module ID \"" + theModuleID + "\"."); + return std::set>(); + } + + if (!SUIT_ShortcutMgr::isInModuleActionIDValid(theInModuleActionID)) { + ShCutDbg() && ShCutDbg("Attempt to set shortcut with invalid in-module action ID \"" + theInModuleActionID + "\"."); + return std::set>(); + } + + return setShortcutNoIDChecks(theModuleID, theInModuleActionID, theKeySequence, theOverride); +} + +const SUIT_ShortcutContainer& SUIT_ShortcutMgr::getShortcutContainer() const +{ + return myShortcutContainer; +} + +void SUIT_ShortcutMgr::mergeShortcutContainer(const SUIT_ShortcutContainer& theContainer, bool theOverride, bool theTreatAbsentIncomingAsDisabled) +{ + ShCutDbg() && ShCutDbg("ShortcutMgr merges shortcut container..."); + const auto changes = myShortcutContainer.merge(theContainer, theOverride, theTreatAbsentIncomingAsDisabled); + ShCutDbg() && ShCutDbg("ShortcutMgr keeps following shortcuts:\n" + myShortcutContainer.toString()); + + // 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) { + const std::set actions = getActions(moduleID, modifiedShortcut.first); + for (QAction* const action : actions) { + action->setShortcut(NO_KEYSEQUENCE); + } + } + } + } + + // 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) { + const std::set actions = getActions(moduleID, modifiedShortcut.first); + for (QAction* const action : actions) { + action->setShortcut(modifiedShortcut.second); + } + } + } + } + + SUIT_ShortcutMgr::saveShortcutsToPreferences(changes); +} + +QKeySequence SUIT_ShortcutMgr::getKeySequence(const QString& theModuleID, const QString& theInModuleActionID) const +{ + return myShortcutContainer.getKeySequence(theModuleID, theInModuleActionID); +} + +const std::map& SUIT_ShortcutMgr::getModuleShortcutsInversed(const QString& theModuleID) const +{ + return myShortcutContainer.getModuleShortcutsInversed(theModuleID); +} + +std::set SUIT_ShortcutMgr::getShortcutModuleIDs() const +{ + return myShortcutContainer.getIDsOfAllModules(); +} + +std::set SUIT_ShortcutMgr::getIDsOfInterferingModules(const QString& theModuleID) const +{ + return myShortcutContainer.getIDsOfInterferingModules(theModuleID); +} + +std::shared_ptr SUIT_ShortcutMgr::getModuleAssets(const QString& theModuleID) const +{ + const auto itModuleAssets = myModuleAssets.find(theModuleID); + if (itModuleAssets == myModuleAssets.end()) { + auto assets = std::shared_ptr(new SUIT_ActionAssets()); + auto lda = SUIT_ActionAssets::LangDependentAssets(); + lda.myName = theModuleID; // At least something meaningful. + + assets->myLangDependentAssets.emplace(SUIT_ShortcutMgr::getLang(), lda); + return assets; + } + return itModuleAssets->second; +} + +QString SUIT_ShortcutMgr::getModuleName(const QString& theModuleID, const QString& theLang) const +{ + const auto assets = getModuleAssets(theModuleID); + const auto& ldaMap = assets->myLangDependentAssets; + if (ldaMap.empty()) + return theModuleID; + + auto itLang = ldaMap.find(theLang.isEmpty() ? SUIT_ShortcutMgr::getLang() : theLang); + if (itLang == ldaMap.end()) + itLang = ldaMap.begin(); // Get name in any language. + + const auto& name = itLang->second.myName; + return name.isEmpty() ? theModuleID : name; +} + +std::shared_ptr SUIT_ShortcutMgr::getActionAssets(const QString& theModuleID, const QString& theInModuleActionID) const +{ + const QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID); + if (actionID.isEmpty()) { + ShCutDbg() && ShCutDbg("Can't get action assets: either/both module ID \"" + theModuleID + "\" or/and in-module action ID \"" + theInModuleActionID + "\" is/are invalid."); + return std::shared_ptr(nullptr); + } + return getActionAssets(actionID); +} + +std::shared_ptr SUIT_ShortcutMgr::getActionAssets(const QString& theActionID) const +{ + const auto it = myActionAssets.find(theActionID); + if (it == myActionAssets.end()) + return std::shared_ptr(nullptr); + else + return it->second; +} + +QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang) const +{ + const QString actionID = SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID); + if (actionID.isEmpty()) { + ShCutDbg() && ShCutDbg("Can't get action name: either/both module ID \"" + theModuleID + "\" or/and in-module action ID \"" + theInModuleActionID + "\" is/are invalid."); + return actionID; + } + + const auto itActionAssets = myActionAssets.find(actionID); + if (itActionAssets != myActionAssets.end() && !itActionAssets->second->myLangDependentAssets.empty()) { + const auto& ldaMap = itActionAssets->second->myLangDependentAssets; + if (ldaMap.empty()) + return theInModuleActionID; + + auto itLang = ldaMap.find(theLang.isEmpty() ? SUIT_ShortcutMgr::getLang() : theLang); + if (itLang == ldaMap.end()) + itLang = ldaMap.begin(); // Get name in any language. + + const auto& name = itLang->second.myName; + return name.isEmpty() ? theInModuleActionID : name; + } + else /* if action assets have not been loaded. */ { + // Try to get action->text() and use it as a name. + + // Pitfall of the approach: at the time this code block is called, the action may not exist. + // Moreover, an action with such an ID may not even have been created at the time of calling this method. + // Thus, even buffering of assets of every action ever created at runtime does not guarantee, + // that the assets will be available at any point in the life of the application, + // unless the assets are added to dedicated section in an asset file. + + const auto actions = getActions(theModuleID, theInModuleActionID); + for (const auto& action : actions) { + if (!action->text().isEmpty()) + return action->text(); + } + return theInModuleActionID; + } +} + +void SUIT_ShortcutMgr::onActionDestroyed(QObject* theObject) +{ + QAction* action = static_cast(theObject); + + auto itID = myActionIDs.find(action); + if (itID == myActionIDs.end()) + return; + + const QString& moduleID = itID->second.first; + const QString& inModuleActionID = itID->second.second; + + auto itModuleActions = myActions.find(moduleID); + if (itModuleActions != myActions.end()) { + std::map>& moduleActions = itModuleActions->second; + auto itActions = moduleActions.find(inModuleActionID); + if (itActions != moduleActions.end()) { + std::set& actions = itActions->second; + actions.erase(action); + } + } + + myActionIDs.erase(itID); +} + +bool SUIT_ShortcutMgr::eventFilter(QObject* theObject, QEvent* theEvent) +{ + if (theEvent) { + if (theEvent->type() == QEvent::ActionAdded) { + auto anActionEvent = static_cast(theEvent); + + QtxAction* aQtxAction = qobject_cast(anActionEvent->action()); + if (aQtxAction) { +#ifdef SHORTCUT_MGR_DBG + { + const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID()); + if (moduleIDAndActionID.second.isEmpty()) + ShCutDbg("ActionAdded event, but ID of the action is invalid. Action name = \"" + aQtxAction->toolTip() + "\", ID = \"" + aQtxAction->ID() + "\"."); + else if (!myShortcutContainer.hasShortcut(moduleIDAndActionID.first, moduleIDAndActionID.second)) + 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 + "\"."); + } +#endif//SHORTCUT_MGR_DBG +#ifdef SHORTCUT_MGR_DEVTOOLS + { + DevTools::get()->collectShortcutAndAssets(aQtxAction); + const auto moduleIDAndActionID = splitIntoModuleIDAndInModuleID(aQtxAction->ID()); + if (moduleIDAndActionID.second.isEmpty()) + DevTools::get()->collectAssetsOfActionWithInvalidID(aQtxAction); + } +#endif//SHORTCUT_MGR_DEVTOOLS + registerAction(aQtxAction); + } + else { + QAction* aQAction = qobject_cast(anActionEvent->action()); +#ifdef SHORTCUT_MGR_DEVTOOLS + 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); + } + } + } + } + + return QObject::eventFilter(theObject, theEvent); +} + +std::set> SUIT_ShortcutMgr::setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride) +{ + std::set> disabledShortcutsIDs = myShortcutContainer.setShortcut(theModuleID, theInModuleActionID, theKeySequence, theOverride); + + if (theOverride || disabledShortcutsIDs.empty()) { + // Bind actions to corresponding modified key sequences. Save changes to preferences. + + /** { moduleID, {inModuleActionID, keySequence}[] }[] */ + std::map> modifiedShortcuts; + + 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; + + const std::set actions = getActions(moduleID, inModuleActionID); + for (QAction* const action : actions) { + action->setShortcut(NO_KEYSEQUENCE); + } + } + + { // Bind actions to theKeySequence. + std::map& modifiedModuleShortcuts = modifiedShortcuts[theModuleID]; + modifiedModuleShortcuts[theInModuleActionID] = theKeySequence; + + const std::set actions = getActions(theModuleID, theInModuleActionID); + for (QAction* const action : actions) { + action->setShortcut(theKeySequence); + } + } + + SUIT_ShortcutMgr::saveShortcutsToPreferences(modifiedShortcuts); + } + + return disabledShortcutsIDs; +} + +void SUIT_ShortcutMgr::setShortcutsFromPreferences() +{ + ShCutDbg() && ShCutDbg("ShortcutMgr is initializing..."); + + SUIT_ShortcutContainer container; + SUIT_ShortcutMgr::fillContainerFromPreferences(container, false /*theDefaultOnly*/); + mergeShortcutContainer(container, true /*theOverrde*/, false /*theTreatAbsentIncomingAsDisabled*/); + setAssetsFromResources(); + + ShCutDbg() && ShCutDbg("ShortcutMgr has been initialized."); +} + +/*static*/ void SUIT_ShortcutMgr::saveShortcutsToPreferences(const std::map>& theShortcutsInversed) { - QtxAction* anAction = (QtxAction*)obj; - - if ( anAction ) - myShortcutActions.remove( anAction->shortcutActionName(), anAction ); + 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.toString()); + + ShCutDbg() && ShCutDbg("Saving shortcut: \"" + moduleID + "\"\t\"" + shortcutInversed.first + "\"\t\"" + shortcutInversed.second.toString() + "\""); + } + } } + +void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage) +{ + ShCutDbg() && ShCutDbg("Retrieving action assets."); + + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); + if (!resMgr) { + Warning("SUIT_ShortcutMgr can't retrieve resource manager!"); + return; + } + + if (theLanguage.isEmpty()) + theLanguage = resMgr->stringValue(LANG_SECTION, LANG_SECTION, DEFAULT_LANG); + + QStringList langPriorityList = LANG_PRIORITY_LIST; + langPriorityList.push_front(theLanguage); + langPriorityList.removeDuplicates(); + + QStringList actionAssetFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ASSET_FILE_PATHS); +#ifdef SHORTCUT_MGR_DBG + ShCutDbg("Asset files: " + actionAssetFilePaths.join(", ") + "."); +#endif + for (const QString& actionAssetFilePath : actionAssetFilePaths) { + const QString path = substituteVars(actionAssetFilePath); + QFile actionAssetFile(path); + if (!actionAssetFile.open(QIODevice::ReadOnly)) { + Warning("SUIT_ShortcutMgr can't open action asset file \"" + path + "\"!"); + continue; + } + + QJsonParseError jsonError; + QJsonDocument document = QJsonDocument::fromJson(actionAssetFile.readAll(), &jsonError); + actionAssetFile.close(); + if(jsonError.error != QJsonParseError::NoError) { + Warning("SUIT_ShortcutMgr: error during parsing of action asset file \"" + path + "\"!"); + continue; + } + + if(document.isObject()) { + QJsonObject object = document.object(); + SUIT_ActionAssets actionAssets; + for (const QString& actionID : object.keys()) { + if (!SUIT_ShortcutMgr::isActionIDValid(actionID)) { + ShCutDbg("Action asset file \"" + path + "\" contains invalid action ID \"" + actionID + "\"."); + continue; + } + + if (!actionAssets.fromJSON(object[actionID].toObject())) { + ShCutDbg("Action asset file \"" + path + "\" contains invalid action assets with ID \"" + actionID + "\"."); + continue; + } + + const bool nameInCurLangExists = actionAssets.myLangDependentAssets.find(theLanguage) != actionAssets.myLangDependentAssets.end(); + if (nameInCurLangExists) { + actionAssets.clearAllLangsExcept(theLanguage); + } + else { + bool nameInLinguaFrancaExists = false; + QString usedLanguage = QString(); + for (int i = 1; i < langPriorityList.length(); i++) { + nameInLinguaFrancaExists = actionAssets.myLangDependentAssets.find(langPriorityList[i]) != actionAssets.myLangDependentAssets.end(); + if (nameInLinguaFrancaExists) { + usedLanguage = langPriorityList[i]; + actionAssets.clearAllLangsExcept(usedLanguage); + break; + } + } + + #ifdef SHORTCUT_MGR_DBG + if (nameInLinguaFrancaExists) + ShCutDbg("Can't find assets for action with ID \"" + actionID + "\" at current (" + theLanguage + ") language. Assets in " + usedLanguage + " is used for the action." ); + else { + ShCutDbg("Can't find assets for action with ID \"" + actionID + "\". Tried " + langPriorityList.join(", ") + " languages." ); + continue; + } + #endif + } + + auto itAssets = myActionAssets.find(actionID); + if (itAssets == myActionAssets.end()) { + auto pAssets = std::shared_ptr(new SUIT_ActionAssets(actionAssets)); + itAssets = myActionAssets.emplace(actionID, pAssets).first; + } + else + itAssets->second->merge(actionAssets, true); + + const auto& assets = itAssets->second; + if (!assets->myIconPath.isEmpty() && assets->myIcon.isNull()) + assets->myIcon = QIcon(substituteVars(assets->myIconPath)); + } + } + } + + #ifdef SHORTCUT_MGR_DBG + ShCutDbg("Parsed assets: "); + QJsonObject object; + for (const auto& actionIDAndAssets : myActionAssets) { + actionIDAndAssets.second->toJSON(object); + QJsonDocument doc(object); + QString strJson = doc.toJson(QJsonDocument::Indented); + ShCutDbg(actionIDAndAssets.first + " : " + strJson); + } + #endif + + // Fill myModuleAssets. + for (const auto& moduleID : myShortcutContainer.getIDsOfAllModules()) { + const auto assets = std::shared_ptr(new SUIT_ActionAssets()); + auto& lda = assets->myLangDependentAssets[DEFAULT_LANG]; + + if (moduleID == ROOT_MODULE_ID) { + lda.myName = tr("General"); + + { // Load icon. + QString dirPath; + if (resMgr->value("resources", "LightApp", dirPath)) { + assets->myIconPath = dirPath + (!dirPath.isEmpty() && dirPath[dirPath.length() - 1] == "/" ? "" : "/") + "icon_default.png"; + assets->myIcon = QIcon(substituteVars(assets->myIconPath)); + } + } + } + else { + QString moduleName = moduleID; + resMgr->value(moduleID, "name", moduleName); + lda.myName = moduleName; + + resMgr->value(moduleID, "description", lda.myToolTip); + + { // Load icon. + QString dirPath; + QString fileName; + if (resMgr->value("resources", moduleID, dirPath) && resMgr->value(moduleID, "icon", fileName)) { + assets->myIconPath = dirPath + (!dirPath.isEmpty() && dirPath[dirPath.length() - 1] == "/" ? "" : "/") + fileName; + assets->myIcon = QIcon(substituteVars(assets->myIconPath)); + } + } + } + + myModuleAssets.emplace(moduleID, std::move(assets)); + } +} \ No newline at end of file diff --git a/src/SUIT/SUIT_ShortcutMgr.h b/src/SUIT/SUIT_ShortcutMgr.h index c86363f69..c8debfb81 100644 --- a/src/SUIT/SUIT_ShortcutMgr.h +++ b/src/SUIT/SUIT_ShortcutMgr.h @@ -26,46 +26,447 @@ #include "SUIT.h" #include -#include +#include +#include +#include +#include +#include +#include +class QAction; class QtxAction; - class QKeySequence; +class QJsonObject; #if defined WIN32 #pragma warning( disable: 4251 ) #endif +// Define SHORTCUT_MGR_DBG to enable SUIT_ShortcutMgr debug logging. +// #define SHORTCUT_MGR_DBG +/*! \returns true, if SUIT_ShortcutMgr debug logging is enabled. */ +SUIT_EXPORT extern inline bool ShCutDbg() { +#ifdef SHORTCUT_MGR_DBG + return true; +#else + return false; +#endif +} +/*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */ +SUIT_EXPORT extern bool ShCutDbg(const QString& theString); +/*! \brief Prints theString to std::wcout, if SUIT_ShortcutMgr debug logging is enabled. */ +SUIT_EXPORT extern bool ShCutDbg(const char* theString); + + +/*! + \class SUIT_ShortcutContainer + \brief Provides means to keep and edit shortcuts in compliance with the application logics. + \ref See SUIT_ShortcutMgr for details. +*/ +class SUIT_EXPORT SUIT_ShortcutContainer +{ +public: + SUIT_ShortcutContainer(); + + /*! \returns IDs of modules, which interfere with the module: + if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */ + std::set getIDsOfInterferingModules(const QString& theModuleID) const; + + std::set getIDsOfAllModules() const; + + /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts. + Redefining a key sequence for the action, if theKeySequence does not conflict with other shortcuts, is not considered as a conflict. + \param theModuleID The method has no effect if theModuleID is invalid. \ref See SUIT_ShortcutMgr::isModuleIDValid(const QString&) for details. + \param theInModuleActionID The method has no effect if theInModuleActionID is invalid. \ref See SUIT_ShortcutMgr::isInModuleActionIDValid(const QString&). + If theInModuleActionID is meta-action ID, the shortcut is set to root module, and theModuleID is ignored. + \param theKeySequence Empty theKeySequence does not cause conflicts, in this case + a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence. + \param theOverride If true, conflicting shortcuts are disabled. + \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false, + otherwise set of actions (without incoming one), whose shortcuts have been disabled. */ + std::set> setShortcut( + QString theModuleID, + const QString& theInModuleActionID, + const QKeySequence& theKeySequence, + bool theOverride + ); + + /*! \brief Checks for conflicts. Existence of a shortcut with another key sequence for the action, + if theKeySequence does not conflict with other shortcuts, is not considered as a conflict. + \param theInModuleActionID If theInModuleActionID is meta-action ID, the shortcut is looked for in root module, and theModuleID is ignored. + \param theKeySequence Empty theKeySequence does not have conflicts. + \returns {moduleID, inModuleActionID}[] - Set of conflicting actions. */ + std::set> getConflicts( + QString theModuleID, + const QString& theInModuleActionID, + const QKeySequence& theKeySequence + ) const; + + /*! \returns empty key sequence if shortcut for the action is not set. + \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/ + const QKeySequence& getKeySequence(QString theModuleID, const QString& theInModuleActionID) const; + + /*! \returns true, if shortcut for the action is set (even if the mapped key sequence is empty). + \param theInModuleActionID If theInModuleActionID is meta-action ID, seeks in root module, and theModuleID is ignored.*/ + bool hasShortcut(QString theModuleID, const QString& theInModuleActionID) const; + + /*! \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */ + const std::map& getModuleShortcutsInversed(const QString& theModuleID) const; + + /*! \brief Seeks for shortcuts in the module with in-module action IDs, which start with theInModuleActionIDPrefix. + \returns {inModuleActionID, keySequence}[] - If the module was not added, the map is empty. */ + const std::map getModuleShortcutsInversed(const QString& theModuleID, const QString& theInModuleActionIDPrefix) const; + + /*! \brief Merges shortcuts of theOther into this. + \param theOverride if true, overrides conflicting shortcuts. + If false, and this has no shortcut for an incoming action, and the incoming shortcut conflicts + with an existing shortcut, disabled shortcut for the incoming action is set. + \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( + const SUIT_ShortcutContainer& theOther, + bool theOverride, + bool theTreatAbsentIncomingAsDisabled = false + ); + + /*! \brief Generates human-readable text representation of content. */ + QString toString() const; + +private: + /** { moduleID, { keySequence, inModuleActionID }[] }[]. keySequence can not be empty. + * Can not contain entries like { , { keySequence, } }. */ + std::map> myShortcuts; + + /** { moduleID, { inModuleActionID, keySequence }[] }[]. keySequence can be empty. + * Can not contain entries like { , { , keySequence } }. */ + std::map> myShortcutsInversed; +}; + + +/*! \brief GUI-related assets. */ +struct SUIT_EXPORT SUIT_ActionAssets +{ + struct LangDependentAssets + { + static const QString PROP_ID_NAME; + static const QString PROP_ID_TOOLTIP; + + bool fromJSON(const QJsonObject& theJsonObject); + void toJSON(QJsonObject& oJsonObject) const; + + QString myName; + QString myToolTip; + }; + + static const QString STRUCT_ID; + static const QString PROP_ID_LANG_DEPENDENT_ASSETS; + static const QString PROP_ID_ICON_PATH; + + bool fromJSON(const QJsonObject& theJsonObject); + void toJSON(QJsonObject& oJsonObject) const; + QString toString() const; + + QStringList getLangs() const; + void clearAllLangsExcept(const QString& theLang); + + /*! \param theOverride If true, values of theOther override conflicting values of this. */ + void merge(const SUIT_ActionAssets& theOther, bool theOverride); + + std::map myLangDependentAssets; + QString myIconPath; + + /*! Is not serialized. */ + QIcon myIcon; +}; + + /*! \class SUIT_ShortcutMgr - \brief Class which manages shortcuts customization. + \brief Handles action shortcut customization. + + 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. + Action IDs are also used to (de)serialize shortcut settings. + Several QActions may be registered under the same ID. + + Most of actions are intercepted on creation in SUIT_ShortcutMgr:eventFilter(QObject* theObject, QEvent* theEvent). + If an intercepted action is instance of QtxAction, it is registered automatically. + Since non-QtxActions have no member ID(), SUIT_ShortcutMgr is unable to register them automatically + in SUIT_ShortcutMgr::eventFilter(). Thus, every non-QtxAction should be + passed to SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAction). + + Action ID is application-unique must be composed as /. + If an action belongs to a desktop or is available even if no module is active (e.g. 'Save As'), + use empty string as . Let's call such actions as root actions. + + There is a need to keep multiple actions, which do the same from user' perspective, + bound to the same key sequence. E.g. 'Front view'. Let's call such set of actions as meta-action. + Actions of a meta-action may belong to different modules, and/or there may be several actions + of the same meta-action in the same module. of all members of a meta-action + must be the same and start with "#". + Meta-action is root action when it comes to checking for conflicts. + Shortcuts of meta-actions are (de)serialized to the same section of preference files as root shortcuts. + + can contain several "/". Go to \ref isInModuleActionIDValid(const QString&) for details. + You can refer to multiple actions, whose starts with the prefix. + + 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. + + 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, + if the key sequence does not conflict with shortcuts kept by SUIT_ShortcutMgr, + 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. */ -class SUIT_EXPORT SUIT_ShortcutMgr: public QObject +class SUIT_EXPORT SUIT_ShortcutMgr: public QObject { Q_OBJECT -public: - static void Init(); - static SUIT_ShortcutMgr* getShortcutMgr(); - void setSectionEnabled( const QString&, const bool = true ); - void updateShortcuts(); +private: + SUIT_ShortcutMgr(); + SUIT_ShortcutMgr(const SUIT_ShortcutMgr&) = delete; + SUIT_ShortcutMgr& operator=(const SUIT_ShortcutMgr&) = delete; protected: - SUIT_ShortcutMgr(); virtual ~SUIT_ShortcutMgr(); +public: + /*! \brief Create new singleton-instance of shortcut manager, if it has not been created. */ + static void Init(); + + static SUIT_ShortcutMgr* get(); + + /*! \brief Checks whether the theKeySequence is platform-compatible. */ + static bool isKeySequenceValid(const QKeySequence& theKeySequence); + + /*! \returns {false, _ } if theKeySequenceString is invalid. */ + static std::pair toKeySequenceIfValid(const QString& theKeySequenceString); + + /*! \brief Valid module ID does not contain "/" and equals to result of QString(theModuleID).simplified(). + Empty module ID is valid - it is root module ID. */ + static bool isModuleIDValid(const QString& theModuleID); + + /*! \brief Valid in-module action ID may consist of several tokens, separated by "/": + /.../. + Each must be non-empty and be equal to QString().simplified(). + Empty or "#" in-module action ID is not valid. */ + static bool isInModuleActionIDValid(const QString& theInModuleActionID); + + /*! \returns true, is theInModuleActionID starts with "#". */ + static bool isInModuleMetaActionID(const QString& theInModuleActionID); + + /*! \brief Extracts module ID and in-module action ID from application-unique action ID. + The theActionID must be composed as /. + \returns { _ , "" }, if theActionID is invalid. */ + static std::pair splitIntoModuleIDAndInModuleID(const QString& theActionID); + + /*! See \ref splitIntoModuleIDAndInModuleID(const QString&). */ + static bool isActionIDValid(const QString& theActionID); + + /*! \brief Creates application-unique action ID. Reverse to splitIntoModuleIDAndInModuleID. + \returns Emppty string, if either theModuleID or theInModuleActionID is invalid*/ + static QString makeActionID(const QString& theModuleID, const QString& theInModuleActionID); + + /*! \brief Sets all shortcuts from preferences to theContainer. Incoming shortcuts override existing ones. + If the container has shortcut for an action, which is absent in preferences, and the existing shortcut + does not conflict with incoming ones, it is untouched. + See \ref setShortcutsFromPreferences() for details. + \param theDefaultOnly If true, user preferences are ignored and only default preferences are used. */ + static void fillContainerFromPreferences(SUIT_ShortcutContainer& theContainer, bool theDefaultOnly); + + /*! \brief Checks the resource manager directly. + \returns {assetsExist, assets}. */ + static std::pair getActionAssetsFromResources(const QString& theActionID); + + /*! \returns Language being set in resource manager. */ + static QString getLang(); + + + /*! \brief Add theAction to map of managed actions. */ + void registerAction(const QString& theActionID, QAction* theAction); + + /*! \brief Add theAction to map of managed actions. QtxAction::ID() is used as action ID. */ + void registerAction(QtxAction* theAction); + + /*! \brief Get registered actions. If theInModuleActionID is meta-action ID, seeks in all modules. */ + std::set getActions(const QString& theModuleID, const QString& theInModuleActionID) const; + + /*! \brief Get module ID and in-module-ID of theAction. + \returns { _ , "" } if theAction is not registered. */ + std::pair getModuleIDAndInModuleID(const QAction* theAction) const; + + /*! Returns true if theAction is registered. */ + bool hasAction(const QAction* theAction) const; + + /*! \brief Get action ID of theActon. + \returns empty string if theAction is not registered. */ + QString getActionID(const QAction* theAction) const; + + /*! \brief Enables/disable actions of the module. + Only those actions are affected, whose parent widget is active desktop. */ + void setActionsOfModuleEnabled(const QString& theModuleID, const 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; + + [[deprecated("Use rebindActionsToKeySequences() instead.")]] + void updateShortcuts() const; + + /*! \brief Checks for conflicts. If theOverride, modifies incoming and disables all conflicting shortcuts. + Then binds key sequences with corresponding registered actions. Saves changes to preferences. + + Redefining a key sequence for the action, if the key sequence does not conflict with other shortcuts, is not considered as a conflict. + \param theInModuleActionID The method has no effect if theInModuleActionID is empty. + \param theKeySequence Empty theKeySequence does not cause conflicts, in this case + a shortcut for the action is disabled: theInModuleActionID is added/retained in the container but mapped to empty key sequence. + \param theOverride If true, conflicting shortcuts are disabled. + \returns {moduleID, inModuleActionID}[] - Set of conflicting actions if theOverride = false, + otherwise set of actions (without incoming one), whose shortcuts have been disabled. */ + std::set> setShortcut(const QString& theActionID, const QKeySequence& theKeySequence, bool theOverride = false); + std::set> setShortcut(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride = false); + + const SUIT_ShortcutContainer& getShortcutContainer() const; + + /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */ + 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; + + /*! \returns {inModuleActionID, keySequence}[] */ + const std::map& getModuleShortcutsInversed(const QString& theModuleID) const; + + /*! \returns All module IDs, which were added to myShortcutContainer. */ + std::set getShortcutModuleIDs() const; + + /*! \returns IDs of modules, which interfere with the module: + if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */ + std::set getIDsOfInterferingModules(const QString& theModuleID) const; + + std::shared_ptr getModuleAssets(const QString& theModuleID) const; + + /*! \brief Retrieves module name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */ + QString getModuleName(const QString& theModuleID, const QString& theLang = "") const; + + std::shared_ptr getActionAssets(const QString& theModuleID, const QString& theInModuleActionID) const; + + std::shared_ptr getActionAssets(const QString& theActionID) const; + + /*! \brief Retrieves action name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */ + QString getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang = "") const; + private slots: - void onActionDestroyed( QObject* ); + /*! + \brief Called when the corresponding action is destroyed. + Removes destroyed action from maps of registered actions. Preserves shortcut. + \param theObject action being destroyed. + */ + void onActionDestroyed(QObject* theObject); private: - virtual bool eventFilter( QObject* o, QEvent* e ); + /*! \brief Overrides QObject::eventFilter(). + If theEvent is QEvent::ActionAdded and the action is instance of QtxAction, registers it. */ + virtual bool eventFilter(QObject* theObject, QEvent* theEvent); + + /*! \brief Does not perform validity checks on theModuleID and theInModuleActionID. */ + std::set> setShortcutNoIDChecks(const QString& theModuleID, const QString& theInModuleActionID, const QKeySequence& theKeySequence, bool theOverride); - void processAction( QtxAction* ); - QKeySequence getShortcutByActionName( const QString& ) const; + /*! \brief Set shortcuts from preference files. The method is intended to be called before any calls to setShortcut() or mergeShortcutContainer(). + Definition of this method assumes, that shortcut settings are serialized as prerefence entries {name=, val=} + in dedicated section for each module, with names of sections being composed as "shortcuts:". + + E.g. in case of XML file it may look like this: + +
+ + + + + + +
+
+ + + + +
+ + Empty inModuleActionIDs are ignored. + + nb! For any theQtxAction you wish user be able to assign it to a shortcut, + add theQtxAction.ID() to default resource files (you can map it to no key sequence).*/ + void setShortcutsFromPreferences(); + + /*! \brief Writes shortcuts to preference files. + \param theShortcuts { moduleID, { inModuleActionID, keySequence }[] }[]. Empty inModuleActionIDs 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. + + Asset files must be structured like this: + { + ... + actionID : { + "langDependentAssets": { + ... + lang: { + "name": name, + "tooltip": tooltip + }, + ... + }, + "iconPath": iconPath + }, + ... + } + */ + void setAssetsFromResources(QString theLanguage = QString()); private: static SUIT_ShortcutMgr* myShortcutMgr; - QMultiMap myShortcutActions; + + /** { moduleID, { inModuleActionID, action[] }[] }[]. May contain entries like { , { , actions[] } }. */ + std::map>> myActions; + + /** { action, { moduleID, inModuleActionID } }[]. May contain entries like { , { , actions[] } }. */ + std::map> myActionIDs; // To maintain uniqueness of actions and effectively retrieve IDs of registered actions. + + /** Can not contain entries like { , { , actions[] } }. */ + SUIT_ShortcutContainer myShortcutContainer; + + /* nb! + Sets of moduleIDs and inModuleActionIDs are equal for myActions and myActionIDs. + Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer. + */ + + /* {actionID, assets}[] */ + std::map> myActionAssets; + + /* {moduleID, assets}[] */ + mutable std::map> myModuleAssets; }; #if defined WIN32 diff --git a/src/SUIT/resources/SUIT_msg_fr.ts b/src/SUIT/resources/SUIT_msg_fr.ts index 97f2cf33d..bdbbb196e 100644 --- a/src/SUIT/resources/SUIT_msg_fr.ts +++ b/src/SUIT/resources/SUIT_msg_fr.ts @@ -170,5 +170,32 @@ Voulez-vous l'écraser ? TLT_IMAGE_FILES Fichiers images (*.bmp *.png *.jpg *.jpeg)
- + + + SUIT_ShortcutMgr + + General + Général + + + Invalid module IDs + ID de module invalides + + + Invalid shortcuts + Raccourcis invalides + + + These shortcuts have not been set, because they conflict with previously parsed ones + Ces raccourcis n'ont pas été définis car ils entrent en conflit avec ceux analysés précédemment + + + Invalid shortcuts in preferences + Raccourcis invalides dans les préférence + + + Fix the following entries in the preference files manually + Corrigez manuellement les entrées suivantes dans les fichiers de préférences + + diff --git a/src/SUIT/resources/SUIT_msg_ja.ts b/src/SUIT/resources/SUIT_msg_ja.ts index 8da7266db..2fb333637 100644 --- a/src/SUIT/resources/SUIT_msg_ja.ts +++ b/src/SUIT/resources/SUIT_msg_ja.ts @@ -164,5 +164,32 @@ TLT_IMAGE_FILES イメージ (*.bmp *.png *.jpg *.jpeg) ファイル
- + + + SUIT_ShortcutMgr + + General + 一般的な + + + Invalid module IDs + 無効なモジュール ID + + + Invalid shortcuts + 無効なショートカットです + + + These shortcuts have not been set, because they conflict with previously parsed ones + これらのショートカットは、以前に解析されたショートカットと競合するため、設定されていません + + + Invalid shortcuts in preferences + 環境設定のショートカットが無効です + + + Fix the following entries in the preference files manually + 設定ファイル内の次のエントリを手動で修正します + + diff --git a/src/SUIT/resources/action_assets.json b/src/SUIT/resources/action_assets.json new file mode 100644 index 000000000..eefb7012c --- /dev/null +++ b/src/SUIT/resources/action_assets.json @@ -0,0 +1,699 @@ +{ + "/#TOT_DESK_EDIT_COPY": { + "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/copy.png", + "langDependentAssets": { + "en": { + "name": "Copy", + "tooltip": "Copy the selection to the Clipboard" + }, + "fr": { + "name": "Copier", + "tooltip": "Copier la sélection dans le presse-papiers" + }, + "ja": { + "name": "コピー", + "tooltip": "選択範囲をクリップボードにコピー" + } + } + }, + "/#Viewers/View/Reset": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Reset", + "tooltip": "Reset View Point" + }, + "fr": { + "name": "Restaurer", + "tooltip": "Restaurer le point de vue" + }, + "ja": { + "name": "復元", + "tooltip": "ビューのポイントを復元します。" + } + } + }, + "/#Viewers/View/Rotate anticlockwise": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Rotate counterclockwise", + "tooltip": "Rotate view counterclockwise" + }, + "fr": { + "name": "Tourner la vue à gauche", + "tooltip": "Tourner la vue à gauche" + }, + "ja": { + "name": "表示を左に", + "tooltip": "表示を左に" + } + } + }, + "/#Viewers/View/Rotate clockwise": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Rotate clockwise", + "tooltip": "Rotate View Clockwise" + }, + "fr": { + "name": "Tourner la vue à droite", + "tooltip": "Tourner la vue à droite" + }, + "ja": { + "name": "右のビューを回転させる", + "tooltip": "右のビューを回転させる" + } + } + }, + "/#Viewers/View/Set X+": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "+OX", + "tooltip": "+OX View" + }, + "fr": { + "name": "+OX", + "tooltip": "Vue +OX" + }, + "ja": { + "name": "+OX", + "tooltip": "+OX View" + } + } + }, + "/#Viewers/View/Set X-": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "-OX", + "tooltip": "-OX View" + }, + "fr": { + "name": "-OX", + "tooltip": "Vue -OX" + }, + "ja": { + "name": "-OX", + "tooltip": "-OX View" + } + } + }, + "/#Viewers/View/Set Y+": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "+OY", + "tooltip": "+OY View" + }, + "fr": { + "name": "+OY", + "tooltip": "Vue +OY" + }, + "ja": { + "name": "+OY", + "tooltip": "+OY View" + } + } + }, + "/#Viewers/View/Set Y-": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "-OY", + "tooltip": "-OY View" + }, + "fr": { + "name": "-OY", + "tooltip": "Vue -OY" + }, + "ja": { + "name": "-OY", + "tooltip": "-OY View" + } + } + }, + "/#Viewers/View/Set Z+": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "+OZ", + "tooltip": "+OZ View" + }, + "fr": { + "name": "+OZ", + "tooltip": "Vue +OZ" + }, + "ja": { + "name": "+OZ", + "tooltip": "+OZ View" + } + } + }, + "/#Viewers/View/Set Z-": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "-OZ", + "tooltip": "-OZ View" + }, + "fr": { + "name": "-OZ", + "tooltip": "Vue -OZ" + }, + "ja": { + "name": "-OZ", + "tooltip": "-OZ View" + } + } + }, + "/#General/Object(s)/Hide": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Hide", + "tooltip": "Hide" + }, + "fr": { + "name": "Cacher", + "tooltip": "Cacher" + }, + "ja": { + "name": "非表示", + "tooltip": "非表示" + } + } + }, + "/#General/Object(s)/Show": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Show", + "tooltip": "Show" + }, + "fr": { + "name": "Afficher", + "tooltip": "Afficher" + }, + "ja": { + "name": "表示", + "tooltip": "表示" + } + } + }, + "/PRP_CLOSE": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Close", + "tooltip": "Close active window" + }, + "fr": { + "name": "Fermer", + "tooltip": "Fermer la fenêtre active" + }, + "ja": { + "name": "閉じる", + "tooltip": "アクティブ ウィンドウを閉じる" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_0": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "GL 2D view", + "tooltip": "Create new GL 2D view" + }, + "fr": { + "name": "Scène GL ", + "tooltip": "Créer une nouvelle Scène GL " + }, + "ja": { + "name": "GL 2D view", + "tooltip": "新しい GL 2D view を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_1": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Plot 2D view", + "tooltip": "Create new Plot 2D view" + }, + "fr": { + "name": "Scène Plot2d ", + "tooltip": "Créer une nouvelle Scène Plot2d " + }, + "ja": { + "name": "Plot 2D View", + "tooltip": "新しい Plot 2D View を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_2": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "OCC 3D view", + "tooltip": "Create new OCC 3D view" + }, + "fr": { + "name": "Scène OCC", + "tooltip": "Créer une nouvelle Scène OCC" + }, + "ja": { + "name": "OCC 3D View", + "tooltip": "新しい OCC 3D View を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_3": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "VTK 3D view", + "tooltip": "Create new VTK 3D view" + }, + "fr": { + "name": "Scène VTK", + "tooltip": "Créer une nouvelle Scène VTK" + }, + "ja": { + "name": "VTK 3D View", + "tooltip": "新しい VTK 3D View を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_4": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "QxScene 2D view", + "tooltip": "Create new QxScene 2D view" + }, + "fr": { + "name": "Scène QxScene", + "tooltip": "Créer une nouvelle Scène QxScene" + }, + "ja": { + "name": "シーン QxScene", + "tooltip": "新しい シーン QxScene を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_5": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Graphics view", + "tooltip": "Create new Graphics view" + }, + "fr": { + "name": "Scène Graphiques", + "tooltip": "Créer une nouvelle Scène Graphiques" + }, + "ja": { + "name": "グラフィックの表示", + "tooltip": "新しい グラフィックの表示 を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_6": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "ParaView view", + "tooltip": "Create new ParaView view" + }, + "fr": { + "name": "Scène ParaView", + "tooltip": "Créer une nouvelle Scène ParaView" + }, + "ja": { + "name": "ParaView 表示 ", + "tooltip": "新しい ParaView 表示 を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_7": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Python view", + "tooltip": "Create new Python view" + }, + "fr": { + "name": "Vue Python", + "tooltip": "Créer une nouvelle Vue Python" + }, + "ja": { + "name": "Python view", + "tooltip": "新しい Python view を作成します。" + } + } + }, + "/PRP_CREATE_NEW_WINDOW_FOR_VIEWER_8": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "ParaView3D view", + "tooltip": "Create new ParaView3D view" + }, + "fr": { + "name": "ParaView3D view", + "tooltip": "Créer une nouvelle ParaView3D view" + }, + "ja": { + "name": "ParaView3D view", + "tooltip": "新しい ParaView3D view を作成します。" + } + } + }, + "/PRP_DESK_CATALOG_GENERATOR": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Catalog Generator", + "tooltip": "Generates XML catalog of a component's interface" + }, + "fr": { + "name": "Genérateur de catalogue", + "tooltip": "Génére un catalogue XML de l'interface du composant" + }, + "ja": { + "name": "カタログ ジェネレーター", + "tooltip": "コンポーネントインターフェイスのXMLカタログを生成" + } + } + }, + "/PRP_DESK_CONNECT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Connect", + "tooltip": "Connect active study" + }, + "fr": { + "name": "Connecter", + "tooltip": "Connecter l'étude en cours" + }, + "ja": { + "name": "接続", + "tooltip": "アクティブスタディの接続" + } + } + }, + "/PRP_DESK_DISCONNECT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Disconnect", + "tooltip": "Disconnect the current study" + }, + "fr": { + "name": "Déconnecter", + "tooltip": "Déconnecter l'étude en cours" + }, + "ja": { + "name": "切断", + "tooltip": "カレントスタディの切断" + } + } + }, + "/PRP_DESK_FILE_DUMP_STUDY": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Dump Study...", + "tooltip": "Dumps study to the python script" + }, + "fr": { + "name": "Générer le script de l'étude...", + "tooltip": "Génère le script python de l'étude" + }, + "ja": { + "name": "スクリプトを保存", + "tooltip": "Pythonスクリプトにスタディをダンプする" + } + } + }, + "/PRP_DESK_FILE_LOAD_SCRIPT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Load Script...", + "tooltip": "Loads python script from file" + }, + "fr": { + "name": "Exécuter un script...", + "tooltip": "Exécute un script Python à partir d'un fichier" + }, + "ja": { + "name": "スクリプトを読込み...", + "tooltip": "ファイルからPythonスクリプトを読込み" + } + } + }, + "/PRP_DESK_HELP_ABOUT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "About...", + "tooltip": "Shows 'About' dialog" + }, + "fr": { + "name": "A propos de...", + "tooltip": "Montre la boîte de dialogue 'A propos'" + }, + "ja": { + "name": "バージョン情報...", + "tooltip": "ソフト情報の表示" + } + } + }, + "/PRP_DESK_PREFERENCES": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Preferences...", + "tooltip": "Allow to change the preferences" + }, + "fr": { + "name": "Préférences...", + "tooltip": "Permettre de changer les préférences" + }, + "ja": { + "name": "環境設定...", + "tooltip": "設定を変更することができます。" + } + } + }, + "/PRP_DESK_VIEW_STATUSBAR": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Status Bar", + "tooltip": "Toggles status bar view on/off" + }, + "fr": { + "name": "Barre de status", + "tooltip": "Activer ou désactiver la barre de status" + }, + "ja": { + "name": "ステータス バー", + "tooltip": "ステータスバーの有効/無効" + } + } + }, + "/PRP_DESK_WINDOW_HSPLIT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Split Horizontally", + "tooltip": "Splits the active window on two horizontal parts" + }, + "fr": { + "name": "Séparation horizontale", + "tooltip": "Diviser la fenêtre actuelle en deux parties horizontales" + }, + "ja": { + "name": "水平分割", + "tooltip": "現在のウィンドウを 2つに水平分割" + } + } + }, + "/PRP_DESK_WINDOW_VSPLIT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Split Vertically", + "tooltip": "Splits the active window on two vertical parts" + }, + "fr": { + "name": "Séparation verticale", + "tooltip": "Diviser la fenêtre actuelle en deux parties verticales" + }, + "ja": { + "name": "垂直分割", + "tooltip": "現在のウィンドウを2つに上下分割" + } + } + }, + "/PRP_FULLSCREEN": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Full screen", + "tooltip": "Switch to full screen mode" + }, + "fr": { + "name": "Plein écran", + "tooltip": "Basculer en mode plein écran" + }, + "ja": { + "name": "全画面表示", + "tooltip": "全画面表示モードに切り替え" + } + } + }, + "/PRP_RENAME": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Rename", + "tooltip": "Rename active window" + }, + "fr": { + "name": "Renommer", + "tooltip": "Renommer la fenêtre active" + }, + "ja": { + "name": "名前変更", + "tooltip": "アクティブなウィンドウの名前を変更" + } + } + }, + "/TOT_DESK_EDIT_PASTE": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Paste", + "tooltip": "Inserts the Clipboard content at the insertion point" + }, + "fr": { + "name": "Coller", + "tooltip": "Insérer le contenu du presse-papiers au point d'insertion" + }, + "ja": { + "name": "貼り付け", + "tooltip": "クリップボードの内容を挿入" + } + } + }, + "/TOT_DESK_FILE_CLOSE": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Close", + "tooltip": "Closes the active document" + }, + "fr": { + "name": "Fermer", + "tooltip": "Ferme le document actuel" + }, + "ja": { + "name": "閉じる", + "tooltip": "現在のドキュメントを閉じる" + } + } + }, + "/TOT_DESK_FILE_EXIT": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Exit", + "tooltip": "Exits the application" + }, + "fr": { + "name": "Quitter", + "tooltip": "Quitte l'application" + }, + "ja": { + "name": "終了", + "tooltip": "アプリケーションを終了" + } + } + }, + "/TOT_DESK_FILE_NEW": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "New", + "tooltip": "Create a new document" + }, + "fr": { + "name": "Nouveau", + "tooltip": "Créer une nouvelle étude" + }, + "ja": { + "name": "新規作成", + "tooltip": "新しいドキュメントを作成" + } + } + }, + "/TOT_DESK_FILE_OPEN": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Open...", + "tooltip": "Open an existing document" + }, + "fr": { + "name": "Ouvrir...", + "tooltip": "Ouvre une étude existant" + }, + "ja": { + "name": "開く...", + "tooltip": "既存のドキュメントを開く" + } + } + }, + "/TOT_DESK_FILE_SAVE": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Save", + "tooltip": "Save the active document" + }, + "fr": { + "name": "Enregistrer", + "tooltip": "Sauvegarder l'étude actuelle" + }, + "ja": { + "name": "保存", + "tooltip": "現在のドキュメントを保存" + } + } + }, + "/TOT_DESK_FILE_SAVEAS": { + "iconPath": "", + "langDependentAssets": { + "en": { + "name": "Save As...", + "tooltip": "Saves the active document with a new name" + }, + "fr": { + "name": "Enregistrer sous...", + "tooltip": "Sauvegarder le document actuel sous un nouveau nom" + }, + "ja": { + "name": "別名保存...", + "tooltip": "現在のドキュメントを新しい名前で保存" + } + } + } +} \ No newline at end of file diff --git a/src/SVTK/SVTK_ViewWindow.cxx b/src/SVTK/SVTK_ViewWindow.cxx index 796da66e5..74412fa3b 100644 --- a/src/SVTK/SVTK_ViewWindow.cxx +++ b/src/SVTK/SVTK_ViewWindow.cxx @@ -129,7 +129,7 @@ namespace SVTK case SUIT_Accel::RotateLeft : return SVTK::RotateLeftEvent; case SUIT_Accel::RotateRight : return SVTK::RotateRightEvent; case SUIT_Accel::RotateUp : return SVTK::RotateUpEvent; - case SUIT_Accel::RotateDown : return SVTK::RotateDownEvent; + case SUIT_Accel::RotateDown : return SVTK::RotateDownEvent; } return accelAction; } @@ -208,21 +208,21 @@ void SVTK_ViewWindow::Initialize(SVTK_ViewModelBase* theModel) ( getAction( ChangeRotationPointId ), this, "SVTK_SetRotationPointDlg" ); myViewParameterDlg = new SVTK_ViewParameterDlg ( getAction( ViewParametersId ), this, "SVTK_ViewParameterDlg" ); - + myDefaultInteractorStyle = SVTK_InteractorStyle::New(); myInteractor->PushInteractorStyle(myDefaultInteractorStyle); myDefaultInteractorStyle->Delete(); - + myRecorder = SVTK_Recorder::New(); - + myRecorder->SetNbFPS( 17.3 ); myRecorder->SetQuality( 100 ); myRecorder->SetProgressiveMode( true ); myRecorder->SetUseSkippedFrames( true ); myRecorder->SetRenderWindow( myInteractor->getRenderWindow() ); - + setCentralWidget(myInteractor); - + myAxesWidget = salomevtk::vtkPVAxesWidget::New(); myAxesWidget->SetParentRenderer(aRenderer->GetDevice()); myAxesWidget->SetViewport(0, 0, 0.25, 0.25); @@ -306,9 +306,9 @@ SVTK_ViewWindow::~SVTK_ViewWindow() /*! \return corresponding view */ -SVTK_View* SVTK_ViewWindow::getView() -{ - return myView; +SVTK_View* SVTK_ViewWindow::getView() +{ + return myView; } /*! @@ -355,8 +355,8 @@ SVTK_Renderer* SVTK_ViewWindow::GetRenderer() const \return corresponding vtk selector */ SVTK_Selector* SVTK_ViewWindow::GetSelector() const -{ - return GetInteractor()->GetSelector(); +{ + return GetInteractor()->GetSelector(); } /*! @@ -507,7 +507,7 @@ Selection_Mode SVTK_ViewWindow::SelectionMode() const /*! Unhilights all objects in viewer */ -void SVTK_ViewWindow::unHighlightAll() +void SVTK_ViewWindow::unHighlightAll() { myView->unHighlightAll(); } @@ -518,9 +518,9 @@ void SVTK_ViewWindow::unHighlightAll() \param theIsHighlight - if it is true, object will be hilighted, otherwise it will be unhilighted \param theIsUpdate - update current viewer */ -void SVTK_ViewWindow::highlight(const Handle(SALOME_InteractiveObject)& theIO, - bool theIsHighlight, - bool theIsUpdate ) +void SVTK_ViewWindow::highlight(const Handle(SALOME_InteractiveObject)& theIO, + bool theIsHighlight, + bool theIsUpdate ) { myView->highlight( theIO, theIsHighlight, theIsUpdate ); } @@ -529,7 +529,7 @@ void SVTK_ViewWindow::highlight(const Handle(SALOME_InteractiveObject)& theIO, \return true if object is in viewer or in collector \param theIO - object to be checked */ -bool SVTK_ViewWindow::isInViewer( const Handle(SALOME_InteractiveObject)& theIO ) +bool SVTK_ViewWindow::isInViewer( const Handle(SALOME_InteractiveObject)& theIO ) { return myView->isInViewer( theIO ); } @@ -538,7 +538,7 @@ bool SVTK_ViewWindow::isInViewer( const Handle(SALOME_InteractiveObject)& theIO \return true if object is displayed in viewer \param theIO - object to be checked */ -bool SVTK_ViewWindow::isVisible( const Handle(SALOME_InteractiveObject)& theIO ) +bool SVTK_ViewWindow::isVisible( const Handle(SALOME_InteractiveObject)& theIO ) { return myView->isVisible( theIO ); } @@ -547,7 +547,7 @@ bool SVTK_ViewWindow::isVisible( const Handle(SALOME_InteractiveObject)& theIO ) Display object \param theEntry - entry that corresponds to intractive objects */ -Handle(SALOME_InteractiveObject) SVTK_ViewWindow::FindIObject(const char* theEntry) +Handle(SALOME_InteractiveObject) SVTK_ViewWindow::FindIObject(const char* theEntry) { return myView->FindIObject(theEntry); } @@ -558,7 +558,7 @@ Handle(SALOME_InteractiveObject) SVTK_ViewWindow::FindIObject(const char* theEnt \param theImmediatly - update viewer */ void SVTK_ViewWindow::Display(const Handle(SALOME_InteractiveObject)& theIO, - bool theImmediatly) + bool theImmediatly) { myView->Display(theIO,theImmediatly); } @@ -569,7 +569,7 @@ void SVTK_ViewWindow::Display(const Handle(SALOME_InteractiveObject)& theIO, \param theImmediatly - update viewer */ void SVTK_ViewWindow::Erase(const Handle(SALOME_InteractiveObject)& theIO, - bool theImmediatly) + bool theImmediatly) { myView->Erase(theIO,theImmediatly); } @@ -578,7 +578,7 @@ void SVTK_ViewWindow::Erase(const Handle(SALOME_InteractiveObject)& theIO, Display only passed object \param theIO - object */ -void SVTK_ViewWindow::DisplayOnly(const Handle(SALOME_InteractiveObject)& theIO) +void SVTK_ViewWindow::DisplayOnly(const Handle(SALOME_InteractiveObject)& theIO) { myView->DisplayOnly(theIO); } @@ -586,7 +586,7 @@ void SVTK_ViewWindow::DisplayOnly(const Handle(SALOME_InteractiveObject)& theIO) /*! Display all objects in view */ -void SVTK_ViewWindow::DisplayAll() +void SVTK_ViewWindow::DisplayAll() { myView->DisplayAll(); } @@ -594,7 +594,7 @@ void SVTK_ViewWindow::DisplayAll() /*! Erase all objects in view */ -void SVTK_ViewWindow::EraseAll() +void SVTK_ViewWindow::EraseAll() { myView->EraseAll(); } @@ -793,7 +793,7 @@ void SVTK_ViewWindow::PopInteractorStyle() */ void SVTK_ViewWindow::Repaint(bool theUpdateTrihedron) { - if(theUpdateTrihedron) + if(theUpdateTrihedron) GetRenderer()->OnAdjustTrihedron(); GetInteractor()->update(); @@ -805,7 +805,7 @@ void SVTK_ViewWindow::Repaint(bool theUpdateTrihedron) if( GetRenderer() ) { aStyle->SetCurrentRenderer(GetRenderer()->GetDevice()); } - } + } #endif aStyle->OnTimer(); } @@ -814,7 +814,7 @@ void SVTK_ViewWindow::Repaint(bool theUpdateTrihedron) /*! Redirect the request to #SVTK_Renderer::GetScale */ -void SVTK_ViewWindow::GetScale( double theScale[3] ) +void SVTK_ViewWindow::GetScale( double theScale[3] ) { GetRenderer()->GetScale( theScale ); } @@ -822,7 +822,7 @@ void SVTK_ViewWindow::GetScale( double theScale[3] ) /*! Redirect the request to #SVTK_Renderer::SetScale */ -void SVTK_ViewWindow::SetScale( double theScale[3] ) +void SVTK_ViewWindow::SetScale( double theScale[3] ) { GetRenderer()->SetScale( theScale ); Repaint(); @@ -1128,7 +1128,7 @@ void SVTK_ViewWindow::SetSelectionEnabled( bool theEnable ) QtxAction* a = getAction( EnableSelectionId ); if ( a->isChecked() != theEnable) a->setChecked( theEnable ); - QtxActionGroup* aPreselectionGroup = + QtxActionGroup* aPreselectionGroup = dynamic_cast( getAction( PreselectionId ) ); if ( aPreselectionGroup ) aPreselectionGroup->setEnabled( theEnable ); @@ -1211,7 +1211,7 @@ void SVTK_ViewWindow::onEnableSelection( bool on ) { SVTK_Viewer* aViewer = dynamic_cast(myModel); if(aViewer) - aViewer->enableSelection(on); + aViewer->enableSelection(on); } /*! @@ -1231,7 +1231,7 @@ void SVTK_ViewWindow::SetIncrementalSpeed(const int theValue, const int theMode) \param theBtn2 - spacemouse button for the "increase speed increment" \param theBtn3 - spacemouse button for the "dominant combined switch" */ -void SVTK_ViewWindow::SetSpacemouseButtons(const int theBtn1, +void SVTK_ViewWindow::SetSpacemouseButtons(const int theBtn1, const int theBtn2, const int theBtn3) { @@ -1267,7 +1267,7 @@ void SVTK_ViewWindow::AdjustTrihedrons(const bool /*theIsForcedUpdate*/) Redirect the request to #SVTK_Renderer::OnAdjustTrihedron */ void SVTK_ViewWindow::onAdjustTrihedron() -{ +{ GetRenderer()->OnAdjustTrihedron(); } @@ -1275,7 +1275,7 @@ void SVTK_ViewWindow::onAdjustTrihedron() Redirect the request to #SVTK_Renderer::OnAdjustCubeAxes */ void SVTK_ViewWindow::onAdjustCubeAxes() -{ +{ GetRenderer()->OnAdjustCubeAxes(); } @@ -1339,12 +1339,12 @@ void SVTK_ViewWindow::onMouseDoubleClicked( QMouseEvent* event ) /*! Redirect the request to #SVTK_Renderer::AddActor */ -void SVTK_ViewWindow::AddActor( VTKViewer_Actor* theActor, +void SVTK_ViewWindow::AddActor( VTKViewer_Actor* theActor, bool theUpdate, bool theIsAdjustActors ) { GetRenderer()->AddActor(theActor, theIsAdjustActors); - if(theUpdate) + if(theUpdate) Repaint(); emit actorAdded(theActor); } @@ -1352,7 +1352,7 @@ void SVTK_ViewWindow::AddActor( VTKViewer_Actor* theActor, /*! Redirect the request to #SVTK_Renderer::RemoveActor */ -void SVTK_ViewWindow::RemoveActor( VTKViewer_Actor* theActor, +void SVTK_ViewWindow::RemoveActor( VTKViewer_Actor* theActor, bool theUpdate, bool theIsAdjustActors ) { @@ -1361,7 +1361,7 @@ void SVTK_ViewWindow::RemoveActor( VTKViewer_Actor* theActor, myDefaultInteractorStyle->FreeActors(); if ( myKeyFreeInteractorStyle ) myKeyFreeInteractorStyle->FreeActors(); - if(theUpdate) + if(theUpdate) Repaint(); emit actorRemoved(theActor); } @@ -1372,7 +1372,7 @@ QImage SVTK_ViewWindow::dumpViewContent() int* aSize = aWindow->GetSize(); int aWidth = aSize[0]; int aHeight = aSize[1]; - + #ifndef DISABLE_GLVIEWER OpenGLUtils_FrameBuffer aFrameBuffer; if( aFrameBuffer.init( aWidth, aHeight ) ) @@ -1383,7 +1383,7 @@ QImage SVTK_ViewWindow::dumpViewContent() // draw scene aWindow->Render(); - + //aFrameBuffer.unbind(); glPopAttrib(); @@ -1400,9 +1400,9 @@ QImage SVTK_ViewWindow::dumpViewContent() #endif // if frame buffers are unsupported, use old functionality - unsigned char *aData = + unsigned char *aData = aWindow->GetRGBACharPixelData( 0, 0, aWidth-1, aHeight-1, 0 ); - + QImage anImage( aData, aWidth, aHeight, QImage::Format_ARGB32 ); anImage = anImage.rgbSwapped(); @@ -1417,7 +1417,7 @@ QImage SVTK_ViewWindow::dumpView() { if( myDumpImage.isNull() ) return dumpViewContent(); - + RefreshDumpImage(); return myDumpImage; } @@ -1445,12 +1445,12 @@ bool SVTK_ViewWindow::dumpViewToFormat( const QImage& img, const QString& fileNa anExporter->SetSort((vtkGL2PSExporter::SortScheme)optionsDlg->getSortType()); anExporter->SetWrite3DPropsAsRasterImage((int)optionsDlg->isRasterize3D()); anExporter->SetPS3Shading((int)optionsDlg->isPs3Shading()); - + if ( format == "PS" ) { anExporter->SetFileFormatToPS(); anExporter->CompressOff(); } - + if ( format == "EPS" ) { anExporter->SetFileFormatToEPS(); anExporter->CompressOff(); @@ -1459,7 +1459,7 @@ bool SVTK_ViewWindow::dumpViewToFormat( const QImage& img, const QString& fileNa if ( format == "PDF" ) { anExporter->SetFileFormatToPDF(); } - + QString aFilePrefix(fileName); QString anExtension(SUIT_Tools::extension(fileName)); aFilePrefix.truncate(aFilePrefix.length() - 1 - anExtension.length()); @@ -1468,7 +1468,7 @@ bool SVTK_ViewWindow::dumpViewToFormat( const QImage& img, const QString& fileNa anExporter->Delete(); } delete optionsDlg; - return true; + return true; } /*! @@ -1482,10 +1482,10 @@ void SVTK_ViewWindow::RefreshDumpImage() /*! Redirect the request to #SVTK_Renderer::SetSelectionProp */ -void SVTK_ViewWindow::SetSelectionProp(const double& theRed, - const double& theGreen, - const double& theBlue, - const int& theWidth) +void SVTK_ViewWindow::SetSelectionProp(const double& theRed, + const double& theGreen, + const double& theBlue, + const int& theWidth) { myView->SetSelectionProp(theRed,theGreen,theBlue,theWidth); } @@ -1493,10 +1493,10 @@ void SVTK_ViewWindow::SetSelectionProp(const double& theRed, /*! Redirect the request to #SVTK_Renderer::SetSelectionProp */ -void SVTK_ViewWindow::SetPreselectionProp(const double& theRed, - const double& theGreen, - const double& theBlue, - const int& theWidth) +void SVTK_ViewWindow::SetPreselectionProp(const double& theRed, + const double& theGreen, + const double& theBlue, + const int& theWidth) { myView->SetPreselectionProp(theRed,theGreen,theBlue,theWidth); } @@ -1504,7 +1504,7 @@ void SVTK_ViewWindow::SetPreselectionProp(const double& theRed, /*! Redirect the request to #SVTK_Renderer::SetSelectionTolerance */ -void SVTK_ViewWindow::SetSelectionTolerance(const double& theTolNodes, +void SVTK_ViewWindow::SetSelectionTolerance(const double& theTolNodes, const double& theTolItems, const double& theTolObjects) { @@ -1551,7 +1551,7 @@ QtxAction* SVTK_ViewWindow::getAction( int id ) const } -// old visual parameters had 13 values. New format added additional +// old visual parameters had 13 values. New format added additional // 76 values for graduated axes, so both numbers are processed. const int nNormalParams = 13; // number of view windows parameters excluding graduated axes params const int nGradAxisParams = 25; // number of parameters of ONE graduated axis (X, Y, or Z) @@ -1602,7 +1602,7 @@ void getGradAxisVisualParams( QXmlStreamWriter& writer, vtkAxisActor2D* actor, Q writer.writeEndElement(); writer.writeEndElement(); - //params.sprintf( "* Graduated Axis: * Name *%u*%s*%.2f*%.2f*%.2f*%u*%u*%u*%u", isVisible, + //params.sprintf( "* Graduated Axis: * Name *%u*%s*%.2f*%.2f*%.2f*%u*%u*%u*%u", isVisible, // title.toLatin1().data(), color[0], color[1], color[2], font, bold, italic, shadow ); // Labels @@ -1639,7 +1639,7 @@ void getGradAxisVisualParams( QXmlStreamWriter& writer, vtkAxisActor2D* actor, Q writer.writeAttribute("B", QString("%1").arg(color[2])); writer.writeEndElement(); writer.writeEndElement(); - // params += QString().sprintf( "* Labels *%u*%u*%u*%.2f*%.2f*%.2f*%u*%u*%u*%u", isVisible, labels, offset, + // params += QString().sprintf( "* Labels *%u*%u*%u*%.2f*%.2f*%.2f*%u*%u*%u*%u", isVisible, labels, offset, // color[0], color[1], color[2], font, bold, italic, shadow ); // Tick marks @@ -1649,9 +1649,9 @@ void getGradAxisVisualParams( QXmlStreamWriter& writer, vtkAxisActor2D* actor, Q writer.writeAttribute("isVisible", QString("%1").arg(isVisible)); writer.writeAttribute("Length", QString("%1").arg(length)); writer.writeEndElement(); - + //params += QString().sprintf( "* Tick marks *%u*%u", isVisible, length ); - + writer.writeEndElement(); //return params; } @@ -1679,7 +1679,7 @@ void setGradAxisVisualParams(QXmlStreamReader& reader, vtkAxisActor2D* actor) do { reader.readNext(); } while (!reader.isStartElement()); - + // Read title color aAttr = reader.attributes(); @@ -1704,7 +1704,7 @@ void setGradAxisVisualParams(QXmlStreamReader& reader, vtkAxisActor2D* actor) do { reader.readNext(); - } while (!reader.isStartElement()); + } while (!reader.isStartElement()); // Read labels aAttr = reader.attributes(); isVisible = aAttr.value("isVisible").toString().toUShort(); @@ -1717,7 +1717,7 @@ void setGradAxisVisualParams(QXmlStreamReader& reader, vtkAxisActor2D* actor) do { reader.readNext(); - } while (!reader.isStartElement()); + } while (!reader.isStartElement()); // Read Color aAttr = reader.attributes(); @@ -1740,13 +1740,13 @@ void setGradAxisVisualParams(QXmlStreamReader& reader, vtkAxisActor2D* actor) // Tick Marks do { reader.readNext(); - } while (!reader.isStartElement()); + } while (!reader.isStartElement()); aAttr = reader.attributes(); // retrieve and set tick marks properties isVisible = aAttr.value("isVisible").toString().toUShort(); int length = aAttr.value("Length").toString().toInt(); - + actor->SetTickVisibility( isVisible ); actor->SetTickLength( length ); } @@ -1823,7 +1823,7 @@ void setGradAxisVisualParams( vtkAxisActor2D* actor, const QString& params ) QString SVTK_ViewWindow::getVisualParameters() { double pos[3], focalPnt[3], viewUp[3], parScale, scale[3]; - + // save position, focal point, viewUp, scale vtkCamera* camera = getRenderer()->GetActiveCamera(); camera->GetPosition( pos ); @@ -1833,7 +1833,7 @@ QString SVTK_ViewWindow::getVisualParameters() GetScale( scale ); // Parameters are given in the following format:view position (3 digits), focal point position (3 digits) - // view up values (3 digits), parallel scale (1 digit), scale (3 digits, + // view up values (3 digits), parallel scale (1 digit), scale (3 digits, // Graduated axes parameters (X, Y, Z axes parameters) QString retStr; QXmlStreamWriter aWriter(&retStr); @@ -1894,13 +1894,13 @@ QString SVTK_ViewWindow::getVisualParameters() /*! The method restores visual parameters of this view or postpones it untill the view is shown -*/ +*/ void SVTK_ViewWindow::setVisualParameters( const QString& parameters ) { //printf("#### %s\n", qPrintable(parameters)); SVTK_RenderWindowInteractor* anInteractor = GetInteractor(); if ( anInteractor->isVisible() ) { - doSetVisualParameters( parameters ); + doSetVisualParameters( parameters ); } else { myVisualParams = parameters; @@ -2188,7 +2188,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) QtxActionToolMgr* mgr = toolMgr(); // Dump view - anAction = new QtxAction(tr("MNU_DUMP_VIEW"), + anAction = new QtxAction(tr("MNU_DUMP_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_DUMP" ) ), tr( "MNU_DUMP_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_DUMP_VIEW")); @@ -2196,7 +2196,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, DumpId ); // FitAll - anAction = new QtxAction(tr("MNU_FITALL"), + anAction = new QtxAction(tr("MNU_FITALL"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_FITALL" ) ), tr( "MNU_FITALL" ), 0, this); anAction->setStatusTip(tr("DSC_FITALL")); @@ -2204,7 +2204,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, FitAllId ); // FitRect - anAction = new QtxAction(tr("MNU_FITRECT"), + anAction = new QtxAction(tr("MNU_FITRECT"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_FITAREA" ) ), tr( "MNU_FITRECT" ), 0, this); anAction->setStatusTip(tr("DSC_FITRECT")); @@ -2220,7 +2220,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, FitSelectionId ); // Zoom - anAction = new QtxAction(tr("MNU_ZOOM_VIEW"), + anAction = new QtxAction(tr("MNU_ZOOM_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_ZOOM" ) ), tr( "MNU_ZOOM_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_ZOOM_VIEW")); @@ -2228,7 +2228,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, ZoomId ); // Panning - anAction = new QtxAction(tr("MNU_PAN_VIEW"), + anAction = new QtxAction(tr("MNU_PAN_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_PAN" ) ), tr( "MNU_PAN_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_PAN_VIEW")); @@ -2236,7 +2236,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, PanId ); // Global Panning - anAction = new QtxAction(tr("MNU_GLOBALPAN_VIEW"), + anAction = new QtxAction(tr("MNU_GLOBALPAN_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_GLOBALPAN" ) ), tr( "MNU_GLOBALPAN_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_GLOBALPAN_VIEW")); @@ -2244,7 +2244,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, GlobalPanId ); // Change rotation point - anAction = new QtxAction(tr("MNU_CHANGINGROTATIONPOINT_VIEW"), + anAction = new QtxAction(tr("MNU_CHANGINGROTATIONPOINT_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_ROTATION_POINT" ) ), tr( "MNU_CHANGINGROTATIONPOINT_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_CHANGINGROTATIONPOINT_VIEW")); @@ -2253,7 +2253,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, ChangeRotationPointId ); // Rotation - anAction = new QtxAction(tr("MNU_ROTATE_VIEW"), + anAction = new QtxAction(tr("MNU_ROTATE_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_ROTATE" ) ), tr( "MNU_ROTATE_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_ROTATE_VIEW")); @@ -2261,9 +2261,9 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, RotationId ); // Projections - anAction = new QtxAction(tr("MNU_FRONT_VIEW"), + anAction = new QtxAction(tr("MNU_FRONT_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_FRONT" ) ), - tr( "MNU_FRONT_VIEW" ), 0, this, false, "Viewers:Front view"); + tr( "MNU_FRONT_VIEW" ), 0, this, false, "/#Viewers/View/Set X-"); anAction->setStatusTip(tr("DSC_FRONT_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onFrontView())); this->addAction(anAction); @@ -2271,7 +2271,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) anAction = new QtxAction(tr("MNU_BACK_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_BACK" ) ), - tr( "MNU_BACK_VIEW" ), 0, this, false, "Viewers:Back view"); + tr( "MNU_BACK_VIEW" ), 0, this, false, "/#Viewers/View/Set X+"); anAction->setStatusTip(tr("DSC_BACK_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onBackView())); this->addAction(anAction); @@ -2279,7 +2279,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) anAction = new QtxAction(tr("MNU_TOP_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_TOP" ) ), - tr( "MNU_TOP_VIEW" ), 0, this, false, "Viewers:Top view"); + tr( "MNU_TOP_VIEW" ), 0, this, false, "/#Viewers/View/Set Z-"); anAction->setStatusTip(tr("DSC_TOP_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onTopView())); this->addAction(anAction); @@ -2287,7 +2287,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) anAction = new QtxAction(tr("MNU_BOTTOM_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_BOTTOM" ) ), - tr( "MNU_BOTTOM_VIEW" ), 0, this, false, "Viewers:Bottom view"); + tr( "MNU_BOTTOM_VIEW" ), 0, this, false, "/#Viewers/View/Set Z+"); anAction->setStatusTip(tr("DSC_BOTTOM_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onBottomView())); this->addAction(anAction); @@ -2295,7 +2295,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) anAction = new QtxAction(tr("MNU_LEFT_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_LEFT" ) ), - tr( "MNU_LEFT_VIEW" ), 0, this, false, "Viewers:Left view"); + tr( "MNU_LEFT_VIEW" ), 0, this, false, "/#Viewers/View/Set Y+"); anAction->setStatusTip(tr("DSC_LEFT_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onLeftView())); this->addAction(anAction); @@ -2303,7 +2303,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) anAction = new QtxAction(tr("MNU_RIGHT_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_RIGHT" ) ), - tr( "MNU_RIGHT_VIEW" ), 0, this, false, "Viewers:Right view"); + tr( "MNU_RIGHT_VIEW" ), 0, this, false, "/#Viewers/View/Set Y-"); anAction->setStatusTip(tr("DSC_RIGHT_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onRightView())); this->addAction(anAction); @@ -2312,7 +2312,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) // rotate anticlockwise anAction = new QtxAction(tr("MNU_ANTICLOCKWISE_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_ANTICLOCKWISE" ) ), - tr( "MNU_ANTICLOCKWISE_VIEW" ), 0, this, false, "Viewers:Rotate anticlockwise"); + tr( "MNU_ANTICLOCKWISE_VIEW" ), 0, this, false, "/#Viewers/View/Rotate anticlockwise"); anAction->setStatusTip(tr("DSC_ANTICLOCKWISE_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onAntiClockWiseView())); this->addAction(anAction); @@ -2321,34 +2321,34 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) // rotate clockwise anAction = new QtxAction(tr("MNU_CLOCKWISE_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_CLOCKWISE" ) ), - tr( "MNU_CLOCKWISE_VIEW" ), 0, this, false, "Viewers:Rotate clockwise"); + tr( "MNU_CLOCKWISE_VIEW" ), 0, this, false, "/#Viewers/View/Rotate clockwise"); anAction->setStatusTip(tr("DSC_CLOCKWISE_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onClockWiseView())); this->addAction(anAction); mgr->registerAction( anAction, ClockWiseId ); // Reset - anAction = new QtxAction(tr("MNU_RESET_VIEW"), + anAction = new QtxAction(tr("MNU_RESET_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_RESET" ) ), - tr( "MNU_RESET_VIEW" ), 0, this, false, "Viewers:Reset view"); + tr( "MNU_RESET_VIEW" ), 0, this, false, "/#Viewers/View/Reset"); anAction->setStatusTip(tr("DSC_RESET_VIEW")); connect(anAction, SIGNAL(triggered()), this, SLOT(onResetView())); this->addAction(anAction); mgr->registerAction( anAction, ResetId ); // onViewTrihedron: Shows - Hides Trihedron - anAction = new QtxAction(tr("MNU_SHOW_TRIHEDRON"), + anAction = new QtxAction(tr("MNU_SHOW_TRIHEDRON"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_VTKVIEWER_VIEW_TRIHEDRON" ) ), tr( "MNU_SHOW_TRIHEDRON" ), 0, this); anAction->setCheckable( true ); anAction->setChecked( true ); - + anAction->setStatusTip(tr("DSC_SHOW_TRIHEDRON")); connect(anAction, SIGNAL(toggled(bool)), this, SLOT(onViewTrihedron(bool))); mgr->registerAction( anAction, ViewTrihedronId ); // onNonIsometric: Manage non-isometric params - anAction = new QtxAction(tr("MNU_SVTK_SCALING"), + anAction = new QtxAction(tr("MNU_SVTK_SCALING"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_SCALING" ) ), tr( "MNU_SVTK_SCALING" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_SCALING")); @@ -2357,7 +2357,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, NonIsometric ); // onGraduatedAxes: Manage graduated axes params - anAction = new QtxAction(tr("MNU_SVTK_GRADUATED_AXES"), + anAction = new QtxAction(tr("MNU_SVTK_GRADUATED_AXES"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_GRADUATED_AXES" ) ), tr( "MNU_SVTK_GRADUATED_AXES" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_GRADUATED_AXES")); @@ -2366,7 +2366,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, GraduatedAxes ); // onGraduatedAxes: Manage graduated axes params - anAction = new QtxAction(tr("MNU_SVTK_UPDATE_RATE"), + anAction = new QtxAction(tr("MNU_SVTK_UPDATE_RATE"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_UPDATE_RATE" ) ), tr( "MNU_SVTK_UPDATE_RATE" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_UPDATE_RATE")); @@ -2375,14 +2375,14 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, UpdateRate ); // Set perspective mode group - anAction = new QtxAction(tr("MNU_SVTK_PARALLEL_MODE"), + anAction = new QtxAction(tr("MNU_SVTK_PARALLEL_MODE"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_VIEW_PARALLEL" ) ), tr( "MNU_SVTK_PARALLEL_MODE" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_PARALLEL_MODE")); anAction->setCheckable(true); mgr->registerAction( anAction, ParallelModeId ); - anAction = new QtxAction(tr("MNU_SVTK_PERSPECTIVE_MODE"), + anAction = new QtxAction(tr("MNU_SVTK_PERSPECTIVE_MODE"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_VIEW_PERSPECTIVE" ) ), tr( "MNU_SVTK_PERSPECTIVE_MODE" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_PERSPECTIVE_MODE")); @@ -2403,7 +2403,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) connect(aPerspectiveGroup, SIGNAL(triggered(QAction*)), this, SLOT(onProjectionMode(QAction*))); // View Parameters - anAction = new QtxAction(tr("MNU_VIEWPARAMETERS_VIEW"), + anAction = new QtxAction(tr("MNU_VIEWPARAMETERS_VIEW"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_VIEW_PARAMETERS" ) ), tr( "MNU_VIEWPARAMETERS_VIEW" ), 0, this); anAction->setStatusTip(tr("DSC_VIEWPARAMETERS_VIEW")); @@ -2411,11 +2411,11 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) connect(anAction, SIGNAL(toggled(bool)), this, SLOT(onViewParameters(bool))); mgr->registerAction( anAction, ViewParametersId ); - // Synchronize View + // Synchronize View mgr->registerAction( synchronizeAction(), SynchronizeId ); // Switch between interaction styles - anAction = new QtxAction(tr("MNU_SVTK_STYLE_SWITCH"), + anAction = new QtxAction(tr("MNU_SVTK_STYLE_SWITCH"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_STYLE_SWITCH" ) ), tr( "MNU_SVTK_STYLE_SWITCH" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_STYLE_SWITCH")); @@ -2424,7 +2424,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, SwitchInteractionStyleId ); // Switch between zooming styles - anAction = new QtxAction(tr("MNU_SVTK_ZOOMING_STYLE_SWITCH"), + anAction = new QtxAction(tr("MNU_SVTK_ZOOMING_STYLE_SWITCH"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_ZOOMING_STYLE_SWITCH" ) ), tr( "MNU_SVTK_ZOOMING_STYLE_SWITCH" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_ZOOMING_STYLE_SWITCH")); @@ -2436,7 +2436,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) QSignalMapper* aSignalMapper = new QSignalMapper( this ); connect(aSignalMapper, SIGNAL(mapped(int)), this, SLOT(onSwitchPreSelectionMode(int))); - anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_STANDARD"), + anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_STANDARD"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_PRESELECTION_STANDARD" ) ), tr( "MNU_SVTK_PRESELECTION_STANDARD" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_PRESELECTION_STANDARD")); @@ -2444,8 +2444,8 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) connect(anAction, SIGNAL(triggered()), aSignalMapper, SLOT(map())); aSignalMapper->setMapping( anAction, Standard_Preselection ); mgr->registerAction( anAction, StandardPreselectionId ); - - anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_DYNAMIC"), + + anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_DYNAMIC"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_PRESELECTION_DYNAMIC" ) ), tr( "MNU_SVTK_PRESELECTION_DYNAMIC" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_PRESELECTION_DYNAMIC")); @@ -2454,7 +2454,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) aSignalMapper->setMapping( anAction, Dynamic_Preselection ); mgr->registerAction( anAction, DynamicPreselectionId ); - anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_DISABLED"), + anAction = new QtxAction(tr("MNU_SVTK_PRESELECTION_DISABLED"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_PRESELECTION_DISABLED" ) ), tr( "MNU_SVTK_PRESELECTION_DISABLED" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_PRESELECTION_DISABLED")); @@ -2470,7 +2470,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( aPreselectionAction, PreselectionId ); // Selection - anAction = new QtxAction(tr("MNU_SVTK_ENABLE_SELECTION"), + anAction = new QtxAction(tr("MNU_SVTK_ENABLE_SELECTION"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_SELECTION" ) ), tr( "MNU_SVTK_ENABLE_SELECTION" ), 0, this); anAction->setStatusTip(tr("DSC_SVTK_ENABLE_SELECTION")); @@ -2479,7 +2479,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( anAction, EnableSelectionId ); // Start recording - myStartAction = new QtxAction(tr("MNU_SVTK_RECORDING_START"), + myStartAction = new QtxAction(tr("MNU_SVTK_RECORDING_START"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_RECORDING_START" ) ), tr( "MNU_SVTK_RECORDING_START" ), 0, this); myStartAction->setStatusTip(tr("DSC_SVTK_RECORDING_START")); @@ -2487,7 +2487,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( myStartAction, StartRecordingId ); // Play recording - myPlayAction = new QtxAction(tr("MNU_SVTK_RECORDING_PLAY"), + myPlayAction = new QtxAction(tr("MNU_SVTK_RECORDING_PLAY"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_RECORDING_PLAY" ) ), tr( "MNU_SVTK_RECORDING_PLAY" ), 0, this); myPlayAction->setStatusTip(tr("DSC_SVTK_RECORDING_PLAY")); @@ -2496,7 +2496,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( myPlayAction, PlayRecordingId ); // Pause recording - myPauseAction = new QtxAction(tr("MNU_SVTK_RECORDING_PAUSE"), + myPauseAction = new QtxAction(tr("MNU_SVTK_RECORDING_PAUSE"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_RECORDING_PAUSE" ) ), tr( "MNU_SVTK_RECORDING_PAUSE" ), 0, this); myPauseAction->setStatusTip(tr("DSC_SVTK_RECORDING_PAUSE")); @@ -2505,7 +2505,7 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) mgr->registerAction( myPauseAction, PauseRecordingId ); // Stop recording - myStopAction = new QtxAction(tr("MNU_SVTK_RECORDING_STOP"), + myStopAction = new QtxAction(tr("MNU_SVTK_RECORDING_STOP"), theResourceMgr->loadPixmap( "VTKViewer", tr( "ICON_SVTK_RECORDING_STOP" ) ), tr( "MNU_SVTK_RECORDING_STOP" ), 0, this); myStopAction->setStatusTip(tr("DSC_SVTK_RECORDING_STOP")); @@ -2520,13 +2520,13 @@ void SVTK_ViewWindow::createActions(SUIT_ResourceMgr* theResourceMgr) void SVTK_ViewWindow::createToolBar() { QtxActionToolMgr* mgr = toolMgr(); - + mgr->append( DumpId, myToolBar ); mgr->append( SwitchInteractionStyleId, myToolBar ); mgr->append( SwitchZoomingStyleId, myToolBar ); mgr->append( mgr->separator(), myToolBar ); - + mgr->append( PreselectionId, myToolBar ); mgr->append( EnableSelectionId, myToolBar ); @@ -2733,7 +2733,7 @@ void SVTK_ViewWindow::onViewParameters(bool theIsActivate) /*! Custom show event handler */ -void SVTK_ViewWindow::showEvent( QShowEvent * theEvent ) +void SVTK_ViewWindow::showEvent( QShowEvent * theEvent ) { emit Show( theEvent ); } @@ -2741,7 +2741,7 @@ void SVTK_ViewWindow::showEvent( QShowEvent * theEvent ) /*! Custom hide event handler */ -void SVTK_ViewWindow::hideEvent( QHideEvent * theEvent ) +void SVTK_ViewWindow::hideEvent( QHideEvent * theEvent ) { emit Hide( theEvent ); } @@ -2778,7 +2778,7 @@ SUIT_CameraProperties SVTK_ViewWindow::cameraProperties() vtkCamera* aCamera = getRenderer()->GetActiveCamera(); if ( !aCamera ) return aProps; - + aProps.setDimension( SUIT_CameraProperties::Dim3D ); if ( toolMgr()->action( ParallelModeId ) ) { if ( toolMgr()->action( ParallelModeId )->isChecked() ) @@ -2796,7 +2796,7 @@ SUIT_CameraProperties SVTK_ViewWindow::cameraProperties() aCamera->GetFocalPoint( aFocalPoint ); aCamera->GetPosition( aPosition ); aCamera->GetViewUp( aViewUp ); - + aProps.setFocalPoint( aFocalPoint[0], aFocalPoint[1], aFocalPoint[2] ); aProps.setPosition( aPosition[0], aPosition[1], aPosition[2] ); aProps.setViewUp( aViewUp[0], aViewUp[1], aViewUp[2] ); @@ -2809,7 +2809,7 @@ SUIT_CameraProperties SVTK_ViewWindow::cameraProperties() GetRenderer()->GetScale( anAxialScale ); aProps.setAxialScale( anAxialScale[0], anAxialScale[1], anAxialScale[2] ); - + return aProps; } @@ -2831,7 +2831,7 @@ void SVTK_ViewWindow::synchronize( SUIT_ViewWindow* theView ) // get camera vtkCamera* aCamera = getRenderer()->GetActiveCamera(); - + double aFocalPoint[3]; double aPosition[3]; double aViewUp[3]; @@ -2842,7 +2842,7 @@ void SVTK_ViewWindow::synchronize( SUIT_ViewWindow* theView ) aProps.getPosition( aPosition[0], aPosition[1], aPosition[2] ); aProps.getFocalPoint( aFocalPoint[0], aFocalPoint[1], aFocalPoint[2] ); aProps.getAxialScale( anAxialScale[0], anAxialScale[1], anAxialScale[2] ); - + // restore properties to the camera aCamera->SetViewUp( aViewUp ); aCamera->SetPosition( aPosition ); diff --git a/src/SalomeApp/SalomeApp_Application.cxx b/src/SalomeApp/SalomeApp_Application.cxx index 640bf3833..6dc0e9eb5 100644 --- a/src/SalomeApp/SalomeApp_Application.cxx +++ b/src/SalomeApp/SalomeApp_Application.cxx @@ -342,12 +342,12 @@ void SalomeApp_Application::createActions() //! Dump study createAction( DumpStudyId, tr( "TOT_DESK_FILE_DUMP_STUDY" ), QIcon(), tr( "MEN_DESK_FILE_DUMP_STUDY" ), tr( "PRP_DESK_FILE_DUMP_STUDY" ), - Qt::CTRL+Qt::Key_D, desk, false, this, SLOT( onDumpStudy() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onDumpStudy() ), "/PRP_DESK_FILE_DUMP_STUDY" ); //! Load script createAction( LoadScriptId, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), QIcon(), tr( "MEN_DESK_FILE_LOAD_SCRIPT" ), tr( "PRP_DESK_FILE_LOAD_SCRIPT" ), - Qt::CTRL+Qt::Key_T, desk, false, this, SLOT( onLoadScript() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onLoadScript() ), "/PRP_DESK_FILE_LOAD_SCRIPT" ); //! Properties createAction( PropertiesId, tr( "TOT_DESK_PROPERTIES" ), QIcon(), @@ -357,7 +357,7 @@ void SalomeApp_Application::createActions() //! Catalog Generator createAction( CatalogGenId, tr( "TOT_DESK_CATALOG_GENERATOR" ), QIcon(), tr( "MEN_DESK_CATALOG_GENERATOR" ), tr( "PRP_DESK_CATALOG_GENERATOR" ), - Qt::ALT+Qt::SHIFT+Qt::Key_G, desk, false, this, SLOT( onCatalogGen() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onCatalogGen() ), "/PRP_DESK_CATALOG_GENERATOR" ); //! Registry Display createAction( RegDisplayId, tr( "TOT_DESK_REGISTRY_DISPLAY" ), QIcon(), @@ -366,13 +366,13 @@ void SalomeApp_Application::createActions() createAction( ConnectId, tr( "TOT_DESK_CONNECT_STUDY" ), QIcon(), tr( "MEN_DESK_CONNECT" ), tr( "PRP_DESK_CONNECT" ), - Qt::CTRL+Qt::Key_L, desk, false, this, SLOT( onLoadDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onLoadDoc() ), "/PRP_DESK_CONNECT" ); //no need at this action for mono-study application because study is always exists action( ConnectId )->setVisible( false ); createAction( DisconnectId, tr( "TOT_DESK_DISCONNECT_STUDY" ), QIcon(), tr( "MEN_DESK_DISCONNECT" ), tr( "PRP_DESK_DISCONNECT" ), - Qt::CTRL+Qt::Key_U, desk, false, this, SLOT( onUnloadDoc() ) ); + QKeySequence::UnknownKey, desk, false, this, SLOT( onUnloadDoc() ), "/PRP_DESK_DISCONNECT" ); //no need at this action for mono-study application because study is always exists action( DisconnectId )->setVisible( false ); @@ -2155,7 +2155,7 @@ void SalomeApp_Application::ensureShaperIsActivated() study->dataModels( models ); for( int i = 0; i < models.count() && !shaperIsActive; i++ ) shaperIsActive = models[i]->module()->moduleName() == "Shaper"; - + if (shaper && !shaperIsActive) onDesktopMessage("register_module_in_study/Shaper"); } diff --git a/tools/DevTools/ShortcutMgr/ShortcutMgr. Resource generator.xlsx b/tools/DevTools/ShortcutMgr/ShortcutMgr. Resource generator.xlsx new file mode 100644 index 000000000..bfc86f94a Binary files /dev/null and b/tools/DevTools/ShortcutMgr/ShortcutMgr. Resource generator.xlsx differ