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