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