Salome HOME
Avoid printing error information if onGUIEvent() method is not implented in the Pytho...
[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   MESSAGE( "SALOME_PYQT_Module::onGUIEvent: id = " << id );
318
319   // perform synchronous request to Python event dispatcher
320   class GUIEvent : public PyInterp_LockRequest
321   {
322   public:
323     GUIEvent( PyInterp_base*      _py_interp, 
324               SALOME_PYQT_Module* _obj,
325               int                 _id ) 
326       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
327         myId    ( _id  ),
328         myObj   ( _obj ) {}
329     
330   protected:
331     virtual void execute()
332     {
333       myObj->guiEvent( myId );
334     }
335
336   private:
337     int                 myId;
338     SALOME_PYQT_Module* myObj;
339   };
340
341   // Posting the request
342   PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
343 }
344
345 /*!
346  * Processes GUI action (from context popup menu, only for XML-based actions!)
347  */
348 void SALOME_PYQT_Module::onGUIEvent( int id ) 
349 {
350   // perform synchronous request to Python event dispatcher
351   class GUIEvent : public PyInterp_LockRequest
352   {
353   public:
354     GUIEvent( PyInterp_base*      _py_interp, 
355               SALOME_PYQT_Module* _obj,
356               int                 _id ) 
357       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
358         myId    ( _id  ),
359         myObj   ( _obj ) {}
360     
361   protected:
362     virtual void execute()
363     {
364       myObj->guiEvent( myId );
365     }
366
367   private:
368     int                 myId;
369     SALOME_PYQT_Module* myObj;
370   };
371
372   // Posting the request
373   PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
374 }
375
376 /*! 
377   Context popup menu request.
378   Called when user activates popup menu in some window (view, object browser, etc).
379   */
380 void SALOME_PYQT_Module::contextMenuPopup( const QString& theContext, QPopupMenu* thePopupMenu, QString& /*title*/ )
381 {
382   MESSAGE( "SALOME_PYQT_Module::contextMenuPopup : " << theContext.latin1() );
383   // perform synchronous request to Python event dispatcher
384   class PopupMenuEvent : public PyInterp_LockRequest
385   {
386   public:
387     PopupMenuEvent( PyInterp_base*     _py_interp, 
388                     SALOME_PYQT_Module* _obj,
389                     const QString&      _context, 
390                     QPopupMenu*        _popup ) 
391       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
392         myContext( _context ), 
393         myPopup  ( _popup  ),
394         myObj    ( _obj )   {}
395     
396   protected:
397     virtual void execute()
398     {
399       myObj->contextMenu( myContext, myPopup );
400     }
401
402   private:
403     SALOME_PYQT_Module* myObj;
404     QString             myContext;
405     QPopupMenu*         myPopup;
406   };
407
408   // Posting the request only if dispatcher is not busy!
409   // Executing the request synchronously
410   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
411     PyInterp_Dispatcher::Get()->Exec( new PopupMenuEvent( myInterp, this, theContext, thePopupMenu ) );
412 }
413
414 /*!
415  * Defines the dockable window associated with the module.
416  * To fill the list of windows the correspondind Python module's windows() 
417  * method is called from SALOME_PYQT_Module::init() method.
418  * By default, ObjectBrowser, PythonConsole and LogWindow are provided.
419  */
420 void SALOME_PYQT_Module::windows( QMap<int, int>& mappa ) const
421 {
422   // First clear the output parameters 
423   QMap<int, int>::ConstIterator it;
424   for ( it = myWindowsMap.begin(); it != myWindowsMap.end(); ++it ) {
425     mappa[ it.key() ] = it.data();
426   }
427 }
428
429 /*!
430  * Defines the compatible views which should be opened on module activation.
431  * To fill the list of views the correspondind Python module's views() 
432  * method is called from SALOME_PYQT_Module::init() method.
433  * By default, the list is empty.
434  */
435 void SALOME_PYQT_Module::viewManagers( QStringList& listik ) const
436 {
437   for ( QStringList::ConstIterator it = myViewMgrList.begin(); it != myViewMgrList.end(); ++it ) {
438     listik.append( *it );
439   }
440 }
441
442 /*!
443  * Performs internal initialization
444  * - initializes/gets the Python interpreter (one per study)
445  * - imports the Python module
446  * - passes the workspace widget to the Python module
447  * - calls Python module's initialize() method
448  * - calls Python module's windows() method
449  * - calls Python module's views() method
450  */
451 void SALOME_PYQT_Module::init( CAM_Application* app )
452 {
453   // reset interpreter to NULL
454   myInterp = NULL;
455
456   // get study Id
457   SalomeApp_Application* anApp = dynamic_cast<SalomeApp_Application*>( app );
458   if ( !anApp )
459     return;
460
461   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
462   if ( !aStudy )
463     return;
464   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
465
466   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
467   initInterp( aStudyId );
468   if ( !myInterp ) 
469     return; // Error 
470
471   // import Python GUI module
472   importModule();
473   if ( !myModule )
474     return; // Error 
475  
476   myInitModule = this;
477
478   if ( IsCallOldMethods ) { // __CALL_OLD_METHODS__
479     // call Python module's setWorkspace() method
480     setWorkSpace();
481   }                         //__CALL_OLD_METHODS__
482
483   // then call Python module's initialize() method
484   // ... first get python lock
485   PyLockWrapper aLock = myInterp->GetLockWrapper();
486   // ... (the Python module is already imported)
487   // ... finally call Python module's initialize() method
488   PyObjWrapper res( PyObject_CallMethod( myModule, "initialize", "" ) );
489   if( !res ) {
490     // VSR: this method may not be implemented in Python module
491     // PyErr_Print();
492     PyErr_Clear();
493   }
494   
495   // get the windows list from the Python module by calling windows() method
496   // ... first put default values
497   myWindowsMap.insert( SalomeApp_Application::WT_ObjectBrowser, Qt::DockLeft );
498   myWindowsMap.insert( SalomeApp_Application::WT_PyConsole,     Qt::DockBottom );
499   // VSR: LogWindow is not yet implemented
500   // myWindowsMap.insert( SalomeApp_Application::WT_LogWindow,     Qt::DockBottom );
501
502   PyObjWrapper res1( PyObject_CallMethod( myModule, "windows", "" ) );
503   if( !res1 ) {
504     // VSR: this method may not be implemented in Python module
505     // PyErr_Print();
506     PyErr_Clear();
507   }
508   else {
509     myWindowsMap.clear();
510     if ( PyDict_Check( res1 ) ) {
511       PyObject* key;
512       PyObject* value;
513       int pos = 0;
514       while ( PyDict_Next( res1, &pos, &key, &value ) ) {
515         // parse the return value
516         // it should be a map: {integer:integer}
517         int aKey, aValue;
518         if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
519           aKey   = PyInt_AsLong( key );
520           aValue = PyInt_AsLong( value );
521           myWindowsMap[ aKey ] = aValue;
522         }
523       }
524     }
525   }
526   // get the windows list from the Python module by calling views() method
527   PyObjWrapper res2( PyObject_CallMethod( myModule, "views", "" ) );
528   if( !res2 ) {
529     // VSR: this method may not be implemented in Python module
530     // PyErr_Print();
531     PyErr_Clear();
532   }
533   else {
534     // parse the return value
535     // result can be one string...
536     if ( PyString_Check( res2 ) ) {
537       myViewMgrList.append( PyString_AsString( res2 ) );
538     }
539     // ... or list of strings
540     else if ( PyList_Check( res2 ) ) {
541       int size = PyList_Size( res2 );
542       for ( int i = 0; i < size; i++ ) {
543         PyObject* value = PyList_GetItem( res2, i );
544         if( value && PyString_Check( value ) ) {
545           myViewMgrList.append( PyString_AsString( value ) );
546         }
547       }
548     }
549   }
550   myInitModule = 0;
551 }
552
553 /*!
554  * Performs internal activation: 
555  * - initializes/gets the Python interpreter (one per study)
556  * - imports the Python GUI module
557  * - calls Python module's setSettings() method (obsolete function, used for compatibility with old code)
558  *   or activate() method (for new modules)
559  */
560 void SALOME_PYQT_Module::activate( SUIT_Study* theStudy )
561 {
562   // get study Id
563   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
564   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
565
566   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
567   initInterp( aStudyId );
568   if ( !myInterp ) 
569     return; // Error 
570
571   // import Python GUI module
572   importModule();
573   if ( !myModule )
574     return; // Error 
575  
576   // get python lock
577   PyLockWrapper aLock = myInterp->GetLockWrapper();
578
579   if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
580     // call Python module's setSettings() method (obsolete)
581     PyObjWrapper res( PyObject_CallMethod( myModule, "setSettings", "" ) );
582     if( !res ) {
583       // VSR: this method may not be implemented in Python module
584       // PyErr_Print();
585       PyErr_Clear();
586     }
587   }                         //__CALL_OLD_METHODS__
588
589   // call Python module's activate() method (for the new modules)
590   PyObjWrapper res1( PyObject_CallMethod( myModule, "activate", "" ) );
591   if( !res1 ) {
592     // VSR: this method may not be implemented in Python module
593     // PyErr_Print();
594     PyErr_Clear();
595   }
596 }
597
598 /*!
599  * Performs internal deactivation: 
600  * - calls Python module's deactivate() method
601  */
602 void SALOME_PYQT_Module::deactivate( SUIT_Study* theStudy )
603 {
604   // check if the subinterpreter is initialized and Python module is imported
605   if ( !myInterp || !myModule ) {
606     // Error! Python subinterpreter should be initialized and module should be imported first!
607     return;
608   }
609   // then call Python module's deactivate() method
610   PyObjWrapper res( PyObject_CallMethod( myModule, "deactivate", "" ) );
611   if( !res ) {
612     // VSR: this method may not be implemented in Python module
613     // PyErr_Print();
614     PyErr_Clear();
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     PyErr_Clear();
649   }
650 }
651
652 /*!
653  * Get module engine, returns nil var if engine is not found in LifeCycleCORBA
654  */
655 Engines::Component_var SALOME_PYQT_Module::getEngine() const
656 {
657   Engines::Component_var comp;
658   // temporary solution
659   try {
660     comp = getApp()->lcc()->FindOrLoad_Component( "FactoryServerPy", name( "" ) );
661   }
662   catch (CORBA::Exception&) {
663   }
664   return comp;
665 }
666
667 /*!
668  * Get module engine IOR, returns empty string if engine is not found in LifeCycleCORBA
669  */
670 QString SALOME_PYQT_Module::engineIOR() const
671 {
672   if ( !CORBA::is_nil( getEngine() ) )
673     return QString( getApp()->orb()->object_to_string( getEngine() ) );
674   return QString( "" );
675 }
676
677 /*! 
678  * Called when study desktop is activated.
679  * Used for notifying about changing of the active study.
680  */
681 void SALOME_PYQT_Module::studyActivated()
682 {
683   // StudyChangedReq: request class for internal studyChanged() operation
684   class StudyChangedReq : public PyInterp_Request
685   {
686   public:
687     StudyChangedReq( SUIT_Study*         _study, 
688                      SALOME_PYQT_Module* _obj ) 
689       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
690         myStudy ( _study ),
691         myObj   ( _obj   ) {}
692     
693   protected:
694     virtual void execute()
695     {
696       myObj->studyChanged( myStudy );
697     }
698
699   private:
700     SUIT_Study*         myStudy;
701     SALOME_PYQT_Module* myObj;
702   };
703
704   // Posting the request
705   PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( application()->activeStudy(), this ) );
706 }
707
708 /*!
709  * Processes context popup menu request
710  * - calls Python module's definePopup(...) method (obsolete function, used for compatibility with old code)
711  *   to define the popup menu context
712  * - parses XML resourses file (if exists) and fills the popup menu with the items)
713  * - calls Python module's customPopup(...) method (obsolete function, used for compatibility with old code)
714  *   to allow module to customize the popup menu
715  * - for new modules calls createPopupMenu() function to allow the modules to build the popup menu
716  *   by using insertItem(...) Qt functions.
717  */
718 void SALOME_PYQT_Module::contextMenu( const QString& theContext, QPopupMenu* thePopupMenu )
719 {
720   // Python interpreter should be initialized and Python module should be
721   // import first
722   if ( !myInterp || !myModule )
723     return;
724   
725   QString aContext( theContext ), aObject( "" ), aParent( "" );
726
727   if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
728     // call definePopup() Python module's function
729     // this is obsolete function, used only for compatibility reasons
730     PyObjWrapper res(PyObject_CallMethod( myModule, 
731                                           "definePopup", 
732                                           "sss",
733                                           aContext.latin1(), 
734                                           aObject.latin1(), 
735                                           aParent.latin1() ) );
736     if( !res ) {
737       // VSR: this method may not be implemented in Python module
738       // PyErr_Print();
739       PyErr_Clear();
740     }
741     else {
742       // parse return value
743       char *co, *ob, *pa;
744       if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
745         aContext = co;
746         aObject  = ob;
747         aParent  = pa;
748       }
749     }
750   }                        //__CALL_OLD_METHODS__
751
752   // first try to create menu via XML parser:
753   // we create popup menus without help of QtxPopupMgr
754   if ( myXmlHandler )
755     myXmlHandler->createPopup( thePopupMenu, aContext, aParent, aObject );
756
757   PyObjWrapper sipPopup( sipBuildResult( 0, "M", thePopupMenu, sipClass_QPopupMenu ) );
758
759   // then call Python module's createPopupMenu() method (for new modules)
760   PyObjWrapper res1( PyObject_CallMethod( myModule,
761                                           "createPopupMenu",
762                                           "Os",
763                                           sipPopup.get(),
764                                           aContext.latin1() ) );
765   if( !res1 ) {
766     // VSR: this method may not be implemented in Python module
767     // PyErr_Print();
768     PyErr_Clear();
769   }
770
771   if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
772     // call customPopup() Python module's function
773     // this is obsolete function, used only for compatibility reasons
774     PyObjWrapper res2( PyObject_CallMethod( myModule,
775                                             "customPopup",
776                                             "Osss",
777                                             sipPopup.get(),
778                                             aContext.latin1(), 
779                                             aObject.latin1(), 
780                                             aParent.latin1() ) );
781     if( !res2 ) {
782       // VSR: this method may not be implemented in Python module
783       // PyErr_Print();
784       PyErr_Clear();
785     }
786   }                        //__CALL_OLD_METHODS__
787 }
788
789 /*!
790  * Processes GUI event
791  * - calls Python module's OnGUIEvent() method
792  */ 
793 void SALOME_PYQT_Module::guiEvent( const int theId )
794 {
795   // Python interpreter should be initialized and Python module should be
796   // import first
797   if ( !myInterp || !myModule )
798     return;
799   
800   PyObjWrapper res( PyObject_CallMethod( myModule, "OnGUIEvent", "i", theId ) );
801   if( !res ) {
802     // VSR: this method may not be implemented in Python module
803     // PyErr_Print();
804     PyErr_Clear();
805   }
806 }
807
808 /*!
809  *  Initialises python subinterpreter (one per study)
810  */
811 void SALOME_PYQT_Module::initInterp( int theStudyId )
812 {
813   // check study Id
814   if ( !theStudyId ) {
815     // Error! Study Id must not be 0!
816     myInterp = NULL;
817     return;
818   }
819   // try to find the subinterpreter
820   if( myInterpMap.find( theStudyId ) != myInterpMap.end() ) {
821     // found!
822     myInterp = myInterpMap[ theStudyId ];
823     return;
824   }
825   // not found - create a new one!
826   ///////////////////////////////////////////////////////////////////
827   // Attention: the creation of Python interpretor must be protected 
828   // by a C++ Lock because of C threads
829   ///////////////////////////////////////////////////////////////////
830   myInterp = new SALOME_PYQT_PyInterp();
831   myInterp->initialize();
832   myInterpMap[ theStudyId ] = myInterp;
833    
834   // import 'salome' module and call 'salome_init' method;
835   // do it only once on interpreter creation
836   // ... first get python lock
837   PyLockWrapper aLock = myInterp->GetLockWrapper();
838   // ... then import a module
839   PyObjWrapper aMod = PyImport_ImportModule( "salome" );
840   if( !aMod ) {
841     // Error!
842     PyErr_Print();
843     return;
844   }
845   // ... then call a method
846   PyObjWrapper aRes( PyObject_CallMethod( aMod, "salome_init", "" ) );
847   if( !aRes ) {
848     // Error!
849     PyErr_Print();
850     return;
851   }
852 }
853
854 /*!
855  *  Imports Python GUI module and remember the reference to the module
856  *  !!! initInterp() should be called first!!!
857  */
858 void SALOME_PYQT_Module::importModule()
859 {
860   // check if the subinterpreter is initialized
861   if ( !myInterp ) {
862     // Error! Python subinterpreter should be initialized first!
863     myModule = 0;
864     return;
865   }
866   // import Python GUI module and puts it in <myModule> attribute
867   // ... first get python lock
868   PyLockWrapper aLock = myInterp->GetLockWrapper();
869   // ... then import a module
870   QString aMod = QString( name("") ) + "GUI";
871   myModule = PyImport_ImportModule( (char*)( aMod.latin1() ) );
872   if( !myModule ) {
873     // Error!
874     PyErr_Print();
875     return;
876   }
877 }
878
879 /*!
880  *  Calls <module>.setWorkSpace() method with PyQt QWidget object to use with
881  *  interpreter.
882  *  !!! initInterp() and importModule() should be called first!!!
883  */
884 void SALOME_PYQT_Module::setWorkSpace()
885 {
886   // check if the subinterpreter is initialized and Python module is imported
887   if ( !myInterp || !myModule ) {
888     // Error! Python subinterpreter should be initialized and module should be imported first!
889     return;
890   }
891
892   // call setWorkspace() method
893   // ... first get python lock
894   PyLockWrapper aLock = myInterp->GetLockWrapper();
895
896   // ... then try to import SalomePyQt module. If it's not possible don't go on.
897   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
898   if( !aQtModule ) {
899     // Error!
900     PyErr_Print();
901     return;
902   }  
903
904   if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
905     // ... then get workspace object
906     QWidget* aWorkspace = 0;
907     if ( getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
908       STD_MDIDesktop* aDesktop = dynamic_cast<STD_MDIDesktop*>( getApp()->desktop() );
909       if ( aDesktop )
910         aWorkspace = aDesktop->workspace();
911     }
912     else if ( getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
913       STD_TabDesktop* aDesktop = dynamic_cast<STD_TabDesktop*>( getApp()->desktop() );
914       if ( aDesktop )
915         aWorkspace = aDesktop->workstack();
916     }
917     PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget ) );
918     // ... and finally call Python module's setWorkspace() method (obsolete)
919     PyObjWrapper res( PyObject_CallMethod( myModule, "setWorkSpace", "O", pyws.get() ) );
920     if( !res ) {
921       // VSR: this method may not be implemented in Python module
922       // PyErr_Print();
923       PyErr_Clear();
924     }
925   }                         //__CALL_OLD_METHODS__
926 }
927
928 /*!
929  * Adds an action into private action list [internal usage]
930  */
931 void SALOME_PYQT_Module::addAction( const PyQtGUIAction type, QAction* action )
932 {
933   switch ( type ) {
934   case PYQT_ACTION_MENU:
935     myMenuActionList.append( action );
936     break;
937   case PYQT_ACTION_TOOLBAL:
938     myToolbarActionList.append( action );
939     break;
940   case PYQT_ACTION_POPUP:
941     myPopupActionList.append( action );
942     break;
943   }
944 }
945
946
947 /*!
948  * The next methods just call the parent implementation.
949  * This is done to open protected methods from CAM_Module class.
950 */
951 int SALOME_PYQT_Module::createTool( const QString& name )
952 {
953   return SalomeApp_Module::createTool( name );
954 }
955 int SALOME_PYQT_Module::createTool( const int id, const int tBar, const int idx )
956 {
957   return SalomeApp_Module::createTool( id, tBar, idx );
958 }
959 int SALOME_PYQT_Module::createTool( const int id, const QString& tBar, const int idx )
960 {
961   return SalomeApp_Module::createTool( id, tBar, idx );
962 }
963 int SALOME_PYQT_Module::createTool( QAction* a, const int tBar, const int id, const int idx )
964 {
965   return SalomeApp_Module::createTool( a, tBar, id, idx );
966 }
967 int SALOME_PYQT_Module::createTool( QAction* a, const QString& tBar, const int id, const int idx )
968 {
969   return SalomeApp_Module::createTool( a, tBar, id, idx );
970 }
971 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const int menu, const int id, const int group, const int idx )
972 {
973   return SalomeApp_Module::createMenu( subMenu, menu, id, group, idx );
974 }
975 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const QString& menu, const int id, const int group, const int idx )
976 {
977   return SalomeApp_Module::createMenu( subMenu, menu, id, group, idx );
978 }
979 int SALOME_PYQT_Module::createMenu( const int id, const int menu, const int group, const int idx )
980 {
981   return SalomeApp_Module::createMenu( id, menu, group, idx );
982 }
983 int SALOME_PYQT_Module::createMenu( const int id, const QString& menu, const int group, const int idx )
984 {
985   return SalomeApp_Module::createMenu( id, menu, group, idx );
986 }
987 int SALOME_PYQT_Module::createMenu( QAction* a, const int menu, const int id, const int group, const int idx )
988 {
989   return SalomeApp_Module::createMenu( a, menu, id, group, idx );
990 }
991 int SALOME_PYQT_Module::createMenu( QAction* a, const QString& menu, const int id, const int group, const int idx )
992 {
993   return SalomeApp_Module::createMenu( a, menu, id, group, idx );
994 }
995 QAction* SALOME_PYQT_Module::createSeparator()
996 {
997   return SalomeApp_Module::separator();
998 }
999 QAction* SALOME_PYQT_Module::action( const int id ) const
1000 {
1001   QAction* a = SalomeApp_Module::action( id );
1002   if ( !a ) // try own action map for menu items
1003     a = SalomeApp_Module::action( id + PYQT_ACTION_MENU );
1004   if ( !a ) // try own action map for toolbar items
1005     a = SalomeApp_Module::action( id + PYQT_ACTION_TOOLBAL );
1006   if ( !a ) // try own action map for popup items
1007     a = SalomeApp_Module::action( id + PYQT_ACTION_POPUP );
1008   return a;
1009 }
1010 int SALOME_PYQT_Module::actionId( const QAction* a ) const
1011 {
1012   int id = SalomeApp_Module::actionId( a );
1013   if ( myMenuActionList.contains( a ) )    // check own action map for menu items
1014     id -= PYQT_ACTION_MENU;
1015   if ( myToolbarActionList.contains( a ) ) // check own action map for toolbar items
1016     id -= PYQT_ACTION_TOOLBAL;
1017   if ( myPopupActionList.contains( a ) )   // check own action map for popup items
1018     id -= PYQT_ACTION_POPUP;
1019   return id;
1020 }
1021 QAction* SALOME_PYQT_Module::createAction( const int id, const QString& text, const QString& icon, 
1022                                            const QString& menu, const QString& tip, const int key,
1023                                            const bool toggle )
1024 {
1025   QIconSet anIcon;
1026   if ( !icon.isEmpty() ) {
1027     QPixmap pixmap  = getApp()->resourceMgr()->loadPixmap( name(""), tr( icon ) );
1028     if ( !pixmap.isNull() )
1029       anIcon = QIconSet( pixmap );
1030   }
1031   return SalomeApp_Module::createAction( id, text, anIcon, menu, tip, key, getApp()->desktop(), toggle, this, SLOT( onGUIEvent() ) );
1032 }
1033
1034
1035 //=============================================================================
1036 // SALOME_PYQT_XmlHandler class implementation
1037 //=============================================================================
1038
1039 // gets an tag name for the dom element [ static ]
1040 // returns an empty string if the element does not have tag name
1041 static QString tagName( const QDomElement& element ) {
1042  return element.tagName().stripWhiteSpace();
1043 }
1044
1045 // gets an attribute by it's name for the dom element [ static ]
1046 // returns an empty string if the element does not have such attribute
1047 static QString attribute( const QDomElement& element, const QString& attName ) {
1048   return element.attribute( attName ).stripWhiteSpace();
1049 }
1050
1051 // checks the given value for the boolean value [ static ]
1052 // returns TRUE if string is "true", "yes" or "1"
1053 static bool checkBool( const QString& value ) {
1054   return ( value == "true" || value == "yes" || value == "1" );
1055 }
1056
1057 // checks the given value for the integer value [ static ]
1058 // returns -1 if item is empty or presents and invalid number
1059 static int checkInt( const QString& value ) 
1060 {
1061   return value.isEmpty() ? -1 : value.toInt();
1062 }
1063
1064 /*!
1065  * Constructor
1066  */
1067 SALOME_PYQT_XmlHandler::SALOME_PYQT_XmlHandler( SALOME_PYQT_Module* module, const QString& fileName ) 
1068      : myModule( module )
1069 {
1070   QFile aFile( fileName );
1071   if ( !aFile.open( IO_ReadOnly ) )
1072     return;
1073   if ( !myDoc.setContent( &aFile ) ) {
1074       aFile.close();
1075       return;
1076   }
1077   aFile.close();
1078 }
1079
1080 /*!
1081   Called by SALOME_PYQT_Module::initialize() in order to create actions 
1082   (menus, toolbars, popup menus)
1083  */
1084 void SALOME_PYQT_XmlHandler::createActions()
1085 {
1086   // get document element
1087   QDomElement aDocElem = myDoc.documentElement();
1088
1089   // get main menu actions
1090   QDomNodeList aMenuList = aDocElem.elementsByTagName( "menu-item" );
1091   for ( int i = 0; i < aMenuList.count(); i++ ) {
1092     QDomNode n = aMenuList.item( i );
1093     createMenu( n );
1094   }
1095
1096   // create toolbars actions
1097   QDomNodeList aToolsList = aDocElem.elementsByTagName( "toolbar" );
1098   for ( int i = 0; i < aToolsList.count(); i++ ) {
1099     QDomNode n = aToolsList.item( i );
1100     createToolBar( n );
1101   }
1102 }
1103
1104 /*!
1105  *  Creates popup menu
1106  */
1107 void SALOME_PYQT_XmlHandler::createPopup( QPopupMenu*    menu, 
1108                                           const QString& context, 
1109                                           const QString& parent, 
1110                                           const QString& object )
1111 {
1112   // get document element
1113   QDomElement aDocElem = myDoc.documentElement();
1114
1115   // get popup menus actions
1116   QDomNodeList aPopupList = aDocElem.elementsByTagName( "popupmenu" );
1117   for ( int i = 0; i < aPopupList.count(); i++ ) {
1118     QDomNode n = aPopupList.item( i );
1119     if ( !n.isNull() && n.isElement() ) {
1120       QDomElement e = n.toElement();
1121       QString lab = attribute( e, "label-id"   );
1122       QString ctx = attribute( e, "context-id" );
1123       QString prt = attribute( e, "parent-id"  );
1124       QString obj = attribute( e, "object-id"  );
1125       if ( ctx == context && prt == parent && obj == object )  {
1126         insertPopupItems( n, menu );
1127         break;
1128       }
1129     }
1130   }
1131 }
1132
1133 /*!
1134   Create main menu with child actions
1135  */
1136 void SALOME_PYQT_XmlHandler::createMenu( QDomNode& parentNode, const int parentMenuId )
1137 {
1138   if ( !myModule )
1139     return;
1140   
1141   if ( parentNode.isNull() )
1142     return;
1143
1144   QDomElement parentElement = parentNode.toElement(); 
1145   if ( !parentElement.isNull() ) {
1146     QString plabel = attribute( parentElement, "label-id" );
1147     int     pid    = checkInt( attribute( parentElement, "item-id" ) );
1148     int     ppos   = checkInt( attribute( parentElement, "pos-id" ) );
1149     if ( !plabel.isEmpty() ) {
1150       // create menu
1151       int menuId = myModule->createMenu( plabel,         // label
1152                                          parentMenuId,   // parent menu ID, should be -1 for main menu
1153                                          pid,            // ID
1154                                          80,             // group ID
1155                                          ppos );         // position
1156       QDomNode node = parentNode.firstChild();
1157       while ( !node.isNull() ) {
1158         if ( node.isElement() ) {
1159           QDomElement elem = node.toElement();
1160           QString aTagName = tagName( elem );
1161           if ( aTagName == "popup-item" ) {
1162             int     id      = checkInt( attribute( elem, "item-id" ) );
1163             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1164             QString label   = attribute( elem, "label-id" );
1165             QString icon    = attribute( elem, "icon-id" );
1166             QString tooltip = attribute( elem, "tooltip-id" );
1167             QString accel   = attribute( elem, "accel-id" );
1168             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
1169             ////QString execute = attribute( elem, "execute-action" );               // not used
1170
1171             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1172             // also check if the action with given ID is already created
1173             if ( id != -1 && !myModule->action( SALOME_PYQT_Module::PYQT_ACTION_MENU + id ) ) {
1174               // little trick to have several actions with same ID for menus and toolbars
1175               id = SALOME_PYQT_Module::PYQT_ACTION_MENU + id;
1176               // create menu action
1177               QAction* action = myModule->createAction( id,                               // ID
1178                                                         tooltip,                          // tooltip
1179                                                         icon,                             // icon
1180                                                         label,                            // menu text
1181                                                         tooltip,                          // status-bar text
1182                                                         QKeySequence( accel ),            // keyboard accelerator
1183                                                         toggle );                         // toogled action
1184               myModule->addAction( SALOME_PYQT_Module::PYQT_ACTION_MENU, action );
1185               myModule->createMenu( action, menuId, -1, 80, pos );
1186             }
1187           }
1188           else if ( aTagName == "submenu" ) {
1189             // create sub-menu
1190             createMenu( node, menuId );
1191           }
1192           else if ( aTagName == "separator" ) {
1193             // create menu separator
1194             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1195             QAction* action = myModule->createSeparator();
1196             myModule->createMenu( action, menuId, -1, 80, pos );
1197           }
1198         }
1199         node = node.nextSibling();
1200       }
1201     }
1202   }
1203 }
1204
1205 /*!
1206   Create a toolbar with child actions
1207  */
1208 void SALOME_PYQT_XmlHandler::createToolBar( QDomNode& parentNode )
1209 {
1210   if ( !myModule )
1211     return;
1212   
1213   if ( parentNode.isNull() )
1214     return;
1215
1216   QDomElement parentElement = parentNode.toElement(); 
1217   if ( !parentElement.isNull() ) {
1218     QString aLabel = attribute( parentElement, "label-id" );
1219     if ( !aLabel.isEmpty() ) {
1220       // create toolbar
1221       int tbId = myModule->createTool( aLabel );
1222       QDomNode node = parentNode.firstChild();
1223       while ( !node.isNull() ) {
1224         if ( node.isElement() ) {
1225           QDomElement elem = node.toElement();
1226           QString aTagName = tagName( elem );
1227           if ( aTagName == "toolbutton-item" ) {
1228             int     id      = checkInt( attribute( elem, "item-id" ) );
1229             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1230             QString label   = attribute( elem, "label-id" );
1231             QString icon    = attribute( elem, "icon-id" );
1232             QString tooltip = attribute( elem, "tooltip-id" );
1233             QString accel   = attribute( elem, "accel-id" );
1234             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
1235             ////QString execute = attribute( elem, "execute-action" );               // not used
1236
1237             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1238             // also check if the action with given ID is already created
1239             if ( id != -1 && !myModule->action( SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL + id ) ) {
1240               // little trick to have several actions with same ID for menus and toolbars
1241               id = SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL + id;
1242               // create toolbar action
1243               QAction* action = myModule->createAction( id,                               // ID
1244                                                         tooltip,                          // tooltip
1245                                                         icon,                             // icon
1246                                                         label,                            // menu text
1247                                                         tooltip,                          // status-bar text
1248                                                         QKeySequence( accel ),            // keyboard accelerator
1249                                                         toggle );                         // toogled action
1250               myModule->addAction( SALOME_PYQT_Module::PYQT_ACTION_TOOLBAL, action );
1251               myModule->createTool( action, tbId, -1, pos );
1252             }
1253           }
1254           else if ( aTagName == "separatorTB" ) {
1255             // create toolbar separator
1256             int     pos     = checkInt( attribute( elem, "pos-id" ) );
1257             QAction* action = myModule->createSeparator();
1258             myModule->createTool( action, tbId, -1, pos );
1259           }
1260         }
1261         node = node.nextSibling();
1262       }
1263     }
1264   }      
1265 }
1266
1267 void SALOME_PYQT_XmlHandler::insertPopupItems( QDomNode& parentNode, QPopupMenu* menu )
1268 {
1269   if ( !myModule )
1270     return;
1271   
1272   if ( parentNode.isNull() )
1273     return;
1274
1275   // we create popup menus without help of QtxPopupMgr
1276   QDomNode node = parentNode.firstChild();
1277   while ( !node.isNull() ) {
1278     if ( node.isElement() ) {
1279       QDomElement elem = node.toElement();
1280       QString aTagName = tagName( elem );
1281       if ( aTagName == "popup-item" ) {
1282         // insert a command item
1283         int     id      = checkInt( attribute( elem, "item-id" ) );
1284         int     pos     = checkInt( attribute( elem, "pos-id" ) );
1285         QString label   = attribute( elem, "label-id" );
1286         QString icon    = attribute( elem, "icon-id" );
1287         /////QString tooltip = attribute( elem, "tooltip-id" );                   // not used
1288         QString accel   = attribute( elem, "accel-id" );
1289         /////bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );       // not used
1290         /////QString execute = attribute( elem, "execute-action" );               // not used
1291
1292         QIconSet anIcon;
1293         if ( !icon.isEmpty() ) {
1294           QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1295           if ( !pixmap.isNull() )
1296             anIcon = QIconSet( pixmap );
1297         }
1298         
1299         // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1300         // also check if the action with given ID is already created
1301         if ( id != -1 ) {
1302           menu->insertItem( anIcon, label, myModule, SLOT( onGUIEvent(int) ), QKeySequence( accel ), id, pos );
1303         }
1304       }
1305       else if ( aTagName == "submenu" ) {
1306         // create sub-menu
1307         int     id    = checkInt( attribute( elem, "item-id" ) );
1308         int     pos   = checkInt( attribute( elem, "pos-id" ) );
1309         QString label = attribute( elem, "label-id" );
1310         QString icon    = attribute( elem, "icon-id" );
1311
1312         QIconSet anIcon;
1313         if ( !icon.isEmpty() ) {
1314           QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1315           if ( !pixmap.isNull() )
1316             anIcon = QIconSet( pixmap );
1317         }
1318
1319         QPopupMenu* newPopup = new QPopupMenu( menu, label );
1320         menu->insertItem( anIcon, label, newPopup, id, pos );
1321         insertPopupItems( node, newPopup );
1322       }
1323       else if ( aTagName == "separator" ) {
1324         // create menu separator
1325         int     pos     = checkInt( attribute( elem, "pos-id" ) );
1326         menu->insertSeparator( pos );
1327       }
1328     }
1329     node = node.nextSibling();
1330   }
1331 }