Salome HOME
CAM_Application: Fix log message (no translation needed).
[modules/gui.git] / src / CAM / CAM_Application.cxx
old mode 100755 (executable)
new mode 100644 (file)
index 473afae..7a92731
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2016  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
 //
 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
 #include <KERNEL_version.h>
 #include <GUI_version.h>
 
+#include <QAction>
 #include <QApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QMutex>
+#include <QMutexLocker>
 #include <QRegExp>
+#include <QTextStream>
+#include <QDateTime>
 
 #ifdef WIN32
 #include <windows.h>
@@ -46,6 +53,8 @@
 #include <cstdio>
 #include <iostream>
 
+#include <utilities.h>
+
 namespace
 {
 class BusyLocker
@@ -71,9 +80,9 @@ extern "C" CAM_EXPORT SUIT_Application* createApplication()
 /*!
   \class CAM_Application
   \brief Introduces an application class which provides modular architecture.
-  
+
   This class defines multi-modular application configuration and behaviour.
-  Each module (CAM_Module) can have own data model, document windows and 
+  Each module (CAM_Module) can have own data model, document windows and
   viewers, etc.
 
   An application provides all necessary functionality for modules management,
@@ -88,7 +97,7 @@ CAM_Application::ModuleInfoList CAM_Application::myInfoList;
 /*!
   \brief Constructor.
 
-  Read modules list (from command line or from resource file). 
+  Read modules list (from command line or from resource file).
   If \a autoLoad parameter is \c true all the modules will be loaded
   immediately after application starting, otherwise each module will
   be loaded by demand (with activateModule()).
@@ -116,7 +125,7 @@ CAM_Application::~CAM_Application()
   myModules.clear();
 }
 
-/*! 
+/*!
   \brief Start an application.
 
   Load all modules, if "auto loading" flag has been set to \c true.
@@ -125,6 +134,15 @@ CAM_Application::~CAM_Application()
 */
 void CAM_Application::start()
 {
+  // check modules
+  for ( ModuleInfoList::iterator it = myInfoList.begin();
+        it != myInfoList.end(); ++it )
+  {
+    if ( (*it).status == stUnknown )
+      (*it).status = checkModule( (*it).title ) ? stReady : stInaccessible;
+  }
+
+  // auto-load modules
   if ( myAutoLoad )
     loadModules();
 
@@ -147,7 +165,7 @@ CAM_Module* CAM_Application::activeModule() const
 CAM_Module* CAM_Application::module(  const QString& modName ) const
 {
   CAM_Module* mod = 0;
-  for ( QList<CAM_Module*>::const_iterator it = myModules.begin(); 
+  for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
         it != myModules.end() && !mod; ++it )
     if ( (*it)->moduleName() == modName )
       mod = *it;
@@ -171,7 +189,7 @@ void CAM_Application::modules( CAM_Application::ModuleList& out ) const
 {
   out.clear();
 
-  for ( QList<CAM_Module*>::const_iterator it = myModules.begin(); 
+  for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
         it != myModules.end(); ++it )
     out.append( *it );
 }
@@ -179,9 +197,9 @@ void CAM_Application::modules( CAM_Application::ModuleList& out ) const
 /*!
   \brief Get names of all modules.
 
-  Get loaded modules names if \a loaded is \c true, 
+  Get loaded modules names if \a loaded is \c true,
   otherwise get all avaiable modules names.
-  
+
   \param lst output list of modules names
   \param loaded boolean flag, defines what modules names to return
 */
@@ -191,15 +209,16 @@ void CAM_Application::modules( QStringList& lst, const bool loaded ) const
 
   if ( loaded )
   {
-    for ( QList<CAM_Module*>::const_iterator it = myModules.begin(); 
+    for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
           it != myModules.end(); ++it )
       lst.append( (*it)->moduleName() );
   }
   else
   {
-    for ( ModuleInfoList::const_iterator it = myInfoList.begin(); 
+    for ( ModuleInfoList::const_iterator it = myInfoList.begin();
           it != myInfoList.end(); ++it )
-      lst.append( (*it).title );
+      if ( (*it).status != stNoGui )
+        lst.append( (*it).title );
   }
 }
 
@@ -207,8 +226,8 @@ void CAM_Application::modules( QStringList& lst, const bool loaded ) const
   \brief Add module \a mod to the modules list.
 
   Performes module initialization. Does nothing if the module
-  is already added. 
-  
+  is already added.
+
   \param mod module being added
   \sa CAM_Module::initialize()
 */
@@ -222,7 +241,7 @@ void CAM_Application::addModule( CAM_Module* mod )
   QMap<CAM_Module*, int> map;
 
   ModuleList newList;
-  for ( ModuleInfoList::const_iterator it = myInfoList.begin(); 
+  for ( ModuleInfoList::const_iterator it = myInfoList.begin();
         it != myInfoList.end(); ++it )
   {
     if ( (*it).title == mod->moduleName() )
@@ -252,16 +271,13 @@ void CAM_Application::addModule( CAM_Module* mod )
 
 /*!
   \brief Load modules from the modules information list.
-  
+
   If some module can not be loaded, an error message is shown.
 */
 void CAM_Application::loadModules()
 {
   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end(); ++it )
   {
-    if ( !isModuleAccessible( (*it).title ) ) {
-      continue;
-    }
     CAM_Module* mod = loadModule( (*it).title );
     if ( mod )
       addModule( mod );
@@ -270,7 +286,7 @@ void CAM_Application::loadModules()
       if ( desktop() && desktop()->isVisible() )
         SUIT_MessageBox::critical( desktop(), tr( "Loading modules" ), wrn );
       else
-        qWarning( qPrintable( wrn ) ); 
+        qWarning( qPrintable( wrn ) );
     }
   }
 }
@@ -294,11 +310,6 @@ CAM_Module* CAM_Application::loadModule( const QString& modName, const bool show
     return 0;
   }
 
-  if ( !isModuleAccessible( modName ) ) {
-    qWarning( qPrintable( tr( "Module \"%1\" cannot be loaded in this application." ).arg( modName ) ) );
-    return 0;
-  }
-
   QString libName = moduleLibrary( modName );
   if ( libName.isEmpty() )
   {
@@ -311,13 +322,28 @@ CAM_Module* CAM_Application::loadModule( const QString& modName, const bool show
   GET_VERSION_FUNC getVersion = 0;
 
 #ifdef WIN32
-  HINSTANCE modLib = ::LoadLibrary( libName.toLatin1() ); 
+#ifdef UNICODE
+  LPTSTR str_libname = new TCHAR[libName.length() + 1];
+  str_libname[libName.toWCharArray(str_libname)] = '\0';
+#else
+  QByteArray arr = libName.toLatin1();
+  LPTSTR str_libname = arr.constData();
+#endif
+  HINSTANCE modLib = ::LoadLibrary( str_libname );
+#ifdef UNICODE
+  delete str_libname;
+#endif
   if ( !modLib )
   {
     LPVOID lpMsgBuf;
     ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS, 0, ::GetLastError(), 0, (LPTSTR)&lpMsgBuf, 0, 0 );
-    err = QString( "Failed to load  %1. %2" ).arg( libName ).arg( (LPTSTR)lpMsgBuf );
+#ifdef UNICODE
+       QString out_err = QString::fromWCharArray((LPTSTR)lpMsgBuf);
+#else
+       QString out_err = (LPTSTR)lpMsgBuf;
+#endif
+    err = QString( "Failed to load  %1. %2" ).arg( libName ).arg(out_err);
     ::LocalFree( lpMsgBuf );
   }
   else
@@ -328,14 +354,20 @@ CAM_Module* CAM_Application::loadModule( const QString& modName, const bool show
       LPVOID lpMsgBuf;
       ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                        FORMAT_MESSAGE_IGNORE_INSERTS, 0, ::GetLastError(), 0, (LPTSTR)&lpMsgBuf, 0, 0 );
-    err = QString( "Failed to find  %1 function. %2" ).arg( GET_MODULE_NAME ).arg( (LPTSTR)lpMsgBuf );
-    ::LocalFree( lpMsgBuf );
+#ifdef UNICODE
+         QString out_err = QString::fromWCharArray((LPTSTR)lpMsgBuf);
+#else
+         QString out_err = (LPTSTR)lpMsgBuf;
+#endif
+
+         err = QString( "Failed to find  %1 function. %2" ).arg( GET_MODULE_NAME ).arg( out_err );
+         ::LocalFree( lpMsgBuf );
     }
 
     getVersion = (GET_VERSION_FUNC)::GetProcAddress( modLib, GET_VERSION_NAME );
   }
 #else
-  void* modLib = dlopen( libName.toLatin1(), RTLD_LAZY );
+  void* modLib = dlopen( libName.toUtf8(), RTLD_LAZY | RTLD_GLOBAL );
   if ( !modLib )
     err = QString( "Can not load library %1. %2" ).arg( libName ).arg( dlerror() );
   else
@@ -359,12 +391,12 @@ CAM_Module* CAM_Application::loadModule( const QString& modName, const bool show
     if ( desktop() && desktop()->isVisible() )
       SUIT_MessageBox::warning( desktop(), tr( "Error" ), err );
     else
-      qWarning( qPrintable( err ) ); 
+      qWarning( qPrintable( err ) );
   }
 
   char* version = getVersion ? getVersion() : 0;
 
-  if ( version ) {    
+  if ( version ) {
     for ( ModuleInfoList::iterator it = myInfoList.begin(); it != myInfoList.end(); ++it ) {
       if ( (*it).title == modName ) {
         if( (*it).version.isEmpty() ) {
@@ -374,7 +406,7 @@ CAM_Module* CAM_Application::loadModule( const QString& modName, const bool show
       }
     }
   }
-  
+
   return module;
 }
 
@@ -392,18 +424,22 @@ bool CAM_Application::activateModule( const QString& modName )
   // See issues 0021307, 0021373
   BusyLocker lock( myBlocked );
 
+  QString name = modName;
+  if ( !name.isEmpty() && !moduleTitle( modName ).isEmpty() )
+    name = moduleTitle( modName );
+
   bool res = false;
-  if ( !modName.isEmpty() )
+  if ( !name.isEmpty() )
   {
-    CAM_Module* mod = module( modName );
-    if ( !mod && !moduleLibrary( modName ).isEmpty() )
-    {
-      mod = loadModule( modName );
-      addModule( mod );
-    }
+    setProperty("activateModule", true);
+    CAM_Module* mod = module( name );
+    if ( !mod )
+      mod = loadModule( name );
+    addModule( mod );
 
     if ( mod )
       res = activateModule( mod );
+    setProperty("activateModule", QVariant());
   }
   else
     res = activateModule( 0 );
@@ -429,17 +465,29 @@ bool CAM_Application::activateModule( CAM_Module* mod )
 
   if ( myModule )
   {
-    if ( !myModule->deactivateModule( activeStudy() ) )
+    if ( myModule->deactivateModule( activeStudy() ) )
     {
-      // ....      
-    }    
-  }     
+      logStructuredUserEvent( myModule->moduleName(),
+                              "",
+                              "",
+                              "deactivated" );
+    }
+    moduleDeactivated( myModule );
+  }
   myModule = mod;
 
-  if ( myModule ){
+  if ( myModule )
+  {
     // Connect the module to the active study
     myModule->connectToStudy( dynamic_cast<CAM_Study*>( activeStudy() ) );
-    if ( !myModule->activateModule( activeStudy() ) )
+    if ( myModule->activateModule( activeStudy() ) )
+    {
+      logStructuredUserEvent( myModule->moduleName(),
+                              "",
+                              "",
+                              "activated" );
+    }
+    else
     {
       myModule->setMenuShown( false );
       myModule->setToolShown( false );
@@ -447,7 +495,7 @@ bool CAM_Application::activateModule( CAM_Module* mod )
       if ( desktop() && desktop()->isVisible() )
         SUIT_MessageBox::critical( desktop(), tr( "ERROR_TLT" ), wrn );
       else
-        qWarning( qPrintable( wrn ) ); 
+        qWarning( qPrintable( wrn ) );
       myModule = 0;
       return false;
     }
@@ -465,14 +513,13 @@ bool CAM_Application::activateModule( CAM_Module* mod )
   \param actionId is a numerical unique operation identifier
   \return \c true in case of success and \c false otherwise
 */
-bool CAM_Application::activateOperation( const QString& modName, int actionId )
+bool CAM_Application::activateOperation( const QString& modName,
+                                         const int actionId )
 {
-  if (isModuleAccessible(modName)) {
-    CAM_Module* mod = loadModule(modName, false);
-    if (mod) {
-      addModule(mod);
-      return mod->activateOperation(actionId);
-    }
+  CAM_Module* mod = loadModule(modName, false);
+  if (mod) {
+    addModule(mod);
+    return mod->activateOperation(actionId);
   }
   return false;
 }
@@ -484,14 +531,13 @@ bool CAM_Application::activateOperation( const QString& modName, int actionId )
   \param actionId is a string unique operation identifier
   \return \c true in case of success and \c false otherwise
 */
-bool CAM_Application::activateOperation( const QString& modName, const QString& actionId )
+bool CAM_Application::activateOperation( const QString& modName,
+                                         const QString& actionId )
 {
-  if (isModuleAccessible(modName)) {
-    CAM_Module* mod = loadModule(modName, false);
-    if (mod) {
-      addModule(mod);
-      return mod->activateOperation(actionId);
-    }
+  CAM_Module* mod = loadModule(modName, false);
+  if (mod) {
+    addModule(mod);
+    return mod->activateOperation(actionId);
   }
   return false;
 }
@@ -509,12 +555,10 @@ bool CAM_Application::activateOperation( const QString& modName,
                                          const QString& actionId,
                                          const QString& pluginName )
 {
-  if (isModuleAccessible(modName)) {
-    CAM_Module* mod = loadModule(modName, false);
-    if (mod) {
-      addModule(mod);
-      return mod->activateOperation(actionId, pluginName);
-    }
+  CAM_Module* mod = loadModule(modName, false);
+  if (mod) {
+    addModule(mod);
+    return mod->activateOperation(actionId, pluginName);
   }
   return false;
 }
@@ -523,8 +567,8 @@ bool CAM_Application::activateOperation( const QString& modName,
   \brief Create new study.
   \return study object pointer
 */
-SUIT_Study* CAM_Application::createNewStudy() 
-{ 
+SUIT_Study* CAM_Application::createNewStudy()
+{
   return new CAM_Study( this );
 }
 
@@ -543,7 +587,7 @@ void CAM_Application::updateCommandsStatus()
   \brief Prepare application to study closing.
 
   Closes all modules in study \a theDoc.
-  
+
   \param theDoc study
 */
 void CAM_Application::beforeCloseDoc( SUIT_Study* theDoc )
@@ -565,9 +609,22 @@ void CAM_Application::setActiveStudy( SUIT_Study* study )
   STD_Application::setActiveStudy( study );
 }
 
+/*!
+  \brief Check module availability.
+
+  The method can be redefined in successors. Default implementation returns \c true.
+
+  \param title module title
+  \return \c true if module is accessible; \c false otherwise
+*/
+bool CAM_Application::checkModule( const QString& )
+{
+  return true;
+}
+
 /*!
   \brief Callback function, called when the module is added to the application.
-  
+
   This virtual method can be re-implemented in the successors. Base implementation
   does nothing.
 
@@ -577,6 +634,18 @@ void CAM_Application::moduleAdded( CAM_Module* /*mod*/ )
 {
 }
 
+/*!
+  \brief Callback function, called when the module is just deactivated.
+
+  This virtual method can be re-implemented in the successors. Base implementation
+  does nothing.
+
+  \param mod module just deactivated
+*/
+void CAM_Application::moduleDeactivated( CAM_Module* /*mod*/ )
+{
+}
+
 /*!
   \brief Get module name by its title (user name).
   \param title module title (user name)
@@ -611,7 +680,7 @@ QString CAM_Application::moduleTitle( const QString& name )
 
 /*!
   \brief Get module icon name.
-  \param name module name
+  \param name module name or title
   \return module icon or null QString if module is not found
 */
 QString CAM_Application::moduleIcon( const QString& name )
@@ -619,63 +688,71 @@ QString CAM_Application::moduleIcon( const QString& name )
   QString res;
   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it )
   {
-    if ( (*it).name == name )
+    if ( (*it).name == name || (*it).title == name )
       res = (*it).icon;
   }
   return res;
 }
 
 /*!
-  \brief Returns \c true if module is accessible for the current application.
-  Singleton module can be loaded only in one application object. In other application
-  objects this module will be unavailable.
-  \param title module title (user name)
-  \return \c true if module is accessible (can be loaded) or \c false otherwise
- */
-bool CAM_Application::isModuleAccessible( const QString& title )
-{
-  bool found   = false;
-  bool blocked = false;
-  
-  QStringList somewhereLoaded;
-  QList<SUIT_Application*> apps = SUIT_Session::session()->applications();
-  foreach( SUIT_Application* app, apps ) {
-    CAM_Application* camApp = dynamic_cast<CAM_Application*>( app );
-    if ( !camApp ) continue;
-    QStringList loaded;
-    camApp->modules( loaded, true );
-    foreach( QString lm, loaded ) {
-      if ( !somewhereLoaded.contains( lm ) ) somewhereLoaded << lm;
-    }
-  }
-
-  for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && !found; ++it )
+  \brief Get module description.
+  \param name module name or title
+  \return module description or null QString if description is not provided in config file.
+*/
+QString CAM_Application::moduleDescription( const QString& name )
+{
+  QString res;
+  for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it )
   {
-    found = (*it).title == title;
-    blocked = (*it).isSingleton && somewhereLoaded.contains((*it).title);
+    if ( (*it).name == name || (*it).title == name )
+      res = tr((*it).description.toUtf8());
   }
-  return found && !blocked;
+  return res;
 }
 
 /*!
   \brief Get module library name by its title (user name).
-  \param title module title (user name)
+  \param title module name or title
   \param full if \c true, return full library name, otherwise return its internal name
   \return module library name or null QString if module is not found
  */
-QString CAM_Application::moduleLibrary( const QString& title, const bool full )
+QString CAM_Application::moduleLibrary( const QString& name, const bool full )
 {
   QString res;
   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
   {
-    if ( (*it).title == title )
-      res = (*it).internal;
+    if ( (*it).name == name || (*it).title == name )
+      res = (*it).library;
   }
   if ( !res.isEmpty() && full )
     res = SUIT_Tools::library( res );
   return res;
 }
 
+/*!
+  \brief Get displayer proxy for given module, by its title (user name).
+  \param name module name or title
+  \return name of module which provides displayer for requested module
+ */
+QString CAM_Application::moduleDisplayer( const QString& name )
+{
+  QString res;
+
+  if ( !name.isEmpty() )
+  {
+    for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
+    {
+      if ( (*it).title == name || (*it).name == name ) {
+        res = (*it).displayer;
+        if ( res.isEmpty() )
+          res = (*it).title;
+      }
+    }
+  }
+
+  return res;
+}
+
 /*!
   \brief Read modules information list
 
@@ -683,12 +760,12 @@ QString CAM_Application::moduleLibrary( const QString& title, const bool full )
   the application command line arguments, looking for the
   "--modules ( <mod_name>[:<mod_name>...] )" option.
   List of modules is separated by colon symbol (":").
-  
+
   If "--modules" command line option is not used, the list of modules
   is retrieved from the application resource file: parameter "modules" of
   the section "launch".
 
-  Then the information about each module (module title (user name), 
+  Then the information about each module (module title (user name),
   library name) is retrieved from the corresponding section of resource
   file with help of resources manager.
 
@@ -701,6 +778,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;
@@ -745,61 +823,11 @@ 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
-
-    QString modTitle = resMgr->stringValue( *it, "name", QString() );
-    if ( modTitle.isEmpty() )
-    {
-      printf( "****************************************************************\n" );
-      printf( "*    Warning: %s GUI resources are not found.\n", qPrintable(*it) );
-      printf( "*    %s GUI will not be available.\n", qPrintable(*it) );
-      printf( "****************************************************************\n" );
-      continue;
-    }
-
-    QString modIcon = resMgr->stringValue( *it, "icon", QString() );
-
-    QString modLibrary = resMgr->stringValue( *it, "library", QString() ).trimmed();
-    if ( !modLibrary.isEmpty() )
-    {
-      modLibrary = SUIT_Tools::file( modLibrary.trimmed() );
-#ifdef WIN32
-      QString libExt = QString( "dll" );
-#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;
-
-    bool aIsSingleton = resMgr->booleanValue(*it, "singleton", false);
-
-    QString ver = resMgr->stringValue(*it, "version", QString());
+  // extra modules loaded manually on previous session
+  // ...
 
-    ModuleInfo inf;
-    inf.name = modName;
-    inf.title = modTitle;
-    inf.internal = modLibrary;
-    inf.icon = modIcon;
-    inf.isSingleton = aIsSingleton;
-    inf.version = ver;
-    myInfoList.append( inf );
-  }
+  foreach ( QString modName, modList )
+    appendModuleInfo( modName.trimmed() );
 
   if ( myInfoList.isEmpty() ) {
     if ( desktop() && desktop()->isVisible() )
@@ -813,6 +841,96 @@ void CAM_Application::readModuleList()
   }
 }
 
+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
+
+  if ( !moduleTitle( modName ).isEmpty() )
+    return false;  // already added
+
+  if ( modName == "KERNEL" || modName == "GUI" )
+    return false; // skip KERNEL and GUI modules
+
+  // we cannot use own resourceMgr() as this method can be called from constructor
+  SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
+
+  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();
+
+  // "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 )
+  {
+    // 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;
+  }
+
+  // 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;
+}
+
+void CAM_Application::removeModuleInfo( const QString& modName )
+{
+  QMutableListIterator<ModuleInfo> it( myInfoList );
+  while ( it.hasNext() )
+  {
+    ModuleInfo info = it.next();
+    if ( info.name == modName )
+    {
+      it.remove();
+      break;
+    }
+  }
+}
+
 /*!
   \brief Add common menu items to the popup menu.
 
@@ -825,7 +943,7 @@ void CAM_Application::readModuleList()
 void CAM_Application::contextMenuPopup( const QString& type, QMenu* menu, QString& title )
 {
   // to do : add common items for popup menu ( if they are exist )
-  if ( activeModule() ) 
+  if ( activeModule() )
     activeModule()->contextMenuPopup( type, menu, title );
 }
 
@@ -857,18 +975,18 @@ CAM_Application::ModuleShortInfoList CAM_Application::getVersionInfo()
 
   for(int i = 0; i < myInfoList.size(); i++) {
     ModuleShortInfo infoItem;
-    infoItem.name = myInfoList.at(i).title;
+    infoItem.name = myInfoList.at(i).title.isEmpty() ? myInfoList.at(i).name : myInfoList.at(i).title;
     infoItem.version = myInfoList.at(i).version;
     info.append(infoItem);
-  }  
+  }
   return info;
 }
 
 /*!
   \brief Abort active operations if there are any
+
   Iterates through all modules and asks each of them if there are pending operations that cannot be aborted.
+
   \return \c false if some operation cannot be aborted
 */
 bool CAM_Application::abortAllOperations()
@@ -880,3 +998,101 @@ bool CAM_Application::abortAllOperations()
   }
   return aborted;
 }
+
+/*!
+  \brief Log GUI event.
+  \param eventDescription GUI event description.
+*/
+void CAM_Application::logUserEvent( const QString& eventDescription )
+{
+  static QString guiLogFile; // null string means log file was not initialized yet
+  static QMutex aGUILogMutex;
+
+  if ( guiLogFile.isNull() )
+  {
+    // log file was not initialized yet, try to do that by parsing command line arguments
+    guiLogFile = ""; // empty string means initialization was done but log file was not set
+    QStringList args = QApplication::arguments();
+    for ( int i = 1; i < args.count(); i++ )
+    {
+      QRegExp rxs ( "--gui-log-file=(.+)" );
+      if ( rxs.indexIn( args[i] ) >= 0 && rxs.capturedTexts().count() > 1 )
+      {
+        QString file = rxs.capturedTexts()[1];
+        QFileInfo fi ( file );
+        if ( !fi.isDir() && fi.dir().exists() )
+       {
+         guiLogFile = fi.absoluteFilePath();
+         if ( fi.exists() ) {
+           QFile file ( guiLogFile );
+           file.remove(); // remove probably existing log file, to start with empty one
+          }
+        }
+        break;
+      }
+    }
+  }
+  if ( !guiLogFile.isEmpty() ) // non-empty string means log file was already initialized
+  {
+    QMutexLocker aLocker( &aGUILogMutex );
+    QFile file ( guiLogFile );
+    if ( file.open( QFile::Append ) ) // append to log file
+    {
+      QDateTime current = QDateTime::currentDateTime();
+      QTextStream stream( &file );
+      stream << current.toString("yyyyMMdd-hhmmss")
+             << "," << eventDescription
+             << endl;
+      file.close();
+    }
+  }
+}
+
+void CAM_Application::logStructuredUserEvent( const QString& module,
+                                              const QString& section,
+                                              const QString& action,
+                                              const QString& event,
+                                              const QString& message )
+{
+  const QStringList mes = (QStringList() << module
+                           << section
+                           << action
+                           << event
+                           << message);
+
+  logUserEvent( mes.join( "," ) );
+}
+
+/*!
+  \brief Log given action.
+  \param action GUI action being logged.
+  \param moduleName optional name of module, owning an action
+*/
+void CAM_Application::logAction( QAction* action, const QString& moduleName )
+{
+  QString text = action->toolTip();
+  if ( text.isEmpty() )
+    text = action->text();
+  if ( text.isEmpty() )
+    text = action->iconText();
+
+  if ( !text.isEmpty() )
+  {
+    if ( action->isCheckable() )
+    {
+      logStructuredUserEvent ( moduleName,
+                               "",
+                               "toggled",
+                               action->isChecked() ? "on" : "off",
+                               text );
+    }
+    else
+    {
+      logStructuredUserEvent ( moduleName,
+                               "",
+                               "triggered",
+                               "",
+                               text );
+    }
+  }
+}