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