Salome HOME
Debug of CMake build procedure
[modules/gui.git] / src / SALOME_PYQT / SALOME_PYQT_GUILight / SALOME_PYQT_PyModule.cxx
1 // Copyright (C) 2007-2014  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
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, or (at your option) any later version.
7 //
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.
12 //
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
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 // File   : SALOME_PYQT_PyModule.cxx
21 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
22 //
23
24 #include "SALOME_PYQT_PyModule.h"
25 #include "SALOME_PYQT_PyInterp.h"
26
27 #include "LightApp_Application.h"
28 #include "LightApp_DataObject.h"
29 #include "LightApp_Module.h"
30 #include "LightApp_Study.h"
31 #include "PyInterp_Dispatcher.h"
32 #include "QtxActionMenuMgr.h"
33 #include "QtxWorkspace.h"
34 #include "QtxWorkstack.h"
35 #include "STD_MDIDesktop.h"
36 #include "STD_TabDesktop.h"
37 #include "SUITApp_init_python.hxx"
38 #include "SUIT_ResourceMgr.h"
39 #include "SUIT_ViewManager.h"
40 #include "SUIT_ViewModel.h"
41 #include "SUIT_ViewWindow.h"
42
43 #include <QApplication>
44 #include <QDomDocument>
45 #include <QDomElement>
46 #include <QDomNode>
47 #include <QFile>
48 #include <QMenu>
49 #include <QMutex>
50
51 #include <utilities.h>
52
53 #include "sipAPISalomePyQtGUILight.h"
54
55 /*!
56   \brief Default menu group number.
57   \internal
58 */
59 const int DEFAULT_GROUP = 40;
60
61 /*!
62   \brief Mutex used to lock access from several threads to the shared
63   (static) data
64   \internal
65 */
66 QMutex myInitMutex;
67
68 /*!
69   \var IsCallOldMethods
70   \brief Allow calling obsolete callback methods.
71   \internal
72   
73   If the macro CALL_OLD_METHODS is not defined, the invoking
74   of obsolete Python module's methods like setSetting(), definePopup(), 
75   etc. is blocked.
76
77   CALL_OLD_METHODS macro can be defined, for example, by adding 
78   -DCALL_OLD_METHODS compilation option to the CMakeLists.txt.
79 */
80 #ifdef CALL_OLD_METHODS
81 const bool IsCallOldMethods = true;
82 #else
83 const bool IsCallOldMethods = false;
84 #endif
85
86 /*!
87   \brief Get tag name for the DOM element.
88   \internal
89   \param element DOM element
90   \return tag name or empty string if the element does not have tag name
91 */
92 static QString tagName( const QDomElement& element )
93 {
94   return element.tagName().trimmed();
95 }
96
97 /*!
98   \brief Get value of DOM element's attribute.
99   \internal
100   \param element DOM element
101   \param attName attribute name
102   \return attribute value or empty string if the element does not have such attribute
103 */
104 static QString attribute( const QDomElement& element, const QString& attName )
105 {
106   return element.attribute( attName ).trimmed();
107 }
108
109 /*!
110   \brief Inspect specified string for the boolean value.
111   \internal
112   
113   This function returns \c true if string represents boolean value: 
114   - "true", "yes" or "1" for \c true
115   - "false", "no" or "0" for \c false
116   Second parameter allows to specify what boolean value is expected:
117   - 1: \c true
118   - 0: \c false
119   - other value is not taken into account (return represented value)
120
121   \param value inspected string
122   \param check expected boolean value
123   \return boolean value represented by the string (\a check is not 1 or 0)
124           or \c true if value correspond to the specified \a check
125 */
126 static bool checkBool( const QString& value, const int check = -1 )
127 {
128   QString v = value.toLower();
129   if ( ( v == "true"  || v == "yes" || v == "1" ) && ( check != 0 ) )
130     return true;
131   if ( ( v == "false" || v == "no"  || v == "0" ) && ( check == 0 ) )
132     return true;
133   return false;
134 }
135
136 /*!
137   \brief Inspect specified string for the integer value.
138   \internal
139   
140   This function returns returns -1 if item is empty or represents
141   an invalid number.
142   \param value inspected string
143   \param def default value
144   \param shift shift value (it is added to the integer value to produce shifted result)
145 */
146 static int checkInt( const QString& value, const int def = -1, const int shift = -1 )
147 {
148   bool bOk;
149   int val = value.toInt( &bOk );
150   if ( !bOk ) val = def;
151   if ( shift > 0 && bOk && val < 0 )
152     val += shift;
153   return val;
154 }
155
156 /*!
157   \class FuncMsg
158   \brief Function call in/out tracer.
159   \internal
160 */
161
162 class FuncMsg
163 {
164 public:
165   FuncMsg( const QString& funcName )
166   {
167     myName = funcName;
168     MESSAGE( qPrintable( myName ) << " [ begin ]" );
169   }
170   ~FuncMsg()
171   {
172     MESSAGE( qPrintable( myName ) << " [ end ]" );
173   }
174   void message( const QString& msg )
175   {
176     MESSAGE( qPrintable( myName ) << " : " << qPrintable( msg ) );
177   }
178 private:
179   QString myName;
180 };
181
182 /*!
183   \class PyModuleHelper::InitLocker
184   \brief Initialization locker
185   \internal
186 */
187
188 class PyModuleHelper::InitLocker
189 {
190 public:
191   InitLocker( LightApp_Module* );
192   ~InitLocker();
193 };
194
195 /*!
196   \brief Constructor
197   \internal
198 */
199 PyModuleHelper::InitLocker::InitLocker( LightApp_Module* module )
200 {
201   QMutexLocker ml( &myInitMutex );
202   myInitModule = module;
203 }
204
205 /*!
206   \brief Destructor
207   \internal
208 */
209 PyModuleHelper::InitLocker::~InitLocker()
210 {
211   QMutexLocker ml( &myInitMutex );
212   myInitModule = 0;
213 }
214
215 /*!
216   \class PyModuleHelper::XmlHandler
217   \brief XML resource files parser.
218   \internal
219
220   This class is used to provide backward compatibility with
221   existing Python modules in which obsolete menu definition system
222   (via XML files) is used.
223 */
224
225 class PyModuleHelper::XmlHandler
226 {
227 public:
228   XmlHandler( PyModuleHelper* helper, const QString& fileName );
229   void             createActions();
230   void             createPopup( QMenu* menu,
231                                 const QString& context,
232                                 const QString& parent,
233                                 const QString& object );
234   void             activateMenus( bool );
235
236 private:
237   LightApp_Module* module() const;
238   QIcon            loadIcon( const QString& fileName );
239
240   void             createMenu( QDomNode& parentNode,
241                                const int parentMenuId = -1,
242                                QMenu* parentPopup = 0 );
243   void             createToolBar( QDomNode& parentNode );
244   void             insertPopupItems( QDomNode& parentNode,
245                                      QMenu* menu );
246
247 private:
248   PyModuleHelper*  myHelper;
249   QDomDocument     myDoc;
250   QList<int>       myMenuItems;
251 };
252
253
254 /*!
255   \brief Constructor
256   \internal
257   \param module pointer to the GUI module
258   \param fileName path to the XML menu description file 
259 */
260 PyModuleHelper::XmlHandler::XmlHandler( PyModuleHelper*  helper,
261                                         const QString&   fileName )
262 : myHelper( helper )
263 {
264   if ( !fileName.isEmpty() ) { 
265     QFile aFile( fileName );
266     if ( aFile.open( QIODevice::ReadOnly ) ) {
267       myDoc.setContent( &aFile );
268     }
269   }
270 }
271
272 /*!
273   \brief Parse XML file and create actions.
274   \internal
275   
276   Called by PyModuleHelper::initialize() in order to create actions
277   (menus, toolbars).
278 */
279 void PyModuleHelper::XmlHandler::createActions()
280 {
281   // get document element
282   QDomElement aDocElem = myDoc.documentElement();
283
284   // create main menu actions
285   QDomNodeList aMenuList = aDocElem.elementsByTagName( "menu-item" );
286   for ( int i = 0; i < aMenuList.count(); i++ ) {
287     QDomNode n = aMenuList.item( i );
288     createMenu( n );
289   }
290
291   // create toolbars actions
292   QDomNodeList aToolsList = aDocElem.elementsByTagName( "toolbar" );
293   for ( int i = 0; i < aToolsList.count(); i++ ) {
294     QDomNode n = aToolsList.item( i );
295     createToolBar( n );
296   }
297 }
298
299 /*!
300   \brief Create popup menu.
301   \internal
302   \param menu popup menu
303   \param context popup menu context
304   \param context popup menu parent object name
305   \param context popup menu object name
306 */
307 void PyModuleHelper::XmlHandler::createPopup( QMenu*         menu,
308                                               const QString& context,
309                                               const QString& parent,
310                                               const QString& object )
311 {
312   // get document element
313   QDomElement aDocElem = myDoc.documentElement();
314
315   // get popup menus actions
316   QDomNodeList aPopupList = aDocElem.elementsByTagName( "popupmenu" );
317   for ( int i = 0; i < aPopupList.count(); i++ ) {
318     QDomNode n = aPopupList.item( i );
319     if ( !n.isNull() && n.isElement() ) {
320       QDomElement e = n.toElement();
321       // QString lab = attribute( e, "label-id" ); // not used // 
322       QString ctx = attribute( e, "context-id" );
323       QString prt = attribute( e, "parent-id"  );
324       QString obj = attribute( e, "object-id"  );
325       if ( ctx == context && prt == parent && obj == object )  {
326         insertPopupItems( n, menu );
327         break;
328       }
329     }
330   }
331 }
332
333 /*!
334   \brief Activate/deactivate menus
335   \internal
336   \param enable if \c true menus are activated, otherwise menus are deactivated
337 */
338 void PyModuleHelper::XmlHandler::activateMenus( bool enable )
339 {
340   if ( module() ) {
341     QtxActionMenuMgr* mgr = module()->menuMgr();
342     foreach( int id, myMenuItems ) mgr->setEmptyEnabled( id, enable );
343   }
344 }
345
346 /*!
347   \brief Get owner module
348 */
349 LightApp_Module* PyModuleHelper::XmlHandler::module() const
350 {
351   return myHelper->module();
352 }
353
354 /*!
355   \brief Load an icon from the module resources by the specified file name.
356   \param fileName icon file name
357   \return icon object
358 */
359
360 QIcon PyModuleHelper::XmlHandler::loadIcon( const QString& fileName )
361 {
362   QIcon icon;
363
364   if ( module() && !fileName.isEmpty() ) {
365       SUIT_ResourceMgr* resMgr = module()->getApp()->resourceMgr();
366       QPixmap pixmap = resMgr->loadPixmap( module()->name(),
367                                            QApplication::translate( module()->name().toLatin1().data(), 
368                                                                     fileName.toLatin1().data() ) );
369     if ( !pixmap.isNull() )
370       icon = QIcon( pixmap );
371   }
372
373   return icon;
374 }
375
376 /*!
377   \brief Create main menu item and insert actions to it.
378   \internal
379   \param parentNode XML node with menu description
380   \param parentMenuId parent menu ID (-1 for top-level menu)
381   \param parentPopup parent popup menu (0 for top-level menu)
382 */
383 void PyModuleHelper::XmlHandler::createMenu( QDomNode& parentNode, 
384                                              const int parentMenuId, 
385                                              QMenu*    parentPopup )
386 {
387   if ( !module() || parentNode.isNull() )
388     return;
389   
390   QDomElement parentElement = parentNode.toElement();
391   if ( !parentElement.isNull() ) {
392     QString plabel = attribute( parentElement, "label-id" );
393     int     pid    = checkInt( attribute( parentElement, "item-id" ) );
394     int     ppos   = checkInt( attribute( parentElement, "pos-id" ) );
395     int     group  = checkInt( attribute( parentElement, "group-id" ), 
396                                PyModuleHelper::defaultMenuGroup() );
397     if ( !plabel.isEmpty() ) {
398       QMenu* popup = 0;
399       int menuId = -1;
400       // create menu
401       menuId = module()->createMenu( plabel,         // label
402                                      parentMenuId,   // parent menu ID, -1 for top-level menu
403                                      pid,            // ID
404                                      group,          // group ID
405                                      ppos );         // position
406       myMenuItems.append( menuId );
407       QDomNode node = parentNode.firstChild();
408       while ( !node.isNull() ) {
409         if ( node.isElement() ) {
410           QDomElement elem = node.toElement();
411           QString aTagName = tagName( elem );
412           if ( aTagName == "popup-item" ) {
413             int     id      = checkInt( attribute( elem, "item-id" ) );
414             int     pos     = checkInt( attribute( elem, "pos-id" ) );
415             int     group   = checkInt( attribute( elem, "group-id" ), 
416                                         PyModuleHelper::defaultMenuGroup() );
417             QString label   = attribute( elem, "label-id" );
418             QIcon   icon    = loadIcon( attribute( elem, "icon-id" ) );
419             QString tooltip = attribute( elem, "tooltip-id" );
420             QString accel   = attribute( elem, "accel-id" );
421             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
422
423             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
424             // also check if the action with given ID is already created
425             if ( id != -1 ) {
426               // create menu action
427               QAction* action = module()->createAction( id,                     // ID
428                                                         tooltip,                // tooltip
429                                                         icon,                   // icon
430                                                         label,                  // menu text
431                                                         tooltip,                // status-bar text
432                                                         QKeySequence( accel ),  // keyboard accelerator
433                                                         module(),               // action owner
434                                                         toggle );               // toogled action
435               myHelper->connectAction( action );
436               module()->createMenu( action,   // action
437                                     menuId,   // parent menu ID
438                                     id,       // ID (same as for createAction())
439                                     group,    // group ID
440                                     pos );    // position
441             }
442           }
443           else if ( aTagName == "submenu" ) {
444             // create sub-menu
445             createMenu( node, menuId, popup );
446           }
447           else if ( aTagName == "separator" ) {
448             // create menu separator
449             int id    = checkInt( attribute( elem, "item-id" ) ); // separator can have ID
450             int pos   = checkInt( attribute( elem, "pos-id" ) );
451             int group = checkInt( attribute( elem, "group-id" ), 
452                                   PyModuleHelper::defaultMenuGroup() );
453             QAction* action = module()->separator();
454             module()->createMenu( action,  // separator action
455                                   menuId,  // parent menu ID
456                                   id,      // ID
457                                   group,   // group ID
458                                   pos );   // position
459           }
460         }
461         node = node.nextSibling();
462       }
463     }
464   }
465 }
466
467 /*!
468   \brief Create a toolbar and insert actions to it.
469   \param parentNode XML node with toolbar description
470 */
471 void PyModuleHelper::XmlHandler::createToolBar( QDomNode& parentNode )
472 {
473   if ( !module() || parentNode.isNull() )
474     return;
475
476   QDomElement parentElement = parentNode.toElement();
477   if ( !parentElement.isNull() ) {
478     QString aLabel = attribute( parentElement, "label-id" );
479     if ( !aLabel.isEmpty() ) {
480       // create toolbar
481       int tbId = module()->createTool( aLabel );
482       QDomNode node = parentNode.firstChild();
483       while ( !node.isNull() ) {
484         if ( node.isElement() ) {
485           QDomElement elem = node.toElement();
486           QString aTagName = tagName( elem );
487           if ( aTagName == "toolbutton-item" ) {
488             int     id      = checkInt( attribute( elem, "item-id" ) );
489             int     pos     = checkInt( attribute( elem, "pos-id" ) );
490             QString label   = attribute( elem, "label-id" );
491             QIcon   icon    = loadIcon( attribute( elem, "icon-id" ) );
492             QString tooltip = attribute( elem, "tooltip-id" );
493             QString accel   = attribute( elem, "accel-id" );
494             bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
495
496             // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
497             // also check if the action with given ID is already created
498             if ( id != -1 ) {
499               // create toolbar action
500               QAction* action = module()->createAction( id,                     // ID
501                                                         tooltip,                // tooltip
502                                                         icon,                   // icon
503                                                         label,                  // menu text
504                                                         tooltip,                // status-bar text
505                                                         QKeySequence( accel ),  // keyboard accelerator
506                                                         module(),               // action owner
507                                                         toggle );               // toogled action
508               myHelper->connectAction( action );
509               module()->createTool( action, tbId, -1, pos );
510             }
511           }
512           else if ( aTagName == "separatorTB" || aTagName == "separator" ) {
513             // create toolbar separator
514             int pos = checkInt( attribute( elem, "pos-id" ) );
515             QAction* action = module()->separator();
516             module()->createTool( action, tbId, -1, pos );
517           }
518         }
519         node = node.nextSibling();
520       }
521     }
522   }
523 }
524
525 /*!
526   \brief Fill popup menu with the items.
527   \param parentNode XML node with popup menu description
528   \param menu popup menu
529 */
530 void PyModuleHelper::XmlHandler::insertPopupItems( QDomNode& parentNode, QMenu* menu )
531 {
532   if ( !module() && parentNode.isNull() )
533     return;
534
535   // we create popup menus without help of QtxPopupMgr
536   QDomNode node = parentNode.firstChild();
537   while ( !node.isNull() ) { 
538     if ( node.isElement() ) {
539       QDomElement elem = node.toElement();
540       QString aTagName = tagName( elem );
541       QList<QAction*> actions = menu->actions();
542       if ( aTagName == "popup-item" ) {
543         // insert a command item
544         int     id      = checkInt( attribute( elem, "item-id" ) );
545         int     pos     = checkInt( attribute( elem, "pos-id" ) );
546         QString label   = attribute( elem, "label-id" );
547         QIcon   icon    = loadIcon( attribute( elem, "icon-id" ) );
548         QString tooltip = attribute( elem, "tooltip-id" );
549         QString accel   = attribute( elem, "accel-id" );
550         bool    toggle  = checkBool( attribute( elem, "toggle-id" ) );
551
552         // -1 action ID is not allowed : it means that <item-id> attribute is missed in the XML file!
553         // also check if the action with given ID is already created
554         if ( id != -1 ) {
555           QAction* action = module()->createAction( id,                     // ID
556                                                     tooltip,                // tooltip
557                                                     icon,                   // icon
558                                                     label,                  // menu text
559                                                     tooltip,                // status-bar text
560                                                     QKeySequence( accel ),  // keyboard accelerator
561                                                     module(),               // action owner
562                                                     toggle );               // toogled action
563           myHelper->connectAction( action );
564           QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
565           menu->insertAction( before, action );
566         }
567       }
568       else if ( aTagName == "submenu" ) {
569         // create sub-menu
570         ////int     id    = checkInt( attribute( elem, "item-id" ) ); // not used //
571         int     pos   = checkInt( attribute( elem, "pos-id" ) );
572         QString label = attribute( elem, "label-id" );
573         QString icon  = attribute( elem, "icon-id" );
574
575         QIcon anIcon;
576         if ( !icon.isEmpty() ) {
577           QPixmap pixmap  = module()->getApp()->resourceMgr()->loadPixmap( module()->name(), icon );
578           if ( !pixmap.isNull() )
579             anIcon = QIcon( pixmap );
580         }
581
582         QMenu* newPopup = menu->addMenu( anIcon, label );
583         QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
584         menu->insertMenu( before, newPopup );
585         insertPopupItems( node, newPopup );
586       }
587       else if ( aTagName == "separator" ) {
588         // create menu separator
589         int pos = checkInt( attribute( elem, "pos-id" ) );
590         QAction* action = module()->separator();
591         QAction* before = ( pos >= 0 && pos < actions.count() ) ? actions[ pos ] : 0;
592         menu->insertAction( before, action );
593       }
594     }
595     node = node.nextSibling();
596   }
597 }
598
599 /*!
600   \class PyModuleHelper
601   \brief This class implements API helper for all the Python-based 
602   SALOME GUI modules.
603 */
604
605 PyModuleHelper::InterpMap PyModuleHelper::myInterpMap;
606 LightApp_Module*          PyModuleHelper::myInitModule = 0;
607
608 /*!
609   \brief Constructor
610   \param module owner module
611 */
612 PyModuleHelper::PyModuleHelper( LightApp_Module* module ) :
613   QObject( module ),
614   myModule( module ),
615   myPyModule( 0 ), 
616   myInterp( 0 ),
617   myXmlHandler ( 0 ),
618   myLastActivateStatus( true )
619 {
620   setObjectName( "python_module_helper" );
621 }
622
623 /*!
624   \brief Destructor
625 */
626 PyModuleHelper::~PyModuleHelper()
627 {
628   delete myXmlHandler;
629   if ( myInterp && myPyModule ) {
630     PyLockWrapper aLock = myInterp->GetLockWrapper();
631     Py_XDECREF( myPyModule );
632   }
633 }
634
635 /*!
636   \brief Get the module being initialized.
637   
638   This is a little trick :) needed to provide an access from Python
639   (SalomePyQt) to the module being currently activated. The problem
640   that during the process of module initialization (initialize() 
641   function) it is not yet available via application->activeModule()
642   call.
643   
644   This method returns valid pointer only if called in scope of
645   initialize() function or in several other specific cases.
646
647   \return the module being currently initialized
648 */
649 LightApp_Module* PyModuleHelper::getInitModule()
650 {
651   QMutexLocker ml( &myInitMutex );
652   return myInitModule;
653 }
654
655 /*!
656   \brief Get default menu group identifier
657   \return menu group ID (40 by default)
658 */
659 int PyModuleHelper::defaultMenuGroup()
660 {
661   return DEFAULT_GROUP; 
662 }
663
664 /*!
665   \brief Get owner module
666   \return owner module
667 */
668 LightApp_Module* PyModuleHelper::module() const
669 {
670   return myModule;
671 }
672
673 /*!
674   \brief Get Python GUI module object
675   \return python module
676 */
677 PyObject* PyModuleHelper::pythonModule() const
678 {
679   return myPyModule;
680 }
681
682 /*!
683   \brief Connect action to the internal actionActivated() slot.
684
685   Actions connected to internal actionActivated(), when activated, will
686   be forwarded to the Python GUI module OnGUIEvent() function.
687
688   \param a action being connected
689 */
690 void PyModuleHelper::connectAction( QAction* a )
691 {
692   if ( myModule && a )
693     QObject::connect( a, SIGNAL( triggered( bool ) ), 
694                       this, SLOT( actionActivated() ),
695                       Qt::UniqueConnection );
696 }
697
698 /*!
699   \brief Get the dockable windows associated with the module.
700   
701   To fill the list of windows the correspondind Python module's windows()
702   method is called during the module initialization.
703
704   By default, ObjectBrowser, PythonConsole and LogWindow windows are 
705   associated to the module.
706
707   Allowed dockable windows:
708   - LightApp_Application::WT_ObjectBrowser : object browser
709   - LightApp_Application::WT_PyConsole : python console
710   - LightApp_Application::WT_LogWindow : log messages output window
711
712   Dock area is defined by Qt::DockWidgetArea enumeration:
713   - Qt::TopDockWidgetArea : top dock area
714   - Qt::BottomDockWidgetArea : bottom dock area
715   - Qt::LeftDockWidgetArea : left dock area
716   - Qt::RightDockWidgetArea : right dock area
717
718   \return map of dockable windows in form { <window_type> : <dock_area> }
719 */
720 QMap<int, int> PyModuleHelper::windows() const
721 {
722   FuncMsg fmsg( "PyModuleHelper::windows()" );
723
724   return myWindowsMap;
725 }
726
727 /*!
728   \brief Define the compatible view windows associated with the module.
729
730   The associated view windows are opened automatically when the module
731   is activated.
732
733   To fill the list of views the correspondind Python module's views()
734   method is called during the module initialization.
735   By default, the list of view types is empty.
736
737   \return list of view windows types
738 */
739 QStringList PyModuleHelper::viewManagers() const
740 {
741   FuncMsg fmsg( "PyModuleHelper::viewManagers()" );
742
743   return myViewMgrList;
744 }
745
746 /*!
747   \brief Initialization of the Python-based SALOME module.
748   
749   This method can be used for creation of the menus, toolbars and 
750   other such staff.
751   
752   There are two ways to do this:
753   1) for obsolete modules implementatino this method first tries to read
754   <module>_<language>.xml resource file which contains a menu,
755   toolbars and popup menus description;
756   2) new modules can create menus by direct calling of the
757   corresponding methods of SalomePyQt Python API in the Python
758   module's initialize() method which is called from here.
759
760   \note SALOME supports two modes of modules loading:
761   - immediate (all the modules are created and initialized 
762   immediately when the application object is created;
763   - postponed modules loading (used currently); in this mode
764   the module is loaded only by explicit request.
765   If postponed modules loading is not used, the active
766   study might be not yet defined at this stage, so initialize()
767   method should not perform any study-based initialization.
768   Such actions can be better done in activate() function.
769
770   \param app parent application object
771 */
772 void PyModuleHelper::initialize( CAM_Application* app )
773 {
774   FuncMsg fmsg( "PyModuleHelper::initialize()" );
775
776   // temporarily store module being currently activated
777   // in the global variable to make it accessible from
778   // Python API
779   InitLocker lock( myModule );
780
781   // try to get XML resource file name
782   SUIT_ResourceMgr* resMgr = myModule->getApp()->resourceMgr();
783   if ( !myXmlHandler && resMgr ) {
784     // get current language
785     QString lang = resMgr->stringValue( "language", "language", "en" );
786     // get menu description file name
787     QString aFileName = QString( "%1_%2.xml" ).arg( myModule->name() ).arg( lang );
788     aFileName = resMgr->path( "resources", myModule->name(), aFileName );
789     if ( !aFileName.isEmpty() && QFile::exists( aFileName ) ) {
790       // create XML handler instance
791       myXmlHandler = new XmlHandler( this, aFileName );
792       // ask XML handler to create actions
793       myXmlHandler->createActions();
794     }
795   }
796
797   class InitializeReq : public PyInterp_Request
798   {
799   public:
800     InitializeReq( PyModuleHelper*  _helper,
801                    CAM_Application* _app )
802       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
803         myHelper( _helper ),
804         myApp( _app )
805     {}
806   protected:
807     virtual void execute()
808     {
809       myHelper->internalInitialize( myApp );
810     }
811   private:
812     PyModuleHelper*  myHelper;
813     CAM_Application* myApp;
814   };
815
816   // post request
817   PyInterp_Dispatcher::Get()->Exec( new InitializeReq( this, app ) );
818 }
819
820 /*!
821   \brief Activation of the module.
822
823   This function is usually used in order to show the module's 
824   specific menus and toolbars, update actions state and perform
825   other such actions required when the module is activated.
826   
827   \note Returning \c false from this function prevents the 
828   module activation.
829
830   \param study parent study
831   \return \c true if activation is successful and \c false otherwise
832 */
833 bool PyModuleHelper::activate( SUIT_Study* study )
834 {
835   FuncMsg fmsg( "PyModuleHelper::activate()" );
836
837   // reset the activation status to the default value
838   myLastActivateStatus = true;
839
840   class ActivateReq : public PyInterp_Request
841   {
842   public:
843     ActivateReq( PyModuleHelper* _helper,
844                  SUIT_Study*     _study,
845                  bool            _customize )
846       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
847         myHelper( _helper ),
848         myStudy ( _study ),
849         myCustomize( _customize )
850     {}
851   protected:
852     virtual void execute()
853     {
854       if ( !myCustomize )
855         myHelper->internalActivate( myStudy );  // first activation stage
856       else
857         myHelper->internalCustomize( myStudy ); // second activation stage
858     }
859   private:
860     PyModuleHelper* myHelper;
861     SUIT_Study*     myStudy;
862     bool            myCustomize;
863   };
864
865   // post request for activation (customize=false)
866   PyInterp_Dispatcher::Get()->Exec( new ActivateReq( this, study, false ) );
867
868   // check activation status (can be set to false by internalActivate())
869   if ( myLastActivateStatus ) {
870     // activate menus, toolbars, etc
871     if ( myXmlHandler ) myXmlHandler->activateMenus( true );
872
873     // show menus / toolbars
874     myModule->setMenuShown( true );
875     myModule->setToolShown( true );
876
877     // post request for customization (customize=true)
878     PyInterp_Dispatcher::Get()->Exec( new ActivateReq( this, study, true ) );
879
880     // check activation status (can be set to false by internalCustomize())
881     if ( myLastActivateStatus ) {
882       // connect preferences changing signal
883       connect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
884                this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
885       
886       // connect active view change signal
887       SUIT_Desktop* d = study->application()->desktop();
888       connect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
889                this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
890       // if active window exists, call activeViewChanged() function;
891       // temporary solution: if a getActiveView() in SalomePyQt available
892       // we no longer need this 
893       SUIT_ViewWindow* view = d->activeWindow();
894       if ( view ) activeViewChanged( view );
895       // get all view currently opened in the study and connect their signals to 
896       // the corresponding slots of the class.
897       foreach ( view, d->windows() ) connectView( view );
898     }
899     else {
900       // hide menus / toolbars in case of error
901       myModule->setMenuShown( false );
902       myModule->setToolShown( false );
903     }
904   }
905
906   return myLastActivateStatus;
907 }
908
909 /*!
910   \brief Deactivation of the module.
911
912   This function is usually used in order to hide the module's 
913   specific menus and toolbars and perform other such actions
914   required when the module is deactivated.
915
916   \param study parent study
917   \return \c true if deactivation is successful and \c false otherwise
918 */
919 bool PyModuleHelper::deactivate( SUIT_Study* study )
920 {
921   FuncMsg fmsg( "PyModuleHelper::deactivate()" );
922
923   class DeactivateReq : public PyInterp_LockRequest
924   {
925   public:
926     DeactivateReq( PyInterp_Interp* _py_interp,
927                    PyModuleHelper*  _helper,
928                    SUIT_Study*      _study )
929       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
930         myHelper( _helper ),
931         myStudy ( _study )
932     {}
933   protected:
934     virtual void execute()
935     {
936       myHelper->internalDeactivate( myStudy );
937     }
938   private:
939     PyModuleHelper* myHelper;
940     SUIT_Study*     myStudy;
941   };
942
943   // post request
944   PyInterp_Dispatcher::Get()->Exec( new DeactivateReq( myInterp, this, study ) );
945
946   // disconnect preferences changing signal
947   disconnect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
948               this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
949   
950   // disconnect the SUIT_Desktop signal windowActivated()
951   SUIT_Desktop* d = study->application()->desktop();
952   disconnect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
953               this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
954
955   // deactivate menus, toolbars, etc
956   if ( myXmlHandler ) myXmlHandler->activateMenus( false );
957
958   // hide menus / toolbars
959   myModule->setMenuShown( false );
960   myModule->setToolShown( false );
961
962   return true;
963 }
964
965 /*!
966   \brief Close of the module.
967
968   This function is usually used in order to close the module's 
969   specific menus and toolbars and perform other such actions
970   required when the module is closed.
971 */
972 void PyModuleHelper::modelClosed( SUIT_Study* study )
973 {
974   FuncMsg fmsg( "PyModuleHelper::modelClosed()" );
975
976   class StudyClosedReq : public PyInterp_LockRequest
977   {
978   public:
979     StudyClosedReq( PyInterp_Interp* _py_interp,
980                    PyModuleHelper*  _helper,
981                    SUIT_Study*      _study )
982       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
983         myHelper( _helper ),
984         myStudy ( _study )
985     {}
986   protected:
987     virtual void execute()
988     {
989       myHelper->internalClosedStudy( myStudy );
990     }
991   private:
992     PyModuleHelper* myHelper;
993     SUIT_Study*     myStudy;
994   };
995
996   // post request
997   PyInterp_Dispatcher::Get()->Exec( new StudyClosedReq( myInterp, this, study ) );
998
999   // disconnect preferences changing signal
1000   disconnect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
1001               this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
1002   
1003   // disconnect the SUIT_Desktop signal windowActivated()
1004   SUIT_Desktop* d = study->application()->desktop();
1005   disconnect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
1006               this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
1007
1008   // deactivate menus, toolbars, etc
1009   if ( myXmlHandler ) myXmlHandler->activateMenus( false );
1010
1011   // hide menus / toolbars
1012   myModule->setMenuShown( false );
1013   myModule->setToolShown( false );
1014 }
1015
1016
1017 /*!
1018   \brief Process module's preferences changing.
1019
1020   Called when the module's own preferences are changed.
1021   
1022   \param section preference resources section
1023   \param parameter preference resources parameter name
1024 */
1025 void PyModuleHelper::preferencesChanged( const QString& section, 
1026                                          const QString& parameter )
1027 {
1028   FuncMsg fmsg( "PyModuleHelper::preferencesChanged()" );
1029
1030   class PrefChangeReq : public PyInterp_LockRequest
1031   {
1032   public:
1033     PrefChangeReq( PyInterp_Interp* _py_interp,
1034                    PyModuleHelper*  _helper,
1035                    const QString&   _section,
1036                    const QString&   _parameter )
1037       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1038         myHelper ( _helper ),
1039         mySection( _section ),
1040         myParameter( _parameter ) 
1041     {}
1042   protected:
1043     virtual void execute()
1044     {
1045       myHelper->internalPreferencesChanged( mySection, myParameter );
1046     }
1047   private:
1048     PyModuleHelper* myHelper;
1049     QString         mySection, myParameter;
1050   };
1051
1052   // post the request only if dispatcher is not busy!
1053   // execute request synchronously
1054   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1055     PyInterp_Dispatcher::Get()->Exec( new PrefChangeReq( myInterp, this, section, parameter ) );
1056 }
1057
1058 /*!
1059   \brief Process application preferences changing.
1060
1061   Called when any application setting is changed.
1062
1063   \param module preference module
1064   \param section preference resources section
1065   \param parameter preference resources parameter name
1066 */
1067 void PyModuleHelper::preferenceChanged( const QString& module, 
1068                                         const QString& section, 
1069                                         const QString& parameter )
1070 {
1071   FuncMsg fmsg( "PyModuleHelper::preferenceChanged()" );
1072
1073   // module's own preferences are processed by other preferencesChanged() method
1074   if ( module != myModule->moduleName() ) {
1075     // call helper
1076     preferencesChanged( section, parameter );
1077   }
1078 }
1079
1080 /*!
1081   \brief Process study activation.
1082   
1083   Called when study desktop is activated. Used for notifying the Python
1084   module about changing of the active study.
1085
1086   \param study study being activated
1087 */
1088 void PyModuleHelper::studyActivated( SUIT_Study* study )
1089 {
1090   FuncMsg fmsg( "PyModuleHelper::studyActivated()" );
1091
1092   // StudyChangedReq: request class for internal studyChanged() operation
1093   class StudyChangedReq : public PyInterp_Request
1094   {
1095   public:
1096     StudyChangedReq( PyModuleHelper* _helper,
1097                      SUIT_Study*     _study )
1098       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
1099         myHelper( _helper ), 
1100         myStudy ( _study )
1101     {}
1102   protected:
1103     virtual void execute()
1104     {
1105       myHelper->internalStudyChanged( myStudy );
1106     }
1107   private:
1108     PyModuleHelper* myHelper;
1109     SUIT_Study*     myStudy;
1110   };
1111
1112   // post request
1113   PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( this, study ) );
1114 }
1115
1116 /*!
1117   \brief Process action activation.
1118   
1119   Called when action is activated. Used for notifying the Python
1120   module about any related action activation.
1121
1122   \sa connectAction()
1123 */
1124 void PyModuleHelper::actionActivated()
1125 {
1126   FuncMsg fmsg( "PyModuleHelper::actionActivated()" );
1127
1128   // perform synchronous request to Python event dispatcher
1129   class ActionReq : public PyInterp_LockRequest
1130   {
1131   public:
1132     ActionReq( PyInterp_Interp* _py_interp,
1133                PyModuleHelper*  _helper,
1134                int              _id )
1135       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1136         myHelper( _helper ),
1137         myId    ( _id  )
1138     {}
1139   protected:
1140     virtual void execute()
1141     {
1142       myHelper->internalActionActivated( myId );
1143     }
1144   private:
1145     PyModuleHelper* myHelper;
1146     int             myId;
1147   };
1148
1149   // get sender action
1150   QAction* action = qobject_cast<QAction*>( sender() );
1151   if ( !action )
1152     return;
1153
1154   // post request
1155   PyInterp_Dispatcher::Get()->Exec( new ActionReq( myInterp, this, myModule->actionId( action ) ) );
1156 }
1157
1158 /*!
1159   \brief Process context popup menu request.
1160   
1161   Called when user activates popup menu in some window
1162   (view, object browser, etc).
1163
1164   \param context popup menu context (e.g. "ObjectBrowser")
1165   \param menu popup menu
1166 */
1167 void PyModuleHelper::contextMenu( const QString& context, QMenu* menu )
1168 {
1169   FuncMsg fmsg( "PyModuleHelper::contextMenu()" );
1170
1171   class ContextMenuReq : public PyInterp_LockRequest
1172   {
1173   public:
1174     ContextMenuReq( PyInterp_Interp* _py_interp,
1175                     PyModuleHelper*  _helper,
1176                     const QString&   _context,
1177                     QMenu*           _menu )
1178       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1179         myHelper ( _helper ),
1180         myContext( _context ),
1181         myMenu   ( _menu )
1182     {}
1183   protected:
1184     virtual void execute()
1185     {
1186       myHelper->internalContextMenu( myContext, myMenu );
1187     }
1188   private:
1189     PyModuleHelper* myHelper;
1190     QString         myContext;
1191     QMenu*          myMenu;
1192   };
1193
1194   // post request only if dispatcher is not busy!
1195   // execute request synchronously
1196   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1197     PyInterp_Dispatcher::Get()->Exec( new ContextMenuReq( myInterp, this, context, menu ) );
1198 }
1199
1200 /*!
1201   \brief Export preferences for the Python module.
1202   Called only once when the first instance of the module is created or
1203   when common Preferences dialog box is first time invoked.
1204 */
1205 void PyModuleHelper::createPreferences()
1206 {
1207   FuncMsg fmsg( "PyModuleHelper::createPreferences()" );
1208
1209   // temporary set myInitModule because createPreferences() method
1210   // might be called during the module intialization process
1211   InitLocker lock( myModule );
1212
1213   class CreatePrefReq : public PyInterp_LockRequest
1214   {
1215   public:
1216     CreatePrefReq( PyInterp_Interp* _py_interp,
1217                    PyModuleHelper*  _helper )
1218       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1219         myHelper( _helper )
1220     {}
1221   protected:
1222     virtual void execute()
1223     {
1224       myHelper->internalCreatePreferences();
1225     }
1226   private:
1227     PyModuleHelper* myHelper;
1228   };
1229
1230   // post request only if dispatcher is not busy!
1231   // execute request synchronously
1232   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1233     PyInterp_Dispatcher::Get()->Exec( new CreatePrefReq( myInterp, this ) );
1234 }
1235
1236 /*!
1237   \brief Signal handler windowActivated(SUIT_ViewWindow*) of SUIT_Desktop
1238
1239   Used to notify Python module that active view has been changed by the user.
1240
1241   \param view view being activated
1242 */
1243 void PyModuleHelper::activeViewChanged( SUIT_ViewWindow* view )
1244 {
1245   FuncMsg fmsg( "PyModuleHelper::activeViewChanged()" );
1246
1247   // perform synchronous request to Python event dispatcher
1248   class ActiveViewChangeReq : public PyInterp_LockRequest
1249   {
1250   public:
1251     ActiveViewChangeReq( PyInterp_Interp* _py_interp,
1252                          PyModuleHelper*  _helper, 
1253                          SUIT_ViewWindow* _view )
1254       : PyInterp_LockRequest( _py_interp, 0, true ),
1255         myHelper( _helper ),
1256         myView( _view )
1257     {}
1258   protected:
1259     virtual void execute()
1260     {
1261       myHelper->internalActiveViewChanged( myView );
1262     }
1263   private:
1264     PyModuleHelper*  myHelper;
1265     SUIT_ViewWindow* myView;
1266   };
1267
1268   // connect view (if it is not connected yet)
1269   connectView( view );
1270   
1271   PyInterp_Dispatcher::Get()->Exec( new ActiveViewChangeReq( myInterp, this, view ) ); 
1272 }
1273
1274 /*!
1275   \brief Signal handler tryClose(SUIT_ViewWindow*) of a view
1276   \param view view being closed
1277 */
1278 void PyModuleHelper::tryCloseView( SUIT_ViewWindow* view )
1279 {
1280   FuncMsg fmsg( "PyModuleHelper::tryCloseView()" );
1281
1282   class TryCloseViewReq : public PyInterp_LockRequest
1283   {
1284   public:
1285     TryCloseViewReq( PyInterp_Interp* _py_interp,
1286                      PyModuleHelper*  _helper, 
1287                      SUIT_ViewWindow* _view )
1288       : PyInterp_LockRequest( _py_interp, 0, true ),
1289         myHelper( _helper ), 
1290         myView( _view )
1291     {}
1292   protected:
1293     virtual void execute()
1294     {
1295       myHelper->internalTryCloseView( myView );
1296     }
1297   private:
1298     PyModuleHelper*  myHelper;
1299     SUIT_ViewWindow* myView;    
1300   };
1301
1302   PyInterp_Dispatcher::Get()->Exec( new TryCloseViewReq( myInterp, this, view ) );
1303 }
1304
1305 /*!
1306   \brief Signal handler closing(SUIT_ViewWindow*) of a view
1307   \param view view being closed
1308 */
1309 void PyModuleHelper::closeView( SUIT_ViewWindow* view )
1310 {
1311   FuncMsg fmsg( "PyModuleHelper::closeView()" );
1312
1313   class CloseViewReq : public PyInterp_LockRequest
1314   {
1315   public:
1316     CloseViewReq( PyInterp_Interp* _py_interp,
1317                   PyModuleHelper*  _helper, 
1318                   SUIT_ViewWindow* _view )
1319       : PyInterp_LockRequest( _py_interp, 0, true ),
1320         myHelper( _helper ),
1321         myView( _view )
1322     {}
1323   protected:
1324     virtual void execute()
1325     {
1326       myHelper->internalCloseView( myView );
1327     }
1328   private:
1329     PyModuleHelper*  myHelper;
1330     SUIT_ViewWindow* myView;    
1331   };
1332
1333   PyInterp_Dispatcher::Get()->Exec( new CloseViewReq( myInterp, this, view ) );
1334 }
1335
1336 /*!
1337   \brief Signal handler cloneView() of OCCViewer_ViewWindow
1338   \param view view being cloned
1339 */
1340 void PyModuleHelper::cloneView( SUIT_ViewWindow* view )
1341 {
1342   FuncMsg fmsg( "PyModuleHelper::cloneView()" );
1343
1344   class CloneViewReq : public PyInterp_LockRequest
1345   {
1346   public:
1347     CloneViewReq( PyInterp_Interp* _py_interp,
1348                   PyModuleHelper*  _helper, 
1349                   SUIT_ViewWindow* _view )
1350       : PyInterp_LockRequest( _py_interp, 0, true ),
1351         myHelper( _helper ),
1352         myView( _view )
1353     {}
1354   protected:
1355     virtual void execute()
1356     {
1357       myHelper->internalCloneView( myView );
1358     }
1359   private:
1360     PyModuleHelper*  myHelper;
1361     SUIT_ViewWindow* myView;
1362   };
1363   
1364   PyInterp_Dispatcher::Get()->Exec( new CloneViewReq( myInterp, this, view ) );
1365 }
1366
1367 /*!
1368   \brief Save module data. Called when user saves study.
1369   \param files output list of files where module stores data
1370 */
1371 void PyModuleHelper::save( QStringList& files )
1372 {
1373   FuncMsg fmsg( "PyModuleHelper::save()" );
1374
1375   // temporary set myInitModule because save() method
1376   // might be called by the framework when this module is inactive,
1377   // but still it should be possible to access this module's data
1378   // from Python
1379   InitLocker lock( myModule );
1380
1381   // perform synchronous request to Python event dispatcher
1382   class SaveReq: public PyInterp_LockRequest
1383   {
1384   public:     
1385     SaveReq( PyInterp_Interp* _py_interp,
1386              PyModuleHelper*  _helper,
1387              QStringList&     _files )
1388       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1389         myHelper( _helper ) ,
1390         myFiles( _files )
1391     {}
1392   protected:
1393     virtual void execute()
1394     {
1395       myHelper->internalSave( myFiles );
1396     }
1397   private:
1398     PyModuleHelper* myHelper;
1399     QStringList&    myFiles;
1400   };
1401   
1402   // Posting the request only if dispatcher is not busy!
1403   // Executing the request synchronously
1404   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1405     PyInterp_Dispatcher::Get()->Exec( new SaveReq( myInterp, this, files ) );
1406 }
1407
1408 /*
1409  \brief Load module data. Called when user opens study 
1410  and activates module.
1411  \param files list of files where module data is stored
1412 */
1413 bool PyModuleHelper::load( const QStringList& files )
1414 {
1415   FuncMsg fmsg( "PyModuleHelper::load()" );
1416
1417   bool loaded = false;
1418
1419   class LoadReq: public PyInterp_LockRequest
1420   {
1421   public:
1422     LoadReq( PyInterp_Interp* _py_interp,
1423              PyModuleHelper*  _helper,
1424              QStringList      _files,
1425              bool&            _loaded )
1426       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1427         myHelper( _helper ) ,
1428         myFiles( _files ),
1429         myLoaded( _loaded )
1430     {}
1431   protected:
1432     virtual void execute()
1433     {
1434       myHelper->internalLoad( myFiles, myLoaded );
1435     }
1436   private:
1437     PyModuleHelper* myHelper;
1438     QStringList     myFiles;
1439     bool&           myLoaded;
1440   };
1441   
1442   // Posting the request only if dispatcher is not busy!
1443   // Executing the request synchronously
1444   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1445     PyInterp_Dispatcher::Get()->Exec( new LoadReq( myInterp, this, files, loaded ) );
1446
1447   return loaded;
1448 }
1449
1450 /*!
1451   \brief Dump module data to the Python script. 
1452   Called when user activates dump study operation.
1453   \param files output list of files where module stores python script
1454 */
1455 void PyModuleHelper::dumpPython( QStringList& files )
1456 {
1457   FuncMsg fmsg( "PyModuleHelper::dumpPython()" );
1458
1459   // temporary set myInitModule because dumpPython() method
1460   // might be called by the framework when this module is inactive,
1461   // but still it should be possible to access this module's data
1462   // from Python
1463   InitLocker lock( myModule );
1464
1465   class DumpPythonReq: public PyInterp_LockRequest
1466   {
1467   public:     
1468     DumpPythonReq( PyInterp_Interp* _py_interp,
1469                    PyModuleHelper*  _helper,
1470                    QStringList&     _files )
1471       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1472         myHelper( _helper ) ,
1473         myFiles( _files )
1474     {}
1475   protected:
1476     virtual void execute()
1477     {
1478       myHelper->internalDumpPython( myFiles );
1479     }
1480   private:
1481     PyModuleHelper* myHelper;
1482     QStringList&    myFiles;
1483   };
1484   
1485   // Posting the request only if dispatcher is not busy!
1486   // Executing the request synchronously
1487   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1488     PyInterp_Dispatcher::Get()->Exec( new DumpPythonReq( myInterp, this, files ) );
1489 }
1490
1491 /*!
1492   \brief Test if object \a what can be dragged by the user.
1493   \param what data object being tested
1494   \return \c true if object can be dragged or \c false otherwise
1495 */
1496 bool PyModuleHelper::isDraggable( const SUIT_DataObject* what ) const
1497 {
1498   FuncMsg fmsg( "PyModuleHelper::isDraggable()" );
1499
1500   bool draggable = false;
1501
1502   // perform synchronous request to Python event dispatcher
1503   class IsDraggableReq: public PyInterp_LockRequest
1504   {
1505   public:
1506     IsDraggableReq( PyInterp_Interp*     _py_interp,
1507                     PyModuleHelper*      _helper,
1508                     LightApp_DataObject* _data_object,
1509                     bool&                _is_draggable )
1510       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1511         myHelper( _helper ) ,
1512         myDataObject( _data_object ),
1513         myIsDraggable( _is_draggable )
1514     {}
1515   protected:
1516     virtual void execute()
1517     {
1518       myIsDraggable = myHelper->internalIsDraggable( myDataObject );
1519     }
1520   private:
1521     PyModuleHelper*      myHelper;
1522     LightApp_DataObject* myDataObject;
1523     bool&                myIsDraggable;
1524   };
1525
1526   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( what );
1527   if ( data_object ) {
1528     // Posting the request only if dispatcher is not busy!
1529     // Executing the request synchronously
1530     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1531       PyInterp_Dispatcher::Get()->Exec( new IsDraggableReq( myInterp,
1532                                                             const_cast<PyModuleHelper*>( this ),
1533                                                             const_cast<LightApp_DataObject*>( data_object ),
1534                                                             draggable ) );
1535   }
1536   
1537   return draggable;
1538 }
1539
1540 /*!
1541   \brief Test if drop operation can be done on the \a where object.
1542   \param where data object being tested
1543   \return \c true if if drop operation is supported by object or \c false otherwise
1544 */
1545 bool PyModuleHelper::isDropAccepted( const SUIT_DataObject* where ) const
1546 {
1547   FuncMsg fmsg( "PyModuleHelper::isDropAccepted()" );
1548
1549   bool dropAccepted = false;
1550
1551   // perform synchronous request to Python event dispatcher
1552   class IsDropAcceptedReq: public PyInterp_LockRequest
1553   {
1554   public:
1555     IsDropAcceptedReq( PyInterp_Interp*     _py_interp,
1556                        PyModuleHelper*      _helper,
1557                        LightApp_DataObject* _data_object,
1558                        bool&                _is_drop_accepted )
1559       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1560         myHelper( _helper ) ,
1561         myDataObject( _data_object ),
1562         myIsDropAccepted( _is_drop_accepted )
1563     {}
1564   protected:
1565     virtual void execute()
1566     {
1567       myIsDropAccepted = myHelper->internalIsDropAccepted( myDataObject );
1568     }
1569   private:
1570     PyModuleHelper*      myHelper;
1571     LightApp_DataObject* myDataObject;
1572     bool&                myIsDropAccepted;
1573   };
1574   
1575   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( where );
1576   if ( data_object ) {
1577     // Posting the request only if dispatcher is not busy!
1578     // Executing the request synchronously
1579     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1580       PyInterp_Dispatcher::Get()->Exec( new IsDropAcceptedReq( myInterp,
1581                                                                const_cast<PyModuleHelper*>( this ),
1582                                                                const_cast<LightApp_DataObject*>( data_object ),
1583                                                                dropAccepted ) );
1584   }
1585
1586   return dropAccepted;
1587 }
1588
1589 /*!
1590   \brief Perform drop operation
1591   \param what list of data objects being dropped
1592   \param where target data object for drop operation
1593   \param row line (child item index) where drop operation is performed to
1594   \param action current drop action (copy or move)
1595 */
1596 void PyModuleHelper::dropObjects( const DataObjectList& what, SUIT_DataObject* where,
1597                                   const int row, Qt::DropAction action )
1598 {
1599   FuncMsg fmsg( "PyModuleHelper::dropObjects()" );
1600
1601   // perform synchronous request to Python event dispatcher
1602   class DropObjectsReq: public PyInterp_LockRequest
1603   {
1604   public:
1605     DropObjectsReq( PyInterp_Interp*      _py_interp,
1606                     PyModuleHelper*       _helper,
1607                     const DataObjectList& _what,
1608                     SUIT_DataObject*      _where,
1609                     const int             _row,
1610                     Qt::DropAction        _action )
1611       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1612         myHelper( _helper ) ,
1613         myWhat( _what ),
1614         myWhere( _where ),
1615         myRow( _row ),
1616         myAction ( _action )
1617     {}
1618   protected:
1619     virtual void execute()
1620     {
1621       myHelper->internalDropObjects( myWhat, myWhere, myRow, myAction );
1622     }
1623   private:
1624     PyModuleHelper*  myHelper;
1625     DataObjectList   myWhat;
1626     SUIT_DataObject* myWhere;
1627     int              myRow;
1628     Qt::DropAction   myAction;
1629   };
1630   
1631   // Posting the request only if dispatcher is not busy!
1632   // Executing the request synchronously
1633   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1634     PyInterp_Dispatcher::Get()->Exec( new DropObjectsReq( myInterp, this, what, where, row, action ) );
1635 }
1636
1637 /*!
1638   \brief Get module engine IOR
1639   \return engine IOR as it is supplied by GUI Python module
1640  */
1641 QString PyModuleHelper::engineIOR() const
1642 {
1643   FuncMsg fmsg( "PyModuleHelper::engineIOR()" );
1644
1645   class EngineIORReq : public PyInterp_LockRequest
1646   {
1647   public:
1648     EngineIORReq( PyInterp_Interp* _py_interp,
1649                   PyModuleHelper*  _helper,
1650                   QString&         _ior )
1651       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1652         myHelper( _helper ),
1653         myIOR( _ior )
1654     {}
1655   protected:
1656     virtual void execute()
1657     {
1658       myIOR = myHelper->internalEngineIOR();
1659     }
1660   private:
1661     PyModuleHelper* myHelper;
1662     QString&        myIOR;
1663   };
1664
1665   static QString anIOR;
1666
1667   if ( anIOR.isEmpty() ) {
1668     // post request
1669     PyInterp_Dispatcher::Get()->Exec( new EngineIORReq( myInterp, 
1670                                                         const_cast<PyModuleHelper*>( this ),
1671                                                         anIOR ) );
1672   }
1673
1674   return anIOR;
1675 }
1676
1677 /*!
1678   \brief Initialize python subinterpreter (one per study).
1679   \internal
1680   \param studyId study ID
1681 */
1682 void PyModuleHelper::initInterp( int studyId )
1683 {
1684   FuncMsg fmsg( "--- PyModuleHelper::initInterp()" );
1685
1686   // check study Id
1687   if ( !studyId ) {
1688     // Error! Study Id must not be 0!
1689     myInterp = 0;
1690     return;
1691   }
1692
1693   QMutexLocker ml( &myInitMutex );
1694
1695   // try to find the subinterpreter
1696   if ( myInterpMap.contains( studyId ) ) {
1697     // found!
1698     myInterp = myInterpMap[ studyId ];
1699     return;
1700   }
1701
1702   myInterp = new SALOME_PYQT_PyInterp();
1703   myInterp->initialize();
1704   myInterpMap[ studyId ] = myInterp;
1705   
1706 #ifndef GUI_DISABLE_CORBA
1707   if ( !SUIT_PYTHON::initialized ) {
1708     // import 'salome' module and call 'salome_init' method;
1709     // do it only once on interpreter creation
1710     // ... first get python lock
1711     PyLockWrapper aLock = myInterp->GetLockWrapper();
1712     // ... then import a module
1713     PyObjWrapper aMod = PyImport_ImportModule( "salome" );
1714     if ( !aMod ) {
1715       // Error!
1716       PyErr_Print();
1717       return;
1718     }
1719     // ... then call a method
1720     int embedded = 1;
1721     PyObjWrapper aRes( PyObject_CallMethod( aMod, (char*)"salome_init", (char*)"ii", studyId, embedded ) );
1722     if ( !aRes ) {
1723       // Error!
1724       PyErr_Print();
1725       return;
1726     }
1727   }
1728 #endif 
1729 }
1730
1731 /*!
1732   \brief Import Python GUI module and store reference to the module.
1733   \internal
1734
1735   Attention! initInterp() should be called first!!!
1736 */
1737 void PyModuleHelper::importModule()
1738 {
1739   FuncMsg fmsg( "--- PyModuleHelper::importModule()" );
1740
1741   // check if the subinterpreter is initialized
1742   if ( !myInterp ) {
1743     // Error! Python subinterpreter should be initialized first!
1744     myPyModule = 0;
1745     return;
1746   }
1747
1748   // import Python GUI module and put it in <myPyModule> attribute
1749   // ... first get python lock
1750   PyLockWrapper aLock = myInterp->GetLockWrapper();
1751   // ... then import a module
1752   QString aMod = QString( "%1GUI" ).arg( myModule->name() );
1753   try {
1754     myPyModule = PyImport_ImportModule( aMod.toLatin1().data() );
1755   }
1756   catch (...) {
1757   }
1758
1759   if ( !myPyModule ) {
1760     // Error!
1761     PyErr_Print();
1762     return;
1763   }
1764 }
1765
1766 /*!
1767   \brief Set study workspace to the Python module.
1768   \internal
1769
1770   Calls setWorkSpace() method of the Python module with 
1771   PyQt QWidget object to use with interpreter.
1772
1773   Attention! initInterp() and importModule() should be called first!!!
1774 */
1775 void PyModuleHelper::setWorkSpace()
1776 {
1777   FuncMsg fmsg( "--- PyModuleHelper::setWorkSpace()" );
1778
1779   if ( !IsCallOldMethods ) 
1780     return;
1781
1782   // check if the subinterpreter is initialized and Python module is imported
1783   if ( !myInterp || !myPyModule ) {
1784     // Error! Python subinterpreter should be initialized and module should be imported first!
1785     return;
1786   }
1787
1788   // call setWorkSpace() method
1789   // ... first get python lock
1790   PyLockWrapper aLock = myInterp->GetLockWrapper();
1791
1792   // ... then try to import SalomePyQt module. If it's not possible don't go on.
1793   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
1794   if( !aQtModule ) {
1795     // Error!
1796     PyErr_Print();
1797     return;
1798   }
1799
1800   // ... then get workspace object
1801   QWidget* aWorkspace = 0;
1802   if ( myModule->getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
1803     STD_MDIDesktop* d = dynamic_cast<STD_MDIDesktop*>( myModule->getApp()->desktop() );
1804     if ( d )
1805       aWorkspace = d->workspace();
1806   }
1807   else if ( myModule->getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
1808     STD_TabDesktop* d = dynamic_cast<STD_TabDesktop*>( myModule->getApp()->desktop() );
1809     if ( d )
1810       aWorkspace = d->workstack();
1811   }
1812 #if SIP_VERSION < 0x040800
1813   PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget) );
1814 #else
1815   PyObjWrapper pyws( sipBuildResult( 0, "D", aWorkspace, sipType_QWidget , NULL) );
1816 #endif
1817   // ... and finally call Python module's setWorkSpace() method (obsolete)
1818   if ( PyObject_HasAttrString( myPyModule, (char*)"setWorkSpace" ) ) {
1819     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setWorkSpace", (char*)"O", pyws.get() ) );
1820     if( !res ) {
1821       PyErr_Print();
1822     }
1823   }
1824 }
1825
1826 /*!
1827   \brief Initialization callback function
1828   \internal
1829
1830   Performs the following actions:
1831   - initialize or get the Python interpreter (one per study)
1832   - import the Python module
1833   - pass the workspace widget to the Python module
1834   - call Python module's initialize() method
1835   - call Python module's windows() method
1836   - call Python module's views() method
1837
1838   \param app parent application object
1839 */
1840 void PyModuleHelper::internalInitialize( CAM_Application* app )
1841 {
1842   FuncMsg fmsg( "--- PyModuleHelper::internalInitialize()" );
1843
1844   // reset interpreter to NULL
1845   myInterp = 0;
1846
1847   // get study Id
1848   LightApp_Application* anApp = dynamic_cast<LightApp_Application*>( app );
1849   if ( !anApp )
1850     return;
1851   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( app->activeStudy() );
1852   if ( !aStudy )
1853     return;
1854   int aStudyId = aStudy ? aStudy->id() : 0;
1855
1856   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
1857   initInterp( aStudyId );
1858   if ( !myInterp )
1859     return; // Error
1860
1861   // import Python GUI module
1862   importModule();
1863   if ( !myPyModule )
1864     return; // Error
1865
1866   // then call Python module's initialize() method
1867   // ... first get python lock
1868   PyLockWrapper aLock = myInterp->GetLockWrapper();
1869
1870   // ... (the Python module is already imported)
1871   // ... finally call Python module's initialize() method
1872   if ( PyObject_HasAttrString( myPyModule, (char*)"initialize" ) ) {
1873     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"initialize", (char*)"" ) );
1874     if ( !res ) {
1875       PyErr_Print();
1876     }
1877   }
1878
1879   // get required dockable windows list from the Python module 
1880   // by calling windows() method
1881   // ... first put default values
1882   myWindowsMap.insert( LightApp_Application::WT_ObjectBrowser, Qt::LeftDockWidgetArea );
1883   myWindowsMap.insert( LightApp_Application::WT_PyConsole,     Qt::BottomDockWidgetArea );
1884   myWindowsMap.insert( LightApp_Application::WT_LogWindow,     Qt::BottomDockWidgetArea );
1885
1886   if ( PyObject_HasAttrString( myPyModule , (char*)"windows" ) ) {
1887     PyObjWrapper res1( PyObject_CallMethod( myPyModule, (char*)"windows", (char*)"" ) );
1888     if ( !res1 ) {
1889       PyErr_Print();
1890     }
1891     else {
1892       myWindowsMap.clear();
1893       if ( PyDict_Check( res1 ) ) {
1894         PyObject* key;
1895         PyObject* value;
1896         Py_ssize_t pos = 0;
1897         while ( PyDict_Next( res1, &pos, &key, &value ) ) {
1898           // parse the return value
1899           // it should be a map: {integer:integer}
1900           int aKey, aValue;
1901           if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
1902             aKey   = PyInt_AsLong( key );
1903             aValue = PyInt_AsLong( value );
1904             myWindowsMap[ aKey ] = aValue;
1905           }
1906         }
1907       }
1908     }
1909   }
1910
1911   // get compatible view windows types from the Python module 
1912   // by calling views() method
1913   if ( PyObject_HasAttrString( myPyModule , (char*)"views" ) ) {
1914     PyObjWrapper res2( PyObject_CallMethod( myPyModule, (char*)"views", (char*)"" ) );
1915     if ( !res2 ) {
1916       PyErr_Print();
1917     }
1918     else {
1919       // parse the return value
1920       // result can be one string...
1921       if ( PyString_Check( res2 ) ) {
1922         myViewMgrList.append( PyString_AsString( res2 ) );
1923       }
1924       // ... or list of strings
1925       else if ( PyList_Check( res2 ) ) {
1926         int size = PyList_Size( res2 );
1927         for ( int i = 0; i < size; i++ ) {
1928           PyObject* value = PyList_GetItem( res2, i );
1929           if( value && PyString_Check( value ) ) {
1930             myViewMgrList.append( PyString_AsString( value ) );
1931           }
1932         }
1933       }
1934     }
1935   }
1936 }
1937
1938 /*!
1939   \brief Activation callback function
1940   \internal
1941
1942   Performs the following actions:
1943   - initialize or get the Python interpreter (one per study)
1944   - import the Python GUI module
1945   - call Python module's activate() method
1946
1947   \param study parent study
1948 */
1949 void PyModuleHelper::internalActivate( SUIT_Study* study )
1950 {
1951   FuncMsg fmsg( "--- PyModuleHelper::internalActivate()" );
1952
1953   // get study Id
1954   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
1955   int aStudyId = aStudy ? aStudy->id() : 0;
1956
1957   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
1958   initInterp( aStudyId );
1959   if ( !myInterp ) {
1960     myLastActivateStatus = false;
1961     return; // Error
1962   }
1963
1964   // import Python GUI module
1965   importModule();
1966   if ( !myPyModule ) {
1967     myLastActivateStatus = false;
1968     return; // Error
1969   }
1970
1971   // get python lock
1972   PyLockWrapper aLock = myInterp->GetLockWrapper();
1973
1974   // call Python module's activate() method (for the new modules)
1975   if ( PyObject_HasAttrString( myPyModule , (char*)"activate" ) ) {
1976     PyObject* res1 = PyObject_CallMethod( myPyModule, (char*)"activate", (char*)"" );
1977     if ( !res1 || !PyBool_Check( res1 ) ) {
1978       PyErr_Print();
1979       // always true for old modules (no return value)
1980       myLastActivateStatus = true;
1981     }
1982     else {
1983       // detect return status
1984       myLastActivateStatus = PyObject_IsTrue( res1 );
1985     }
1986   } 
1987 }
1988
1989 /*!
1990   \brief Additional menu customization callback function
1991   \internal
1992
1993   Performs the following actions:
1994   - get the Python interpreter (one per study)
1995   - import the Python GUI module
1996   - call Python module's setSettings() method (obsolete function, 
1997   used for compatibility with old code)
1998
1999   \param study parent study
2000 */
2001 void PyModuleHelper::internalCustomize( SUIT_Study* study )
2002 {
2003   FuncMsg fmsg( "--- PyModuleHelper::internalCustomize()" );
2004
2005   // get study Id
2006   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2007   int aStudyId = aStudy ? aStudy->id() : 0;
2008
2009   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2010   initInterp( aStudyId );
2011   if ( !myInterp ) {
2012     myLastActivateStatus = false;
2013     return; // Error
2014   }
2015
2016   // import Python GUI module
2017   importModule();
2018   if ( !myPyModule ) {
2019     myLastActivateStatus = false;
2020     return; // Error
2021   }
2022
2023   // call Python module's setWorkSpace() method (obsolete)
2024   setWorkSpace();
2025
2026   // get python lock
2027   PyLockWrapper aLock = myInterp->GetLockWrapper();
2028
2029   if ( IsCallOldMethods ) {
2030     // call Python module's setSettings() method (obsolete)
2031     if ( PyObject_HasAttrString( myPyModule , (char*)"setSettings" ) ) {
2032       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setSettings", (char*)"" ) );
2033       if( !res ) {
2034         PyErr_Print();
2035       }
2036       myLastActivateStatus = myLastActivateStatus && true;
2037     }
2038   }
2039 }
2040
2041 /*!
2042   \brief Deactivation callback function
2043   \internal
2044
2045   Performs the following actions:
2046   - call Python module's deactivate() method
2047
2048   \param study parent study
2049 */
2050 void PyModuleHelper::internalDeactivate( SUIT_Study* study )
2051 {
2052   FuncMsg fmsg( "--- PyModuleHelper::internalDeactivate()" );
2053
2054   // check that Python subinterpreter is initialized and Python module is imported
2055   if ( !myInterp || !myPyModule ) {
2056     // Error! Python subinterpreter should be initialized and module should be imported first!
2057     return;
2058   }
2059   // then call Python module's deactivate() method
2060   if ( PyObject_HasAttrString( myPyModule , (char*)"deactivate" ) ) {
2061     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"deactivate", (char*)"" ) );
2062     if( !res ) {
2063       PyErr_Print();
2064     }
2065   }
2066 }
2067
2068 /*!
2069   \brief Internal closure:
2070
2071   Performs the following actions:
2072   - call Python module's closeStudy() method
2073
2074   \param theStudy parent study object
2075 */
2076 void PyModuleHelper::internalClosedStudy( SUIT_Study* theStudy )
2077 {
2078   FuncMsg fmsg( "--- PyModuleHelper::internalClosedStudy()" );
2079
2080   // Get study Id
2081   // get study Id
2082   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( theStudy );
2083   int aStudyId = aStudy ? aStudy->id() : 0;
2084
2085   // check that Python subinterpreter is initialized and Python module is imported
2086   if ( !myInterp || !myPyModule ) {
2087     // Error! Python subinterpreter should be initialized and module should be imported first!
2088     return;
2089   }
2090   // then call Python module's deactivate() method
2091   if ( PyObject_HasAttrString( myPyModule , (char*)"closeStudy" ) ) {
2092     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"closeStudy", (char*)"i", aStudyId ) );
2093     if( !res ) {
2094       PyErr_Print();
2095     }
2096   }
2097 }
2098
2099
2100
2101 /*!
2102   \brief Preference changing callback function.
2103   \internal
2104
2105   Performs the following actions:
2106   - call Python module's preferenceChanged() method
2107
2108   \param section resources section name
2109   \param setting resources parameter name
2110 */
2111 void PyModuleHelper::internalPreferencesChanged( const QString& section, const QString& setting )
2112 {
2113   FuncMsg fmsg( "--- PyModuleHelper::internalPreferencesChanged()" );
2114
2115   // check that Python subinterpreter is initialized and Python module is imported
2116   if ( !myInterp || !myPyModule ) {
2117     // Error! Python subinterpreter should be initialized and module should be imported first!
2118     return;
2119   }
2120
2121   if ( PyObject_HasAttrString( myPyModule, (char*)"preferenceChanged" ) ) {
2122     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2123                                            (char*)"preferenceChanged", 
2124                                            (char*)"ss", 
2125                                            section.toLatin1().constData(), 
2126                                            setting.toLatin1().constData() ) );
2127     if( !res ) {
2128       PyErr_Print();
2129     }
2130   }
2131 }
2132
2133 /*!
2134   \brief Active study change callback function.
2135   \internal
2136
2137   Called when active the study is actived (user brings its 
2138   desktop to top):
2139   - initialize or get the Python interpreter (one per study)
2140   - import the Python GUI module
2141   - call Python module's activeStudyChanged() method
2142
2143   \param study study being activated
2144 */
2145 void PyModuleHelper::internalStudyChanged( SUIT_Study* study )
2146 {
2147   FuncMsg fmsg( "--- PyModuleHelper::internalStudyChanged()" );
2148
2149   // get study Id
2150   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2151   int id = aStudy ? aStudy->id() : 0;
2152
2153   fmsg.message( QString( "study id = %1" ).arg( id ) );
2154
2155   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2156   initInterp( id );
2157   if ( !myInterp )
2158     return; // Error
2159
2160   // import Python GUI module
2161   importModule();
2162   if ( !myPyModule )
2163     return; // Error
2164
2165   // call Python module's setWorkSpace() method
2166   setWorkSpace();
2167
2168   // get python lock
2169   PyLockWrapper aLock = myInterp->GetLockWrapper();
2170
2171   // call Python module's activeStudyChanged() method
2172   if ( PyObject_HasAttrString( myPyModule, (char*)"activeStudyChanged" ) ) {
2173     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeStudyChanged", (char*)"i", id ) );
2174     if( !res ) {
2175       PyErr_Print();
2176     }
2177   }
2178 }
2179
2180 /*!
2181   \brief GUI event handling callback function
2182   \internal 
2183
2184   Performs the following actions:
2185   - calls Python module's OnGUIEvent() method
2186
2187   \param id GUI action ID
2188 */
2189 void PyModuleHelper::internalActionActivated( int id )
2190 {
2191   FuncMsg fmsg( "--- PyModuleHelper::internalActionActivated()" );
2192   fmsg.message( QString( "action id = %1" ).arg( id ) );
2193
2194   // Python interpreter should be initialized and Python module should be
2195   // import first
2196   if ( !myInterp || !myPyModule )
2197     return; // Error
2198
2199   if ( PyObject_HasAttrString( myPyModule, (char*)"OnGUIEvent" ) ) {
2200     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"OnGUIEvent", (char*)"i", id ) );
2201     if( !res ) {
2202       PyErr_Print();
2203     }
2204   }
2205 }
2206
2207 /*!
2208   \brief Context popup menu handling callback function
2209   \internal
2210
2211   Performs the following actions:
2212   - calls Python module's definePopup(...) method (obsolete function, 
2213   used for compatibility with old code) to define the popup menu context
2214   - parses XML resourses file (if exists) and fills the popup menu with the items)
2215   - calls Python module's customPopup(...) method (obsolete function, 
2216   used for compatibility with old code) to allow module to customize the popup menu
2217   - for new modules calls createPopupMenu() function to allow the 
2218   modules to build the popup menu by using insertItem(...) Qt functions.
2219
2220   \param context popup menu context
2221   \param menu popup menu
2222 */
2223 void PyModuleHelper::internalContextMenu( const QString& context, QMenu* menu )
2224 {
2225   FuncMsg fmsg( "--- PyModuleHelper::internalContextMenu()" );
2226   fmsg.message( QString( "context: %1" ).arg( context ) );
2227
2228   // Python interpreter should be initialized and Python module should be
2229   // import first
2230   if ( !myInterp || !myPyModule )
2231     return; // Error
2232
2233   QString aContext( "" ), aObject( "" ), aParent( context );
2234
2235   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"definePopup" ) ) {
2236     // call definePopup() Python module's function
2237     // this is obsolete function, used only for compatibility reasons
2238     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2239                                            (char*)"definePopup",
2240                                            (char*)"sss",
2241                                            context.toLatin1().constData(),
2242                                            aObject.toLatin1().constData(),
2243                                            aParent.toLatin1().constData() ) );
2244     if( !res ) {
2245       PyErr_Print();
2246     }
2247     else {
2248       // parse return value
2249       char *co, *ob, *pa;
2250       if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
2251         aContext = co;
2252         aObject  = ob;
2253         aParent  = pa;
2254       }
2255     }
2256   } // if ( IsCallOldMethods ... )
2257
2258   // first try to create menu via XML parser:
2259   // we create popup menus without help of QtxPopupMgr
2260   if ( myXmlHandler )
2261     myXmlHandler->createPopup( menu, aContext, aParent, aObject );
2262
2263 #if SIP_VERSION < 0x040800
2264   PyObjWrapper sipPopup( sipBuildResult( 0, "M", menu, sipClass_QMenu ) );
2265 #else
2266   PyObjWrapper sipPopup( sipBuildResult( 0, "D", menu, sipType_QMenu, NULL ) );
2267 #endif
2268
2269   // then call Python module's createPopupMenu() method (for new modules)
2270   if ( PyObject_HasAttrString( myPyModule, (char*)"createPopupMenu" ) ) {
2271     PyObjWrapper res1( PyObject_CallMethod( myPyModule,
2272                                             (char*)"createPopupMenu",
2273                                             (char*)"Os",
2274                                             sipPopup.get(),
2275                                             context.toLatin1().constData() ) );
2276     if( !res1 ) {
2277       PyErr_Print();
2278     }
2279   }
2280
2281   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"customPopup" ) ) {
2282     // call customPopup() Python module's function
2283     // this is obsolete function, used only for compatibility reasons
2284     PyObjWrapper res2( PyObject_CallMethod( myPyModule,
2285                                             (char*)"customPopup",
2286                                             (char*)"Osss",
2287                                             sipPopup.get(),
2288                                             aContext.toLatin1().constData(),
2289                                             aObject.toLatin1().constData(),
2290                                             aParent.toLatin1().constData() ) );
2291     if( !res2 ) {
2292       PyErr_Print();
2293     }
2294   }
2295 }
2296
2297 /*!
2298   \brief Preferences initialization callback function.
2299   \internal
2300
2301   Performs the following actions:
2302   - calls Python module's createPreferences() method
2303 */
2304 void PyModuleHelper::internalCreatePreferences()
2305 {
2306   FuncMsg fmsg( "--- PyModuleHelper::internalCreatePreferences()" );
2307
2308   // Python interpreter should be initialized and Python module should be
2309   // import first
2310   if ( !myInterp || !myPyModule )
2311     return; // Error
2312
2313   if ( PyObject_HasAttrString( myPyModule, (char*)"createPreferences" ) ) {
2314     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"createPreferences", (char*)"" ) );
2315     if( !res ) {
2316       PyErr_Print();
2317     }
2318   }
2319 }
2320
2321 /*!
2322   \brief Active view changing callback function
2323   \internal
2324   \param view view being activated
2325 */
2326 void PyModuleHelper::internalActiveViewChanged( SUIT_ViewWindow* view )
2327 {
2328   FuncMsg fmsg( "--- PyModuleHelper::internalActiveViewChanged()" );
2329
2330   if ( !myInterp || !myPyModule || !view ) 
2331     return;
2332   
2333   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2334
2335   if ( PyObject_HasAttrString( myPyModule, (char*)"activeViewChanged" ) ) {
2336     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeViewChanged", (char*)"i" , view->getId() ) );
2337     if ( !res ) {
2338       PyErr_Print();
2339     }
2340   }
2341 }
2342
2343 /*!
2344   \brief View closing callback function
2345   \internal
2346   \param view view user tries to close
2347 */
2348 void PyModuleHelper::internalTryCloseView( SUIT_ViewWindow* view )
2349 {
2350   FuncMsg fmsg( "--- PyModuleHelper::internalTryCloseView()" );
2351
2352   if ( !myInterp || !myPyModule || !view ) 
2353     return;  
2354
2355   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2356
2357   if ( PyObject_HasAttrString( myPyModule, (char*)"viewTryClose" ) ) 
2358   {
2359     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewTryClose", (char*)"i", view->getId() ) );
2360     if ( !res )
2361     {
2362       PyErr_Print();
2363     }
2364   }
2365 }
2366
2367 /*!
2368   \brief View closing callback function
2369   \internal
2370   \param view view being closed
2371 */
2372 void PyModuleHelper::internalCloseView( SUIT_ViewWindow* view )
2373 {
2374   FuncMsg fmsg( "--- PyModuleHelper::internalCloseView()" );
2375
2376   if ( !myInterp || !myPyModule || !view ) 
2377     return;  
2378
2379   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2380
2381   if ( PyObject_HasAttrString( myPyModule, (char*)"viewClosed" ) ) 
2382   {
2383     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewClosed", (char*)"i", view->getId() ) );
2384     if ( !res )
2385     {
2386       PyErr_Print();
2387     }
2388   }
2389 }
2390
2391 /*!
2392   \brief View cloning callback function
2393   \internal
2394   \param view view being cloned
2395 */
2396 void PyModuleHelper::internalCloneView( SUIT_ViewWindow* view )
2397 {
2398   FuncMsg fmsg( "--- PyModuleHelper::internalCloneView()" );
2399
2400   if ( !myInterp || !myPyModule || !view ) 
2401     return;  
2402
2403   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2404
2405   if ( PyObject_HasAttrString( myPyModule, (char*)"viewCloned" ) ) 
2406   {
2407     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewCloned", (char*)"i", view->getId() ) );
2408     if( !res )
2409       PyErr_Print();
2410   }
2411 }
2412
2413 /*!
2414   \brief Module data saving callback function.
2415   \internal
2416   \param files output list of files where module stores data
2417 */
2418 void PyModuleHelper::internalSave( QStringList& files )
2419 {
2420   FuncMsg fmsg( "--- PyModuleHelper::internalSave()" );
2421
2422   // Python interpreter should be initialized and Python module should be
2423   // import firs
2424   // files list should contain a path to the temporary directory as a first entry
2425   if ( !myInterp || !myPyModule || files.isEmpty() )
2426     return;
2427
2428   if ( PyObject_HasAttrString(myPyModule, (char*)"saveFiles") ) {
2429
2430     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"saveFiles",
2431                                            (char*)"s", files.first().toLatin1().constData() ) );
2432
2433     if ( !res ) {
2434       PyErr_Print();
2435     }
2436     else {
2437       // parse the return value
2438       // result can be one string...
2439       if ( PyString_Check( res ) ) {
2440         QString astr = PyString_AsString( res );
2441         files.append( astr );
2442       }
2443       //also result can be a list...
2444       else if ( PyList_Check( res ) ) {
2445         int size = PyList_Size( res );
2446         for ( int i = 0; i < size; i++ ) {
2447           PyObject* value = PyList_GetItem( res, i );
2448           if ( value && PyString_Check( value ) ) {
2449             files.append( PyString_AsString( value ) );
2450           }
2451         }
2452       }
2453     }
2454   }
2455 }
2456
2457 /*!
2458   \brief Module data loading callback function.
2459   \internal
2460   \param files list of files where module data is stored
2461   \param opened output success flag
2462 */
2463 void PyModuleHelper::internalLoad( const QStringList& files, bool& opened )
2464 {
2465   FuncMsg fmsg( "--- PyModuleHelper::internalLoad()" );
2466
2467   // Python interpreter should be initialized and Python module should be
2468   // import first
2469   if ( !myInterp || !myPyModule || files.isEmpty() )
2470     return;
2471
2472   QStringList* theList = new QStringList( files );
2473
2474 #if SIP_VERSION < 0x040800
2475   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList ) );
2476 #else
2477   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL ) );
2478 #endif
2479   if ( PyObject_HasAttrString(myPyModule , (char*)"openFiles") ) {
2480     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"openFiles",
2481                                            (char*)"O", sipList.get()));
2482     if( !res || !PyBool_Check( res )) {
2483       PyErr_Print();
2484       opened = false;
2485     }
2486     else{
2487       opened = PyObject_IsTrue( res );
2488     }
2489   }
2490 }
2491
2492 /*!
2493   \brief Module dump python callback function.
2494   \internal
2495   \param files output list of files where module stores python script
2496 */
2497 void PyModuleHelper::internalDumpPython( QStringList& files )
2498 {
2499   FuncMsg fmsg( "--- PyModuleHelper::internalDumpPython()" );
2500
2501   // Python interpreter should be initialized and Python module should be
2502   // import first
2503   // files list should contain a path to the temporary directory
2504   if ( !myInterp || !myPyModule || files.isEmpty() )
2505     return;
2506
2507   if ( PyObject_HasAttrString(myPyModule, (char*)"dumpStudy") ) {
2508     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dumpStudy",
2509                                            (char*)"s", files.first().toLatin1().constData()));
2510
2511     if ( !res ) {
2512       PyErr_Print();
2513     }
2514     else {
2515       // parse the return value
2516       // result can be one string...
2517       if ( PyString_Check( res ) ) {
2518         QString astr = PyString_AsString( res );
2519         //SCRUTE(astr);
2520         files.append(astr);
2521       }
2522       //also result can be a list...
2523       else if ( PyList_Check( res ) ) {
2524         int size = PyList_Size( res );
2525         for ( int i = 0; i < size; i++ ) {
2526           PyObject* value = PyList_GetItem( res, i );
2527           if( value && PyString_Check( value ) ) {
2528             files.append( PyString_AsString( value ) );
2529           }
2530         }
2531       }
2532     }
2533   }
2534 }
2535
2536 /*!
2537   \brief Check data object's 'draggable' status callback function.
2538   \internal
2539   \param what data object being tested
2540   \return \c true if object can be dragged or \c false otherwise
2541 */
2542 bool PyModuleHelper::internalIsDraggable( LightApp_DataObject* what )
2543 {
2544   FuncMsg fmsg( "--- PyModuleHelper::internalIsDraggable()" );
2545
2546   // Python interpreter should be initialized and Python module should be
2547   // import first
2548   if ( !myInterp || !myPyModule || !what )
2549     return false;
2550
2551   bool draggable = false;
2552
2553   if ( PyObject_HasAttrString(myPyModule , (char*)"isDraggable") ) {
2554     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDraggable",
2555                                            (char*)"s", what->entry().toLatin1().constData() ) );
2556     if( !res || !PyBool_Check( res )) {
2557       PyErr_Print();
2558       draggable = false;
2559     }
2560     else{
2561       draggable = PyObject_IsTrue( res );
2562     }
2563   }
2564
2565   return draggable;
2566 }
2567
2568 /*!
2569   \brief Check data object's 'drop allowed' status callback function.
2570   \internal
2571   \param where data object being tested
2572   \return \c true if if drop operation is supported by object or \c false otherwise
2573 */
2574 bool PyModuleHelper::internalIsDropAccepted( LightApp_DataObject* where )
2575 {
2576   FuncMsg fmsg( "--- PyModuleHelper::internalIsDropAccepted()" );
2577
2578   // Python interpreter should be initialized and Python module should be
2579   // import first
2580   if ( !myInterp || !myPyModule || !where )
2581     return false;
2582
2583   bool dropAccepted = false;
2584
2585   if ( PyObject_HasAttrString(myPyModule , (char*)"isDropAccepted") ) {
2586     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDropAccepted",
2587                                            (char*)"s", where->entry().toLatin1().constData() ) );
2588     if( !res || !PyBool_Check( res )) {
2589       PyErr_Print();
2590       dropAccepted = false;
2591     }
2592     else{
2593       dropAccepted = PyObject_IsTrue( res );
2594     }
2595   }
2596
2597   return dropAccepted;
2598 }
2599
2600 /*!
2601   \brief Data dropping callback function.
2602   \internal
2603   \param what list of data objects being dropped
2604   \param where target data object for drop operation
2605   \param row line (child item index) where drop operation is performed to
2606   \param action current drop action (copy or move)
2607 */
2608 void PyModuleHelper::internalDropObjects( const DataObjectList& what, SUIT_DataObject* where,
2609                                           const int row, Qt::DropAction action )
2610 {
2611   FuncMsg fmsg( "--- PyModuleHelper::internalDropObjects()" );
2612
2613   // Python interpreter should be initialized and Python module should be
2614   // import first
2615   if ( !myInterp || !myPyModule || what.isEmpty() || !where )
2616     return;
2617
2618   QStringList* theList = new QStringList();
2619
2620   LightApp_DataObject* whereObject = dynamic_cast<LightApp_DataObject*>( where );
2621   if ( !whereObject ) return;
2622   
2623   for ( int i = 0; i < what.count(); i++ ) {
2624     LightApp_DataObject* dataObject = dynamic_cast<LightApp_DataObject*>( what[i] );
2625     if ( dataObject ) theList->append( dataObject->entry() );
2626   }
2627
2628 #if SIP_VERSION < 0x040800
2629   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList) );
2630 #else
2631   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL) );
2632 #endif
2633   if ( PyObject_HasAttrString(myPyModule, (char*)"dropObjects") ) {
2634     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dropObjects", (char*)"Osii",
2635                                            sipList.get(),
2636                                            whereObject->entry().toLatin1().constData(),
2637                                            row, action ) );
2638     
2639     if( !res ) {
2640       PyErr_Print();
2641     }
2642   }
2643 }
2644
2645 /*!
2646   \brief Get engine IOR callback function
2647   \internal
2648   
2649   Tries to get engine IOR from the Python module using engineIOR() function.
2650   That function can load module engine using appropriate container if required.
2651
2652   \return engine IOR or empty string if it is not provided by Python module 
2653 */
2654 QString PyModuleHelper::internalEngineIOR() const
2655 {
2656   FuncMsg fmsg( "--- PyModuleHelper::internalEngineIOR()" );
2657
2658   QString ior;
2659
2660   // Python interpreter should be initialized and Python module should be
2661   // import first
2662   if ( myInterp && myModule ) {
2663     if ( PyObject_HasAttrString( myPyModule , "engineIOR" ) ) {
2664       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"engineIOR", (char*)"" ) );
2665       if ( !res ) {
2666         PyErr_Print();
2667       }
2668       else {
2669         // parse the return value, result chould be string
2670         if ( PyString_Check( res ) ) {
2671           ior = PyString_AsString( res );
2672         }
2673       }
2674     }
2675   }
2676   return ior;
2677 }
2678
2679 /*!
2680   \brief Connects signals about activating and cloning view on internal slots
2681   \param view view being connected
2682 */
2683 void PyModuleHelper::connectView( SUIT_ViewWindow* view )
2684 {
2685   SUIT_ViewManager* viewMgr = view->getViewManager();
2686   SUIT_ViewModel* viewModel = viewMgr ? viewMgr->getViewModel() : 0;
2687       
2688   // Connect tryCloseView() and deleteView() signals
2689   if ( viewMgr ) {
2690     connect( viewMgr, SIGNAL( tryCloseView( SUIT_ViewWindow* ) ),
2691              this, SLOT( tryCloseView( SUIT_ViewWindow* ) ),
2692              Qt::UniqueConnection );
2693     connect( viewMgr, SIGNAL( deleteView( SUIT_ViewWindow* ) ),
2694              this, SLOT( closeView( SUIT_ViewWindow* ) ),
2695              Qt::UniqueConnection );
2696   }
2697   
2698   // Connect cloneView() signal of an OCC View
2699   if ( view->inherits( "OCCViewer_ViewWindow" ) ) {
2700     connect( view, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2701              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2702              Qt::UniqueConnection );
2703   }
2704   // Connect cloneView() signal of Plot2d View 
2705   else if ( viewModel && viewModel->inherits( "Plot2d_Viewer" ) ) {
2706     connect( viewModel, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2707              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2708              Qt::UniqueConnection );
2709   }
2710 }
2711
2712
2713
2714 void PyModuleHelper::internalOBClickedPython( const QString& theObj, int theColumn)
2715 {
2716   FuncMsg fmsg( "--- PyModuleHelper::internalOBClickedPython()" );
2717
2718   // Python interpreter should be initialized and Python module should be
2719   // import first
2720   if ( !myInterp || !myPyModule )
2721     return; // Error
2722
2723   if ( PyObject_HasAttrString( myPyModule, (char*)"onObjectBrowserClicked" ) ) {
2724     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"onObjectBrowserClicked", (char*)"si", theObj.toLatin1().constData(), theColumn ) );
2725     if( !res ) {
2726       PyErr_Print();
2727     }
2728   }
2729 }
2730
2731
2732
2733 void PyModuleHelper::onObjectBrowserClicked(SUIT_DataObject* theObj, int theColumn)
2734 {
2735   FuncMsg fmsg( "PyModuleHelper::onObjectBrowserClicked()" );
2736
2737   // temporary set myInitModule because dumpPython() method
2738   // might be called by the framework when this module is inactive,
2739   // but still it should be possible to access this module's data
2740   // from Python
2741   InitLocker lock( myModule );
2742
2743   class PythonReq: public PyInterp_LockRequest
2744   {
2745   public:     
2746     PythonReq( PyInterp_Interp* _py_interp,
2747                PyModuleHelper*  _helper,
2748                const QString& _entry,
2749                int     _column )
2750       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
2751         myHelper( _helper ) ,
2752         myEntry( _entry ),
2753         myColumn( _column )
2754     {}
2755   protected:
2756     virtual void execute()
2757     {
2758       myHelper->internalOBClickedPython( myEntry, myColumn );
2759     }
2760   private:
2761     PyModuleHelper* myHelper;
2762     int    myColumn;
2763     QString myEntry;
2764   };
2765   
2766   // Posting the request only if dispatcher is not busy!
2767   // Executing the request synchronously
2768   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( theObj );
2769   if ( (!PyInterp_Dispatcher::Get()->IsBusy()) && data_object )
2770     PyInterp_Dispatcher::Get()->Exec( new PythonReq( myInterp, this, data_object->entry(), theColumn ) );
2771 }
2772