Salome HOME
CAM_Application: Fix log message (no translation needed).
[modules/gui.git] / src / CAM / CAM_Application.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 #include "CAM_Application.h"
24
25 #include "CAM_Study.h"
26 #include "CAM_Module.h"
27
28 #include <SUIT_Tools.h>
29 #include <SUIT_Desktop.h>
30 #include <SUIT_Session.h>
31 #include <SUIT_MessageBox.h>
32 #include <SUIT_ResourceMgr.h>
33
34 #include <KERNEL_version.h>
35 #include <GUI_version.h>
36
37 #include <QAction>
38 #include <QApplication>
39 #include <QDir>
40 #include <QFileInfo>
41 #include <QMutex>
42 #include <QMutexLocker>
43 #include <QRegExp>
44 #include <QTextStream>
45 #include <QDateTime>
46
47 #ifdef WIN32
48 #include <windows.h>
49 #else
50 #include <dlfcn.h>
51 #endif
52
53 #include <cstdio>
54 #include <iostream>
55
56 #include <utilities.h>
57
58 namespace
59 {
60 class BusyLocker
61 {
62 public:
63   BusyLocker( bool& busy ) : myPrev( busy ), myBusy( busy ) { myBusy = true; }
64   ~BusyLocker() { myBusy = myPrev; }
65 private:
66   bool  myPrev;
67   bool& myBusy;
68 };
69 }
70
71 /*!
72   \brief Create new instance of CAM_Application.
73   \return new instance of CAM_Application class
74 */
75 extern "C" CAM_EXPORT SUIT_Application* createApplication()
76 {
77   return new CAM_Application();
78 }
79
80 /*!
81   \class CAM_Application
82   \brief Introduces an application class which provides modular architecture.
83
84   This class defines multi-modular application configuration and behaviour.
85   Each module (CAM_Module) can have own data model, document windows and
86   viewers, etc.
87
88   An application provides all necessary functionality for modules management,
89   like
90   - loading of modules
91   - modules activation/deactivation
92   - etc
93 */
94
95 CAM_Application::ModuleInfoList CAM_Application::myInfoList;
96
97 /*!
98   \brief Constructor.
99
100   Read modules list (from command line or from resource file).
101   If \a autoLoad parameter is \c true all the modules will be loaded
102   immediately after application starting, otherwise each module will
103   be loaded by demand (with activateModule()).
104
105   \param autoLoad auto loading flag
106 */
107 CAM_Application::CAM_Application( const bool autoLoad )
108 : STD_Application(),
109   myModule( 0 ),
110   myAutoLoad( autoLoad ),
111   myBlocked( false )
112 {
113   readModuleList();
114 }
115
116 /*!
117   \brief Destructor.
118
119   Does nothing currently.
120 */
121 CAM_Application::~CAM_Application()
122 {
123   for ( QList<CAM_Module*>::const_iterator it = myModules.begin(); it != myModules.end(); ++it )
124     delete *it;
125   myModules.clear();
126 }
127
128 /*!
129   \brief Start an application.
130
131   Load all modules, if "auto loading" flag has been set to \c true.
132
133   \sa CAM_Application()
134 */
135 void CAM_Application::start()
136 {
137   // check modules
138   for ( ModuleInfoList::iterator it = myInfoList.begin();
139         it != myInfoList.end(); ++it )
140   {
141     if ( (*it).status == stUnknown )
142       (*it).status = checkModule( (*it).title ) ? stReady : stInaccessible;
143   }
144
145   // auto-load modules
146   if ( myAutoLoad )
147     loadModules();
148
149   STD_Application::start();
150 }
151
152 /*!
153   \brief Get active module.
154   \return active module or 0 if there are no any
155 */
156 CAM_Module* CAM_Application::activeModule() const
157 {
158   return myModule;
159 }
160
161 /*!
162   \brief Get the module with specified name.
163   \return module or 0 if not found
164 */
165 CAM_Module* CAM_Application::module(  const QString& modName ) const
166 {
167   CAM_Module* mod = 0;
168   for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
169         it != myModules.end() && !mod; ++it )
170     if ( (*it)->moduleName() == modName )
171       mod = *it;
172   return mod;
173 }
174
175 /*!
176   \brief Get all loaded modules.
177   \return list of modules
178 */
179 CAM_Application::ModuleList CAM_Application::modules() const
180 {
181   return myModules;
182 }
183
184 /*!
185   \brief Get all loaded modules.
186   \param returning list of modules
187 */
188 void CAM_Application::modules( CAM_Application::ModuleList& out ) const
189 {
190   out.clear();
191
192   for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
193         it != myModules.end(); ++it )
194     out.append( *it );
195 }
196
197 /*!
198   \brief Get names of all modules.
199
200   Get loaded modules names if \a loaded is \c true,
201   otherwise get all avaiable modules names.
202
203   \param lst output list of modules names
204   \param loaded boolean flag, defines what modules names to return
205 */
206 void CAM_Application::modules( QStringList& lst, const bool loaded ) const
207 {
208   lst.clear();
209
210   if ( loaded )
211   {
212     for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
213           it != myModules.end(); ++it )
214       lst.append( (*it)->moduleName() );
215   }
216   else
217   {
218     for ( ModuleInfoList::const_iterator it = myInfoList.begin();
219           it != myInfoList.end(); ++it )
220       if ( (*it).status != stNoGui )
221         lst.append( (*it).title );
222   }
223 }
224
225 /*!
226   \brief Add module \a mod to the modules list.
227
228   Performes module initialization. Does nothing if the module
229   is already added.
230
231   \param mod module being added
232   \sa CAM_Module::initialize()
233 */
234 void CAM_Application::addModule( CAM_Module* mod )
235 {
236   if ( !mod || myModules.contains( mod ) )
237     return;
238
239   mod->initialize( this );
240
241   QMap<CAM_Module*, int> map;
242
243   ModuleList newList;
244   for ( ModuleInfoList::const_iterator it = myInfoList.begin();
245         it != myInfoList.end(); ++it )
246   {
247     if ( (*it).title == mod->moduleName() )
248       newList.append( mod );
249     else
250     {
251       CAM_Module* curMod = module( (*it).title );
252       if ( curMod )
253         newList.append( curMod );
254     }
255   }
256
257   for ( QList<CAM_Module*>::const_iterator it = myModules.begin();
258         it != myModules.end(); ++it )
259   {
260     if ( !newList.contains( *it ) )
261       newList.append( *it );
262   }
263
264   if ( !newList.contains( mod ) )
265       newList.append( mod );
266
267   myModules = newList;
268
269   moduleAdded( mod );
270 }
271
272 /*!
273   \brief Load modules from the modules information list.
274
275   If some module can not be loaded, an error message is shown.
276 */
277 void CAM_Application::loadModules()
278 {
279   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end(); ++it )
280   {
281     CAM_Module* mod = loadModule( (*it).title );
282     if ( mod )
283       addModule( mod );
284     else {
285       QString wrn = tr( "Can not load module %1" ).arg( (*it).title );
286       if ( desktop() && desktop()->isVisible() )
287         SUIT_MessageBox::critical( desktop(), tr( "Loading modules" ), wrn );
288       else
289         qWarning( qPrintable( wrn ) );
290     }
291   }
292 }
293
294 /*!
295   \brief Load module \a modName.
296
297   The function prints warning message if:
298   - modules information list is empty
299   - modules information list does not include specified module info
300   - module library can not be loaded by some reason
301
302   \param modName module name
303   \return module object pointer or 0 if module could not be loaded
304 */
305 CAM_Module* CAM_Application::loadModule( const QString& modName, const bool showMsg )
306 {
307   if ( myInfoList.isEmpty() )
308   {
309     qWarning( qPrintable( tr( "Modules configuration is not defined." ) ) );
310     return 0;
311   }
312
313   QString libName = moduleLibrary( modName );
314   if ( libName.isEmpty() )
315   {
316     qWarning( qPrintable( tr( "Information about module \"%1\" doesn't exist." ).arg( modName ) ) );
317     return 0;
318   }
319
320   QString err;
321   GET_MODULE_FUNC crtInst = 0;
322   GET_VERSION_FUNC getVersion = 0;
323
324 #ifdef WIN32
325 #ifdef UNICODE
326   LPTSTR str_libname = new TCHAR[libName.length() + 1];
327   str_libname[libName.toWCharArray(str_libname)] = '\0';
328 #else
329   QByteArray arr = libName.toLatin1();
330   LPTSTR str_libname = arr.constData();
331 #endif
332   HINSTANCE modLib = ::LoadLibrary( str_libname );
333 #ifdef UNICODE
334   delete str_libname;
335 #endif
336   if ( !modLib )
337   {
338     LPVOID lpMsgBuf;
339     ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
340                      FORMAT_MESSAGE_IGNORE_INSERTS, 0, ::GetLastError(), 0, (LPTSTR)&lpMsgBuf, 0, 0 );
341 #ifdef UNICODE
342         QString out_err = QString::fromWCharArray((LPTSTR)lpMsgBuf);
343 #else
344         QString out_err = (LPTSTR)lpMsgBuf;
345 #endif
346     err = QString( "Failed to load  %1. %2" ).arg( libName ).arg(out_err);
347     ::LocalFree( lpMsgBuf );
348   }
349   else
350   {
351     crtInst = (GET_MODULE_FUNC)::GetProcAddress( modLib, GET_MODULE_NAME );
352     if ( !crtInst )
353     {
354       LPVOID lpMsgBuf;
355       ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
356                        FORMAT_MESSAGE_IGNORE_INSERTS, 0, ::GetLastError(), 0, (LPTSTR)&lpMsgBuf, 0, 0 );
357 #ifdef UNICODE
358           QString out_err = QString::fromWCharArray((LPTSTR)lpMsgBuf);
359 #else
360           QString out_err = (LPTSTR)lpMsgBuf;
361 #endif
362
363           err = QString( "Failed to find  %1 function. %2" ).arg( GET_MODULE_NAME ).arg( out_err );
364           ::LocalFree( lpMsgBuf );
365     }
366
367     getVersion = (GET_VERSION_FUNC)::GetProcAddress( modLib, GET_VERSION_NAME );
368   }
369 #else
370   void* modLib = dlopen( libName.toUtf8(), RTLD_LAZY | RTLD_GLOBAL );
371   if ( !modLib )
372     err = QString( "Can not load library %1. %2" ).arg( libName ).arg( dlerror() );
373   else
374   {
375     crtInst = (GET_MODULE_FUNC)dlsym( modLib, GET_MODULE_NAME );
376     if ( !crtInst )
377       err = QString( "Failed to find function %1. %2" ).arg( GET_MODULE_NAME ).arg( dlerror() );
378
379     getVersion = (GET_VERSION_FUNC)dlsym( modLib, GET_VERSION_NAME );
380   }
381 #endif
382
383   CAM_Module* module = crtInst ? crtInst() : 0;
384   if ( module )
385   {
386     module->setModuleName( modName );
387     module->setName( moduleName( modName ) );
388   }
389
390   if ( !err.isEmpty() && showMsg ) {
391     if ( desktop() && desktop()->isVisible() )
392       SUIT_MessageBox::warning( desktop(), tr( "Error" ), err );
393     else
394       qWarning( qPrintable( err ) );
395   }
396
397   char* version = getVersion ? getVersion() : 0;
398
399   if ( version ) {
400     for ( ModuleInfoList::iterator it = myInfoList.begin(); it != myInfoList.end(); ++it ) {
401       if ( (*it).title == modName ) {
402         if( (*it).version.isEmpty() ) {
403           (*it).version = QString(version);
404         }
405         break;
406       }
407     }
408   }
409
410   return module;
411 }
412
413 /*!
414   \brief Activate module \a modName.
415   \param modName module name
416   \return \c true, if module is loaded and activated successfully and \c false otherwise
417 */
418 bool CAM_Application::activateModule( const QString& modName )
419 {
420   if ( (!modName.isEmpty() && !activeStudy()) || myBlocked )
421     return false;
422
423   // VSR 25/10/2011: prevent nested activation/deactivation
424   // See issues 0021307, 0021373
425   BusyLocker lock( myBlocked );
426
427   QString name = modName;
428   if ( !name.isEmpty() && !moduleTitle( modName ).isEmpty() )
429     name = moduleTitle( modName );
430
431   bool res = false;
432   if ( !name.isEmpty() )
433   {
434     setProperty("activateModule", true);
435     CAM_Module* mod = module( name );
436     if ( !mod )
437       mod = loadModule( name );
438     addModule( mod );
439
440     if ( mod )
441       res = activateModule( mod );
442     setProperty("activateModule", QVariant());
443   }
444   else
445     res = activateModule( 0 );
446
447   return res;
448 }
449
450 /*!
451   \brief Activate module \a mod.
452
453   Shows error message if module could not be activated in the current study.
454
455   \param mod module object pointer
456   \return \c true, if module is loaded and activated successfully and \c false otherwise
457 */
458 bool CAM_Application::activateModule( CAM_Module* mod )
459 {
460   if ( mod && !activeStudy() )
461     return false;
462
463   if ( myModule == mod )
464     return true;
465
466   if ( myModule )
467   {
468     if ( myModule->deactivateModule( activeStudy() ) )
469     {
470       logStructuredUserEvent( myModule->moduleName(),
471                               "",
472                               "",
473                               "deactivated" );
474     }
475     moduleDeactivated( myModule );
476   }
477   myModule = mod;
478
479   if ( myModule )
480   {
481     // Connect the module to the active study
482     myModule->connectToStudy( dynamic_cast<CAM_Study*>( activeStudy() ) );
483     if ( myModule->activateModule( activeStudy() ) )
484     {
485       logStructuredUserEvent( myModule->moduleName(),
486                               "",
487                               "",
488                               "activated" );
489     }
490     else
491     {
492       myModule->setMenuShown( false );
493       myModule->setToolShown( false );
494       QString wrn = tr( "ERROR_ACTIVATE_MODULE_MSG" ).arg( myModule->moduleName() );
495       if ( desktop() && desktop()->isVisible() )
496         SUIT_MessageBox::critical( desktop(), tr( "ERROR_TLT" ), wrn );
497       else
498         qWarning( qPrintable( wrn ) );
499       myModule = 0;
500       return false;
501     }
502   }
503
504   updateCommandsStatus();
505
506   return true;
507 }
508
509 /*!
510   \brief Load module \a modName and activate its operation, corresponding to \a actionId.
511   This method is dedicated to run operations of some module from any other module.
512   \param modName module name
513   \param actionId is a numerical unique operation identifier
514   \return \c true in case of success and \c false otherwise
515 */
516 bool CAM_Application::activateOperation( const QString& modName,
517                                          const int actionId )
518 {
519   CAM_Module* mod = loadModule(modName, false);
520   if (mod) {
521     addModule(mod);
522     return mod->activateOperation(actionId);
523   }
524   return false;
525 }
526
527 /*!
528   \brief Load module \a modName and activate its operation, corresponding to \a actionId.
529   This method is dedicated to run operations of some module from any other module.
530   \param modName module name
531   \param actionId is a string unique operation identifier
532   \return \c true in case of success and \c false otherwise
533 */
534 bool CAM_Application::activateOperation( const QString& modName,
535                                          const QString& actionId )
536 {
537   CAM_Module* mod = loadModule(modName, false);
538   if (mod) {
539     addModule(mod);
540     return mod->activateOperation(actionId);
541   }
542   return false;
543 }
544
545 /*!
546   \brief Load module \a modName and activate its operation,
547          corresponding to \a actionId and \a pluginName.
548   This method is dedicated to run operations of some module from any other module.
549   \param modName module name
550   \param actionId is a string unique operation identifier
551   \param pluginName is a name of a plugin where the operation is implemented
552   \return \c true in case of success and \c false otherwise
553 */
554 bool CAM_Application::activateOperation( const QString& modName,
555                                          const QString& actionId,
556                                          const QString& pluginName )
557 {
558   CAM_Module* mod = loadModule(modName, false);
559   if (mod) {
560     addModule(mod);
561     return mod->activateOperation(actionId, pluginName);
562   }
563   return false;
564 }
565
566 /*!
567   \brief Create new study.
568   \return study object pointer
569 */
570 SUIT_Study* CAM_Application::createNewStudy()
571 {
572   return new CAM_Study( this );
573 }
574
575 /*!
576   \brief Update menu commands status.
577 */
578 void CAM_Application::updateCommandsStatus()
579 {
580   STD_Application::updateCommandsStatus();
581
582   if ( activeModule() )
583     activeModule()->updateCommandsStatus();
584 }
585
586 /*!
587   \brief Prepare application to study closing.
588
589   Closes all modules in study \a theDoc.
590
591   \param theDoc study
592 */
593 void CAM_Application::beforeCloseDoc( SUIT_Study* theDoc )
594 {
595   for ( QList<CAM_Module*>::iterator it = myModules.begin(); it != myModules.end(); ++it )
596     (*it)->studyClosed( theDoc );
597 }
598
599 void CAM_Application::afterCloseDoc()
600 {
601 }
602
603 /*!
604   \brief Set active study.
605   \param study study to be made active
606 */
607 void CAM_Application::setActiveStudy( SUIT_Study* study )
608 {
609   STD_Application::setActiveStudy( study );
610 }
611
612 /*!
613   \brief Check module availability.
614
615   The method can be redefined in successors. Default implementation returns \c true.
616
617   \param title module title
618   \return \c true if module is accessible; \c false otherwise
619 */
620 bool CAM_Application::checkModule( const QString& )
621 {
622   return true;
623 }
624
625 /*!
626   \brief Callback function, called when the module is added to the application.
627
628   This virtual method can be re-implemented in the successors. Base implementation
629   does nothing.
630
631   \param mod module being added
632 */
633 void CAM_Application::moduleAdded( CAM_Module* /*mod*/ )
634 {
635 }
636
637 /*!
638   \brief Callback function, called when the module is just deactivated.
639
640   This virtual method can be re-implemented in the successors. Base implementation
641   does nothing.
642
643   \param mod module just deactivated
644 */
645 void CAM_Application::moduleDeactivated( CAM_Module* /*mod*/ )
646 {
647 }
648
649 /*!
650   \brief Get module name by its title (user name).
651   \param title module title (user name)
652   \return module name or null QString if module is not found
653 */
654 QString CAM_Application::moduleName( const QString& title )
655 {
656   QString res;
657   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
658   {
659     if ( (*it).title == title )
660       res = (*it).name;
661   }
662   return res;
663 }
664
665 /*!
666   \brief Get module title (user name) by its name.
667   \param name module name
668   \return module title (user name) or null QString if module is not found
669 */
670 QString CAM_Application::moduleTitle( const QString& name )
671 {
672   QString res;
673   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
674   {
675     if ( (*it).name == name )
676       res = (*it).title;
677   }
678   return res;
679 }
680
681 /*!
682   \brief Get module icon name.
683   \param name module name or title
684   \return module icon or null QString if module is not found
685 */
686 QString CAM_Application::moduleIcon( const QString& name )
687 {
688   QString res;
689   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it )
690   {
691     if ( (*it).name == name || (*it).title == name )
692       res = (*it).icon;
693   }
694   return res;
695 }
696
697 /*!
698   \brief Get module description.
699   \param name module name or title
700   \return module description or null QString if description is not provided in config file.
701 */
702 QString CAM_Application::moduleDescription( const QString& name )
703 {
704   QString res;
705   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isNull(); ++it )
706   {
707     if ( (*it).name == name || (*it).title == name )
708       res = tr((*it).description.toUtf8());
709   }
710   return res;
711 }
712
713 /*!
714   \brief Get module library name by its title (user name).
715   \param title module name or title
716   \param full if \c true, return full library name, otherwise return its internal name
717   \return module library name or null QString if module is not found
718  */
719 QString CAM_Application::moduleLibrary( const QString& name, const bool full )
720 {
721   QString res;
722   for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
723   {
724     if ( (*it).name == name || (*it).title == name )
725       res = (*it).library;
726   }
727   if ( !res.isEmpty() && full )
728     res = SUIT_Tools::library( res );
729   return res;
730 }
731
732 /*!
733   \brief Get displayer proxy for given module, by its title (user name).
734   \param name module name or title
735   \return name of module which provides displayer for requested module
736  */
737 QString CAM_Application::moduleDisplayer( const QString& name )
738 {
739   QString res;
740
741   if ( !name.isEmpty() )
742   {
743     for ( ModuleInfoList::const_iterator it = myInfoList.begin(); it != myInfoList.end() && res.isEmpty(); ++it )
744     {
745       if ( (*it).title == name || (*it).name == name ) {
746         res = (*it).displayer;
747         if ( res.isEmpty() )
748           res = (*it).title;
749       }
750     }
751   }
752
753   return res;
754 }
755
756 /*!
757   \brief Read modules information list
758
759   This function first tries to get the modules names list by parsing
760   the application command line arguments, looking for the
761   "--modules ( <mod_name>[:<mod_name>...] )" option.
762   List of modules is separated by colon symbol (":").
763
764   If "--modules" command line option is not used, the list of modules
765   is retrieved from the application resource file: parameter "modules" of
766   the section "launch".
767
768   Then the information about each module (module title (user name),
769   library name) is retrieved from the corresponding section of resource
770   file with help of resources manager.
771
772   Shows the warning message, if module information list is empty.
773
774   \sa SUIT_ResourceMgr
775 */
776 void CAM_Application::readModuleList()
777 {
778   if ( !myInfoList.isEmpty() )
779     return;
780
781   // we cannot use own resourceMgr() as this method can be called from constructor
782   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
783
784   QStringList modList;
785
786   QString args = QApplication::arguments().join( " " );
787
788   QRegExp rx1("--modules=([\\w,]*)");
789   rx1.setMinimal( false );
790   QRegExp rx2("--modules\\s+\\(\\s*(.*)\\s*\\)");
791   rx2.setMinimal( true );
792   int pos = 0;
793   while ( 1 ) {
794     QString modules;
795     int pos1 = rx1.indexIn( args, pos );
796     int pos2 = rx2.indexIn( args, pos );
797     if ( pos1 != -1 && pos2 != -1 ) {
798       modules = pos1 < pos2 ? rx1.cap( 1 ) : rx2.cap(1);
799       pos = pos1 < pos2 ? pos1 + rx1.matchedLength() : pos2 + rx2.matchedLength();
800     }
801     else if ( pos1 != -1 ) {
802       modules = rx1.cap( 1 );
803       pos = pos1 + rx1.matchedLength();
804     }
805     else if ( pos2 != -1 ) {
806       modules = rx2.cap( 1 );
807       pos = pos2 + rx2.matchedLength();
808     }
809     else {
810       break;
811     }
812
813     modList.clear();
814     QStringList mods = modules.split( QRegExp( "[:|,\\s]" ), QString::SkipEmptyParts );
815     for ( int i = 0; i < mods.count(); i++ ) {
816       if ( !mods[i].trimmed().isEmpty() )
817         modList.append( mods[i].trimmed() );
818     }
819   }
820
821   if ( modList.isEmpty() ) {
822     QString mods = resMgr->stringValue( "launch", "modules", QString() );
823     modList = mods.split( ",", QString::SkipEmptyParts );
824   }
825
826   // extra modules loaded manually on previous session
827   // ...
828
829   foreach ( QString modName, modList )
830     appendModuleInfo( modName.trimmed() );
831
832   if ( myInfoList.isEmpty() ) {
833     if ( desktop() && desktop()->isVisible() )
834       SUIT_MessageBox::warning( desktop(), tr( "Warning" ), tr( "Modules list is empty" ) );
835     else
836       {
837         printf( "****************************************************************\n" );
838         printf( "*    Warning: modules list is empty.\n" );
839         printf( "****************************************************************\n" );
840       }
841   }
842 }
843
844 bool CAM_Application::appendModuleInfo( const QString& modName )
845 {
846   MESSAGE("Start to append module info for a given module name: ");
847   SCRUTE(modName.toStdString());
848
849   if ( modName.isEmpty() )
850     return false;  // empty module name
851
852   if ( !moduleTitle( modName ).isEmpty() )
853     return false;  // already added
854
855   if ( modName == "KERNEL" || modName == "GUI" )
856     return false; // skip KERNEL and GUI modules
857
858   // we cannot use own resourceMgr() as this method can be called from constructor
859   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
860
861   ModuleInfo inf;
862
863   // module internal name
864   inf.name = modName;
865   // module version
866   inf.version = resMgr->stringValue( modName, "version", QString() ).trimmed();
867   // displayer, if module does not have GUI, displayer may be delegated to other module
868   inf.displayer = resMgr->stringValue( modName, "displayer", QString() ).trimmed();
869
870   // "gui" option explicitly says that module has GUI
871   // Now trying to get the "gui" option value, we always get a default one,
872   // then we can't rely on it.
873   bool hasGui = resMgr->booleanValue(modName, "gui", false);
874
875   // Additional check if the module actually has a title and icon.
876   // Module with GUI must explicitly specify title (GUI name).
877   inf.title = resMgr->stringValue(modName, "name", QString()).trimmed();
878   const bool hasTitle = !inf.title.isEmpty();
879   SCRUTE(hasGui);
880   SCRUTE(hasTitle);
881   if (hasGui && !hasTitle)
882   {
883     MESSAGE("Invalid config! The module has gui option, but doesn't have a title.");
884     return false;
885   }
886
887   // While we can't rely on gui option, use a title to make a decision about gui.
888   hasGui = hasTitle;
889
890   // status; if module has GUI, availability will be checked on activation
891   inf.status = hasGui ? stUnknown : stNoGui;
892
893   if ( hasGui )
894   {
895     // icon
896     inf.icon = resMgr->stringValue( modName, "icon", QString() ).trimmed();
897     // description, for Info panel
898     inf.description = resMgr->stringValue( modName, "description", QString() );
899     // library; if not specified, we use internal module name
900     inf.library = SUIT_Tools::libraryName( resMgr->stringValue( modName, "library", QString() ).trimmed() );
901     if ( inf.library.isEmpty() )
902       inf.library = modName;
903   }
904
905   // At this point we should have only valid inf object.
906   myInfoList.append(inf);
907
908   SCRUTE(inf.name.toStdString());
909   SCRUTE(inf.version.toStdString());
910   SCRUTE(inf.displayer.toStdString());
911   SCRUTE(inf.status);
912   SCRUTE(inf.title.toStdString());
913   SCRUTE(inf.icon.toStdString());
914   SCRUTE(inf.description.toStdString());
915   SCRUTE(inf.library.toStdString());
916
917   return true;
918 }
919
920 void CAM_Application::removeModuleInfo( const QString& modName )
921 {
922   QMutableListIterator<ModuleInfo> it( myInfoList );
923   while ( it.hasNext() )
924   {
925     ModuleInfo info = it.next();
926     if ( info.name == modName )
927     {
928       it.remove();
929       break;
930     }
931   }
932 }
933
934 /*!
935   \brief Add common menu items to the popup menu.
936
937   Menu items list is defined by the active module.
938
939   \param type popup menu context
940   \param menu popup menu
941   \param title popup menu title, which can be set by the module if required
942 */
943 void CAM_Application::contextMenuPopup( const QString& type, QMenu* menu, QString& title )
944 {
945   // to do : add common items for popup menu ( if they are exist )
946   if ( activeModule() )
947     activeModule()->contextMenuPopup( type, menu, title );
948 }
949
950 /*!
951   \brief Create new empty study.
952 */
953 void CAM_Application::createEmptyStudy()
954 {
955   /*SUIT_Study* study = */activeStudy();
956   STD_Application::createEmptyStudy();
957 }
958
959 /*!
960   \brief Return information about version of the each module.
961 */
962 CAM_Application::ModuleShortInfoList CAM_Application::getVersionInfo()
963 {
964   ModuleShortInfoList info;
965
966   ModuleShortInfo kernel;
967   kernel.name = "KERNEL";
968   kernel.version = KERNEL_VERSION_STR;
969   info.append(kernel);
970
971   ModuleShortInfo gui;
972   gui.name = "GUI";
973   gui.version = GUI_VERSION_STR;
974   info.append(gui);
975
976   for(int i = 0; i < myInfoList.size(); i++) {
977     ModuleShortInfo infoItem;
978     infoItem.name = myInfoList.at(i).title.isEmpty() ? myInfoList.at(i).name : myInfoList.at(i).title;
979     infoItem.version = myInfoList.at(i).version;
980     info.append(infoItem);
981   }
982   return info;
983 }
984
985 /*!
986   \brief Abort active operations if there are any
987
988   Iterates through all modules and asks each of them if there are pending operations that cannot be aborted.
989
990   \return \c false if some operation cannot be aborted
991 */
992 bool CAM_Application::abortAllOperations()
993 {
994   bool aborted = true;
995   for ( QList<CAM_Module*>::const_iterator it = myModules.begin(); it != myModules.end() && aborted; ++it )
996   {
997     aborted = (*it)->abortAllOperations();
998   }
999   return aborted;
1000 }
1001
1002 /*!
1003   \brief Log GUI event.
1004   \param eventDescription GUI event description.
1005 */
1006 void CAM_Application::logUserEvent( const QString& eventDescription )
1007 {
1008   static QString guiLogFile; // null string means log file was not initialized yet
1009   static QMutex aGUILogMutex;
1010
1011   if ( guiLogFile.isNull() )
1012   {
1013     // log file was not initialized yet, try to do that by parsing command line arguments
1014     guiLogFile = ""; // empty string means initialization was done but log file was not set
1015     QStringList args = QApplication::arguments();
1016     for ( int i = 1; i < args.count(); i++ )
1017     {
1018       QRegExp rxs ( "--gui-log-file=(.+)" );
1019       if ( rxs.indexIn( args[i] ) >= 0 && rxs.capturedTexts().count() > 1 )
1020       {
1021         QString file = rxs.capturedTexts()[1];
1022         QFileInfo fi ( file );
1023         if ( !fi.isDir() && fi.dir().exists() )
1024         {
1025           guiLogFile = fi.absoluteFilePath();
1026           if ( fi.exists() ) {
1027             QFile file ( guiLogFile );
1028             file.remove(); // remove probably existing log file, to start with empty one
1029           }
1030         }
1031         break;
1032       }
1033     }
1034   }
1035   if ( !guiLogFile.isEmpty() ) // non-empty string means log file was already initialized
1036   {
1037     QMutexLocker aLocker( &aGUILogMutex );
1038     QFile file ( guiLogFile );
1039     if ( file.open( QFile::Append ) ) // append to log file
1040     {
1041       QDateTime current = QDateTime::currentDateTime();
1042       QTextStream stream( &file );
1043       stream << current.toString("yyyyMMdd-hhmmss")
1044              << "," << eventDescription
1045              << endl;
1046       file.close();
1047     }
1048   }
1049 }
1050
1051 void CAM_Application::logStructuredUserEvent( const QString& module,
1052                                               const QString& section,
1053                                               const QString& action,
1054                                               const QString& event,
1055                                               const QString& message )
1056 {
1057   const QStringList mes = (QStringList() << module
1058                            << section
1059                            << action
1060                            << event
1061                            << message);
1062
1063   logUserEvent( mes.join( "," ) );
1064 }
1065
1066 /*!
1067   \brief Log given action.
1068   \param action GUI action being logged.
1069   \param moduleName optional name of module, owning an action
1070 */
1071 void CAM_Application::logAction( QAction* action, const QString& moduleName )
1072 {
1073   QString text = action->toolTip();
1074   if ( text.isEmpty() )
1075     text = action->text();
1076   if ( text.isEmpty() )
1077     text = action->iconText();
1078
1079   if ( !text.isEmpty() )
1080   {
1081     if ( action->isCheckable() )
1082     {
1083       logStructuredUserEvent ( moduleName,
1084                                "",
1085                                "toggled",
1086                                action->isChecked() ? "on" : "off",
1087                                text );
1088     }
1089     else
1090     {
1091       logStructuredUserEvent ( moduleName,
1092                                "",
1093                                "triggered",
1094                                "",
1095                                text );
1096     }
1097   }
1098 }