From: vsr Date: Thu, 21 Apr 2022 13:07:06 +0000 (+0300) Subject: bos #29458 Salome on demand X-Git-Tag: V9_9_1b1^2 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=6ebe476d3bc01ddc4f7529ff63807a8153a6cb95;p=modules%2Fgui.git bos #29458 Salome on demand --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 06d28fc02..069b7fea0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ SET(BUILD_SHARED_LIBS TRUE) 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) CMAKE_DEPENDENT_OPTION(SALOME_GUI_BUILD_FRENCH_DOC "Generate SALOME GUI French documentation" OFF "SALOME_BUILD_DOC" OFF) @@ -136,6 +137,11 @@ IF(SALOME_GUI_USE_OBSERVERS) ADD_DEFINITIONS(-DWITH_SALOMEDS_OBSERVER) ENDIF() +# SALOME on demand +IF(SALOME_ON_DEMAND) + ADD_DEFINITIONS(-DWITH_SALOME_ON_DEMAND) +ENDIF() + # Single-study GUI IF(SALOME_USE_SINGLE_DESKTOP) ADD_DEFINITIONS(-DSINGLE_DESKTOP) diff --git a/SalomeGUIConfig.cmake.in b/SalomeGUIConfig.cmake.in index 6edcf645d..62159e832 100644 --- a/SalomeGUIConfig.cmake.in +++ b/SalomeGUIConfig.cmake.in @@ -64,7 +64,11 @@ SET(SALOME_USE_PYCONSOLE @SALOME_USE_PYCONSOLE@) SET(SALOME_USE_SALOMEOBJECT @SALOME_USE_SALOMEOBJECT@) SET(SALOME_GUI_USE_OBSERVERS @SALOME_GUI_USE_OBSERVERS@) SET(SALOME_GUI_USE_OPENGL2 @SALOME_GUI_USE_OPENGL2@) +SET(SALOME_ON_DEMAND @SALOME_ON_DEMAND@) +IF(SALOME_ON_DEMAND) + LIST(APPEND GUI_DEFINITIONS "-DWITH_SALOME_ON_DEMAND") +ENDIF() IF(SALOME_GUI_LIGHT_ONLY) LIST(APPEND GUI_DEFINITIONS "-DGUI_DISABLE_CORBA") ENDIF() diff --git a/doc/salome/gui/images/modules_add.png b/doc/salome/gui/images/modules_add.png new file mode 100644 index 000000000..0e37c5cdb Binary files /dev/null and b/doc/salome/gui/images/modules_add.png differ diff --git a/doc/salome/gui/images/modules_remove.png b/doc/salome/gui/images/modules_remove.png new file mode 100644 index 000000000..d15caeed4 Binary files /dev/null and b/doc/salome/gui/images/modules_remove.png differ diff --git a/doc/salome/gui/images/modules_toolbar.png b/doc/salome/gui/images/modules_toolbar.png new file mode 100644 index 000000000..147cbf4ef Binary files /dev/null and b/doc/salome/gui/images/modules_toolbar.png differ diff --git a/doc/salome/gui/input/extend_salome.rst b/doc/salome/gui/input/extend_salome.rst new file mode 100644 index 000000000..e7f967b48 --- /dev/null +++ b/doc/salome/gui/input/extend_salome.rst @@ -0,0 +1,85 @@ +.. _extend_salome: + +****************************** +Add/remove modules dynamically +****************************** + +.. contents:: Table of Contents + +"SALOME on demand" feature provides a possibility to dynamically add or remove +SALOME modules directly from GUI. + +.. _hp_salomex_format: + +Description file +================ + +**"SALOME on demand"** feature introduces notion of a module description file. +This is the file in JSON format that has `.salomex` extension. The description +file provides some parameters of the module; two parameters are mandatory: +name of the module and path to its root directory. + +Example: + +.. code-block:: json + + { + "name": "PYHELLO", + "root": "/home/user/salome/modules/PYHELLO" + } + +.. note:: In the description file, the `name` parameter is an **internal** name of the module, + in contrast to its **title** which is described in `SalomeApp.xml` or `LightApp.xml` + resource file. + +.. note:: One `.salomex` file may describe one SALOME module only. + +.. _hp_modules_toolbar: + +Modules toolbar +=============== + +The *Modules* toolbar provides two additional buttons, which allow adding or removing +modules directly from GUI. + +.. figure:: ../images/modules_toolbar.png + :align: center + :alt: Modules toolbar + +.. |add| image:: ../images/modules_add.png + :height: 24px + +.. |remove| image:: ../images/modules_remove.png + :height: 24px + +.. _hp_modules_add: + +Adding modules +-------------- + +Pressing the first button |add| shows a standard *Open File* dialog that allows +selecting one or more `.salomex` files. Each module correctly configured in the +corresponding `.salomex` file is then added to the SALOME session and appears in the +*Modules* toolbar. As soon as it is done, the user may work with the module in the same +way as with any other module: all features are available after activating module from +*Modules* toolbar. + +The modules added in this way become *persistent*. This means that after restarting +SALOME these modules are available in the *Modules* toolbar. + +.. _hp_modules_remove: + +Removing modules +---------------- + +Pressing a small arrow near the second button |remove| shows a drop-down menu with +the list of manually added modules, where the user may select a module to remove. As soon +as user selects a module to remove, a message box is shown asking the user if it is +necessary also to remove a directory, containing the module, from disk. If user +confirms removal of the directory, it will be necessary to save the study, in order to +avoid possible data loss. + +.. note:: The module libraries are not removed from RAM until exit from the current + SALOME session, so some its functionality may be still accessible, for example, + via Python API. However, using module functionality may have unpredicted + behavior. diff --git a/doc/salome/gui/input/howtos_and_best_practives.rst b/doc/salome/gui/input/howtos_and_best_practives.rst index d281d87c3..837d8c071 100644 --- a/doc/salome/gui/input/howtos_and_best_practives.rst +++ b/doc/salome/gui/input/howtos_and_best_practives.rst @@ -13,4 +13,4 @@ and usage examples of SALOME platform. drag_and_drop.rst using_pluginsmanager.rst using_help_panel.rst - + extend_salome.rst diff --git a/src/CAM/CAM_Application.cxx b/src/CAM/CAM_Application.cxx index afd1faf37..2e6f51e43 100644 --- a/src/CAM/CAM_Application.cxx +++ b/src/CAM/CAM_Application.cxx @@ -757,6 +757,7 @@ void CAM_Application::readModuleList() if ( !myInfoList.isEmpty() ) return; + // we cannot use own resourceMgr() as this method can be called from constructor SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); QStringList modList; @@ -801,88 +802,85 @@ void CAM_Application::readModuleList() modList = mods.split( ",", QString::SkipEmptyParts ); } - for ( QStringList::const_iterator it = modList.begin(); it != modList.end(); ++it ) - { - QString modName = (*it).trimmed(); - - if ( modName.isEmpty() ) - continue; // empty module name - - if ( !moduleTitle( modName ).isEmpty() ) - continue; // already added + // extra modules loaded manually on previous session + // ... - if ( modName == "KERNEL" || modName == "GUI" ) - continue; // omit KERNEL and GUI modules + foreach ( QString modName, modList ) + appendModuleInfo( modName.trimmed() ); - bool hasGui = resMgr->booleanValue( *it, "gui", true ); - - QString modTitle, modIcon, modLibrary, modDescription; - - if ( hasGui ) - { - // if module has GUI, check that it is present - modTitle = resMgr->stringValue( *it, "name", QString() ); - if ( modTitle.isEmpty() ) + if ( myInfoList.isEmpty() ) { + if ( desktop() && desktop()->isVisible() ) + SUIT_MessageBox::warning( desktop(), tr( "Warning" ), tr( "Modules list is empty" ) ); + else { printf( "****************************************************************\n" ); - printf( " Warning: module %s is improperly configured!\n", qPrintable(*it) ); - printf( " Module %s will not be available in GUI mode!\n", qPrintable(*it) ); + printf( "* Warning: modules list is empty.\n" ); printf( "****************************************************************\n" ); - continue; } + } +} - modIcon = resMgr->stringValue( *it, "icon", QString() ); +bool CAM_Application::appendModuleInfo( const QString& modName ) +{ + if ( modName.isEmpty() ) + return false; // empty module name - modDescription = resMgr->stringValue( *it, "description", QString() ); + if ( !moduleTitle( modName ).isEmpty() ) + return false; // already added - modLibrary = resMgr->stringValue( *it, "library", QString() ).trimmed(); - if ( !modLibrary.isEmpty() ) - { - modLibrary = SUIT_Tools::file( modLibrary.trimmed() ); -#if defined(WIN32) - QString libExt = QString( "dll" ); -#elif defined(__APPLE__) - QString libExt = QString( "dylib" ); -#else - QString libExt = QString( "so" ); -#endif - if ( SUIT_Tools::extension( modLibrary ).toLower() == libExt ) - modLibrary.truncate( modLibrary.length() - libExt.length() - 1 ); -#ifndef WIN32 - QString prefix = QString( "lib" ); - if ( modLibrary.startsWith( prefix ) ) - modLibrary.remove( 0, prefix.length() ); -#endif - } - else - modLibrary = modName; - } + if ( modName == "KERNEL" || modName == "GUI" ) + return false; // skip KERNEL and GUI modules - QString version = resMgr->stringValue( *it, "version", QString() ); + // we cannot use own resourceMgr() as this method can be called from constructor + SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); - QString modDisplayer = resMgr->stringValue( *it, "displayer", QString() ); + // "gui" option explicitly says that module has GUI + bool hasGui = resMgr->booleanValue( modName, "gui", true ); - ModuleInfo inf; - inf.name = modName; - inf.title = modTitle; - inf.status = hasGui ? stUnknown : stNoGui; - if ( hasGui ) inf.library = modLibrary; - inf.icon = modIcon; - inf.description = modDescription; - inf.displayer = modDisplayer; - inf.version = version; - myInfoList.append( inf ); + ModuleInfo inf; + + // module internal name + inf.name = modName; + // module version + 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(); + // 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.description = resMgr->stringValue( modName, "description", QString() ); + // library; if not specified, we use internal module name + inf.library = SUIT_Tools::libraryName( resMgr->stringValue( modName, "library", QString() ).trimmed() ); + if ( inf.library.isEmpty() ) + inf.library = modName; } - if ( myInfoList.isEmpty() ) { - if ( desktop() && desktop()->isVisible() ) - SUIT_MessageBox::warning( desktop(), tr( "Warning" ), tr( "Modules list is empty" ) ); - else - { - printf( "****************************************************************\n" ); - printf( "* Warning: modules list is empty.\n" ); - printf( "****************************************************************\n" ); - } + if ( inf.status != stInvalid ) + myInfoList.append( inf ); + + return true; +} + +void CAM_Application::removeModuleInfo( const QString& modName ) +{ + QMutableListIterator it( myInfoList ); + while ( it.hasNext() ) + { + ModuleInfo info = it.next(); + if ( info.name == modName ) + { + it.remove(); + break; + } } } diff --git a/src/CAM/CAM_Application.h b/src/CAM/CAM_Application.h index 3dc79da71..daa7624e5 100644 --- a/src/CAM/CAM_Application.h +++ b/src/CAM/CAM_Application.h @@ -97,21 +97,25 @@ protected: virtual bool abortAllOperations(); -private: - void readModuleList(); +protected: + bool appendModuleInfo( const QString& ); + void removeModuleInfo( const QString& ); private: - enum { stUnknown = 0, stNoGui, stInaccessible, stReady }; - typedef struct { + enum { stInvalid = -1, stUnknown = 0, stNoGui, stInaccessible, stReady }; + struct ModuleInfo + { QString name, title, icon, library, version, description, displayer; int status; - } ModuleInfo; - typedef QList ModuleInfoList; + ModuleInfo() : status( stInvalid ) {} + }; + void readModuleList(); private: + typedef QList ModuleInfoList; + static ModuleInfoList myInfoList; //!< modules info list CAM_Module* myModule; //!< active module ModuleList myModules; //!< loaded modules list - static ModuleInfoList myInfoList; //!< modules info list bool myAutoLoad; //!< auto loading flag bool myBlocked; //!< "blocked" flag, internal usage }; diff --git a/src/LightApp/CMakeLists.txt b/src/LightApp/CMakeLists.txt index efb0579fe..993298328 100644 --- a/src/LightApp/CMakeLists.txt +++ b/src/LightApp/CMakeLists.txt @@ -224,10 +224,11 @@ SET(_other_RESOURCES resources/icon_applogo.png resources/icon_default.png resources/icon_module.png - resources/icon_module_big.png resources/icon_select.png resources/icon_earth.png resources/icon_life_ring.png + resources/icon_add_module.png + resources/icon_remove_module.png resources/LightApp.xml ) diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index e64fef2c7..718efbd13 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -323,6 +323,13 @@ namespace } return QString(); } + + const bool HAS_SALOME_ON_DEMAND = +#if defined(WITH_SALOME_ON_DEMAND) + true; +#else + false; +#endif } /*!Create new instance of LightApp_Application.*/ @@ -625,8 +632,6 @@ void LightApp_Application::createActions() int helpMenu = createMenu( tr( "MEN_DESK_HELP" ), -1, -1, 1000 ); - int id = LightApp_Application::UserID + FIRST_HELP_ID; - QString url; // a) Link to web site @@ -688,87 +693,17 @@ void LightApp_Application::createActions() // e) Help for modules - // - First create top-level menus to preserve correct order - QString userGuide = "User's Guide"; - QString devGuide = "Developer's Guide"; - createMenu( userGuide, helpMenu, -1, 5 ); - createMenu( devGuide, helpMenu, -1, 5 ); - QStringList aModuleList; modules( aModuleList, false ); aModuleList.prepend( "GUI" ); aModuleList.prepend( "KERNEL" ); - QString aModule; - foreach( aModule, aModuleList ) { - if ( aModule.isEmpty() ) // module title (user name) - continue; - IMap helpData; // list of help files for the module - QString helpSubMenu; // help submenu name (empty if not needed) - QString modName = moduleName( aModule ); // module name - if ( modName.isEmpty() ) modName = aModule; // for KERNEL and GUI - QString rootDir = QString( "%1_ROOT_DIR" ).arg( modName ); // module root dir env variable - QString modDir = Qtx::getenv( rootDir.toUtf8().constData() ); // module root dir path - QString docSection; - if (resMgr->hasValue( modName, "documentation" ) ) - docSection = resMgr->stringValue(modName, "documentation"); - else if ( resMgr->hasSection( modName + "_documentation" ) ) - docSection = modName + "_documentation"; - if ( !docSection.isEmpty() ) { - helpSubMenu = resMgr->stringValue( docSection, "sub_menu", "" ); - if ( helpSubMenu.contains( "%1" ) ) - helpSubMenu = helpSubMenu.arg( aModule ); - foreach( QString paramName, resMgr->parameters( docSection ) ) { - QString key = paramName.contains( "%1" ) ? paramName.arg( aModule ) : 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( aModule ), indexFile ); - } - - IMapConstIterator fileIt; - for ( fileIt = helpData.begin(); fileIt != helpData.end(); fileIt++ ) { - QString helpItemPath = fileIt.key(); - // remove all '//' occurances - while ( helpItemPath.contains( "//" ) ) - helpItemPath.replace( "//", "" ); - // obtain submenus hierarchy if given - QStringList smenus = helpItemPath.split( "/" ); - helpItemPath = smenus.takeLast(); - // workaround for User's Guide and Developer's Guide to avoid having single item in module's submenu. - if ( helpItemPath == userGuide || helpItemPath == devGuide ) { - QString menuPath = smenus.join( "/" ); - QStringList allKeys = helpData.keys(); - QStringList total = allKeys.filter( QRegExp( QString( "^%1" ).arg( menuPath ) ) ); - if ( total.count() == 1 && smenus.count() > 0 ) - helpItemPath = smenus.takeLast(); - } - QPixmap helpIcon = fileIt.value().startsWith( "http", Qt::CaseInsensitive ) ? - resMgr->loadPixmap( "STD", tr( "ICON_WWW" ), false ) : resMgr->loadPixmap( "STD", tr( "ICON_HELP" ), false ); - QAction* a = createAction( id, helpItemPath, helpIcon, helpItemPath, helpItemPath, - 0, desk, false, this, SLOT( onHelpContentsModule() ) ); - a->setData( fileIt.value() ); - if ( !helpSubMenu.isEmpty() ) { - smenus.prepend( helpSubMenu ); - } - // create sub-menus hierarchy - int menuId = helpMenu; - foreach ( QString subMenu, smenus ) - menuId = createMenu( subMenu, menuId, -1, 5 ); - createMenu( a, menuId, -1, ( menuId != helpMenu && (helpItemPath == userGuide || helpItemPath == devGuide) ) ? 0 : 5 ); - id++; - } - } - - // - Additional help items + foreach( QString aModule, aModuleList ) + createHelpItems( aModule ); + + // f) Additional help items + int id = LightApp_Application::UserID + FIRST_HELP_ID + 1000; createMenu( separator(), helpMenu, -1, 10 ); QStringList addHelpItems = resMgr->parameters( "add_help" ); @@ -790,57 +725,24 @@ void LightApp_Application::createActions() connect( mru, SIGNAL( activated( const QString& ) ), this, SLOT( onMRUActivated( const QString& ) ) ); registerAction( MRUId, mru ); - // default icon for neutral point ('SALOME' module) - QPixmap defIcon = resMgr->loadPixmap( "LightApp", tr( "APP_DEFAULT_ICO" ), false ); - if ( defIcon.isNull() ) - defIcon = QPixmap( imageEmptyIcon ); - - //! default icon for any module - QPixmap modIcon = resMgr->loadPixmap( "LightApp", tr( "APP_MODULE_ICO" ), false ); - if ( modIcon.isNull() ) - modIcon = QPixmap( imageEmptyIcon ); - + // List of modules + LightApp_ModuleAction* moduleAction = new LightApp_ModuleAction( resMgr, desk ); + registerAction( ModulesListId, moduleAction ); + // a. here we add regular modules (specified to GUI via --modules cmd line option, or default list from configuration) + // b. custom modules are added in customize() method QStringList modList; modules( modList, false ); - - if ( modList.count() > 1 ) - { - LightApp_ModuleAction* moduleAction = - new LightApp_ModuleAction( tr( "APP_NAME" ), defIcon, desk ); - - QMap iconMap; - moduleIconNames( iconMap ); - - const int iconSize = 20; - - QStringList::Iterator it; - for ( it = modList.begin(); it != modList.end(); ++it ) - { - QString modName = moduleName( *it ); - - QString iconName; - if ( iconMap.contains( *it ) ) - iconName = iconMap[*it]; - - QPixmap icon = resMgr->loadPixmap( modName, iconName, false ); - if ( icon.isNull() ) - { - icon = modIcon; - INFOS( std::endl << - "****************************************************************" << std::endl << - " Warning: icon for " << qPrintable(*it) << " is not found!" << std::endl << - " Using the default icon." << std::endl << - "****************************************************************" << std::endl); - } - icon = Qtx::scaleIcon( icon, iconSize ); - - moduleAction->insertModule( *it, icon ); - } - - connect( moduleAction, SIGNAL( moduleActivated( const QString& ) ), - this, SLOT( onModuleActivation( const QString& ) ) ); - registerAction( ModulesListId, moduleAction ); - } + foreach ( QString aModule, modList ) + moduleAction->insertModule( aModule, moduleIcon( aModule, 20 ) ); // scale icon to 20x20 pix + + connect( this, SIGNAL( moduleActivated( QString ) ), + moduleAction, SLOT( setActiveModule( QString ) ) ); + connect( moduleAction, SIGNAL( moduleActivated( const QString& ) ), + this, SLOT( onModuleActivation( const QString& ) ) ); + connect( moduleAction, SIGNAL( adding() ), + this, SLOT( onModuleAdding() ) ); + connect( moduleAction, SIGNAL( removing( QString ) ), + this, SLOT( onModuleRemoving( QString ) ) ); // New window int windowMenu = createMenu( tr( "MEN_DESK_WINDOW" ), -1, MenuWindowId, 100 ); @@ -910,26 +812,44 @@ void LightApp_Application::createActions() createMenu( StyleId, viewMenu, 20, -1 ); #endif // USE_SALOME_STYLE createMenu( FullScreenId, viewMenu, 20, -1 ); + createMenu( separator(), viewMenu, -1, 20, -1 ); + createMenu( ModulesListId, viewMenu ); int modTBar = createTool( tr( "INF_TOOLBAR_MODULES" ), // title (language-dependant) QString( "SalomeModules" ) ); // name (language-independant) createTool( ModulesListId, modTBar ); } +/*! + Customize actions. +*/ +void LightApp_Application::customize() +{ + // List of modules + LightApp_ModuleAction* moduleAction = qobject_cast( 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 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 opmap = activateModuleActions(); for ( QMap::ConstIterator it = opmap.begin(); it != opmap.end(); ++it ) aDlg.addButton( it.value(), it.key() ); @@ -942,17 +862,186 @@ void LightApp_Application::onModuleActivation( const QString& modName ) else { // cancelled putInfo( tr("INF_CANCELLED") ); - - LightApp_ModuleAction* moduleAction = - qobject_cast( action( ModulesListId ) ); - if ( moduleAction ) - moduleAction->setActiveModule( QString() ); + emit moduleActivated( QString() ); cancelled = true; } } if ( !cancelled ) - activateModule( modName ); + activateModule( modTitle ); +} + +/*!On module adding action.*/ +void LightApp_Application::onModuleAdding() +{ + // show dialog to browse configuration file + QStringList filters = ( QStringList() << tr( "Config files") + " (*.salomex)" << tr( "All files" ) + " (*)" ); + QStringList paths = getOpenFileNames( QString(), filters.join( ";;" ), QString(), desktop() ); + if ( paths.isEmpty() ) // cancelled + return; + + // loop via selected configuration files + foreach( QString path, paths ) + { + // read description file (.salomex) and check it's OK + QtxResourceMgr resMgr; + if ( !resMgr.addResource( path ) ) + { + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_SALOMEX_FILE" ).arg( path ) ); + continue; + } + // retrieve module name + QString name = resMgr.stringValue( "General", "name" ).trimmed(); + if ( name.isEmpty() ) + { + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_NAME" ).arg( path ) ); + continue; + } + // retrieve root directory + QString root = resMgr.stringValue( "General", "root" ).trimmed(); + if ( root.isEmpty() ) + { + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_EMPTY_ROOT" ).arg( path ) ); + continue; + } + addUserModule( name, root, true ); + } +} + +/*Add user module.*/ +bool LightApp_Application::addUserModule( const QString& name, const QString& root, bool interactive ) +{ + if ( name.isEmpty() || root.isEmpty() ) + return false; + + if ( !moduleTitle( name ).isEmpty() ) // module alread in current session + { + if ( interactive ) + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_DUPLICATED" ).arg( name ) ); + return false; + } + if ( !QFileInfo( root ).exists() ) // root directory does not exist + { + if ( interactive ) + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_ROOT_DOES_NOT_EXIST" ).arg( root ) ); + return false; + } + // resources directory + QString resDir = Qtx::joinPath( QStringList() << root << "share" << "salome" << "resources" << name.toLower() ); + if ( !QFileInfo( resDir ).exists() ) // resources directory does not exist + { + if ( interactive ) + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_RESDIR" ).arg( resDir ) ); + return false; + } + // read XML configuration file + resourceMgr()->setConstant( QString( "%1_ROOT_DIR" ).arg( name ), root ); + if ( !resourceMgr()->addResource( resDir ) ) // cannot read configuration + { + if ( interactive ) + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_CANNOT_READ_CFG" ).arg( resDir ) ); + return false; + } + // fill in information about module + if ( !appendModuleInfo( name ) ) // cannot append module information to internal table + { + if ( interactive ) + SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_CFG_FILE" ).arg( name ) ); + return false; + } + // load translations + resourceMgr()->loadLanguage( name ); + // append module to the menu / toolbar + LightApp_ModuleAction* moduleAction = qobject_cast( action( ModulesListId ) ); + if ( moduleAction ) + moduleAction->insertModule( moduleTitle( name ), moduleIcon( moduleTitle( name ), 20 ), true ); // scale icon to 20x20 pix + // add empty page to Preferences dialog + LightApp_Preferences* prefs = preferences(); + if ( prefs && !prefs->hasModule( moduleTitle( name ) ) ) + { + int prefId = prefs->addPreference( moduleTitle( name ) ); + prefs->setItemIcon( prefId, moduleIcon( moduleTitle( name ), 20 ) ); // scale icon to 20x20 pix + LightApp_Module* m = qobject_cast( module( moduleTitle( name ) ) ); + if ( m ) + { + m->createPreferences(); + emptyPreferences( moduleTitle( name ) ); + } + } + // add Help items + createHelpItems( moduleTitle( name ) ); + // extend module catalog + QString catalogue = QDir( resDir ).filePath( QString( "%1Catalog.xml" ).arg( name ) ); + addCatalogue( name, catalogue ); + // update windows (in particular, Info panel) + updateWindows(); + // save module in the resource manager + if ( interactive ) + { + QStringList customModules = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts ); + customModules << name; + customModules.removeDuplicates(); + resourceMgr()->setValue( "launch", "user_modules", customModules.join( ";" ) ); + resourceMgr()->setValue( "user_modules", name, root ); + } + return true; +} + +/*!On module removing action.*/ +void LightApp_Application::onModuleRemoving( const QString& title ) +{ + QString root = resourceMgr()->stringValue( "user_modules", moduleName( title ) ); + QDir rootDirectory = QDir( root ); + + if ( rootDirectory.exists() ) + { + int answer = SUIT_MessageBox::question( desktop(), + tr( "TLT_REMOVE_MODULE" ), + tr( "QUE_REMOVE_MODULE_DIR" ).arg( root ), + SUIT_MessageBox::Yes | SUIT_MessageBox::No | SUIT_MessageBox::Cancel, + SUIT_MessageBox::No ); + if ( answer == SUIT_MessageBox::Cancel ) + return; // cancelled + if ( answer == SUIT_MessageBox::Yes ) + { + if ( activeStudy() && activeStudy()->isModified() && !onSaveDoc() ) + // doc is not saved, or saving cancelled + return; + if ( !rootDirectory.removeRecursively() ) + { + // canont remove directory + if ( SUIT_MessageBox::question( desktop(), + tr( "WRN_WARNING" ), + tr( "WRN_CANNOT_REMOVE_DIR" ).arg( root ), + SUIT_MessageBox::Yes | SUIT_MessageBox::No, + SUIT_MessageBox::No ) == SUIT_MessageBox::No ) + return; // removal is cancelled + } + } + } + + if ( activeModule() && activeModule()->moduleName() == title ) + activateModule( "" ); + + // remove from "Modules" menu and toolbar + LightApp_ModuleAction* moduleAction = qobject_cast( action( ModulesListId ) ); + if ( moduleAction ) + { + moduleAction->removeModule( title ); + } + // remove Help menu items + removeHelpItems( title ); + // remove Preferences + LightApp_Preferences* prefs = preferences(); + if ( prefs ) + prefs->removeModule( title ); + // remove settings + QStringList customModules = resourceMgr()->stringValue( "launch", "user_modules" ).split( ";", QString::SkipEmptyParts ); + customModules.removeAll( moduleName( title ) ); + resourceMgr()->setValue( "launch", "user_modules", customModules.join( ";" ) ); + removeModuleInfo( moduleName( title ) ); + // update windows (in particular, Info panel) + updateWindows(); } /*!Default module activation.*/ @@ -1220,7 +1309,7 @@ protected: { // normalize path if ( myUrl.startsWith( "file://", Qt::CaseInsensitive ) ) - myUrl = myUrl.remove( 0, QString( "file://" ).count() ); + myUrl = myUrl.remove( 0, QString( "file://" ).count() ); // For the external browser we always specify 'file://' protocol, // because some web browsers (e.g. Mozilla Firefox) can't open local file without protocol. myUrl = myUrl.prepend( "file://" ); @@ -1335,7 +1424,7 @@ void LightApp_Application::onHelpContextModule( const QString& component, QString rootDir = Qtx::getenv( (component + "_ROOT_DIR").toLatin1().constData() ); if ( !rootDir.isEmpty() ) { - path = (QStringList() << rootDir << "share" << "doc" << "salome" << "gui" << component << url).join( QDir::separator() ); + path = (QStringList() << rootDir << "share" << "doc" << "salome" << "gui" << component << url).join( QDir::separator() ); } } } @@ -2260,8 +2349,6 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const if ( !crt ) return myPrefs; - SUIT_ResourceMgr* resMgr = resourceMgr(); - QList appList = SUIT_Session::session()->applications(); for ( QList::iterator appIt = appList.begin(); appIt != appList.end(); ++appIt ) { @@ -2273,10 +2360,6 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const QStringList names; app->modules( names, false ); - // icons of modules - QMap icons; - app->moduleIconNames( icons ); - // step 1: iterate through list of all available modules // and add empty preferences page for ( QStringList::const_iterator it = names.begin(); it != names.end(); ++it ) @@ -2284,9 +2367,7 @@ LightApp_Preferences* LightApp_Application::preferences( const bool crt ) const if ( !_prefs_->hasModule( *it ) ) // prevent possible duplications { int modId = _prefs_->addPreference( *it ); // add empty page - if ( icons.contains( *it ) ) // set icon - _prefs_->setItemIcon( modId, Qtx::scaleIcon( resMgr->loadPixmap( moduleName( *it ), - icons[*it], false ), 20 ) ); + _prefs_->setItemIcon( modId, moduleIcon( *it, 20 ) ); // scale icon to 20x20 pix } } @@ -4045,22 +4126,7 @@ void LightApp_Application::afterCloseDoc() */ void LightApp_Application::updateModuleActions() { - QString modName; - if ( activeModule() ) - modName = activeModule()->moduleName(); - - LightApp_ModuleAction* moduleAction = - qobject_cast( action( ModulesListId ) ); - if ( moduleAction ) - moduleAction->setActiveModule( modName ); -} - -void LightApp_Application::removeModuleAction( const QString& modName ) -{ - LightApp_ModuleAction* moduleAction = - qobject_cast( action( ModulesListId ) ); - if ( moduleAction ) - moduleAction->removeModule( modName ); + emit moduleActivated( activeModule() ? activeModule()->moduleName() : QString() ); } bool LightApp_Application::checkModule( const QString& title ) @@ -4452,34 +4518,18 @@ void LightApp_Application::dockWindowsState( const QByteArray& arr, QMap& 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; } /*! @@ -5401,17 +5451,17 @@ bool LightApp_Application::checkExistingDoc( bool closeExistingDoc ) if( activeStudy() ) { int answer = !activeStudy()->isModified() ? 1 : SUIT_MessageBox::question( desktop(), - tr( "APPCLOSE_CAPTION" ), - tr( "STUDYCLOSE_DESCRIPTION" ), - tr( "APPCLOSE_SAVE" ), - tr( "APPCLOSE_CLOSE" ), - tr( "APPCLOSE_CANCEL" ), 0 ); + tr( "APPCLOSE_CAPTION" ), + tr( "STUDYCLOSE_DESCRIPTION" ), + tr( "APPCLOSE_SAVE" ), + tr( "APPCLOSE_CLOSE" ), + tr( "APPCLOSE_CANCEL" ), 0 ); if(answer == 0) { if ( activeStudy()->isSaved() ) { onSaveDoc(); - if (closeExistingDoc) { - closeDoc(false); - } + if (closeExistingDoc) { + closeDoc(false); + } } else if ( onSaveAsDoc() ) { if (closeExistingDoc) { if( !closeDoc( false ) ) { @@ -5424,7 +5474,7 @@ bool LightApp_Application::checkExistingDoc( bool closeExistingDoc ) } else if( answer == 1 ) { if (closeExistingDoc) { - closeDoc( false ); + closeDoc( false ); } } else if( answer == 2 ) { result = false; @@ -5451,3 +5501,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 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 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 ); + } +} diff --git a/src/LightApp/LightApp_Application.h b/src/LightApp/LightApp_Application.h index c6e43cf47..aae6c005e 100644 --- a/src/LightApp/LightApp_Application.h +++ b/src/LightApp/LightApp_Application.h @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -194,6 +195,7 @@ public: #endif signals: + void moduleActivated( const QString& ); void studyOpened(); void studySaved(); void studyClosed(); @@ -217,6 +219,7 @@ public slots: protected: void showHelp( const QString& ); virtual void createActions(); + virtual void customize(); virtual void createActionForViewer( const int id, const int parentId, const QString& suffix, @@ -251,6 +254,8 @@ protected: virtual PyConsole_Interp* createPyInterp(); #endif + virtual void addCatalogue( const QString&, const QString& ) {} + protected slots: virtual void onDesktopActivated(); virtual void onViewManagerRemoved( SUIT_ViewManager* ); @@ -258,6 +263,8 @@ protected slots: void onNewWindow(); virtual void onModuleActivation( const QString& ); + void onModuleAdding(); + void onModuleRemoving( const QString& ); void onCloseView( SUIT_ViewManager* ); virtual void onStudyCreated( SUIT_Study* ); @@ -293,7 +300,6 @@ protected: void updateWindows(); void updateViewManagers(); void updateModuleActions(); - void removeModuleAction( const QString& ); bool checkModule( const QString& ); @@ -309,7 +315,7 @@ protected: QString defaultModule() const; virtual void currentWindows( QMap& ) const; void currentViewManagers( QStringList& ) const; - void moduleIconNames( QMap& ) const; + QPixmap moduleIcon( const QString&, const int = -1 ) const; QDockWidget* windowDock( QWidget* ) const; QByteArray dockWindowsState( const QMap&, const QMap& ) const; @@ -322,20 +328,25 @@ protected: void showPreferences( const QStringList& ); private: + bool addUserModule( const QString&, const QString&, bool = false ); void emptyPreferences( const QString& ); QList findToolBars( const QStringList& names = QStringList() ); + void createHelpItems( const QString& ); + void removeHelpItems( const QString& ); QByteArray processState(QByteArray& input, - const bool processWin, - const bool processTb, - const bool isRestoring, - QByteArray defaultState = QByteArray()); + const bool processWin, + const bool processTb, + const bool isRestoring, + QByteArray defaultState = QByteArray()); protected: - typedef QPointer WinPtr; - typedef QMap WinMap; - typedef QMap WinVis; - typedef QMap WinGeom; + typedef QPointer WinPtr; + typedef QMap WinMap; + typedef QMap WinVis; + typedef QMap WinGeom; + typedef QList IdList; + typedef QMap IdMap; enum { OpenReload = CAM_Application::OpenExist + 1 }; @@ -347,6 +358,7 @@ protected: WinMap myWin; WinVis myWinVis; + IdMap myHelpItems; SUIT_Accel* myAccel; QTimer* myAutoSaveTimer; diff --git a/src/LightApp/LightApp_ModuleAction.cxx b/src/LightApp/LightApp_ModuleAction.cxx index d6a8ae939..f7a8f27af 100644 --- a/src/LightApp/LightApp_ModuleAction.cxx +++ b/src/LightApp/LightApp_ModuleAction.cxx @@ -22,11 +22,14 @@ // #include "LightApp_ModuleAction.h" -#include #include +#include +#include #include #include #include +#include +#include /*! \class LightApp_ModuleAction::ActionSet @@ -146,7 +149,7 @@ QList LightApp_ModuleAction::ComboAction::widgets() const QList wlist = createdWidgets(); for ( QList::const_iterator wit = wlist.begin(); wit != wlist.end(); ++wit ) - lst += (*wit)->findChildren(); + lst += qobject_cast(*wit); return lst; } @@ -162,18 +165,11 @@ QWidget* LightApp_ModuleAction::ComboAction::createWidget( QWidget* parent ) if ( !parent->inherits( "QToolBar" ) ) return 0; - QWidget* dumb = new QWidget( parent ); - QVBoxLayout* l = new QVBoxLayout( dumb ); - l->setSpacing( 0 ); l->setMargin( 0 ); - QtxComboBox* cb = new QtxComboBox( dumb ); + QtxComboBox* cb = new QtxComboBox( parent ); cb->setSizeAdjustPolicy( QComboBox::AdjustToContents ); cb->setFocusPolicy( Qt::NoFocus ); - l->addWidget( cb ); - l->addSpacing( 3 ); - connect( cb, SIGNAL( activatedId( int ) ), this, SIGNAL( activatedId( int ) ) ); - - return dumb; + return cb; } /*! @@ -205,63 +201,71 @@ private: \brief An action, representing the list of modules to be inserted to the toolbar. - This action is represented in the toolbar as combo box and a set of buttons - for each module. In addition to the modules items, the combo box contains - an item corresponding to the "neutral point" of the application - (when there is no active module). - - The action can be constructed with up to two parameters, defining the text - and icon to be displayed for the "neutral point". + In the toolbar this action is represented as the combo box with the list of + available modules, and a set of buttons for each module. Additionally, combo box + contains an item representing "neutral point" of the application (i.e. no active module). + + 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 with setActiveModule() function. Use this method with empty string to turn to the "neutral point". To get active module, use activeModule() function. - When user activates/deactivates any module, the signal moduleActivated() + When user activates/deactivates a module, the moduleActivated() signal is emitted. - The action can be represented in the toolbar in different modes: - * as combo box only (Qtx::ComboItem) - * as set of modules buttons only (Qtx::Buttons) - * as combo box followed by the set of modules buttons (Qtx::All) - * as none (Qtx::None) - By default, both combo box and buttons set are shown. Use method - setMode() to change this behavior. + The action also provides an additional separate item "Add modules"; when + this button is pressed, a adding() signal is emitted. This signal + can be connected to a dedicated slot aimed to dynamically add a new module + into the application. In addition, a button "Remove module" shows a dropdown menu + with the list of user modules; when any item is selected, the removing() signal + is emitted. This signal may be connected to a slot aimed to dynamically remove + selected user module from application. - An action can be also added to the popup menu, but combo box is never shown - in this case, only modules buttons. + It is possible to customize which elements to show via the setMode() of setModeEnabled() + functions. By default, all elements are shown. The following choices are possible: + + - LightApp_ModuleAction::Buttons: show separate items for all modules + - LightApp_ModuleAction::List: show combo box with list of modules (in toolbar only) + - LightApp_ModuleAction::AddRemove: show "Add modules" and "Remove modules" items + - LightApp_ModuleAction::All: show all items */ /*! \brief Constructor - - Creates an module action with "neutral point" item described by \a text. - - \param text "neutral point" item's text + \param resMgr resource manager \param parent parent object */ -LightApp_ModuleAction::LightApp_ModuleAction( const QString& text, QObject* parent ) +LightApp_ModuleAction::LightApp_ModuleAction( QtxResourceMgr* resMgr, QObject* parent ) : QtxAction( parent ) { - setText( text ); - init(); -} + setText( tr( "APP_NAME" ) ); + setIcon( resMgr->loadPixmap( "LightApp", tr( "APP_DEFAULT_ICO" ), false ) ); + setVisible( false ); -/*! - \brief Constructor + myMode = All; + myCombo = new ComboAction( this ); + myAdd = new QtxAction( tr( "ADD_MODULE"), + resMgr->loadPixmap( "LightApp", tr( "ICON_ADD_MODULE" ), false ), + tr( "ADD_MODULE"), + 0, this ); + myRemove = new QtxAction( tr( "REMOVE_MODULE"), + resMgr->loadPixmap( "LightApp", tr( "ICON_REMOVE_MODULE" ), false ), + tr( "REMOVE_MODULE"), + 0, this ); + myRemove->setEnabled( false ); + myRemove->setMenu( new QMenu() ); + mySeparator = new QAction( this ); + mySeparator->setSeparator( true ); + mySet = new ActionSet( this ); - Creates an module action with "neutral point" item described by \a text and \a ico. + myMapper = new QSignalMapper( this ); - \param text "neutral point" item's text - \param ico "neutral point" item's icon - \param parent parent object -*/ -LightApp_ModuleAction::LightApp_ModuleAction( const QString& text, const QIcon& ico, QObject* parent ) -: QtxAction( parent ) -{ - setText( text ); - setIcon( ico ); - init(); + connect( this, SIGNAL( changed() ), this, SLOT( onChanged() ) ); + connect( myAdd, SIGNAL( triggered( bool ) ), this, SIGNAL( adding() ) ); + 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 ) ) ); } /*! @@ -341,9 +345,32 @@ QAction* LightApp_ModuleAction::moduleAction( const QString& name ) const */ void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico, const int idx ) +{ + insertModule( name, ico, false, idx ); +} + +/*! + \brief Add module into the list. + \param name module name + \param ico module icon + \param isCustom \c false to insert regular module, \c true to insert user module + \param idx position in the module list (if -1, the module is added to the end of list) + \sa removeModule() +*/ +void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico, + bool isCustom, const int idx) + { 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 ); + } mySet->insertAction( a, -1, idx ); update(); @@ -360,7 +387,23 @@ void LightApp_ModuleAction::removeModule( const QString& name ) if ( id == -1 ) return; + QAction* a = moduleAction( name ); + bool isCustom = a->data().toBool(); + mySet->removeAction( id ); + if ( isCustom ) + { + foreach ( QAction* ma, myRemove->menu()->actions() ) + { + if ( ma->text() == name ) + { + myRemove->menu()->removeAction( ma ); + break; + } + } + myRemove->setEnabled( !myRemove->menu()->actions().isEmpty() ); + } + update(); } @@ -399,30 +442,38 @@ void LightApp_ModuleAction::setActiveModule( const QString& name ) /*! \brief Set action display mode. - - Action can be represented in the toolbar as - * combo box only (Qtx::ComboItem) - * set of modules buttons only (Qtx::Buttons) - * combo box followed by the set of modules buttons (Qtx::All) - * none (Qtx::None) - - \param mode action display mode + \param mode action display options (combination of flags) \sa mode() */ -void LightApp_ModuleAction::setMode( const int mode ) +void LightApp_ModuleAction::setMode( const LightApp_ModuleAction::Mode& mode ) { myMode = mode; update(); } +/*! + \brief Enable / disable action display mode. + \param mode action display options (combination of flags) + \param enabled \c true to enable mode, \c false to disable mode + \sa mode() +*/ +void LightApp_ModuleAction::setModeEnabled( const LightApp_ModuleAction::Mode& mode, bool enabled ) +{ + if ( enabled ) + myMode |= mode; + else + myMode &= ~mode; + update(); +} + /*! \brief Get action display mode. \param mode action display mode \sa setMode() */ -int LightApp_ModuleAction::mode() const +bool LightApp_ModuleAction::isModeEnabled( const LightApp_ModuleAction::Mode& mode ) const { - return myMode; + return (bool)( myMode & mode ); } /*! @@ -433,6 +484,9 @@ void LightApp_ModuleAction::addedTo( QWidget* w ) { if ( w->inherits( "QToolBar" ) ) w->insertAction( this, myCombo ); + w->insertAction( this, myAdd ); + w->insertAction( this, myRemove ); + w->insertAction( this, mySeparator ); w->insertAction( this, mySet ); update(); } @@ -447,6 +501,9 @@ void LightApp_ModuleAction::removedFrom( QWidget* w ) { if ( w->inherits( "QToolBar" ) ) w->removeAction( myCombo ); + w->removeAction( myAdd ); + w->removeAction( myRemove ); + w->removeAction( mySeparator ); w->removeAction( mySet ); } @@ -470,23 +527,6 @@ bool LightApp_ModuleAction::event( QEvent* e ) \param name module name (empty string for neutral point) */ -/*! - \brief Initialize an action, - \internal -*/ -void LightApp_ModuleAction::init() -{ - setVisible( false ); - - myMode = All; - myCombo = new ComboAction( this ); - mySet = new ActionSet( this ); - - connect( this, SIGNAL( changed() ), this, SLOT( onChanged() ) ); - connect( mySet, SIGNAL( triggered( int ) ), this, SLOT( onTriggered( int ) ) ); - connect( myCombo, SIGNAL( activatedId( int ) ), this, SLOT( onComboActivated( int ) ) ); -} - /*! \brief Update an action. \internal @@ -497,7 +537,9 @@ void LightApp_ModuleAction::update() for ( QList::const_iterator it = lst.begin(); it != lst.end(); ++it ) update( *it ); - myCombo->setVisible( myMode & ComboItem ); + myCombo->setVisible( myMode & List ); + myAdd->setVisible( myMode & AddRemove ); + myRemove->setVisible( myMode & AddRemove ); mySet->setVisible( myMode & Buttons ); } diff --git a/src/LightApp/LightApp_ModuleAction.h b/src/LightApp/LightApp_ModuleAction.h index f669d681e..acc92746e 100644 --- a/src/LightApp/LightApp_ModuleAction.h +++ b/src/LightApp/LightApp_ModuleAction.h @@ -32,6 +32,8 @@ #endif class QtxComboBox; +class QtxResourceMgr; +class QSignalMapper; class LIGHTAPP_EXPORT LightApp_ModuleAction : public QtxAction { @@ -43,11 +45,14 @@ private: class ActivateEvent; public: - enum { None = 0x00, Buttons = 0x01, ComboItem = 0x02, All = Buttons | ComboItem }; + typedef enum { Buttons = 0x01, + List = 0x02, + AddRemove = 0x04, + All = Buttons | List | AddRemove + } Mode; public: - LightApp_ModuleAction( const QString&, QObject* = 0 ); - LightApp_ModuleAction( const QString&, const QIcon&, QObject* = 0 ); + LightApp_ModuleAction( QtxResourceMgr*, QObject* = 0 ); virtual ~LightApp_ModuleAction(); int count() const; @@ -59,13 +64,18 @@ public: QAction* moduleAction( const QString& ) const; void insertModule( const QString&, const QIcon&, const int = -1 ); + void insertModule( const QString&, const QIcon&, bool, const int = -1 ); void removeModule( const QString& ); QString activeModule() const; + + void setMode( const Mode& ); + void setModeEnabled( const Mode&, bool ); + bool isModeEnabled( const Mode& ) const; + +public slots: void setActiveModule( const QString& ); - void setMode( const int ); - int mode() const; protected: virtual void addedTo( QWidget* ); @@ -75,10 +85,10 @@ protected: signals: void moduleActivated( const QString& ); + void adding(); + void removing( const QString& ); private: - void init(); - void update(); void update( QtxComboBox* ); @@ -92,8 +102,12 @@ private slots: private: ComboAction* myCombo; + QtxAction* myAdd; + QtxAction* myRemove; ActionSet* mySet; + QAction* mySeparator; int myMode; + QSignalMapper* myMapper; }; class LightApp_ModuleAction::ComboAction : public QtxAction diff --git a/src/LightApp/LightApp_Preferences.cxx b/src/LightApp/LightApp_Preferences.cxx index ea93eeb8d..2e1d35246 100644 --- a/src/LightApp/LightApp_Preferences.cxx +++ b/src/LightApp/LightApp_Preferences.cxx @@ -61,6 +61,21 @@ int LightApp_Preferences::addPreference( const QString& mod, const QString& labe return id; } +/*! + Remove module preferences. +*/ +void LightApp_Preferences::removeModule( const QString& mod ) +{ + QMutableMapIterator it( myPrefMod ); + while ( it.hasNext() ) + { + it.next(); + if ( it.value() == mod ) + it.remove(); + } + removeItem( mod ); +} + /*! Checks: is preferences has module with name \a mod. */ diff --git a/src/LightApp/LightApp_Preferences.h b/src/LightApp/LightApp_Preferences.h index f589541c2..91fcad6dc 100644 --- a/src/LightApp/LightApp_Preferences.h +++ b/src/LightApp/LightApp_Preferences.h @@ -53,6 +53,7 @@ public: const QString& section = QString(), const QString& param = QString() ); bool hasModule( const QString& ) const; + void removeModule( const QString& ); void activateItem( const QString& ); void activateItem( const QStringList& ); diff --git a/src/LightApp/resources/LightApp_images.ts b/src/LightApp/resources/LightApp_images.ts index d8a16f562..29f083f39 100644 --- a/src/LightApp/resources/LightApp_images.ts +++ b/src/LightApp/resources/LightApp_images.ts @@ -15,10 +15,6 @@ ABOUT_SPLASH icon_about.png - - APP_MODULE_BIG_ICO - icon_module_big.png - APP_BASE_LOGO icon_applogo.png @@ -39,5 +35,13 @@ ICON_LIFE_RIGN icon_life_ring.png + + ICON_ADD_MODULE + icon_add_module.png + + + ICON_REMOVE_MODULE + icon_remove_module.png + diff --git a/src/LightApp/resources/LightApp_msg_en.ts b/src/LightApp/resources/LightApp_msg_en.ts index 1f810e27b..1a159cacf 100644 --- a/src/LightApp/resources/LightApp_msg_en.ts +++ b/src/LightApp/resources/LightApp_msg_en.ts @@ -1149,6 +1149,62 @@ File does not exist INFO_AVAILABLE_MODULES Available modules + + WRN_MODULE_BAD_SALOMEX_FILE + Cannot read module description file: +%1. + + + WRN_MODULE_EMPTY_NAME + Empty or invalid module name in: +%1. + + + WRN_MODULE_EMPTY_ROOT + Module root directory is not set or incorrectly specified in: +%1. + + + WRN_MODULE_ROOT_DOES_NOT_EXIST + Module root directory does not exist: +%1. + + + WRN_MODULE_DUPLICATED + Module "%1" is already present in this session + + + WRN_MODULE_BAD_RESDIR + Bad or non-existing resources directory: +%1. + + + WRN_MODULE_CANNOT_READ_CFG + Cannot read XML configuration file for the module from: +%1. + + + WRN_MODULE_BAD_CFG_FILE + XML configuration file for the module "%1" is bad or incomplete + + + TLT_REMOVE_MODULE + Remove module + + + QUE_REMOVE_MODULE_DIR + Do you also want to remove module directory: +%1? + +If you answer "Yes", you may need to save your study before removal. + + + WRN_CANNOT_REMOVE_DIR + Cannot remove directory: +%1. + +Continue? + LightApp_Module @@ -1269,6 +1325,14 @@ File does not exist ACTIVATE_MODULE_TOP Activate/deactivate %1 module + + ADD_MODULE + Add modules + + + REMOVE_MODULE + Remove modules + LightApp_PyEditor diff --git a/src/LightApp/resources/LightApp_msg_fr.ts b/src/LightApp/resources/LightApp_msg_fr.ts index 76415b674..29e2505d8 100644 --- a/src/LightApp/resources/LightApp_msg_fr.ts +++ b/src/LightApp/resources/LightApp_msg_fr.ts @@ -1149,6 +1149,62 @@ Le fichier n'existe pas INFO_AVAILABLE_MODULES Modules disponibles + + WRN_MODULE_BAD_SALOMEX_FILE + Cannot read module description file: +%1. + + + WRN_MODULE_EMPTY_NAME + Empty or invalid module name in: +%1. + + + WRN_MODULE_EMPTY_ROOT + Module root directory is not set or incorrectly specified in: +%1. + + + WRN_MODULE_ROOT_DOES_NOT_EXIST + Module root directory does not exist: +%1. + + + WRN_MODULE_DUPLICATED + Module "%1" is already present in this session + + + WRN_MODULE_BAD_RESDIR + Bad or non-existing resources directory: +%1. + + + WRN_MODULE_CANNOT_READ_CFG + Cannot read XML configuration file for the module from: +%1. + + + WRN_MODULE_BAD_CFG_FILE + XML configuration file for the module "%1" is bad or incomplete + + + TLT_REMOVE_MODULE + Remove module + + + QUE_REMOVE_MODULE_DIR + Do you also want to remove module directory: +%1? + +If you answer "Yes", you may need to save your study before removal. + + + WRN_CANNOT_REMOVE_DIR + Cannot remove directory: +%1. + +Continue? + LightApp_Module @@ -1269,6 +1325,14 @@ Le fichier n'existe pas ACTIVATE_MODULE_TOP Activer/désactiver le module %1 + + ADD_MODULE + Ajouter les modules + + + REMOVE_MODULE + Supprimer les modules + LightApp_PyEditor diff --git a/src/LightApp/resources/LightApp_msg_ja.ts b/src/LightApp/resources/LightApp_msg_ja.ts index e23d9b4b5..50e43b8f3 100644 --- a/src/LightApp/resources/LightApp_msg_ja.ts +++ b/src/LightApp/resources/LightApp_msg_ja.ts @@ -1147,6 +1147,62 @@ Pythonファイルは、文字、数字、アンダースコアが含まれて INFO_AVAILABLE_MODULES Available modules + + WRN_MODULE_BAD_SALOMEX_FILE + Cannot read module description file: +%1. + + + WRN_MODULE_EMPTY_NAME + Empty or invalid module name in: +%1. + + + WRN_MODULE_EMPTY_ROOT + Module root directory is not set or incorrectly specified in: +%1. + + + WRN_MODULE_ROOT_DOES_NOT_EXIST + Module root directory does not exist: +%1. + + + WRN_MODULE_DUPLICATED + Module "%1" is already present in this session + + + WRN_MODULE_BAD_RESDIR + Bad or non-existing resources directory: +%1. + + + WRN_MODULE_CANNOT_READ_CFG + Cannot read XML configuration file for the module from: +%1. + + + WRN_MODULE_BAD_CFG_FILE + XML configuration file for the module "%1" is bad or incomplete + + + TLT_REMOVE_MODULE + Remove module + + + QUE_REMOVE_MODULE_DIR + Do you also want to remove module directory: +%1? + +If you answer "Yes", you may need to save your study before removal. + + + WRN_CANNOT_REMOVE_DIR + Cannot remove directory: +%1. + +Continue? + LightApp_Module @@ -1266,6 +1322,14 @@ Pythonファイルは、文字、数字、アンダースコアが含まれて ACTIVATE_MODULE_TOP モジュール %1 の有効/無効にします。 + + ADD_MODULE + Add modules + + + REMOVE_MODULE + Remove modules + LightApp_PyEditor diff --git a/src/LightApp/resources/icon_add_module.png b/src/LightApp/resources/icon_add_module.png new file mode 100644 index 000000000..ac8bb01ef Binary files /dev/null and b/src/LightApp/resources/icon_add_module.png differ diff --git a/src/LightApp/resources/icon_module.png b/src/LightApp/resources/icon_module.png index 298e581b0..c303e3bc3 100644 Binary files a/src/LightApp/resources/icon_module.png and b/src/LightApp/resources/icon_module.png differ diff --git a/src/LightApp/resources/icon_module_big.png b/src/LightApp/resources/icon_module_big.png deleted file mode 100644 index c303e3bc3..000000000 Binary files a/src/LightApp/resources/icon_module_big.png and /dev/null differ diff --git a/src/LightApp/resources/icon_remove_module.png b/src/LightApp/resources/icon_remove_module.png new file mode 100644 index 000000000..037c8265f Binary files /dev/null and b/src/LightApp/resources/icon_remove_module.png differ diff --git a/src/Qtx/Qtx.cxx b/src/Qtx/Qtx.cxx index f050d0328..919adfaf3 100644 --- a/src/Qtx/Qtx.cxx +++ b/src/Qtx/Qtx.cxx @@ -439,22 +439,52 @@ QString Qtx::extension( const QString& path, const bool full ) return full ? QFileInfo( path ).completeSuffix() : QFileInfo( path ).suffix(); } +/*! + \brief EXtract base library name. + + The function removes platform-specific prefix (lib) and suffix (.dll/.so) + from the library file name. + For example, if \a str = "libmylib.so", "mylib" is returned.. + + \param libName library name + \return base library name +*/ +QString Qtx::libraryName( const QString& libName ) +{ + QString fullName = file( libName ); +#if defined(WIN32) + QString libExt = QString( "dll" ); +#elif defined(__APPLE__) + QString libExt = QString( "dylib" ); +#else + QString libExt = QString( "so" ); +#endif + if ( extension( fullName ).toLower() == libExt ) + fullName.truncate( fullName.length() - libExt.length() - 1 ); +#ifndef WIN32 + QString prefix = QString( "lib" ); + if ( fullName.startsWith( prefix ) ) + fullName.remove( 0, prefix.length() ); +#endif + return fullName; +} + /*! \brief Convert the given parameter to the platform-specific library name. The function appends platform-specific prefix (lib) and suffix (.dll/.so) to the library file name. - For example, if \a str = "mylib", "libmylib.so" is returned for Linux and + For example, if \a libName = "mylib", "libmylib.so" is returned for Linux and mylib.dll for Windows. - \param str short library name + \param libName short library name \return full library name */ -QString Qtx::library( const QString& str ) +QString Qtx::library( const QString& libName ) { - QString path = dir( str, false ); - QString name = file( str, false ); - QString ext = extension( str ); + QString path = dir( libName, false ); + QString name = file( libName, false ); + QString ext = extension( libName ); #ifndef WIN32 if ( !name.startsWith( "lib" ) ) @@ -556,6 +586,17 @@ QString Qtx::addSlash( const QString& path ) return res; } +/*! + \brief Return full path obtained by joining given components with + the native directory separator. + \param path Separate path components + \return complete path +*/ +QString Qtx::joinPath( const QStringList& path ) +{ + return path.join( QDir::separator() ); +} + /*! \brief Convert text file from DOS format to UNIX. diff --git a/src/Qtx/Qtx.h b/src/Qtx/Qtx.h index 0c28e3830..c602f574f 100644 --- a/src/Qtx/Qtx.h +++ b/src/Qtx/Qtx.h @@ -234,6 +234,7 @@ public: static QString file( const QString&, const bool = true ); static QString extension( const QString&, const bool = false ); + static QString libraryName( const QString& ); static QString library( const QString& ); static QString tmpDir(); @@ -241,6 +242,7 @@ public: static bool rmDir( const QString& ); static bool dos2unix( const QString& ); static QString addSlash( const QString& ); + static QString joinPath( const QStringList& ); static QCompleter* pathCompleter( const PathType, const QString& = QString() ); static QString findEnvVar( const QString&, int&, int& ); diff --git a/src/Qtx/QtxResourceMgr.cxx b/src/Qtx/QtxResourceMgr.cxx index ae35b5007..fdf1c7c82 100644 --- a/src/Qtx/QtxResourceMgr.cxx +++ b/src/Qtx/QtxResourceMgr.cxx @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -536,7 +538,7 @@ bool QtxResourceMgr::IniFormat::load( const QString& fname, QMap impMap; if ( !load( impFInfo.absoluteFilePath(), impMap, importHistory ) ) { @@ -1032,6 +1034,222 @@ QString QtxResourceMgr::XmlFormat::valueAttribute() const return str; } +/*! + \class QtxResourceMgr::JsonFormat + \internal + \brief Reader/writer for .json resources files. +*/ + +class QtxResourceMgr::JsonFormat : public Format +{ +public: + JsonFormat(); + ~JsonFormat(); + +protected: + JsonFormat( const QString& ); + virtual bool load( const QString&, QMap& ); + virtual bool save( const QString&, const QMap& ); + +private: + bool load( const QString&, QMap&, QSet& ); +}; + +/*! + \brief Constructor. +*/ +QtxResourceMgr::JsonFormat::JsonFormat() +: QtxResourceMgr::JsonFormat( "json" ) +{ +} + +/*! + \brief Constructor. +*/ +QtxResourceMgr::JsonFormat::JsonFormat( const QString& fmt ) +: Format( fmt ) +{ +} + +/*! + \brief Destructor. +*/ +QtxResourceMgr::JsonFormat::~JsonFormat() +{ +} + +/*! + \brief Load resources from json-file. + \param fname resources file name + \param secMap resources map to be filled in + \return \c true on success and \c false on error +*/ +bool QtxResourceMgr::JsonFormat::load( const QString& fname, QMap& secMap ) +{ + QSet importHistory; + return load( fname, secMap, importHistory ); +} + +/*! + \brief Load resources from json-file. + \param fname resources file name + \param secMap resources map to be filled in + \param importHistory list of already imported resources files (to prevent import loops) + \return \c true on success or \c false on error +*/ +bool QtxResourceMgr::JsonFormat::load( const QString& fname, QMap& secMap, QSet& importHistory ) +{ + QString aFName = fname.trimmed(); + if ( !QFileInfo( aFName ).exists() ) + { + if ( QFileInfo( aFName + ".json" ).exists() ) + aFName += ".json"; + else if ( QFileInfo( aFName + ".JSON" ).exists() ) + aFName += ".JSON"; + else + return false; // file does not exist + } + QFileInfo aFinfo( aFName ); + aFName = aFinfo.canonicalFilePath(); + + if ( !importHistory.contains( aFName ) ) + importHistory.insert( aFName ); + else + return true; // already imported (prevent import loops) + + QFile file( aFName ); + if ( !file.open( QFile::ReadOnly ) ) + return false; // file is not accessible + + QJsonDocument document = QJsonDocument::fromJson( file.readAll() ); + if ( document.isNull() ) + return false; // invalid json file + + QJsonObject root = document.object(); + foreach ( QString sectionName, root.keys() ) + { + if ( sectionName == "import" ) + { + QString impFile = root.value( sectionName ).toString(); + if ( impFile.isEmpty() ) + continue; + QString impPath = QDir::toNativeSeparators( Qtx::makeEnvVarSubst( impFile, Qtx::Always ) ); + QFileInfo impFInfo( impPath ); + if ( impFInfo.isRelative() ) + impFInfo.setFile( aFinfo.absoluteDir(), impPath ); + QMap impMap; + if ( !load( impFInfo.absoluteFilePath(), impMap, importHistory ) ) + { + qDebug() << "QtxResourceMgr: Error with importing file:" << impPath; + } + else + { + QMap::const_iterator it = impMap.constBegin(); + for ( ; it != impMap.constEnd() ; ++it ) + { + if ( !secMap.contains( it.key() ) ) + { + // insert full section + secMap.insert( it.key(), it.value() ); + } + else + { + // insert all parameters from the section + Section::ConstIterator paramIt = it.value().begin(); + for ( ; paramIt != it.value().end() ; ++paramIt ) + { + if ( !secMap[it.key()].contains( paramIt.key() ) ) + secMap[it.key()].insert( paramIt.key(), paramIt.value() ); + } + } + } + } + } + else + { + QJsonObject section = root.value( sectionName ).toObject(); + if ( !section.isEmpty() ) + { + // case when a top-level item is a section + foreach ( QString parameterName, section.keys() ) + { + // each value must be a string, number, or boolean + QJsonValue parameter = section.value( parameterName ); + if ( parameter.isDouble() ) + secMap[sectionName].insert( parameterName, QString::number( parameter.toDouble() ) ); + else if ( parameter.isBool() ) + secMap[sectionName].insert( parameterName, QString( parameter.toBool() ? "true" : "false" ) ); + else if ( parameter.isString() ) + secMap[sectionName].insert( parameterName, parameter.toString() ); + } + } + else + { + QString parameterName = sectionName; + sectionName = "General"; // default section name for top-level items + // each value must be a string, number, or boolean + QJsonValue parameter = root.value( parameterName ); + if ( parameter.isDouble() ) + secMap[sectionName].insert( parameterName, QString::number( parameter.toDouble() ) ); + else if ( parameter.isBool() ) + secMap[sectionName].insert( parameterName, QString( parameter.toBool() ? "true" : "false" ) ); + else if ( parameter.isString() ) + secMap[sectionName].insert( parameterName, parameter.toString() ); + } + } + } + + if ( !secMap.isEmpty() ) + qDebug() << "QtxResourceMgr: File" << fname << "is loaded successfully"; + return true; +} + +/*! + \brief Save resources to the json-file. + \param fname resources file name + \param secMap resources map + \return \c true on success and \c false on error +*/ +bool QtxResourceMgr::JsonFormat::save( const QString& fname, const QMap& secMap ) +{ + if ( !Qtx::mkDir( QFileInfo( fname ).absolutePath() ) ) + return false; + + QFile file( fname ); + if ( !file.open( QFile::WriteOnly ) ) + return false; + + QJsonObject root; + for ( QMap::ConstIterator it = secMap.begin(); it != secMap.end(); ++it ) + { + // note: we write all values as string, as it's enough to store resources as strings + // anyway resource manager converts values to strings when reading JSON file + QJsonObject section; + for ( Section::ConstIterator iter = it.value().begin(); iter != it.value().end(); ++iter ) + section.insert( iter.key(), iter.value() ); + root.insert( it.key(), section ); + } + + QJsonDocument document; + document.setObject( root ); + file.write( document.toJson() ); + file.close(); + return true; +} + +/*! + \class QtxResourceMgr::SalomexFormat + \internal + \brief Reader/writer for .salomex resources files. This is an alias for JSON format. +*/ + +class QtxResourceMgr::SalomexFormat : public JsonFormat +{ +public: + SalomexFormat() : JsonFormat( "salomex" ) {} +}; + + /*! \class QtxResourceMgr::Format \brief Generic resources files reader/writer class. @@ -1127,9 +1345,13 @@ bool QtxResourceMgr::Format::save( Resources* res ) if ( !res ) return false; + QtxResourceMgr* mgr = res->resMgr(); + + if ( mgr->appName().isEmpty() ) + return false; + Qtx::mkDir( Qtx::dir( res->myFileName ) ); - QtxResourceMgr* mgr = res->resMgr(); QString name = mgr ? mgr->userFileName( mgr->appName(), false ) : res->myFileName; return save( name, res->mySections ); } @@ -1168,7 +1390,7 @@ bool QtxResourceMgr::Format::save( Resources* res ) (internationalization mechanism), load pixmaps and other resources from external files, etc. - Currently it supports .ini and .xml resources file formats. To implement + Currently it supports .ini, .xml, and .json resources file formats. To implement own resources file format, inherit from the Format class and implement virtual Format::load() and Format::save() methods. @@ -1264,6 +1486,26 @@ QtxResourceMgr::QtxResourceMgr( const QString& appName, const QString& resVarTem installFormat( new XmlFormat() ); installFormat( new IniFormat() ); + installFormat( new JsonFormat() ); + installFormat( new SalomexFormat() ); + + setOption( "translators", QString( "%P_msg_%L.qm|%P_images.qm" ) ); +} + +/*! + \brief Default constructor +*/ +QtxResourceMgr::QtxResourceMgr() +: myCheckExist( true ), + myDefaultPix( 0 ), + myIsPixmapCached( true ), + myHasUserValues( false ), + myWorkingMode( IgnoreUserValues ) +{ + installFormat( new XmlFormat() ); + installFormat( new IniFormat() ); + installFormat( new JsonFormat() ); + installFormat( new SalomexFormat() ); setOption( "translators", QString( "%P_msg_%L.qm|%P_images.qm" ) ); } @@ -1339,7 +1581,7 @@ QStringList QtxResourceMgr::dirList() const */ void QtxResourceMgr::initialize( const bool autoLoad ) const { - if ( !myResources.isEmpty() ) + if ( !myResources.isEmpty() || appName().isEmpty() ) return; QtxResourceMgr* that = (QtxResourceMgr*)this; @@ -2189,7 +2431,7 @@ void QtxResourceMgr::setCurrentFormat( const QString& fmt ) myFormats.removeAll( form ); myFormats.prepend( form ); - if ( myResources.isEmpty() ) + if ( myResources.isEmpty() || appName().isEmpty() ) return; ResList::Iterator resIt = myResources.begin(); @@ -2382,6 +2624,42 @@ bool QtxResourceMgr::save() return result; } +/*! + \brief Load resource from given file. +*/ +bool QtxResourceMgr::addResource( const QString& fname ) +{ + if ( fname.isEmpty() ) + return false; + + QFileInfo fi( fname ); + + if ( fi.exists() && fi.isDir() && !appName().isEmpty() ) + fi.setFile( QDir( fname ).filePath( globalFileName( appName() ) ) ); + + if ( !fi.exists() ) + return false; + + QString dirName = fi.absolutePath(); + if ( myDirList.contains( dirName ) ) + return true; // file already loaded + + Format* fmt = format( fi.suffix() ); + if ( !fmt ) + return false; + + Resources* resource = new Resources( this, fi.absoluteFilePath() ); + if ( !fmt->load( resource ) ) + { + delete resource; + return false; + } + + myDirList << dirName; + myResources << resource; + return true; +} + /*! \brief Get all sections names. \return list of section names @@ -2744,7 +3022,8 @@ void QtxResourceMgr::loadLanguage( const QString& pref, const QString& preferabl initialize( true ); QMap substMap; - substMap.insert( 'A', appName() ); + if ( !appName().isEmpty() ) + substMap.insert( 'A', appName() ); QString lang = language( preferableLanguage ); diff --git a/src/Qtx/QtxResourceMgr.h b/src/Qtx/QtxResourceMgr.h index 9c9ced174..3694ae9d6 100644 --- a/src/Qtx/QtxResourceMgr.h +++ b/src/Qtx/QtxResourceMgr.h @@ -53,6 +53,8 @@ class QTX_EXPORT QtxResourceMgr { class IniFormat; class XmlFormat; + class JsonFormat; + class SalomexFormat; class Resources; public: @@ -71,6 +73,7 @@ public: } WorkingMode; public: + QtxResourceMgr(); QtxResourceMgr( const QString&, const QString& = QString() ); virtual ~QtxResourceMgr(); @@ -168,6 +171,7 @@ public: bool load(); bool import( const QString& ); bool save(); + bool addResource( const QString& ); QStringList sections() const; QStringList sections(const QRegExp&) const; diff --git a/src/STD/STD_Application.cxx b/src/STD/STD_Application.cxx index 1fa73dfe1..27a0c1722 100644 --- a/src/STD/STD_Application.cxx +++ b/src/STD/STD_Application.cxx @@ -88,6 +88,7 @@ QString STD_Application::applicationName() const void STD_Application::start() { createActions(); + customize(); updateDesktopTitle(); updateCommandsStatus(); @@ -270,6 +271,13 @@ void STD_Application::createActions() createTool( EditPasteId, stdTBar ); } +/*! + Customize actions. +*/ +void STD_Application::customize() +{ +} + /*!Opens new application*/ void STD_Application::onNewDoc() { @@ -561,13 +569,13 @@ bool STD_Application::openAction( const int choice, const QString& aName ) } /*!Save document if all ok, else error message.*/ -void STD_Application::onSaveDoc() +bool STD_Application::onSaveDoc() { if ( !activeStudy() ) - return; + return false; if ( !abortAllOperations() ) - return; + return false; bool isOk = false; if ( activeStudy()->isSaved() ) @@ -594,7 +602,8 @@ void STD_Application::onSaveDoc() if ( isOk ) studySaved( activeStudy() ); else - onSaveAsDoc(); + isOk = onSaveAsDoc(); + return isOk; } /*! \retval \c true, if document saved successfully, else \c false.*/ diff --git a/src/STD/STD_Application.h b/src/STD/STD_Application.h index 25273d5f1..4a3631c1d 100644 --- a/src/STD/STD_Application.h +++ b/src/STD/STD_Application.h @@ -125,7 +125,7 @@ public slots: virtual bool onNewDoc( const QString& ); virtual void onCloseDoc( bool ask = true ); - virtual void onSaveDoc(); + virtual bool onSaveDoc(); virtual bool onSaveAsDoc(); virtual void onOpenDoc(); @@ -154,6 +154,7 @@ protected: protected: virtual void createActions(); + virtual void customize(); virtual void updateCommandsStatus(); virtual void setDesktop( SUIT_Desktop* ); diff --git a/src/SUIT/SUIT_PreferenceMgr.cxx b/src/SUIT/SUIT_PreferenceMgr.cxx index b19b0797d..4b2c482a6 100644 --- a/src/SUIT/SUIT_PreferenceMgr.cxx +++ b/src/SUIT/SUIT_PreferenceMgr.cxx @@ -174,6 +174,18 @@ int SUIT_PreferenceMgr::addItem( const QString& title, const int pId, return item ? item->id() : -1; } +void SUIT_PreferenceMgr::removeItem( const QString& title ) +{ + if ( myRoot ) + { + QtxPreferenceItem* item = myRoot->findItem( title, false ); + if ( item ) { + QtxPagePrefMgr::removeItem( item ); + delete item; + } + } +} + QVariant SUIT_PreferenceMgr::optionValue( const QString& name ) const { QVariant val = QtxPagePrefMgr::optionValue( name ); diff --git a/src/SUIT/SUIT_PreferenceMgr.h b/src/SUIT/SUIT_PreferenceMgr.h index b78490e8d..8e150a2b4 100644 --- a/src/SUIT/SUIT_PreferenceMgr.h +++ b/src/SUIT/SUIT_PreferenceMgr.h @@ -53,6 +53,7 @@ public: int addItem( const QString&, const int pId = -1, const PrefItemType = Auto, const QString& = QString(), const QString& = QString() ); + void removeItem( const QString& ); protected: virtual QVariant optionValue( const QString& ) const; diff --git a/src/SalomeApp/SalomeApp_Application.cxx b/src/SalomeApp/SalomeApp_Application.cxx index 3e60d3282..1e9492add 100644 --- a/src/SalomeApp/SalomeApp_Application.cxx +++ b/src/SalomeApp/SalomeApp_Application.cxx @@ -110,6 +110,9 @@ #include +#include +#include CORBA_CLIENT_HEADER(SALOME_ModuleCatalog) + std::unique_ptr SalomeApp_Application::_ns; /*!Internal class that updates object browser item properties */ @@ -2156,3 +2159,19 @@ void SalomeApp_Application::ensureShaperIsActivated() onDesktopMessage("register_module_in_study/Shaper"); } } + +void SalomeApp_Application::addCatalogue( const QString& moduleName, const QString& catalogue ) +{ + CORBA::Object_var obj = namingService()->Resolve( "/Kernel/ModulCatalog" ); + SALOME_ModuleCatalog::ModuleCatalog_var moduleCatalogue = SALOME_ModuleCatalog::ModuleCatalog::_narrow( obj ); + QFileInfo fi( catalogue ); + if ( !CORBA::is_nil( moduleCatalogue ) && fi.isFile() ) + { + SALOME_ModuleCatalog::ListOfComponents_var known = moduleCatalogue->GetComponentList(); + bool loaded = false; + for ( int i = 0; i < (int)known->length() && !loaded; i++ ) + loaded = QString( known[i].in() ) == moduleName; + if ( !loaded ) + moduleCatalogue->ImportXmlCatalogFile( catalogue.toUtf8().constData() ); + } +} diff --git a/src/SalomeApp/SalomeApp_Application.h b/src/SalomeApp/SalomeApp_Application.h index c0e84fc37..f96590866 100644 --- a/src/SalomeApp/SalomeApp_Application.h +++ b/src/SalomeApp/SalomeApp_Application.h @@ -178,6 +178,8 @@ protected: virtual bool canOpenDoc( const QString& ); virtual void afterCloseDoc(); + virtual void addCatalogue( const QString&, const QString& ); + private slots: void onDeleteInvalidReferences(); void onDblClick( SUIT_DataObject* );