OPTION(SALOME_BUILD_DOC "Generate SALOME GUI documentation" ON)
OPTION(SALOME_BUILD_TESTS "Build SALOME tests" ON)
OPTION(SALOME_GUI_USE_OBSERVERS "Use study observers in GUI (advanced)" ON)
-OPTION(SALOME_ON_DEMAND "Activate SALOME on demand feature" OFF)
+OPTION(SALOME_ON_DEMAND "Activate SALOME on demand feature" ON)
CMAKE_DEPENDENT_OPTION(SALOME_GUI_BUILD_FRENCH_DOC "Generate SALOME GUI French documentation" OFF
"SALOME_BUILD_DOC" OFF)
FIND_PACKAGE(SalomeOmniORBPy REQUIRED)
ELSE()
ADD_DEFINITIONS("-DGUI_DISABLE_CORBA")
-ENDIF()
+ENDIF()
IF(SALOME_BUILD_TESTS)
ENABLE_TESTING()
FIND_PACKAGE(SalomeCppUnit)
# - VTK viewer: VTK
IF(SALOME_USE_VTKVIEWER)
- # Required components are listed in the FindSalomeVTK.cmake file:
- FIND_PACKAGE(SalomeVTK)
+ # Required components are listed in the FindSalomeVTK.cmake file:
+ FIND_PACKAGE(SalomeVTK)
SALOME_LOG_OPTIONAL_PACKAGE(VTK SALOME_USE_VTKVIEWER)
# VSR: TODO: since ParaView 5.8 the following is not needed, as OPENGL2 is hardcoded: to be removed (everywhere)
ADD_DEFINITIONS("-DVTK_OPENGL2")
SET(SALOME_INSTALL_LIBS "${SALOME_INSTALL_LIBS}" CACHE PATH "Install path: SALOME libs")
SET(SALOME_INSTALL_IDLS "${SALOME_INSTALL_IDLS}" CACHE PATH "Install path: SALOME IDL files")
SET(SALOME_INSTALL_HEADERS "${SALOME_INSTALL_HEADERS}" CACHE PATH "Install path: SALOME headers")
-SET(SALOME_INSTALL_SCRIPT_SCRIPTS "${SALOME_INSTALL_SCRIPT_SCRIPTS}" CACHE PATH
+SET(SALOME_INSTALL_SCRIPT_SCRIPTS "${SALOME_INSTALL_SCRIPT_SCRIPTS}" CACHE PATH
"Install path: SALOME scripts")
-SET(SALOME_INSTALL_SCRIPT_DATA "${SALOME_INSTALL_SCRIPT_DATA}" CACHE PATH
+SET(SALOME_INSTALL_SCRIPT_DATA "${SALOME_INSTALL_SCRIPT_DATA}" CACHE PATH
"Install path: SALOME script data")
-SET(SALOME_INSTALL_SCRIPT_PYTHON "${SALOME_INSTALL_SCRIPT_PYTHON}" CACHE PATH
+SET(SALOME_INSTALL_SCRIPT_PYTHON "${SALOME_INSTALL_SCRIPT_PYTHON}" CACHE PATH
"Install path: SALOME Python scripts")
SET(SALOME_INSTALL_PYTHON "${SALOME_INSTALL_PYTHON}" CACHE PATH "Install path: SALOME Python stuff")
-SET(SALOME_INSTALL_PYTHON_SHARED "${SALOME_INSTALL_PYTHON_SHARED}" CACHE PATH
+SET(SALOME_INSTALL_PYTHON_SHARED "${SALOME_INSTALL_PYTHON_SHARED}" CACHE PATH
"Install path: SALOME Python shared modules")
SET(SALOME_INSTALL_CMAKE "${SALOME_INSTALL_CMAKE}" CACHE PATH "Install path: SALOME CMake files")
-SET(SALOME_INSTALL_CMAKE_LOCAL "${SALOME_INSTALL_CMAKE_LOCAL}" CACHE PATH
- "Install path: local SALOME CMake files")
+SET(SALOME_INSTALL_CMAKE_LOCAL "${SALOME_INSTALL_CMAKE_LOCAL}" CACHE PATH
+ "Install path: local SALOME CMake files")
SET(SALOME_INSTALL_AMCONFIG_LOCAL "${SALOME_INSTALL_AMCONFIG_LOCAL}" CACHE PATH
"Install path: local SALOME config files (obsolete, to be removed)")
SET(SALOME_INSTALL_DOC "${SALOME_INSTALL_DOC}" CACHE PATH "Install path: SALOME documentation")
# Specific to GUI:
-SET(SALOME_GUI_INSTALL_RES_DATA "${SALOME_INSTALL_RES}/gui" CACHE PATH
+SET(SALOME_GUI_INSTALL_RES_DATA "${SALOME_INSTALL_RES}/gui" CACHE PATH
"Install path: SALOME GUI specific data")
-SET(SALOME_GUI_INSTALL_RES_SCRIPTS "${SALOME_INSTALL_RES}/gui" CACHE PATH
+SET(SALOME_GUI_INSTALL_RES_SCRIPTS "${SALOME_INSTALL_RES}/gui" CACHE PATH
"Install path: SALOME GUI scripts")
-SET(SALOME_GUI_INSTALL_PLUGINS share/salome/plugins/gui CACHE PATH
+SET(SALOME_GUI_INSTALL_PLUGINS share/salome/plugins/gui CACHE PATH
"Install path: SALOME GUI plugins")
MARK_AS_ADVANCED(SALOME_INSTALL_BINS SALOME_INSTALL_LIBS SALOME_INSTALL_IDLS SALOME_INSTALL_HEADERS)
${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}
${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}
${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON_SHARED})
-SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS})
-
-# Sources
+SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS})
+
+# Sources
# ========
IF(NOT SALOME_LIGHT_ONLY)
ADD_SUBDIRECTORY(idl)
# List of targets in this project we want to make visible to the rest of the world.
# They all have to be INSTALL'd with the option "EXPORT ${PROJECT_NAME}TargetGroup"
-SET(_${PROJECT_NAME}_exposed_targets
+SET(_${PROJECT_NAME}_exposed_targets
CAM CASCatch DDS Event LightApp LogWindow ObjBrowser
QDS qtx SalomePrs SalomeStyle std SUITApp suit ViewerTools ViewerData
ImageComposer
)
IF(SALOME_USE_OCCVIEWER OR SALOME_USE_VTKVIEWER OR SALOME_USE_GLVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
OpenGLUtils)
ENDIF(SALOME_USE_OCCVIEWER OR SALOME_USE_VTKVIEWER OR SALOME_USE_GLVIEWER)
# SALOME object specific targets:
IF(SALOME_USE_SALOMEOBJECT)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
SalomeObject)
ENDIF(SALOME_USE_SALOMEOBJECT)
# GL specific targets:
IF(SALOME_USE_GLVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
GLViewer)
ENDIF(SALOME_USE_GLVIEWER)
# VTK specific targets:
IF(SALOME_USE_VTKVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
VTKViewer )
IF(SALOME_USE_SALOMEOBJECT)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
SVTK)
ENDIF(SALOME_USE_SALOMEOBJECT)
ENDIF(SALOME_USE_VTKVIEWER)
# OCC specific targets:
IF(SALOME_USE_OCCVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
OCCViewer)
IF(SALOME_USE_SALOMEOBJECT)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
SOCC)
ENDIF(SALOME_USE_SALOMEOBJECT)
ENDIF(SALOME_USE_OCCVIEWER)
# Plot2d specific targets:
IF(SALOME_USE_PLOT2DVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
Plot2d)
IF(SALOME_USE_SALOMEOBJECT)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
SPlot2d)
ENDIF(SALOME_USE_SALOMEOBJECT)
ENDIF(SALOME_USE_PLOT2DVIEWER)
# Qx specific targets:
IF(SALOME_USE_QXGRAPHVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
QxScene)
ENDIF(SALOME_USE_QXGRAPHVIEWER)
# GraphicsView specific targets:
IF(SALOME_USE_GRAPHICSVIEW)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
GraphicsView)
ENDIF(SALOME_USE_GRAPHICSVIEW)
# PyEditor/Viewer specific targets:
IF(SALOME_USE_PYVIEWER)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
PyEditor PyViewer)
ENDIF(SALOME_USE_PYVIEWER)
# Python-based packages specific targets:
IF(SALOME_USE_PYCONSOLE)
- LIST(APPEND _${PROJECT_NAME}_exposed_targets
+ LIST(APPEND _${PROJECT_NAME}_exposed_targets
PyInterp PyConsole SalomePyQtGUILight)
IF(SALOME_USE_PLOT2DVIEWER)
LIST(APPEND _${PROJECT_NAME}_exposed_targets SalomePyQt)
SET(PARAVIEW_ROOT_DIR "${PARAVIEW_ROOT_DIR}")
SET(VTK_ROOT_DIR "${VTK_ROOT_DIR}")
SET(QWT_ROOT_DIR "${QWT_ROOT_DIR}")
-
+
SET(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/include")
# Build variables that will be expanded when configuring Salome<MODULE>Config.cmake:
SALOME_CONFIGURE_PREPARE(OpenCASCADE OpenGL Qt5 PyQt5 Qwt SIP ParaView VTK)
-CONFIGURE_PACKAGE_CONFIG_FILE(${PROJECT_NAME}Config.cmake.in
+CONFIGURE_PACKAGE_CONFIG_FILE(${PROJECT_NAME}Config.cmake.in
${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}"
PATH_VARS CONF_INCLUDE_DIRS SALOME_INSTALL_CMAKE_LOCAL CMAKE_INSTALL_PREFIX
- KERNEL_ROOT_DIR SIP_ROOT_DIR QT_ROOT_DIR PYQT_ROOT_DIR OPENCASCADE_ROOT_DIR
+ KERNEL_ROOT_DIR SIP_ROOT_DIR QT_ROOT_DIR PYQT_ROOT_DIR OPENCASCADE_ROOT_DIR
OPENGL_ROOT_DIR PARAVIEW_ROOT_DIR VTK_ROOT_DIR QWT_ROOT_DIR)
# - in the install tree (VSR 16/08/2013: TEMPORARILY COMMENT THIS - TO REMOVE?):
-# Get the relative path of the include directory so
+# Get the relative path of the include directory so
# we can register it in the generated configuration files:
#SET(CONF_INCLUDE_DIRS "${CMAKE_INSTALL_PREFIX}/${INSTALL_INCLUDE_DIR}")
-#CONFIGURE_PACKAGE_CONFIG_FILE(adm_local/cmake_files/${PROJECT_NAME}Config.cmake.in
+#CONFIGURE_PACKAGE_CONFIG_FILE(adm_local/cmake_files/${PROJECT_NAME}Config.cmake.in
# ${PROJECT_BINARY_DIR}/to_install/${PROJECT_NAME}Config.cmake
# INSTALL_DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}"
-# PATH_VARS CONF_INCLUDE_DIRS SALOME_INSTALL_CMAKE_LOCAL CMAKE_INSTALL_PREFIX
-# KERNEL_ROOT_DIR SIP_ROOT_DIR QT_ROOT_DIR PYQT_ROOT_DIR OPENCASCADE_ROOT_DIR
+# PATH_VARS CONF_INCLUDE_DIRS SALOME_INSTALL_CMAKE_LOCAL CMAKE_INSTALL_PREFIX
+# KERNEL_ROOT_DIR SIP_ROOT_DIR QT_ROOT_DIR PYQT_ROOT_DIR OPENCASCADE_ROOT_DIR
# OPENGL_ROOT_DIR PARAVIEW_ROOT_DIR VTK_ROOT_DIR QWT_ROOT_DIR)
WRITE_BASIC_PACKAGE_VERSION_FILE(${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
VERSION ${${PROJECT_NAME_UC}_VERSION}
COMPATIBILITY AnyNewerVersion)
-
+
# Install the CMake configuration files:
INSTALL(FILES
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}")
# Install the export set for use with the install-tree
-INSTALL(EXPORT ${PROJECT_NAME}TargetGroup DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}"
+INSTALL(EXPORT ${PROJECT_NAME}TargetGroup DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}"
FILE ${PROJECT_NAME}Targets.cmake)
#include <cstdio>
#include <iostream>
+#include <utilities.h>
+
namespace
{
class BusyLocker
bool CAM_Application::appendModuleInfo( const QString& modName )
{
+ MESSAGE("Start to append module info for a given module name: ");
+ SCRUTE(modName.toStdString());
+
if ( modName.isEmpty() )
return false; // empty module name
// we cannot use own resourceMgr() as this method can be called from constructor
SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
- // "gui" option explicitly says that module has GUI
- bool hasGui = resMgr->booleanValue( modName, "gui", true );
-
ModuleInfo inf;
// module internal name
inf.version = resMgr->stringValue( modName, "version", QString() ).trimmed();
// displayer, if module does not have GUI, displayer may be delegated to other module
inf.displayer = resMgr->stringValue( modName, "displayer", QString() ).trimmed();
+
+ // "gui" option explicitly says that module has GUI
+ // Now trying to get the "gui" option value, we always get a default one,
+ // then we can't rely on it.
+ bool hasGui = resMgr->booleanValue(modName, "gui", false);
+
+ // Additional check if the module actually has a title and icon.
+ // Module with GUI must explicitly specify title (GUI name).
+ inf.title = resMgr->stringValue(modName, "name", QString()).trimmed();
+ const bool hasTitle = !inf.title.isEmpty();
+ SCRUTE(hasGui);
+ SCRUTE(hasTitle);
+ if (hasGui && !hasTitle)
+ {
+ MESSAGE("Invalid config! The module has gui option, but doesn't have a title.");
+ return false;
+ }
+
+ // While we can't rely on gui option, use a title to make a decision about gui.
+ hasGui = hasTitle;
+
// status; if module has GUI, availability will be checked on activation
inf.status = hasGui ? stUnknown : stNoGui;
if ( hasGui )
{
- // module with GUI must explicitly specify title (GUI name)
- inf.title = resMgr->stringValue( modName, "name", QString() ).trimmed();
- if ( inf.title.isEmpty() )
- inf.status = stInvalid;
// icon
inf.icon = resMgr->stringValue( modName, "icon", QString() ).trimmed();
// description, for Info panel
inf.library = modName;
}
- if ( inf.status != stInvalid )
- myInfoList.append( inf );
+ // At this point we should have only valid inf object.
+ myInfoList.append(inf);
+
+ SCRUTE(inf.name.toStdString());
+ SCRUTE(inf.version.toStdString());
+ SCRUTE(inf.displayer.toStdString());
+ SCRUTE(inf.status);
+ SCRUTE(inf.title.toStdString());
+ SCRUTE(inf.icon.toStdString());
+ SCRUTE(inf.description.toStdString());
+ SCRUTE(inf.library.toStdString());
return true;
}
ADD_DEFINITIONS(${QT_DEFINITIONS})
# libraries to link to
-SET(_link_LIBRARIES ${QT_LIBRARIES} qtx suit std)
+SET(_link_LIBRARIES
+ ${QT_LIBRARIES}
+ ${KERNEL_SALOMELocalTrace}
+ qtx suit std
+ )
# --- headers ---
${OpenCASCADE_FoundationClasses_LIBRARIES}
${QT_LIBRARIES}
${HDF5_LIBRARIES}
- CASCatch qtx suit std SalomeStyle SalomePrs CAM LogWindow ObjBrowser Event
+ CASCatch qtx suit std SalomeStyle SalomePrs CAM LogWindow ObjBrowser Event
${KERNEL_SalomeHDFPersist} ${KERNEL_SALOMELocalTrace}
+ ${GRAPHVIZ_LIBRARIES}
)
+
IF(SALOME_USE_SALOMEOBJECT)
LIST(APPEND _link_LIBRARIES SalomeObject)
ENDIF()
# --- headers ---
# header files / to be processed by moc
-SET(_moc_HEADERS
+SET(_moc_HEADERS
LightApp_AboutDlg.h
LightApp_Application.h
LightApp_DataModel.h
LightApp_Study.h
LightApp_SwitchOp.h
LightApp_WgViewModel.h
+ LightApp_ExtInfoDlg.h
)
IF(SALOME_USE_GLVIEWER)
LIST(APPEND _moc_HEADERS LightApp_GLSelector.h)
resources/icon_life_ring.png
resources/icon_add_module.png
resources/icon_remove_module.png
+ resources/icon_info_module.png
resources/LightApp.xml
)
LightApp_Study.cxx
LightApp_SwitchOp.cxx
LightApp_WgViewModel.cxx
+ LightApp_ExtInfoDlg.cxx
)
IF(SALOME_USE_GLVIEWER)
LIST(APPEND _other_SOURCES LightApp_GLSelector.cxx)
// 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
#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"
//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_ ) {
foreach( QString aModule, aModuleList )
createHelpItems( aModule );
-
+
// f) Additional help items
int id = LightApp_Application::UserID + FIRST_HELP_ID + 1000;
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 );
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.
*/
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;
+ }
- // loop via selected configuration files
- foreach( QString path, paths )
+ LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+ if (!moduleAction)
{
- // read description file (.salomex) and check it's OK
- QtxResourceMgr resMgr;
- if ( !resMgr.addResource( path ) )
+ 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_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() )
+
+ // Iterate all the components (modules) for this extension
+ for (Py_ssize_t pos = 0; pos < PyList_Size(unpackedModules); ++pos)
{
- SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_NAME" ).arg( path ) );
- continue;
+ auto moduleNameItem = PyList_GetItem(unpackedModules, pos);
+ QString moduleName(PyUnicode_AsUTF8(moduleNameItem));
+ SCRUTE(moduleName.toStdString());
+
+ addUserModule(moduleName, SalomeExtDir, true);
}
- // retrieve root directory
- QString root = resMgr.stringValue( "General", "root" ).trimmed();
- if ( root.isEmpty() )
+
+ // Add an extension to GUI
+ if (moduleAction)
{
- SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_ROOT" ).arg( path ) );
- continue;
+ QFileInfo extFileInfo(path);
+ QString extName = extFileInfo.baseName();
+ moduleAction->insertExtension(extName);
}
- addUserModule( name, root, true );
}
+
+ // Udate actions only once after all of them were already inserted
+ moduleAction->updateExtActions();
}
/*Add user module.*/
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 ) );
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 ) ) )
// 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());
- if ( rootDirectory.exists() )
+ // 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)
{
- 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
{
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();
#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();
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();
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" ); //
aResMgr->value( "windows_visibility", modName, aDefaultVisibility );
bool hasDefaultVisibility = !aDefaultVisibility.isEmpty();
aResMgr->setWorkingMode( prevMode );
-
+
if( !storeWin && !storeTb && aDefaultState.isEmpty() && !hasDefaultVisibility)
return;
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) {
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()] );
}
}
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()] );
}
}
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);
}
/*!
- Internal method.
+ Internal method.
Returns all top level toolbars.
Note : Result list contains only main window toolbars, not including toolbars from viewers.
*/
class LightApp_SelectionMgr;
class LightApp_FullScreenHelper;
class LightApp_DataObject;
+class LightApp_ModuleAction;
class SUIT_DataBrowser;
class SUIT_Study;
class SUIT_Accel;
protected:
void showHelp( const QString& );
virtual void createActions();
+ virtual void addExtensionsActions(LightApp_ModuleAction* moduleAction);
virtual void customize();
virtual void createActionForViewer( const int id,
const int parentId,
void onNewWindow();
virtual void onModuleActivation( const QString& );
- void onModuleAdding();
- void onModuleRemoving( const QString& );
+ void onExtAdding();
+ void onExtRemoving( const QString& );
+ void onShowExtInfo();
void onCloseView( SUIT_ViewManager* );
virtual void onStudyCreated( SUIT_Study* );
private:
bool addUserModule( const QString&, const QString&, bool = false );
+ void removeUserModule(const QString& moduleInnerName, LightApp_ModuleAction* moduleAction);
void emptyPreferences( const QString& );
QList<QToolBar*> findToolBars( const QStringList& names = QStringList() );
void createHelpItems( const QString& );
--- /dev/null
+// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE
+//
+// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+// File: LightApp_ExtInfoDlg.cxx
+// Author: Konstantin Leontev
+//
+#include "LightApp_ExtInfoDlg.h"
+#include "utilities.h"
+
+// Prevent slot compilation error
+#pragma push_macro("slots")
+#undef slots
+#include "PyInterp_Utils.h"
+#pragma pop_macro("slots")
+
+#include <QHBoxLayout>
+
+// Show extensions info
+#include <QTableWidget>
+#include <QTableWidgetItem>
+#include <QHeaderView>
+
+// Render dependency tree
+#include <QSvgWidget>
+#include <QByteArray>
+#include <graphviz/gvc.h>
+
+
+/*! RAII wrapper to graphviz Agraph_t.*/
+class GraphWrapper
+{
+ public:
+ virtual ~GraphWrapper();
+
+ void init(char* name, Agdesc_t desc = Agstrictdirected, Agdisc_t* disc = nullptr);
+
+ Agraph_t* getGraph() const { return m_graph; }
+ GVC_t* getGvc() const { return m_gvc; }
+
+ private:
+ Agraph_t* m_graph = nullptr;
+ GVC_t* m_gvc = nullptr;
+};
+
+GraphWrapper::~GraphWrapper()
+{
+ if (m_graph)
+ {
+ agclose(m_graph);
+ m_graph = nullptr;
+ }
+
+ if (m_gvc)
+ {
+ gvFreeContext(m_gvc);
+ m_gvc = nullptr;
+ }
+}
+
+void GraphWrapper::init(char *name, Agdesc_t desc /* = Agstrictdirected */, Agdisc_t* disc /* = nullptr */)
+{
+ m_graph = agopen(name, desc, disc);
+ m_gvc = gvContext();
+}
+
+/*!Constructor.*/
+LightApp_ExtInfoDlg::LightApp_ExtInfoDlg(QWidget* parent)
+: QtxDialog(parent, true, true, ButtonFlags::OK)
+{
+ MESSAGE("Start creating a dialog...\n");
+
+ setObjectName("salome_ext_info_dialog");
+ setWindowTitle(tr("EXT_INFO_CAPTION"));
+ setSizeGripEnabled(true);
+ setButtonPosition(ButtonPosition::Center, ButtonFlags::OK);
+
+ auto extInfoWiget = getExtListWidget(mainFrame());
+ auto extTreeWiget = getExtTreeWidget(mainFrame());
+
+ auto layout = new QHBoxLayout(mainFrame());
+ layout->addWidget(extInfoWiget);
+ layout->addWidget(extTreeWiget);
+}
+
+/*!Destructor.*/
+LightApp_ExtInfoDlg::~LightApp_ExtInfoDlg()
+{
+ //! Do nothing.
+}
+
+/*! Fill the given widget with info about installed extensions */
+bool LightApp_ExtInfoDlg::fillExtListWidget(QTableWidget* extListWidget) const
+{
+ MESSAGE("Getting info from SalomeOnDemandTK.extension_query...\n");
+
+ // Import Python module that manages SALOME extensions
+ PyLockWrapper lck; // acquire GIL
+ PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
+ auto extRootDir = getenv("SALOME_APPLICATION_DIR");
+ PyObjWrapper extInfoDict = PyObject_CallMethod(extensionQuery, (char*)"ext_info_dict", (char*)"s", extRootDir);
+ if (!extInfoDict)
+ {
+ PyErr_Print();
+ return false;
+ }
+
+ // Check if we have any info to display
+ Py_ssize_t rowCount = PyDict_Size(extInfoDict);
+ if (!rowCount)
+ {
+ MESSAGE("Didn't find any extensions! Return.\n");
+ return false;
+ }
+
+ extListWidget->setRowCount(rowCount);
+ const int columnCount = extListWidget->columnCount();
+
+ auto makeTableWidgetItem = [](PyObject* itemText) -> QTableWidgetItem*
+ {
+ const char* itemTextStr = PyUnicode_AsUTF8(itemText);
+ SCRUTE(itemTextStr);
+
+ return new QTableWidgetItem(itemTextStr);
+ };
+
+ PyObject* keyName = nullptr;
+ PyObject* infoList = nullptr;
+ Py_ssize_t keyNamePos = 0;
+
+ // Iterate name:info_list dictionary
+ while (PyDict_Next(extInfoDict, &keyNamePos, &keyName, &infoList))
+ {
+ auto widgetItem = makeTableWidgetItem(keyName);
+
+ // keyNamePos is already 1 on the first iteration, so we need to decrease it
+ extListWidget->setItem(keyNamePos - 1, 0, widgetItem);
+
+ // Iterate an extension info list
+ for (Py_ssize_t infoPos = 0; infoPos < PyList_Size(infoList); ++infoPos)
+ {
+ if (infoPos >= columnCount)
+ {
+ MESSAGE("Number of info items is greater than column count! Skip.\n");
+ break;
+ }
+
+ auto info = PyList_GetItem(infoList, infoPos);
+ widgetItem = makeTableWidgetItem(info);
+
+ // keyNamePos started from 1 instead of 0, so decrease
+ // info need to be filled from column 1, so increase
+ extListWidget->setItem(keyNamePos - 1, infoPos + 1, widgetItem);
+ }
+ }
+
+ return true;
+}
+
+/*! Return widget with info about installed extensions */
+QWidget* LightApp_ExtInfoDlg::getExtListWidget(QWidget* parent) const
+{
+ MESSAGE("Make a widget to display extensions info...\n");
+
+ auto extListWidget = new QTableWidget(parent);
+
+ // Setup the table params
+ const QStringList headerLabels = {
+ "Name", "Description", "Author", "Components", "Size"
+ };
+
+ extListWidget->setColumnCount(headerLabels.count());
+ extListWidget->setHorizontalHeaderLabels(headerLabels);
+
+ // Fill it with data about extensions
+ if (fillExtListWidget(extListWidget))
+ {
+ // Tune an appearance
+ extListWidget->sortItems(0);
+ extListWidget->horizontalHeader()->setStretchLastSection(true);
+ extListWidget->resizeColumnsToContents();
+ extListWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
+ }
+
+ return extListWidget;
+}
+
+/*! Fill a given extension tree graph with nodes and edges */
+bool LightApp_ExtInfoDlg::fillExtTreeGraph(const GraphWrapper& graph) const
+{
+ MESSAGE("Start to get info from SalomeOnDemandTK.extension_query...\n");
+
+ // Import Python module that manages SALOME extensions
+ PyLockWrapper lck; // acquire GIL
+ PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
+ auto extRootDir = getenv("SALOME_APPLICATION_DIR");
+ PyObjWrapper dependencyTree = PyObject_CallMethod(extensionQuery, (char*)"dependency_tree", (char*)"s", extRootDir);
+ if (!dependencyTree)
+ {
+ PyErr_Print();
+ return false;
+ }
+
+ Agraph_t* extTreeGraph = graph.getGraph();
+
+ // Python declarations
+ PyObject* extName = nullptr;
+ PyObject* dependantsList = nullptr;
+ Py_ssize_t pos = 0;
+
+ // Iterate the tree
+ while (PyDict_Next(dependencyTree, &pos, &extName, &dependantsList))
+ {
+ // Create a parent node if it doesn't already exist
+ auto parentName = PyUnicode_AsUTF8(extName);
+ Agnode_t* parentNode = agnode(extTreeGraph, const_cast<char*>(parentName), true);
+ SCRUTE(parentName);
+
+ // Iterate a list of dependants
+ for (Py_ssize_t depPos = 0; depPos < PyList_Size(dependantsList); ++depPos)
+ {
+ auto dependant = PyList_GetItem(dependantsList, depPos);
+
+ // Create a child node if it doesn't already exist
+ auto dependantName = PyUnicode_AsUTF8(dependant);
+ Agnode_t* dependantNode = agnode(extTreeGraph, const_cast<char*>(dependantName), true);
+ SCRUTE(dependantName);
+
+ // Make an edge
+ std::string edgeNameStr(parentName);
+ edgeNameStr += dependantName;
+ auto edgeName = edgeNameStr.c_str();
+ agedge(extTreeGraph, parentNode, dependantNode, const_cast<char*>(edgeName), true);
+ SCRUTE(edgeName);
+ }
+ }
+
+ return true;
+}
+
+/*! Render dependency tree to array of bytes */
+QByteArray LightApp_ExtInfoDlg::renderExtTreeGraph(const GraphWrapper& graph) const
+{
+ Agraph_t* extTreeGraph = graph.getGraph();
+ GVC_t* gvc = graph.getGvc();
+
+ // Layout and render to buffer
+ MESSAGE("Layout dependency tree...\n");
+ int res = gvLayout(gvc, extTreeGraph, "dot");
+ if (res)
+ {
+ MESSAGE("gvLayout failed!\n");
+ return {};
+ }
+
+ MESSAGE("Render dependency tree...\n");
+ char* buffer = nullptr;
+ unsigned int bufferLength = 0;
+ res = gvRenderData(gvc, extTreeGraph, "svg", &buffer, &bufferLength);
+ if (res)
+ {
+ MESSAGE("gvRenderData failed!\n");
+ return {};
+ }
+
+ QByteArray renderedGraph(buffer, bufferLength);
+
+ // Clean up layout and buffer
+ gvFreeLayout(gvc, extTreeGraph);
+ gvFreeRenderData(buffer);
+
+ return renderedGraph;
+}
+
+/*! Return widget with an image of dependency tree fot installed extensions */
+QWidget* LightApp_ExtInfoDlg::getExtTreeWidget(QWidget* parent) const
+{
+ MESSAGE("Start to make a widget to show dependency tree...\n");
+
+ auto extTreeWidget = new QSvgWidget(parent);
+
+ // Graph to be filled up from python data
+ GraphWrapper extTreeGraph;
+ char graphName[] = "extTreeGraph";
+ extTreeGraph.init(graphName);
+ if (fillExtTreeGraph(extTreeGraph))
+ {
+ extTreeWidget->load(renderExtTreeGraph(extTreeGraph));
+ }
+
+ return extTreeWidget;
+}
--- /dev/null
+// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE
+//
+// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+// File: LightApp_ExtInfoDlg.h
+// Author: Konstantin Leontev
+//
+#ifndef LIGHTAPP_EXTINFODLG_H
+#define LIGHTAPP_EXTINFODLG_H
+
+#include "LightApp.h"
+
+#include <QtxDialog.h>
+
+class QTableWidget;
+class GraphWrapper;
+class QByteArray;
+
+/*!
+ \class LightApp_ExtInfoDlg
+ LightApp dialog with info about loaded extensions
+*/
+class LIGHTAPP_EXPORT LightApp_ExtInfoDlg : public QtxDialog
+{
+ Q_OBJECT
+
+public:
+ LightApp_ExtInfoDlg(QWidget* = 0);
+ virtual ~LightApp_ExtInfoDlg();
+
+private:
+ QWidget* getExtListWidget(QWidget* parent) const;
+ QWidget* getExtTreeWidget(QWidget* parent) const;
+
+ bool fillExtListWidget(QTableWidget* extListWidget) const;
+ bool fillExtTreeGraph(const GraphWrapper& graph) const;
+ QByteArray renderExtTreeGraph(const GraphWrapper& graph) const;
+};
+
+#endif
#include <QMenu>
#include <QSignalMapper>
+#include <utilities.h>
+
+// Prevent slot compilation error
+#pragma push_macro("slots")
+#undef slots
+#include "PyInterp_Utils.h"
+#pragma pop_macro("slots")
+
/*!
\class LightApp_ModuleAction::ActionSet
\brief Internal class to represent list of modules buttons.
\param parent parent object
*/
LightApp_ModuleAction::ActionSet::ActionSet( QObject* parent )
-: QtxActionSet( parent )
+: QtxActionSet( parent )
{
}
In menu, the action is represented as a plain list of items, one per module.
- Only one module can be active at the moment. It can be set programmatically
+ Only one module can be active at the moment. It can be set programmatically
with setActiveModule() function. Use this method with empty string to turn
to the "neutral point". To get active module, use activeModule() function.
0, this );
myRemove->setEnabled( false );
myRemove->setMenu( new QMenu() );
+ myInfo = new QtxAction( tr( "INFO_MODULE"),
+ resMgr->loadPixmap( "LightApp", tr( "ICON_INFO_MODULE" ), false ),
+ tr( "INFO_MODULE"),
+ 0, this );
mySeparator = new QAction( this );
mySeparator->setSeparator( true );
mySet = new ActionSet( this );
connect( this, SIGNAL( changed() ), this, SLOT( onChanged() ) );
connect( myAdd, SIGNAL( triggered( bool ) ), this, SIGNAL( adding() ) );
+ connect( myInfo, SIGNAL( triggered( bool ) ), this, SIGNAL( showExtInfo() ) );
connect( mySet, SIGNAL( triggered( int ) ), this, SLOT( onTriggered( int ) ) );
connect( myCombo, SIGNAL( activatedId( int ) ), this, SLOT( onComboActivated( int ) ) );
connect( myMapper, SIGNAL( mapped( QString ) ), this, SIGNAL( removing( QString ) ) );
{
QtxAction* a = new QtxAction( name, ico, name, 0, this, true );
a->setStatusTip( tr( "ACTIVATE_MODULE_TOP" ).arg( name ) );
- a->setData( isCustom );
- if ( isCustom )
- {
- myRemove->setEnabled( true );
- QAction* inserted = myRemove->menu()->addAction( name );
- connect( inserted, SIGNAL( triggered() ), myMapper, SLOT( map() ) );
- myMapper->setMapping( inserted, name );
- }
+
+ // Commented because the next call mySet->insertAction() overrides it with
+ // action id int != 0 value, so test a->data().toBool() is always true after that.
+ // Leave it here to mention that we need other approach to mark module as custom.
+ // a->setData( isCustom );
mySet->insertAction( a, -1, idx );
update();
*/
void LightApp_ModuleAction::removeModule( const QString& name )
{
+ MESSAGE("Start to remove module...");
+
int id = mySet->moduleId( name );
+ SCRUTE(id);
if ( id == -1 )
+ {
+ MESSAGE("Can't get a module's id! Return");
return;
+ }
+
+ MESSAGE("Remove action by id...");
+ mySet->removeAction(id);
+
+ update();
+
+ MESSAGE("Module was removed");
+}
+
+/*!
+ \brief Add an installed extension. Now only to the Remove button's menu.
+ \param name an extension's name
+ \sa removeExtension()
+*/
+void LightApp_ModuleAction::insertExtension(const QString& name)
+{
+ MESSAGE("Insert an extension's action...");
+ SCRUTE(name.toStdString());
- QAction* a = moduleAction( name );
- bool isCustom = a->data().toBool();
+ myRemove->setEnabled(true);
- mySet->removeAction( id );
- if ( isCustom )
+ // Find a place to insert in the alphabetical order
+ QAction* insertBefore = nullptr;
+ foreach(QAction* curAction, myRemove->menu()->actions())
{
- foreach ( QAction* ma, myRemove->menu()->actions() )
+ int compareRes = QString::compare(curAction->text(), name, Qt::CaseInsensitive);
+ if (!compareRes)
{
- if ( ma->text() == name )
- {
- myRemove->menu()->removeAction( ma );
- break;
- }
+ return; // already added
+ }
+ else if (compareRes > 0)
+ {
+ insertBefore = curAction;
+
+ SCRUTE(insertBefore->text().toStdString());
+ break;
+ }
+ }
+
+ QAction* inserted = new QAction(name);
+
+ myRemove->menu()->insertAction(insertBefore, inserted);
+ connect(inserted, SIGNAL(triggered()), myMapper, SLOT(map()));
+ myMapper->setMapping(inserted, name);
+
+ MESSAGE("An extension's action was inserted");
+}
+
+/*!
+ \brief Remove an installed extension.
+ \param name an extension's name
+ \sa insertExtension()
+*/
+void LightApp_ModuleAction::removeExtension(const QString& name)
+{
+ MESSAGE("Remove an extension's action...");
+ SCRUTE(name.toStdString());
+
+ foreach(QAction* ma, myRemove->menu()->actions())
+ {
+ if (ma->text() == name)
+ {
+ myRemove->menu()->removeAction(ma);
+
+ MESSAGE("Extension's action was removed");
+ break;
}
- myRemove->setEnabled( !myRemove->menu()->actions().isEmpty() );
}
+ myRemove->setEnabled(!myRemove->menu()->actions().isEmpty());
+
+ updateExtActions();
update();
}
/*!
\brief Get active module.
- If there is no active module ("neutral point"), then the null string
+ If there is no active module ("neutral point"), then the null string
is returned.
\return active module name
w->insertAction( this, myCombo );
w->insertAction( this, myAdd );
w->insertAction( this, myRemove );
+ w->insertAction( this, myInfo );
w->insertAction( this, mySeparator );
w->insertAction( this, mySet );
update();
w->removeAction( myCombo );
w->removeAction( myAdd );
w->removeAction( myRemove );
+ w->removeAction( myInfo );
w->removeAction( mySeparator );
w->removeAction( mySet );
}
for ( QList<QtxComboBox*>::const_iterator it = lst.begin(); it != lst.end(); ++it )
update( *it );
+
myCombo->setVisible( myMode & List );
- myAdd->setVisible( myMode & AddRemove );
- myRemove->setVisible( myMode & AddRemove );
+ if ( QString::compare(getenv("SALOME_ON_DEMAND"),"HIDE", Qt::CaseInsensitive) != 0)
+ {
+ myAdd->setVisible( myMode & AddRemove );
+ myRemove->setVisible( myMode & AddRemove );
+ myInfo->setVisible( myMode & All );
+ }
+ else
+ {
+ myAdd->setVisible(false);
+ myRemove->setVisible( false );
+ myInfo->setVisible( false );
+ }
mySet->setVisible( myMode & Buttons );
}
int curId = mySet->moduleId( active() );
QList<QAction*> alist = mySet->actions();
cb->clear();
-
+
cb->addItem( icon(), text() );
cb->setId( 0, -1 );
cb->blockSignals( blocked );
}
+/*!
+ \brief Update extension actions based on dependencies.
+ \internal
+ \param
+*/
+void LightApp_ModuleAction::updateExtActions()
+{
+ MESSAGE("Check dependencies to update extensions actions...");
+
+ // It should be set on the app start
+ auto extRootDir = getenv("SALOME_APPLICATION_DIR");
+ if (!extRootDir)
+ {
+ MESSAGE("Cannot get SALOME_APPLICATION_DIR env variable! Cancel adding selected extensions.");
+ return;
+ }
+ SCRUTE(extRootDir);
+
+ // Import Python module that manages SALOME extensions.
+ PyLockWrapper lck; // acquire GIL
+ PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
+ PyObjWrapper extCanRemoveDict = PyObject_CallMethod(extensionQuery, (char*)"ext_canremove_flags", (char*)"s", extRootDir);
+ if (!extCanRemoveDict || extCanRemoveDict == Py_None)
+ {
+ MESSAGE("Couldn't get <ext>:<can_remove> dictionary from SalomeOnDemandTK.extension_query! Return.");
+ return;
+ }
+
+ // Iterate extensions' actions to disable ones we can't remove because of dependencies.
+ foreach(QAction* curAction, myRemove->menu()->actions())
+ {
+ const std::string action_name = curAction->text().toStdString();
+ SCRUTE(action_name);
+
+ PyObject* canRemoveObject = PyDict_GetItemString(extCanRemoveDict, action_name.c_str());
+ if (!canRemoveObject)
+ {
+ MESSAGE("Couldn't get can remove flag from dictionary! Skip.");
+ continue;
+ }
+
+ const int isTrueRes = PyObject_IsTrue(canRemoveObject);
+ if (isTrueRes == -1)
+ {
+ MESSAGE("PyObject_IsTrue() failed. Using false value instead.");
+ }
+ const bool canRemove = isTrueRes == 1;
+ SCRUTE(canRemove);
+
+ curAction->setEnabled(canRemove);
+ }
+}
+
/*!
\brief Get an action corresponding to the active module.
\internal
/*!
\brief Called when action state is changed.
\internal
-
+
This slot is used to prevent making the parent action visible.
*/
void LightApp_ModuleAction::onChanged()
void LightApp_ModuleAction::onComboActivated( int id )
{
QApplication::postEvent( this, new ActivateEvent( QEvent::MaxUser, id ) );
-}
+}
void insertModule( const QString&, const QIcon&, const int = -1 );
void insertModule( const QString&, const QIcon&, bool, const int = -1 );
void removeModule( const QString& );
+ void insertExtension(const QString&);
+ void removeExtension(const QString&);
+ void updateExtActions();
QString activeModule() const;
void moduleActivated( const QString& );
void adding();
void removing( const QString& );
+ void showExtInfo();
private:
void update();
ComboAction* myCombo;
QtxAction* myAdd;
QtxAction* myRemove;
+ QtxAction* myInfo;
ActionSet* mySet;
QAction* mySeparator;
int myMode;
<source>ICON_REMOVE_MODULE</source>
<translation>icon_remove_module.png</translation>
</message>
+ <message>
+ <source>ICON_INFO_MODULE</source>
+ <translation>icon_info_module.png</translation>
+ </message>
</context>
</TS>
<source>ABOUT_MODULE_INFOS</source>
<translation>Modules information</translation>
</message>
+ <message>
+ <source>EXT_INFO_CAPTION</source>
+ <translation>Extensions information</translation>
+ </message>
<message>
<source>ABOUT_UNKNOWN_VERSION</source>
<translation>Unknown</translation>
Continue?</translation>
</message>
+ <message>
+ <source>WRN_FAILED_UNPACK_EXTENSION</source>
+ <translation>Failed to unpack a salome extension from file: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_FAILED_REMOVE_EXTENSION</source>
+ <translation>Failed to remove a salome extension: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_SALOME_APPLICATION_DIR</source>
+ <translation>Cannot get SALOME_APPLICATION_DIR env variable!
+Cancel current extensions operation.
+ </translation>
+ </message>
+ <message>
+ <source>TLT_REMOVE_EXTENSION</source>
+ <translation>Remove extension</translation>
+ </message>
+ <message>
+ <source>QUE_REMOVE_EXTENSION</source>
+ <translation>Do you want to completely remove an extension %1 and all its modules and files?
+If you answer "Ok", you may need to save your study before removal.
+Continue?
+ </translation>
+ </message>
+ <message>
+ <source>WRN_CANCEL_REMOVE_EXTENSION_UNSAVE</source>
+ <translation>Removing of a salome extension %1 was cancelled because current doc is unsaved.
+ </translation>
+ </message>
</context>
<context>
<name>LightApp_Module</name>
<source>REMOVE_MODULE</source>
<translation>Remove modules</translation>
</message>
+ <message>
+ <source>INFO_MODULE</source>
+ <translation>Module information</translation>
+ </message>
</context>
<context>
<name>LightApp_PyEditor</name>
<source>ABOUT_MODULE_INFOS</source>
<translation>Modules</translation>
</message>
+ <message>
+ <source>EXT_INFO_CAPTION</source>
+ <translation>Informations sur les extensions</translation>
+ </message>
<message>
<source>ABOUT_UNKNOWN_VERSION</source>
<translation>Inconnue</translation>
<source>WRN_MODULE_DUPLICATED</source>
<translation>Module "%1" is already present in this session</translation>
</message>
+ <message>
+ <source>WRN_FAILED_UNPACK_EXTENSION</source>
+ <translation>Échec de la décompression d'une extension salome à partir d'un fichier: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_FAILED_REMOVE_EXTENSION</source>
+ <translation>Échec de la suppression d'une extension Salomé: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_SALOME_APPLICATION_DIR</source>
+ <translation>Impossible d'obtenir la variable d'environnement SALOME_APPLICATION_DIR!
+Annulez l'opération d'extensions en cours.
+ </translation>
+ </message>
+ <message>
+ <source>TLT_REMOVE_EXTENSION</source>
+ <translation>Supprimer l'extension</translation>
+ </message>
+ <message>
+ <source>QUE_REMOVE_EXTENSION</source>
+ <translation>Voulez-vous supprimer complètement une extension %1 et tous ses modules et fichiers?
+Si vous répondez "Ok", vous devrez peut-être enregistrer votre étude avant de la supprimer.
+Continuer?
+ </translation>
+ </message>
+ <message>
+ <source>WRN_CANCEL_REMOVE_EXTENSION_UNSAVE</source>
+ <translation>La suppression d'une extension salome %1 a été annulée car la documentation actuelle n'est pas enregistrée.
+ </translation>
+ </message>
<message>
<source>WRN_MODULE_BAD_RESDIR</source>
<translation>Bad or non-existing resources directory:
<source>REMOVE_MODULE</source>
<translation>Supprimer les modules</translation>
</message>
+ <message>
+ <source>INFO_MODULE</source>
+ <translation>Information sur le module</translation>
+ </message>
</context>
<context>
<name>LightApp_PyEditor</name>
<source>ABOUT_MODULE_INFOS</source>
<translation>モジュール情報</translation>
</message>
+ <message>
+ <source>EXT_INFO_CAPTION</source>
+ <translation>内線情報</translation>
+ </message>
<message>
<source>ABOUT_UNKNOWN_VERSION</source>
<translation>不明</translation>
Continue?</translation>
</message>
+ <message>
+ <source>WRN_FAILED_UNPACK_EXTENSION</source>
+ <translation>ファイルからサロメ拡張機能を解凍できませんでした: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_FAILED_REMOVE_EXTENSION</source>
+ <translation>サロメ エクステンションの削除に失敗しました: %1.</translation>
+ </message>
+ <message>
+ <source>WRN_SALOME_APPLICATION_DIR</source>
+ <translation>SALOME_APPLICATION_DIR 環境変数を取得できません!
+現在の拡張操作をキャンセルします.
+ </translation>
+ </message>
+ <message>
+ <source>TLT_REMOVE_EXTENSION</source>
+ <translation>拡張子を削除</translation>
+ </message>
+ <message>
+ <source>QUE_REMOVE_EXTENSION</source>
+ <translation>拡張機能を完全に削除しますか %1 およびそのすべてのモジュールとファイル?
+"Ok" と答えた場合、削除する前にスタディを保存する必要があるかもしれません。
+続く?
+ </translation>
+ </message>
+ <message>
+ <source>WRN_CANCEL_REMOVE_EXTENSION_UNSAVE</source>
+ <translation>サロメ拡張の除去 %1 現在のドキュメントが保存されていないため、キャンセルされました。
+ </translation>
+ </message>
</context>
<context>
<name>LightApp_Module</name>