Salome HOME
Fix a bug: do not load default icon for the menu items which do not have any icons
[modules/gui.git] / src / SALOME_PYQT / SALOME_PYQT_GUI / SALOME_PYQT_Module.cxx
1 //=============================================================================
2 // File      : SALOME_PYQT_Module.cxx
3 // Created   : 25/04/05
4 // Author    : Vadim SANDLER
5 // Project   : SALOME
6 // Copyright : 2003-2005 CEA/DEN, EDF R&D
7 // $Header   : $
8 //=============================================================================
9
10 #include "SALOME_PYQT_Module.h"
11
12 #include "PyInterp_Dispatcher.h"
13 #include "SUIT_ResourceMgr.h"
14 #include "STD_MDIDesktop.h"
15 #include "SalomeApp_Application.h"
16 #include "SalomeApp_Study.h"
17 #include "SalomeApp_DataModel.h"
18
19 #include <SALOME_LifeCycleCORBA.hxx>
20
21 #include <qfile.h>
22 #include <qdom.h>
23 #include <qworkspace.h>
24 #include <qpopupmenu.h>
25
26 #if QT_VERSION > 0x030005
27 #include "sipAPISalomePyQtGUI.h"
28 #else
29 #include "sipSalomePyQtGUIDeclSalomePyQtGUI.h"
30 #endif
31
32 #include <sipqtQWidget.h>
33 #include <sipqtQPopupMenu.h>
34
35 using namespace std;
36
37 // Default name of the module, should be replaced at the moment 
38 // of module creation
39 #define __DEFAULT_NAME__ "SALOME_PYQT_Module"
40 // Comment this define to block invoking of obsolete Python module's
41 // methods like setSetting(), definePopup(), etc.
42 #define __CALL_OLD_METHODS__
43
44 //=============================================================================
45 // General rule for Python requests created by SALOME_PYQT_Module:
46 // all requests should be executed SYNCHRONOUSLY within the main GUI thread.
47 // However, it is obligatory that ANY Python call is wrapped with a request object,
48 // so that ALL Python API calls are serialized with PyInterp_Dispatcher.
49 //=============================================================================
50
51
52 //=============================================================================
53 // The default PyQt module data model.
54 // Reuses common data model from SalomeApp.
55 //=============================================================================
56 class SALOME_PYQT_DataModel: public SalomeApp_DataModel
57 {
58 public:
59   SALOME_PYQT_DataModel( CAM_Module* theModule ) : SalomeApp_DataModel( theModule ) {}
60   bool isModified() const { return false; }
61   bool isSaved()  const   { return false; }
62 };
63
64 //=============================================================================
65 // The class for parsing of the XML resource files.
66 // Used for backward compatibility with existing Python modules.
67 //=============================================================================
68 class SALOME_PYQT_XmlHandler
69 {
70 public:
71   SALOME_PYQT_XmlHandler( SALOME_PYQT_Module* module, const QString& fileName );
72   void createActions();
73   void createPopup  ( QPopupMenu*    menu, 
74                       const QString& context, 
75                       const QString& parent, 
76                       const QString& object );
77
78 protected:
79   void createToolBar   ( QDomNode& parentNode );
80   void createMenu      ( QDomNode& parentNode, 
81                          const int parentMenuId = -1 );
82
83   void insertPopupItems( QDomNode&   parentNode, 
84                          QPopupMenu* menu );
85
86 private:
87   SALOME_PYQT_Module* myModule;
88   QDomDocument        myDoc;
89 };
90
91 //=============================================================================
92 // SALOME_PYQT_Module class implementation (implements CAM_Module API for
93 // all Python-based SALOME module
94 //=============================================================================
95
96 /*!
97  * This function creates an instance of SALOME_PYQT_Module object by request
98  * of and application object when the module is loaded.
99  */
100 extern "C" {
101   SALOME_PYQT_EXPORT CAM_Module* createModule() {
102     return new SALOME_PYQT_Module();
103   }
104 }
105
106 /*! 
107  * Static variables definition
108  */
109 SALOME_PYQT_Module::InterpMap SALOME_PYQT_Module::myInterpMap;
110
111 /*!
112  * Constructor
113  */
114 SALOME_PYQT_Module::SALOME_PYQT_Module() :
115        SalomeApp_Module( __DEFAULT_NAME__ ), myModule( 0 ), myXmlHandler ( 0 )
116 {
117   myMenuActionList.setAutoDelete( false );
118   myPopupActionList.setAutoDelete( false );
119   myToolbarActionList.setAutoDelete( false );
120 }
121
122 /*!
123  * Destructor
124  */
125 SALOME_PYQT_Module::~SALOME_PYQT_Module()
126 {
127   myMenuActionList.clear();
128   myPopupActionList.clear();
129   myToolbarActionList.clear();
130   if ( myXmlHandler )
131     delete myXmlHandler;
132 }
133
134 /*!
135  * Creates data model for the module.
136  * Reimplemented from CAM_Module.
137  */
138 CAM_DataModel* SALOME_PYQT_Module::createDataModel()
139 {
140   // VSR: this is a temporary solution : 
141   // should reuse default data model from SalomeApp
142   return new SALOME_PYQT_DataModel( this );
143 }
144
145 /*!
146  * Initialization of the module.
147  * Inherited from CAM_Module.
148  *
149  * This method is used for creation of the menus, toolbars and other staff.
150  * There are two ways:
151  * - for obsolete modules this method first tries to read <module>_<language>.xml 
152  *   resource file which contains a menu, toolbars and popup menus description.
153  * - new modules can create menus by by calling the corresponding methods of SalomePyQt
154  *   Python API in the Python module's initialize() method which is called from here.
155  * NOTE: if postponed modules loading is not used, the active study might be not defined
156  * yet at this stage, so initialize() method should not perform any study-based initialization.
157  */
158 void SALOME_PYQT_Module::initialize( CAM_Application* app )
159 {
160   SalomeApp_Module::initialize( app );
161
162   // Try to get XML resource file name
163   SUIT_ResourceMgr* aResMgr = getApp()->resourceMgr();
164   QString aLang = aResMgr->stringValue( "language", "language", QString::null );
165   if ( aLang.isEmpty() ) aLang = QString( "en" );
166   QString aName = name( "" );
167   QString aFileName = aName + "_" + aLang + ".xml";
168   aFileName = aResMgr->path( "resources", aName, aFileName );
169  
170   // parse XML file if it is found and create actions
171   if ( !myXmlHandler && !aFileName.isEmpty() ) {
172     myXmlHandler = new SALOME_PYQT_XmlHandler( this, aFileName );
173     myXmlHandler->createActions();
174   }
175
176   // perform internal initialization and call module's initialize() method
177   // InitializeReq: request class for internal init() operation
178   class InitializeReq : public PyInterp_Request
179   {
180   public:
181     InitializeReq( CAM_Application*    _app,
182                    SALOME_PYQT_Module* _obj ) 
183       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
184         myApp( _app ),
185         myObj( _obj ) {}
186     
187   protected:
188     virtual void execute()
189     {
190       myObj->init( myApp );
191     }
192
193   private:
194     CAM_Application*    myApp;
195     SALOME_PYQT_Module* myObj;
196   };
197
198   // Posting the request
199   PyInterp_Dispatcher::Get()->Exec( new InitializeReq( app, this ) );
200 }
201
202 /*!
203  * Activation of the module.
204  * Inherited from CAM_Module.
205  */
206 bool SALOME_PYQT_Module::activateModule( SUIT_Study* theStudy )
207 {
208   MESSAGE( "SALOME_PYQT_Module::activateModule" );
209
210   bool res = SalomeApp_Module::activateModule( theStudy );
211   
212   if ( !res )
213     return res;
214
215   // activate menus, toolbars, etc
216   setMenuShown( true );
217   setToolShown( true );
218
219   // ActivateReq: request class for internal activate() operation
220   class ActivateReq : public PyInterp_Request
221   {
222   public:
223     ActivateReq( SUIT_Study*         _study, 
224                  SALOME_PYQT_Module* _obj ) 
225       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
226         myStudy ( _study ),
227         myObj   ( _obj   ) {}
228     
229   protected:
230     virtual void execute()
231     {
232       myObj->activate( myStudy );
233     }
234
235   private:
236     SUIT_Study*         myStudy;
237     SALOME_PYQT_Module* myObj;
238   };
239
240   // Posting the request
241   PyInterp_Dispatcher::Get()->Exec( new ActivateReq( theStudy, this ) );
242
243   // connect desktop activation signal
244   connect( application()->desktop(), SIGNAL( activated() ), this, SLOT( onDesktopActivated() ) );
245   
246   return true;
247 }
248
249 /*!
250  * Deactivation of the module.
251  * Inherited from CAM_Module.
252  */
253 bool SALOME_PYQT_Module::deactivateModule( SUIT_Study* theStudy )
254 {
255   MESSAGE( "SALOME_PYQT_Module::deactivateModule" );
256
257   bool res = SalomeApp_Module::deactivateModule( theStudy );
258
259   // deactivate menus, toolbars, etc
260   setMenuShown( false );
261   setToolShown( false );
262
263   // DeactivateReq: request class for internal deactivate() operation
264   class DeactivateReq : public PyInterp_LockRequest
265   {
266   public:
267     DeactivateReq( PyInterp_base*      _py_interp, 
268                    SUIT_Study*         _study, 
269                    SALOME_PYQT_Module* _obj ) 
270       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
271         myStudy ( _study ),
272         myObj   ( _obj   ) {}
273     
274   protected:
275     virtual void execute()
276     {
277       myObj->deactivate( myStudy );
278     }
279
280   private:
281     SUIT_Study*         myStudy;
282     SALOME_PYQT_Module* myObj;
283   };
284
285   // Posting the request
286   PyInterp_Dispatcher::Get()->Exec( new DeactivateReq( myInterp, theStudy, this ) );
287
288   // disconnect desktop activation signal
289   disconnect( application()->desktop(), SIGNAL( activated() ), this, SLOT( onDesktopActivated() ) );
290   
291   return res;
292 }
293
294 /*!
295  * Processes GUI action (from main menu, toolbar or context popup menu)
296  */
297 void SALOME_PYQT_Module::onGUIEvent()
298 {
299   // get sender action
300   const QObject* obj = sender();
301   if ( !obj || !obj->inherits( "QAction" ) )
302     return;
303   QAction* action = (QAction*)obj;
304
305   // get action ID
306   int id = actionId( action );
307   if ( myMenuActionList.contains( action ) )
308     id -= PYQT_ACTION_MENU;
309   if ( myToolbarActionList.contains( action ) )
310     id -= PYQT_ACTION_TOOLBAL;
311   if ( myPopupActionList.contains( action ) )
312     id -= PYQT_ACTION_POPUP;
313   MESSAGE( "SALOME_PYQT_Module::onGUIEvent: id = " << id );
314
315   // perform synchronous request to Python event dispatcher
316   class GUIEvent : public PyInterp_LockRequest
317   {
318   public:
319     GUIEvent( PyInterp_base*      _py_interp, 
320               SALOME_PYQT_Module* _obj,
321               int                 _id ) 
322       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
323         myId    ( _id  ),
324         myObj   ( _obj ) {}
325     
326   protected:
327     virtual void execute()
328     {
329       myObj->guiEvent( myId );
330     }
331
332   private:
333     int                 myId;
334     SALOME_PYQT_Module* myObj;
335   };
336
337   // Posting the request
338   PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
339 }
340
341 /*!
342  * Desktop activation slot. Used for notifying about changing of the active study.
343  */
344 void SALOME_PYQT_Module::onDesktopActivated()
345 {
346   // StudyChangedReq: request class for internal studyChanged() operation
347   class StudyChangedReq : public PyInterp_Request
348   {
349   public:
350     StudyChangedReq( SUIT_Study*         _study, 
351                      SALOME_PYQT_Module* _obj ) 
352       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
353         myStudy ( _study ),
354         myObj   ( _obj   ) {}
355     
356   protected:
357     virtual void execute()
358     {
359       myObj->studyChanged( myStudy );
360     }
361
362   private:
363     SUIT_Study*         myStudy;
364     SALOME_PYQT_Module* myObj;
365   };
366
367   // Posting the request
368   PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( application()->activeStudy(), this ) );
369 }
370
371 /*! 
372   Context popup menu request.
373   Called when user activates popup menu in some window (view, object browser, etc).
374   */
375 void SALOME_PYQT_Module::contextMenuPopup( const QString& theContext, QPopupMenu* thePopupMenu, QString& /*title*/ )
376 {
377   MESSAGE( "SALOME_PYQT_Module::contextMenuPopup : " << theContext.latin1() );
378   // perform synchronous request to Python event dispatcher
379   class PopupMenuEvent : public PyInterp_LockRequest
380   {
381   public:
382     PopupMenuEvent( PyInterp_base*     _py_interp, 
383                     SALOME_PYQT_Module* _obj,
384                     const QString&      _context, 
385                     QPopupMenu*        _popup ) 
386       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
387         myContext( _context ), 
388         myPopup  ( _popup  ),
389         myObj    ( _obj )   {}
390     
391   protected:
392     virtual void execute()
393     {
394       myObj->contextMenu( myContext, myPopup );
395     }
396
397   private:
398     SALOME_PYQT_Module* myObj;
399     QString             myContext;
400     QPopupMenu*         myPopup;
401   };
402
403   // Posting the request only if dispatcher is not busy!
404   // Executing the request synchronously
405   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
406     PyInterp_Dispatcher::Get()->Exec( new PopupMenuEvent( myInterp, this, theContext, thePopupMenu ) );
407 }
408
409 /*!
410  * Defines the dockable window associated with the module.
411  * To fill the list of windows the correspondind Python module's windows() 
412  * method is called from SALOME_PYQT_Module::init() method.
413  * By default, ObjectBrowser, PythonConsole and LogWindow are provided.
414  */
415 void SALOME_PYQT_Module::windows( QMap<int, int>& mappa ) const
416 {
417   // First clear the output parameters 
418   QMap<int, int>::ConstIterator it;
419   for ( it = myWindowsMap.begin(); it != myWindowsMap.end(); ++it ) {
420     mappa[ it.key() ] = it.data();
421   }
422 }
423
424 /*!
425  * Defines the compatible views which should be opened on module activation.
426  * To fill the list of views the correspondind Python module's views() 
427  * method is called from SALOME_PYQT_Module::init() method.
428  * By default, the list is empty.
429  */
430 void SALOME_PYQT_Module::viewManagers( QStringList& listik ) const
431 {
432   for ( QStringList::ConstIterator it = myViewMgrList.begin(); it != myViewMgrList.end(); ++it ) {
433     listik.append( *it );
434   }
435 }
436
437 /*!
438  * Performs internal initialization
439  * - initializes/gets the Python interpreter (one per study)
440  * - imports the Python module
441  * - passes the workspace widget to the Python module
442  * - calls Python module's initialize() method
443  * - calls Python module's windows() method
444  * - calls Python module's views() method
445  */
446 void SALOME_PYQT_Module::init( CAM_Application* app )
447 {
448   // reset interpreter to NULL
449   myInterp = NULL;
450
451   // get study Id
452   SalomeApp_Application* anApp = dynamic_cast<SalomeApp_Application*>( app );
453   if ( !anApp )
454     return;
455
456   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
457   if ( !aStudy )
458     return;
459   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
460
461   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
462   initInterp( aStudyId );
463   if ( !myInterp ) 
464     return; // Error 
465
466   // import Python GUI module
467   importModule();
468   if ( !myModule )
469     return; // Error 
470  
471 #ifdef __CALL_OLD_METHODS__
472   // call Python module's setWorkspace() method
473   setWorkSpace();
474 #endif // __CALL_OLD_METHODS__
475
476   // then call Python module's initialize() method
477   // ... first get python lock
478   PyLockWrapper aLock = myInterp->GetLockWrapper();
479   // ... (the Python module is already imported)
480   // ... finally call Python module's initialize() method
481   PyObjWrapper res( PyObject_CallMethod( myModule, "initialize", "" ) );
482   if( !res ) {
483     // VSR: this method may not be implemented in Python module
484     // PyErr_Print();
485   }
486   
487   // get the windows list from the Python module by calling windows() method
488   // ... first put default values
489   myWindowsMap.insert( SalomeApp_Application::WT_ObjectBrowser, Qt::DockLeft );
490   myWindowsMap.insert( SalomeApp_Application::WT_PyConsole,     Qt::DockBottom );
491   // VSR: LogWindow is not yet implemented
492   // myWindowsMap.insert( SalomeApp_Application::WT_LogWindow,     Qt::DockBottom );
493
494   PyObjWrapper res1( PyObject_CallMethod( myModule, "windows", "" ) );
495   if( !res1 ) {
496     // VSR: this method may not be implemented in Python module
497     // PyErr_Print();
498   }
499   else {
500     myWindowsMap.clear();
501     if ( PyDict_Check( res1 ) ) {
502       PyObject* key;
503       PyObject* value;
504       int pos = 0;
505       while ( PyDict_Next( res1, &pos, &key, &value ) ) {
506         // parse the return value
507         // it should be a map: {integer:integer}
508         int aKey, aValue;
509         if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
510           aKey   = PyInt_AsLong( key );
511           aValue = PyInt_AsLong( value );
512           myWindowsMap[ aKey ] = aValue;
513         }
514       }
515     }
516   }
517   // get the windows list from the Python module by calling views() method
518   PyObjWrapper res2( PyObject_CallMethod( myModule, "views", "" ) );
519   if( !res2 ) {
520     // VSR: this method may not be implemented in Python module
521     // PyErr_Print();
522   }
523   else {
524     // parse the return value
525     // result can be one string...
526     if ( PyString_Check( res2 ) ) {
527       myViewMgrList.append( PyString_AsString( res2 ) );
528     }
529     // ... or list of strings
530     else if ( PyList_Check( res2 ) ) {
531       int size = PyList_Size( res2 );
532       for ( int i = 0; i < size; i++ ) {
533         PyObject* value = PyList_GetItem( res2, i );
534         if( value && PyString_Check( value ) ) {
535           myViewMgrList.append( PyString_AsString( value ) );
536         }
537       }
538     }
539   }
540 }
541
542 /*!
543  * Performs internal activation: 
544  * - initializes/gets the Python interpreter (one per study)
545  * - imports the Python GUI module
546  * - calls Python module's setSettings() method (obsolete function, used for compatibility with old code)
547  *   or activate() method (for new modules)
548  */
549 void SALOME_PYQT_Module::activate( SUIT_Study* theStudy )
550 {
551   // get study Id
552   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
553   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
554
555   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
556   initInterp( aStudyId );
557   if ( !myInterp ) 
558     return; // Error 
559
560   // import Python GUI module
561   importModule();
562   if ( !myModule )
563     return; // Error 
564  
565   // get python lock
566   PyLockWrapper aLock = myInterp->GetLockWrapper();
567
568 #ifdef __CALL_OLD_METHODS__
569   // call Python module's setSettings() method (obsolete)
570   PyObjWrapper res( PyObject_CallMethod( myModule, "setSettings", "" ) );
571   if( !res ) {
572     // VSR: this method may not be implemented in Python module
573     // PyErr_Print();
574   }
575 #endif // __CALL_OLD_METHODS__
576
577   // call Python module's activate() method (for the new modules)
578   PyObjWrapper res1( PyObject_CallMethod( myModule, "activate", "" ) );
579   if( !res1 ) {
580     // VSR: this method may not be implemented in Python module
581     // PyErr_Print();
582   }
583 }
584
585 /*!
586  * Performs internal deactivation: 
587  * - calls Python module's deactivate() method
588  */
589 void SALOME_PYQT_Module::deactivate( SUIT_Study* theStudy )
590 {
591   // check if the subinterpreter is initialized and Python module is imported
592   if ( !myInterp || !myModule ) {
593     // Error! Python subinterpreter should be initialized and module should be imported first!
594     return;
595   }
596   // then call Python module's deactivate() method
597   PyObjWrapper res( PyObject_CallMethod( myModule, "deactivate", "" ) );
598   if( !res ) {
599     // VSR: this method may not be implemented in Python module
600     // PyErr_Print();
601   }
602 }
603
604 /*!
605  * Called when active the study is actived (user brings its desktop to top)
606  * - initializes/gets the Python interpreter (one per study)
607  * - imports the Python GUI module
608  * - calls Python module's activeStudyChanged() method
609  */
610 void SALOME_PYQT_Module::studyChanged( SUIT_Study* theStudy )
611 {
612   // get study Id
613   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
614   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
615
616   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
617   initInterp( aStudyId );
618   if ( !myInterp ) 
619     return; // Error 
620
621   // import Python GUI module
622   importModule();
623   if ( !myModule )
624     return; // Error 
625  
626   // get python lock
627   PyLockWrapper aLock = myInterp->GetLockWrapper();
628
629   // call Python module's activeStudyChanged() method
630   PyObjWrapper res( PyObject_CallMethod( myModule, "activeStudyChanged", "i", aStudyId ) );
631   if( !res ) {
632     // VSR: this method may not be implemented in Python module
633     // PyErr_Print();
634   }
635 }
636
637 /*!
638  * Get module engine, returns nil var if engine is not found in LifeCycleCORBA
639  */
640 Engines::Component_var SALOME_PYQT_Module::getEngine() const
641 {
642   Engines::Component_var comp = getApp()->lcc()->FindOrLoad_Component( "FactoryServerPy", name( "" ) );
643   return comp;
644 }
645
646 /*!
647  * Get module engine IOR, returns empty string if engine is not found in LifeCycleCORBA
648  */
649 QString SALOME_PYQT_Module::engineIOR() const
650 {
651   if ( !CORBA::is_nil( getEngine() ) )
652     return QString( getApp()->orb()->object_to_string( getEngine() ) );
653   return QString( "" );
654 }
655
656 /*!
657  * Processes context popup menu request
658  * - calls Python module's definePopup(...) method (obsolete function, used for compatibility with old code)
659  *   to define the popup menu context
660  * - parses XML resourses file (if exists) and fills the popup menu with the items)
661  * - calls Python module's customPopup(...) method (obsolete function, used for compatibility with old code)
662  *   to allow module to customize the popup menu
663  * - for new modules calls createPopupMenu() function to allow the modules to build the popup menu
664  *   by using insertItem(...) Qt functions.
665  */
666 void SALOME_PYQT_Module::contextMenu( const QString& theContext, QPopupMenu* thePopupMenu )
667 {
668   // Python interpreter should be initialized and Python module should be
669   // import first
670   if ( !myInterp || !myModule )
671     return;
672   
673   QString aContext( theContext ), aObject( "" ), aParent( "" );
674 #ifdef __CALL_OLD_METHODS__
675   // call definePopup() Python module's function
676   // this is obsolete function, used only for compatibility reasons
677   PyObjWrapper res(PyObject_CallMethod( myModule, 
678                                         "definePopup", 
679                                         "sss",
680                                         aContext.latin1(), 
681                                         aObject.latin1(), 
682                                         aParent.latin1() ) );
683   if( !res ) {
684     // VSR: this method may not be implemented in Python module
685     // PyErr_Print();
686   }
687   else {
688     // parse return value
689     char *co, *ob, *pa;
690     if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
691       aContext = co;
692       aObject  = ob;
693       aParent  = pa;
694     }
695   }
696 #endif // __CALL_OLD_METHODS__
697
698   // first try to create menu via XML parser:
699   // we create popup menus without help of QtxPopupMgr
700   if ( !myXmlHandler )
701     myXmlHandler->createPopup( thePopupMenu, aContext, aParent, aObject );
702
703   PyObjWrapper sipPopup( sipMapCppToSelf( thePopupMenu, sipClass_QPopupMenu ) );
704
705   // then call Python module's createPopupMenu() method (for new modules)
706   PyObjWrapper res1( PyObject_CallMethod( myModule,
707                                           "createPopupMenu",
708                                           "Os",
709                                           sipPopup.get(),
710                                           aContext.latin1() ) );
711   if( !res1 ) {
712     // VSR: this method may not be implemented in Python module
713     // PyErr_Print();
714   }
715
716 #ifdef __CALL_OLD_METHODS__
717   // call customPopup() Python module's function
718   // this is obsolete function, used only for compatibility reasons
719   PyObjWrapper res2( PyObject_CallMethod( myModule,
720                                           "customPopup",
721                                           "Osss",
722                                           sipPopup.get(),
723                                           aContext.latin1(), 
724                                           aObject.latin1(), 
725                                           aParent.latin1() ) );
726   if( !res2 ) {
727     // VSR: this method may not be implemented in Python module
728     // PyErr_Print();
729   }
730 #endif // __CALL_OLD_METHODS__
731 }
732
733 /*!
734  * Processes GUI event
735  * - calls Python module's OnGUIEvent() method
736  */ 
737 void SALOME_PYQT_Module::guiEvent( const int theId )
738 {
739   // Python interpreter should be initialized and Python module should be
740   // import first
741   if ( !myInterp || !myModule )
742     return;
743   
744   PyObjWrapper res( PyObject_CallMethod( myModule, "OnGUIEvent", "i", theId ) );
745   if( !res ) {
746     // Error!
747     PyErr_Print();
748   }
749 }
750
751 /*!
752  *  Initialises python subinterpreter (one per study)
753  */
754 void SALOME_PYQT_Module::initInterp( int theStudyId )
755 {
756   // check study Id
757   if ( !theStudyId ) {
758     // Error! Study Id must not be 0!
759     myInterp = NULL;
760     return;
761   }
762   // try to find the subinterpreter
763   if( myInterpMap.find( theStudyId ) != myInterpMap.end() ) {
764     // found!
765     myInterp = myInterpMap[ theStudyId ];
766     return;
767   }
768   // not found - create a new one!
769   ///////////////////////////////////////////////////////////////////
770   // Attention: the creation of Python interpretor must be protected 
771   // by a C++ Lock because of C threads
772   ///////////////////////////////////////////////////////////////////
773   myInterp = new SALOME_PYQT_PyInterp();
774   myInterp->initialize();
775   myInterpMap[ theStudyId ] = myInterp;
776    
777   // import 'salome' module and call 'salome_init' method;
778   // do it only once on interpreter creation
779   // ... first get python lock
780   PyLockWrapper aLock = myInterp->GetLockWrapper();
781   // ... then import a module
782   PyObjWrapper aMod = PyImport_ImportModule( "salome" );
783   if( !aMod ) {
784     // Error!
785     PyErr_Print();
786     return;
787   }
788   // ... then call a method
789   PyObjWrapper aRes( PyObject_CallMethod( aMod, "salome_init", "" ) );
790   if( !aRes ) {
791     // Error!
792     PyErr_Print();
793     return;
794   }
795 }
796
797 /*!
798  *  Imports Python GUI module and remember the reference to the module
799  *  !!! initInterp() should be called first!!!
800  */
801 void SALOME_PYQT_Module::importModule()
802 {
803   // check if the subinterpreter is initialized
804   if ( !myInterp ) {
805     // Error! Python subinterpreter should be initialized first!
806     myModule = 0;
807     return;
808   }
809   // import Python GUI module and puts it in <myModule> attribute
810   // ... first get python lock
811   PyLockWrapper aLock = myInterp->GetLockWrapper();
812   // ... then import a module
813   QString aMod = QString( name("") ) + "GUI";
814   myModule = PyImport_ImportModule( (char*)( aMod.latin1() ) );
815   if( !myModule ) {
816     // Error!
817     PyErr_Print();
818     return;
819   }
820 }
821
822 /*!
823  *  Calls <module>.setWorkSpace() method with PyQt QWidget object to use with
824  *  interpreter.
825  *  !!! initInterp() and importModule() should be called first!!!
826  */
827 void SALOME_PYQT_Module::setWorkSpace()
828 {
829   // check if the subinterpreter is initialized and Python module is imported
830   if ( !myInterp || !myModule ) {
831     // Error! Python subinterpreter should be initialized and module should be imported first!
832     return;
833   }
834
835   // call setWorkspace() method
836   // ... first get python lock
837   PyLockWrapper aLock = myInterp->GetLockWrapper();
838
839   // ... then try to import SalomePyQt module. If it's not possible don't go on.
840   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
841   if( !aQtModule ) {
842     // Error!
843     PyErr_Print();
844     return;
845   }  
846 #ifdef __CALL_OLD_METHODS__
847   // ... then get workspace object
848   STD_MDIDesktop* aDesktop = dynamic_cast<STD_MDIDesktop*>( getApp()->desktop() );
849   if ( aDesktop && aDesktop->workspace() ) {
850     QWidget* aWorkspace = aDesktop->workspace();
851     PyObjWrapper pyws( sipMapCppToSelfSubClass( aWorkspace, sipClass_QWidget ) );
852     // ... and finally call Python module's setWorkspace() method (obsolete)
853     PyObjWrapper res( PyObject_CallMethod( myModule, "setWorkSpace", "O", pyws.get() ) );
854     if( !res ) {
855       // VSR: this method may not be implemented in Python module
856       // PyErr_Print();
857       return;
858     }
859   }
860 #endif // __CALL_OLD_METHODS__
861 }
862
863 /*!
864  * Adds an action into private action list [internal usage]
865  */
866 void SALOME_PYQT_Module::addAction( const PyQtGUIAction type, QAction* action )
867 {
868   switch ( type ) {
869   case PYQT_ACTION_MENU:
870     myMenuActionList.append( action );
871     break;
872   case PYQT_ACTION_TOOLBAL:
873     myToolbarActionList.append( action );
874     break;
875   case PYQT_ACTION_POPUP:
876     myPopupActionList.append( action );
877     break;
878   }
879 }
880
881 //=============================================================================
882 // SALOME_PYQT_XmlHandler class implementation
883 //=============================================================================
884
885 // gets an tag name for the dom element [ static ]
886 // returns an empty string if the element does not have tag name
887 static QString tagName( const QDomElement& element ) {
888  return element.tagName().stripWhiteSpace();
889 }
890
891 // gets an attribute by it's name for the dom element [ static ]
892 // returns an empty string if the element does not have such attribute
893 static QString attribute( const QDomElement& element, const QString& attName ) {
894   return element.attribute( attName ).stripWhiteSpace();
895 }
896
897 // checks the given value for the boolean value [ static ]
898 // returns TRUE if string is "true", "yes" or "1"
899 static bool checkBool( const QString& value ) {
900   return ( value == "true" || value == "yes" || value == "1" );
901 }
902
903 // checks the given value for the integer value [ static ]
904 // returns -1 if item is empty or presents and invalid number
905 static int checkInt( const QString& value ) 
906 {
907   return value.isEmpty() ? -1 : value.toInt();
908 }
909
910 /*!
911  * Constructor
912  */
913 SALOME_PYQT_XmlHandler::SALOME_PYQT_XmlHandler( SALOME_PYQT_Module* module, const QString& fileName ) 
914      : myModule( module )
915 {
916   QFile aFile( fileName );
917   if ( !aFile.open( IO_ReadOnly ) )
918     return;
919   if ( !myDoc.setContent( &aFile ) ) {
920       aFile.close();
921       return;
922   }
923   aFile.close();
924 }
925
926 /*!
927   Called by SALOME_PYQT_Module::initialize() in order to create actions 
928   (menus, toolbars, popup menus)
929  */
930 void SALOME_PYQT_XmlHandler::createActions()
931 {
932   // get document element
933   QDomElement aDocElem = myDoc.documentElement();
934
935   // get main menu actions
936   QDomNodeList aMenuList = aDocElem.elementsByTagName( "menu-item" );
937   for ( int i = 0; i < aMenuList.count(); i++ ) {
938     QDomNode n = aMenuList.item( i );
939     createMenu( n );
940   }
941
942   // create toolbars actions
943   QDomNodeList aToolsList = aDocElem.elementsByTagName( "toolbar" );
944   for ( int i = 0; i < aToolsList.count(); i++ ) {
945     QDomNode n = aToolsList.item( i );
946     createToolBar( n );
947   }
948 }
949
950 /*!
951  *  Creates popup menu
952  */
953 void SALOME_PYQT_XmlHandler::createPopup( QPopupMenu*    menu, 
954                                           const QString& context, 
955                                           const QString& parent, 
956                                           const QString& object )
957 {
958   // get document element
959   QDomElement aDocElem = myDoc.documentElement();
960
961   // get popup menus actions
962   QDomNodeList aPopupList = aDocElem.elementsByTagName( "popupmenu" );
963   for ( int i = 0; i < aPopupList.count(); i++ ) {
964     QDomNode n = aPopupList.item( i );
965     if ( !n.isNull() && n.isElement() ) {
966       QDomElement e = n.toElement();
967       QString lab = attribute( e, "label-id"   );
968       QString ctx = attribute( e, "context-id" );
969       QString prt = attribute( e, "parent-id"  );
970       QString obj = attribute( e, "object-id"  );
971       if ( ctx == context && prt == parent && obj == object )  {
972         insertPopupItems( n, menu );
973         break;
974       }
975     }
976   }
977 }
978
979 /*!
980   Create main menu with child actions
981  */
982 void SALOME_PYQT_XmlHandler::createMenu( QDomNode& parentNode, const int parentMenuId )
983 {
984   if ( !myModule )
985     return;
986   
987   if ( parentNode.isNull() )
988     return;
989
990   QDomElement parentElement = parentNode.toElement(); 
991   if ( !parentElement.isNull() ) {
992     QString plabel = attribute( parentElement, "label-id" );
993     int     pid    = checkInt( attribute( parentElement, "item-id" ) );
994     int     ppos   = checkInt( attribute( parentElement, "pos-id" ) );
995     if ( !plabel.isEmpty() ) {
996       // create menu
997       int menuId = myModule->createMenu( plabel,         // label
998                                          parentMenuId,   // parent menu ID, should be -1 for main menu
999                                          pid,            // ID
1000                                          100,            // group ID
1001                                          ppos );         // position
1002       QDomNode node = parentNode.firstChild();
1003       while ( !node.isNull() ) {
1004         if ( node.isElement() ) {
1005           QDomElement elem = node.toElement();
1006           QString aTagName = tagName( elem );
1007           if ( aTagName == "popup-item" ) {
1008             int     id      = checkInt( attribute( elem, "item-id" ) );
1009             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1010             QString label   = attribute( elem, "label-id" );
1011             QString icon    = attribute( elem, "icon-id" );
1012             QString tooltip = attribute( elem, "tooltip-id" );
1013             QString accel   = attribute( elem, "accel-id" );
1014             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
1015             QString execute = attribute( elem, "execute-action" );               // not used
1016
1017             QIconSet anIcon;
1018             if ( !icon.isEmpty() ) {
1019               QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1020               if ( !pixmap.isNull() )
1021                 anIcon = QIconSet( pixmap );
1022             }
1023
1024             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1025             // also check if the action with given ID is already created
1026             if ( id != -1 && !myModule->action( SALOME_PYQT_Module::PYQT_ACTION_MENU + id ) ) {
1027               // little trick to have several actions with same ID for menus and toolbars
1028               id = SALOME_PYQT_Module::PYQT_ACTION_MENU + id;
1029               // create menu action
1030               QAction* action = myModule->createAction( id,                               // ID
1031                                                         tooltip,                          // tooltip
1032                                                         anIcon,                           // icon
1033                                                         label,                            // menu text
1034                                                         tooltip,                          // status-bar text
1035                                                         QKeySequence( accel ),            // keyboard accelerator
1036                                                         myModule->getApp()->desktop(),    // desktop
1037                                                         toggle,                           // toogled action
1038                                                         myModule,                         // receiver
1039                                                         SLOT( onGUIEvent() ) );           // slot
1040               myModule->addAction( SALOME_PYQT_Module::PYQT_ACTION_MENU, action );
1041               myModule->createMenu( action, menuId, -1, 100, pos );
1042             }
1043           }
1044           else if ( aTagName == "submenu" ) {
1045             // create sub-menu
1046             createMenu( node, menuId );
1047           }
1048           else if ( aTagName == "separator" ) {
1049             // create menu separator
1050             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1051             QAction* action = myModule->separator();
1052             myModule->createMenu( action, menuId, -1, 100, pos );
1053           }
1054         }
1055         node = node.nextSibling();
1056       }
1057     }
1058   }
1059 }
1060
1061 /*!
1062   Create a toolbar with child actions
1063  */
1064 void SALOME_PYQT_XmlHandler::createToolBar( QDomNode& parentNode )
1065 {
1066   if ( !myModule )
1067     return;
1068   
1069   if ( parentNode.isNull() )
1070     return;
1071
1072   QDomElement parentElement = parentNode.toElement(); 
1073   if ( !parentElement.isNull() ) {
1074     QString aLabel = attribute( parentElement, "label-id" );
1075     if ( !aLabel.isEmpty() ) {
1076       // create toolbar
1077       int tbId = myModule->createTool( aLabel );
1078       QDomNode node = parentNode.firstChild();
1079       while ( !node.isNull() ) {
1080         if ( node.isElement() ) {
1081           QDomElement elem = node.toElement();
1082           QString aTagName = tagName( elem );
1083           if ( aTagName == "toolbutton-item" ) {
1084             int     id      = checkInt( attribute( elem, "item-id" ) );
1085             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1086             QString label   = attribute( elem, "label-id" );
1087             QString icon    = attribute( elem, "icon-id" );
1088             QString tooltip = attribute( elem, "tooltip-id" );
1089             QString accel   = attribute( elem, "accel-id" );
1090             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
1091             QString execute = attribute( elem, "execute-action" );               // not used
1092
1093             QIconSet anIcon;
1094             if ( !icon.isEmpty() ) {
1095               QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1096               if ( !pixmap.isNull() )
1097                 anIcon = QIconSet( pixmap );
1098             }
1099
1100             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1101             // also check if the action with given ID is already created
1102             if ( id != -1 && !myModule->action( SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL + id ) ) {
1103               // little trick to have several actions with same ID for menus and toolbars
1104               id = SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL + id;
1105               // create toolbar action
1106               QAction* action = myModule->createAction( id,                               // ID
1107                                                         tooltip,                          // tooltip
1108                                                         anIcon,                           // icon
1109                                                         label,                            // menu text
1110                                                         tooltip,                          // status-bar text
1111                                                         QKeySequence( accel ),            // keyboard accelerator
1112                                                         myModule->getApp()->desktop(),    // desktop
1113                                                         toggle,                           // toogled action
1114                                                         myModule,                         // receiver
1115                                                         SLOT( onGUIEvent() ) );           // slot
1116               myModule->addAction( SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL, action );
1117               myModule->createTool( action, tbId, -1, pos );
1118             }
1119           }
1120           else if ( aTagName == "separatorTB" ) {
1121             // create toolbar separator
1122             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1123             QAction* action = myModule->separator();
1124             myModule->createTool( action, tbId, -1, pos );
1125           }
1126         }
1127         node = node.nextSibling();
1128       }
1129     }
1130   }      
1131 }
1132
1133 void SALOME_PYQT_XmlHandler::insertPopupItems( QDomNode& parentNode, QPopupMenu* menu )
1134 {
1135   if ( !myModule )
1136     return;
1137   
1138   if ( parentNode.isNull() )
1139     return;
1140
1141   // we create popup menus without help of QtxPopupMgr
1142   QDomNode node = parentNode.firstChild();
1143   while ( !node.isNull() ) {
1144     if ( node.isElement() ) {
1145       QDomElement elem = node.toElement();
1146       QString aTagName = tagName( elem );
1147       if ( aTagName == "popup-item" ) {
1148         // insert a command item
1149         int     id      = checkInt( attribute( elem, "item-id" ) );
1150         int     pos     = checkInt( attribute( elem, "pos-id" ) );
1151         QString label   = attribute( elem, "label-id" );
1152         QString icon    = attribute( elem, "icon-id" );
1153         QString tooltip = attribute( elem, "tooltip-id" );                   // not used
1154         QString accel   = attribute( elem, "accel-id" );
1155         bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );       // not used
1156         QString execute = attribute( elem, "execute-action" );               // not used
1157
1158         QIconSet anIcon;
1159         if ( !icon.isEmpty() ) {
1160           QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1161           if ( !pixmap.isNull() )
1162             anIcon = QIconSet( pixmap );
1163         }
1164         
1165         // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1166         // also check if the action with given ID is already created
1167         if ( id != -1 ) {
1168           menu->insertItem( anIcon, label, myModule, SLOT( onGUIEvent() ), QKeySequence( accel ), id, pos );
1169         }
1170       }
1171       else if ( aTagName == "submenu" ) {
1172         // create sub-menu
1173         int     id    = checkInt( attribute( elem, "item-id" ) );
1174         int     pos   = checkInt( attribute( elem, "pos-id" ) );
1175         QString label = attribute( elem, "label-id" );
1176         QString icon    = attribute( elem, "icon-id" );
1177
1178         QIconSet anIcon;
1179         if ( !icon.isEmpty() ) {
1180           QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1181           if ( !pixmap.isNull() )
1182             anIcon = QIconSet( pixmap );
1183         }
1184
1185         QPopupMenu* newPopup = new QPopupMenu( menu, label );
1186         menu->insertItem( anIcon, label, newPopup, id, pos );
1187         insertPopupItems( node, newPopup );
1188       }
1189       else if ( aTagName == "separator" ) {
1190         // create menu separator
1191         int     pos     = checkInt( attribute( elem, "pos-id" ) );
1192         menu->insertSeparator( pos );
1193       }
1194     }
1195     node = node.nextSibling();
1196   }
1197 }