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