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