1 // Copyright (C) 2005 OPEN CASCADE, CEA/DEN, EDF R&D, PRINCIPIA R&D
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License.
8 // This library is distributed in the hope that it will be useful
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Lesser General Public License for more details.
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 // See http://www.salome-platform.org/
19 //=============================================================================
20 // File : SALOME_PYQT_Module.cxx
22 // Author : Vadim SANDLER
24 // Copyright : 2003-2005 CEA/DEN, EDF R&D
26 //=============================================================================
28 #include "SALOME_PYQT_Module.h"
30 #include "PyInterp_Dispatcher.h"
31 #include "SUIT_ResourceMgr.h"
32 #include "STD_MDIDesktop.h"
33 #include "STD_TabDesktop.h"
34 #include "SalomeApp_Application.h"
35 #include "SalomeApp_Study.h"
37 #include "QtxWorkstack.h"
38 #include "QtxActionMenuMgr.h"
39 #include "QtxActionToolMgr.h"
40 #include <SALOME_LifeCycleCORBA.hxx>
41 #include <Container_init_python.hxx>
45 #include <qworkspace.h>
46 #include <qpopupmenu.h>
48 #include "SALOME_PYQT_SipDefs.h"
49 #if defined(SIP_VERS_v4_old) || defined(SIP_VERS_v4_new)
50 #include "sipAPISalomePyQtGUI.h"
52 #include "sipSalomePyQtGUIDeclSalomePyQtGUI.h"
55 #include <sipqtQWidget.h>
56 #include <sipqtQPopupMenu.h>
62 ///////////////////////////////////////////////////////////////////////////////
63 // Default name of the module, replaced at the moment of module creation
64 const char* __DEFAULT_NAME__ = "SALOME_PYQT_Module";
66 ///////////////////////////////////////////////////////////////////////////////
67 // Default menu group number
68 const int __DEFAULT_GROUP__ = 40;
70 ///////////////////////////////////////////////////////////////////////////////
71 // If __CALL_OLD_METHODS__ macro is not defined the invoking of obsolete Python
72 // module's methods like setSetting(), definePopup(), etc. is blocked.
73 // This macro is defined by default (in Makefile)
74 #ifdef __CALL_OLD_METHODS__
75 const bool IsCallOldMethods = true;
77 const bool IsCallOldMethods = false;
80 ///////////////////////////////////////////////////////////////////////////////
81 // NB: Python requests.
82 // General rule for Python requests created by SALOME_PYQT_Module:
83 // all requests should be executed SYNCHRONOUSLY within the main GUI thread.
84 // However, it is obligatory that ANY Python call is wrapped with a request object,
85 // so that ALL Python API calls are serialized with PyInterp_Dispatcher.
86 ///////////////////////////////////////////////////////////////////////////////
88 //=============================================================================
89 // The class for parsing of the XML resource files.
90 // Used for backward compatibility with existing Python modules.
91 //=============================================================================
92 class SALOME_PYQT_XmlHandler
95 SALOME_PYQT_XmlHandler( SALOME_PYQT_Module* module, const QString& fileName );
97 void createPopup ( QPopupMenu* menu,
98 const QString& context,
99 const QString& parent,
100 const QString& object );
103 void createToolBar ( QDomNode& parentNode );
104 void createMenu ( QDomNode& parentNode,
105 const int parentMenuId = -1 );
107 void insertPopupItems( QDomNode& parentNode,
111 SALOME_PYQT_Module* myModule;
115 //=============================================================================
116 // SALOME_PYQT_Module class implementation (implements CAM_Module API for
117 // all Python-based SALOME module
118 //=============================================================================
120 // While the SalomePyQtGUI library is not imported in Python it's initialization function
121 // should be called manually (and only once) in order to initialize global sip data
122 // and to get C API from sip : sipBuildResult for example
123 #if defined(SIP_VERS_v4_old) || defined(SIP_VERS_v4_new)
124 #define INIT_FUNCTION initSalomePyQtGUI
125 #if defined(SIP_STATIC_MODULE)
126 extern "C" void INIT_FUNCTION();
128 PyMODINIT_FUNC INIT_FUNCTION();
131 #define INIT_FUNCTION initlibSalomePyQtGUIc
132 extern "C" void INIT_FUNCTION();
136 * This function creates an instance of SALOME_PYQT_Module object by request
137 * of and application object when the module is loaded.
140 SALOME_PYQT_EXPORT CAM_Module* createModule() {
141 static bool alreadyInitialized = false;
142 if ( !alreadyInitialized ) {
143 // call only once (see above) !
144 PyEval_RestoreThread( KERNEL_PYTHON::_gtstate );
146 PyEval_ReleaseThread( KERNEL_PYTHON::_gtstate );
147 alreadyInitialized = !alreadyInitialized;
149 return new SALOME_PYQT_Module();
154 * Static variables definition
156 SALOME_PYQT_Module::InterpMap SALOME_PYQT_Module::myInterpMap;
157 SALOME_PYQT_Module* SALOME_PYQT_Module::myInitModule = 0;
160 * Little trick : provide an access to being activated Python module from outside;
161 * needed by the SalomePyQt library :(
163 SALOME_PYQT_Module* SALOME_PYQT_Module::getInitModule()
171 SALOME_PYQT_Module::SALOME_PYQT_Module() :
172 SalomeApp_Module( __DEFAULT_NAME__ ), myModule( 0 ), myXmlHandler ( 0 )
179 SALOME_PYQT_Module::~SALOME_PYQT_Module()
186 * Initialization of the module.
187 * Inherited from CAM_Module.
189 * This method is used for creation of the menus, toolbars and other staff.
190 * There are two ways:
191 * - for obsolete modules this method first tries to read <module>_<language>.xml
192 * resource file which contains a menu, toolbars and popup menus description.
193 * - new modules can create menus by by calling the corresponding methods of SalomePyQt
194 * Python API in the Python module's initialize() method which is called from here.
195 * NOTE: if postponed modules loading is not used, the active study might be not defined
196 * yet at this stage, so initialize() method should not perform any study-based initialization.
198 void SALOME_PYQT_Module::initialize( CAM_Application* app )
200 SalomeApp_Module::initialize( app );
202 // Try to get XML resource file name
203 SUIT_ResourceMgr* aResMgr = getApp()->resourceMgr();
204 QString aLang = aResMgr->stringValue( "language", "language", QString::null );
205 if ( aLang.isEmpty() ) aLang = QString( "en" );
206 QString aName = name( "" );
207 QString aFileName = aName + "_" + aLang + ".xml";
208 aFileName = aResMgr->path( "resources", aName, aFileName );
210 // parse XML file if it is found and create actions
211 if ( !myXmlHandler && !aFileName.isEmpty() ) {
212 myXmlHandler = new SALOME_PYQT_XmlHandler( this, aFileName );
213 myXmlHandler->createActions();
216 // perform internal initialization and call module's initialize() method
217 // InitializeReq: request class for internal init() operation
218 class InitializeReq : public PyInterp_Request
221 InitializeReq( CAM_Application* _app,
222 SALOME_PYQT_Module* _obj )
223 : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
228 virtual void execute()
230 myObj->init( myApp );
234 CAM_Application* myApp;
235 SALOME_PYQT_Module* myObj;
238 // Posting the request
239 PyInterp_Dispatcher::Get()->Exec( new InitializeReq( app, this ) );
243 * Activation of the module.
244 * Inherited from CAM_Module.
246 bool SALOME_PYQT_Module::activateModule( SUIT_Study* theStudy )
248 MESSAGE( "SALOME_PYQT_Module::activateModule" );
250 bool res = SalomeApp_Module::activateModule( theStudy );
255 // ActivateReq: request class for internal activate() operation
256 class ActivateReq : public PyInterp_Request
259 ActivateReq( SUIT_Study* _study,
260 SALOME_PYQT_Module* _obj )
261 : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
266 virtual void execute()
268 myObj->activate( myStudy );
273 SALOME_PYQT_Module* myObj;
276 // Posting the request
277 PyInterp_Dispatcher::Get()->Exec( new ActivateReq( theStudy, this ) );
279 // activate menus, toolbars, etc
280 setMenuShown( true );
281 setToolShown( true );
284 connect( menuMgr(), SIGNAL( menuHighlighted( int, int ) ),
285 this, SLOT( onMenuHighlighted( int, int ) ) );
291 * Deactivation of the module.
292 * Inherited from CAM_Module.
294 bool SALOME_PYQT_Module::deactivateModule( SUIT_Study* theStudy )
296 MESSAGE( "SALOME_PYQT_Module::deactivateModule" );
299 disconnect( menuMgr(), SIGNAL( menuHighlighted( int, int ) ),
300 this, SLOT( onMenuHighlighted( int, int ) ) );
302 bool res = SalomeApp_Module::deactivateModule( theStudy );
304 // deactivate menus, toolbars, etc
305 setMenuShown( false );
306 setToolShown( false );
308 // DeactivateReq: request class for internal deactivate() operation
309 class DeactivateReq : public PyInterp_LockRequest
312 DeactivateReq( PyInterp_base* _py_interp,
314 SALOME_PYQT_Module* _obj )
315 : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
320 virtual void execute()
322 myObj->deactivate( myStudy );
327 SALOME_PYQT_Module* myObj;
330 // Posting the request
331 PyInterp_Dispatcher::Get()->Exec( new DeactivateReq( myInterp, theStudy, this ) );
337 * Processes GUI action (from main menu, toolbar or context popup menu)
339 void SALOME_PYQT_Module::onGUIEvent()
342 const QObject* obj = sender();
343 if ( !obj || !obj->inherits( "QAction" ) )
345 QAction* action = (QAction*)obj;
348 int id = actionId( action );
349 MESSAGE( "SALOME_PYQT_Module::onGUIEvent: id = " << id );
351 // perform synchronous request to Python event dispatcher
352 class GUIEvent : public PyInterp_LockRequest
355 GUIEvent( PyInterp_base* _py_interp,
356 SALOME_PYQT_Module* _obj,
358 : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
363 virtual void execute()
365 myObj->guiEvent( myId );
370 SALOME_PYQT_Module* myObj;
373 // Posting the request
374 PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
378 * Processes GUI action (from context popup menu, only for XML-based actions!)
380 void SALOME_PYQT_Module::onGUIEvent( int id )
382 // perform synchronous request to Python event dispatcher
383 class GUIEvent : public PyInterp_LockRequest
386 GUIEvent( PyInterp_base* _py_interp,
387 SALOME_PYQT_Module* _obj,
389 : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
394 virtual void execute()
396 myObj->guiEvent( myId );
401 SALOME_PYQT_Module* myObj;
404 // Posting the request
405 PyInterp_Dispatcher::Get()->Exec( new GUIEvent( myInterp, this, id ) );
409 * Menu highlight processing
411 void SALOME_PYQT_Module::onMenuHighlighted( int menu, int submenu )
413 if ( !action( menu ) && registered( menu, submenu ) ) {
414 // perform synchronous request to Python event dispatcher
415 class MenuHighlightEvent : public PyInterp_LockRequest
418 MenuHighlightEvent( PyInterp_base* _py_interp,
419 SALOME_PYQT_Module* _obj,
422 : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
424 mySubMenu( _submenu ),
428 virtual void execute()
430 myObj->menuHighlight( myMenu, mySubMenu );
436 SALOME_PYQT_Module* myObj;
439 // Posting the request
440 PyInterp_Dispatcher::Get()->Exec( new MenuHighlightEvent( myInterp, this, menu, submenu ) );
445 Context popup menu request.
446 Called when user activates popup menu in some window (view, object browser, etc).
448 void SALOME_PYQT_Module::contextMenuPopup( const QString& theContext, QPopupMenu* thePopupMenu, QString& /*title*/ )
450 MESSAGE( "SALOME_PYQT_Module::contextMenuPopup : " << theContext.latin1() );
451 // perform synchronous request to Python event dispatcher
452 class PopupMenuEvent : public PyInterp_LockRequest
455 PopupMenuEvent( PyInterp_base* _py_interp,
456 SALOME_PYQT_Module* _obj,
457 const QString& _context,
459 : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
460 myContext( _context ),
465 virtual void execute()
467 myObj->contextMenu( myContext, myPopup );
471 SALOME_PYQT_Module* myObj;
476 // Posting the request only if dispatcher is not busy!
477 // Executing the request synchronously
478 if ( !PyInterp_Dispatcher::Get()->IsBusy() )
479 PyInterp_Dispatcher::Get()->Exec( new PopupMenuEvent( myInterp, this, theContext, thePopupMenu ) );
483 * Defines the dockable window associated with the module.
484 * To fill the list of windows the correspondind Python module's windows()
485 * method is called from SALOME_PYQT_Module::init() method.
486 * By default, ObjectBrowser, PythonConsole and LogWindow are provided.
488 void SALOME_PYQT_Module::windows( QMap<int, int>& mappa ) const
490 // First clear the output parameters
491 QMap<int, int>::ConstIterator it;
492 for ( it = myWindowsMap.begin(); it != myWindowsMap.end(); ++it ) {
493 mappa[ it.key() ] = it.data();
498 * Defines the compatible views which should be opened on module activation.
499 * To fill the list of views the correspondind Python module's views()
500 * method is called from SALOME_PYQT_Module::init() method.
501 * By default, the list is empty.
503 void SALOME_PYQT_Module::viewManagers( QStringList& listik ) const
505 for ( QStringList::ConstIterator it = myViewMgrList.begin(); it != myViewMgrList.end(); ++it ) {
506 listik.append( *it );
511 * Performs internal initialization
512 * - initializes/gets the Python interpreter (one per study)
513 * - imports the Python module
514 * - passes the workspace widget to the Python module
515 * - calls Python module's initialize() method
516 * - calls Python module's windows() method
517 * - calls Python module's views() method
519 void SALOME_PYQT_Module::init( CAM_Application* app )
521 // reset interpreter to NULL
525 SalomeApp_Application* anApp = dynamic_cast<SalomeApp_Application*>( app );
529 SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
532 int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
534 // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
535 initInterp( aStudyId );
539 // import Python GUI module
546 if ( IsCallOldMethods ) { // __CALL_OLD_METHODS__
547 // call Python module's setWorkspace() method
549 } //__CALL_OLD_METHODS__
551 // then call Python module's initialize() method
552 // ... first get python lock
553 PyLockWrapper aLock = myInterp->GetLockWrapper();
554 // ... (the Python module is already imported)
555 // ... finally call Python module's initialize() method
556 if(PyObject_HasAttrString(myModule , "initialize")){
557 PyObjWrapper res( PyObject_CallMethod( myModule, "initialize", "" ) );
563 // get the windows list from the Python module by calling windows() method
564 // ... first put default values
565 myWindowsMap.insert( SalomeApp_Application::WT_ObjectBrowser, Qt::DockLeft );
566 myWindowsMap.insert( SalomeApp_Application::WT_PyConsole, Qt::DockBottom );
567 // VSR: LogWindow is not yet implemented
568 // myWindowsMap.insert( SalomeApp_Application::WT_LogWindow, Qt::DockBottom );
570 if(PyObject_HasAttrString(myModule , "windows")){
571 PyObjWrapper res1( PyObject_CallMethod( myModule, "windows", "" ) );
576 myWindowsMap.clear();
577 if ( PyDict_Check( res1 ) ) {
581 while ( PyDict_Next( res1, &pos, &key, &value ) ) {
582 // parse the return value
583 // it should be a map: {integer:integer}
585 if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
586 aKey = PyInt_AsLong( key );
587 aValue = PyInt_AsLong( value );
588 myWindowsMap[ aKey ] = aValue;
594 // get the windows list from the Python module by calling views() method
595 if(PyObject_HasAttrString(myModule , "views")){
596 PyObjWrapper res2( PyObject_CallMethod( myModule, "views", "" ) );
601 // parse the return value
602 // result can be one string...
603 if ( PyString_Check( res2 ) ) {
604 myViewMgrList.append( PyString_AsString( res2 ) );
606 // ... or list of strings
607 else if ( PyList_Check( res2 ) ) {
608 int size = PyList_Size( res2 );
609 for ( int i = 0; i < size; i++ ) {
610 PyObject* value = PyList_GetItem( res2, i );
611 if( value && PyString_Check( value ) ) {
612 myViewMgrList.append( PyString_AsString( value ) );
622 * Performs internal activation:
623 * - initializes/gets the Python interpreter (one per study)
624 * - imports the Python GUI module
625 * - calls Python module's setSettings() method (obsolete function, used for compatibility with old code)
626 * or activate() method (for new modules)
628 void SALOME_PYQT_Module::activate( SUIT_Study* theStudy )
631 SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
632 int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
634 // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
635 initInterp( aStudyId );
639 // import Python GUI module
645 PyLockWrapper aLock = myInterp->GetLockWrapper();
647 if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
648 // call Python module's setSettings() method (obsolete)
649 if(PyObject_HasAttrString(myModule , "setSettings")){
650 PyObjWrapper res( PyObject_CallMethod( myModule, "setSettings", "" ) );
655 } //__CALL_OLD_METHODS__
657 // call Python module's activate() method (for the new modules)
658 if(PyObject_HasAttrString(myModule , "activate")){
659 PyObjWrapper res1( PyObject_CallMethod( myModule, "activate", "" ) );
667 * Performs internal deactivation:
668 * - calls Python module's deactivate() method
670 void SALOME_PYQT_Module::deactivate( SUIT_Study* theStudy )
672 // check if the subinterpreter is initialized and Python module is imported
673 if ( !myInterp || !myModule ) {
674 // Error! Python subinterpreter should be initialized and module should be imported first!
677 // then call Python module's deactivate() method
678 if(PyObject_HasAttrString(myModule , "deactivate")){
679 PyObjWrapper res( PyObject_CallMethod( myModule, "deactivate", "" ) );
687 * Called when active the study is actived (user brings its desktop to top)
688 * - initializes/gets the Python interpreter (one per study)
689 * - imports the Python GUI module
690 * - calls Python module's activeStudyChanged() method
692 void SALOME_PYQT_Module::studyChanged( SUIT_Study* theStudy )
695 SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( theStudy );
696 int aStudyId = aStudy ? aStudy->studyDS()->StudyId() : 0;
698 // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
699 initInterp( aStudyId );
703 // import Python GUI module
709 PyLockWrapper aLock = myInterp->GetLockWrapper();
711 // call Python module's activeStudyChanged() method
712 if(PyObject_HasAttrString(myModule , "activeStudyChanged")){
713 PyObjWrapper res( PyObject_CallMethod( myModule, "activeStudyChanged", "i", aStudyId ) );
721 * Get module engine, returns nil var if engine is not found in LifeCycleCORBA
723 Engines::Component_var SALOME_PYQT_Module::getEngine() const
725 Engines::Component_var comp;
726 // temporary solution
728 comp = getApp()->lcc()->FindOrLoad_Component( "FactoryServerPy", name( "" ) );
730 catch (CORBA::Exception&) {
736 * Get module engine IOR, returns empty string if engine is not found in LifeCycleCORBA
738 QString SALOME_PYQT_Module::engineIOR() const
740 if ( !CORBA::is_nil( getEngine() ) )
741 return QString( getApp()->orb()->object_to_string( getEngine() ) );
742 return QString( "" );
746 * Called when study desktop is activated.
747 * Used for notifying about changing of the active study.
749 void SALOME_PYQT_Module::studyActivated()
751 // StudyChangedReq: request class for internal studyChanged() operation
752 class StudyChangedReq : public PyInterp_Request
755 StudyChangedReq( SUIT_Study* _study,
756 SALOME_PYQT_Module* _obj )
757 : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
762 virtual void execute()
764 myObj->studyChanged( myStudy );
769 SALOME_PYQT_Module* myObj;
772 // Posting the request
773 PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( application()->activeStudy(), this ) );
777 * Processes context popup menu request
778 * - calls Python module's definePopup(...) method (obsolete function, used for compatibility with old code)
779 * to define the popup menu context
780 * - parses XML resourses file (if exists) and fills the popup menu with the items)
781 * - calls Python module's customPopup(...) method (obsolete function, used for compatibility with old code)
782 * to allow module to customize the popup menu
783 * - for new modules calls createPopupMenu() function to allow the modules to build the popup menu
784 * by using insertItem(...) Qt functions.
786 void SALOME_PYQT_Module::contextMenu( const QString& theContext, QPopupMenu* thePopupMenu )
788 // Python interpreter should be initialized and Python module should be
790 if ( !myInterp || !myModule )
793 QString aContext( theContext ), aObject( "" ), aParent( "" );
795 if ( IsCallOldMethods && PyObject_HasAttrString(myModule , "definePopup") ) { //__CALL_OLD_METHODS__
796 // call definePopup() Python module's function
797 // this is obsolete function, used only for compatibility reasons
798 PyObjWrapper res(PyObject_CallMethod( myModule,
803 aParent.latin1() ) );
808 // parse return value
810 if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
816 } //__CALL_OLD_METHODS__
818 // first try to create menu via XML parser:
819 // we create popup menus without help of QtxPopupMgr
821 myXmlHandler->createPopup( thePopupMenu, aContext, aParent, aObject );
823 PyObjWrapper sipPopup( sipBuildResult( 0, "M", thePopupMenu, sipClass_QPopupMenu ) );
825 // then call Python module's createPopupMenu() method (for new modules)
826 if ( PyObject_HasAttrString(myModule , "createPopupMenu") ) {
827 PyObjWrapper res1( PyObject_CallMethod( myModule,
831 aContext.latin1() ) );
837 if ( IsCallOldMethods && PyObject_HasAttrString(myModule , "customPopup") ) { //__CALL_OLD_METHODS__
838 // call customPopup() Python module's function
839 // this is obsolete function, used only for compatibility reasons
840 PyObjWrapper res2( PyObject_CallMethod( myModule,
846 aParent.latin1() ) );
850 } //__CALL_OLD_METHODS__
854 * Processes GUI event
855 * - calls Python module's OnGUIEvent() method
857 void SALOME_PYQT_Module::guiEvent( const int theId )
859 // Python interpreter should be initialized and Python module should be
861 if ( !myInterp || !myModule )
864 if ( PyObject_HasAttrString(myModule , "OnGUIEvent") ) {
865 PyObjWrapper res( PyObject_CallMethod( myModule, "OnGUIEvent", "i", theId ) );
873 * Menu highlight processing
874 * - calls Python module's menuActivated(int,int) method
876 void SALOME_PYQT_Module::menuHighlight( const int menu, const int submenu )
878 // Python interpreter should be initialized and Python module should be
880 if ( !myInterp || !myModule )
883 if ( PyObject_HasAttrString(myModule , "menuHighlight") ) {
884 PyObjWrapper res( PyObject_CallMethod( myModule, "menuHighlight", "ii", menu, submenu ) );
892 * Initialises python subinterpreter (one per study)
894 void SALOME_PYQT_Module::initInterp( int theStudyId )
898 // Error! Study Id must not be 0!
902 // try to find the subinterpreter
903 if( myInterpMap.find( theStudyId ) != myInterpMap.end() ) {
905 myInterp = myInterpMap[ theStudyId ];
908 // not found - create a new one!
909 ///////////////////////////////////////////////////////////////////
910 // Attention: the creation of Python interpretor must be protected
911 // by a C++ Lock because of C threads
912 ///////////////////////////////////////////////////////////////////
913 myInterp = new SALOME_PYQT_PyInterp();
914 myInterp->initialize();
915 myInterpMap[ theStudyId ] = myInterp;
917 // import 'salome' module and call 'salome_init' method;
918 // do it only once on interpreter creation
919 // ... first get python lock
920 PyLockWrapper aLock = myInterp->GetLockWrapper();
921 // ... then import a module
922 PyObjWrapper aMod = PyImport_ImportModule( "salome" );
928 // ... then call a method
929 PyObjWrapper aRes( PyObject_CallMethod( aMod, "salome_init", "" ) );
938 * Imports Python GUI module and remember the reference to the module
939 * !!! initInterp() should be called first!!!
941 void SALOME_PYQT_Module::importModule()
943 // check if the subinterpreter is initialized
945 // Error! Python subinterpreter should be initialized first!
949 // import Python GUI module and puts it in <myModule> attribute
950 // ... first get python lock
951 PyLockWrapper aLock = myInterp->GetLockWrapper();
952 // ... then import a module
953 QString aMod = QString( name("") ) + "GUI";
954 myModule = PyImport_ImportModule( (char*)( aMod.latin1() ) );
963 * Calls <module>.setWorkSpace() method with PyQt QWidget object to use with
965 * !!! initInterp() and importModule() should be called first!!!
967 void SALOME_PYQT_Module::setWorkSpace()
969 // check if the subinterpreter is initialized and Python module is imported
970 if ( !myInterp || !myModule ) {
971 // Error! Python subinterpreter should be initialized and module should be imported first!
975 // call setWorkspace() method
976 // ... first get python lock
977 PyLockWrapper aLock = myInterp->GetLockWrapper();
979 // ... then try to import SalomePyQt module. If it's not possible don't go on.
980 PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
987 if ( IsCallOldMethods ) { //__CALL_OLD_METHODS__
988 // ... then get workspace object
989 QWidget* aWorkspace = 0;
990 if ( getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
991 STD_MDIDesktop* aDesktop = dynamic_cast<STD_MDIDesktop*>( getApp()->desktop() );
993 aWorkspace = aDesktop->workspace();
995 else if ( getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
996 STD_TabDesktop* aDesktop = dynamic_cast<STD_TabDesktop*>( getApp()->desktop() );
998 aWorkspace = aDesktop->workstack();
1000 PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget ) );
1001 // ... and finally call Python module's setWorkspace() method (obsolete)
1002 if ( PyObject_HasAttrString(myModule , "setWorkSpace") ) {
1003 PyObjWrapper res( PyObject_CallMethod( myModule, "setWorkSpace", "O", pyws.get() ) );
1008 } //__CALL_OLD_METHODS__
1012 * Returns default menu group
1014 int SALOME_PYQT_Module::defaultMenuGroup()
1016 return __DEFAULT_GROUP__;
1020 * The next methods call the parent implementation.
1021 * This is done to open protected methods from CAM_Module class.
1022 * Also these methods are used to register created from outside menus
1023 * in order to enable dynamic menus handling.
1025 int SALOME_PYQT_Module::createTool( const QString& name )
1027 return SalomeApp_Module::createTool( name );
1029 int SALOME_PYQT_Module::createTool( const int id, const int tBar, const int idx )
1031 return SalomeApp_Module::createTool( id, tBar, idx );
1033 int SALOME_PYQT_Module::createTool( const int id, const QString& tBar, const int idx )
1035 return SalomeApp_Module::createTool( id, tBar, idx );
1037 int SALOME_PYQT_Module::createTool( QAction* a, const int tBar, const int id, const int idx )
1039 return SalomeApp_Module::createTool( a, tBar, id, idx );
1041 int SALOME_PYQT_Module::createTool( QAction* a, const QString& tBar, const int id, const int idx )
1043 return SalomeApp_Module::createTool( a, tBar, id, idx );
1045 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const int menu, const int id, const int group, const int idx, const bool constantMenu )
1047 bool exists = hasMenu( subMenu, menu );
1048 int regId = SalomeApp_Module::createMenu( subMenu, menu, id, group, idx, true );
1050 registerMenu( regId, menu, constantMenu );
1053 int SALOME_PYQT_Module::createMenu( const QString& subMenu, const QString& menu, const int id, const int group, const int idx, const bool constantMenu )
1055 QStringList menus = QStringList::split( "|", menu, false );
1057 for (int i = 0; i < menus.count(); i++ ) {
1058 pid = createMenu( menus[i], pid, -1, -1, -1, constantMenu );
1059 if ( pid == -1 ) break;
1062 pid = createMenu( subMenu, pid, id, group, idx, constantMenu );
1065 int SALOME_PYQT_Module::createMenu( const int id, const int menu, const int group, const int idx, const bool constantMenu )
1067 int regId = SalomeApp_Module::createMenu( id, menu, group, idx );
1069 registerMenu( regId, menu, constantMenu );
1072 int SALOME_PYQT_Module::createMenu( const int id, const QString& menu, const int group, const int idx, const bool constantMenu )
1074 QStringList menus = QStringList::split( "|", menu, false );
1076 for (int i = 0; i < menus.count(); i++ ) {
1077 pid = createMenu( menus[i], pid, -1, -1, -1, constantMenu );
1078 if ( pid == -1 ) break;
1081 pid = createMenu( id, pid, group, idx, constantMenu );
1084 int SALOME_PYQT_Module::createMenu( QAction* a, const int menu, const int id, const int group, const int idx, const bool constantMenu )
1086 int regId = SalomeApp_Module::createMenu( a, menu, id, group, idx );
1088 registerMenu( regId, menu, constantMenu );
1091 int SALOME_PYQT_Module::createMenu( QAction* a, const QString& menu, const int id, const int group, const int idx, const bool constantMenu )
1093 QStringList menus = QStringList::split( "|", menu, false );
1095 for (int i = 0; i < menus.count(); i++ ) {
1096 pid = createMenu( menus[i], pid, -1, -1, -1, constantMenu );
1097 if ( pid == -1 ) break;
1100 pid = createMenu( a, pid, id, group, idx, constantMenu );
1103 QAction* SALOME_PYQT_Module::createSeparator()
1105 return SalomeApp_Module::separator();
1107 QAction* SALOME_PYQT_Module::action( const int id ) const
1109 return SalomeApp_Module::action( id );
1111 int SALOME_PYQT_Module::actionId( const QAction* a ) const
1113 return SalomeApp_Module::actionId( a );
1115 QAction* SALOME_PYQT_Module::createAction( const int id, const QString& text, const QString& icon,
1116 const QString& menu, const QString& tip, const int key,
1120 if ( !icon.isEmpty() ) {
1121 QPixmap pixmap = getApp()->resourceMgr()->loadPixmap( name(""), tr( icon ) );
1122 if ( !pixmap.isNull() )
1123 anIcon = QIconSet( pixmap );
1125 QAction* a = action( id );
1127 if ( a->text().isEmpty() && !text.isEmpty() ) a->setText( text );
1128 if ( a->menuText().isEmpty() && !menu.isEmpty() ) a->setMenuText( menu );
1129 if ( a->iconSet().isNull() && !anIcon.isNull() ) a->setIconSet( anIcon );
1130 if ( a->statusTip().isEmpty() && !tip.isNull() ) a->setStatusTip( tip );
1131 if ( a->accel().isEmpty() && key ) a->setAccel( key );
1132 if ( a->isToggleAction() != toggle ) a->setToggleAction( toggle );
1133 disconnect( a, SIGNAL( activated() ), this, SLOT( onGUIEvent() ) );
1134 connect( a, SIGNAL( activated() ), this, SLOT( onGUIEvent() ) );
1137 a = SalomeApp_Module::createAction( id, text, anIcon, menu, tip, key, getApp()->desktop(), toggle, this, SLOT( onGUIEvent() ) );
1143 * Returns TRUE if menu already exists
1145 bool SALOME_PYQT_Module::hasMenu( const QString& subMenu, const int menu )
1147 return menuMgr() && menuMgr()->containsMenu( subMenu, menu );
1153 void SALOME_PYQT_Module::registerMenu( const int id, const int menu, const bool constantMenu )
1155 QAction* a = action( id );
1156 QAction* s = separator();
1159 if ( !registered( id, menu ) )
1160 myMenus[menu].append( MenuId( id, constantMenu ) );
1164 * Unregister the menu
1166 void SALOME_PYQT_Module::unregisterMenu( const int id, const int menu )
1168 if ( myMenus.find( menu ) != myMenus.end() ) {
1169 MenuIdList::iterator lit;
1170 for ( lit = myMenus[menu].begin(); lit != myMenus[menu].end(); ++lit ) {
1171 if ( (*lit).id == id ) {
1172 myMenus[menu].remove( lit );
1180 * Returns TRUE if the menu is registered
1182 bool SALOME_PYQT_Module::registered( const int id, const int menu )
1184 MenuMap::iterator mit;
1185 for ( mit = myMenus.begin(); mit != myMenus.end(); ++mit ) {
1186 MenuIdList::iterator lit;
1187 for ( lit = mit.data().begin(); lit != mit.data().end(); ++lit ) {
1188 if ( (*lit).id == id && ( menu == 0 || mit.key() == menu ) )
1196 * Returns TRUE if the menu is constant (not removed by clearMenu()).
1197 * This concerns the menus which are created from XML files.
1199 bool SALOME_PYQT_Module::isConstantMenu( const int id, const int menu )
1201 if ( myMenus.find( menu ) != myMenus.end() ) {
1202 MenuIdList& l = myMenus[ menu ];
1203 MenuIdList::iterator lit;
1204 for ( lit = l.begin(); lit != l.end(); ++lit ) {
1205 if ( (*lit).id == id && (*lit).constantMenu )
1213 * Displays/hides the module's menus.
1215 void SALOME_PYQT_Module::setMenuShown( const bool show )
1217 QtxActionMenuMgr* mMgr = menuMgr();
1221 bool upd = mMgr->isUpdatesEnabled();
1222 mMgr->setUpdatesEnabled( false );
1224 SalomeApp_Module::setMenuShown( show );
1226 for ( MenuMap::iterator mit = myMenus.begin(); mit != myMenus.end(); ++mit ) {
1227 MenuIdList::iterator lit;
1228 for ( lit = mit.data().begin(); lit != mit.data().end(); ++lit )
1229 if ( !action( (*lit).id ) )
1230 mMgr->setShown( (*lit).id, show );
1233 mMgr->setUpdatesEnabled( upd );
1239 * Displays/hides the module's toolbars.
1241 void SALOME_PYQT_Module::setToolShown( const bool show )
1243 SalomeApp_Module::setToolShown( show );
1247 * Clears the given menu.
1248 * If <id> = 0 : clear all items in the <menu> menu
1249 * If <menu> = 0 : clear all menus recursively starting from top-level.
1250 * If <removeActions> = 0 : also unregister all removed actions (not delete!).
1252 bool SALOME_PYQT_Module::clearMenu( const int id, const int menu, const bool removeActions )
1254 QAction* a = action( id );
1255 QAction* s = separator();
1256 typedef QValueList<int> IntList;
1257 if ( a && a != s ) {
1261 if ( registered( id, menu ) ) menus.append( menu );
1264 MenuMap::iterator mit;
1265 for ( mit = myMenus.begin(); mit != myMenus.end(); ++mit )
1266 if ( registered( id, mit.key() ) ) menus.append( mit.key() );
1268 for ( int i = 0; i < menus.count(); i++ ) {
1269 if ( !isConstantMenu( id, menus[i] ) ) {
1270 menuMgr()->remove( menuMgr()->actionId( a ), menus[ i ] );
1271 unregisterMenu( id, menus[i] );
1274 if ( !registered( id ) && removeActions )
1275 unregisterAction( id );
1281 // clear all menus recursively starting from top-level (main menu)
1282 IntList l = myMenus.keys();
1283 IntList::iterator lit;
1284 for ( lit = l.begin(); lit != l.end(); ++lit )
1285 clearMenu( 0, *lit, removeActions );
1288 if ( myMenus.find( menu ) != myMenus.end() ) {
1289 // remove all items in the parent menu
1291 MenuIdList::iterator lit;
1292 for ( lit = myMenus[menu].begin(); lit != myMenus[menu].end(); ++lit )
1293 l.append( (*lit).id );
1294 for ( int i = 0; i < l.count(); i++ )
1295 clearMenu( l[i], menu, removeActions );
1296 if ( myMenus[menu].empty() )
1297 myMenus.remove( menu );
1302 MenuMap::iterator mit;
1303 for ( mit = myMenus.begin(); mit != myMenus.end(); ++mit ) {
1304 MenuIdList::iterator lit;
1305 for ( lit = mit.data().begin(); lit != mit.data().end(); ++lit ) {
1306 if ( (*lit).id == id && ( menu == 0 || mit.key() == menu ) ) {
1307 clearMenu( 0, id, removeActions ); // first remove all sub-menus
1308 if ( !isConstantMenu( id, mit.key() ) ) {
1309 menuMgr()->remove( id, mit.key() );
1310 mit.data().remove( lit );
1321 //=============================================================================
1322 // SALOME_PYQT_XmlHandler class implementation
1323 //=============================================================================
1325 // gets an tag name for the dom element [ static ]
1326 // returns an empty string if the element does not have tag name
1327 static QString tagName( const QDomElement& element ) {
1328 return element.tagName().stripWhiteSpace();
1331 // gets an attribute by it's name for the dom element [ static ]
1332 // returns an empty string if the element does not have such attribute
1333 static QString attribute( const QDomElement& element, const QString& attName ) {
1334 return element.attribute( attName ).stripWhiteSpace();
1337 // checks the given value for the boolean value [ static ]
1338 // returns TRUE if string is "true", "yes" or "1"
1339 static bool checkBool( const QString& value ) {
1340 return ( value == "true" || value == "yes" || value == "1" );
1343 // checks the given value for the integer value [ static ]
1344 // returns -1 if item is empty or presents and invalid number
1345 static int checkInt( const QString& value, const int def = -1 )
1347 return value.isEmpty() ? def : value.toInt();
1353 SALOME_PYQT_XmlHandler::SALOME_PYQT_XmlHandler( SALOME_PYQT_Module* module, const QString& fileName )
1354 : myModule( module )
1356 QFile aFile( fileName );
1357 if ( !aFile.open( IO_ReadOnly ) )
1359 if ( !myDoc.setContent( &aFile ) ) {
1367 Called by SALOME_PYQT_Module::initialize() in order to create actions
1368 (menus, toolbars, popup menus)
1370 void SALOME_PYQT_XmlHandler::createActions()
1372 // get document element
1373 QDomElement aDocElem = myDoc.documentElement();
1375 // get main menu actions
1376 QDomNodeList aMenuList = aDocElem.elementsByTagName( "menu-item" );
1377 for ( int i = 0; i < aMenuList.count(); i++ ) {
1378 QDomNode n = aMenuList.item( i );
1382 // create toolbars actions
1383 QDomNodeList aToolsList = aDocElem.elementsByTagName( "toolbar" );
1384 for ( int i = 0; i < aToolsList.count(); i++ ) {
1385 QDomNode n = aToolsList.item( i );
1391 * Creates popup menu
1393 void SALOME_PYQT_XmlHandler::createPopup( QPopupMenu* menu,
1394 const QString& context,
1395 const QString& parent,
1396 const QString& object )
1398 // get document element
1399 QDomElement aDocElem = myDoc.documentElement();
1401 // get popup menus actions
1402 QDomNodeList aPopupList = aDocElem.elementsByTagName( "popupmenu" );
1403 for ( int i = 0; i < aPopupList.count(); i++ ) {
1404 QDomNode n = aPopupList.item( i );
1405 if ( !n.isNull() && n.isElement() ) {
1406 QDomElement e = n.toElement();
1407 QString lab = attribute( e, "label-id" );
1408 QString ctx = attribute( e, "context-id" );
1409 QString prt = attribute( e, "parent-id" );
1410 QString obj = attribute( e, "object-id" );
1411 if ( ctx == context && prt == parent && obj == object ) {
1412 insertPopupItems( n, menu );
1420 Create main menu with child actions
1422 void SALOME_PYQT_XmlHandler::createMenu( QDomNode& parentNode, const int parentMenuId )
1427 if ( parentNode.isNull() )
1430 QDomElement parentElement = parentNode.toElement();
1431 if ( !parentElement.isNull() ) {
1432 QString plabel = attribute( parentElement, "label-id" );
1433 int pid = checkInt( attribute( parentElement, "item-id" ) );
1434 int ppos = checkInt( attribute( parentElement, "pos-id" ) );
1435 int group = checkInt( attribute( parentElement, "group-id" ),
1436 myModule->defaultMenuGroup() );
1437 if ( !plabel.isEmpty() ) {
1439 int menuId = myModule->createMenu( plabel, // label
1440 parentMenuId, // parent menu ID, should be -1 for main menu
1444 true ); // create constant menu (not removed by clearMenu())
1445 QDomNode node = parentNode.firstChild();
1446 while ( !node.isNull() ) {
1447 if ( node.isElement() ) {
1448 QDomElement elem = node.toElement();
1449 QString aTagName = tagName( elem );
1450 if ( aTagName == "popup-item" ) {
1451 int id = checkInt( attribute( elem, "item-id" ) );
1452 int pos = checkInt( attribute( elem, "pos-id" ) );
1453 int group = checkInt( attribute( elem, "group-id" ),
1454 myModule->defaultMenuGroup() );
1455 QString label = attribute( elem, "label-id" );
1456 QString icon = attribute( elem, "icon-id" );
1457 QString tooltip = attribute( elem, "tooltip-id" );
1458 QString accel = attribute( elem, "accel-id" );
1459 bool toggle = checkBool( attribute( elem, "toggle-id" ) );
1460 ////QString execute = attribute( elem, "execute-action" ); // not used
1462 // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1463 // also check if the action with given ID is already created
1465 // create menu action
1466 QAction* action = myModule->createAction( id, // ID
1470 tooltip, // status-bar text
1471 QKeySequence( accel ), // keyboard accelerator
1472 toggle ); // toogled action
1473 myModule->createMenu( action, // action
1474 menuId, // parent menu ID
1475 id, // ID (same as for createAction())
1478 true ); // create constant menu (not removed by clearMenu())
1481 else if ( aTagName == "submenu" ) {
1483 createMenu( node, menuId );
1485 else if ( aTagName == "separator" ) {
1486 // create menu separator
1487 int id = checkInt( attribute( elem, "item-id" ) ); // separator can have ID
1488 int pos = checkInt( attribute( elem, "pos-id" ) );
1489 int group = checkInt( attribute( elem, "group-id" ),
1490 myModule->defaultMenuGroup() );
1491 QAction* action = myModule->createSeparator();
1492 myModule->createMenu( action, // separator action
1493 menuId, // parent menu ID
1497 true ); // create constant menu (not removed by clearMenu())
1500 node = node.nextSibling();
1507 Create a toolbar with child actions
1509 void SALOME_PYQT_XmlHandler::createToolBar( QDomNode& parentNode )
1514 if ( parentNode.isNull() )
1517 QDomElement parentElement = parentNode.toElement();
1518 if ( !parentElement.isNull() ) {
1519 QString aLabel = attribute( parentElement, "label-id" );
1520 if ( !aLabel.isEmpty() ) {
1522 int tbId = myModule->createTool( aLabel );
1523 QDomNode node = parentNode.firstChild();
1524 while ( !node.isNull() ) {
1525 if ( node.isElement() ) {
1526 QDomElement elem = node.toElement();
1527 QString aTagName = tagName( elem );
1528 if ( aTagName == "toolbutton-item" ) {
1529 int id = checkInt( attribute( elem, "item-id" ) );
1530 int pos = checkInt( attribute( elem, "pos-id" ) );
1531 QString label = attribute( elem, "label-id" );
1532 QString icon = attribute( elem, "icon-id" );
1533 QString tooltip = attribute( elem, "tooltip-id" );
1534 QString accel = attribute( elem, "accel-id" );
1535 bool toggle = checkBool( attribute( elem, "toggle-id" ) );
1536 ////QString execute = attribute( elem, "execute-action" ); // not used
1538 // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1539 // also check if the action with given ID is already created
1541 // create toolbar action
1542 QAction* action = myModule->createAction( id, // ID
1546 tooltip, // status-bar text
1547 QKeySequence( accel ), // keyboard accelerator
1548 toggle ); // toogled action
1549 myModule->createTool( action, tbId, -1, pos );
1552 else if ( aTagName == "separatorTB" ) {
1553 // create toolbar separator
1554 int pos = checkInt( attribute( elem, "pos-id" ) );
1555 QAction* action = myModule->createSeparator();
1556 myModule->createTool( action, tbId, -1, pos );
1559 node = node.nextSibling();
1565 void SALOME_PYQT_XmlHandler::insertPopupItems( QDomNode& parentNode, QPopupMenu* menu )
1570 if ( parentNode.isNull() )
1573 // we create popup menus without help of QtxPopupMgr
1574 QDomNode node = parentNode.firstChild();
1575 while ( !node.isNull() ) {
1576 if ( node.isElement() ) {
1577 QDomElement elem = node.toElement();
1578 QString aTagName = tagName( elem );
1579 if ( aTagName == "popup-item" ) {
1580 // insert a command item
1581 int id = checkInt( attribute( elem, "item-id" ) );
1582 int pos = checkInt( attribute( elem, "pos-id" ) );
1583 QString label = attribute( elem, "label-id" );
1584 QString icon = attribute( elem, "icon-id" );
1585 /////QString tooltip = attribute( elem, "tooltip-id" ); // not used
1586 QString accel = attribute( elem, "accel-id" );
1587 /////bool toggle = checkBool( attribute( elem, "toggle-id" ) ); // not used
1588 /////QString execute = attribute( elem, "execute-action" ); // not used
1591 if ( !icon.isEmpty() ) {
1592 QPixmap pixmap = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1593 if ( !pixmap.isNull() )
1594 anIcon = QIconSet( pixmap );
1597 // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
1598 // also check if the action with given ID is already created
1600 menu->insertItem( anIcon, label, myModule, SLOT( onGUIEvent(int) ), QKeySequence( accel ), id, pos );
1603 else if ( aTagName == "submenu" ) {
1605 int id = checkInt( attribute( elem, "item-id" ) );
1606 int pos = checkInt( attribute( elem, "pos-id" ) );
1607 QString label = attribute( elem, "label-id" );
1608 QString icon = attribute( elem, "icon-id" );
1611 if ( !icon.isEmpty() ) {
1612 QPixmap pixmap = myModule->getApp()->resourceMgr()->loadPixmap( myModule->name(""), icon );
1613 if ( !pixmap.isNull() )
1614 anIcon = QIconSet( pixmap );
1617 QPopupMenu* newPopup = new QPopupMenu( menu, label );
1618 menu->insertItem( anIcon, label, newPopup, id, pos );
1619 insertPopupItems( node, newPopup );
1621 else if ( aTagName == "separator" ) {
1622 // create menu separator
1623 int pos = checkInt( attribute( elem, "pos-id" ) );
1624 menu->insertSeparator( pos );
1627 node = node.nextSibling();