Salome HOME
Update from BR_V5_DEV 13Feb2009
[modules/gui.git] / src / SALOME_PYQT / SALOME_PYQT_GUI / SALOME_PYQT_Module.cxx
1 //  Copyright (C) 2007-2008  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 //  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 //  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 //  This library is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU Lesser General Public
8 //  License as published by the Free Software Foundation; either
9 //  version 2.1 of the License.
10 //
11 //  This library is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //  Lesser General Public License for more details.
15 //
16 //  You should have received a copy of the GNU Lesser General Public
17 //  License along with this library; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 //  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22 // File   : SALOME_PYQT_Module.cxx
23 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
24 //
25
26 #include "SALOME_PYQT_Module.h"
27
28 #include <PyInterp_Dispatcher.h>
29
30 #include <SUIT_ResourceMgr.h>
31 #include <SUIT_Desktop.h>
32 #include <SUIT_ViewModel.h>
33 #include <SUIT_ViewWindow.h>
34 #include <SUIT_ViewManager.h>
35 #include <STD_MDIDesktop.h>
36 #include <STD_TabDesktop.h>
37 #include <LightApp_Preferences.h>
38 #include <SalomeApp_Application.h>
39 #include <SalomeApp_Study.h>
40
41 #include <QtxWorkstack.h>
42 #include <QtxWorkspace.h>
43 #include <QtxActionGroup.h>
44 #include <QtxActionMenuMgr.h>
45 #include <QtxActionToolMgr.h>
46
47 #include <SALOME_LifeCycleCORBA.hxx>
48 #include <Container_init_python.hxx>
49
50 #include <QFile>
51 #include <QDomDocument>
52 #include <QDomNode>
53 #include <QDomElement>
54 #include <QMenuBar>
55 #include <QMenu>
56 #include <QAction>
57
58 #include "sipAPISalomePyQtGUI.h"
59
60 #include <sip.h>
61 #if SIP_VERSION < 0x040700
62 #include "sipQtGuiQWidget.h"
63 #include "sipQtGuiQMenu.h"
64 #endif
65
66 /*!
67   \brief Default name of the module, replaced at the moment
68   of module creation.
69   \internal
70 */
71 const char* DEFAULT_NAME  = "SALOME_PYQT_Module";
72
73 /*!
74   \brief Default menu group number.
75   \internal
76 */
77 const int DEFAULT_GROUP = 40;
78
79 /*!
80   \var IsCallOldMethods
81   \brief Allow calling obsolete callback methods.
82   \internal
83   
84   If the macro CALL_OLD_METHODS is not defined, the invoking
85   of obsolete Python module's methods like setSetting(), definePopup(), 
86   etc. is blocked.
87
88   CALL_OLD_METHODS macro can be defined for example by adding 
89   -DCALL_OLD_METHODS compilation option to the Makefile.
90 */
91 #ifdef CALL_OLD_METHODS
92 const bool IsCallOldMethods = true;
93 #else
94 const bool IsCallOldMethods = false;
95 #endif
96
97 /* Py_ssize_t for old Pythons */
98 /* This code is as recommended by: */
99 /* http://www.python.org/dev/peps/pep-0353/#conversion-guidelines */
100 #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
101 typedef int Py_ssize_t;
102 # define PY_SSIZE_T_MAX INT_MAX
103 # define PY_SSIZE_T_MIN INT_MIN
104 #endif
105
106 //
107 // NB: Python requests.
108 // General rule for Python requests created by SALOME_PYQT_Module:
109 // all requests should be executed SYNCHRONOUSLY within the main GUI thread.
110 // However, it is obligatory that ANY Python call is wrapped with a request object,
111 // so that ALL Python API calls are serialized with PyInterp_Dispatcher.
112 //
113
114 /*!
115   \class SALOME_PYQT_Module::XmlHandler
116   \brief XML resource files parser.
117   \internal
118
119   This class is used to provide backward compatibility with
120   existing Python modules in which obsolete menu definition system
121   (via XML files) is used.
122 */
123
124 class SALOME_PYQT_Module::XmlHandler
125 {
126 public:
127   XmlHandler( SALOME_PYQT_Module* module, const QString& fileName );
128   void createActions();
129   void createPopup  ( QMenu*         menu,
130                       const QString& context,
131                       const QString& parent,
132                       const QString& object );
133   void activateMenus( bool );
134
135 protected:
136   void createToolBar   ( QDomNode&   parentNode );
137   void createMenu      ( QDomNode&   parentNode,
138                          const int   parentMenuId = -1,
139                          QMenu*      parentPopup = 0 );
140
141   void insertPopupItems( QDomNode&   parentNode,
142                          QMenu*      menu );
143
144 private:
145   SALOME_PYQT_Module* myModule;
146   QDomDocument        myDoc;
147   QList<int>          myMenuItems;
148 };
149
150 //
151 // NB: Library initialization
152 // Since the SalomePyQtGUI library is not imported in Python it's initialization function
153 // should be called manually (and only once) in order to initialize global sip data
154 // and to get C API from sip : sipBuildResult for example
155 //
156 #define INIT_FUNCTION initSalomePyQtGUI
157 #if defined(SIP_STATIC_MODULE)
158 extern "C" void INIT_FUNCTION();
159 #else
160 PyMODINIT_FUNC INIT_FUNCTION();
161 #endif
162
163 /*!
164   \fn CAM_Module* createModule()
165   \brief Module factory function.
166   \internal
167   
168   Creates an instance of SALOME_PYQT_Module object by request
169   of an application when the module is loaded and initialized.
170
171   \return new module object
172 */
173
174 extern "C" {
175   SALOME_PYQT_EXPORT CAM_Module* createModule() {
176     static bool alreadyInitialized = false;
177     if ( !alreadyInitialized ) {
178       // call only once (see comment above) !
179       PyEval_RestoreThread( KERNEL_PYTHON::_gtstate );
180       INIT_FUNCTION();
181       PyEval_ReleaseThread( KERNEL_PYTHON::_gtstate );
182       alreadyInitialized = !alreadyInitialized;
183     }
184     return new SALOME_PYQT_Module();
185   }
186 }
187
188 /*!
189   \class FuncMsg
190   \brief Function call in/out tracer.
191   \internal
192 */
193
194 class FuncMsg
195 {
196 public:
197   FuncMsg( const QString& funcName )
198   {
199     myName = funcName;
200     MESSAGE( myName.toLatin1().constData() << " [ begin ]" );
201   }
202   ~FuncMsg()
203   {
204     MESSAGE( myName.toLatin1().constData() << " [ end ]" );
205   }
206   void message( const QString& msg )
207   {
208     MESSAGE( myName.toLatin1().constData() << " : " << msg.toLatin1().constData() );
209   }
210 private:
211   QString myName;
212 };
213
214 /*!
215   \class SALOME_PYQT_Module
216   \brief This class implements module API for all the Python-based 
217   SALOME modules.
218 */
219
220 //
221 // Static variables definition
222 //
223 SALOME_PYQT_Module::InterpMap SALOME_PYQT_Module::myInterpMap;
224 SALOME_PYQT_Module* SALOME_PYQT_Module::myInitModule = 0;
225
226 /*!
227   \brief Get the module being initialized.
228   
229   This is a little trick :) needed to provide an access from Python
230   (SalomePyQt) to the module being currently activated. The problem
231   that during the process of module initialization (initialize() 
232   function) it is not yet available via application->activeModule()
233   call.
234   
235   This method returns valid pointer only if called in scope of
236   initialize() function.
237
238   \return the module being currently initialized
239 */
240 SALOME_PYQT_Module* SALOME_PYQT_Module::getInitModule()
241 {
242   return myInitModule;
243 }
244
245 /*!
246   \brief Constructor
247 */
248 SALOME_PYQT_Module::SALOME_PYQT_Module()
249 : SalomeApp_Module( DEFAULT_NAME ),
250   myInterp( 0 ),
251   myModule( 0 ), 
252   myXmlHandler ( 0 ),
253   myLastActivateStatus( true )
254 {
255 }
256
257 /*!
258   \brief Destructor
259 */
260 SALOME_PYQT_Module::~SALOME_PYQT_Module()
261 {
262   if ( myXmlHandler )
263     delete myXmlHandler;
264 }
265
266 /*!
267   \brief Initialization of the module.
268   
269   This method can be used for creation of the menus, toolbars and 
270   other such staff.
271   
272   There are two ways to do this:
273   - for obsolete modules this method first tries to read
274   <module>_<language>.xml resource file which contains a menu,
275   toolbars and popup menus description;
276   - new modules can create menus by direct calling of the
277   corresponding methods of SalomePyQt Python API in the Python
278   module's initialize() method which is called from here.
279
280   NOTE: SALOME supports two modes of modules loading:
281   - immediate (all the modules are created and initialized 
282   immediately when the application object is created;
283   - postponed modules loading (used currently); in this mode
284   the module is loaded only be request.
285   If postponed modules loading is not used, the active
286   study might be not yet defined at this stage, so initialize()
287   method should not perform any study-based initialization.
288
289   \param app parent application object
290 */
291 void SALOME_PYQT_Module::initialize( CAM_Application* app )
292 {
293   FuncMsg fmsg( "SALOME_PYQT_Module::initialize()" );
294
295   // call base implementation
296   SalomeApp_Module::initialize( app );
297
298   // try to get XML resource file name
299   SUIT_ResourceMgr* aResMgr = getApp()->resourceMgr();
300   if ( !myXmlHandler && aResMgr ) {
301     // get current language
302     QString aLang = aResMgr->stringValue( "language", "language", QString() );
303     if ( aLang.isEmpty() ) 
304       aLang = "en";
305     // define resource file name
306     QString aFileName = name() + "_" + aLang + ".xml";
307     aFileName = aResMgr->path( "resources", name(), aFileName );
308     // create XML handler instance
309     if ( !aFileName.isEmpty() && QFile::exists( aFileName ) )
310       myXmlHandler = new SALOME_PYQT_Module::XmlHandler( this, aFileName );
311     // create menus & toolbars from XML file if required
312     if ( myXmlHandler )
313       myXmlHandler->createActions();
314   }
315
316   // perform internal initialization and call module's initialize() funtion
317   // InitializeReq: request class for internal init() operation
318   class InitializeReq : public PyInterp_Request
319   {
320   public:
321     InitializeReq( CAM_Application*    _app,
322                    SALOME_PYQT_Module* _obj )
323       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
324         myApp( _app ),
325         myObj( _obj ) {}
326
327   protected:
328     virtual void execute()
329     {
330       myObj->init( myApp );
331     }
332
333   private:
334     CAM_Application*    myApp;
335     SALOME_PYQT_Module* myObj;
336   };
337
338   // post request
339   PyInterp_Dispatcher::Get()->Exec( new InitializeReq( app, this ) );
340 }
341
342 /*!
343   \brief Activation of the module.
344
345   This function is usually used in order to show the module's 
346   specific menus and toolbars, update actions state and perform
347   other such actions required when the module is activated.
348   
349   Note, that returning \c false in this function prevents the 
350   module activation.
351
352   \param theStudy parent study
353   \return \c true if activation is successful and \c false otherwise
354 */
355 bool SALOME_PYQT_Module::activateModule( SUIT_Study* theStudy )
356 {
357   FuncMsg fmsg( "SALOME_PYQT_Module::activateModule()" );
358
359   // call base implementation
360   bool res = SalomeApp_Module::activateModule( theStudy );
361
362   if ( !res )
363     return res;
364
365   // reset the activation status to the default value
366   myLastActivateStatus = true;
367
368   // perform internal activation
369   // ActivateReq: request class for internal activate() operation
370   class ActivateReq : public PyInterp_Request
371   {
372   public:
373     ActivateReq( SUIT_Study*         _study,
374                  SALOME_PYQT_Module* _obj )
375       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
376         myStudy ( _study ),
377         myObj   ( _obj   ) {}
378
379   protected:
380     virtual void execute()
381     {
382       myObj->activate( myStudy );
383     }
384
385   private:
386     SUIT_Study*         myStudy;
387     SALOME_PYQT_Module* myObj;
388   };
389
390   // post request
391   PyInterp_Dispatcher::Get()->Exec( new ActivateReq( theStudy, this ) );
392
393   // check activation status (set by activate())
394   if ( !lastActivationStatus() )
395     return false;
396
397   // activate menus, toolbars, etc
398   if ( myXmlHandler ) myXmlHandler->activateMenus( true );
399   setMenuShown( true );
400   setToolShown( true );
401
402   // connect preferences changing signal
403   connect( getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
404            this,     SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
405
406   // perform custom activation actions
407   // CustomizeReq: request class for internal customize() operation
408   class CustomizeReq : public PyInterp_Request
409   {
410   public:
411     CustomizeReq( SUIT_Study*         _study,
412                   SALOME_PYQT_Module* _obj )
413       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
414         myStudy ( _study ),
415         myObj   ( _obj   ) {}
416
417   protected:
418     virtual void execute()
419     {
420       myObj->customize( myStudy );
421     }
422
423   private:
424     SUIT_Study*         myStudy;
425     SALOME_PYQT_Module* myObj;
426   };
427
428   // post request
429   PyInterp_Dispatcher::Get()->Exec( new CustomizeReq( theStudy, this ) );
430
431   return true;
432 }
433
434 /*!
435   \brief Deactivation of the module.
436
437   This function is usually used in order to hide the module's 
438   specific menus and toolbars and perform other such actions
439   required when the module is deactivated.
440
441   \param theStudy parent study
442   \return \c true if deactivation is successful and \c false otherwise
443 */
444 bool SALOME_PYQT_Module::deactivateModule( SUIT_Study* theStudy )
445 {
446   FuncMsg fmsg( "SALOME_PYQT_Module::deactivateModule()" );
447
448   // disconnect preferences changing signal
449   disconnect( getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
450               this,     SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
451
452   // perform internal deactivation
453   // DeactivateReq: request class for internal deactivate() operation
454   class DeactivateReq : public PyInterp_LockRequest
455   {
456   public:
457     DeactivateReq( PyInterp_Interp*    _py_interp,
458                    SUIT_Study*         _study,
459                    SALOME_PYQT_Module* _obj )
460       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
461         myStudy ( _study ),
462         myObj   ( _obj   ) {}
463
464   protected:
465     virtual void execute()
466     {
467       myObj->deactivate( myStudy );
468     }
469
470   private:
471     SUIT_Study*         myStudy;
472     SALOME_PYQT_Module* myObj;
473   };
474
475   // post request
476   PyInterp_Dispatcher::Get()->Exec( new DeactivateReq( myInterp, theStudy, this ) );
477
478   // deactivate menus, toolbars, etc
479   if ( myXmlHandler ) myXmlHandler->activateMenus( false );
480   setMenuShown( false );
481   setToolShown( false );
482
483   // call base implementation
484   return SalomeApp_Module::deactivateModule( theStudy );
485 }
486
487 /*!
488  \brief Get last activation status.
489  \return status of last module activation operation
490  \sa activateModule()
491 */
492 bool SALOME_PYQT_Module::lastActivationStatus() const
493 {
494   return myLastActivateStatus;
495 }
496
497 /*!
498   \breif Process application preferences changing.
499
500   Called when any application setting is changed.
501
502   \param module preference module
503   \param section preference resource file section
504   \param setting preference resource name
505 */
506 void SALOME_PYQT_Module::preferenceChanged( const QString& module, 
507                                             const QString& section, 
508                                             const QString& setting )
509 {
510   FuncMsg fmsg( "SALOME_PYQT_Module::preferenceChanged()" );
511
512   // perform synchronous request to Python event dispatcher
513   class Event : public PyInterp_LockRequest
514   {
515   public:
516     Event( PyInterp_Interp*    _py_interp,
517            SALOME_PYQT_Module* _obj,
518            const QString&      _section,
519            const QString&      _setting )
520       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
521         myObj    ( _obj ),
522         mySection( _section ),
523         mySetting( _setting ) {}
524
525   protected:
526     virtual void execute()
527     {
528       myObj->prefChanged( mySection, mySetting );
529     }
530
531   private:
532     SALOME_PYQT_Module* myObj;
533     QString mySection, mySetting;
534   };
535
536   if ( module != moduleName() ) {
537     // module's own preferences are processed by preferencesChanged() method
538     // ...
539     // post the request only if dispatcher is not busy!
540     // execute request synchronously
541     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
542       PyInterp_Dispatcher::Get()->Exec( new Event( myInterp, this, section, setting ) );
543   }
544 }
545
546 /*!
547   \brief Process study activation.
548   
549   Called when study desktop is activated. Used for notifying the Python
550   module about changing of the active study.
551 */
552 void SALOME_PYQT_Module::studyActivated()
553 {
554   FuncMsg fmsg( "SALOME_PYQT_Module::studyActivated()" );
555
556   // StudyChangedReq: request class for internal studyChanged() operation
557   class StudyChangedReq : public PyInterp_Request
558   {
559   public:
560     StudyChangedReq( SUIT_Study*         _study,
561                      SALOME_PYQT_Module* _obj )
562       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
563         myStudy ( _study ),
564         myObj   ( _obj   ) {}
565
566   protected:
567     virtual void execute()
568     {
569       myObj->studyChanged( myStudy );
570     }
571
572   private:
573     SUIT_Study*         myStudy;
574     SALOME_PYQT_Module* myObj;
575   };
576
577   // post request
578   PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( application()->activeStudy(), this ) );
579 }
580
581 /*!
582   \brief Process GUI action (from main menu, toolbar or 
583   context popup menu action).
584 */
585 void SALOME_PYQT_Module::onGUIEvent()
586 {
587   FuncMsg fmsg( "SALOME_PYQT_Module::onGUIEvent()" );
588
589   // get sender action
590   QAction* action = qobject_cast<QAction*>( sender() );
591   if ( !action )
592     return;
593
594   // get action ID
595   int id = actionId( action );
596   fmsg.message( QString( "action id = %1" ).arg( id ) );
597
598   // perform synchronous request to Python event dispatcher
599   class GUIEvent : public PyInterp_LockRequest
600   {
601   public:
602     GUIEvent( PyInterp_Interp*    _py_interp,
603               SALOME_PYQT_Module* _obj,
604               int                 _id )
605       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
606         myId    ( _id  ),
607         myObj   ( _obj ) {}
608
609   protected:
610     virtual void execute()
611     {
612       myObj->guiEvent( myId );
613     }
614
615   private:
616     int                 myId;
617     SALOME_PYQT_Module* myObj;
618   };
619
620   // post request
621   PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
622 }
623
624 /*!
625   \brief Process context popup menu request.
626   
627   Called when user activates popup menu in some window
628   (view, object browser, etc).
629
630   \param theContext popup menu context (e.g. "ObjectBrowser")
631   \param thePopupMenu popup menu
632   \param title popup menu title (not used)
633 */
634 void SALOME_PYQT_Module::contextMenuPopup( const QString& theContext, 
635                                            QMenu*         thePopupMenu, 
636                                            QString&       /*title*/ )
637 {
638   FuncMsg fmsg( "SALOME_PYQT_Module::contextMenuPopup()" );
639   fmsg.message( QString( "context: %1" ).arg( theContext ) );
640
641   // perform synchronous request to Python event dispatcher
642   class PopupMenuEvent : public PyInterp_LockRequest
643   {
644   public:
645     PopupMenuEvent( PyInterp_Interp*    _py_interp,
646                     SALOME_PYQT_Module* _obj,
647                     const QString&      _context,
648                     QMenu*        _popup )
649       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
650         myContext( _context ),
651         myPopup  ( _popup  ),
652         myObj    ( _obj )   {}
653
654   protected:
655     virtual void execute()
656     {
657       myObj->contextMenu( myContext, myPopup );
658     }
659
660   private:
661     SALOME_PYQT_Module* myObj;
662     QString             myContext;
663     QMenu*         myPopup;
664   };
665
666   // post request only if dispatcher is not busy!
667   // execute request synchronously
668   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
669     PyInterp_Dispatcher::Get()->Exec( new PopupMenuEvent( myInterp, this, theContext, thePopupMenu ) );
670 }
671
672 /*!
673   \brief Export preferences for the Python module.
674   
675   Called only once when the first instance of the module is created.
676 */
677 void SALOME_PYQT_Module::createPreferences()
678 {
679   FuncMsg fmsg( "SALOME_PYQT_Module::createPreferences()" );
680
681   // perform synchronous request to Python event dispatcher
682   class Event : public PyInterp_LockRequest
683   {
684   public:
685     Event( PyInterp_Interp*    _py_interp,
686            SALOME_PYQT_Module* _obj )
687       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
688         myObj    ( _obj )   {}
689
690   protected:
691     virtual void execute()
692     {
693       myObj->initPreferences();
694     }
695
696   private:
697     SALOME_PYQT_Module* myObj;
698   };
699
700   // post request only if dispatcher is not busy!
701   // execute request synchronously
702   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
703     PyInterp_Dispatcher::Get()->Exec( new Event( myInterp, this ) );
704 }
705
706 /*!
707   \brief Define the dockable windows associated with the module.
708   
709   To fill the list of windows the correspondind Python module's windows()
710   method is called from SALOME_PYQT_Module::init() method.
711
712   By default, ObjectBrowser, PythonConsole and LogWindow windows are 
713   associated to the module.
714
715   Allowed dockable windows:
716   - SalomeApp_Application::WT_ObjectBrowser : object browser
717   - SalomeApp_Application::WT_PyConsole : python console
718   - SalomeApp_Application::WT_LogWindow : log messages output window
719
720   Dock area is defined by Qt::DockWidgetArea enumeration:
721   - Qt::TopDockWidgetArea : top dock area
722   - Qt::BottomDockWidgetArea : bottom dock area
723   - Qt::LeftDockWidgetArea : left dock area
724   - Qt::RightDockWidgetArea : right dock area
725
726   \param mappa map of dockable windows: { <window_type> : <dock_area> }
727 */
728 void SALOME_PYQT_Module::windows( QMap<int, int>& mappa ) const
729 {
730   FuncMsg fmsg( "SALOME_PYQT_Module::windows()" );
731
732   mappa = myWindowsMap;
733 }
734
735 /*!
736   \brief Define the compatible view windows associated with the module.
737
738   The associated view windows are opened automatically when the module
739   is activated.
740
741   To fill the list of views the correspondind Python module's views()
742   method is called from SALOME_PYQT_Module::init() method.
743   By default, the list is empty.
744
745   \param listik list of view windows types
746 */
747 void SALOME_PYQT_Module::viewManagers( QStringList& lst ) const
748 {
749   FuncMsg fmsg( "SALOME_PYQT_Module::viewManagers()" );
750
751   lst = myViewMgrList;
752 }
753
754 /*!
755   \brief Process module's preferences changing.
756
757   Called when the module's preferences are changed.
758   
759   \param section setting section
760   \param setting setting name
761 */
762 void SALOME_PYQT_Module::preferencesChanged( const QString& section, const QString& setting )
763 {
764   FuncMsg fmsg( "SALOME_PYQT_Module::preferencesChanged()" );
765
766   // perform synchronous request to Python event dispatcher
767   class Event : public PyInterp_LockRequest
768   {
769   public:
770     Event( PyInterp_Interp*    _py_interp,
771            SALOME_PYQT_Module* _obj,
772            const QString&      _section,
773            const QString&      _setting )
774       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
775         myObj    ( _obj ),
776         mySection( _section ),
777         mySetting( _setting ) {}
778
779   protected:
780     virtual void execute()
781     {
782       myObj->prefChanged( mySection, mySetting );
783     }
784
785   private:
786     SALOME_PYQT_Module* myObj;
787     QString mySection, mySetting;
788   };
789
790   // post request only if dispatcher is not busy!
791   // execut request synchronously
792   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
793     PyInterp_Dispatcher::Get()->Exec( new Event( myInterp, this, section, setting ) );
794 }
795
796 /*!
797   \brief Internal module initialization:
798
799   Performs the following actions:
800   - initialize or get the Python interpreter (one per study)
801   - import the Python module
802   - pass the workspace widget to the Python module
803   - call Python module's initialize() method
804   - call Python module's windows() method
805   - call Python module's views() method
806
807   \param app parent application object
808 */
809 void SALOME_PYQT_Module::init( CAM_Application* app )
810 {
811   FuncMsg fmsg( "SALOME_PYQT_Module::init()" );
812
813   // reset interpreter to NULL
814   myInterp = NULL;
815
816   // get study Id
817   SalomeApp_Application* anApp = dynamic_cast<SalomeApp_Application*>( app );
818   if ( !anApp )
819     return;
820   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
821   if ( !aStudy )
822     return;
823   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
824
825   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
826   initInterp( aStudyId );
827   if ( !myInterp )
828     return; // Error
829
830   // import Python GUI module
831   importModule();
832   if ( !myModule )
833     return; // Error
834
835   // this module is being activated now!
836   myInitModule = this;
837
838   // then call Python module's initialize() method
839   // ... first get python lock
840   PyLockWrapper aLock = myInterp->GetLockWrapper();
841   // ... (the Python module is already imported)
842   // ... finally call Python module's initialize() method
843   if ( PyObject_HasAttrString( myModule , "initialize" ) ) {
844     PyObjWrapper res( PyObject_CallMethod( myModule, "initialize", "" ) );
845     if ( !res ) {
846       PyErr_Print();
847     }
848   }
849
850   // get required dockable windows list from the Python module 
851   // by calling windows() method
852   // ... first put default values
853   myWindowsMap.insert( SalomeApp_Application::WT_ObjectBrowser, Qt::LeftDockWidgetArea );
854   myWindowsMap.insert( SalomeApp_Application::WT_PyConsole,     Qt::BottomDockWidgetArea );
855   myWindowsMap.insert( SalomeApp_Application::WT_LogWindow,     Qt::BottomDockWidgetArea );
856
857   if ( PyObject_HasAttrString( myModule , "windows" ) ) {
858     PyObjWrapper res1( PyObject_CallMethod( myModule, "windows", "" ) );
859     if ( !res1 ) {
860       PyErr_Print();
861     }
862     else {
863       myWindowsMap.clear();
864       if ( PyDict_Check( res1 ) ) {
865         PyObject* key;
866         PyObject* value;
867         Py_ssize_t pos = 0;
868         while ( PyDict_Next( res1, &pos, &key, &value ) ) {
869           // parse the return value
870           // it should be a map: {integer:integer}
871           int aKey, aValue;
872           if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
873             aKey   = PyInt_AsLong( key );
874             aValue = PyInt_AsLong( value );
875             myWindowsMap[ aKey ] = aValue;
876           }
877         }
878       }
879     }
880   }
881
882   // get compatible view windows types from the Python module 
883   // by calling views() method
884   if ( PyObject_HasAttrString( myModule , "views" ) ) {
885     PyObjWrapper res2( PyObject_CallMethod( myModule, "views", "" ) );
886     if ( !res2 ) {
887       PyErr_Print();
888     }
889     else {
890       // parse the return value
891       // result can be one string...
892       if ( PyString_Check( res2 ) ) {
893         myViewMgrList.append( PyString_AsString( res2 ) );
894       }
895       // ... or list of strings
896       else if ( PyList_Check( res2 ) ) {
897         int size = PyList_Size( res2 );
898         for ( int i = 0; i < size; i++ ) {
899           PyObject* value = PyList_GetItem( res2, i );
900           if( value && PyString_Check( value ) ) {
901             myViewMgrList.append( PyString_AsString( value ) );
902           }
903         }
904       }
905     }
906   }
907   // module is already activated!
908   myInitModule = 0;
909 }
910
911 /*!
912   \brief Internal activation:
913
914   Performs the following actions:
915   - initialize or get the Python interpreter (one per study)
916   - import the Python GUI module
917   - call Python module's activate() method
918
919   \param theStudy parent study object
920 */
921 void SALOME_PYQT_Module::activate( SUIT_Study* theStudy )
922 {
923   FuncMsg fmsg( "SALOME_PYQT_Module::activate()" );
924
925   // get study Id
926   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
927   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
928
929   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
930   initInterp( aStudyId );
931   if ( !myInterp )
932     return; // Error
933
934   // import Python GUI module
935   importModule();
936   if ( !myModule )
937     return; // Error
938
939   // get python lock
940   PyLockWrapper aLock = myInterp->GetLockWrapper();
941
942   // call Python module's activate() method (for the new modules)
943   if ( PyObject_HasAttrString( myModule , "activate" ) ) {
944     PyObject* res1 = PyObject_CallMethod( myModule, "activate", "" );
945     if ( !res1 || !PyBool_Check( res1 ) ) {
946       PyErr_Print();
947       // always true for old modules (no return value)
948       myLastActivateStatus = true;
949     }
950     else {
951       // detect return status
952       myLastActivateStatus = PyObject_IsTrue( res1 );
953     }
954   } 
955   
956   // Connect the SUIT_Desktop signal windowActivated() to this->onActiveViewChanged()
957   SUIT_Desktop* aDesk = theStudy->application()->desktop();
958   if ( aDesk )
959   {
960     connect( aDesk, SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
961              this,  SLOT( onActiveViewChanged( SUIT_ViewWindow* ) ) );
962     // If a active window exists send activeViewChanged
963     // If a getActiveView() in SalomePyQt available we no longer need this 
964     SUIT_ViewWindow* aView = aDesk->activeWindow();
965     if ( aView ) 
966       activeViewChanged( aView );
967     
968     // get all view currently opened in the study and connect their signals  to 
969     // the corresponding slots of the class.
970     QList<SUIT_ViewWindow*> wndList = aDesk->windows();
971     SUIT_ViewWindow* wnd;
972     foreach ( wnd, wndList )
973       connectView( wnd );
974   }
975 }
976
977 /*!
978   \brief Additional customization after module is activated:
979
980   Performs the following actions:
981   - get the Python interpreter (one per study)
982   - import the Python GUI module
983   - call Python module's setSettings() method (obsolete function, 
984   used for compatibility with old code)
985
986   \param theStudy parent study object
987 */
988 void SALOME_PYQT_Module::customize( SUIT_Study* theStudy )
989 {
990   FuncMsg fmsg( "SALOME_PYQT_Module::customize()" );
991
992   // get study Id
993   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
994   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
995
996   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
997   initInterp( aStudyId );
998   if ( !myInterp )
999     return; // Error
1000
1001   // import Python GUI module
1002   importModule();
1003   if ( !myModule )
1004     return; // Error
1005
1006   if ( IsCallOldMethods ) {
1007     // call Python module's setWorkspace() method
1008     setWorkSpace();
1009   }
1010
1011   // get python lock
1012   PyLockWrapper aLock = myInterp->GetLockWrapper();
1013
1014   if ( IsCallOldMethods ) {
1015     // call Python module's setSettings() method (obsolete)
1016     if ( PyObject_HasAttrString( myModule , "setSettings" ) ) {
1017       PyObjWrapper res( PyObject_CallMethod( myModule, "setSettings", "" ) );
1018       if( !res ) {
1019         PyErr_Print();
1020       }
1021     }
1022   }
1023 }
1024
1025 /*!
1026   \brief Internal deactivation:
1027
1028   Performs the following actions:
1029   - call Python module's deactivate() method
1030
1031   \param theStudy parent study object
1032 */
1033 void SALOME_PYQT_Module::deactivate( SUIT_Study* theStudy )
1034 {
1035   FuncMsg fmsg( "SALOME_PYQT_Module::deactivate()" );
1036
1037   // check if the subinterpreter is initialized and Python module is imported
1038   if ( !myInterp || !myModule ) {
1039     // Error! Python subinterpreter should be initialized and module should be imported first!
1040     return;
1041   }
1042   // then call Python module's deactivate() method
1043   if ( PyObject_HasAttrString( myModule , "deactivate" ) ) {
1044     PyObjWrapper res( PyObject_CallMethod( myModule, "deactivate", "" ) );
1045     if( !res ) {
1046       PyErr_Print();
1047     }
1048   }
1049   
1050   // Disconnect the SUIT_Desktop signal windowActivated()
1051   SUIT_Desktop* aDesk = theStudy->application()->desktop();
1052   if ( aDesk )
1053   {
1054     disconnect( aDesk, SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
1055                 this,  SLOT( onActiveViewChanged( SUIT_ViewWindow* ) ) );      
1056   }
1057 }
1058
1059 /*!
1060   \brief Perform internal actions when active study is changed.
1061
1062   Called when active the study is actived (user brings its 
1063   desktop to top):
1064   - initialize or get the Python interpreter (one per study)
1065   - import the Python GUI module
1066   - call Python module's activeStudyChanged() method
1067
1068   \param theStudy study being activated
1069 */
1070 void SALOME_PYQT_Module::studyChanged( SUIT_Study* theStudy )
1071 {
1072   FuncMsg fmsg( "SALOME_PYQT_Module::studyChanged()" );
1073
1074   // get study Id
1075   SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
1076   int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
1077
1078   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
1079   initInterp( aStudyId );
1080   if ( !myInterp )
1081     return; // Error
1082
1083   // import Python GUI module
1084   importModule();
1085   if ( !myModule )
1086     return; // Error
1087
1088   if ( IsCallOldMethods ) {
1089     // call Python module's setWorkspace() method
1090     setWorkSpace();
1091   }
1092
1093   // get python lock
1094   PyLockWrapper aLock = myInterp->GetLockWrapper();
1095
1096   // call Python module's activeStudyChanged() method
1097   if ( PyObject_HasAttrString( myModule , "activeStudyChanged" ) ) {
1098     PyObjWrapper res( PyObject_CallMethod( myModule, "activeStudyChanged", "i", aStudyId ) );
1099     if( !res ) {
1100       PyErr_Print();
1101     }
1102   }
1103 }
1104
1105 /*!
1106   \brief Get module engine.
1107
1108   Returns nil var if engine is not found in LifeCycleCORBA.
1109   
1110   \return module's engine reference
1111 */
1112 Engines::Component_var SALOME_PYQT_Module::getEngine() const
1113 {
1114   FuncMsg fmsg( "SALOME_PYQT_Module::getEngine()" );
1115
1116   Engines::Component_var comp;
1117   try {
1118     comp = getApp()->lcc()->FindOrLoad_Component( "FactoryServerPy", name().toLatin1() );
1119   }
1120   catch ( CORBA::Exception& ) {
1121   }
1122   return comp;
1123 }
1124
1125 /*!
1126   \birief Get module engine IOR.
1127
1128   Returns empty string if engine is not found in LifeCycleCORBA.
1129
1130   \return module's engine IOR
1131 */
1132 QString SALOME_PYQT_Module::engineIOR() const
1133 {
1134   FuncMsg fmsg( "SALOME_PYQT_Module::engineIOR()" );
1135
1136   QString anIOR = "";
1137   if ( !CORBA::is_nil( getEngine() ) )
1138     anIOR = getApp()->orb()->object_to_string( getEngine() );
1139   return anIOR;
1140 }
1141
1142 /*!
1143   \brief Process (internally) context popup menu request.
1144
1145   Performs the following actions:
1146   - calls Python module's definePopup(...) method (obsolete function, 
1147   used for compatibility with old code) to define the popup menu context
1148   - parses XML resourses file (if exists) and fills the popup menu with the items)
1149   - calls Python module's customPopup(...) method (obsolete function, 
1150   used for compatibility with old code) to allow module to customize the popup menu
1151   - for new modules calls createPopupMenu() function to allow the 
1152   modules to build the popup menu by using insertItem(...) Qt functions.
1153
1154   \param theContext popup menu context
1155   \param thePopupMenu popup menu
1156 */
1157 void SALOME_PYQT_Module::contextMenu( const QString& theContext, QMenu* thePopupMenu )
1158 {
1159   FuncMsg fmsg( "SALOME_PYQT_Module::contextMenu()" );
1160
1161   // Python interpreter should be initialized and Python module should be
1162   // import first
1163   if ( !myInterp || !myModule )
1164     return;
1165
1166   QString aContext( "" ), aObject( "" ), aParent( theContext );
1167
1168   if ( IsCallOldMethods && PyObject_HasAttrString( myModule , "definePopup" ) ) {
1169     // call definePopup() Python module's function
1170     // this is obsolete function, used only for compatibility reasons
1171     PyObjWrapper res( PyObject_CallMethod( myModule,
1172                                            "definePopup",
1173                                            "sss",
1174                                            aContext.toLatin1().constData(),
1175                                            aObject.toLatin1().constData(),
1176                                            aParent.toLatin1().constData() ) );
1177     if( !res ) {
1178       PyErr_Print();
1179     }
1180     else {
1181       // parse return value
1182       char *co, *ob, *pa;
1183       if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
1184         aContext = co;
1185         aObject  = ob;
1186         aParent  = pa;
1187       }
1188     }
1189   } // if ( IsCallOldMethods ... )
1190
1191   // first try to create menu via XML parser:
1192   // we create popup menus without help of QtxPopupMgr
1193   if ( myXmlHandler )
1194     myXmlHandler->createPopup( thePopupMenu, aContext, aParent, aObject );
1195
1196   PyObjWrapper sipPopup( sipBuildResult( 0, "M", thePopupMenu, sipClass_QMenu ) );
1197
1198   // then call Python module's createPopupMenu() method (for new modules)
1199   if ( PyObject_HasAttrString( myModule , "createPopupMenu" ) ) {
1200     PyObjWrapper res1( PyObject_CallMethod( myModule,
1201                                             "createPopupMenu",
1202                                             "Os",
1203                                             sipPopup.get(),
1204                                             aContext.toLatin1().constData() ) );
1205     if( !res1 ) {
1206       PyErr_Print();
1207     }
1208   }
1209
1210   if ( IsCallOldMethods && PyObject_HasAttrString( myModule , "customPopup" ) ) {
1211     // call customPopup() Python module's function
1212     // this is obsolete function, used only for compatibility reasons
1213     PyObjWrapper res2( PyObject_CallMethod( myModule,
1214                                             "customPopup",
1215                                             "Osss",
1216                                             sipPopup.get(),
1217                                             aContext.toLatin1().constData(),
1218                                             aObject.toLatin1().constData(),
1219                                             aParent.toLatin1().constData() ) );
1220     if( !res2 ) {
1221       PyErr_Print();
1222     }
1223   }
1224 }
1225
1226 /*!
1227   \brief Internal GUI event handling.
1228
1229   Performs the following actions:
1230   - calls Python module's OnGUIEvent() method
1231
1232   \param theId GUI action ID
1233 */
1234 void SALOME_PYQT_Module::guiEvent( const int theId )
1235 {
1236   FuncMsg fmsg( "SALOME_PYQT_Module::guiEvent()" );
1237
1238   // Python interpreter should be initialized and Python module should be
1239   // import first
1240   if ( !myInterp || !myModule )
1241     return;
1242
1243   if ( PyObject_HasAttrString( myModule , "OnGUIEvent" ) ) {
1244     PyObjWrapper res( PyObject_CallMethod( myModule, "OnGUIEvent", "i", theId ) );
1245     if( !res ) {
1246       PyErr_Print();
1247     }
1248   }
1249 }
1250
1251 /*!
1252   \brief Initialize (internally) preferences for the module.
1253
1254   Performs the following actions:
1255   - calls Python module's createPreferences() method
1256 */
1257 void SALOME_PYQT_Module::initPreferences()
1258 {
1259   FuncMsg fmsg( "SALOME_PYQT_Module::initPreferences()" );
1260
1261   // Python interpreter should be initialized and Python module should be
1262   // import first
1263   if ( !myInterp || !myModule )
1264     return;
1265
1266   // temporary set myInitModule because createPreferences() method
1267   // might be called during the module intialization process
1268   myInitModule = this;
1269
1270   if ( PyObject_HasAttrString( myModule , "createPreferences" ) ) {
1271     PyObjWrapper res( PyObject_CallMethod( myModule, "createPreferences", "" ) );
1272     if( !res ) {
1273       PyErr_Print();
1274     }
1275   }
1276
1277   myInitModule = 0;
1278 }
1279
1280 /*!
1281   \brief Initialize python subinterpreter (one per study).
1282   \param theStudyId study ID
1283 */
1284 void SALOME_PYQT_Module::initInterp( int theStudyId )
1285 {
1286   FuncMsg fmsg( "SALOME_PYQT_Module::initInterp()" );
1287
1288   // check study Id
1289   if ( !theStudyId ) {
1290     // Error! Study Id must not be 0!
1291     myInterp = NULL;
1292     return;
1293   }
1294   // try to find the subinterpreter
1295   if( myInterpMap.contains( theStudyId ) ) {
1296     // found!
1297     myInterp = myInterpMap[ theStudyId ];
1298     return;
1299   }
1300   // not found - create a new one!
1301   ///////////////////////////////////////////////////////////////////
1302   // Attention: the creation of Python interpretor must be protected
1303   // by a C++ Lock because of C threads
1304   ///////////////////////////////////////////////////////////////////
1305   myInterp = new SALOME_PYQT_PyInterp();
1306   myInterp->initialize();
1307   myInterpMap[ theStudyId ] = myInterp;
1308
1309   // import 'salome' module and call 'salome_init' method;
1310   // do it only once on interpreter creation
1311   // ... first get python lock
1312   PyLockWrapper aLock = myInterp->GetLockWrapper();
1313   // ... then import a module
1314   PyObjWrapper aMod = PyImport_ImportModule( "salome" );
1315   if( !aMod ) {
1316     // Error!
1317     PyErr_Print();
1318     return;
1319   }
1320   // ... then call a method
1321   int embedded = 1;
1322   PyObjWrapper aRes( PyObject_CallMethod( aMod, "salome_init", "ii", theStudyId, embedded ) );
1323   if( !aRes ) {
1324     // Error!
1325     PyErr_Print();
1326     return;
1327   }
1328 }
1329
1330 /*!
1331   \brief Import Python GUI module and remember the reference to the module.
1332
1333   Attention! initInterp() should be called first!!!
1334 */
1335 void SALOME_PYQT_Module::importModule()
1336 {
1337   FuncMsg fmsg( "SALOME_PYQT_Module::importModule()" );
1338
1339   // check if the subinterpreter is initialized
1340   if ( !myInterp ) {
1341     // Error! Python subinterpreter should be initialized first!
1342     myModule = 0;
1343     return;
1344   }
1345   // import Python GUI module and puts it in <myModule> attribute
1346   // ... first get python lock
1347   PyLockWrapper aLock = myInterp->GetLockWrapper();
1348   // ... then import a module
1349   QString aMod = name() + "GUI";
1350   myModule = PyImport_ImportModule( aMod.toLatin1().data() );
1351   if( !myModule ) {
1352     // Error!
1353     PyErr_Print();
1354     return;
1355   }
1356 }
1357
1358 /*!
1359   \brief Set study workspace to the Python module.
1360
1361   Calls setWorkSpace() method of the Pythohn module with 
1362   PyQt QWidget object to use with interpreter.
1363
1364   Attention! initInterp() and importModule() should be called first!!!
1365 */
1366 void SALOME_PYQT_Module::setWorkSpace()
1367 {
1368   FuncMsg fmsg( "SALOME_PYQT_Module::setWorkSpace()" );
1369
1370   // check if the subinterpreter is initialized and Python module is imported
1371   if ( !myInterp || !myModule ) {
1372     // Error! Python subinterpreter should be initialized and module should be imported first!
1373     return;
1374   }
1375
1376   // call setWorkspace() method
1377   // ... first get python lock
1378   PyLockWrapper aLock = myInterp->GetLockWrapper();
1379
1380   // ... then try to import SalomePyQt module. If it's not possible don't go on.
1381   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
1382   if( !aQtModule ) {
1383     // Error!
1384     PyErr_Print();
1385     return;
1386   }
1387
1388   if ( IsCallOldMethods ) {
1389     // ... then get workspace object
1390     QWidget* aWorkspace = 0;
1391     if ( getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
1392       STD_MDIDesktop* aDesktop = dynamic_cast<STD_MDIDesktop*>( getApp()->desktop() );
1393       if ( aDesktop )
1394         aWorkspace = aDesktop->workspace();
1395     }
1396     else if ( getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
1397       STD_TabDesktop* aDesktop = dynamic_cast<STD_TabDesktop*>( getApp()->desktop() );
1398       if ( aDesktop )
1399         aWorkspace = aDesktop->workstack();
1400     }
1401     PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget ) );
1402     // ... and finally call Python module's setWorkspace() method (obsolete)
1403     if ( PyObject_HasAttrString( myModule , "setWorkSpace" ) ) {
1404       PyObjWrapper res( PyObject_CallMethod( myModule, "setWorkSpace", "O", pyws.get() ) );
1405       if( !res ) {
1406         PyErr_Print();
1407       }
1408     }
1409   }
1410 }
1411
1412 /*!
1413   \brief Preference changing callback function (internal).
1414
1415   Performs the following actions:
1416   - call Python module's preferenceChanged() method
1417
1418   \param section setting section name
1419   \param setting setting name
1420 */
1421 void SALOME_PYQT_Module::prefChanged( const QString& section, const QString& setting )
1422 {
1423   FuncMsg fmsg( "SALOME_PYQT_Module::prefChanged()" );
1424
1425   // Python interpreter should be initialized and Python module should be
1426   // import first
1427   if ( !myInterp || !myModule )
1428     return;
1429
1430   if ( PyObject_HasAttrString( myModule , "preferenceChanged" ) ) {
1431     PyObjWrapper res( PyObject_CallMethod( myModule,
1432                                            "preferenceChanged", 
1433                                            "ss", 
1434                                            section.toLatin1().constData(), 
1435                                            setting.toLatin1().constData() ) );
1436     if( !res ) {
1437       PyErr_Print();
1438     }
1439   }
1440 }
1441
1442 /*!
1443   \brief Get default menu group identifier
1444   \return menu group ID (40 by default)
1445 */
1446 int SALOME_PYQT_Module::defaultMenuGroup()
1447 {
1448   return DEFAULT_GROUP; 
1449 }
1450
1451 //
1452 // The next methods call the parent implementation.
1453 // This is done to open protected methods from CAM_Module class.
1454 //
1455
1456 /*!
1457   \brief Create toolbar with specified \a name.
1458   \param name toolbar name
1459   \return toolbar ID or -1 if toolbar creation is failed
1460 */
1461 int SALOME_PYQT_Module::createTool( const QString& name )
1462 {
1463   return SalomeApp_Module::createTool( name );
1464 }
1465
1466 /*! 
1467   \brief Insert action with specified \a id to the toolbar.
1468   \param id action ID
1469   \param tBar toolbar ID
1470   \param idx required index in the toolbar
1471   \return action ID or -1 if action could not be added
1472 */
1473 int SALOME_PYQT_Module::createTool( const int id, const int tBar, const int idx )
1474 {
1475   return SalomeApp_Module::createTool( id, tBar, idx );
1476 }
1477
1478 /*!
1479   \brief Insert action with specified \a id to the toolbar.
1480   \param id action ID
1481   \param tBar toolbar name
1482   \param idx required index in the toolbar
1483   \return action ID or -1 if action could not be added
1484 */
1485 int SALOME_PYQT_Module::createTool( const int id, const QString& tBar, const int idx )
1486 {
1487   return SalomeApp_Module::createTool( id, tBar, idx );
1488 }
1489
1490 /*!
1491   \brief Insert action to the toolbar.
1492   \param a action
1493   \param tBar toolbar ID
1494   \param id required action ID
1495   \param idx required index in the toolbar
1496   \return action ID or -1 if action could not be added
1497 */
1498 int SALOME_PYQT_Module::createTool( QAction* a, const int tBar, const int id, const int idx )
1499 {
1500   return SalomeApp_Module::createTool( a, tBar, id, idx );
1501 }
1502
1503 /*!
1504   \brief Insert action to the toolbar.
1505   \param a action
1506   \param tBar toolbar name
1507   \param id required action ID
1508   \param idx required index in the toolbar
1509   \return action ID or -1 if action could not be added
1510 */
1511 int SALOME_PYQT_Module::createTool( QAction* a, const QString& tBar, const int id, const int idx )
1512 {
1513   return SalomeApp_Module::createTool( a, tBar, id, idx );
1514 }
1515
1516 /*!
1517   \brief Create main menu.
1518   \param subMenu menu name
1519   \param menu parent menu ID
1520   \param id required menu ID
1521   \param group menu group ID
1522   \param idx required index in the menu
1523   \return menu ID or -1 if menu could not be added
1524 */
1525 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const int menu, const int id, const int group, const int idx )
1526 {
1527   return SalomeApp_Module::createMenu( subMenu, menu, id, group, idx );
1528 }
1529
1530 /*!
1531   \brief Create main menu.
1532   \param subMenu menu name
1533   \param menu parent menu name (list of menu names separated by "|")
1534   \param id required menu ID
1535   \param group menu group ID
1536   \param idx required index in the menu
1537   \return menu ID or -1 if menu could not be added
1538 */
1539 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const QString& menu, const int id, const int group, const int idx )
1540 {
1541   return SalomeApp_Module::createMenu( subMenu, menu, id, group, idx );
1542 }
1543
1544 /*!
1545   \brief Insert action to the main menu.
1546   \param id action ID
1547   \param menu parent menu ID
1548   \param group menu group ID
1549   \param idx required index in the menu
1550   \return action ID or -1 if action could not be added
1551 */
1552 int SALOME_PYQT_Module::createMenu( const int id, const int menu, const int group, const int idx )
1553 {
1554   return SalomeApp_Module::createMenu( id, menu, group, idx );
1555 }
1556
1557 /*!
1558   \brief Insert action to the main menu.
1559   \param id action ID
1560   \param menu parent menu name (list of menu names separated by "|")
1561   \param group menu group ID
1562   \param idx required index in the menu
1563   \return action ID or -1 if action could not be added
1564 */
1565 int SALOME_PYQT_Module::createMenu( const int id, const QString& menu, const int group, const int idx )
1566 {
1567   return SalomeApp_Module::createMenu( id, menu, group, idx );
1568 }
1569
1570 /*!
1571   \brief Insert action to the main menu.
1572   \param a action
1573   \param menu parent menu ID
1574   \param group menu group ID
1575   \param idx required index in the menu
1576   \return action ID or -1 if action could not be added
1577 */
1578 int SALOME_PYQT_Module::createMenu( QAction* a, const int menu, const int id, const int group, const int idx )
1579 {
1580   return SalomeApp_Module::createMenu( a, menu, id, group, idx );
1581 }
1582
1583 /*!
1584   \brief Insert action to the main menu.
1585   \param a action
1586   \param menu parent menu name (list of menu names separated by "|")
1587   \param group menu group ID
1588   \param idx required index in the menu
1589   \return action ID or -1 if action could not be added
1590 */
1591 int SALOME_PYQT_Module::createMenu( QAction* a, const QString& menu, const int id, const int group, const int idx )
1592 {
1593   return SalomeApp_Module::createMenu( a, menu, id, group, idx );
1594 }
1595
1596 /*!
1597   \brief Create separator action which can be used in the menu or toolbar.
1598   \return new separator action
1599 */
1600 QAction* SALOME_PYQT_Module::separator()
1601 {
1602   return SalomeApp_Module::separator();
1603 }
1604
1605 /*!
1606   \brief Get action by specified \a id.
1607   \return action or 0 if it is not found
1608 */
1609 QAction* SALOME_PYQT_Module::action( const int id ) const
1610 {
1611   QAction* a = SalomeApp_Module::action( id );
1612   if ( !a ) {
1613     // try menu
1614     QMenu* m = menuMgr()->findMenu( id );
1615     if ( m ) a = m->menuAction();
1616   }
1617   return a;
1618 }
1619
1620 /*!
1621   \brief Get action identifier.
1622   \return action ID or -1 if action is not registered
1623 */
1624 int SALOME_PYQT_Module::actionId( const QAction* a ) const
1625 {
1626   return SalomeApp_Module::actionId( a );
1627 }
1628
1629 /*!
1630   \brief Create new action.
1631   
1632   If the action with specified identifier already registered
1633   it is not created, but its attributes are only modified.
1634
1635   \param id action ID
1636   \param text tooltip text
1637   \param icon icon
1638   \param menu menu text
1639   \param tip status tip
1640   \param key keyboard shortcut
1641   \param toggle \c true for checkable action
1642   \return created action
1643 */
1644 QAction* SALOME_PYQT_Module::createAction( const int id, const QString& text, const QString& icon,
1645                                            const QString& menu, const QString& tip, const int key,
1646                                            const bool toggle, QObject* parent )
1647 {
1648   QIcon anIcon = loadIcon( icon );
1649   QAction* a = action( id );
1650   if ( a ) {
1651     if ( a->toolTip().isEmpty()   && !text.isEmpty() )  a->setToolTip( text );
1652     if ( a->text().isEmpty()      && !menu.isEmpty() )  a->setText( menu );
1653     if ( a->icon().isNull()       && !anIcon.isNull() ) a->setIcon( anIcon );
1654     if ( a->statusTip().isEmpty() && !tip.isEmpty() )   a->setStatusTip( tip );
1655     if ( a->shortcut().isEmpty()  && key )              a->setShortcut( key );
1656     if ( a->isCheckable() != toggle )                   a->setCheckable( toggle );
1657     disconnect( a, SIGNAL( triggered( bool ) ), this, SLOT( onGUIEvent() ) );
1658     connect(    a, SIGNAL( triggered( bool ) ), this, SLOT( onGUIEvent() ) );
1659   }
1660   else {
1661     a = SalomeApp_Module::createAction( id, 
1662                                         text, 
1663                                         anIcon, 
1664                                         menu, 
1665                                         tip, 
1666                                         key, 
1667                                         parent ? parent : this, 
1668                                         toggle, 
1669                                         this, 
1670                                         SLOT( onGUIEvent() ) );
1671   }
1672   return a;
1673 }
1674
1675 /*!
1676   \brief Create new action group.
1677   
1678   If the action with specified identifier already registered
1679   it is not created, but its attributes are only modified.
1680
1681   \param id action ID
1682   \param text tooltip text
1683   \param icon icon
1684   \param menu menu text
1685   \param tip status tip
1686   \param key keyboard shortcut
1687   \param toggle \c true for checkable action
1688   \return created action
1689 */
1690 QtxActionGroup* SALOME_PYQT_Module::createActionGroup(const int id, const bool exclusive)
1691 {
1692   QtxActionGroup* a = qobject_cast<QtxActionGroup*>( action( id ) );
1693   if ( !a ) {
1694     a = new QtxActionGroup( this );
1695     SalomeApp_Module::registerAction( id, a );
1696   }
1697   a->setExclusive( exclusive );
1698   return a;
1699 }
1700
1701 /*! 
1702   \brief Load icon from resource file.
1703   \param fileName icon file name
1704   \return icon (null icon if loading failed)
1705 */
1706 QIcon SALOME_PYQT_Module::loadIcon( const QString& fileName )
1707 {
1708   QIcon anIcon;
1709   if ( !fileName.isEmpty() ) {
1710     QPixmap pixmap = getApp()->resourceMgr()->loadPixmap( name(), tr( fileName.toLatin1() ) );
1711     if ( !pixmap.isNull() )
1712       anIcon = QIcon( pixmap );
1713   }
1714   return anIcon;
1715 }
1716
1717 /*!
1718   \brief Add global application preference (for example, 
1719   application specific section).
1720   \param label preference name
1721   \return preference ID
1722 */
1723 int SALOME_PYQT_Module::addGlobalPreference( const QString& label )
1724 {
1725   LightApp_Preferences* pref = preferences();
1726   if ( !pref )
1727     return -1;
1728
1729   return pref->addPreference( label, -1 );
1730 }
1731
1732 /*!
1733   \brief Add preference.
1734   \param label preference name
1735   \return preference ID
1736 */
1737 int SALOME_PYQT_Module::addPreference( const QString& label )
1738 {
1739   return SalomeApp_Module::addPreference( label );
1740 }
1741                                        
1742 /*!
1743   \brief Add preference.
1744   \param label preference name
1745   \param pId parent preference ID
1746   \param type preference type
1747   \param section resource file section name
1748   \param param resource file setting name
1749   \return preference ID
1750 */
1751 int SALOME_PYQT_Module::addPreference( const QString& label, 
1752                                        const int pId, const int type,
1753                                        const QString& section,
1754                                        const QString& param )
1755 {
1756   return SalomeApp_Module::addPreference( label, pId, type, section, param );
1757 }
1758
1759 /*!
1760   \brief Get the preference property.
1761   \param id preference ID
1762   \param prop property name
1763   \return property value (invalid QVariant() if property is not found)
1764 */
1765 QVariant SALOME_PYQT_Module::preferenceProperty( const int id, 
1766                                                  const QString& prop ) const
1767 {
1768   QVariant v = SalomeApp_Module::preferenceProperty( id, prop );
1769   return v;
1770 }
1771
1772 /*!
1773   \brief Set the preference property.
1774   \param id preference ID
1775   \param prop property name
1776   \param var property value
1777 */
1778 void SALOME_PYQT_Module::setPreferenceProperty( const int id, 
1779                                                 const QString& prop, 
1780                                                 const QVariant& var )
1781 {
1782   SalomeApp_Module::setPreferenceProperty( id, prop, var );
1783 }
1784
1785
1786 /*!
1787   \brief Signal handler windowActivated(SUIT_ViewWindow*) of SUIT_Desktop
1788   \param pview view being activated
1789 */
1790 void SALOME_PYQT_Module::onActiveViewChanged( SUIT_ViewWindow* pview )
1791 {
1792   class ActiveViewChange : public PyInterp_LockRequest
1793   {
1794   public:
1795     ActiveViewChange( PyInterp_Interp* _py_interp, SALOME_PYQT_Module* _obj, const SUIT_ViewWindow* _pview )
1796       : PyInterp_LockRequest( _py_interp, 0, true ),
1797         myObj(_obj),myView(_pview) {}
1798
1799   protected:
1800     virtual void execute()
1801     {
1802       myObj->activeViewChanged( myView );
1803     }
1804
1805   private:
1806     SALOME_PYQT_Module* myObj;
1807     const SUIT_ViewWindow * myView;
1808   };
1809   
1810   PyInterp_Dispatcher::Get()->Exec( new ActiveViewChange( myInterp, this, pview ) ); 
1811 }
1812
1813 /*!
1814   \brief Processes the view changing, calls Python module's activeViewChanged() method 
1815   \param pview view being activated
1816 */
1817 void SALOME_PYQT_Module::activeViewChanged( const SUIT_ViewWindow* pview )
1818 {
1819   if ( !myInterp || !myModule ) 
1820     return;
1821   
1822   // Do not use SUIT_ViewWindow::closing() signal here. View manager reacts on 
1823   // this signal and deletes view. So our slot does not works if it is connected 
1824   // on this signal. SUIT_ViewManager::deleteView(SUIT_ViewWindow*) is used here
1825   
1826   connectView( pview );
1827
1828   if ( PyObject_HasAttrString( myModule, "activeViewChanged" ) ) 
1829   {
1830     if ( !pview ) 
1831       return;   
1832
1833     PyObjWrapper res( PyObject_CallMethod( myModule, "activeViewChanged", "i" , pview->getId() ) );
1834     if( !res )
1835       PyErr_Print();
1836   }
1837 }
1838
1839 /*!
1840   \brief Signal handler cloneView() of OCCViewer_ViewWindow
1841   \param pview view being cloned
1842 */
1843 void SALOME_PYQT_Module::onViewCloned( SUIT_ViewWindow* pview )
1844 {
1845   class ViewClone : public PyInterp_LockRequest
1846   {
1847   public:
1848     ViewClone( PyInterp_Interp* _py_interp, SALOME_PYQT_Module* _obj, const SUIT_ViewWindow* _pview )
1849       : PyInterp_LockRequest( _py_interp, 0, true ),
1850         myObj(_obj), myView(_pview) {}
1851
1852   protected:
1853     virtual void execute()
1854     {
1855       myObj->viewCloned( myView );
1856     }
1857
1858   private:
1859     SALOME_PYQT_Module* myObj;    
1860     const SUIT_ViewWindow* myView;
1861   };
1862   
1863   PyInterp_Dispatcher::Get()->Exec( new ViewClone( myInterp, this, pview ) );
1864 }
1865
1866 /*!
1867   \brief Processes the view cloning, calls Python module's activeViewCloned() method
1868   \param pview view being cloned
1869 */
1870 void SALOME_PYQT_Module::viewCloned( const SUIT_ViewWindow* pview )
1871 {
1872   if ( !myInterp || !myModule || !pview ) 
1873     return;  
1874
1875   if ( PyObject_HasAttrString( myModule, "viewCloned" ) ) 
1876   {
1877     PyObjWrapper res( PyObject_CallMethod( myModule, "viewCloned", "i", pview->getId() ) );
1878     if( !res )
1879       PyErr_Print();
1880   }
1881 }
1882
1883 /*!
1884   \brief Signal handler closing(SUIT_ViewWindow*) of a view
1885   \param pview view being closed
1886 */
1887 void SALOME_PYQT_Module::onViewClosed( SUIT_ViewWindow* pview )
1888 {
1889   class ViewClose : public PyInterp_LockRequest
1890   {
1891   public:
1892     ViewClose( PyInterp_Interp* _py_interp, SALOME_PYQT_Module* _obj, const SUIT_ViewWindow* _pview )
1893       : PyInterp_LockRequest( _py_interp, 0, true ),
1894         myObj(_obj),myView(_pview) {}
1895
1896   protected:
1897     virtual void execute()
1898     {
1899       myObj->viewClosed( myView );
1900     }
1901
1902   private:
1903     SALOME_PYQT_Module* myObj;
1904     const SUIT_ViewWindow * myView;    
1905   };
1906
1907   PyInterp_Dispatcher::Get()->Exec( new ViewClose( myInterp, this, pview ) );
1908 }
1909
1910 /*!
1911   \brief Processes the view closing, calls Python module's viewClosed() method
1912   \param pview view being closed
1913 */
1914 void SALOME_PYQT_Module::viewClosed( const SUIT_ViewWindow* pview )
1915 {
1916   if ( !myInterp || !myModule ) 
1917     return;  
1918
1919   if ( PyObject_HasAttrString( myModule, "viewClosed" ) ) 
1920   {
1921     PyObjWrapper res( PyObject_CallMethod( myModule, "viewClosed", "i", pview->getId() ) );
1922     if ( !res )
1923     {
1924       PyErr_Print();
1925     }
1926   }
1927 }
1928
1929 /*!
1930   \brief Connects or disconnects signals about activating and cloning view on the module slots
1931   \param pview view which is connected/disconnected
1932 */
1933 void SALOME_PYQT_Module::connectView( const SUIT_ViewWindow* pview )
1934 {
1935   SUIT_ViewManager* viewMgr = pview->getViewManager();
1936   SUIT_ViewModel* viewModel = viewMgr ? viewMgr->getViewModel() : 0;
1937       
1938   if ( viewMgr )
1939   {
1940     disconnect( viewMgr, SIGNAL( deleteView( SUIT_ViewWindow* ) ),
1941                this, SLOT( onViewClosed( SUIT_ViewWindow* ) ) );
1942   
1943     connect( viewMgr, SIGNAL( deleteView( SUIT_ViewWindow* ) ),
1944              this, SLOT( onViewClosed( SUIT_ViewWindow* ) ) );
1945   }
1946   
1947   // Connect cloneView() signal of an OCC View
1948   if ( pview->inherits( "OCCViewer_ViewWindow" ) )
1949   {
1950     disconnect( pview, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
1951                 this, SLOT( onViewCloned( SUIT_ViewWindow* ) ) );
1952     connect( pview, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
1953              this, SLOT( onViewCloned( SUIT_ViewWindow* ) ) );
1954   }
1955   // Connect cloneView() signal of Plot2d View manager
1956   else if ( viewModel && viewModel->inherits( "Plot2d_Viewer" ) )
1957   {
1958     disconnect( viewModel, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
1959                 this, SLOT( onViewCloned( SUIT_ViewWindow* ) ) );
1960     connect( viewModel, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
1961              this, SLOT( onViewCloned( SUIT_ViewWindow* ) ) );
1962   }
1963 }
1964
1965 /*!
1966   \brief Get tag name for the DOM element.
1967   \param element DOM element
1968   \return empty string if the element does not have tag name
1969   \internal
1970 */
1971 static QString tagName( const QDomElement& element )
1972 {
1973   return element.tagName().trimmed();
1974 }
1975
1976 /*!
1977   \brief Get DOM element's attribute by its name.
1978   \param element DOM element
1979   \param attName attribute name
1980   \return empty string if the element does not have such attribute
1981   \internal
1982 */
1983 static QString attribute( const QDomElement& element, const QString& attName )
1984 {
1985   return element.attribute( attName ).trimmed();
1986 }
1987
1988 /*!
1989   \brief Inspect specified string for the boolean value.
1990   
1991   This function returns \c true if string represents boolean value: 
1992   - "true", "yes" or "1" for \c true
1993   - "false", "no" or "0" for \c false
1994   Second parameter allows to specify what boolean value is expected:
1995   - 1: \c true
1996   - 0: \c false
1997   - other value is not taken into account (return represented value)
1998
1999   \param value inspected string
2000   \param check expected boolean value
2001   \return boolean value represented by the string (\a check is not 1 or 0)
2002           or \c true if value correspond to the specified \a check
2003 */
2004 static bool checkBool( const QString& value, const int check = -1 )
2005 {
2006   QString v = value.toLower();
2007   if ( ( v == "true"  || v == "yes" || v == "1" ) && ( check != 0 ) )
2008     return true;
2009   if ( ( v == "false" || v == "no"  || v == "0" ) && ( check == 0 ) )
2010     return true;
2011   return false;
2012 }
2013
2014 /*!
2015   \brief Inspect specified string for the integer value.
2016   
2017   This function returns returns -1 if item is empty or represents
2018   an invalid number.
2019   \param value inspected string
2020   \param def default value
2021   \param shift shift value (it is added to the integer value to produce shifted result)
2022 */
2023 static int checkInt( const QString& value, const int def = -1, const int shift = -1 )
2024 {
2025   bool bOk;
2026   int val = value.toInt( &bOk );
2027   if ( !bOk ) val = def;
2028   if ( shift > 0 && bOk && val < 0 )
2029     val += shift;
2030   return val;
2031 }
2032
2033 /*!
2034   \brief Constructor
2035   \internal
2036   \param module parent module pointer
2037   \param fileName XML file path
2038 */
2039 SALOME_PYQT_Module::XmlHandler::XmlHandler( SALOME_PYQT_Module* module, 
2040                                             const QString&      fileName )
2041 : myModule( module )
2042 {
2043   if ( fileName.isEmpty() ) 
2044     return;
2045   QFile aFile( fileName );
2046   if ( !aFile.open( QIODevice::ReadOnly ) )
2047     return;
2048   myDoc.setContent( &aFile );
2049   aFile.close();
2050 }
2051
2052 /*!
2053   \brief Parse XML file and create actions.
2054   \internal
2055   
2056   Called by SALOME_PYQT_Module::activate() in order to create actions
2057   (menus, toolbars).
2058 */
2059 void SALOME_PYQT_Module::XmlHandler::createActions()
2060 {
2061   // get document element
2062   QDomElement aDocElem = myDoc.documentElement();
2063
2064   // create main menu actions
2065   QDomNodeList aMenuList = aDocElem.elementsByTagName( "menu-item" );
2066   for ( int i = 0; i < aMenuList.count(); i++ ) {
2067     QDomNode n = aMenuList.item( i );
2068     createMenu( n );
2069   }
2070
2071   // create toolbars actions
2072   QDomNodeList aToolsList = aDocElem.elementsByTagName( "toolbar" );
2073   for ( int i = 0; i < aToolsList.count(); i++ ) {
2074     QDomNode n = aToolsList.item( i );
2075     createToolBar( n );
2076   }
2077 }
2078
2079 /*!
2080   \brief Create popup menu.
2081   \internal
2082   \param menu popup menu
2083   \param context popup menu context
2084   \param context popup menu parent object name
2085   \param context popup menu object name
2086 */
2087 void SALOME_PYQT_Module::XmlHandler::createPopup( QMenu*         menu,
2088                                                   const QString& context,
2089                                                   const QString& parent,
2090                                                   const QString& object )
2091 {
2092   // get document element
2093   QDomElement aDocElem = myDoc.documentElement();
2094
2095   // get popup menus actions
2096   QDomNodeList aPopupList = aDocElem.elementsByTagName( "popupmenu" );
2097   for ( int i = 0; i < aPopupList.count(); i++ ) {
2098     QDomNode n = aPopupList.item( i );
2099     if ( !n.isNull() && n.isElement() ) {
2100       QDomElement e = n.toElement();
2101       // QString lab = attribute( e, "label-id" ); // not used // 
2102       QString ctx = attribute( e, "context-id" );
2103       QString prt = attribute( e, "parent-id"  );
2104       QString obj = attribute( e, "object-id"  );
2105       if ( ctx == context && prt == parent && obj == object )  {
2106         insertPopupItems( n, menu );
2107         break;
2108       }
2109     }
2110   }
2111 }
2112
2113 /*!
2114   \brief Activate menus
2115   \internal
2116   \param enable if \c true menus are activated, otherwise menus are deactivated
2117 */
2118 void SALOME_PYQT_Module::XmlHandler::activateMenus( bool enable )
2119 {
2120   if ( !myModule )
2121     return;
2122
2123   QtxActionMenuMgr* mgr = myModule->menuMgr();
2124   int id;
2125   foreach( id, myMenuItems ) mgr->setEmptyEnabled( id, enable );
2126 }
2127
2128 /*!
2129   \brief Create main menu item and insert actions to it.
2130   \internal
2131   \param parentNode XML node with menu description
2132   \param parentMenuId parent menu ID (-1 for top-level menu)
2133   \param parentPopup parent popup menu (0 for top-level menu)
2134 */
2135 void SALOME_PYQT_Module::XmlHandler::createMenu( QDomNode& parentNode, 
2136                                                  const int parentMenuId, 
2137                                                  QMenu*    parentPopup )
2138 {
2139   if ( !myModule || parentNode.isNull() )
2140     return;
2141   
2142   QDomElement parentElement = parentNode.toElement();
2143   if ( !parentElement.isNull() ) {
2144     QString plabel = attribute( parentElement, "label-id" );
2145     int     pid    = checkInt( attribute( parentElement, "item-id" ) );
2146     int     ppos   = checkInt( attribute( parentElement, "pos-id" ) );
2147     int     group  = checkInt( attribute( parentElement, "group-id" ), 
2148                                myModule->defaultMenuGroup() );
2149     if ( !plabel.isEmpty() ) {
2150       QMenu* popup = 0;
2151       int menuId = -1;
2152       // create menu
2153       menuId = myModule->createMenu( plabel,         // label
2154                                      parentMenuId,   // parent menu ID, -1 for top-level menu
2155                                      pid,            // ID
2156                                      group,          // group ID
2157                                      ppos );         // position
2158       myMenuItems.append( menuId );
2159       QDomNode node = parentNode.firstChild();
2160       while ( !node.isNull() ) {
2161         if ( node.isElement() ) {
2162           QDomElement elem = node.toElement();
2163           QString aTagName = tagName( elem );
2164           if ( aTagName == "popup-item" ) {
2165             int     id      = checkInt( attribute( elem, "item-id" ) );
2166             int     pos     = checkInt( attribute( elem, "pos-id" ) );
2167             int     group   = checkInt( attribute( elem, "group-id" ), 
2168                                         myModule->defaultMenuGroup() );
2169             QString label   = attribute( elem, "label-id" );
2170             QString icon    = attribute( elem, "icon-id" );
2171             QString tooltip = attribute( elem, "tooltip-id" );
2172             QString accel   = attribute( elem, "accel-id" );
2173             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
2174
2175             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
2176             // also check if the action with given ID is already created
2177             if ( id != -1 ) {
2178               // create menu action
2179               QAction* action = myModule->createAction( id,                     // ID
2180                                                         tooltip,                // tooltip
2181                                                         icon,                   // icon
2182                                                         label,                  // menu text
2183                                                         tooltip,                // status-bar text
2184                                                         QKeySequence( accel ),  // keyboard accelerator
2185                                                         toggle );               // toogled action
2186               myModule->createMenu( action,   // action
2187                                     menuId,   // parent menu ID
2188                                     id,       // ID (same as for createAction())
2189                                     group,    // group ID
2190                                     pos );    // position
2191             }
2192           }
2193           else if ( aTagName == "submenu" ) {
2194             // create sub-menu
2195             createMenu( node, menuId, popup );
2196           }
2197           else if ( aTagName == "separator" ) {
2198             // create menu separator
2199             int id    = checkInt( attribute( elem, "item-id" ) ); // separator can have ID
2200             int pos   = checkInt( attribute( elem, "pos-id" ) );
2201             int group = checkInt( attribute( elem, "group-id" ), 
2202                                   myModule->defaultMenuGroup() );
2203             QAction* action = myModule->separator();
2204             myModule->createMenu( action,  // separator action
2205                                   menuId,  // parent menu ID
2206                                   id,      // ID
2207                                   group,   // group ID
2208                                   pos );   // position
2209           }
2210         }
2211         node = node.nextSibling();
2212       }
2213     }
2214   }
2215 }
2216
2217 /*!
2218   \brief Create a toolbar and insert actions to it.
2219   \param parentNode XML node with toolbar description
2220 */
2221 void SALOME_PYQT_Module::XmlHandler::createToolBar( QDomNode& parentNode )
2222 {
2223   if ( !myModule || parentNode.isNull() )
2224     return;
2225
2226   QDomElement parentElement = parentNode.toElement();
2227   if ( !parentElement.isNull() ) {
2228     QString aLabel = attribute( parentElement, "label-id" );
2229     if ( !aLabel.isEmpty() ) {
2230       // create toolbar
2231       int tbId = myModule->createTool( aLabel );
2232       QDomNode node = parentNode.firstChild();
2233       while ( !node.isNull() ) {
2234         if ( node.isElement() ) {
2235           QDomElement elem = node.toElement();
2236           QString aTagName = tagName( elem );
2237           if ( aTagName == "toolbutton-item" ) {
2238             int     id      = checkInt( attribute( elem, "item-id" ) );
2239             int     pos     = checkInt( attribute( elem, "pos-id" ) );
2240             QString label   = attribute( elem, "label-id" );
2241             QString icon    = attribute( elem, "icon-id" );
2242             QString tooltip = attribute( elem, "tooltip-id" );
2243             QString accel   = attribute( elem, "accel-id" );
2244             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
2245
2246             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
2247             // also check if the action with given ID is already created
2248             if ( id != -1 ) {
2249               // create toolbar action
2250               QAction* action = myModule->createAction( id,                    // ID
2251                                                         tooltip,               // tooltip
2252                                                         icon,                  // icon
2253                                                         label,                 // menu text
2254                                                         tooltip,               // status-bar text
2255                                                         QKeySequence( accel ), // keyboard accelerator
2256                                                         toggle );              // toogled action
2257               myModule->createTool( action, tbId, -1, pos );
2258             }
2259           }
2260           else if ( aTagName == "separatorTB" || aTagName == "separator" ) {
2261             // create toolbar separator
2262             int pos = checkInt( attribute( elem, "pos-id" ) );
2263             QAction* action = myModule->separator();
2264             myModule->createTool( action, tbId, -1, pos );
2265           }
2266         }
2267         node = node.nextSibling();
2268       }
2269     }
2270   }
2271 }
2272
2273 /*!
2274   \brief Fill popup menu with the items.
2275   \param parentNode XML node with popup menu description
2276   \param menu popup menu
2277 */
2278 void SALOME_PYQT_Module::XmlHandler::insertPopupItems( QDomNode& parentNode, QMenu* menu )
2279 {
2280   if ( !myModule && parentNode.isNull() )
2281     return;
2282
2283   // we create popup menus without help of QtxPopupMgr
2284   QDomNode node = parentNode.firstChild();
2285   while ( !node.isNull() ) { 
2286     if ( node.isElement() ) {
2287       QDomElement elem = node.toElement();
2288       QString aTagName = tagName( elem );
2289       QList<QAction*> actions = menu->actions();
2290       if ( aTagName == "popup-item" ) {
2291         // insert a command item
2292         int     id      = checkInt( attribute( elem, "item-id" ) );
2293         int     pos     = checkInt( attribute( elem, "pos-id" ) );
2294         QString label   = attribute( elem, "label-id" );
2295         QString icon    = attribute( elem, "icon-id" );
2296         QString tooltip = attribute( elem, "tooltip-id" );
2297         QString accel   = attribute( elem, "accel-id" );
2298         bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
2299
2300         // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
2301         // also check if the action with given ID is already created
2302         if ( id != -1 ) {
2303           QAction* action = myModule->createAction( id,                     // ID
2304                                                     tooltip,                // tooltip
2305                                                     icon,                   // icon
2306                                                     label,                  // menu text
2307                                                     tooltip,                // status-bar text
2308                                                     QKeySequence( accel ),  // keyboard accelerator
2309                                                     toggle );               // toogled action
2310           QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
2311           menu->insertAction( before, action );
2312         }
2313       }
2314       else if ( aTagName == "submenu" ) {
2315         // create sub-menu
2316         ////int     id    = checkInt( attribute( elem, "item-id" ) ); // not used //
2317         int     pos   = checkInt( attribute( elem, "pos-id" ) );
2318         QString label = attribute( elem, "label-id" );
2319         QString icon  = attribute( elem, "icon-id" );
2320
2321         QIcon anIcon;
2322         if ( !icon.isEmpty() ) {
2323           QPixmap pixmap  = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(), icon );
2324           if ( !pixmap.isNull() )
2325             anIcon = QIcon( pixmap );
2326         }
2327
2328         QMenu* newPopup = menu->addMenu( anIcon, label );
2329         QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
2330         menu->insertMenu( before, newPopup );
2331         insertPopupItems( node, newPopup );
2332       }
2333       else if ( aTagName == "separator" ) {
2334         // create menu separator
2335         int pos = checkInt( attribute( elem, "pos-id" ) );
2336         QAction* action = myModule->separator();
2337         QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
2338         menu->insertAction( before, action );
2339       }
2340     }
2341     node = node.nextSibling();
2342   }
2343 }