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