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