Salome HOME
Second integration of Salome On Demand
[modules/gui.git] / src / LightApp / LightApp_Application.cxx
index 49972104e670860d5d1f733e978ac0b3e0d199b7..89fb8ec1278723b21a344ea4783e00963e0672e2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2022  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"
@@ -98,6 +98,7 @@
 #include <QtxMap.h>
 
 #include <LogWindow.h>
+#include <SalomeApprc_utils.h>
 
 #ifndef DISABLE_GLVIEWER
   #include <GLViewer_Viewer.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
 
@@ -250,6 +265,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_ ) {
@@ -700,7 +720,7 @@ void LightApp_Application::createActions()
 
   foreach( QString aModule, aModuleList )
     createHelpItems( aModule );
-  
+
   // f) Additional help items
 
   int id = LightApp_Application::UserID + FIRST_HELP_ID + 1000;
@@ -740,9 +760,13 @@ void LightApp_Application::createActions()
   connect( moduleAction, SIGNAL( moduleActivated( const QString& ) ),
            this, SLOT( onModuleActivation( const QString& ) ) );
   connect( moduleAction, SIGNAL( adding() ),
-           this, SLOT( onModuleAdding() ) );
+           this, SLOT( onExtAdding() ) );
   connect( moduleAction, SIGNAL( removing( QString ) ),
-           this, SLOT( onModuleRemoving( 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 );
@@ -784,6 +808,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() ) );
@@ -820,6 +847,49 @@ void LightApp_Application::createActions()
   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.
 */
@@ -831,6 +901,9 @@ void LightApp_Application::customize()
   // b. here we add custom modules (manually added by the user)
   if ( HAS_SALOME_ON_DEMAND )
   {
+    // Update rc file
+    updateSalomeApprc();
+
     QStringList modList = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts );
     foreach ( QString aModule, modList )
       addUserModule(  aModule, resourceMgr()->stringValue( "user_modules", aModule ) );
@@ -841,6 +914,21 @@ void LightApp_Application::customize()
   }
 }
 
+/*!
+  Update rc file with SALOME_APPLICATION_DIR or with SALOME_MODULES.
+*/
+void LightApp_Application::updateSalomeApprc()
+{
+    SUIT_ResourceMgr* resMgr = resourceMgr();
+    auto extRootDir = getenv(salomeAppDir);
+
+    QString salomemodules(getenv("SALOME_MODULES"));
+    if(salomemodules.isEmpty())
+        AddComponents_from_salomeappdir(  QDir(extRootDir), resMgr );
+    else
+        AddComponents_from_salomemodules(salomemodules, QDir(extRootDir), resMgr);
+}
+
 /*!On module activation action.*/
 void LightApp_Application::onModuleActivation( const QString& modTitle )
 {
@@ -871,41 +959,91 @@ void LightApp_Application::onModuleActivation( const QString& modTitle )
     activateModule( modTitle );
 }
 
-/*!On module adding action.*/
-void LightApp_Application::onModuleAdding()
+/*!On extension adding action.*/
+void LightApp_Application::onExtAdding()
 {
-  // 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
+  // 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;
+  }
 
-  // loop via selected configuration files
-  foreach( QString path, paths )
+  // It should be set on the app start
+  auto extRootDir = getenv(salomeAppDir);
+  if (!extRootDir)
   {
-    // read description file (.salomex) and check it's OK
-    QtxResourceMgr resMgr;
-    if ( !resMgr.addResource( path ) )
+    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");
+  PyObjWrapper runSalomeOnDemand = PyImport_ImportModule((char*)"runSalomeOnDemand");
+
+  // 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_MODULE_BAD_SALOMEX_FILE" ).arg( path ) );
+      SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_UNPACK_EXTENSION").arg(path) );
       continue;
     }
-    // retrieve module name
-    QString name = resMgr.stringValue( "General", "name" ).trimmed();
-    if ( name.isEmpty() )
+
+    PyObjWrapper pKeys = PyDict_Keys(unpackedModules);
+    // Iterate all the components (modules) for this extension
+    for (Py_ssize_t pos = 0; pos < PyDict_Size(unpackedModules); ++pos)
     {
-      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_NAME" ).arg( path ) );
-      continue;
+      auto moduleNameItem = PyList_GetItem(pKeys, pos);
+      auto interactiveItem = PyDict_GetItem(unpackedModules, moduleNameItem);
+
+      QString moduleName(PyUnicode_AsUTF8(moduleNameItem));
+      SCRUTE(moduleName.toStdString());
+      addUserModule(moduleName, SalomeExtDir, PyObject_IsTrue(interactiveItem));
     }
-    // retrieve root directory
-    QString root = resMgr.stringValue( "General", "root" ).trimmed();
-    if ( root.isEmpty() )
+
+    // Add an extension to GUI
+    QFileInfo extFileInfo(path);
+    QString extName = extFileInfo.baseName();
+    if (moduleAction)
     {
-      SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_ROOT" ).arg( path ) );
+      moduleAction->insertExtension(extName);
+    }
+
+    // Update environment of salome
+    PyObjWrapper update_env = PyObject_CallMethod(
+      runSalomeOnDemand, (char*)"set_selext_env", (char*)"ss", extRootDir, extName.toStdString().c_str());
+    if (!update_env)
+    {
+      SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_UPDATE_ENV").arg(extName + "_env.py") );
       continue;
     }
-    addUserModule( name, root, true );
   }
+
+  // Udate actions only once after all of them were already inserted
+  moduleAction->updateExtActions();
 }
 
 /*Add user module.*/
@@ -934,9 +1072,12 @@ bool LightApp_Application::addUserModule( const QString& name, const QString& ro
       SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_RESDIR" ).arg( resDir ) );
     return false;
   }
+
+  SUIT_ResourceMgr* resMgr = resourceMgr();
+
   // read XML configuration file
-  resourceMgr()->setConstant( QString( "%1_ROOT_DIR" ).arg( name ), root );
-  if ( !resourceMgr()->addResource( resDir ) ) // cannot read configuration
+  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 ) );
@@ -949,12 +1090,26 @@ bool LightApp_Application::addUserModule( const QString& name, const QString& ro
       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
+  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 ) ) )
@@ -978,72 +1133,145 @@ bool LightApp_Application::addUserModule( const QString& name, const QString& ro
   // save module in the resource manager
   if ( interactive )
   {
-    QStringList customModules = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts );
+    QStringList customModules = resMgr->stringValue("launch", "user_modules").split(";", QString::SkipEmptyParts);
     customModules << name;
     customModules.removeDuplicates();
-    resourceMgr()->setValue( "launch", "user_modules", customModules.join( ";" ) );
-    resourceMgr()->setValue( "user_modules", name, root );
+    resMgr->setValue( "launch", "user_modules", customModules.join( ";" ) );
+    resMgr->setValue( "user_modules", name, root );
   }
   return true;
 }
 
-/*!On module removing action.*/
-void LightApp_Application::onModuleRemoving( const QString& title )
+/*!Remove user module from UI.*/
+void LightApp_Application::removeUserModule(const QString& moduleInnerName, LightApp_ModuleAction* moduleAction)
 {
-  QString root = resourceMgr()->stringValue( "user_modules", moduleName( title ) );
-  QDir rootDirectory = QDir( root );
+  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("");
 
-  if ( rootDirectory.exists() )
+  // Remove from "Modules" menu and toolbar
+  if (moduleAction)
   {
-    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
-      }
-    }
+    moduleAction->removeModule(moduleUserName);
   }
 
-  if ( activeModule() && activeModule()->moduleName() == title )
-    activateModule( "" );
+  // Remove Help menu items
+  removeHelpItems(moduleUserName);
 
-  // remove from "Modules" menu and toolbar
-  LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>( action( ModulesListId ) );
-  if ( moduleAction )
+  // 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)
   {
-    moduleAction->removeModule( title );
+    MESSAGE("Removing of an extension was cancelled");
+    return; // cancelled
   }
-  // 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)
+  
+  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.*/
 QString LightApp_Application::defaultModule() const
 {
@@ -1104,6 +1332,11 @@ void LightApp_Application::onNewWindow()
   case NewPyViewerId:
     type = PyViewer_Viewer::Type();
     break;
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  case NewPV3DViewId:
+    type = PV3DViewer_ViewModel::Type();
+    break;
 #endif
   }
 
@@ -1131,16 +1364,16 @@ void LightApp_Application::onNewDoc()
 void LightApp_Application::onOpenDoc()
 {
   SUIT_Study* study = activeStudy();
-  
+
   if ( !checkExistingDoc( false ) )
     return;
-  
+
   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();
@@ -1278,6 +1511,12 @@ void LightApp_Application::updateCommandsStatus()
   if( a )
     a->setEnabled( activeStudy() );
 #endif
+
+#ifndef DISABLE_PV3DVIEWER
+  a = action( NewPV3DViewId );
+  if( a )
+    a->setEnabled( activeStudy() );
+#endif
 }
 
 /*!
@@ -1327,7 +1566,7 @@ protected:
 #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) 
+      // (especially in the case of universal binaries)
       env.remove("LD_LIBRARY_PATH");
 #endif
       QProcess* proc = new QProcess();
@@ -1843,6 +2082,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;
 
@@ -2984,7 +3249,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();
@@ -3009,7 +3274,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" ); //
@@ -4374,7 +4639,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;
 
@@ -4399,13 +4664,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) {
@@ -4427,9 +4692,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()] );
         }
       }
@@ -4440,11 +4705,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()] );
       }
@@ -4966,6 +5231,9 @@ QStringList LightApp_Application::viewManagersTypes() const
  #else
   aTypesList<<VTKViewer_Viewer::Type();
  #endif
+#endif
+#ifndef DISABLE_PV3DVIEWER
+  aTypesList<<PV3DViewer_ViewModel::Type();
 #endif
   return aTypesList;
 }
@@ -5152,7 +5420,7 @@ void LightApp_Application::onDesktopMessage( const QString& message )
   }
   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 
+    // 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);
@@ -5215,7 +5483,7 @@ void LightApp_Application::onInfoPanelShown()
 }
 
 /*!
-  Internal method. 
+  Internal method.
   Returns all top level toolbars.
   Note : Result list contains only main window toolbars, not including toolbars from viewers.
 */