+ activateModule( modTitle );
+}
+
+/*!On extension adding action.*/
+void LightApp_Application::onExtAdding()
+{
+ // Show dialog to browse a salome extension file
+ QStringList filters = (QStringList() << tr("Salome extension files") + " (*.salomex)" << tr("All files") + " (*)");
+ QStringList paths = getOpenFileNames(QString(), filters.join(";;"), QString(), desktop());
+ if (paths.isEmpty())
+ {
+ MESSAGE("Adding an extension was cancelled.");
+ return;
+ }
+
+ LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+ if (!moduleAction)
+ {
+ MESSAGE("Couldn't get a moduleAction! Return.");
+ return;
+ }
+
+ // It should be set on the app start
+ auto extRootDir = getenv(salomeAppDir);
+ if (!extRootDir)
+ {
+ SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_SALOME_APPLICATION_DIR"));
+ return;
+ }
+ SCRUTE(extRootDir);
+
+ // We'll load all the extensions modules from this path
+ auto SalomeExtDir = QDir::cleanPath(QString(extRootDir) + QDir::separator() + "__SALOME_EXT__");
+ SCRUTE(SalomeExtDir.toStdString());
+
+ // Import Python module that manages SALOME extensions.
+ // It seems to be faster to lock and unlock once than on each iteration,
+ // but I didn't compare the performance for each case.
+ PyLockWrapper lck; // acquire GIL
+ PyObjWrapper extensionUnpacker = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_unpacker");
+ PyObjWrapper runSalomeOnDemand = PyImport_ImportModule((char*)"runSalomeOnDemand");
+
+ // Loop via selected extensions files
+ foreach(QString path, paths)
+ {
+ std::string extPath = path.toStdString();
+ SCRUTE(extPath);
+
+ PyObjWrapper unpackedModules = PyObject_CallMethod(
+ extensionUnpacker, (char*)"install_salomex", (char*)"s", extPath.c_str());
+ if (!unpackedModules || unpackedModules == Py_None)
+ {
+ SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_UNPACK_EXTENSION").arg(path) );
+ continue;
+ }
+
+ PyObjWrapper pKeys = PyDict_Keys(unpackedModules);
+ // Iterate all the components (modules) for this extension
+ for (Py_ssize_t pos = 0; pos < PyDict_Size(unpackedModules); ++pos)
+ {
+ auto moduleNameItem = PyList_GetItem(pKeys, pos);
+ auto interactiveItem = PyDict_GetItem(unpackedModules, moduleNameItem);
+
+ QString moduleName(PyUnicode_AsUTF8(moduleNameItem));
+ SCRUTE(moduleName.toStdString());
+ addUserModule(moduleName, SalomeExtDir, PyObject_IsTrue(interactiveItem));
+ }
+
+ // Add an extension to GUI
+ QFileInfo extFileInfo(path);
+ QString extName = extFileInfo.baseName();
+ if (moduleAction)
+ {
+ moduleAction->insertExtension(extName);
+ }
+
+ // Update environment of salome
+ PyObjWrapper update_env = PyObject_CallMethod(
+ runSalomeOnDemand, (char*)"set_selext_env", (char*)"ss", extRootDir, extName.toStdString().c_str());
+ if (!update_env)
+ {
+ SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_UPDATE_ENV").arg(extName + "_env.py") );
+ continue;
+ }
+ }
+
+ // Udate actions only once after all of them were already inserted
+ moduleAction->updateExtActions();
+}
+
+/*Add user module.*/
+bool LightApp_Application::addUserModule( const QString& name, const QString& root, bool interactive )
+{
+ if ( name == "KERNEL" || name == "GUI" )
+ return false; // skip KERNEL and GUI modules
+
+ if ( name.isEmpty() || root.isEmpty() )
+ return false;
+
+ if ( !moduleTitle( name ).isEmpty() ) // module alread in current session
+ {
+ if ( interactive )
+ SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_DUPLICATED" ).arg( name ) );
+ return false;
+ }
+ if ( !QFileInfo( root ).exists() ) // root directory does not exist
+ {
+ if ( interactive )
+ SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_ROOT_DOES_NOT_EXIST" ).arg( root ) );
+ return false;
+ }
+ // resources directory
+ QString resDir = Qtx::joinPath( QStringList() << root << "share" << "salome" << "resources" << name.toLower() );
+ if ( !QFileInfo( resDir ).exists() ) // resources directory does not exist
+ {
+ if ( interactive )
+ SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_RESDIR" ).arg( resDir ) );
+ return false;
+ }
+
+ SUIT_ResourceMgr* resMgr = resourceMgr();
+
+ // read XML configuration file
+ resMgr->setConstant(QString("%1_ROOT_DIR").arg(name), root);
+ if (!resMgr->addResource(resDir)) // cannot read configuration
+ {
+ if ( interactive )
+ SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_CANNOT_READ_CFG" ).arg( resDir ) );
+ return false;
+ }
+ // fill in information about module
+ if ( !appendModuleInfo( name ) ) // cannot append module information to internal table
+ {
+ if ( interactive )
+ SUIT_MessageBox::warning( desktop(), tr( "WRN_WARNING" ), tr( "WRN_MODULE_BAD_CFG_FILE" ).arg( name ) );
+ return false;
+ }
+
+ // load translations
+ resMgr->loadLanguage(name);
+
+ // Do all the GUI related stuff only if the module supports that.
+ // We already did check for GUI inside CAM_Application::appendModuleInfo, but
+ // need to do that again.
+ // TODO: Maybe it's better to return ModuleInfo from appendModuleInfo() and check status.
+ const QString title = resMgr->stringValue(name, "name", QString()).trimmed();
+ if (resMgr->booleanValue(name, "gui", false) || !title.isEmpty() && interactive)
+ {
+ // Append module to the menu / toolbar
+ LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+ if (moduleAction)
+ {
+ // Scale icon to 20x20 pix
+ moduleAction->insertModule(moduleTitle(name), moduleIcon(moduleTitle(name), 20), true);
+ }
+ }
+
+ // add empty page to Preferences dialog
+ LightApp_Preferences* prefs = preferences();
+ if ( prefs && !prefs->hasModule( moduleTitle( name ) ) )
+ {
+ int prefId = prefs->addPreference( moduleTitle( name ) );
+ prefs->setItemIcon( prefId, moduleIcon( moduleTitle( name ), 20 ) ); // scale icon to 20x20 pix
+ LightApp_Module* m = qobject_cast<LightApp_Module*>( module( moduleTitle( name ) ) );
+ if ( m )
+ {
+ m->createPreferences();
+ emptyPreferences( moduleTitle( name ) );
+ }
+ }
+ // add Help items
+ createHelpItems( moduleTitle( name ) );
+ // extend module catalog
+ QString catalogue = QDir( resDir ).filePath( QString( "%1Catalog.xml" ).arg( name ) );
+ addCatalogue( name, catalogue );
+ // update windows (in particular, Info panel)
+ updateWindows();
+ // save module in the resource manager
+ if ( interactive )
+ {
+ QStringList customModules = resMgr->stringValue("launch", "user_modules").split(";", QString::SkipEmptyParts);
+ customModules << name;
+ customModules.removeDuplicates();
+ resMgr->setValue( "launch", "user_modules", customModules.join( ";" ) );
+ resMgr->setValue( "user_modules", name, root );
+ }
+ return true;
+}
+
+/*!Remove user module from UI.*/
+void LightApp_Application::removeUserModule(const QString& moduleInnerName, LightApp_ModuleAction* moduleAction)
+{
+ MESSAGE("Remove a module from UI...");
+ SCRUTE(moduleInnerName.toStdString());
+
+ // There is a some confusion point, because now we have a module's 'inner' name
+ // from the extension's salomexd file.
+ // But, in the next GUI methods we need to use a module title (user name).
+ // For example, PYHELLO (inner name) and PyHello (user name to display in GUI).
+ // Then, from the inner module's name we need to get a user one.
+ QString moduleUserName = moduleTitle(moduleInnerName);
+ SCRUTE(moduleUserName.toStdString());
+
+ // Set current state in modules combo box
+ // Don't confuse again, because activeModule()->moduleName() returns a module title, not an inner one!
+ if (activeModule() && activeModule()->moduleName() == moduleUserName)
+ activateModule("");
+
+ // Remove from "Modules" menu and toolbar
+ if (moduleAction)
+ {
+ moduleAction->removeModule(moduleUserName);
+ }
+
+ // Remove Help menu items
+ removeHelpItems(moduleUserName);
+
+ // Remove Preferences
+ LightApp_Preferences* prefs = preferences();
+ if (prefs)
+ prefs->removeModule(moduleUserName);
+
+ // Remove settings
+ // Here we use an inner module name!
+ QStringList customModules = resourceMgr()->stringValue("launch", "user_modules").split(";", QString::SkipEmptyParts);
+ customModules.removeAll(moduleInnerName);
+ resourceMgr()->setValue("launch", "user_modules", customModules.join(";"));
+ removeModuleInfo(moduleInnerName);
+}
+
+/*!On module removing action.*/
+void LightApp_Application::onExtRemoving(const QString& title)
+{
+ MESSAGE("Remove an extension...");
+ std::string extName = title.toStdString();
+ SCRUTE(extName);
+
+ // Ask user if he's ready to completely remove an extension and all its modules.
+ int answer = SUIT_MessageBox::question(
+ desktop(),
+ tr("TLT_REMOVE_EXTENSION"),
+ tr("QUE_REMOVE_EXTENSION").arg(title),
+ SUIT_MessageBox::Ok | SUIT_MessageBox::Cancel,
+ SUIT_MessageBox::Ok
+ );
+
+ if (answer == SUIT_MessageBox::Cancel)
+ {
+ MESSAGE("Removing of an extension was cancelled");
+ return; // cancelled
+ }
+
+ if (activeStudy() && activeStudy()->isModified() && !onSaveDoc())
+ {
+ // doc is not saved, or saving cancelled
+ SUIT_MessageBox::warning(
+ desktop(),
+ tr("WRN_WARNING"), tr("WRN_CANCEL_REMOVE_EXTENSION_UNSAVE").arg(title)
+ );
+
+ return;
+ }
+
+ // It should be set on the app start
+ auto extRootDir = getenv(salomeAppDir);
+ if (!extRootDir)
+ {
+ SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_SALOME_APPLICATION_DIR"));
+ return;
+ }
+ SCRUTE(extRootDir);
+
+ // Import Python module that manages SALOME extensions.
+ PyLockWrapper lck; // acquire GIL
+ PyObjWrapper extensionRemover = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_remover");
+ PyObjWrapper removedModules = PyObject_CallMethod(
+ extensionRemover, (char*)"remove_salomex", (char*)"ss", extRootDir, extName.c_str());
+ if (!removedModules || removedModules == Py_None)
+ {
+ SUIT_MessageBox::warning(desktop(), tr("WRN_WARNING"), tr("WRN_FAILED_REMOVE_EXTENSION").arg(title));
+ return;
+ }
+
+ // We need it to remove ext and modules from UI
+ LightApp_ModuleAction* moduleAction = qobject_cast<LightApp_ModuleAction*>(action(ModulesListId));
+ if (!moduleAction)
+ {
+ MESSAGE("Cannot get a pointer to LightApp_ModuleAction! Removing from menue and toolbars will skipped.");
+ }
+
+ // Module's content was already removed on python remove_salomex call,
+ // then all we do next - just remove UI items.
+ for (Py_ssize_t pos = 0; pos < PyList_Size(removedModules); ++pos)
+ {
+ // Get the current module's name
+ auto moduleNameItem = PyList_GetItem(removedModules, pos);
+ const QString moduleInnerName(PyUnicode_AsUTF8(moduleNameItem));
+
+ removeUserModule(moduleInnerName, moduleAction);
+ }
+
+ // Remove an ext from UI
+ if (moduleAction)
+ {
+ moduleAction->removeExtension(title);
+ }
+
+ // Update windows (in particular, Info panel)
+ updateWindows();
+}
+
+/*!On show extension info action.*/
+void LightApp_Application::onShowExtInfo()
+{
+ // Show dialog with information about loaded salome extensions
+ LightApp_ExtInfoDlg dlg(desktop());
+ dlg.exec();