Salome HOME
First integration of a new PV3D viewer.
[modules/gui.git] / src / LightApp / LightApp_Application.cxx
index 249ce2fd1a3f12f2a58ff382335163dbd5bc750c..4758aa40e0d85764065ab849bcf113cc330fb8e9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2020  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2022  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
   #include <PyViewer_ViewWindow.h>
 #endif
 
+#ifndef DISABLE_PV3DVIEWER
+#ifndef DISABLE_SALOMEOBJECT
+  #include <SPV3D_ViewModel.h>
+  #include <SPV3D_ViewManager.h>
+  #include "LightApp_PV3DSelector.h"
+#else
+  #include <PV3DViewer_ViewModel.h>
+  #include <PV3DViewer_ViewManager.h>
+#endif
+  #include <PV3DViewer_ViewManager.h>
+  #include <PV3DViewer_ViewModel.h>
+  #include "PV3DViewer_ViewWindow.h"
+#endif
+
 
 #define VISIBILITY_COLUMN_WIDTH 25
 
 #include <utilities.h>
 
 #define FIRST_HELP_ID 1000000
+#define HAS_WWW_URL true
+#define HAS_FORUM_URL true
+#define HAS_YOUTUBE_URL true
+#define HAS_TUTORIAL_URL false
 
 #ifndef DISABLE_SALOMEOBJECT
   #include <SALOME_InteractiveObject.hxx>
@@ -301,6 +319,31 @@ namespace
       result = QLocale( lang ).nativeLanguageName();
     return result;
   }
+
+  QString getHelpItem( const QString& section, const QString& parameter, const QString& root = QString() )
+  {
+    SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
+    foreach( QString item, resMgr->stringValue( section, parameter ).split( ";;", QString::SkipEmptyParts ) )
+    {
+      if ( item.startsWith( "http", Qt::CaseInsensitive ) )
+        return item;
+      QString path = item;
+      path.remove( QRegExp( "#.*$" ) );
+      QFileInfo fi( path );
+      if ( fi.isRelative() && !root.isEmpty() )
+        path = Qtx::addSlash( root ) + path;
+      if ( QFile::exists( path ) )
+        return item;
+    }
+    return QString();
+  }
+
+  const bool HAS_SALOME_ON_DEMAND =
+#if defined(WITH_SALOME_ON_DEMAND)
+    true;
+#else
+    false;
+#endif
 }
 
 /*!Create new instance of LightApp_Application.*/
@@ -520,17 +563,21 @@ bool LightApp_Application::activateModule( const QString& modName )
   if ( prevMod )
     actName = prevMod->moduleName();
 
-  if ( actName == modName )
+  QString name = modName;
+  if ( !name.isEmpty() && !moduleTitle( modName ).isEmpty() )
+    name = moduleTitle( modName );
+
+  if ( actName == name )
     return true;
 
-  putInfo( tr( "ACTIVATING_MODULE" ).arg( modName ) );
+  putInfo( tr( "ACTIVATING_MODULE" ).arg( name ) );
 
   saveDockWindowsState();
 
   if ( infoPanel() )
     infoPanel()->clear();
 
-  bool status = CAM_Application::activateModule( modName );
+  bool status = CAM_Application::activateModule( name );
 
   updateModuleActions();
 
@@ -599,177 +646,91 @@ void LightApp_Application::createActions()
 
   int helpMenu = createMenu( tr( "MEN_DESK_HELP" ), -1, -1, 1000 );
 
-  int id = LightApp_Application::UserID + FIRST_HELP_ID;
+  QString url;
 
   // a) Link to web site
-  QString url = resMgr->stringValue("GUI", "site_url");
-  if ( !url.isEmpty() ) {
-    QString title = tr ( "SALOME_SITE" );
-    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 );
+  if ( HAS_WWW_URL ) {
+    url = resMgr->stringValue("GUI", "site_url");
+    if ( !url.isEmpty() ) {
+      QString title = tr ( "SALOME_SITE" );
+      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 );
+    }
   }
 
   // b) Link to Forum
-  url = resMgr->stringValue("GUI", "forum_url");
-  if ( !url.isEmpty() ) {
-    QString title = tr ( "SALOME_FORUM" );
-    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 );
+  if ( HAS_FORUM_URL ) {
+    url = resMgr->stringValue("GUI", "forum_url");
+    if ( !url.isEmpty() ) {
+      QString title = tr ( "SALOME_FORUM" );
+      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 );
+    }
   }
 
   // c) Link to YouTube channel
-  url = resMgr->stringValue("GUI", "channel_url");
-  if ( !url.isEmpty() ) {
-    createMenu( separator(), helpMenu, -1, 0 );
-    QString title = tr ( "SALOME_VIDEO_TUTORIALS" );
-    QAction* av = createAction( VideosId, title,
-                               resMgr->loadPixmap( "LightApp", tr( "ICON_LIFE_RIGN" ), false ),
-                               title, tr( "PRP_SALOME_VIDEO_TUTORIALS" ),
-                               0, desk, false, this, SLOT( onHelpContentsModule() ) );
-    av->setData( url );
-    createMenu( av, helpMenu, -1, 0 );
+  if ( HAS_YOUTUBE_URL ) {
+    url = resMgr->stringValue("GUI", "channel_url");
+    if ( !url.isEmpty() ) {
+      createMenu( separator(), helpMenu, -1, 0 );
+      QString title = tr ( "SALOME_VIDEO_TUTORIALS" );
+      QAction* av = createAction( VideosId, title,
+                                  resMgr->loadPixmap( "LightApp", tr( "ICON_LIFE_RIGN" ), false ),
+                                  title, tr( "PRP_SALOME_VIDEO_TUTORIALS" ),
+                                  0, desk, false, this, SLOT( onHelpContentsModule() ) );
+      av->setData( url );
+      createMenu( av, helpMenu, -1, 0 );
+    }
   }
 
   // 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 );
+  if ( HAS_TUTORIAL_URL ) {
+    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";
-  QString devGuide = "Developer's Guide";
-  createMenu( userGuide, helpMenu, -1, 5 );
-  createMenu( devGuide, helpMenu, -1, 5 );
-
   QStringList aModuleList;
   modules( aModuleList, false );
   aModuleList.prepend( "GUI" );
   aModuleList.prepend( "KERNEL" );
 
-  QString aModule;
-  foreach( aModule, aModuleList ) {
-    if ( aModule.isEmpty() )                                         // module title (user name)
-      continue;
-    IMap <QString, QString> helpData;                                // list of help files for the module
-    QString helpSubMenu;                                             // help submenu name (empty if not needed)
-    QString modName = moduleName( aModule );                         // module name
-    if ( modName.isEmpty() ) modName = aModule;                      // for KERNEL and GUI
-    QString rootDir = QString( "%1_ROOT_DIR" ).arg( modName );       // module root dir env variable
-    QString modDir  = Qtx::getenv( rootDir.toUtf8().constData() );        // module root dir path
-    QString docSection;
-    if (resMgr->hasValue( modName, "documentation" ) )
-      docSection = resMgr->stringValue(modName, "documentation");
-    else if ( resMgr->hasSection( modName + "_documentation" ) )
-      docSection = modName + "_documentation";
-    if ( !docSection.isEmpty() ) {
-      helpSubMenu = resMgr->stringValue( docSection, "sub_menu", "" );
-      if ( helpSubMenu.contains( "%1" ) )
-        helpSubMenu = helpSubMenu.arg( aModule );
-      QStringList listOfParam = resMgr->parameters( docSection );
-      foreach( QString paramName, listOfParam ) {
-        QString valueStr = resMgr->stringValue( docSection, paramName );
-        if ( !valueStr.isEmpty() ) {
-          QStringList valueItems = valueStr.split( ";;", QString::SkipEmptyParts );
-          foreach( QString item, valueItems ) {
-            if ( item.startsWith( "http", Qt::CaseInsensitive ) ) {
-              QString key = paramName.contains( "%1" ) ? paramName.arg( aModule ) : paramName;
-              helpData.insert( key, item );
-              break;
-            }
-            else {
-              QFileInfo fi( item );
-              if ( fi.isRelative() && !modDir.isEmpty() )
-                item = Qtx::addSlash( modDir ) + item;
-              if ( QFile::exists( item ) ) {
-                QString key = paramName.contains( "%1" ) ? paramName.arg( aModule ) : paramName;
-                helpData.insert( key, item );
-                break;
-              }
-            }
-          }
-        }
-      }
-    }
-
-    if ( helpData.isEmpty() && !modDir.isEmpty() ) {
-      QStringList idxLst = QStringList() << modDir << "share" << "doc" << "salome" << "gui" << modName << "index.html";
-      QString indexFile = idxLst.join( QDir::separator() );          // index file
-      if ( QFile::exists( indexFile ) )
-        helpData.insert( tr( "%1 module Users's Guide" ).arg( aModule ), indexFile );
-    }
-
-    IMapConstIterator<QString, QString > fileIt;
-    for ( fileIt = helpData.begin(); fileIt != helpData.end(); fileIt++ ) {
-      QString helpItemPath = fileIt.key();
-      // remove all '//' occurances
-      while ( helpItemPath.contains( "//" ) )
-        helpItemPath.replace( "//", "" );
-      // obtain submenus hierarchy if given
-      QStringList smenus = helpItemPath.split( "/" );
-      helpItemPath = smenus.takeLast();
-      // workaround for User's Guide and Developer's Guide to avoid having single item in module's submenu.
-      if ( helpItemPath == userGuide || helpItemPath == devGuide ) {
-       QString menuPath = smenus.join( "/" );
-       QStringList allKeys = helpData.keys();
-       QStringList total = allKeys.filter( QRegExp( QString( "^%1" ).arg( menuPath ) ) );
-       if ( total.count() == 1 && smenus.count() > 0 )
-         helpItemPath = smenus.takeLast();
-      }
-      QPixmap helpIcon = fileIt.value().startsWith( "http", Qt::CaseInsensitive ) ?
-        resMgr->loadPixmap( "STD", tr( "ICON_WWW" ), false ) : resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false );
-      QAction* a = createAction( id, helpItemPath, helpIcon, helpItemPath, helpItemPath,
-                                 0, desk, false, this, SLOT( onHelpContentsModule() ) );
-      a->setData( fileIt.value() );
-      if ( !helpSubMenu.isEmpty() ) {
-        smenus.prepend( helpSubMenu );
-      }
-      // create sub-menus hierarchy
-      int menuId = helpMenu;
-      foreach ( QString subMenu, smenus )
-        menuId = createMenu( subMenu, menuId, -1, 5 );
-      createMenu( a, menuId, -1, ( menuId != helpMenu && (helpItemPath == userGuide || helpItemPath == devGuide) ) ? 0 : 5 );
-      id++;
-    }
-  }
-
-  // - Additional help items
+  foreach( QString aModule, aModuleList )
+    createHelpItems( aModule );
+  
+  // f) Additional help items
 
+  int id = LightApp_Application::UserID + FIRST_HELP_ID + 1000;
   createMenu( separator(), helpMenu, -1, 10 );
 
   QStringList addHelpItems = resMgr->parameters( "add_help" );
   foreach ( QString paramName, addHelpItems ) {
-    QString valueStr = resMgr->stringValue( "add_help", paramName );
-    if ( !valueStr.isEmpty() ) {
-      QStringList valueItems = valueStr.split( ";;", QString::SkipEmptyParts );
-      foreach( QString item, valueItems ) {
-        if ( item.startsWith( "http", Qt::CaseInsensitive ) || QFile::exists( item ) ) {
-          QPixmap helpIcon = item.startsWith( "http", Qt::CaseInsensitive ) ?
-            resMgr->loadPixmap( "STD", tr( "ICON_WWW" ), false ) : resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false );
-          QAction* a = createAction( id++, paramName, helpIcon, paramName, paramName,
-                                     0, desk, false, this, SLOT( onHelpContentsModule() ) );
-          a->setData( item );
-          createMenu( a, helpMenu, -1, 10 );
-          break;
-        }
-      }
+    QString helpItem = getHelpItem( "add_help", paramName );
+    if ( !helpItem.isEmpty() )
+    {
+      QPixmap helpIcon = helpItem.startsWith( "http", Qt::CaseInsensitive ) ?
+        resMgr->loadPixmap( "STD", tr( "ICON_WWW" ), false ) : resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false );
+      QAction* a = createAction( id++, paramName, helpIcon, paramName, paramName,
+                                 0, desk, false, this, SLOT( onHelpContentsModule() ) );
+      a->setData( helpItem );
+      createMenu( a, helpMenu, -1, 10 );
     }
   }
 
@@ -778,57 +739,24 @@ void LightApp_Application::createActions()
   connect( mru, SIGNAL( activated( const QString& ) ), this, SLOT( onMRUActivated( const QString& ) ) );
   registerAction( MRUId, mru );
 
-  // default icon for neutral point ('SALOME' module)
-  QPixmap defIcon = resMgr->loadPixmap( "LightApp", tr( "APP_DEFAULT_ICO" ), false );
-  if ( defIcon.isNull() )
-    defIcon = QPixmap( imageEmptyIcon );
-
-  //! default icon for any module
-  QPixmap modIcon = resMgr->loadPixmap( "LightApp", tr( "APP_MODULE_ICO" ), false );
-  if ( modIcon.isNull() )
-    modIcon = QPixmap( imageEmptyIcon );
-
+  // List of modules
+  LightApp_ModuleAction* moduleAction = new LightApp_ModuleAction( resMgr, desk );
+  registerAction( ModulesListId, moduleAction );
+  // a. here we add regular modules (specified to GUI via --modules cmd line option, or default list from configuration)
+  // b. custom modules are added in customize() method
   QStringList modList;
   modules( modList, false );
-
-  if ( modList.count() > 1 )
-  {
-    LightApp_ModuleAction* moduleAction =
-      new LightApp_ModuleAction( tr( "APP_NAME" ), defIcon, desk );
-
-    QMap<QString, QString> iconMap;
-    moduleIconNames( iconMap );
-
-    const int iconSize = 20;
-
-    QStringList::Iterator it;
-    for ( it = modList.begin(); it != modList.end(); ++it )
-    {
-      QString modName = moduleName( *it );
-
-      QString iconName;
-      if ( iconMap.contains( *it ) )
-        iconName = iconMap[*it];
-
-      QPixmap icon = resMgr->loadPixmap( modName, iconName, false );
-      if ( icon.isNull() )
-      {
-        icon = modIcon;
-        INFOS( std::endl <<
-               "****************************************************************" << std::endl <<
-               "     Warning: icon for " << qPrintable(*it) << " is not found!" << std::endl <<
-               "     Using the default icon." << std::endl <<
-               "****************************************************************" << std::endl);
-      }
-      icon = Qtx::scaleIcon( icon, iconSize );
-
-      moduleAction->insertModule( *it, icon );
-    }
-
-    connect( moduleAction, SIGNAL( moduleActivated( const QString& ) ),
-             this, SLOT( onModuleActivation( const QString& ) ) );
-    registerAction( ModulesListId, moduleAction );
-  }
+  foreach ( QString aModule, modList )
+    moduleAction->insertModule( aModule, moduleIcon( aModule, 20 ) ); // scale icon to 20x20 pix
+
+  connect( this, SIGNAL( moduleActivated( QString ) ),
+           moduleAction, SLOT( setActiveModule( QString ) ) );
+  connect( moduleAction, SIGNAL( moduleActivated( const QString& ) ),
+           this, SLOT( onModuleActivation( const QString& ) ) );
+  connect( moduleAction, SIGNAL( adding() ),
+           this, SLOT( onModuleAdding() ) );
+  connect( moduleAction, SIGNAL( removing( QString ) ),
+           this, SLOT( onModuleRemoving( QString ) ) );
 
   // New window
   int windowMenu = createMenu( tr( "MEN_DESK_WINDOW" ), -1, MenuWindowId, 100 );
@@ -870,6 +798,9 @@ void LightApp_Application::createActions()
 #ifndef DISABLE_PYVIEWER
   createActionForViewer( NewPyViewerId, newWinMenu, QString::number( 7 ), Qt::ALT+Qt::Key_Y );
 #endif
+#ifndef DISABLE_PV3DVIEWER
+  createActionForViewer( NewPV3DViewId, newWinMenu, QString::number( 8 ), Qt::ALT+Qt::Key_3 );
+#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() ) );
@@ -898,26 +829,44 @@ void LightApp_Application::createActions()
   createMenu( StyleId, viewMenu, 20, -1 );
 #endif // USE_SALOME_STYLE
   createMenu( FullScreenId, viewMenu, 20, -1 );
+  createMenu( separator(), viewMenu, -1, 20, -1 );
+  createMenu( ModulesListId, viewMenu );
 
   int modTBar = createTool( tr( "INF_TOOLBAR_MODULES" ),    // title (language-dependant)
                             QString( "SalomeModules" ) );   // name (language-independant)
   createTool( ModulesListId, modTBar );
 }
 
+/*!
+  Customize actions.
+*/
+void LightApp_Application::customize()
+{
+  // List of modules
+  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
+  // a. regular modules were added in createActions() method
+  // b. here we add custom modules (manually added by the user)
+  if ( HAS_SALOME_ON_DEMAND )
+  {
+    QStringList modList = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts );
+    foreach ( QString aModule, modList )
+      addUserModule(  aModule, resourceMgr()->stringValue( "user_modules", aModule ) );
+  }
+  else
+  {
+    moduleAction->setModeEnabled( LightApp_ModuleAction::AddRemove, false );
+  }
+}
+
 /*!On module activation action.*/
-void LightApp_Application::onModuleActivation( const QString& modName )
+void LightApp_Application::onModuleActivation( const QString& modTitle )
 {
   // Force user to create/open a study before module activation
-  QMap<QString, QString> iconMap;
-  moduleIconNames( iconMap );
-  QPixmap icon = resourceMgr()->loadPixmap( moduleName( modName ), iconMap[ modName ], false );
-  if ( icon.isNull() )
-    icon = resourceMgr()->loadPixmap( "LightApp", tr( "APP_MODULE_BIG_ICO" ), false ); // default icon for any module
-
+  QPixmap icon = moduleIcon( modTitle );
   bool cancelled = false;
 
-  while ( !modName.isEmpty() && !activeStudy() && !cancelled ){
-    LightApp_ModuleDlg aDlg( desktop(), modName, icon );
+  while ( !modTitle.isEmpty() && !activeStudy() && !cancelled ){
+    LightApp_ModuleDlg aDlg( desktop(), modTitle, icon );
     QMap<int, QString> opmap = activateModuleActions();
     for ( QMap<int, QString>::ConstIterator it = opmap.begin(); it != opmap.end(); ++it )
       aDlg.addButton( it.value(), it.key() );
@@ -930,17 +879,186 @@ void LightApp_Application::onModuleActivation( const QString& modName )
     else {
       // cancelled
       putInfo( tr("INF_CANCELLED") );
-
-      LightApp_ModuleAction* moduleAction =
-        qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
-      if ( moduleAction )
-        moduleAction->setActiveModule( QString() );
+      emit moduleActivated( QString() );
       cancelled = true;
     }
   }
 
   if ( !cancelled )
-    activateModule( modName );
+    activateModule( modTitle );
+}
+
+/*!On module adding action.*/
+void LightApp_Application::onModuleAdding()
+{
+  // show dialog to browse configuration file
+  QStringList filters = ( QStringList() << tr( "Config files") + " (*.salomex)" << tr( "All files" ) + " (*)" );
+  QStringList paths = getOpenFileNames( QString(), filters.join( ";;" ), QString(), desktop() );
+  if ( paths.isEmpty() ) // cancelled
+    return;
+
+  // loop via selected configuration files
+  foreach( QString path, paths )
+  {
+    // read description file (.salomex) and check it's OK
+    QtxResourceMgr resMgr;
+    if ( !resMgr.addResource( path ) )
+    {
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_SALOMEX_FILE" ).arg( path ) );
+      continue;
+    }
+    // retrieve module name
+    QString name = resMgr.stringValue( "General", "name" ).trimmed();
+    if ( name.isEmpty() )
+    {
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_NAME" ).arg( path ) );
+      continue;
+    }
+    // retrieve root directory
+    QString root = resMgr.stringValue( "General", "root" ).trimmed();
+    if ( root.isEmpty() )
+    {
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_ROOT" ).arg( path ) );
+      continue;
+    }
+    addUserModule( name, root, true );
+  }
+}
+
+/*Add user module.*/
+bool LightApp_Application::addUserModule( const QString& name, const QString& root, bool interactive )
+{
+  if ( name.isEmpty() || root.isEmpty() )
+    return false;
+
+  if ( !moduleTitle( name ).isEmpty() ) // module alread in current session
+  {
+    if ( interactive )
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_DUPLICATED" ).arg( name ) );
+    return false;
+  }
+  if ( !QFileInfo( root ).exists() ) // root directory does not exist
+  {
+    if ( interactive )
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_ROOT_DOES_NOT_EXIST" ).arg( root ) );
+    return false;
+  }
+  // resources directory
+  QString resDir = Qtx::joinPath( QStringList() << root << "share" << "salome" << "resources" << name.toLower() );
+  if ( !QFileInfo( resDir ).exists() ) // resources directory does not exist
+  {
+    if ( interactive )
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_RESDIR" ).arg( resDir ) );
+    return false;
+  }
+  // read XML configuration file
+  resourceMgr()->setConstant( QString( "%1_ROOT_DIR" ).arg( name ), root );
+  if ( !resourceMgr()->addResource( resDir ) ) // cannot read configuration
+  {
+    if ( interactive )
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_CANNOT_READ_CFG" ).arg( resDir ) );
+    return false;
+  }
+  // fill in information about module
+  if ( !appendModuleInfo( name ) ) // cannot append module information to internal table
+  {
+    if ( interactive )
+      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_CFG_FILE" ).arg( name ) );
+    return false;
+  }
+  // load translations
+  resourceMgr()->loadLanguage( name );
+  // append module to the menu / toolbar
+  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
+  if ( moduleAction )
+    moduleAction->insertModule( moduleTitle( name ), moduleIcon( moduleTitle( name ), 20 ), true ); // scale icon to 20x20 pix
+  // add empty page to Preferences dialog
+  LightApp_Preferences* prefs = preferences();
+  if ( prefs && !prefs->hasModule( moduleTitle( name ) ) )
+  {
+    int prefId = prefs->addPreference( moduleTitle( name ) );
+    prefs->setItemIcon( prefId, moduleIcon( moduleTitle( name ), 20 ) ); // scale icon to 20x20 pix
+    LightApp_Module* m = qobject_cast<LightApp_Module*>( module( moduleTitle( name ) ) );
+    if ( m )
+    {
+      m->createPreferences();
+      emptyPreferences( moduleTitle( name ) );
+    }
+  }
+  // add Help items
+  createHelpItems( moduleTitle( name ) );
+  // extend module catalog
+  QString catalogue = QDir( resDir ).filePath( QString( "%1Catalog.xml" ).arg( name ) );
+  addCatalogue( name, catalogue );
+  // update windows (in particular, Info panel)
+  updateWindows();
+  // save module in the resource manager
+  if ( interactive )
+  {
+    QStringList customModules = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts );
+    customModules << name;
+    customModules.removeDuplicates();
+    resourceMgr()->setValue( "launch", "user_modules", customModules.join( ";" ) );
+    resourceMgr()->setValue( "user_modules", name, root );
+  }
+  return true;
+}
+
+/*!On module removing action.*/
+void LightApp_Application::onModuleRemoving( const QString& title )
+{
+  QString root = resourceMgr()->stringValue( "user_modules", moduleName( title ) );
+  QDir rootDirectory = QDir( root );
+
+  if ( rootDirectory.exists() )
+  {
+    int answer = SUIT_MessageBox::question( desktop(),
+                                           tr( "TLT_REMOVE_MODULE" ),
+                                           tr( "QUE_REMOVE_MODULE_DIR" ).arg( root ),
+                                           SUIT_MessageBox::Yes | SUIT_MessageBox::No | SUIT_MessageBox::Cancel,
+                                           SUIT_MessageBox::No );
+    if ( answer == SUIT_MessageBox::Cancel )
+      return; // cancelled
+    if ( answer == SUIT_MessageBox::Yes )
+    {
+      if ( activeStudy() && activeStudy()->isModified() && !onSaveDoc() )
+        // doc is not saved, or saving cancelled
+        return;
+      if ( !rootDirectory.removeRecursively() )
+      {
+        // canont remove directory
+        if ( SUIT_MessageBox::question( desktop(),
+                                        tr( "WRN_WARNING" ),
+                                        tr( "WRN_CANNOT_REMOVE_DIR" ).arg( root ),
+                                        SUIT_MessageBox::Yes | SUIT_MessageBox::No,
+                                        SUIT_MessageBox::No ) == SUIT_MessageBox::No )
+        return; // removal is cancelled
+      }
+    }
+  }
+
+  if ( activeModule() && activeModule()->moduleName() == title )
+    activateModule( "" );
+
+  // remove from "Modules" menu and toolbar
+  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
+  if ( moduleAction )
+  {
+    moduleAction->removeModule( title );
+  }
+  // remove Help menu items
+  removeHelpItems( title );
+  // remove Preferences
+  LightApp_Preferences* prefs = preferences();
+  if ( prefs )
+    prefs->removeModule( title );
+  // remove settings
+  QStringList customModules = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts );
+  customModules.removeAll( moduleName( title ) );
+  resourceMgr()->setValue( "launch", "user_modules", customModules.join( ";" ) );
+  removeModuleInfo( moduleName( title ) );
+  // update windows (in particular, Info panel)
+  updateWindows();
 }
 
 /*!Default module activation.*/
@@ -1003,6 +1121,11 @@ void LightApp_Application::onNewWindow()
   case NewPyViewerId:
     type = PyViewer_Viewer::Type();
     break;
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  case NewPV3DViewId:
+    type = PV3DViewer_ViewModel::Type();
+    break;
 #endif
   }
 
@@ -1177,6 +1300,12 @@ void LightApp_Application::updateCommandsStatus()
   if( a )
     a->setEnabled( activeStudy() );
 #endif
+
+#ifndef DISABLE_PV3DVIEWER
+  a = action( NewPV3DViewId );
+  if( a )
+    a->setEnabled( activeStudy() );
+#endif
 }
 
 /*!
@@ -1208,7 +1337,7 @@ protected:
     {
       // normalize path
       if ( myUrl.startsWith( "file://", Qt::CaseInsensitive ) )
-       myUrl = myUrl.remove( 0, QString( "file://" ).count() );
+        myUrl = myUrl.remove( 0, QString( "file://" ).count() );
       // For the external browser we always specify 'file://' protocol,
       // because some web browsers (e.g. Mozilla Firefox) can't open local file without protocol.
       myUrl = myUrl.prepend( "file://" );
@@ -1323,7 +1452,7 @@ void LightApp_Application::onHelpContextModule( const QString& component,
       QString rootDir = Qtx::getenv( (component + "_ROOT_DIR").toLatin1().constData() );
       if ( !rootDir.isEmpty() )
       {
-       path = (QStringList() << rootDir << "share" << "doc" << "salome" << "gui" << component << url).join( QDir::separator() );
+        path = (QStringList() << rootDir << "share" << "doc" << "salome" << "gui" << component << url).join( QDir::separator() );
       }
     }
   }
@@ -1742,6 +1871,32 @@ SUIT_ViewManager* LightApp_Application::createViewManager( const QString& vmType
   }
 #endif
 
+#ifndef DISABLE_PV3DVIEWER
+# ifndef DISABLE_SALOMEOBJECT
+  if ( vmType == SPV3D_ViewModel::Type() )
+# else
+  if ( vmType == PV3DViewer_ViewModel::Type() )
+# endif
+  {
+    viewMgr = new SPV3D_ViewManager( activeStudy(), desktop() );
+    SPV3D_ViewModel* vm = dynamic_cast<SPV3D_ViewModel*>( viewMgr->getViewModel() );
+    if ( vm )
+    {
+      // vm->setBackground(...); //NYI
+      // vm->...
+
+      new LightApp_PV3DSelector( vm, mySelMgr );
+    }
+#else
+    viewMgr = new PV3DViewer_ViewManager( activeStudy(), desktop() );
+    PV3DViewer_ViewModel* vm = dynamic_cast<PV3DViewer_ViewModel*>( viewMgr->getViewModel() );
+    if ( vm )
+    {
+      // vm->setBackground(...); //NYI
+    }
+#endif
+  }
+
   if ( !viewMgr )
     return 0;
 
@@ -2248,8 +2403,6 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const
   if ( !crt )
     return myPrefs;
 
-  SUIT_ResourceMgr* resMgr = resourceMgr();
-
   QList<SUIT_Application*> appList = SUIT_Session::session()->applications();
   for ( QList<SUIT_Application*>::iterator appIt = appList.begin(); appIt != appList.end(); ++appIt )
   {
@@ -2261,10 +2414,6 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const
     QStringList names;
     app->modules( names, false );
 
-    // icons of modules
-    QMap<QString, QString> icons;
-    app->moduleIconNames( icons );
-
     // step 1: iterate through list of all available modules
     // and add empty preferences page
     for ( QStringList::const_iterator it = names.begin(); it != names.end(); ++it )
@@ -2272,9 +2421,7 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const
       if ( !_prefs_->hasModule( *it ) ) // prevent possible duplications
       {
         int modId = _prefs_->addPreference( *it ); // add empty page
-        if ( icons.contains( *it ) )               // set icon
-          _prefs_->setItemIcon( modId, Qtx::scaleIcon( resMgr->loadPixmap( moduleName( *it ),
-                                                                           icons[*it], false ), 20 ) );
+        _prefs_->setItemIcon( modId, moduleIcon( *it, 20 ) ); // scale icon to 20x20 pix
       }
     }
 
@@ -2322,6 +2469,12 @@ void LightApp_Application::moduleAdded( CAM_Module* mod )
   }
 }
 
+void LightApp_Application::moduleDeactivated( CAM_Module* /*mod*/ )
+{
+  if ( infoPanel() )
+    infoPanel()->clear();
+}
+
 void LightApp_Application::emptyPreferences( const QString& modName )
 {
   QtxPreferenceItem* item = myPrefs->findItem( modName, true );
@@ -2756,6 +2909,41 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
   pref->setItemProperty( "step", 0.1, light_dz );
   // ... "Light source" group <<end>>
 
+  // ... "View cube" group <<start>>
+  int occViewCubeGroup = pref->addPreference( tr( "PREF_GROUP_VIEWCUBE" ), occGroup );
+  pref->setItemProperty( "columns", 2, occViewCubeGroup );
+  // .... -> show view cube on viewer start
+  pref->addPreference( tr( "PREF_VIEWCUBE_SHOW" ), occViewCubeGroup,
+               LightApp_Preferences::Bool, "OCCViewer", "viewcube_show" );
+  // .... -> view cube duration of animation (sec)
+  int viewcube_dur = pref->addPreference( tr( "PREF_VIEWCUBE_DURATION" ), occViewCubeGroup,
+               LightApp_Preferences::DblSpin, "OCCViewer", "viewcube_duration" );
+  pref->setItemProperty( "min", 0.1, viewcube_dur );
+  pref->setItemProperty( "max", 10.0, viewcube_dur );
+  pref->setItemProperty( "step", 0.1, viewcube_dur );
+  // .... -> show view cube axes
+  pref->addPreference( tr( "PREF_VIEWCUBE_AXES" ), occViewCubeGroup,
+               LightApp_Preferences::Bool, "OCCViewer", "viewcube_axes" );
+  // ... "View cube" group <<end>>
+
+  // ... "View cube default (OCCT) attributes" group <<start>>
+  int occViewCubeAttrsGroup = pref->addPreference( tr( "PREF_VIEWCUBE_CUSTOM" ), occGroup,
+               LightApp_Preferences::Auto, "OCCViewer", "viewcube_custom" );
+  pref->setItemProperty( "columns", 2, occViewCubeAttrsGroup );
+  // .... -> box color
+  pref->addPreference( tr( "PREF_VIEWCUBE_COLOR" ), occViewCubeAttrsGroup,
+               LightApp_Preferences::Color, "OCCViewer", "viewcube_color" );
+  // .... -> view cube size
+  int viewcube_size = pref->addPreference( tr( "PREF_VIEWCUBE_SIZE" ), occViewCubeAttrsGroup,
+               LightApp_Preferences::DblSpin, "OCCViewer", "viewcube_size" );
+  pref->setItemProperty( "min",  30.0, viewcube_size );
+  pref->setItemProperty( "max", 100.0, viewcube_size );
+  pref->setItemProperty( "step", 10.0, viewcube_size );
+  // .... -> text color
+  pref->addPreference( tr( "PREF_VIEWCUBE_TEXTCOLOR" ), occViewCubeAttrsGroup,
+               LightApp_Preferences::Color, "OCCViewer", "viewcube_text_color" );
+  // ... "View cube" group <<end>>
+
   // ... -> empty frame (for layout) <<start>>
   int occGen = pref->addPreference( "", occGroup, LightApp_Preferences::Frame );
   pref->setItemProperty( "margin",  0, occGen );
@@ -3541,6 +3729,24 @@ void LightApp_Application::preferencesChanged( const QString& sec, const QString
   }
 #endif
 
+#ifndef DISABLE_OCCVIEWER
+  if ( sec == QString( "OCCViewer" ) && param.contains( "viewcube" ) )
+  {
+    QList<SUIT_ViewManager*> lst;
+    viewManagers( OCCViewer_Viewer::Type(), lst );
+    QListIterator<SUIT_ViewManager*> it( lst );
+    while ( it.hasNext() )
+    {
+      SUIT_ViewModel* vm = it.next()->getViewModel();
+      if ( !vm || !vm->inherits( "OCCViewer_Viewer" ) )
+        continue;
+
+      OCCViewer_Viewer* occVM = (OCCViewer_Viewer*)vm;
+      occVM->setViewCubeParamsFromPreferences();
+    }
+  }
+#endif
+
   if ( sec == QString( "3DViewer" ) && param == QString( "zooming_mode" ) )
   {
     int mode = resMgr->integerValue( "3DViewer", "zooming_mode", 0 );
@@ -4027,22 +4233,7 @@ void LightApp_Application::afterCloseDoc()
 */
 void LightApp_Application::updateModuleActions()
 {
-  QString modName;
-  if ( activeModule() )
-    modName = activeModule()->moduleName();
-
-  LightApp_ModuleAction* moduleAction =
-    qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
-  if ( moduleAction )
-    moduleAction->setActiveModule( modName );
-}
-
-void LightApp_Application::removeModuleAction( const QString& modName )
-{
-  LightApp_ModuleAction* moduleAction =
-    qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
-  if ( moduleAction )
-    moduleAction->removeModule( modName );
+  emit moduleActivated( activeModule() ? activeModule()->moduleName() : QString() );
 }
 
 bool LightApp_Application::checkModule( const QString& title )
@@ -4180,8 +4371,10 @@ void LightApp_Application::updateWindows()
     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 );
+    if ( HAS_TUTORIAL_URL ) {
+      infoPanel()->addAction( action( TutorialsId ), grp );
+      infoPanel()->addLabel( action( TutorialsId )->statusTip(), grp );
+    }
     infoPanel()->addAction( action( VideosId ), grp );
     infoPanel()->addLabel( action( VideosId )->statusTip(), grp );
 
@@ -4432,34 +4625,18 @@ void LightApp_Application::dockWindowsState( const QByteArray& arr, QMap<QString
   }
 }
 
-/*!
-  Adds icon names for modules
-*/
-void LightApp_Application::moduleIconNames( QMap<QString, QString>& iconMap ) const
+QPixmap LightApp_Application::moduleIcon( const QString& moduleTitle, const int size ) const
 {
-  iconMap.clear();
-
-  SUIT_ResourceMgr* resMgr = resourceMgr();
-  if ( !resMgr )
-    return;
-
-  QStringList modList;
-  modules( modList, false );
-
-  for ( QStringList::const_iterator it = modList.begin(); it != modList.end(); ++it )
+  QPixmap icon;
+  if ( resourceMgr() )
   {
-    QString modName = *it;
-    QString modIntr = moduleName( modName );
-    QString modIcon = resMgr->stringValue( modIntr, "icon", QString() );
-
-    if ( modIcon.isEmpty() )
-      continue;
-
-    if ( SUIT_Tools::extension( modIcon ).isEmpty() )
-      modIcon += QString( ".png" );
-
-    iconMap.insert( modName, modIcon );
+    QPixmap defaultIcon = resourceMgr()->loadPixmap( "LightApp", tr( "APP_MODULE_ICO" ), QPixmap( imageEmptyIcon ) );
+    QString iconName = resourceMgr()->stringValue( moduleName( moduleTitle ), "icon", QString() );
+    icon = resourceMgr()->loadPixmap( moduleName( moduleTitle ), iconName, defaultIcon );
+    if ( size > 0 )
+      icon = Qtx::scaleIcon( icon, size );
   }
+  return icon;
 }
 
 /*!
@@ -4843,6 +5020,9 @@ QStringList LightApp_Application::viewManagersTypes() const
  #else
   aTypesList<<VTKViewer_Viewer::Type();
  #endif
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  aTypesList<<PV3DViewer_ViewModel::Type();
 #endif
   return aTypesList;
 }
@@ -5278,7 +5458,7 @@ void LightApp_Application::updateVisibilityState( DataObjectList& theList,
     if ( aDisplayer ) {
       Qtx::VisibilityState anObjState = Qtx::UnpresentableState;
       if ( aDisplayer->canBeDisplayed( obj->entry(), theViewModel->getType() ) ) {
-        if ( aView && aDisplayer->IsDisplayed( obj->entry(), aView ) )
+        if ( aDisplayer->IsDisplayed( obj->entry(), aView ) )
           anObjState = Qtx::ShownState;
         else
           anObjState = Qtx::HiddenState;
@@ -5381,17 +5561,17 @@ bool LightApp_Application::checkExistingDoc( bool closeExistingDoc )
   if( activeStudy() ) {
     int answer = !activeStudy()->isModified() ? 1 :
                  SUIT_MessageBox::question( desktop(),
-                                           tr( "APPCLOSE_CAPTION" ),
-                                           tr( "STUDYCLOSE_DESCRIPTION" ),
-                                           tr( "APPCLOSE_SAVE" ),
-                                           tr( "APPCLOSE_CLOSE" ),
-                                           tr( "APPCLOSE_CANCEL" ), 0 );
+                                            tr( "APPCLOSE_CAPTION" ),
+                                            tr( "STUDYCLOSE_DESCRIPTION" ),
+                                            tr( "APPCLOSE_SAVE" ),
+                                            tr( "APPCLOSE_CLOSE" ),
+                                            tr( "APPCLOSE_CANCEL" ), 0 );
     if(answer == 0) {
       if ( activeStudy()->isSaved() ) {
         onSaveDoc();
-               if (closeExistingDoc) {
-                       closeDoc(false);
-               }
+                if (closeExistingDoc) {
+                        closeDoc(false);
+                }
       } else if ( onSaveAsDoc() ) {
          if (closeExistingDoc) {
            if( !closeDoc( false ) ) {
@@ -5404,7 +5584,7 @@ bool LightApp_Application::checkExistingDoc( bool closeExistingDoc )
     }
     else if( answer == 1 ) {
       if (closeExistingDoc) {
-       closeDoc( false );
+        closeDoc( false );
       }
     } else if( answer == 2 ) {
       result = false;
@@ -5431,3 +5611,98 @@ PyConsole_Interp* LightApp_Application::createPyInterp()
 }
 
 #endif // DISABLE_PYCONSOLE
+
+void LightApp_Application::createHelpItems( const QString& modTitle )
+{
+  if ( modTitle.isEmpty() )
+    return;
+
+  QString userGuide = "User's Guide";
+  QString devGuide = "Developer's Guide";
+
+  int helpMenu = createMenu( tr( "MEN_DESK_HELP" ), -1, -1, 1000 );
+
+  createMenu( userGuide, helpMenu, -1, 5 );
+  createMenu( devGuide, helpMenu, -1, 5 );
+
+  IMap <QString, QString> helpData;                                 // list of help files for the module
+  QString helpSubMenu;                                              // help submenu name (empty if not needed)
+  QString modName = moduleName( modTitle );                         // module name
+  if ( modName.isEmpty() ) modName = modTitle;                      // for KERNEL and GUI
+  QString rootDir = QString( "%1_ROOT_DIR" ).arg( modName );        // module root dir env variable
+  QString modDir = Qtx::getenv( rootDir.toUtf8().constData() );     // module root dir path
+  QString docSection;
+  if ( resourceMgr()->hasValue( modName, "documentation" ) )
+    docSection = resourceMgr()->stringValue( modName, "documentation" );
+  else if ( resourceMgr()->hasSection( modName + "_documentation" ) )
+    docSection = modName + "_documentation";
+  if ( !docSection.isEmpty() )
+  {
+    helpSubMenu = resourceMgr()->stringValue( docSection, "sub_menu", "" );
+    if ( helpSubMenu.contains( "%1" ) )
+      helpSubMenu = helpSubMenu.arg( modTitle );
+    foreach( QString paramName, resourceMgr()->parameters( docSection ) )
+    {
+      QString key = paramName.contains( "%1" ) ? paramName.arg( modTitle ) : paramName;
+      QString helpItem = getHelpItem( docSection, paramName );
+      if ( !helpItem.isEmpty() )
+        helpData.insert( key, helpItem );
+    }
+  }
+
+  if ( helpData.isEmpty() && !modDir.isEmpty() )
+  {
+    QStringList idxLst = QStringList() << modDir << "share" << "doc" << "salome" << "gui" << modName << "index.html";
+    QString indexFile = idxLst.join( QDir::separator() );          // index file
+    if ( QFile::exists( indexFile ) )
+      helpData.insert( tr( "%1 module Users's Guide" ).arg( modTitle ), indexFile );
+  }
+
+  IMapConstIterator<QString, QString > fileIt;
+  for ( fileIt = helpData.begin(); fileIt != helpData.end(); fileIt++ )
+  {
+    QString helpItemPath = fileIt.key();
+    // remove all '//' occurances
+    while ( helpItemPath.contains( "//" ) )
+      helpItemPath.replace( "//", "" );
+    // obtain submenus hierarchy if given
+    QStringList smenus = helpItemPath.split( "/" );
+    helpItemPath = smenus.takeLast();
+    // workaround for User's Guide and Developer's Guide to avoid having single item in module's submenu.
+    if ( helpItemPath == userGuide || helpItemPath == devGuide )
+    {
+      QString menuPath = smenus.join( "/" );
+      QStringList allKeys = helpData.keys();
+      QStringList total = allKeys.filter( QRegExp( QString( "^%1" ).arg( menuPath ) ) );
+      if ( total.count() == 1 && smenus.count() > 0 )
+        helpItemPath = smenus.takeLast();
+    }
+    QPixmap helpIcon = fileIt.value().startsWith( "http", Qt::CaseInsensitive ) ?
+      resourceMgr()->loadPixmap( "STD", tr( "ICON_WWW" ), false ) :
+      resourceMgr()->loadPixmap( "STD", tr( "ICON_HELP" ), false );
+    QAction* a = createAction( -1, helpItemPath, helpIcon, helpItemPath, helpItemPath,
+                               0, desktop(), false, this, SLOT( onHelpContentsModule() ) );
+    a->setData( fileIt.value() );
+    if ( !helpSubMenu.isEmpty() )
+      smenus.prepend( helpSubMenu );
+    // create sub-menus hierarchy
+    int menuId = helpMenu;
+    foreach ( QString subMenu, smenus )
+      menuId = createMenu( subMenu, menuId, -1, 5 );
+    createMenu( a, menuId, -1, ( menuId != helpMenu && ( helpItemPath == userGuide || helpItemPath == devGuide ) ) ? 0 : 5 );
+    if ( !myHelpItems.contains( modName ) )
+      myHelpItems[modName] = IdList();
+    myHelpItems[modName].append( actionId( a ) );
+  }
+}
+
+void LightApp_Application::removeHelpItems( const QString& modTitle )
+{
+  QString modName = moduleName( modTitle );
+  if ( myHelpItems.contains( modName ) )
+  {
+    foreach( int id, myHelpItems[modName] )
+      setMenuShown( id, false );
+    myHelpItems.remove( modName );
+  }
+}