Salome HOME
bos #29467 SALOME GUI logger
[modules/gui.git] / src / LightApp / LightApp_Application.cxx
index a649d14fa51657c8fda32c6e53efc907e91d1b4e..19639c2812cea9d2732149a0cecf41434ad2d981 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2016  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
 //
 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
@@ -23,7 +23,6 @@
 // File:      LightApp_Application.cxx
 // Created:   6/20/2005 18:39:45 PM
 // Author:    Natalia Donis
-
 #ifdef WIN32
 // E.A. : On windows with python 2.6, there is a conflict
 // E.A. : between pymath.h and Standard_math.h which define
@@ -50,6 +49,7 @@
 #include "LightApp_PreferencesDlg.h"
 #include "LightApp_ModuleDlg.h"
 #include "LightApp_AboutDlg.h"
+#include "LightApp_ExtInfoDlg.h"
 #include "LightApp_ModuleAction.h"
 // temporary commented
 #include "LightApp_EventFilter.h"
@@ -88,6 +88,7 @@
 #include <QtxFontEdit.h>
 #include <QtxToolBar.h>
 #include <QtxTreeView.h>
+#include <QtxInfoPanel.h>
 #include <QtxMRUAction.h>
 #include <QtxDockAction.h>
 #include <QtxDockWidget.h>
   #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 <QTreeView>
 #include <QMimeData>
 #include <QShortcut>
+#include <QRegExp>
 
 #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>
@@ -244,6 +264,11 @@ static const char* imageEmptyIcon[] = {
 //since the 'toolbar marker' is not unique, find index of first occurrence of the
 //'toolbar marker' in the array and check that next string is name of the toolbar
 
+namespace
+{
+  const char* salomeAppDir = "SALOME_APPLICATION_DIR";
+}
+
 void LightAppCleanUpAppResources()
 {
   if ( LightApp_Application::_prefs_ ) {
@@ -259,7 +284,7 @@ namespace
     int inputLen = input.length();
     QDataStream anInputData( &input, QIODevice::ReadOnly );
     while ( tmp < inputLen ) {
-      tmp = input.indexOf( QToolBarMarker, tmp + 1 );
+      tmp = input.indexOf( (uchar)QToolBarMarker, tmp + 1 );
       if ( tmp < 0 )
         break;
       anInputData.device()->seek( tmp );
@@ -299,6 +324,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.*/
@@ -463,7 +513,7 @@ void LightApp_Application::closeApplication()
 #ifndef DISABLE_QTXWEBBROWSER
   QProcess::startDetached( "HelpBrowser",
                            QStringList() << QString( "--remove=%1" ).arg( QApplication::instance()->applicationPid() ) );
-#endif  
+#endif
   CAM_Application::closeApplication();
 }
 
@@ -518,14 +568,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();
 
-  bool status = CAM_Application::activateModule( modName );
+  if ( infoPanel() )
+    infoPanel()->clear();
+
+  bool status = CAM_Application::activateModule( name );
 
   updateModuleActions();
 
@@ -538,13 +595,14 @@ bool LightApp_Application::activateModule( const QString& modName )
   updateViewManagers();
 
   if ( activeStudy() && activeStudy()->root() && objectBrowser() ) {
-    if ( objectBrowser()->root() != activeStudy()->root() ) 
+    if ( objectBrowser()->root() != activeStudy()->root() )
       objectBrowser()->setRoot( activeStudy()->root() );
     updateObjectBrowser( true );
   }
 
   if ( activeModule() ) activeModule()->updateModuleVisibilityState();
 
+  updateActions();
   return true;
 }
 
@@ -589,99 +647,95 @@ void LightApp_Application::createActions()
                 tr( "MEN_DESK_PREFERENCES" ), tr( "PRP_DESK_PREFERENCES" ),
                 Qt::CTRL+Qt::Key_P, desk, false, this, SLOT( onPreferences() ) );
 
-  // Help menu:
-
-  // - Help for modules
+  // Help menu
 
   int helpMenu = createMenu( tr( "MEN_DESK_HELP" ), -1, -1, 1000 );
-  createMenu( separator(), helpMenu, -1, 10 );
-  QStringList aModuleList;
-  modules( aModuleList, false );
-  aModuleList.prepend( "GUI" );
-  aModuleList.prepend( "KERNEL" );
 
-  int id = LightApp_Application::UserID + FIRST_HELP_ID;
+  QString url;
+
+  // a) Link to web site
+  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 );
+    }
+  }
 
-  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 variable
-    QString modDir  = getenv( rootDir.toLatin1().constData() );      // module root dir
-    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", "" ).arg( aModule );
-      QStringList listOfParam = resMgr->parameters( docSection );
-      foreach( QString paramName, listOfParam ) {
-        QString valueStr = resMgr->stringValue( docSection, paramName );
-        if ( !valueStr.isEmpty() ) {
-          QFileInfo fi( valueStr );
-          if ( fi.isRelative() && !modDir.isEmpty() )
-            valueStr = Qtx::addSlash( modDir ) + valueStr;
-          if ( QFile::exists( valueStr ) )
-            helpData.insert( paramName.arg( aModule ), valueStr );
-        }
-      }
+  // b) Link to Forum
+  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 );
     }
+  }
 
-    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 );
+  // c) Link to YouTube channel
+  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 );
     }
+  }
 
-    IMapConstIterator<QString, QString > fileIt;
-    for ( fileIt = helpData.begin(); fileIt != helpData.end(); fileIt++ ) {
-      QString helpFileName = fileIt.key();
-      // remove all '//' occurances 
-      while ( helpFileName.contains( "//" ) )
-        helpFileName.replace( "//", "" );
-      // obtain submenus hierarchy if given
-      QStringList smenus = helpFileName.split( "/" );
-      helpFileName = smenus.last();
-      smenus.removeLast();
-      QAction* a = createAction( id, helpFileName,
-                                 resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false ),
-                                 helpFileName, helpFileName,
-                                 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, 0 );
-      }
-      createMenu( a, menuId, -1, 0 );
-      id++;
+  // d) Link to Tutorials
+  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 );
     }
   }
 
-  // - Additional help items
+  // e) Help for modules
 
-  createMenu( separator(), helpMenu, -1, 5 );
+  QStringList aModuleList;
+  modules( aModuleList, false );
+  aModuleList.prepend( "GUI" );
+  aModuleList.prepend( "KERNEL" );
+
+  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 addHelpItem, addHelpItems ) {
-    QString valueStr = resMgr->stringValue( "add_help", addHelpItem );
-    if ( !valueStr.isEmpty() && QFile::exists( valueStr ) ) {
-      QAction* a = createAction( id, addHelpItem,
-                                 resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false ),
-                                 addHelpItem, addHelpItem,
+  foreach ( QString paramName, addHelpItems ) {
+    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( valueStr );
-      createMenu( a, helpMenu, -1, 5 );
-      id++;
+      a->setData( helpItem );
+      createMenu( a, helpMenu, -1, 10 );
     }
   }
 
@@ -690,60 +744,28 @@ 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 )
-    {
-      if ( !isModuleAccessible( *it ) )
-        continue;
-
-      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( onExtAdding() ) );
+  connect( moduleAction, SIGNAL( removing( QString ) ),
+           this, SLOT( onExtRemoving( QString ) ) );
+  connect( moduleAction, SIGNAL(showExtInfo()),
+           this, SLOT(onShowExtInfo()));
+
+  addExtensionsActions(moduleAction);
 
   // New window
   int windowMenu = createMenu( tr( "MEN_DESK_WINDOW" ), -1, MenuWindowId, 100 );
@@ -785,6 +807,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() ) );
@@ -813,26 +838,87 @@ 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 );
 }
 
+/*!Create actions for installed extensions:*/
+void LightApp_Application::addExtensionsActions(LightApp_ModuleAction* moduleAction)
+{
+  if (!moduleAction)
+  {
+    MESSAGE("Couldn't get a moduleAction! Return.");
+    return;
+  }
+
+  // It should be set on the app start if we use an --on_demand 1 command line option
+  auto extRootDir = getenv(salomeAppDir);
+  if (!extRootDir)
+  {
+    // It's ok if we don't use --on_demand
+    return;
+  }
+  SCRUTE(extRootDir);
+
+   // Import Python module that manages SALOME extensions.
+  PyLockWrapper lck; // acquire GIL
+  PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
+  PyObjWrapper installedExtensions = PyObject_CallMethod(
+      extensionQuery, (char*)"ext_by_name", (char*)"s", extRootDir);
+  if (!installedExtensions)
+  {
+    return;
+  }
+
+  // Iterate installed extensions
+  for (Py_ssize_t pos = 0; pos < PyList_Size(installedExtensions); ++pos)
+  {
+    // Get the current ext name
+    auto extNameItem = PyList_GetItem(installedExtensions, pos);
+    QString extName(PyUnicode_AsUTF8(extNameItem));
+    SCRUTE(extName.toStdString());
+
+    moduleAction->insertExtension(extName);
+  }
+
+  // Udate actions only once after all of them were already inserted
+  moduleAction->updateExtActions();
+}
+
+/*!
+  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() );
@@ -845,17 +931,314 @@ 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 extension adding action.*/
+void LightApp_Application::onExtAdding()
+{
+  // Show dialog to browse a salome extension file
+  QStringList filters = (QStringList() << tr("Salome extension files") + " (*.salomex)" << tr("All files") + " (*)");
+  QStringList paths = getOpenFileNames(QString(), filters.join(";;"), QString(), desktop());
+  if (paths.isEmpty())
+  {
+    MESSAGE("Adding an extension was cancelled.");
+    return;
+  }
+
+  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+  if (!moduleAction)
+  {
+    MESSAGE("Couldn't get a moduleAction! Return.");
+    return;
+  }
+
+  // It should be set on the app start
+  auto extRootDir = getenv(salomeAppDir);
+  if (!extRootDir)
+  {
+    SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_SALOME_APPLICATION_DIR"));
+    return;
+  }
+  SCRUTE(extRootDir);
+
+  // We'll load all the extensions modules from this path
+  auto SalomeExtDir = QDir::cleanPath(QString(extRootDir) + QDir::separator() + "__SALOME_EXT__");
+  SCRUTE(SalomeExtDir.toStdString());
+
+  // Import Python module that manages SALOME extensions.
+  // It seems to be faster to lock and unlock once than on each iteration,
+  // but I didn't compare the performance for each case.
+  PyLockWrapper lck; // acquire GIL
+  PyObjWrapper extensionUnpacker = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_unpacker");
+
+  // Loop via selected extensions files
+  foreach(QString path, paths)
+  {
+    std::string extPath = path.toStdString();
+    SCRUTE(extPath);
+
+    PyObjWrapper unpackedModules = PyObject_CallMethod(
+      extensionUnpacker, (char*)"install_salomex", (char*)"s", extPath.c_str());
+    if (!unpackedModules || unpackedModules == Py_None)
+    {
+      SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_UNPACK_EXTENSION").arg(path) );
+      continue;
+    }
+
+    // Iterate all the components (modules) for this extension
+    for (Py_ssize_t pos = 0; pos < PyList_Size(unpackedModules); ++pos)
+    {
+      auto moduleNameItem = PyList_GetItem(unpackedModules, pos);
+      QString moduleName(PyUnicode_AsUTF8(moduleNameItem));
+      SCRUTE(moduleName.toStdString());
+
+      addUserModule(moduleName, SalomeExtDir, true);
+    }
+
+    // Add an extension to GUI
+    if (moduleAction)
+    {
+      QFileInfo extFileInfo(path);
+      QString extName = extFileInfo.baseName();
+      moduleAction->insertExtension(extName);
+    }
+  }
+
+  // Udate actions only once after all of them were already inserted
+  moduleAction->updateExtActions();
+}
+
+/*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;
+  }
+
+  SUIT_ResourceMgr* resMgr = resourceMgr();
+
+  // read XML configuration file
+  resMgr->setConstant(QString("%1_ROOT_DIR").arg(name), root);
+  if (!resMgr->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
+  resMgr->loadLanguage(name);
+
+  // Do all the GUI related stuff only if the module supports that.
+  // We already did check for GUI inside CAM_Application::appendModuleInfo, but
+  // need to do that again.
+  // TODO: Maybe it's better to return ModuleInfo from appendModuleInfo() and check status.
+  const QString title = resMgr->stringValue(name, "name", QString()).trimmed();
+  if (resMgr->booleanValue(name, "gui", false) || !title.isEmpty())
+  {
+    // Append module to the menu / toolbar
+    LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+    if (moduleAction)
+    {
+      // Scale icon to 20x20 pix
+      moduleAction->insertModule(moduleTitle(name), moduleIcon(moduleTitle(name), 20), true);
+    }
+  }
+
+  // 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 = resMgr->stringValue("launch", "user_modules").split(";", QString::SkipEmptyParts);
+    customModules << name;
+    customModules.removeDuplicates();
+    resMgr->setValue( "launch", "user_modules", customModules.join( ";" ) );
+    resMgr->setValue( "user_modules", name, root );
+  }
+  return true;
+}
+
+/*!Remove user module from UI.*/
+void LightApp_Application::removeUserModule(const QString& moduleInnerName, LightApp_ModuleAction* moduleAction)
+{
+  MESSAGE("Remove a module from UI...");
+  SCRUTE(moduleInnerName.toStdString());
+
+  // There is a some confusion point, because now we have a module's 'inner' name
+  // from the extension's salomexd file.
+  // But, in the next GUI methods we need to use a module title (user name).
+  // For example, PYHELLO (inner name) and PyHello (user name to display in GUI).
+  // Then, from the inner module's name we need to get a user one.
+  QString moduleUserName = moduleTitle(moduleInnerName);
+  SCRUTE(moduleUserName.toStdString());
+
+  // Set current state in modules combo box
+  // Don't confuse again, because activeModule()->moduleName() returns a module title, not an inner one!
+  if (activeModule() && activeModule()->moduleName() == moduleUserName)
+    activateModule("");
+
+  // Remove from "Modules" menu and toolbar
+  if (moduleAction)
+  {
+    moduleAction->removeModule(moduleUserName);
+  }
+
+  // Remove Help menu items
+  removeHelpItems(moduleUserName);
+
+  // Remove Preferences
+  LightApp_Preferences* prefs = preferences();
+  if (prefs)
+    prefs->removeModule(moduleUserName);
+
+  // Remove settings
+  // Here we use an inner module name!
+  QStringList customModules = resourceMgr()->stringValue("launch", "user_modules").split(";", QString::SkipEmptyParts);
+  customModules.removeAll(moduleInnerName);
+  resourceMgr()->setValue("launch", "user_modules", customModules.join(";"));
+  removeModuleInfo(moduleInnerName);
+}
+
+/*!On module removing action.*/
+void LightApp_Application::onExtRemoving(const QString& title)
+{
+  MESSAGE("Remove an extension...");
+  std::string extName = title.toStdString();
+  SCRUTE(extName);
+
+  // Ask user if he's ready to completely remove an extension and all its modules.
+  int answer = SUIT_MessageBox::question(
+    desktop(),
+    tr("TLT_REMOVE_EXTENSION"),
+    tr("QUE_REMOVE_EXTENSION").arg(title),
+    SUIT_MessageBox::Ok | SUIT_MessageBox::Cancel,
+    SUIT_MessageBox::Ok
+  );
+
+  if (answer == SUIT_MessageBox::Cancel)
+  {
+    MESSAGE("Removing of an extension was cancelled");
+    return; // cancelled
+  }
+  
+  if (activeStudy() && activeStudy()->isModified() && !onSaveDoc())
+  {
+    // doc is not saved, or saving cancelled
+    SUIT_MessageBox::warning(
+      desktop(),
+      tr("WRN_WARNING"), tr("WRN_CANCEL_REMOVE_EXTENSION_UNSAVE").arg(title)
+    );
+
+    return;
+  }
+
+  // It should be set on the app start
+  auto extRootDir = getenv(salomeAppDir);
+  if (!extRootDir)
+  {
+    SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_SALOME_APPLICATION_DIR"));
+    return;
+  }
+  SCRUTE(extRootDir);
+
+  // Import Python module that manages SALOME extensions.
+  PyLockWrapper lck; // acquire GIL
+  PyObjWrapper extensionRemover = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_remover");
+  PyObjWrapper removedModules = PyObject_CallMethod(
+      extensionRemover, (char*)"remove_salomex", (char*)"ss", extRootDir, extName.c_str());
+  if (!removedModules || removedModules == Py_None)
+  {
+    SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_REMOVE_EXTENSION").arg(title));
+    return;
+  }
+
+  // We need it to remove ext and modules from UI
+  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+  if (!moduleAction)
+  {
+    MESSAGE("Cannot get a pointer to LightApp_ModuleAction! Removing from menue and toolbars will skipped.");
+  }
+
+  // Module's content was already removed on python remove_salomex call,
+  // then all we do next - just remove UI items.
+  for (Py_ssize_t pos = 0; pos < PyList_Size(removedModules); ++pos)
+  {
+    // Get the current module's name
+    auto moduleNameItem = PyList_GetItem(removedModules, pos);
+    const QString moduleInnerName(PyUnicode_AsUTF8(moduleNameItem));
+
+    removeUserModule(moduleInnerName, moduleAction);
+  }
+
+  // Remove an ext from UI
+  if (moduleAction)
+  {
+    moduleAction->removeExtension(title);
+  }
+
+  // Update windows (in particular, Info panel)
+  updateWindows();
+}
+
+/*!On show extension info action.*/
+void LightApp_Application::onShowExtInfo()
+{
+  // Show dialog with information about loaded salome extensions
+  LightApp_ExtInfoDlg dlg(desktop());
+  dlg.exec();
 }
 
 /*!Default module activation.*/
@@ -918,6 +1301,11 @@ void LightApp_Application::onNewWindow()
   case NewPyViewerId:
     type = PyViewer_Viewer::Type();
     break;
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  case NewPV3DViewId:
+    type = PV3DViewer_ViewModel::Type();
+    break;
 #endif
   }
 
@@ -935,7 +1323,7 @@ void LightApp_Application::onNewDoc()
 
   //asl: fix for 0020515
   saveDockWindowsState();
-  
+
   CAM_Application::onNewDoc();
 }
 
@@ -945,12 +1333,16 @@ void LightApp_Application::onNewDoc()
 void LightApp_Application::onOpenDoc()
 {
   SUIT_Study* study = activeStudy();
-  
-  if ( !checkExistingDoc() )
+
+  if ( !checkExistingDoc( false ) )
     return;
-  
-  CAM_Application::onOpenDoc();
-  
+
+  QString aName = getFileName( true, QString(), getFileFilter( true ), QString(), 0 );
+  if ( aName.isNull() ) //Cancel
+    return;
+
+  onOpenDoc( aName );
+
   if ( !study ) // new study will be create in THIS application
   {
     updateWindows();
@@ -958,12 +1350,30 @@ void LightApp_Application::onOpenDoc()
   }
 }
 
+bool LightApp_Application::canOpenDoc( const QString& )
+{
+  return true;
+}
+
 /*!
   SLOT: Opens new document.
   \param aName - name of file
 */
 bool LightApp_Application::onOpenDoc( const QString& aName )
 {
+  if ( !canOpenDoc(aName)) {
+    bool showError = !property("open_study_from_command_line").isValid() ||
+      !property("open_study_from_command_line").toBool();
+
+    putInfo( tr("OPEN_DOCUMENT_PROBLEM") );
+    if ( showError )
+      SUIT_MessageBox::critical( desktop(), tr("ERR_ERROR"), tr("OPEN_DOCUMENT_PROBLEM"));
+
+    return false;
+  }
+
+  closeDoc(false);
+
   if ( !checkExistingDoc() )
     return false;
 
@@ -971,7 +1381,7 @@ bool LightApp_Application::onOpenDoc( const QString& aName )
 
   // We should take mru action first because this application instance can be deleted later.
   QtxMRUAction* mru = ::qobject_cast<QtxMRUAction*>( action( MRUId ) );
-  
+
   bool res = CAM_Application::onOpenDoc( aName );
 
   if ( mru )
@@ -1070,6 +1480,12 @@ void LightApp_Application::updateCommandsStatus()
   if( a )
     a->setEnabled( activeStudy() );
 #endif
+
+#ifndef DISABLE_PV3DVIEWER
+  a = action( NewPV3DViewId );
+  if( a )
+    a->setEnabled( activeStudy() );
+#endif
 }
 
 /*!
@@ -1079,172 +1495,152 @@ void LightApp_Application::updateCommandsStatus()
 class RunBrowser: public QThread
 {
 public:
-  RunBrowser( LightApp_Application* app,
-              const QString&        theApp,
-              const QString&        theParams,
-              const QString&        theHelpFile,
-              const QString&        theContext = QString() )
-    : myApp( theApp ),
-      myParams( theParams ),
-      myContext( theContext ),
-      myStatus(0),
-      myLApp( app )
+  static void execute( LightApp_Application* application,
+                       const QString& browser,
+                       const QString& parameters,
+                       const QString& url )
   {
-    //For the external browser always specify 'file://' protocol,
-    //because some WEB browsers (for example Mozilla Firefox) can't open local file without protocol.
-    myHelpFile = QString("file://%1").arg( QFileInfo( theHelpFile ).canonicalFilePath() );
+    (new RunBrowser( application, browser, parameters, url ))->start();
+  }
+
+protected:
+  RunBrowser( LightApp_Application* application,
+              const QString&        browser,
+              const QString&        parameters,
+              const QString&        url)
+    : myApplication( application ),
+      myBrowser( browser ),
+      myParameters( parameters ),
+      myUrl( url )
+  {
+    if ( !myUrl.startsWith( "http", Qt::CaseInsensitive ) )
+    {
+      // normalize path
+      if ( myUrl.startsWith( "file://", Qt::CaseInsensitive ) )
+        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://" );
+    }
+    connect(this, SIGNAL(finished()), SLOT(deleteLater()));
   }
 
   virtual void run()
   {
-    if ( !myApp.isEmpty() && !myHelpFile.isEmpty()) {
-      QString aCommand = QString( "%1 %2 \"%3%4\"" ).arg( myApp, myParams, myHelpFile, myContext.isEmpty() ? QString("") : QString( "#%1" ).arg( myContext ) );
-
+    if ( !myBrowser.isEmpty() && !myUrl.isEmpty() )
+    {
+      QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+#ifdef WIN32
+      QString cmdLine = QString( "\"%1\" %2 \"%3\"" ).arg( myBrowser, myParameters, myUrl );
+#else
+      QString cmdLine = QString( "%1 %2 \"%3\"" ).arg( myBrowser, myParameters, myUrl );
+      // remove LD_LIBRARY_PATH from the environement before starting launcher to avoid bad interactions.
+      // (especially in the case of universal binaries)
+      env.remove("LD_LIBRARY_PATH");
+#endif
       QProcess* proc = new QProcess();
-
-      proc->start( aCommand );
-      if ( !proc->waitForStarted() ) {
+      proc->setProcessEnvironment(env);
+      proc->start( cmdLine );
+      if ( !proc->waitForStarted() )
+      {
         SALOME_CustomEvent* ce2000 = new SALOME_CustomEvent( 2000 );
-        QString* msg = new QString( QObject::tr( "EXTERNAL_BROWSER_CANNOT_SHOW_PAGE" ).arg( myApp, myHelpFile ) );
+        QString* msg = new QString( QObject::tr( "EXTERNAL_BROWSER_CANNOT_SHOW_PAGE" ).arg( myBrowser, myUrl ) );
         ce2000->setData( msg );
-        QApplication::postEvent( myLApp, ce2000 );
+        QApplication::postEvent( myApplication, ce2000 );
       }
     }
   }
 
 private:
-  QString               myApp;
-  QString               myParams;
-  QString               myHelpFile;
-  QString               myContext;
-  int                   myStatus;
-  LightApp_Application* myLApp;
+  LightApp_Application* myApplication;
+  QString               myBrowser;
+  QString               myParameters;
+  QString               myUrl;
 };
 
-/*!
-  SLOT: Displays help contents for choosen module
-*/
-void LightApp_Application::onHelpContentsModule()
+void LightApp_Application::showHelp( const QString& path )
 {
-  const QAction* a = (QAction*) sender();
-  QString helpFile = a->data().toString();
-  if ( helpFile.isEmpty() ) return;
-
   SUIT_ResourceMgr* resMgr = resourceMgr();
-  QString platform;
-#ifdef WIN32
-  platform = "winapplication";
+
+#if DISABLE_QTXWEBBROWSER
+  bool useExternalBrowser = true;
 #else
-  platform = "application";
+  bool useExternalBrowser = resMgr->booleanValue("ExternalBrowser", "use_external_browser", false );
 #endif
-  QString anApp = resMgr->stringValue("ExternalBrowser", platform);
+
+  if ( useExternalBrowser )
+  {
 #ifdef WIN32
-  QString quote("\"");
-  anApp.prepend( quote );
-  anApp.append( quote );
-#endif
-  QString aParams = resMgr->stringValue("ExternalBrowser", "parameters");
-#if DISABLE_QTXWEBBROWSER
-  bool useExtBrowser = true;
-#else  
-  bool useExtBrowser = resMgr->booleanValue("ExternalBrowser", "use_external_browser", false );
+    QString browser = resMgr->stringValue( "ExternalBrowser", "winapplication" ) ;
+#else
+    QString browser = resMgr->stringValue( "ExternalBrowser", "application" );
 #endif
-  
-  if( useExtBrowser ) {
-    if ( !anApp.isEmpty() ) {
-      RunBrowser* rs = new RunBrowser( this, anApp, aParams, helpFile );
-      rs->start();
+    QString parameters = resMgr->stringValue("ExternalBrowser", "parameters");
+
+    if ( !browser.isEmpty() )
+    {
+      RunBrowser::execute( this, browser, parameters, path );
     }
-    else {
+    else
+    {
       if ( SUIT_MessageBox::question( desktop(), tr( "WRN_WARNING" ), tr( "DEFINE_EXTERNAL_BROWSER" ),
                                       SUIT_MessageBox::Yes | SUIT_MessageBox::No,
                                       SUIT_MessageBox::Yes ) == SUIT_MessageBox::Yes )
-
-        showPreferences( tr( "PREF_APP" ) );
+      {
+        QStringList path;
+        path << tr( "PREF_CATEGORY_SALOME" ) << tr( "PREF_TAB_GENERAL" )
+             << tr( "PREF_GROUP_EXT_BROWSER" ) << tr( "PREF_APP" );
+        showPreferences( path );
+      }
     }
   }
-  else {
-    QStringList parameters;
-    parameters << QString( "--language=%1" ).arg( resMgr->stringValue( "language", "language" ) );
-    parameters << QString( "--add=%1" ).arg( QApplication::instance()->applicationPid() );
-    parameters << helpFile;
-    QProcess::startDetached( "HelpBrowser", parameters );
+  else
+  {
+    QStringList cmdLine;
+    cmdLine << QString( "--language=%1" ).arg( resMgr->stringValue( "language", "language" ) );
+    cmdLine << QString( "--add=%1" ).arg( QApplication::instance()->applicationPid() );
+    cmdLine << path;
+    QProcess::startDetached( "HelpBrowser", cmdLine );
   }
 }
 
 /*!
-  SLOT: Displays help contents for choosen dialog
+  SLOT: Displays help contents for choosen module
 */
-void LightApp_Application::onHelpContextModule( const QString& theComponentName,
-                                                const QString& theFileName,
-                                                const QString& theContext )
-{
-  QString fileName = theFileName;
-  QString context  = theContext;
-  if ( !QFile::exists( fileName ) && theContext.isEmpty() ) {
-    // context might be passed within theFileName argument
-    QStringList comps = fileName.split("#");
-    if ( comps.count() > 1 ) {
-      context = comps.last();
-      comps.removeLast();
-      fileName = comps.join("#");
-    }
-  }
-
-  QString homeDir = "";
-  if ( !theComponentName.isEmpty() ) {
-    QString dir = getenv( ( theComponentName + "_ROOT_DIR" ).toLatin1().constData() );
-    if ( !dir.isEmpty() )
-      homeDir = Qtx::addSlash( Qtx::addSlash( dir )      +
-                               Qtx::addSlash( "share" )  +
-                               Qtx::addSlash( "doc" )    +
-                               Qtx::addSlash( "salome" ) +
-                               Qtx::addSlash( "gui" )    +
-                               Qtx::addSlash( theComponentName ) );
-  }
-
-  QString helpFile = QFileInfo( homeDir + fileName ).absoluteFilePath();
-  SUIT_ResourceMgr* resMgr = resourceMgr();
-        QString platform;
-#ifdef WIN32
-        platform = "winapplication";
-#else
-        platform = "application";
-#endif
-        QString anApp = resMgr->stringValue("ExternalBrowser", platform);
-#ifdef WIN32
-        QString quote("\"");
-        anApp.prepend( quote );
-        anApp.append( quote );
-#endif
-
-#if DISABLE_QTXWEBBROWSER
-  bool useExtBrowser = true;
-#else  
-  bool useExtBrowser = resMgr->booleanValue("ExternalBrowser", "use_external_browser", false );
-#endif
-
-  if(useExtBrowser) {
-    QString aParams = resMgr->stringValue("ExternalBrowser", "parameters");
+void LightApp_Application::onHelpContentsModule()
+{
+  const QAction* a = (QAction*) sender();
+  QString helpFile = a->data().toString();
+  if ( !helpFile.isEmpty() )
+    showHelp( helpFile );
+}
 
-    if ( !anApp.isEmpty() ) {
-      RunBrowser* rs = new RunBrowser( this, anApp, aParams, helpFile, context );
-      rs->start();
-    }
-    else {
-      if ( SUIT_MessageBox::question( desktop(), tr( "WRN_WARNING" ), tr( "DEFINE_EXTERNAL_BROWSER" ),
-                                      SUIT_MessageBox::Yes | SUIT_MessageBox::No,
-                                      SUIT_MessageBox::Yes ) == SUIT_MessageBox::Yes )
-        showPreferences( tr( "PREF_APP" ) );
+/*!
+  SLOT: Displays contextual help (e.g. for choosen dialog)
+*/
+void LightApp_Application::onHelpContextModule( const QString& component,
+                                                const QString& url,
+                                                const QString& context )
+{
+  QString path = url;
+  if ( !url.startsWith( "http", Qt::CaseInsensitive ) )
+  {
+    // local file path
+    QFileInfo fi( url );
+    if ( fi.isRelative() && !component.isEmpty() )
+    {
+      QString rootDir = Qtx::getenv( (component + "_ROOT_DIR").toLatin1().constData() );
+      if ( !rootDir.isEmpty() )
+      {
+        path = (QStringList() << rootDir << "share" << "doc" << "salome" << "gui" << component << url).join( QDir::separator() );
+      }
     }
   }
-  else {
-    QStringList parameters;
-    parameters << QString( "--language=%1" ).arg( resMgr->stringValue( "language", "language" ) );
-    parameters << QString( "--add=%1" ).arg( QApplication::instance()->applicationPid() );
-    parameters << QString( "%1#%2" ).arg( helpFile ).arg( context );
-    QProcess::startDetached( "HelpBrowser", parameters );
+  if ( !context.isEmpty() )
+  {
+    path += QString( "#%1" ).arg( context );
   }
+  showHelp( path );
 }
 
 /*!
@@ -1301,9 +1697,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 );
@@ -1373,6 +1778,11 @@ SUIT_DataBrowser* LightApp_Application::objectBrowser()
   return qobject_cast<SUIT_DataBrowser*>( dockWindow( WT_ObjectBrowser ) );
 }
 
+QtxInfoPanel* LightApp_Application::infoPanel()
+{
+  return qobject_cast<QtxInfoPanel *>( dockWindow( WT_InfoPanel ));
+}
+
 /*!
   \return Log Window
 */
@@ -1387,7 +1797,7 @@ LogWindow* LightApp_Application::logWindow()
   when you request the python console, this function could return
   null. Then the optional parameter force (default to false) can be
   set to force the creation of the python console if it is not done
-  already. 
+  already.
   \param force - if true, the pythonConsole is created if it does not exist yet
   \return Python Console
 */
@@ -1460,7 +1870,8 @@ SUIT_ViewManager* LightApp_Application::getViewManager( const QString& vmType, c
       aVM = anActiveVM;
     }
 
-  if ( aVM && !aVM->getDetached() && create )
+  bool keepDetached = property("keep_detached").toBool();
+  if ( aVM && (!aVM->getDetached() || keepDetached) && create )
   {
     if ( !aVM->getActiveView() )
       {
@@ -1542,7 +1953,7 @@ SUIT_ViewManager* LightApp_Application::createViewManager( const QString& vmType
       viewMgr->getActiveView()->setFocus();
       return 0;
     } else {
-      viewMgr = new PVViewer_ViewManager( activeStudy(), desktop(), logWindow() );
+      viewMgr = new PVViewer_ViewManager( activeStudy(), desktop() );
     }
   }
 #endif
@@ -1581,6 +1992,7 @@ SUIT_ViewManager* LightApp_Application::createViewManager( const QString& vmType
                                resMgr->doubleValue( "OCCViewer", "focus_value", vm->stereographicFocusValue() ));
     vm->setInterocularDistance( resMgr->integerValue( "OCCViewer", "iod_type", vm->interocularDistanceType() ),
                                 resMgr->doubleValue( "OCCViewer", "iod_value", vm->interocularDistanceValue() ));
+    vm->setSelectionStyle((OCCViewer_ViewWindow::SelectionStyle) resMgr->integerValue( "OCCViewer", "adv_selection_mode", vm->selectionStyle() ) );
 
     vm->setReverseStereo( resMgr->booleanValue( "OCCViewer", "reverse_stereo", vm->isReverseStereo() ) );
     vm->setVSync( resMgr->booleanValue( "OCCViewer", "enable_vsync", vm->isVSync() ) );
@@ -1639,6 +2051,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;
 
@@ -1723,6 +2161,7 @@ void LightApp_Application::onStudyCreated( SUIT_Study* theStudy )
   }
 
   getWindow( WT_ObjectBrowser );
+  getWindow( WT_InfoPanel );
 
   loadDockWindowsState();
 
@@ -1754,6 +2193,7 @@ void LightApp_Application::onStudyOpened( SUIT_Study* theStudy )
   }
 
   getWindow( WT_ObjectBrowser );
+  getWindow( WT_InfoPanel );
 
   loadDockWindowsState();
 
@@ -1784,7 +2224,7 @@ void LightApp_Application::onStudySaved( SUIT_Study* s )
 }
 
 /*!Protected SLOT. On study closed.*/
-void LightApp_Application::onStudyClosed( SUIT_Study* s )
+void LightApp_Application::onStudyClosed( SUIT_Study* /*s*/ )
 {
   /*
   disconnect( this, SIGNAL( viewManagerRemoved( SUIT_ViewManager* ) ),
@@ -1924,7 +2364,12 @@ void LightApp_Application::onPreferences()
 }
 
 /*!Private SLOT. On preferences.*/
-void LightApp_Application::showPreferences( const QString& itemText )
+void LightApp_Application::showPreferences( const QString& path )
+{
+  showPreferences( QStringList() << path );
+}
+
+void LightApp_Application::showPreferences( const QStringList& path )
 {
   QApplication::setOverrideCursor( Qt::WaitCursor );
 
@@ -1935,7 +2380,7 @@ void LightApp_Application::showPreferences( const QString& itemText )
   if ( !prefDlg )
     return;
 
-  preferences()->activateItem( itemText );
+  preferences()->activateItem( path );
 
   if ( ( prefDlg->exec() == QDialog::Accepted || prefDlg->isSaved() ) &&  resourceMgr() )
   {
@@ -2047,17 +2492,20 @@ QWidget* LightApp_Application::createWindow( const int flag )
 
     // Create OBSelector
     new LightApp_OBSelector( ob, mySelMgr );
-#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
-    ob->treeView()->header()->setResizeMode(SUIT_DataObject::VisibilityId, QHeaderView::Fixed);
-#else
     ob->treeView()->header()->setSectionResizeMode(SUIT_DataObject::VisibilityId, QHeaderView::Fixed);
-#endif
     ob->treeView()->header()->moveSection(SUIT_DataObject::NameId,SUIT_DataObject::VisibilityId);
     ob->treeView()->setColumnWidth(SUIT_DataObject::VisibilityId, VISIBILITY_COLUMN_WIDTH);
     ob->setProperty( "shortcut", QKeySequence( "Alt+Shift+O" ) );
     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 )
   {
@@ -2074,6 +2522,7 @@ QWidget* LightApp_Application::createWindow( const int flag )
   else if ( flag == WT_LogWindow )
   {
     LogWindow* logWin = new LogWindow( desktop() );
+    logWin->handleQtMessages( true );
     logWin->setObjectName( "logWindow" );
     logWin->setWindowTitle( tr( "LOG_WINDOW" ) );
     logWin->setProperty( "shortcut", QKeySequence( "Alt+Shift+L" ) );
@@ -2094,6 +2543,7 @@ void LightApp_Application::defaultWindows( QMap<int, int>& aMap ) const
 #endif
   if ( activeStudy() ) {
     aMap.insert( WT_ObjectBrowser, Qt::LeftDockWidgetArea );
+    aMap.insert( WT_InfoPanel, Qt::RightDockWidgetArea );
     //  aMap.insert( WT_LogWindow, Qt::DockBottom );
   }
 }
@@ -2133,8 +2583,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 )
   {
@@ -2142,42 +2590,42 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const
     if ( !app )
       continue;
 
-    QStringList modNameList;
-    app->modules( modNameList, false );
+    // all modules available in current session
+    QStringList names;
+    app->modules( names, false );
 
-    QMap<QString, QString> iconMap;
-    app->moduleIconNames( iconMap );
-
-    for ( QStringList::const_iterator it = modNameList.begin(); it != modNameList.end(); ++it )
+    // 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 )
     {
-      if ( !app->isModuleAccessible( *it ) || _prefs_->hasModule( *it ) )
-        continue;
-
-      int modId = _prefs_->addPreference( *it );
-      if ( iconMap.contains( *it ) )
-        _prefs_->setItemIcon( modId, Qtx::scaleIcon( resMgr->loadPixmap( moduleName( *it ), iconMap[*it], false ), 20 ) );
+      if ( !_prefs_->hasModule( *it ) ) // prevent possible duplications
+      {
+        int modId = _prefs_->addPreference( *it ); // add empty page
+        _prefs_->setItemIcon( modId, moduleIcon( *it, 20 ) ); // scale icon to 20x20 pix
+      }
     }
 
-    ModuleList modList;
-    app->modules( modList );
-    QListIterator<CAM_Module*> itr( modList );
+    // step 2: iterate through list of all loaded modules
+    // and initialize their preferences
+    ModuleList loadedModules;
+    app->modules( loadedModules );
+    QListIterator<CAM_Module*> itr( loadedModules );
     while ( itr.hasNext() )
     {
-      LightApp_Module* mod = 0;
+      LightApp_Module* module = 0;
+      CAM_Module* m = itr.next();
+      if ( m->inherits( "LightApp_Module" ) )
+        module = (LightApp_Module*)m;
 
-      CAM_Module* anItem = itr.next();
-      if ( anItem->inherits( "LightApp_Module" ) )
-        mod = (LightApp_Module*)anItem;
-
-      if ( mod && !_prefs_->hasModule( mod->moduleName() ) )
+      if ( module && !_prefs_->hasModule( module->moduleName() ) )
       {
-        _prefs_->addPreference( mod->moduleName() );
-        mod->createPreferences();
-        that->emptyPreferences( mod->moduleName() );
+        _prefs_->addPreference( module->moduleName() ); // add page (for sure, had to be done at step 1)
+        module->createPreferences();                    // initialize preferences
+        that->emptyPreferences( module->moduleName() ); // show dummy page if module does not export any preferences
       }
     }
   }
-  _prefs_->setItemProperty( "info", tr( "PREFERENCES_NOT_LOADED" ) );
+  _prefs_->setItemProperty( "info", tr( "PREFERENCES_NOT_LOADED" ) ); // dummy page for modules which are not loaded yet
 
   return myPrefs;
 }
@@ -2201,14 +2649,18 @@ 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 );
   if ( !item || !item->isEmpty() )
     return;
 
-  //  printf( "---------------------> Modify for empty module.\n" );
-
   QtxPagePrefFrameItem* frm = new QtxPagePrefFrameItem( item->title(), item->parentItem() );
   frm->setIcon( item->icon() );
   frm->setStretch( false );
@@ -2269,8 +2721,14 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
   pref->addPreference( tr( "PREF_SHOW_SPLASH" ), lookGroup, LightApp_Preferences::Bool, "launch", "splash" );
   // .... -> opaque resize
   pref->addPreference( tr( "PREF_OPAQUE_RESIZE" ), lookGroup, LightApp_Preferences::Bool, "desktop", "opaque_resize" );
-  // .... -> drop-down buttons 
+  // .... -> drop-down buttons
   pref->addPreference( tr( "PREF_DROP_DOWN_BUTTONS" ), lookGroup, LightApp_Preferences::Bool, "viewers", "drop_down_buttons" );
+  // .... -> Notification timeout
+  int delay = pref->addPreference( tr( "PREF_NOTIFY_TIMEOUT" ), lookGroup, LightApp_Preferences::IntSpin, "notification", "timeout" );
+  pref->setItemProperty( "special", tr("PREF_NOTIFY_TIMEOUT_NONE"), delay );
+  pref->setItemProperty( "min", 0, delay );
+  pref->setItemProperty( "max", 100, delay );
+  pref->setItemProperty( "suffix", " sec", delay );
   // ... "Look and feel" group <<end>>
 
   // ... "Study properties" group <<start>>
@@ -2532,13 +2990,22 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
 
   // ... "Selection" group <<start>>
   int occSelectionGroup = pref->addPreference( tr( "PREF_GROUP_SELECTION" ), occGroup );
-  pref->setItemProperty( "columns", 2, occSelectionGroup );
+  pref->setItemProperty( "columns", 3, occSelectionGroup );
   // .... -> enable preselection
   pref->addPreference( tr( "PREF_ENABLE_PRESELECTION" ), occSelectionGroup,
                        LightApp_Preferences::Bool, "OCCViewer", "enable_preselection" );
   // .... -> enable selection
   pref->addPreference( tr( "PREF_ENABLE_SELECTION" ), occSelectionGroup,
                        LightApp_Preferences::Bool, "OCCViewer", "enable_selection" );
+  // .... -> selection style
+  int aSeleStyle = pref->addPreference( tr( "PREF_SELECTION_STYLE" ), occSelectionGroup,
+                       LightApp_Preferences::Selector, "OCCViewer", "adv_selection_mode" );
+  aValuesList.clear();
+  anIndicesList.clear();
+  aValuesList   << tr("PREF_POLYGON_SELECTION") << tr("PREF_CIRCLE_SELECTION");
+  anIndicesList << 0 << 1;
+  pref->setItemProperty( "strings", aValuesList, aSeleStyle);
+  pref->setItemProperty( "indexes", anIndicesList, aSeleStyle);
   // ... "Selection" group <<end>>
 
   // ... "Clipping" group <<start>>
@@ -2622,6 +3089,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 );
@@ -2716,7 +3218,7 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
   int vtkSelectionGroup = pref->addPreference( tr( "PREF_GROUP_SELECTION" ), vtkGroup );
   pref->setItemProperty( "columns", 2, vtkSelectionGroup );
   // .... -> preselection
-  int vtkPreselection = pref->addPreference( tr( "PREF_PRESELECTION" ),  vtkSelectionGroup, 
+  int vtkPreselection = pref->addPreference( tr( "PREF_PRESELECTION" ),  vtkSelectionGroup,
                                              LightApp_Preferences::Selector, "VTKViewer", "preselection" );
   aValuesList.clear();
   anIndicesList.clear();
@@ -2741,7 +3243,7 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
   int spacemousePref2 = pref->addPreference( tr( "PREF_SPACEMOUSE_FUNC_2" ), vtkSM,
                                              LightApp_Preferences::Selector, "VTKViewer",
                                              "spacemouse_func2_btn" );
-  // .... -> dominant / combined switch  
+  // .... -> dominant / combined switch
   int spacemousePref3 = pref->addPreference( tr( "PREF_SPACEMOUSE_FUNC_3" ), vtkSM,
                                              LightApp_Preferences::Selector, "VTKViewer",
                                              "spacemouse_func5_btn" ); //
@@ -3249,6 +3751,27 @@ void LightApp_Application::preferencesChanged( const QString& sec, const QString
   }
 #endif
 
+
+#ifndef DISABLE_OCCVIEWER
+  if (sec == QString("OCCViewer") && param == QString("adv_selection_mode"))
+  {
+    int mode = resMgr->integerValue("OCCViewer", "adv_selection_mode", 0);
+    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->setSelectionStyle((OCCViewer_ViewWindow::SelectionStyle)mode);
+    }
+  }
+#endif
+
+
 #ifndef DISABLE_OCCVIEWER
   if ( sec == QString( "OCCViewer" ) && param == QString( "stereo_type" ) )
   {
@@ -3386,6 +3909,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 );
@@ -3872,31 +4413,7 @@ void LightApp_Application::afterCloseDoc()
 */
 void LightApp_Application::updateModuleActions()
 {
-  QString modName;
-  if ( activeModule() ) {
-    modName = activeModule()->moduleName();
-    if ( !isModuleAccessible( modName ) ) {
-      QList<SUIT_Application*> apps = SUIT_Session::session()->applications();
-      foreach( SUIT_Application* app, apps ) {
-        LightApp_Application* lapp = dynamic_cast<LightApp_Application*>( app );
-        if ( lapp && lapp != this )
-          lapp->removeModuleAction( modName );
-      }
-    }
-  }
-
-  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 )
@@ -3914,11 +4431,11 @@ bool LightApp_Application::checkModule( const QString& title )
 
   QStringList paths;
 #if defined(WIN32)
-  paths = QString( ::getenv( "PATH" ) ).split( ";", QString::SkipEmptyParts );
+  paths = QString( Qtx::getenv( "PATH" ) ).split( ";", QString::SkipEmptyParts );
 #elif defined(__APPLE__)
-  paths = QString( ::getenv( "DYLD_LIBRARY_PATH" ) ).split( ":", QString::SkipEmptyParts );
+  paths = QString( Qtx::getenv( "DYLD_LIBRARY_PATH" ) ).split( ":", QString::SkipEmptyParts );
 #else
-  paths = QString( ::getenv( "LD_LIBRARY_PATH" ) ).split( ":", QString::SkipEmptyParts );
+  paths = QString( Qtx::getenv( "LD_LIBRARY_PATH" ) ).split( ":", QString::SkipEmptyParts );
 #endif
 
   bool isFound = false;
@@ -3941,8 +4458,11 @@ bool LightApp_Application::checkModule( const QString& title )
   if ( isPyModule )
   {
     QString pyModule = QString( "%1GUI.py" ).arg( name );
-    paths = QString( ::getenv( "PYTHONPATH" ) ).split( ":", QString::SkipEmptyParts );
-
+#if defined(WIN32)
+    paths = QString( Qtx::getenv( "PYTHONPATH" ) ).split( ";", QString::SkipEmptyParts );
+#else
+    paths = QString( Qtx::getenv( "PYTHONPATH" ) ).split( ":", QString::SkipEmptyParts );
+#endif
     isFound = false;
     for ( it = paths.begin(); it != paths.end() && !isFound; ++it )
     {
@@ -4009,13 +4529,47 @@ void LightApp_Application::updateWindows()
   for ( WinMap::ConstIterator it = myWin.begin(); it != myWin.end(); ++it )
   {
     QWidget* wid = it.value();
+    if ( !wid )
+      continue;
     if ( winMap.contains( it.key() ) )
       wid->setVisible( true );
-    else
+    else if ( !activeStudy() )
       delete wid;
+    else
+      wid->setVisible( false );
   }
 
   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 );
+    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 );
+
+    LightApp_ModuleAction* ma = qobject_cast<LightApp_ModuleAction*>(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 );
+      }
+    }
+  }
 }
 
 /*!
@@ -4054,7 +4608,7 @@ void LightApp_Application::loadDockWindowsState()
   aResMgr->value( "windows_visibility", modName, aDefaultVisibility );
   bool hasDefaultVisibility = !aDefaultVisibility.isEmpty();
   aResMgr->setWorkingMode( prevMode );
-  
+
   if( !storeWin && !storeTb && aDefaultState.isEmpty() && !hasDefaultVisibility)
     return;
 
@@ -4079,13 +4633,13 @@ void LightApp_Application::loadDockWindowsState()
 
   QMap<QString, bool> *tbMap = 0;
   QMap<QString, bool> *dwMap = 0;
-  
+
   QMap<QString, bool> userTbMap, userDwMap;
   dockWindowsState( myWinVis[modName], userTbMap, userDwMap );
 
   QMap<QString, bool> defaultTbMap, defaultDwMap;
   if(hasDefaultVisibility) {
-    dockWindowsState( aDefaultVisibility, defaultTbMap, defaultDwMap);    
+    dockWindowsState( aDefaultVisibility, defaultTbMap, defaultDwMap);
   }
 
   if(storeTb) {
@@ -4107,9 +4661,9 @@ void LightApp_Application::loadDockWindowsState()
   if(tbMap) {
     QList<QToolBar*> tbList = findToolBars();
     for ( QList<QToolBar*>::iterator tit = tbList.begin(); tit != tbList.end(); ++tit )
-      { 
+      {
         QToolBar* tb = *tit;
-        if ( tbMap->contains( tb->objectName() ) ) {      
+        if ( tbMap->contains( tb->objectName() ) ) {
           tb->setVisible( (*tbMap)[tb->objectName()] );
         }
       }
@@ -4120,11 +4674,11 @@ void LightApp_Application::loadDockWindowsState()
     for ( QList<QDockWidget*>::iterator dit = dwList.begin(); dit != dwList.end(); ++dit )
       {
         QDockWidget* dw = *dit;
-        
+
         QObject* po = Qtx::findParent( dw, "QMainWindow" );
         if ( po != desktop() )
           continue;
-        
+
         if ( dwMap->contains( dw->objectName() ) )
           dw->setVisible( (*dwMap)[dw->objectName()] );
       }
@@ -4251,34 +4805,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;
 }
 
 /*!
@@ -4555,7 +5093,12 @@ bool LightApp_Application::event( QEvent* e )
                                   d ? *d : "",
                                   SUIT_MessageBox::Yes | SUIT_MessageBox::No,
                                   SUIT_MessageBox::Yes ) == SUIT_MessageBox::Yes )
-      showPreferences( tr( "PREF_APP" ) );
+    {
+      QStringList path;
+      path << tr( "PREF_CATEGORY_SALOME" ) << tr( "PREF_TAB_GENERAL" )
+           << tr( "PREF_GROUP_EXT_BROWSER" ) << tr( "PREF_APP" );
+      showPreferences( path );
+    }
     if( d )
       delete d;
     return true;
@@ -4657,6 +5200,9 @@ QStringList LightApp_Application::viewManagersTypes() const
  #else
   aTypesList<<VTKViewer_Viewer::Type();
  #endif
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  aTypesList<<PV3DViewer_ViewModel::Type();
 #endif
   return aTypesList;
 }
@@ -4780,9 +5326,9 @@ QString LightApp_Application::browseObjects( const QStringList& theEntryList,
         SUIT_DataOwnerPtrList aList;
 #ifndef DISABLE_SALOMEOBJECT
         Handle(SALOME_InteractiveObject) aSObj = new SALOME_InteractiveObject
-          ( anObject->entry().toLatin1().constData(),
+          ( anObject->entry().toUtf8().constData(),
             anObject->componentDataType().toLatin1().constData(),
-            anObject->name().toLatin1().constData() );
+            anObject->name().toUtf8().constData() );
         LightApp_DataOwner* owner = new LightApp_DataOwner( aSObj  );
 #else
         LightApp_DataOwner* owner = new LightApp_DataOwner( anEntry );
@@ -4822,7 +5368,7 @@ bool LightApp_Application::renameAllowed( const QString& /*entry*/) const {
   \param name new name of the object
   \brief Return \c true if rename operation finished successfully, \c false otherwise.
 */
-bool LightApp_Application::renameObject( const QString& entry, const QString& ) {
+bool LightApp_Application::renameObject( const QString& /*entry*/, const QString& /*name*/ ) {
   return false;
 }
 
@@ -4841,6 +5387,34 @@ void LightApp_Application::onDesktopMessage( const QString& message )
     if ( !vtype.isEmpty() )
       getViewManager( vtype, true );
   }
+  else if ( message.toLower().startsWith("register_module_in_study" ) ) {
+    QString moduleName = message.split( sectionSeparator ).last();
+    // Check name of current activating module name in order to avoid ciclik
+    // call because of messages
+    if (!property("activateModule").toBool()) {
+      CAM_Module* mod = module(moduleName);
+      if (!mod)
+        mod = module(moduleTitle(moduleName));
+      if (!mod) {
+        mod = loadModule(moduleName);
+        if (!mod)
+          mod = loadModule(moduleTitle(moduleName));
+        if (mod) {
+          addModule(mod);
+        }
+      }
+      if (mod) {
+        CAM_Study* anActiveStudy = dynamic_cast<CAM_Study*>(activeStudy());
+        if (anActiveStudy) {
+          mod->connectToStudy(anActiveStudy);
+          LightApp_DataModel* aDM = dynamic_cast<LightApp_DataModel*>(mod->dataModel());
+          if(aDM) {
+            aDM->initRootObject();
+          }
+        }
+      }
+    }
+  }
   else {
     QStringList data = message.split( sectionSeparator );
     if ( data.count() > 1 ) {
@@ -4871,8 +5445,14 @@ void LightApp_Application::onDesktopMessage( const QString& message )
   }
 }
 
+void LightApp_Application::onInfoPanelShown()
+{
+  if ( activeModule() && activeModule()->inherits( "LightApp_Module" ) )
+    ((LightApp_Module*)activeModule())->updateInfoPanel();
+}
+
 /*!
-  Internal method. 
+  Internal method.
   Returns all top level toolbars.
   Note : Result list contains only main window toolbars, not including toolbars from viewers.
 */
@@ -4934,7 +5514,7 @@ QByteArray LightApp_Application::processState(QByteArray& input,
       return aRes;
     QDataStream anInputData(&input, QIODevice::ReadOnly);
 
-    int toolBarMarkerIndexDef;
+    int toolBarMarkerIndexDef = 0;
     if(hasDefaultState) {
       toolBarMarkerIndexDef = getToolbarMarkerIndex(defaultState, aNames);
       if(toolBarMarkerIndexDef < 0)
@@ -5038,7 +5618,7 @@ void LightApp_Application::emitOperationFinished( const QString& theModuleName,
   Update visibility state of given objects
 */
 void LightApp_Application::updateVisibilityState( DataObjectList& theList,
-                                                  SUIT_ViewModel*  theViewModel )
+                                                  SUIT_ViewModel* theViewModel )
 {
   if ( !theViewModel || theList.isEmpty() ) return;
 
@@ -5053,18 +5633,66 @@ void LightApp_Application::updateVisibilityState( DataObjectList& theList,
     if ( !obj || aStudy->isComponent( obj->entry() ) )
       continue;
 
-    LightApp_Module* anObjModule = dynamic_cast<LightApp_Module*>(obj->module());
-    if ( anObjModule ) {
-      LightApp_Displayer* aDisplayer = anObjModule->displayer();
-      if ( aDisplayer ) {
-        Qtx::VisibilityState anObjState = Qtx::UnpresentableState;
-        if ( aDisplayer->canBeDisplayed( obj->entry(), theViewModel->getType() ) ) {
-          if ( aView && aDisplayer->IsDisplayed( obj->entry(), aView ) )
-            anObjState = Qtx::ShownState;
-          else
-            anObjState = Qtx::HiddenState;
+    QString mname = aStudy->componentDataType(obj->entry());
+    LightApp_Displayer* aDisplayer = LightApp_Displayer::FindDisplayer(mname, false);
+    if ( aDisplayer ) {
+      Qtx::VisibilityState anObjState = Qtx::UnpresentableState;
+      if ( aDisplayer->canBeDisplayed( obj->entry(), theViewModel->getType() ) ) {
+        if ( aDisplayer->IsDisplayed( obj->entry(), aView ) )
+          anObjState = Qtx::ShownState;
+        else
+          anObjState = Qtx::HiddenState;
+      }
+      aStudy->setVisibilityState( obj->entry(), anObjState );
+    }
+  }
+}
+
+/*!
+  Update presentations of all displayed objects of theComponent in specified viewers
+*/
+void LightApp_Application::updatePresentations( const QString& theComponent,
+                                                const QStringList& theViewManagerTypes )
+{
+  LightApp_Displayer* aDisplayer = LightApp_Displayer::FindDisplayer(theComponent, false);
+  if ( aDisplayer ) {
+    LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>(activeStudy());
+    DataObjectList aComps;
+    bool isFound = false;
+    aStudy->root()->children( aComps );
+    DataObjectList::const_iterator aCompsIt = aComps.begin();
+    for ( ; aCompsIt != aComps.end() && !isFound; aCompsIt++ ) {
+      LightApp_DataObject* aComp = dynamic_cast<LightApp_DataObject*>( *aCompsIt );
+      if ( aComp && aComp->componentDataType() ==  theComponent) {
+        isFound = true;
+        DataObjectList anObjs;
+        aComp->children(anObjs, true);
+
+        QList<SUIT_ViewManager*> aViewMgrs;
+        QStringList::const_iterator itVMTypes = theViewManagerTypes.begin();
+        for ( ; itVMTypes != theViewManagerTypes.end(); ++itVMTypes )
+          viewManagers( *itVMTypes, aViewMgrs );
+
+        DataObjectList::const_iterator itObjs = anObjs.begin();
+        for ( ; itObjs != anObjs.end(); itObjs++ ) {
+          LightApp_DataObject* anObj = dynamic_cast<LightApp_DataObject*>( *itObjs );
+          QString anEntry = anObj->entry();
+
+          QListIterator<SUIT_ViewManager*> itViewMgrs( aViewMgrs );
+          while ( itViewMgrs.hasNext()) {
+            SUIT_ViewModel* aVM = itViewMgrs.next()->getViewModel();
+            if ( aVM ) {
+              SALOME_View* aView = dynamic_cast<SALOME_View*>(aVM);
+              if ( aView ) {
+                bool isDisp = aDisplayer->IsDisplayed( anEntry, aView );
+                aDisplayer->Erase( anEntry, true, false, aView );
+                if ( isDisp ) {
+                  aDisplayer->Display( anEntry, false, aView );
+                }
+              }
+            }
+          }
         }
-        aStudy->setVisibilityState( obj->entry(), anObjState );
       }
     }
   }
@@ -5107,31 +5735,37 @@ void LightApp_Application::onViewManagerRemoved( SUIT_ViewManager* )
 /*!
   Check existing document.
 */
-bool LightApp_Application::checkExistingDoc()
+bool LightApp_Application::checkExistingDoc( bool closeExistingDoc )
 {
   bool result = true;
   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();
-        closeDoc( false );
+                if (closeExistingDoc) {
+                        closeDoc(false);
+                }
       } else if ( onSaveAsDoc() ) {
-         if( !closeDoc( false ) ) {
-           result = false;
+         if (closeExistingDoc) {
+           if( !closeDoc( false ) ) {
+             result = false;
+           }
         }
       } else {
         result = false;
       }
     }
     else if( answer == 1 ) {
-      closeDoc( false );
+      if (closeExistingDoc) {
+        closeDoc( false );
+      }
     } else if( answer == 2 ) {
       result = false;
     }
@@ -5157,3 +5791,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 );
+  }
+}