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