From 60dfa5505a35714e1ff59b424514100648e38cab Mon Sep 17 00:00:00 2001 From: Viktor UZLOV Date: Wed, 18 Nov 2020 11:02:22 +0300 Subject: [PATCH] bos #20215 Help panels into SALOME for new users --- doc/salome/gui/images/help_panel.png | Bin 0 -> 35121 bytes .../gui/input/howtos_and_best_practives.rst | 6 +- doc/salome/gui/input/using_help_panel.rst | 176 ++++++++ doc/salome/tui/doxyfile.in | 2 +- src/CAM/CAM_Application.cxx | 29 +- src/CAM/CAM_Application.h | 3 +- src/LightApp/LightApp_Application.cxx | 90 +++- src/LightApp/LightApp_Application.h | 6 + src/LightApp/LightApp_Module.cxx | 5 + src/LightApp/LightApp_Module.h | 2 + src/LightApp/LightApp_ModuleAction.cxx | 18 + src/LightApp/LightApp_ModuleAction.h | 3 + src/LightApp/resources/LightApp.xml | 9 +- src/LightApp/resources/LightApp_msg_en.ts | 28 ++ src/LightApp/resources/LightApp_msg_fr.ts | 28 ++ src/LightApp/resources/LightApp_msg_ja.ts | 28 ++ src/Qtx/CMakeLists.txt | 4 +- src/Qtx/QtxDockWidget.cxx | 13 +- src/Qtx/QtxDockWidget.h | 1 + src/Qtx/QtxInfoPanel.cxx | 415 ++++++++++++++++++ src/Qtx/QtxInfoPanel.h | 72 +++ src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx | 251 +++++++++++ src/SALOME_PYQT/SalomePyQt/SalomePyQt.h | 13 + src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip | 13 + src/STD/resources/STD_msg_en.ts | 8 +- 25 files changed, 1184 insertions(+), 39 deletions(-) create mode 100644 doc/salome/gui/images/help_panel.png create mode 100644 doc/salome/gui/input/using_help_panel.rst create mode 100644 src/Qtx/QtxInfoPanel.cxx create mode 100644 src/Qtx/QtxInfoPanel.h diff --git a/doc/salome/gui/images/help_panel.png b/doc/salome/gui/images/help_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..859dcd0fd6e39612e3f44615465996ef6cde4e56 GIT binary patch literal 35121 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 map) const + { + map.insert(SalomeApp_Application::WT_ObjectBrowser, Qt::LeftDockWidgetArea); + map.insert(SalomeApp_Application::WT_InfoPanel, Qt::RightDockWidgetArea); + map.insert(SalomeApp_Application::WT_NoteBook, Qt::LeftDockWidgetArea); + map.insert(SalomeApp_Application::WT_PyConsole, Qt::BottomDockWidgetArea); + } + +.. note:: The dock area position flag is ignored for the *Help panel*. + +.. _hp_module_description: + +Module description +================== + +To display short annotation of the module in the desktop in the application's +*neutral point* (when there's no active module, see figure above), +add the ```` parameter to the main module's section in the configuration +file - ``LightApp.xml`` or ``SalomeApp.xml``, for example: + +.. code-block:: xml + :emphasize-lines: 6 + + +
+ + + + +
+
+ +Also, provide a translation of the module description to other languages in the +corresponding translation files (``*.ts``). + +.. _hp_api: + +API of Help panel +================= + +.. _hp_api_cpp: + +C++ +--- + +Help panel is implemented in SALOME GUI module, via the ``QtxInfoPanel`` class. +To obtain a reference to the Help panel, use method ``infoPanel()`` of the +``LightApp_Application`` or ``SalomeApp_Application`` class: + +.. code-block:: c++ + + #include + ... + SalomeApp_Application* app = dynamic_cast(application()); + QtxInfoPanel* ip = app->infoPanel(); + +The class ``QtxInfoPanel`` provides several methods which can be used to manage +content of the *Help panel*. It is possible to add text labels and actions (these latter +are presented as buttons). The items can be arranged into the logical blocks - groups; +groups can contain other groups. All methods creating content items return unique +identifier which can be later used to change item's visibility, enable/disable it +(actions can be also enabled/disable directly), remove it or clear its contents +(for groups). Top level container has identifier ``-1``. + +.. code-block:: c++ + + // Set panel's title (put empty string to hide title) + ip->setTitle("Welcome to my module"); + + // Create group box + int gb = ip->addGroup("General features"); + + // Add action to the group box + QAction* action = new QAction("My feature"); + int id1 = ip->addAction(action, gb); + + // Add informative label to the group box + int id2 = ip->addLabel("My cool feature", gb); + + // Add another label, right-aligned, to the top-level container + int id3 = ip->addLabel("Some information", Qt::AlignRight); + + // Change visibility of given item + ip->setVisible(id3, false); + + // Enable/disable given item + ip->setEnabled(gb, false); + + // Remove given item + ip->remove(id1); + + // Remove all content of group + ip->clear(gb); + + // Clear Help panel + ip->clear(); + +.. _hp_api_python: + +Python +------ + +For Python modules, *Help panel* can be accessed via the ``SalomePyQt`` Python module. + +.. code-block:: python + + from PyQt5 import Qt as Q + from SalomePyQt import SalomePyQt as sg + + # Set panel's title (put empty string to hide title) + sg.infoSetTitle("Welcome to my module") + + # Create group box + gb = sg.infoAddGroup("General features") + + # Add action to the group box + action = Q.QAction("My feature") + id1 = sg.infoAddAction(action, gb) + + # Add informative label to the group box + id2 = sg.infoAddLabel("My cool feature", gb) + + # Add another label, right-aligned, to the top-level container + # Note: -1 is used explicitly as group identifier + id3 = sg.infoAddLabel("Some information", Q.Qt.AlignRight, -1) + + # Change visibility of given item + sg.infoSetVisible(id3, False) + + # Enable/disable given item + sg.infoSetEnabled(gb, False) + + # Remove given item + sg.infoRemove(id1) + + # Remove all content of group + sg.infoClear(gb) + + # Clear Help panel + sg.infoClear() + +.. _hp_update_panel + +Notifications +============= + +Each time when *Help panel* is shown, currently active module is informed via +the virtual method ``updateInfoPanel()``. This method can be used to properly +update the contents of the *Help panel*, depending on the current context. diff --git a/doc/salome/tui/doxyfile.in b/doc/salome/tui/doxyfile.in index a0ee40194..732ada6f6 100644 --- a/doc/salome/tui/doxyfile.in +++ b/doc/salome/tui/doxyfile.in @@ -8,7 +8,7 @@ CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English USE_WINDOWS_ENCODING = NO BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = NO +REPEAT_BRIEF = YES ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = YES INLINE_INHERITED_MEMB = NO diff --git a/src/CAM/CAM_Application.cxx b/src/CAM/CAM_Application.cxx index ae731cee5..ac5989830 100644 --- a/src/CAM/CAM_Application.cxx +++ b/src/CAM/CAM_Application.cxx @@ -640,7 +640,7 @@ QString CAM_Application::moduleTitle( const QString& name ) /*! \brief Get module icon name. - \param name module name + \param name module name or title \return module icon or null QString if module is not found */ QString CAM_Application::moduleIcon( const QString& name ) @@ -648,24 +648,40 @@ QString CAM_Application::moduleIcon( const QString& name ) QString res; for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it ) { - if ( (*it).name == name ) + if ( (*it).name == name || (*it).title == name ) res = (*it).icon; } return res; } +/*! + \brief Get module description. + \param name module name or title + \return module description or null QString if description is not provided in config file. +*/ +QString CAM_Application::moduleDescription( const QString& name ) +{ + QString res; + for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it ) + { + if ( (*it).name == name || (*it).title == name ) + res = tr((*it).description.toUtf8()); + } + return res; +} + /*! \brief Get module library name by its title (user name). - \param title module title (user name) + \param title module name or title \param full if \c true, return full library name, otherwise return its internal name \return module library name or null QString if module is not found */ -QString CAM_Application::moduleLibrary( const QString& title, const bool full ) +QString CAM_Application::moduleLibrary( const QString& name, const bool full ) { QString res; for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it ) { - if ( (*it).title == title ) + if ( (*it).name == name || (*it).title == name ) res = (*it).library; } if ( !res.isEmpty() && full ) @@ -771,6 +787,8 @@ void CAM_Application::readModuleList() QString modIcon = resMgr->stringValue( *it, "icon", QString() ); + QString modDescription = resMgr->stringValue( *it, "description", QString() ); + QString modLibrary = resMgr->stringValue( *it, "library", QString() ).trimmed(); if ( !modLibrary.isEmpty() ) { @@ -801,6 +819,7 @@ void CAM_Application::readModuleList() inf.status = hasGui ? stUnknown : stNoGui; if ( hasGui ) inf.library = modLibrary; inf.icon = modIcon; + inf.description = modDescription; inf.version = version; myInfoList.append( inf ); } diff --git a/src/CAM/CAM_Application.h b/src/CAM/CAM_Application.h index b0b69d3f6..a11669897 100644 --- a/src/CAM/CAM_Application.h +++ b/src/CAM/CAM_Application.h @@ -74,6 +74,7 @@ public: static QString moduleName( const QString& ); static QString moduleTitle( const QString& ); static QString moduleIcon( const QString& ); + static QString moduleDescription( const QString& ); static QString moduleLibrary( const QString&, const bool = true ); virtual void createEmptyStudy(); @@ -100,7 +101,7 @@ private: private: enum { stUnknown = 0, stNoGui, stInaccessible, stReady }; typedef struct { - QString name, title, icon, library, version; + QString name, title, icon, library, version, description; int status; } ModuleInfo; typedef QList ModuleInfoList; diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index 1aa01cf53..d9379a40a 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -526,6 +527,9 @@ bool LightApp_Application::activateModule( const QString& modName ) saveDockWindowsState(); + if ( infoPanel() ) + infoPanel()->clear(); + bool status = CAM_Application::activateModule( modName ); updateModuleActions(); @@ -601,26 +605,24 @@ void LightApp_Application::createActions() QString url = resMgr->stringValue("GUI", "site_url"); if ( !url.isEmpty() ) { QString title = tr ( "SALOME_SITE" ); - QAction* as = createAction( id, title, + QAction* as = createAction( WebSiteId, title, resMgr->loadPixmap( "LightApp", tr( "ICON_WWW" ), false ), title, title, 0, desk, false, this, SLOT( onHelpContentsModule() ) ); as->setData( url ); createMenu( as, helpMenu, -1, 0 ); - id++; } // b) Link to Forum url = resMgr->stringValue("GUI", "forum_url"); if ( !url.isEmpty() ) { QString title = tr ( "SALOME_FORUM" ); - QAction* af = createAction( helpMenu, title, + QAction* af = createAction( ForumId, title, resMgr->loadPixmap( "LightApp", tr( "ICON_WWW" ), false ), title, title, 0, desk, false, this, SLOT( onHelpContentsModule() ) ); af->setData( url ); createMenu( af, helpMenu, -1, 0 ); - id++; } // c) Link to YouTube channel @@ -628,16 +630,28 @@ void LightApp_Application::createActions() if ( !url.isEmpty() ) { createMenu( separator(), helpMenu, -1, 0 ); QString title = tr ( "SALOME_VIDEO_TUTORIALS" ); - QAction* av = createAction( helpMenu, title, + QAction* av = createAction( VideosId, title, resMgr->loadPixmap( "LightApp", tr( "ICON_LIFE_RIGN" ), false ), - title, title, + title, tr( "PRP_SALOME_VIDEO_TUTORIALS" ), 0, desk, false, this, SLOT( onHelpContentsModule() ) ); av->setData( url ); createMenu( av, helpMenu, -1, 0 ); - id++; } - // d) Help for modules + // d) Link to Tutorials + + url = resMgr->stringValue("GUI", "tutorials_url"); + if ( !url.isEmpty() ) { + QString title = tr ( "SALOME_TUTORIALS" ); + QAction* as = createAction( TutorialsId, title, + resMgr->loadPixmap( "LightApp", tr( "ICON_WWW" ), false ), + title, tr( "PRP_SALOME_TUTORIALS" ), + 0, desk, false, this, SLOT( onHelpContentsModule() ) ); + as->setData( url ); + createMenu( as, helpMenu, -1, 0 ); + } + + // e) Help for modules // - First create top-level menus to preserve correct order QString userGuide = "User's Guide"; @@ -1369,9 +1383,18 @@ void LightApp_Application::insertDockWindow( const int id, QWidget* wid ) myWin.insert( id, wid ); QtxDockWidget* dock = new QtxDockWidget( true, desktop() ); + if ( id == WT_InfoPanel ) { + // Info panel's position is strongly limited to the right area; + // It is not movable and not floatable. + dock->setAllowedAreas( Qt::RightDockWidgetArea ); + dock->setFeatures( QDockWidget::DockWidgetClosable ); + connect( dock, SIGNAL( aboutToShow()), this, SLOT( onInfoPanelShown() ) ); + } + else { + dock->setFeatures( QDockWidget::AllDockWidgetFeatures ); + } connect( dock, SIGNAL( destroyed( QObject* ) ), this, SLOT( onWCDestroyed( QObject* ) ) ); - dock->setFeatures( QDockWidget::AllDockWidgetFeatures ); dock->setObjectName( wid->objectName().isEmpty() ? QString( "window_%1" ).arg( id ) : QString( "%1Dock" ).arg( wid->objectName() ) ); dock->setWidget( wid ); @@ -1441,6 +1464,11 @@ SUIT_DataBrowser* LightApp_Application::objectBrowser() return qobject_cast( dockWindow( WT_ObjectBrowser ) ); } +QtxInfoPanel* LightApp_Application::infoPanel() +{ + return qobject_cast( dockWindow( WT_InfoPanel )); +} + /*! \return Log Window */ @@ -1793,6 +1821,7 @@ void LightApp_Application::onStudyCreated( SUIT_Study* theStudy ) } getWindow( WT_ObjectBrowser ); + getWindow( WT_InfoPanel ); loadDockWindowsState(); @@ -1824,6 +1853,7 @@ void LightApp_Application::onStudyOpened( SUIT_Study* theStudy ) } getWindow( WT_ObjectBrowser ); + getWindow( WT_InfoPanel ); loadDockWindowsState(); @@ -2129,6 +2159,13 @@ QWidget* LightApp_Application::createWindow( const int flag ) wid = ob; ob->connectPopupRequest( this, SLOT( onConnectPopupRequest( SUIT_PopupClient*, QContextMenuEvent* ) ) ); } + else if ( flag == WT_InfoPanel) + { + QtxInfoPanel* ipanel = new QtxInfoPanel( desktop() ); + ipanel->setObjectName( "infoPanel" ); + ipanel->setWindowTitle( tr( "INFO_PANEL" ) ); + wid = ipanel; + } #ifndef DISABLE_PYCONSOLE else if ( flag == WT_PyConsole ) { @@ -2166,6 +2203,7 @@ void LightApp_Application::defaultWindows( QMap& aMap ) const #endif if ( activeStudy() ) { aMap.insert( WT_ObjectBrowser, Qt::LeftDockWidgetArea ); + aMap.insert( WT_InfoPanel, Qt::RightDockWidgetArea ); // aMap.insert( WT_LogWindow, Qt::DockBottom ); } } @@ -4126,6 +4164,34 @@ void LightApp_Application::updateWindows() } loadDockWindowsState(); + + if ( !activeModule() && infoPanel() ) + { + infoPanel()->clear(); + infoPanel()->setTitle( tr( "INFO_WELCOME_TO_SALOME" ) ); + + int grp = infoPanel()->addGroup( tr( "INFO_GETTING_STARTED" ) ); + infoPanel()->addAction( action( FileNewId ), grp ); + infoPanel()->addLabel( action( FileNewId )->statusTip(), grp ); + infoPanel()->addAction( action( FileOpenId ), grp ); + infoPanel()->addLabel( action( FileOpenId )->statusTip(), grp ); + infoPanel()->addAction( action( TutorialsId ), grp ); + infoPanel()->addLabel( action( TutorialsId )->statusTip(), grp ); + infoPanel()->addAction( action( VideosId ), grp ); + infoPanel()->addLabel( action( VideosId )->statusTip(), grp ); + + LightApp_ModuleAction* ma = qobject_cast(action(ModulesListId)); + if ( ma && ma->count() > 0 ) + { + grp = infoPanel()->addGroup( tr( "INFO_AVAILABLE_MODULES" ) ); + foreach ( QString mname, ma->modules() ) + { + infoPanel()->addAction( ma->moduleAction( mname ), grp ); + if ( !moduleDescription( mname ).isEmpty() ) + infoPanel()->addLabel( moduleDescription( mname ), grp ); + } + } + } } /*! @@ -5014,6 +5080,12 @@ void LightApp_Application::onDesktopMessage( const QString& message ) } } +void LightApp_Application::onInfoPanelShown() +{ + if ( activeModule() && activeModule()->inherits( "LightApp_Module" ) ) + ((LightApp_Module*)activeModule())->updateInfoPanel(); +} + /*! Internal method. Returns all top level toolbars. diff --git a/src/LightApp/LightApp_Application.h b/src/LightApp/LightApp_Application.h index 448294867..da8ffc266 100644 --- a/src/LightApp/LightApp_Application.h +++ b/src/LightApp/LightApp_Application.h @@ -40,6 +40,7 @@ #include class LogWindow; +class QtxInfoPanel; #ifndef DISABLE_PYCONSOLE class PyConsole_Console; class PyConsole_Interp; @@ -75,6 +76,7 @@ class LIGHTAPP_EXPORT LightApp_Application : public CAM_Application, public SUIT public: typedef enum { WT_ObjectBrowser, + WT_InfoPanel, #ifndef DISABLE_PYCONSOLE WT_PyConsole, #endif @@ -89,6 +91,7 @@ public: PreferencesId, MRUId, ModulesListId, NewGLViewId, NewPlot2dId, NewOCCViewId, NewVTKViewId, NewQxSceneViewId, NewGraphicsViewId, NewPVViewId, NewPyViewerId, StyleId, FullScreenId, + WebSiteId, ForumId, VideosId, TutorialsId, UserID }; protected: @@ -110,6 +113,7 @@ public: LogWindow* logWindow(); SUIT_DataBrowser* objectBrowser(); + QtxInfoPanel* infoPanel(); #ifndef DISABLE_PYCONSOLE PyConsole_Console* pythonConsole(const bool force = false); #endif @@ -266,6 +270,8 @@ protected slots: virtual void onDesktopMessage( const QString& ); + virtual void onInfoPanelShown(); + private slots: void onSelection(); void onRefresh(); diff --git a/src/LightApp/LightApp_Module.cxx b/src/LightApp/LightApp_Module.cxx index ff19dc9c4..ee0b2e505 100644 --- a/src/LightApp/LightApp_Module.cxx +++ b/src/LightApp/LightApp_Module.cxx @@ -315,6 +315,11 @@ void LightApp_Module::MenuItem() { } +/*!NOT IMPLEMENTED*/ +void LightApp_Module::updateInfoPanel() +{ +} + /*!NOT IMPLEMENTED*/ void LightApp_Module::createPreferences() { diff --git a/src/LightApp/LightApp_Module.h b/src/LightApp/LightApp_Module.h index 31222c615..94fd9396f 100644 --- a/src/LightApp/LightApp_Module.h +++ b/src/LightApp/LightApp_Module.h @@ -117,6 +117,8 @@ public slots: void MenuItem(); + virtual void updateInfoPanel(); + protected slots: virtual void onModelSaved(); virtual void onModelOpened(); diff --git a/src/LightApp/LightApp_ModuleAction.cxx b/src/LightApp/LightApp_ModuleAction.cxx index fa9a8c695..9431ec346 100644 --- a/src/LightApp/LightApp_ModuleAction.cxx +++ b/src/LightApp/LightApp_ModuleAction.cxx @@ -271,6 +271,15 @@ LightApp_ModuleAction::~LightApp_ModuleAction() { } +/*! + \brief Get number of registered modules. + \return modules count +*/ +int LightApp_ModuleAction::count() const +{ + return modules().count(); +} + /*! \brief Get list of modules. \return modules names list @@ -314,6 +323,15 @@ void LightApp_ModuleAction::setModuleIcon( const QString& name, const QIcon& ico update(); } +/*! + \brief Get module action. + \param name module name +*/ +QAction* LightApp_ModuleAction::moduleAction( const QString& name ) const +{ + return mySet->moduleAction( name ); +} + /*! \brief Add module into the list. \param name module name diff --git a/src/LightApp/LightApp_ModuleAction.h b/src/LightApp/LightApp_ModuleAction.h index c5e6da61c..1048e8b4c 100644 --- a/src/LightApp/LightApp_ModuleAction.h +++ b/src/LightApp/LightApp_ModuleAction.h @@ -50,11 +50,14 @@ public: LightApp_ModuleAction( const QString&, const QIcon&, QObject* = 0 ); virtual ~LightApp_ModuleAction(); + int count() const; QStringList modules() const; QIcon moduleIcon( const QString& ) const; void setModuleIcon( const QString&, const QIcon& ); + QAction* moduleAction( const QString& ) const; + void insertModule( const QString&, const QIcon&, const int = -1 ); void removeModule( const QString& ); diff --git a/src/LightApp/resources/LightApp.xml b/src/LightApp/resources/LightApp.xml index 3371eec9f..aed32a11a 100644 --- a/src/LightApp/resources/LightApp.xml +++ b/src/LightApp/resources/LightApp.xml @@ -221,6 +221,7 @@
+
@@ -274,11 +275,11 @@
- - + +
- - + +
diff --git a/src/LightApp/resources/LightApp_msg_en.ts b/src/LightApp/resources/LightApp_msg_en.ts index 55249ed3f..8a2ffb82c 100644 --- a/src/LightApp/resources/LightApp_msg_en.ts +++ b/src/LightApp/resources/LightApp_msg_en.ts @@ -58,6 +58,18 @@ CEA/DEN, CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITASSALOME_VIDEO_TUTORIALS Video Tutorials + + PRP_SALOME_VIDEO_TUTORIALS + Visit YouTube channel with some videos + + + SALOME_TUTORIALS + Tutorials + + + PRP_SALOME_TUTORIALS + Try tutorials from SALOME site + ENTRY_COLUMN Entry @@ -168,6 +180,10 @@ The changes will be applied on the next application session. OBJECT_BROWSER Object Browser + + INFO_PANEL + Help panel + PRP_DESK_PREFERENCES Allow to change the preferences @@ -1121,6 +1137,18 @@ File does not exist PREF_PY_NUM_COLUMNS Number of columns: + + INFO_WELCOME_TO_SALOME + Welcome to SALOME + + + INFO_GETTING_STARTED + Getting started + + + INFO_AVAILABLE_MODULES + Available modules + LightApp_Module diff --git a/src/LightApp/resources/LightApp_msg_fr.ts b/src/LightApp/resources/LightApp_msg_fr.ts index 3667f6b0d..ad5568fc2 100644 --- a/src/LightApp/resources/LightApp_msg_fr.ts +++ b/src/LightApp/resources/LightApp_msg_fr.ts @@ -58,6 +58,18 @@ CEA/DEN, CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITASSALOME_VIDEO_TUTORIALS Video Tutorials + + PRP_SALOME_VIDEO_TUTORIALS + Visit YouTube channel with some videos + + + SALOME_TUTORIALS + Tutorials + + + PRP_SALOME_TUTORIALS + Try tutorials from SALOME site + ENTRY_COLUMN Entrée @@ -168,6 +180,10 @@ Les modifications seront appliquées à la prochaine session. OBJECT_BROWSER Arbre d'étude + + INFO_PANEL + Help panel + PRP_DESK_PREFERENCES Permettre de changer les préférences @@ -1121,6 +1137,18 @@ Le fichier n'existe pas PREF_PY_NUM_COLUMNS Nombre de colonnes: + + INFO_WELCOME_TO_SALOME + Welcome to SALOME + + + INFO_GETTING_STARTED + Getting started + + + INFO_AVAILABLE_MODULES + Available modules + LightApp_Module diff --git a/src/LightApp/resources/LightApp_msg_ja.ts b/src/LightApp/resources/LightApp_msg_ja.ts index 0d83e623c..5688c2296 100644 --- a/src/LightApp/resources/LightApp_msg_ja.ts +++ b/src/LightApp/resources/LightApp_msg_ja.ts @@ -58,6 +58,18 @@ CEA/DEN, CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITASSALOME_VIDEO_TUTORIALS Video Tutorials + + PRP_SALOME_VIDEO_TUTORIALS + Visit YouTube channel with some videos + + + SALOME_TUTORIALS + Tutorials + + + PRP_SALOME_TUTORIALS + Try tutorials from SALOME site + ENTRY_COLUMN エントリ @@ -167,6 +179,10 @@ Pythonファイルは、文字、数字、アンダースコアが含まれて OBJECT_BROWSER オブジェクトブラウザー + + INFO_PANEL + Help panel + PRP_DESK_PREFERENCES 設定を変更することができます。 @@ -1119,6 +1135,18 @@ Pythonファイルは、文字、数字、アンダースコアが含まれて PREF_PY_NUM_COLUMNS 列数 + + INFO_WELCOME_TO_SALOME + Welcome to SALOME + + + INFO_GETTING_STARTED + Getting started + + + INFO_AVAILABLE_MODULES + Available modules + LightApp_Module diff --git a/src/Qtx/CMakeLists.txt b/src/Qtx/CMakeLists.txt index 74f9edfd1..09835fef8 100644 --- a/src/Qtx/CMakeLists.txt +++ b/src/Qtx/CMakeLists.txt @@ -33,7 +33,7 @@ SET(_link_LIBRARIES ${QT_LIBRARIES} ${OPENGL_LIBRARIES}) # --- headers --- # header files / to be processed by moc -SET(_moc_HEADERS +SET(_moc_HEADERS QtxAction.h QtxActionGroup.h QtxActionMenuMgr.h @@ -53,6 +53,7 @@ SET(_moc_HEADERS QtxFontEdit.h QtxGridBox.h QtxGroupBox.h + QtxInfoPanel.h QtxIntSpinBox.h QtxIntSpinSlider.h QtxListAction.h @@ -146,6 +147,7 @@ SET(_other_SOURCES QtxFontEdit.cxx QtxGridBox.cxx QtxGroupBox.cxx + QtxInfoPanel.cxx QtxIntSpinBox.cxx QtxIntSpinSlider.cxx QtxListAction.cxx diff --git a/src/Qtx/QtxDockWidget.cxx b/src/Qtx/QtxDockWidget.cxx index fc9827654..4bb125b31 100644 --- a/src/Qtx/QtxDockWidget.cxx +++ b/src/Qtx/QtxDockWidget.cxx @@ -376,13 +376,7 @@ QtxDockWidget::~QtxDockWidget() */ QSize QtxDockWidget::sizeHint() const { - QSize sz = QDockWidget::sizeHint(); - - // printf( "----------------> QtxDockWidget::sizeHint()\n" ); - return QSize( 500, 100 ); - - return sz; } /*! @@ -391,9 +385,7 @@ QSize QtxDockWidget::sizeHint() const */ QSize QtxDockWidget::minimumSizeHint() const { - QSize sz = QDockWidget::minimumSizeHint(); - - return sz; + return QDockWidget::minimumSizeHint(); } /*! @@ -415,6 +407,9 @@ void QtxDockWidget::setVisible( bool on ) else myWatcher->hidden( this ); } + + if ( on ) + emit( aboutToShow() ); } /*! diff --git a/src/Qtx/QtxDockWidget.h b/src/Qtx/QtxDockWidget.h index 53ecbb61f..048a9e64d 100644 --- a/src/Qtx/QtxDockWidget.h +++ b/src/Qtx/QtxDockWidget.h @@ -49,6 +49,7 @@ public: signals: void orientationChanged( Qt::Orientation ); + void aboutToShow(); public slots: virtual void setVisible( bool ); diff --git a/src/Qtx/QtxInfoPanel.cxx b/src/Qtx/QtxInfoPanel.cxx new file mode 100644 index 000000000..49b44ddae --- /dev/null +++ b/src/Qtx/QtxInfoPanel.cxx @@ -0,0 +1,415 @@ +// Copyright (C) 2007-2020 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "QtxInfoPanel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \internal + \class QtxInfoPanel::Container + \brief Container to store widgets within info panel +*/ +class QtxInfoPanel::Container: public QWidget +{ +public: + Container( QWidget* = 0 ); + Container( const QString&, QWidget* = 0 ); + + void addAction( QAction*, const int ); + void addLabel( const QString&, Qt::Alignment, const int ); + void addGroup( const QString&, const int ); + + QWidget* find( const int ) const; + + void remove( const int ); + void clear(); + + void put( QWidget* ); + +private: + QMap ids; + QGroupBox* group; +}; + +QtxInfoPanel::Container::Container( QWidget* parent ) + : QWidget( parent ), group( 0 ) +{ + QVBoxLayout* l = new QVBoxLayout( this ); + l->setContentsMargins( 0, 0, 0, 0 ); +} + +QtxInfoPanel::Container::Container( const QString& title, QWidget* parent ) + : Container( parent ) +{ + QVBoxLayout* l = dynamic_cast( layout() ); + group = new QGroupBox( title ); + group->setLayout( new QVBoxLayout() ); + l->addWidget( group ); +} + +void QtxInfoPanel::Container::put( QWidget* widget ) +{ + QVBoxLayout* l = group ? dynamic_cast( group->layout() ) : dynamic_cast( layout() ); + l->addWidget( widget ); +} + +void QtxInfoPanel::Container::addLabel( const QString& text, Qt::Alignment alignment, const int id ) +{ + QLabel* label = new QLabel( text ); + QFont f = label->font(); + f.setItalic( true ); + label->setFont( f ); + label->setAlignment( alignment ); + label->setWordWrap( true ); + put( label ); + ids[ id ] = label; +} + +void QtxInfoPanel::Container::addAction( QAction* action, const int id ) +{ + QToolButton* button = new QToolButton( this ); + button->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setAutoRaise( true ); + button->setDefaultAction( action ); + put( button ); + ids[ id ] = button; +} + +void QtxInfoPanel::Container::addGroup( const QString& text, const int id ) +{ + Container* group = new Container( text, this ); + put( group ); + ids[ id ] = group; +} + +QWidget* QtxInfoPanel::Container::find( const int id ) const +{ + if ( ids.contains( id ) ) + return ids[id]; + + QMap::ConstIterator it; + QWidget* widget = 0; + for( it = ids.begin(); it != ids.end() && !widget; ++it ) + { + Container* group = dynamic_cast( it.value() ); + if ( group ) + widget = group->find( id ); + } + + return widget; +} + +void QtxInfoPanel::Container::remove( const int id ) +{ + if ( ids.contains( id ) ) + { + QVBoxLayout* l = group ? dynamic_cast( group->layout() ) : dynamic_cast( layout() ); + l->removeWidget( ids[id] ); + ids[id]->deleteLater(); + l->invalidate(); + ids.remove( id ); + } +} + +void QtxInfoPanel::Container::clear() +{ + QVBoxLayout* l = group ? dynamic_cast( group->layout() ) : dynamic_cast( layout() ); + + QList widgets = ids.values(); + foreach( QWidget* widget, widgets ) + { + l->removeWidget( widget ); + widget->deleteLater(); + } + + l->invalidate(); + ids.clear(); +} + + +/*! + \internal + \class QtxInfoPanel::Title + \brief Info panel's title widget +*/ +class QtxInfoPanel::Title: public QLabel +{ +public: + Title( QWidget* parent = 0 ); +}; + +QtxInfoPanel::Title::Title( QWidget* parent ) + : QLabel( parent ) +{ + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + QString bg = palette().color( QPalette::Highlight ).name(); + QString fg = palette().color( QPalette::HighlightedText ).name(); + setStyleSheet( QString( "QLabel { background:%1; color:%2; }" ).arg( bg ).arg( fg ) ); + setTextFormat( Qt::PlainText ); + QFont f = font(); + f.setBold( true ); + setFont( f ); + setContentsMargins( 2, 5, 2, 5 ); +} + + +/*! + \class QtxInfoPanel + \brief Info panel which allows presenting welcome, useful hints + and other information dynamically, e.g. in the dock panel of main + application's window. + + The *Info panel* normally has a title (aimed to shortly present the + current application's context) and a set of buttons and text labels + combined into the groups (which may be nested). + + Buttons normally represent some quick actions which are applicable in + the current context. Text labels can be used to show additional information + like hints, proposed actions, etc. + + To set the title to the panel, use method setTitle(). Text label can be + added to the panel with addLabel() method, action (button) is added via + addAction() method. + + By default, items are added to the top level, untitled group. Additionally, + panel allows arranging items into groups; new group can be added with the + addGroup() method. + + Each of addAction(), addLabel(), addGroup() methods return item's unique + identifier. This identifier can be used, for instance, to enable/disable + item with setEnabled() method, hide/show with setVisible() method, remove + from panel with remove() method. The same action can be added to the panel + several times, e.g. to the different groups - the corresponding buttons will + have different unique ids. + + To remove all contents of panel, use clear() method. +*/ + +/*! + \brief Create panel. + \param parent Parent widget. + */ +QtxInfoPanel::QtxInfoPanel( QWidget* parent ) + : QWidget( parent ) +{ + title = new Title( this ); + container = new Container( this ); + + QVBoxLayout* layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + + QWidget* wg = new QWidget(); + QVBoxLayout* wg_layout = new QVBoxLayout( wg ); + wg_layout->setContentsMargins( 0, 0, 0, 0 ); + wg_layout->addWidget( container ); + wg_layout->addStretch(); + + QScrollArea* scroll = new QScrollArea(); + scroll->setWidgetResizable( true ); + scroll->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + scroll->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding ); + scroll->setSizeAdjustPolicy( QScrollArea::AdjustToContents ); + scroll->setFrameStyle( QScrollArea::NoFrame ); + scroll->setContentsMargins( 0, 0, 0, 0 ); + scroll->setWidget( wg ); + + layout->addWidget( title ); + layout->addWidget( scroll ); + setTitle( "" ); +} + +/*! + \brief Destructor. +*/ +QtxInfoPanel::~QtxInfoPanel() +{ +} + +/*! + \brief Add left-aligned text label to the given group. + \param text Label's text. + \param groupId Group's identifier. Value -1 (default) is used + to add label to the top-level (untitled) group. + \return Label's unique identifier. +*/ +int QtxInfoPanel::addLabel( const QString& text, const int groupId ) +{ + return addLabel( text, Qt::AlignLeft, groupId ); +} + +/*! + \brief Add text label to the given group. + \param text Label's text. + \param alignment Label's alignment. + \param groupId Group's identifier. Value -1 (default) is used + to add label to the top-level (untitled) group. + \return Label's unique identifier. +*/ +int QtxInfoPanel::addLabel( const QString& text, Qt::Alignment alignment, const int groupId ) +{ + int id = 0; + Container* c = dynamic_cast( find( groupId ) ); + if ( c ) + { + id = generateId(); + c->addLabel( text, alignment, id ); + } + return id; +} + +/*! + \brief Add action button to the given group. + \param action Action being added (note: parent is not changed). + \param groupId Group's identifier. Value -1 (default) is used + to add button to the top-level (untitled) group. + \return Button's unique identifier. +*/ +int QtxInfoPanel::addAction( QAction* action, const int groupId ) +{ + int id = 0; + Container* c = dynamic_cast( find( groupId ) ); + if ( c ) + { + id = generateId(); + c->addAction( action, id ); + } + return id; +} + +/*! + \brief Add (sub-)group to the given group. + \param text Group's title. + \param groupId Parent group's identifier. Value -1 (default) is used + to add (sub-)group to the top-level (untitled) group (i.e. panel itself). + \return Group's unique identifier. +*/ +int QtxInfoPanel::addGroup( const QString& text, const int groupId ) +{ + int id = 0; + Container* c = dynamic_cast( find( groupId ) ); + if ( c ) + { + id = generateId(); + c->addGroup( text, id ); + } + return id; +} + +/*! + \brief Set panel's title. + \param text %Title text (empty string removes title). +*/ +void QtxInfoPanel::setTitle( const QString& text ) +{ + title->setText( text ); + title->setVisible( !title->text().isEmpty() ); +} + +/*! + \brief Remove given item from panel. + \note If a group is removed, all its contents (recursively) is removed + as well. + \param id Item's (label's, button's, group's) identifier. +*/ +void QtxInfoPanel::remove( const int id ) +{ + QWidget* widget = find( id ); + if ( widget ) + { + Container* group = dynamic_cast( widget->parentWidget() ); + if ( !group ) + group = dynamic_cast( widget->parentWidget()->parentWidget() ); + if ( group ) + group->remove( id ); + } +} + +/*! + \brief Clear contents of panel of group. + \param groupId Group's identifier. Value -1 (default) is used + to clear all contents of panel. +*/ +void QtxInfoPanel::clear( const int groupId ) +{ + Container* c = dynamic_cast( find( groupId ) ); + if ( c ) + c->clear(); +} + +/*! + \internal + \brief Find widget that represents given item. + \param id Item's (label's, button's, group's) identifier. + \return Item's widget (0 if not found). +*/ +QWidget* QtxInfoPanel::find( const int id ) const +{ + if ( id == -1 ) + return container; + return container->find( id ); +} + +/*! + \brief Change item's visibility. + \param id Item's (label's, button's, group's) identifier. + \param visible \c true to show item, \c false to hide it. +*/ +void QtxInfoPanel::setVisible( const int id, bool visible ) +{ + QWidget* widget = find( id ); + if ( widget ) + widget->setVisible( visible ); +} + +/*! + \brief Enable / disable item. + \param id Item's (label's, button's, group's) identifier. + \param enabled \c true to enable item, \c false to disable it. +*/ +void QtxInfoPanel::setEnabled( const int id, bool enabled ) +{ + QWidget* widget = find( id ); + if ( widget ) + widget->setEnabled( enabled ); +} + +/*! + \internal + \brief Generate new unique identifier. + \return Unique identifier. +*/ +int QtxInfoPanel::generateId() const +{ + static int id = -100; + return --id; +} diff --git a/src/Qtx/QtxInfoPanel.h b/src/Qtx/QtxInfoPanel.h new file mode 100644 index 000000000..4f4120dd6 --- /dev/null +++ b/src/Qtx/QtxInfoPanel.h @@ -0,0 +1,72 @@ +// Copyright (C) 2007-2020 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef QTXINFOPANEL_H +#define QTXINFOPANEL_H + +#include "Qtx.h" + +#include + +#ifdef WIN32 +#pragma warning( disable:4251 ) +#endif + +class QAction; + +class QTX_EXPORT QtxInfoPanel : public QWidget +{ + Q_OBJECT + + class Container; + class Title; + +public: + QtxInfoPanel( QWidget* = 0 ); + ~QtxInfoPanel(); + + void setTitle( const QString& ); + int addLabel( const QString&, const int = -1 ); + int addLabel( const QString&, Qt::Alignment, const int = -1 ); + int addAction( QAction*, const int = -1 ); + int addGroup( const QString&, const int = -1 ); + + void remove( const int ); + void clear( const int = -1 ); + + void setVisible( const int, bool ); + void setEnabled( const int, bool ); + +private: + int generateId() const; + QWidget* find( const int ) const; + +private: + Title* title; + Container* container; +}; + +#ifdef WIN32 +#pragma warning( default:4251 ) +#endif + +#endif // QTXINFOPANEL_H diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx index e243c2d4a..55d8e32bb 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx @@ -56,6 +56,7 @@ #include "QtxActionMenuMgr.h" #include "QtxWorkstack.h" #include "QtxTreeView.h" +#include "QtxInfoPanel.h" #include "SALOME_Event.h" #include "STD_TabDesktop.h" #include "SUIT_DataBrowser.h" @@ -2903,6 +2904,256 @@ void SalomePyQt::message( const QString& msg, bool addSeparator ) ProcessVoidEvent( new TEvent( msg, addSeparator ) ); } +/*! + \brief Set the title to the Help panel. + \param title Title text (empty string removes title) +*/ +void SalomePyQt::infoSetTitle( const QString& title ) +{ + class TEvent: public SALOME_Event + { + QString myTitle; + public: + TEvent( const QString& title ) + : myTitle( title ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + ip->setTitle( myTitle ); + } + } + }; + ProcessVoidEvent( new TEvent( title ) ); +} + +/*! + \fn int SalomePyQt::infoAddLabel( const QString& text, const int groupId ) + \brief Insert left-aligned text label into the Help panel + \param text Label text + \param groupId Parent group's identifier (defaults to -1 for top-level group) + \return Label's identifier +*/ + +class TInfoAddLabel2paramEvent: public SALOME_Event +{ +public: + typedef int TResult; + TResult myResult; + QString myText; + int myGroupId; + TInfoAddLabel2paramEvent( const QString& text, const int groupId ) + : myText( text ), myGroupId( groupId ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + myResult = ip->addLabel( myText, myGroupId ); + } + } +}; +int SalomePyQt::infoAddLabel( const QString& text, const int groupId ) +{ + return ProcessEvent( new TInfoAddLabel2paramEvent( text, groupId ) ); +} + +/*! + \fn int SalomePyQt::infoAddLabel( const QString& text, Qt::Alignment alignment, const int groupId ) + \brief Insert text label into the Help panel + \param text Label text + \param alignment Alignment flag for text label + \param groupId Parent group's identifier (defaults to -1 for top-level group) + \return Label's identifier +*/ + +class TInfoAddLabel3paramEvent: public SALOME_Event +{ +public: + typedef int TResult; + TResult myResult; + QString myText; + Qt::Alignment myAlignment; + int myGroupId; + TInfoAddLabel3paramEvent( const QString& text, Qt::Alignment alignment, const int groupId ) + : myText( text ), myAlignment( alignment ), myGroupId( groupId ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + myResult = ip->addLabel( myText, myAlignment, myGroupId ); + } + } +}; +int SalomePyQt::infoAddLabel( const QString& text, Qt::Alignment alignment, const int groupId ) +{ + return ProcessEvent( new TInfoAddLabel3paramEvent( text, alignment, groupId ) ); +} + +/*! + \fn int SalomePyQt::infoAddAction( QAction* action, const int groupId ) + \brief Insert action button into the Help panel + \param action Action being added + \param groupId Parent group's identifier (defaults to -1 for top-level group) + \return Action's identifier +*/ + +class TInfoAddActionEvent: public SALOME_Event +{ +public: + typedef int TResult; + TResult myResult; + QAction* myAction; + int myGroupId; + TInfoAddActionEvent( QAction* action, const int groupId ) + : myAction( action ), myGroupId( groupId ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + myResult = ip->addAction( myAction, myGroupId ); + } + } +}; +int SalomePyQt::infoAddAction( QAction* action, const int groupId ) +{ + return ProcessEvent( new TInfoAddActionEvent( action, groupId ) ); +} + +/*! + \fn int SalomePyQt::infoAddGroup( const QString& text, const int groupId ) + \brief Create a (sub-)group in the Help panel + \param text Group title + \param groupId Parent group's identifier (defaults to -1 for top-level group) + \return Group's identifier +*/ + +class TInfoAddGroupEvent: public SALOME_Event +{ +public: + typedef int TResult; + TResult myResult; + QString myText; + int myGroupId; + TInfoAddGroupEvent( const QString& text, const int groupId ) + : myText( text ), myGroupId( groupId ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + myResult = ip->addGroup( myText, myGroupId ); + } + } +}; +int SalomePyQt::infoAddGroup( const QString& text, const int groupId ) +{ + return ProcessEvent( new TInfoAddGroupEvent( text, groupId ) ); +} + +/*! + \brief Remove item from the Help panel + \param id Item's (label's, action's, group's, ...) identifier +*/ +void SalomePyQt::infoRemove( const int id ) +{ + class TEvent: public SALOME_Event + { + int myId; + public: + TEvent( const int id ) + : myId( id ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + ip->remove( myId ); + } + } + }; + ProcessVoidEvent( new TEvent( id ) ); +} + +/*! + \brief Clear Help panel's contents + \param groupId Group's identifier (default is -1, to clear whole panel) +*/ +void SalomePyQt::infoClear( const int groupId ) +{ + class TEvent: public SALOME_Event + { + int myGroupId; + public: + TEvent( const int groupId ) + : myGroupId( groupId ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + ip->clear( myGroupId ); + } + } + }; + ProcessVoidEvent( new TEvent( groupId ) ); +} + +/*! + \brief Set item's visibility in the Help panel + \param id Item's (label's, action's, group's, ...) identifier + \param visible Visibility flag +*/ +void SalomePyQt::infoSetVisible( const int id, bool visible ) +{ + class TEvent: public SALOME_Event + { + int myId; + bool myVisible; + public: + TEvent( const int id, bool visible ) + : myId( id ), myVisible( visible ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + ip->setVisible( myId, myVisible ); + } + } + }; + ProcessVoidEvent( new TEvent( id, visible ) ); +} + +/*! + \brief Enable/disable item in the Help panel + \param id Item's (label's, action's, group's, ...) identifier + \param enabled Enabled state +*/ +void SalomePyQt::infoSetEnabled( const int id, bool enabled ) +{ + class TEvent: public SALOME_Event + { + int myId; + bool myEnabled; + public: + TEvent( const int id, bool enabled ) + : myId( id ), myEnabled( enabled ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + QtxInfoPanel* ip = anApp->infoPanel(); + if ( ip ) + ip->setEnabled( myId, myEnabled ); + } + } + }; + ProcessVoidEvent( new TEvent( id, enabled ) ); +} + /*! \brief Remove all the messages from the Log messages output window. */ diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h index 34089b95b..82ecb8173 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h @@ -89,6 +89,7 @@ enum { WT_ObjectBrowser = LightApp_Application::WT_ObjectBrowser, WT_PyConsole = LightApp_Application::WT_PyConsole, WT_LogWindow = LightApp_Application::WT_LogWindow, + WT_InfoPanel = LightApp_Application::WT_InfoPanel, #ifndef GUI_DISABLE_CORBA WT_NoteBook = SalomeApp_Application::WT_NoteBook, WT_User = SalomeApp_Application::WT_User @@ -199,6 +200,18 @@ public: static void registerModule( const QString& ); static void updateObjBrowser(); + static void infoSetTitle( const QString& ); + static int infoAddLabel( const QString&, const int = -1 ); + static int infoAddLabel( const QString&, Qt::Alignment, const int = -1 ); + static int infoAddAction( QAction*, const int = -1 ); + static int infoAddGroup( const QString&, const int = -1 ); + + static void infoRemove( const int ); + static void infoClear( const int = -1 ); + + static void infoSetVisible( const int, bool ); + static void infoSetEnabled( const int, bool ); + static void putInfo( const QString&, const int = 0 ); static int showNotification( const QString&, const QString&, const int = -1 ); static void hideNotification( const QString& ); diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip index ed20aafac..bc30e3450 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip @@ -79,6 +79,7 @@ enum WindowType { WT_ObjectBrowser, WT_PyConsole, WT_LogWindow, + WT_InfoPanel, %If (ENABLE_CORBA) WT_NoteBook, %End @@ -310,6 +311,18 @@ public: static void registerModule( const QString& ) /ReleaseGIL/ ; static void updateObjBrowser() /ReleaseGIL/ ; + static void infoSetTitle( const QString& ) /ReleaseGIL/ ; + static int infoAddLabel( const QString&, const int = -1 ) /ReleaseGIL/ ; + static int infoAddLabel( const QString&, Qt::Alignment, const int = -1 ) /ReleaseGIL/ ; + static int infoAddAction( QAction*, const int = -1 ) /ReleaseGIL/ ; + static int infoAddGroup( const QString&, const int = -1 ) /ReleaseGIL/ ; + + static void infoRemove( const int ) /ReleaseGIL/ ; + static void infoClear( const int = -1 ) /ReleaseGIL/ ; + + static void infoSetVisible( const int, bool ) /ReleaseGIL/ ; + static void infoSetEnabled( const int, bool ) /ReleaseGIL/ ; + static void putInfo( const QString&, const int = 0 ) /ReleaseGIL/ ; static int showNotification( const QString&, const QString&, const int = -1 ) /ReleaseGIL/ ; static void hideNotification( const QString& ) /ReleaseGIL/ ; diff --git a/src/STD/resources/STD_msg_en.ts b/src/STD/resources/STD_msg_en.ts index 382d9db5a..0956e8f71 100644 --- a/src/STD/resources/STD_msg_en.ts +++ b/src/STD/resources/STD_msg_en.ts @@ -144,11 +144,11 @@ Directory with this name exist on disc. Try to use another name PRP_DESK_FILE_NEW - Creates a new document + Create a new document PRP_DESK_FILE_MRU - Opens a document + Open a document MEN_DESK_FILE_NEW @@ -376,7 +376,7 @@ Do you want to overwrite it? PRP_DESK_FILE_OPEN - Opens an existing document + Open an existing document PRP_DESK_FILE_REOPEN @@ -384,7 +384,7 @@ Do you want to overwrite it? PRP_DESK_FILE_SAVE - Saves the active document + Save the active document PRP_DESK_WINDOW_HTILE -- 2.39.2