Salome HOME
selection from PyQt first step OK
[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 update selection from other views or modules.
1167
1168   Called when selection is modified outside.
1169 */
1170 void PyModuleHelper::selectionUpdated(const QStringList& entries)
1171 {
1172   FuncMsg fmsg( "PyModuleHelper::selectionUpdated()" );
1173
1174   // perform synchronous request to Python event dispatcher
1175   class SelectionReq : public PyInterp_LockRequest
1176   {
1177   public:
1178     SelectionReq( PyInterp_Interp* _py_interp,
1179                   PyModuleHelper*  _helper,
1180                   const QStringList& _entries )
1181       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1182         myHelper( _helper ),
1183         myEntries( _entries  )
1184     {}
1185   protected:
1186     virtual void execute()
1187     {
1188       myHelper->internalSelectionUpdated( myEntries );
1189     }
1190   private:
1191     PyModuleHelper* myHelper;
1192     const QStringList& myEntries;
1193   };
1194
1195   // get sender action
1196   QAction* action = qobject_cast<QAction*>( sender() );
1197   if ( !action )
1198     return;
1199
1200   // post request
1201   PyInterp_Dispatcher::Get()->Exec( new SelectionReq( myInterp, this, entries ) );
1202 }
1203
1204 /*!
1205   \brief Process context popup menu request.
1206   
1207   Called when user activates popup menu in some window
1208   (view, object browser, etc).
1209
1210   \param context popup menu context (e.g. "ObjectBrowser")
1211   \param menu popup menu
1212 */
1213 void PyModuleHelper::contextMenu( const QString& context, QMenu* menu )
1214 {
1215   FuncMsg fmsg( "PyModuleHelper::contextMenu()" );
1216
1217   class ContextMenuReq : public PyInterp_LockRequest
1218   {
1219   public:
1220     ContextMenuReq( PyInterp_Interp* _py_interp,
1221                     PyModuleHelper*  _helper,
1222                     const QString&   _context,
1223                     QMenu*           _menu )
1224       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1225         myHelper ( _helper ),
1226         myContext( _context ),
1227         myMenu   ( _menu )
1228     {}
1229   protected:
1230     virtual void execute()
1231     {
1232       myHelper->internalContextMenu( myContext, myMenu );
1233     }
1234   private:
1235     PyModuleHelper* myHelper;
1236     QString         myContext;
1237     QMenu*          myMenu;
1238   };
1239
1240   // post request only if dispatcher is not busy!
1241   // execute request synchronously
1242   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1243     PyInterp_Dispatcher::Get()->Exec( new ContextMenuReq( myInterp, this, context, menu ) );
1244 }
1245
1246 /*!
1247   \brief Export preferences for the Python module.
1248   Called only once when the first instance of the module is created or
1249   when common Preferences dialog box is first time invoked.
1250 */
1251 void PyModuleHelper::createPreferences()
1252 {
1253   FuncMsg fmsg( "PyModuleHelper::createPreferences()" );
1254
1255   // temporary set myInitModule because createPreferences() method
1256   // might be called during the module intialization process
1257   InitLocker lock( myModule );
1258
1259   class CreatePrefReq : public PyInterp_LockRequest
1260   {
1261   public:
1262     CreatePrefReq( PyInterp_Interp* _py_interp,
1263                    PyModuleHelper*  _helper )
1264       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1265         myHelper( _helper )
1266     {}
1267   protected:
1268     virtual void execute()
1269     {
1270       myHelper->internalCreatePreferences();
1271     }
1272   private:
1273     PyModuleHelper* myHelper;
1274   };
1275
1276   // post request only if dispatcher is not busy!
1277   // execute request synchronously
1278   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1279     PyInterp_Dispatcher::Get()->Exec( new CreatePrefReq( myInterp, this ) );
1280 }
1281
1282 /*!
1283   \brief Signal handler windowActivated(SUIT_ViewWindow*) of SUIT_Desktop
1284
1285   Used to notify Python module that active view has been changed by the user.
1286
1287   \param view view being activated
1288 */
1289 void PyModuleHelper::activeViewChanged( SUIT_ViewWindow* view )
1290 {
1291   FuncMsg fmsg( "PyModuleHelper::activeViewChanged()" );
1292
1293   // perform synchronous request to Python event dispatcher
1294   class ActiveViewChangeReq : public PyInterp_LockRequest
1295   {
1296   public:
1297     ActiveViewChangeReq( PyInterp_Interp* _py_interp,
1298                          PyModuleHelper*  _helper,
1299                          SUIT_ViewWindow* _view )
1300       : PyInterp_LockRequest( _py_interp, 0, true ),
1301         myHelper( _helper ),
1302         myView( _view )
1303     {}
1304   protected:
1305     virtual void execute()
1306     {
1307       myHelper->internalActiveViewChanged( myView );
1308     }
1309   private:
1310     PyModuleHelper*  myHelper;
1311     SUIT_ViewWindow* myView;
1312   };
1313
1314   // connect view (if it is not connected yet)
1315   connectView( view );
1316   
1317   PyInterp_Dispatcher::Get()->Exec( new ActiveViewChangeReq( myInterp, this, view ) ); 
1318 }
1319
1320 /*!
1321   \brief Signal handler tryClose(SUIT_ViewWindow*) of a view
1322   \param view view being closed
1323 */
1324 void PyModuleHelper::tryCloseView( SUIT_ViewWindow* view )
1325 {
1326   FuncMsg fmsg( "PyModuleHelper::tryCloseView()" );
1327
1328   class TryCloseViewReq : public PyInterp_LockRequest
1329   {
1330   public:
1331     TryCloseViewReq( PyInterp_Interp* _py_interp,
1332                      PyModuleHelper*  _helper,
1333                      SUIT_ViewWindow* _view )
1334       : PyInterp_LockRequest( _py_interp, 0, true ),
1335         myHelper( _helper ), 
1336         myView( _view )
1337     {}
1338   protected:
1339     virtual void execute()
1340     {
1341       myHelper->internalTryCloseView( myView );
1342     }
1343   private:
1344     PyModuleHelper*  myHelper;
1345     SUIT_ViewWindow* myView;    
1346   };
1347
1348   PyInterp_Dispatcher::Get()->Exec( new TryCloseViewReq( myInterp, this, view ) );
1349 }
1350
1351 /*!
1352   \brief Signal handler closing(SUIT_ViewWindow*) of a view
1353   \param view view being closed
1354 */
1355 void PyModuleHelper::closeView( SUIT_ViewWindow* view )
1356 {
1357   FuncMsg fmsg( "PyModuleHelper::closeView()" );
1358
1359   class CloseViewReq : public PyInterp_LockRequest
1360   {
1361   public:
1362     CloseViewReq( PyInterp_Interp* _py_interp,
1363                   PyModuleHelper*  _helper,
1364                   SUIT_ViewWindow* _view )
1365       : PyInterp_LockRequest( _py_interp, 0, true ),
1366         myHelper( _helper ),
1367         myView( _view )
1368     {}
1369   protected:
1370     virtual void execute()
1371     {
1372       myHelper->internalCloseView( myView );
1373     }
1374   private:
1375     PyModuleHelper*  myHelper;
1376     SUIT_ViewWindow* myView;    
1377   };
1378
1379   PyInterp_Dispatcher::Get()->Exec( new CloseViewReq( myInterp, this, view ) );
1380 }
1381
1382 /*!
1383   \brief Signal handler cloneView() of OCCViewer_ViewWindow
1384   \param view view being cloned
1385 */
1386 void PyModuleHelper::cloneView( SUIT_ViewWindow* view )
1387 {
1388   FuncMsg fmsg( "PyModuleHelper::cloneView()" );
1389
1390   class CloneViewReq : public PyInterp_LockRequest
1391   {
1392   public:
1393     CloneViewReq( PyInterp_Interp* _py_interp,
1394                   PyModuleHelper*  _helper,
1395                   SUIT_ViewWindow* _view )
1396       : PyInterp_LockRequest( _py_interp, 0, true ),
1397         myHelper( _helper ),
1398         myView( _view )
1399     {}
1400   protected:
1401     virtual void execute()
1402     {
1403       myHelper->internalCloneView( myView );
1404     }
1405   private:
1406     PyModuleHelper*  myHelper;
1407     SUIT_ViewWindow* myView;
1408   };
1409   
1410   PyInterp_Dispatcher::Get()->Exec( new CloneViewReq( myInterp, this, view ) );
1411 }
1412
1413 /*!
1414   \brief Save module data. Called when user saves study.
1415   \param files output list of files where module stores data
1416   \param url study URL
1417 */
1418 void PyModuleHelper::save( QStringList& files, const QString& url )
1419 {
1420   FuncMsg fmsg( "PyModuleHelper::save()" );
1421
1422   // temporary set myInitModule because save() method
1423   // might be called by the framework when this module is inactive,
1424   // but still it should be possible to access this module's data
1425   // from Python
1426   InitLocker lock( myModule );
1427
1428   // perform synchronous request to Python event dispatcher
1429   class SaveReq: public PyInterp_LockRequest
1430   {
1431   public:     
1432     SaveReq( PyInterp_Interp* _py_interp,
1433              PyModuleHelper*  _helper,
1434              QStringList&     _files,
1435              const QString&   _url )
1436       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1437         myHelper( _helper ) ,
1438         myFiles( _files ),
1439         myUrl( _url )
1440     {}
1441   protected:
1442     virtual void execute()
1443     {
1444       myHelper->internalSave( myFiles, myUrl );
1445     }
1446   private:
1447     PyModuleHelper* myHelper;
1448     QStringList&    myFiles;
1449     QString         myUrl;
1450   };
1451   
1452   // Posting the request only if dispatcher is not busy!
1453   // Executing the request synchronously
1454   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1455     PyInterp_Dispatcher::Get()->Exec( new SaveReq( myInterp, this, files, url ) );
1456 }
1457
1458 /*
1459   \brief Load module data. Called when user opens study 
1460   and activates module.
1461   \param files list of files where module data is stored
1462   \param url study URL
1463   \return \c true if loading has been finished successfully or \c false otherwise
1464 */
1465 bool PyModuleHelper::load( const QStringList& files, const QString& url )
1466 {
1467   FuncMsg fmsg( "PyModuleHelper::load()" );
1468
1469   bool loaded = false;
1470
1471   class LoadReq: public PyInterp_LockRequest
1472   {
1473   public:
1474     LoadReq( PyInterp_Interp* _py_interp,
1475              PyModuleHelper*  _helper,
1476              QStringList      _files,
1477              const QString&   _url,
1478              bool&            _loaded )
1479       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1480         myHelper( _helper ) ,
1481         myFiles( _files ),
1482         myUrl( _url ),
1483         myLoaded( _loaded )
1484     {}
1485   protected:
1486     virtual void execute()
1487     {
1488       myHelper->internalLoad( myFiles, myUrl, myLoaded );
1489     }
1490   private:
1491     PyModuleHelper* myHelper;
1492     QStringList     myFiles;
1493     QString         myUrl;
1494     bool&           myLoaded;
1495   };
1496   
1497   // Posting the request only if dispatcher is not busy!
1498   // Executing the request synchronously
1499   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1500     PyInterp_Dispatcher::Get()->Exec( new LoadReq( myInterp, this, files, url, loaded ) );
1501
1502   return loaded;
1503 }
1504
1505 /*!
1506   \brief Dump module data to the Python script. 
1507   Called when user activates dump study operation.
1508   \param files output list of files where module stores python script
1509 */
1510 void PyModuleHelper::dumpPython( QStringList& files )
1511 {
1512   FuncMsg fmsg( "PyModuleHelper::dumpPython()" );
1513
1514   // temporary set myInitModule because dumpPython() method
1515   // might be called by the framework when this module is inactive,
1516   // but still it should be possible to access this module's data
1517   // from Python
1518   InitLocker lock( myModule );
1519
1520   class DumpPythonReq: public PyInterp_LockRequest
1521   {
1522   public:     
1523     DumpPythonReq( PyInterp_Interp* _py_interp,
1524                    PyModuleHelper*  _helper,
1525                    QStringList&     _files )
1526       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1527         myHelper( _helper ) ,
1528         myFiles( _files )
1529     {}
1530   protected:
1531     virtual void execute()
1532     {
1533       myHelper->internalDumpPython( myFiles );
1534     }
1535   private:
1536     PyModuleHelper* myHelper;
1537     QStringList&    myFiles;
1538   };
1539   
1540   // Posting the request only if dispatcher is not busy!
1541   // Executing the request synchronously
1542   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1543     PyInterp_Dispatcher::Get()->Exec( new DumpPythonReq( myInterp, this, files ) );
1544 }
1545
1546 /*!
1547   \brief Test if object \a what can be dragged by the user.
1548   \param what data object being tested
1549   \return \c true if object can be dragged or \c false otherwise
1550 */
1551 bool PyModuleHelper::isDraggable( const SUIT_DataObject* what ) const
1552 {
1553   FuncMsg fmsg( "PyModuleHelper::isDraggable()" );
1554
1555   bool draggable = false;
1556
1557   // perform synchronous request to Python event dispatcher
1558   class IsDraggableReq: public PyInterp_LockRequest
1559   {
1560   public:
1561     IsDraggableReq( PyInterp_Interp*     _py_interp,
1562                     PyModuleHelper*      _helper,
1563                     LightApp_DataObject* _data_object,
1564                     bool&                _is_draggable )
1565       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1566         myHelper( _helper ) ,
1567         myDataObject( _data_object ),
1568         myIsDraggable( _is_draggable )
1569     {}
1570   protected:
1571     virtual void execute()
1572     {
1573       myIsDraggable = myHelper->internalIsDraggable( myDataObject );
1574     }
1575   private:
1576     PyModuleHelper*      myHelper;
1577     LightApp_DataObject* myDataObject;
1578     bool&                myIsDraggable;
1579   };
1580
1581   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( what );
1582   if ( data_object ) {
1583     // Posting the request only if dispatcher is not busy!
1584     // Executing the request synchronously
1585     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1586       PyInterp_Dispatcher::Get()->Exec( new IsDraggableReq( myInterp,
1587                                         const_cast<PyModuleHelper*>( this ),
1588                                         const_cast<LightApp_DataObject*>( data_object ),
1589                                         draggable ) );
1590   }
1591   
1592   return draggable;
1593 }
1594
1595 /*!
1596   \brief Test if drop operation can be done on the \a where object.
1597   \param where data object being tested
1598   \return \c true if if drop operation is supported by object or \c false otherwise
1599 */
1600 bool PyModuleHelper::isDropAccepted( const SUIT_DataObject* where ) const
1601 {
1602   FuncMsg fmsg( "PyModuleHelper::isDropAccepted()" );
1603
1604   bool dropAccepted = false;
1605
1606   // perform synchronous request to Python event dispatcher
1607   class IsDropAcceptedReq: public PyInterp_LockRequest
1608   {
1609   public:
1610     IsDropAcceptedReq( PyInterp_Interp*     _py_interp,
1611                        PyModuleHelper*      _helper,
1612                        LightApp_DataObject* _data_object,
1613                        bool&                _is_drop_accepted )
1614       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1615         myHelper( _helper ) ,
1616         myDataObject( _data_object ),
1617         myIsDropAccepted( _is_drop_accepted )
1618     {}
1619   protected:
1620     virtual void execute()
1621     {
1622       myIsDropAccepted = myHelper->internalIsDropAccepted( myDataObject );
1623     }
1624   private:
1625     PyModuleHelper*      myHelper;
1626     LightApp_DataObject* myDataObject;
1627     bool&                myIsDropAccepted;
1628   };
1629   
1630   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( where );
1631   if ( data_object ) {
1632     // Posting the request only if dispatcher is not busy!
1633     // Executing the request synchronously
1634     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1635       PyInterp_Dispatcher::Get()->Exec( new IsDropAcceptedReq( myInterp,
1636                                          const_cast<PyModuleHelper*>( this ),
1637                                          const_cast<LightApp_DataObject*>( data_object ),
1638                                          dropAccepted ) );
1639   }
1640
1641   return dropAccepted;
1642 }
1643
1644 /*!
1645   \brief Perform drop operation
1646   \param what list of data objects being dropped
1647   \param where target data object for drop operation
1648   \param row line (child item index) where drop operation is performed to
1649   \param action current drop action (copy or move)
1650 */
1651 void PyModuleHelper::dropObjects( const DataObjectList& what, SUIT_DataObject* where,
1652                                   const int row, Qt::DropAction action )
1653 {
1654   FuncMsg fmsg( "PyModuleHelper::dropObjects()" );
1655
1656   // perform synchronous request to Python event dispatcher
1657   class DropObjectsReq: public PyInterp_LockRequest
1658   {
1659   public:
1660     DropObjectsReq( PyInterp_Interp*      _py_interp,
1661                     PyModuleHelper*       _helper,
1662                     const DataObjectList& _what,
1663                     SUIT_DataObject*      _where,
1664                     const int             _row,
1665                     Qt::DropAction        _action )
1666       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1667         myHelper( _helper ) ,
1668         myWhat( _what ),
1669         myWhere( _where ),
1670         myRow( _row ),
1671         myAction ( _action )
1672     {}
1673   protected:
1674     virtual void execute()
1675     {
1676       myHelper->internalDropObjects( myWhat, myWhere, myRow, myAction );
1677     }
1678   private:
1679     PyModuleHelper*  myHelper;
1680     DataObjectList   myWhat;
1681     SUIT_DataObject* myWhere;
1682     int              myRow;
1683     Qt::DropAction   myAction;
1684   };
1685   
1686   // Posting the request only if dispatcher is not busy!
1687   // Executing the request synchronously
1688   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1689     PyInterp_Dispatcher::Get()->Exec( new DropObjectsReq( myInterp, this, what, where, row, action ) );
1690 }
1691
1692 /*!
1693   \brief Get module engine IOR
1694   \return engine IOR as it is supplied by GUI Python module
1695  */
1696 QString PyModuleHelper::engineIOR() const
1697 {
1698   FuncMsg fmsg( "PyModuleHelper::engineIOR()" );
1699
1700   class EngineIORReq : public PyInterp_LockRequest
1701   {
1702   public:
1703     EngineIORReq( PyInterp_Interp* _py_interp,
1704                   PyModuleHelper*  _helper,
1705                   QString&         _ior )
1706       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1707         myHelper( _helper ),
1708         myIOR( _ior )
1709     {}
1710   protected:
1711     virtual void execute()
1712     {
1713       myIOR = myHelper->internalEngineIOR();
1714     }
1715   private:
1716     PyModuleHelper* myHelper;
1717     QString&        myIOR;
1718   };
1719
1720   static QString anIOR;
1721
1722   if ( anIOR.isEmpty() ) {
1723     // post request
1724     PyInterp_Dispatcher::Get()->Exec( new EngineIORReq( myInterp, 
1725                                       const_cast<PyModuleHelper*>( this ),
1726                                       anIOR ) );
1727   }
1728
1729   return anIOR;
1730 }
1731
1732 /*!
1733   \brief Initialize python subinterpreter (one per study).
1734   \internal
1735   \param studyId study ID
1736 */
1737 void PyModuleHelper::initInterp( int studyId )
1738 {
1739   FuncMsg fmsg( "--- PyModuleHelper::initInterp()" );
1740
1741   // check study Id
1742   if ( !studyId ) {
1743     // Error! Study Id must not be 0!
1744     myInterp = 0;
1745     return;
1746   }
1747
1748   QMutexLocker ml( &myInitMutex );
1749
1750   // try to find the subinterpreter
1751   if ( myInterpMap.contains( studyId ) ) {
1752     // found!
1753     myInterp = myInterpMap[ studyId ];
1754     return;
1755   }
1756
1757   myInterp = new SALOME_PYQT_PyInterp();
1758   myInterp->initialize();
1759   myInterpMap[ studyId ] = myInterp;
1760   
1761 #ifndef GUI_DISABLE_CORBA
1762   if ( !SUIT_PYTHON::initialized ) {
1763     // import 'salome' module and call 'salome_init' method;
1764     // do it only once on interpreter creation
1765     // ... first get python lock
1766     PyLockWrapper aLock; // Acquire GIL
1767     // ... then import a module
1768     PyObjWrapper aMod = PyImport_ImportModule( "salome" );
1769     if ( !aMod ) {
1770       // Error!
1771       PyErr_Print();
1772       return;
1773     }
1774     // ... then call a method
1775     int embedded = 1;
1776     PyObjWrapper aRes( PyObject_CallMethod( aMod, (char*)"salome_init", (char*)"ii", studyId, embedded ) );
1777     if ( !aRes ) {
1778       // Error!
1779       PyErr_Print();
1780       return;
1781     }
1782   }
1783 #endif 
1784 }
1785
1786 /*!
1787   \brief Import Python GUI module and store reference to the module.
1788   \internal
1789
1790   Warning! initInterp() should be called first!!!
1791 */
1792 void PyModuleHelper::importModule()
1793 {
1794   FuncMsg fmsg( "--- PyModuleHelper::importModule()" );
1795
1796   // check if the subinterpreter is initialized
1797   if ( !myInterp ) {
1798     // Error! Python subinterpreter should be initialized first!
1799     myPyModule = 0;
1800     return;
1801   }
1802
1803   // import Python GUI module and put it in <myPyModule> attribute
1804   // ... first get python lock
1805   PyLockWrapper aLock; // Acquire GIL
1806   // ... then import a module
1807   QString aMod = QString( "%1GUI" ).arg( myModule->name() );
1808   try {
1809     myPyModule = PyImport_ImportModule( aMod.toLatin1().data() );
1810   }
1811   catch (...) {
1812   }
1813
1814   if ( !myPyModule ) {
1815     // Error!
1816     PyErr_Print();
1817     return;
1818   }
1819 }
1820
1821 /*!
1822   \brief Set study workspace to the Python module.
1823   \internal
1824
1825   Calls setWorkSpace() method of the Python module with 
1826   PyQt QWidget object to use with interpreter.
1827
1828   Attention! initInterp() and importModule() should be called first!!!
1829 */
1830 void PyModuleHelper::setWorkSpace()
1831 {
1832   FuncMsg fmsg( "--- PyModuleHelper::setWorkSpace()" );
1833
1834   if ( !IsCallOldMethods ) 
1835     return;
1836
1837   // check if the subinterpreter is initialized and Python module is imported
1838   if ( !myInterp || !myPyModule ) {
1839     // Error! Python subinterpreter should be initialized and module should be imported first!
1840     return;
1841   }
1842
1843   // call setWorkSpace() method
1844   // ... first get python lock
1845   PyLockWrapper aLock; // Acquire GIL
1846
1847   // ... then try to import SalomePyQt module. If it's not possible don't go on.
1848   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
1849   if( !aQtModule ) {
1850     // Error!
1851     PyErr_Print();
1852     return;
1853   }
1854
1855   // ... then get workspace object
1856   QWidget* aWorkspace = 0;
1857   if ( myModule->getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
1858     STD_MDIDesktop* d = dynamic_cast<STD_MDIDesktop*>( myModule->getApp()->desktop() );
1859     if ( d )
1860       aWorkspace = d->workspace();
1861   }
1862   else if ( myModule->getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
1863     STD_TabDesktop* d = dynamic_cast<STD_TabDesktop*>( myModule->getApp()->desktop() );
1864     if ( d )
1865       aWorkspace = d->workstack();
1866   }
1867 #if SIP_VERSION < 0x040800
1868   PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget) );
1869 #else
1870   PyObjWrapper pyws( sipBuildResult( 0, "D", aWorkspace, sipType_QWidget , NULL) );
1871 #endif
1872   // ... and finally call Python module's setWorkSpace() method (obsolete)
1873   if ( PyObject_HasAttrString( myPyModule, (char*)"setWorkSpace" ) ) {
1874     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setWorkSpace", (char*)"O", pyws.get() ) );
1875     if( !res ) {
1876       PyErr_Print();
1877     }
1878   }
1879 }
1880
1881 /*!
1882   \brief Initialization callback function
1883   \internal
1884
1885   Performs the following actions:
1886   - initialize or get the Python interpreter (one per study)
1887   - import the Python module
1888   - pass the workspace widget to the Python module
1889   - call Python module's initialize() method
1890   - call Python module's windows() method
1891   - call Python module's views() method
1892
1893   \param app parent application object
1894 */
1895 void PyModuleHelper::internalInitialize( CAM_Application* app )
1896 {
1897   FuncMsg fmsg( "--- PyModuleHelper::internalInitialize()" );
1898
1899   // reset interpreter to NULL
1900   myInterp = 0;
1901
1902   // get study Id
1903   LightApp_Application* anApp = dynamic_cast<LightApp_Application*>( app );
1904   if ( !anApp )
1905     return;
1906   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( app->activeStudy() );
1907   if ( !aStudy )
1908     return;
1909   int aStudyId = aStudy ? aStudy->id() : 0;
1910
1911   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
1912   initInterp( aStudyId );
1913   if ( !myInterp )
1914     return; // Error
1915
1916   // import Python GUI module
1917   importModule();
1918   if ( !myPyModule )
1919     return; // Error
1920
1921   // then call Python module's initialize() method
1922   // ... first get python lock
1923   PyLockWrapper aLock; // Acquire GIL
1924
1925   // ... (the Python module is already imported)
1926   // ... finally call Python module's initialize() method
1927   if ( PyObject_HasAttrString( myPyModule, (char*)"initialize" ) ) {
1928     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"initialize", (char*)"" ) );
1929     if ( !res ) {
1930       PyErr_Print();
1931     }
1932   }
1933
1934   // get required dockable windows list from the Python module 
1935   // by calling windows() method
1936   // ... first put default values
1937   myWindowsMap.insert( LightApp_Application::WT_ObjectBrowser, Qt::LeftDockWidgetArea );
1938   myWindowsMap.insert( LightApp_Application::WT_PyConsole,     Qt::BottomDockWidgetArea );
1939   myWindowsMap.insert( LightApp_Application::WT_LogWindow,     Qt::BottomDockWidgetArea );
1940
1941   if ( PyObject_HasAttrString( myPyModule , (char*)"windows" ) ) {
1942     PyObjWrapper res1( PyObject_CallMethod( myPyModule, (char*)"windows", (char*)"" ) );
1943     if ( !res1 ) {
1944       PyErr_Print();
1945     }
1946     else {
1947       myWindowsMap.clear();
1948       if ( PyDict_Check( res1 ) ) {
1949         PyObject* key;
1950         PyObject* value;
1951         Py_ssize_t pos = 0;
1952         while ( PyDict_Next( res1, &pos, &key, &value ) ) {
1953           // parse the return value
1954           // it should be a map: {integer:integer}
1955           int aKey, aValue;
1956           if( key && PyInt_Check( key ) && value && PyInt_Check( value ) ) {
1957             aKey   = PyInt_AsLong( key );
1958             aValue = PyInt_AsLong( value );
1959             myWindowsMap[ aKey ] = aValue;
1960           }
1961         }
1962       }
1963     }
1964   }
1965
1966   // get compatible view windows types from the Python module 
1967   // by calling views() method
1968   if ( PyObject_HasAttrString( myPyModule , (char*)"views" ) ) {
1969     PyObjWrapper res2( PyObject_CallMethod( myPyModule, (char*)"views", (char*)"" ) );
1970     if ( !res2 ) {
1971       PyErr_Print();
1972     }
1973     else {
1974       // parse the return value
1975       // result can be one string...
1976       if ( PyString_Check( res2 ) ) {
1977         myViewMgrList.append( PyString_AsString( res2 ) );
1978       }
1979       // ... or list of strings
1980       else if ( PyList_Check( res2 ) ) {
1981         int size = PyList_Size( res2 );
1982         for ( int i = 0; i < size; i++ ) {
1983           PyObject* value = PyList_GetItem( res2, i );
1984           if( value && PyString_Check( value ) ) {
1985             myViewMgrList.append( PyString_AsString( value ) );
1986           }
1987         }
1988       }
1989     }
1990   }
1991 }
1992
1993 /*!
1994   \brief Activation callback function
1995   \internal
1996
1997   Performs the following actions:
1998   - initialize or get the Python interpreter (one per study)
1999   - import the Python GUI module
2000   - call Python module's activate() method
2001
2002   \param study parent study
2003 */
2004 void PyModuleHelper::internalActivate( SUIT_Study* study )
2005 {
2006   FuncMsg fmsg( "--- PyModuleHelper::internalActivate()" );
2007
2008   // get study Id
2009   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2010   int aStudyId = aStudy ? aStudy->id() : 0;
2011
2012   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2013   initInterp( aStudyId );
2014   if ( !myInterp ) {
2015     myLastActivateStatus = false;
2016     return; // Error
2017   }
2018
2019   // import Python GUI module
2020   importModule();
2021   if ( !myPyModule ) {
2022     myLastActivateStatus = false;
2023     return; // Error
2024   }
2025
2026   // get python lock
2027   PyLockWrapper aLock; // Acquire GIL
2028
2029   // call Python module's activate() method (for the new modules)
2030   if ( PyObject_HasAttrString( myPyModule , (char*)"activate" ) ) {
2031     PyObject* res1 = PyObject_CallMethod( myPyModule, (char*)"activate", (char*)"" );
2032     if ( !res1 || !PyBool_Check( res1 ) ) {
2033       PyErr_Print();
2034       // always true for old modules (no return value)
2035       myLastActivateStatus = true;
2036     }
2037     else {
2038       // detect return status
2039       myLastActivateStatus = PyObject_IsTrue( res1 );
2040     }
2041   } 
2042 }
2043
2044 /*!
2045   \brief Additional menu customization callback function
2046   \internal
2047
2048   Performs the following actions:
2049   - get the Python interpreter (one per study)
2050   - import the Python GUI module
2051   - call Python module's setSettings() method (obsolete function, 
2052   used for compatibility with old code)
2053
2054   \param study parent study
2055 */
2056 void PyModuleHelper::internalCustomize( SUIT_Study* study )
2057 {
2058   FuncMsg fmsg( "--- PyModuleHelper::internalCustomize()" );
2059
2060   // get study Id
2061   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2062   int aStudyId = aStudy ? aStudy->id() : 0;
2063
2064   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2065   initInterp( aStudyId );
2066   if ( !myInterp ) {
2067     myLastActivateStatus = false;
2068     return; // Error
2069   }
2070
2071   // import Python GUI module
2072   importModule();
2073   if ( !myPyModule ) {
2074     myLastActivateStatus = false;
2075     return; // Error
2076   }
2077
2078   // call Python module's setWorkSpace() method (obsolete)
2079   setWorkSpace();
2080
2081   // get python lock
2082   PyLockWrapper aLock; // Acquire GIL
2083
2084   if ( IsCallOldMethods ) {
2085     // call Python module's setSettings() method (obsolete)
2086     if ( PyObject_HasAttrString( myPyModule , (char*)"setSettings" ) ) {
2087       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setSettings", (char*)"" ) );
2088       if( !res ) {
2089         PyErr_Print();
2090       }
2091       myLastActivateStatus = myLastActivateStatus && true;
2092     }
2093   }
2094 }
2095
2096 /*!
2097   \brief Deactivation callback function
2098   \internal
2099
2100   Performs the following actions:
2101   - call Python module's deactivate() method
2102
2103   \param study parent study
2104 */
2105 void PyModuleHelper::internalDeactivate( SUIT_Study* study )
2106 {
2107   FuncMsg fmsg( "--- PyModuleHelper::internalDeactivate()" );
2108
2109   // check that Python subinterpreter is initialized and Python module is imported
2110   if ( !myInterp || !myPyModule ) {
2111     // Error! Python subinterpreter should be initialized and module should be imported first!
2112     return;
2113   }
2114   // then call Python module's deactivate() method
2115   if ( PyObject_HasAttrString( myPyModule , (char*)"deactivate" ) ) {
2116     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"deactivate", (char*)"" ) );
2117     if( !res ) {
2118       PyErr_Print();
2119     }
2120   }
2121 }
2122
2123 /*!
2124   \brief Internal closure:
2125
2126   Performs the following actions:
2127   - call Python module's closeStudy() method
2128
2129   \param theStudy parent study object
2130 */
2131 void PyModuleHelper::internalClosedStudy( SUIT_Study* theStudy )
2132 {
2133   FuncMsg fmsg( "--- PyModuleHelper::internalClosedStudy()" );
2134
2135   // Get study Id
2136   // get study Id
2137   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( theStudy );
2138   int aStudyId = aStudy ? aStudy->id() : 0;
2139
2140   // check that Python subinterpreter is initialized and Python module is imported
2141   if ( !myInterp || !myPyModule ) {
2142     // Error! Python subinterpreter should be initialized and module should be imported first!
2143     return;
2144   }
2145   // then call Python module's deactivate() method
2146   if ( PyObject_HasAttrString( myPyModule , (char*)"closeStudy" ) ) {
2147     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"closeStudy", (char*)"i", aStudyId ) );
2148     if( !res ) {
2149       PyErr_Print();
2150     }
2151   }
2152 }
2153
2154
2155
2156 /*!
2157   \brief Preference changing callback function.
2158   \internal
2159
2160   Performs the following actions:
2161   - call Python module's preferenceChanged() method
2162
2163   \param section resources section name
2164   \param setting resources parameter name
2165 */
2166 void PyModuleHelper::internalPreferencesChanged( const QString& section, const QString& setting )
2167 {
2168   FuncMsg fmsg( "--- PyModuleHelper::internalPreferencesChanged()" );
2169
2170   // check that Python subinterpreter is initialized and Python module is imported
2171   if ( !myInterp || !myPyModule ) {
2172     // Error! Python subinterpreter should be initialized and module should be imported first!
2173     return;
2174   }
2175
2176   if ( PyObject_HasAttrString( myPyModule, (char*)"preferenceChanged" ) ) {
2177     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2178                                            (char*)"preferenceChanged", 
2179                                            (char*)"ss", 
2180                                            section.toLatin1().constData(), 
2181                                            setting.toLatin1().constData() ) );
2182     if( !res ) {
2183       PyErr_Print();
2184     }
2185   }
2186 }
2187
2188 /*!
2189   \brief Active study change callback function.
2190   \internal
2191
2192   Called when active the study is actived (user brings its 
2193   desktop to top):
2194   - initialize or get the Python interpreter (one per study)
2195   - import the Python GUI module
2196   - call Python module's activeStudyChanged() method
2197
2198   \param study study being activated
2199 */
2200 void PyModuleHelper::internalStudyChanged( SUIT_Study* study )
2201 {
2202   FuncMsg fmsg( "--- PyModuleHelper::internalStudyChanged()" );
2203
2204   // get study Id
2205   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2206   int id = aStudy ? aStudy->id() : 0;
2207
2208   fmsg.message( QString( "study id = %1" ).arg( id ) );
2209
2210   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2211   initInterp( id );
2212   if ( !myInterp )
2213     return; // Error
2214
2215   // import Python GUI module
2216   importModule();
2217   if ( !myPyModule )
2218     return; // Error
2219
2220   // call Python module's setWorkSpace() method
2221   setWorkSpace();
2222
2223   // get python lock
2224   PyLockWrapper aLock; // Acquire GIL
2225
2226   // call Python module's activeStudyChanged() method
2227   if ( PyObject_HasAttrString( myPyModule, (char*)"activeStudyChanged" ) ) {
2228     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeStudyChanged", (char*)"i", id ) );
2229     if( !res ) {
2230       PyErr_Print();
2231     }
2232   }
2233 }
2234
2235 /*!
2236   \brief GUI event handling callback function
2237   \internal 
2238
2239   Performs the following actions:
2240   - calls Python module's OnGUIEvent() method
2241
2242   \param id GUI action ID
2243 */
2244 void PyModuleHelper::internalActionActivated( int id )
2245 {
2246   FuncMsg fmsg( "--- PyModuleHelper::internalActionActivated()" );
2247   fmsg.message( QString( "action id = %1" ).arg( id ) );
2248
2249   // Python interpreter should be initialized and Python module should be
2250   // import first
2251   if ( !myInterp || !myPyModule )
2252     return; // Error
2253
2254   if ( PyObject_HasAttrString( myPyModule, (char*)"OnGUIEvent" ) ) {
2255     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"OnGUIEvent", (char*)"i", id ) );
2256     if( !res ) {
2257       PyErr_Print();
2258     }
2259   }
2260 }
2261
2262 /*!
2263   \brief update selection from other views or modules
2264   \internal
2265
2266   Performs the following actions:
2267   - calls Python module's onSelectionpdated(entries) method
2268
2269   \param list of entries
2270 */
2271 void PyModuleHelper::internalSelectionUpdated(const QStringList& entries)
2272 {
2273   FuncMsg fmsg("--- PyModuleHelper::internalSelectionUpdated()");
2274
2275   // Python interpreter should be initialized and Python module should be imported first
2276   if (!myInterp || !myPyModule)
2277     return; // Error
2278
2279   QStringList* theList = new QStringList(entries);
2280
2281 #if SIP_VERSION < 0x040800
2282   PyObjWrapper sipList(sipBuildResult(0, "M", theList, sipClass_QStringList));
2283 #else
2284   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL ) );
2285 #endif
2286   if (PyObject_HasAttrString(myPyModule, (char*) "onSelectionUpdated"))
2287     {
2288       PyObjWrapper res(PyObject_CallMethod(myPyModule, (char*) "onSelectionUpdated", (char*) "O", sipList.get()));
2289
2290       if (!res)
2291         {
2292           PyErr_Print();
2293         }
2294     }
2295 }
2296
2297 /*!
2298   \brief Context popup menu handling callback function
2299   \internal
2300
2301   Performs the following actions:
2302   - calls Python module's definePopup(...) method (obsolete function, 
2303   used for compatibility with old code) to define the popup menu context
2304   - parses XML resourses file (if exists) and fills the popup menu with the items)
2305   - calls Python module's customPopup(...) method (obsolete function, 
2306   used for compatibility with old code) to allow module to customize the popup menu
2307   - for new modules calls createPopupMenu() function to allow the 
2308   modules to build the popup menu by using insertItem(...) Qt functions.
2309
2310   \param context popup menu context
2311   \param menu popup menu
2312 */
2313 void PyModuleHelper::internalContextMenu( const QString& context, QMenu* menu )
2314 {
2315   FuncMsg fmsg( "--- PyModuleHelper::internalContextMenu()" );
2316   fmsg.message( QString( "context: %1" ).arg( context ) );
2317
2318   // Python interpreter should be initialized and Python module should be
2319   // import first
2320   if ( !myInterp || !myPyModule )
2321     return; // Error
2322
2323   QString aContext( "" ), aObject( "" ), aParent( context );
2324
2325   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"definePopup" ) ) {
2326     // call definePopup() Python module's function
2327     // this is obsolete function, used only for compatibility reasons
2328     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2329                                            (char*)"definePopup",
2330                                            (char*)"sss",
2331                                            context.toLatin1().constData(),
2332                                            aObject.toLatin1().constData(),
2333                                            aParent.toLatin1().constData() ) );
2334     if( !res ) {
2335       PyErr_Print();
2336     }
2337     else {
2338       // parse return value
2339       char *co, *ob, *pa;
2340       if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
2341         aContext = co;
2342         aObject  = ob;
2343         aParent  = pa;
2344       }
2345     }
2346   } // if ( IsCallOldMethods ... )
2347
2348   // first try to create menu via XML parser:
2349   // we create popup menus without help of QtxPopupMgr
2350   if ( myXmlHandler )
2351     myXmlHandler->createPopup( menu, aContext, aParent, aObject );
2352
2353 #if SIP_VERSION < 0x040800
2354   PyObjWrapper sipPopup( sipBuildResult( 0, "M", menu, sipClass_QMenu ) );
2355 #else
2356   PyObjWrapper sipPopup( sipBuildResult( 0, "D", menu, sipType_QMenu, NULL ) );
2357 #endif
2358
2359   // then call Python module's createPopupMenu() method (for new modules)
2360   if ( PyObject_HasAttrString( myPyModule, (char*)"createPopupMenu" ) ) {
2361     PyObjWrapper res1( PyObject_CallMethod( myPyModule,
2362                                             (char*)"createPopupMenu",
2363                                             (char*)"Os",
2364                                             sipPopup.get(),
2365                                             context.toLatin1().constData() ) );
2366     if( !res1 ) {
2367       PyErr_Print();
2368     }
2369   }
2370
2371   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"customPopup" ) ) {
2372     // call customPopup() Python module's function
2373     // this is obsolete function, used only for compatibility reasons
2374     PyObjWrapper res2( PyObject_CallMethod( myPyModule,
2375                                             (char*)"customPopup",
2376                                             (char*)"Osss",
2377                                             sipPopup.get(),
2378                                             aContext.toLatin1().constData(),
2379                                             aObject.toLatin1().constData(),
2380                                             aParent.toLatin1().constData() ) );
2381     if( !res2 ) {
2382       PyErr_Print();
2383     }
2384   }
2385 }
2386
2387 /*!
2388   \brief Preferences initialization callback function.
2389   \internal
2390
2391   Performs the following actions:
2392   - calls Python module's createPreferences() method
2393 */
2394 void PyModuleHelper::internalCreatePreferences()
2395 {
2396   FuncMsg fmsg( "--- PyModuleHelper::internalCreatePreferences()" );
2397
2398   // Python interpreter should be initialized and Python module should be
2399   // import first
2400   if ( !myInterp || !myPyModule )
2401     return; // Error
2402
2403   if ( PyObject_HasAttrString( myPyModule, (char*)"createPreferences" ) ) {
2404     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"createPreferences", (char*)"" ) );
2405     if( !res ) {
2406       PyErr_Print();
2407     }
2408   }
2409 }
2410
2411 /*!
2412   \brief Active view changing callback function
2413   \internal
2414   \param view view being activated
2415 */
2416 void PyModuleHelper::internalActiveViewChanged( SUIT_ViewWindow* view )
2417 {
2418   FuncMsg fmsg( "--- PyModuleHelper::internalActiveViewChanged()" );
2419
2420   if ( !myInterp || !myPyModule || !view ) 
2421     return;
2422   
2423   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2424
2425   if ( PyObject_HasAttrString( myPyModule, (char*)"activeViewChanged" ) ) {
2426     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeViewChanged", (char*)"i" , view->getId() ) );
2427     if ( !res ) {
2428       PyErr_Print();
2429     }
2430   }
2431 }
2432
2433 /*!
2434   \brief View closing callback function
2435   \internal
2436   \param view view user tries to close
2437 */
2438 void PyModuleHelper::internalTryCloseView( SUIT_ViewWindow* view )
2439 {
2440   FuncMsg fmsg( "--- PyModuleHelper::internalTryCloseView()" );
2441
2442   if ( !myInterp || !myPyModule || !view ) 
2443     return;  
2444
2445   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2446
2447   if ( PyObject_HasAttrString( myPyModule, (char*)"viewTryClose" ) ) 
2448   {
2449     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewTryClose", (char*)"i", view->getId() ) );
2450     if ( !res )
2451     {
2452       PyErr_Print();
2453     }
2454   }
2455 }
2456
2457 /*!
2458   \brief View closing callback function
2459   \internal
2460   \param view view being closed
2461 */
2462 void PyModuleHelper::internalCloseView( SUIT_ViewWindow* view )
2463 {
2464   FuncMsg fmsg( "--- PyModuleHelper::internalCloseView()" );
2465
2466   if ( !myInterp || !myPyModule || !view ) 
2467     return;  
2468
2469   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2470
2471   if ( PyObject_HasAttrString( myPyModule, (char*)"viewClosed" ) ) 
2472   {
2473     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewClosed", (char*)"i", view->getId() ) );
2474     if ( !res )
2475     {
2476       PyErr_Print();
2477     }
2478   }
2479 }
2480
2481 /*!
2482   \brief View cloning callback function
2483   \internal
2484   \param view view being cloned
2485 */
2486 void PyModuleHelper::internalCloneView( SUIT_ViewWindow* view )
2487 {
2488   FuncMsg fmsg( "--- PyModuleHelper::internalCloneView()" );
2489
2490   if ( !myInterp || !myPyModule || !view ) 
2491     return;  
2492
2493   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2494
2495   if ( PyObject_HasAttrString( myPyModule, (char*)"viewCloned" ) ) 
2496   {
2497     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewCloned", (char*)"i", view->getId() ) );
2498     if( !res )
2499       PyErr_Print();
2500   }
2501 }
2502
2503 /*!
2504   \brief Module data saving callback function.
2505   \internal
2506   \param files output list of files where module stores data
2507   \param url study URL
2508 */
2509 void PyModuleHelper::internalSave( QStringList& files, const QString& url )
2510 {
2511   FuncMsg fmsg( "--- PyModuleHelper::internalSave()" );
2512
2513   // Python interpreter should be initialized and Python module should be
2514   // import firs
2515   // files list should contain a path to the temporary directory as a first entry
2516   if ( !myInterp || !myPyModule || files.isEmpty() )
2517     return;
2518
2519   if ( PyObject_HasAttrString(myPyModule, (char*)"saveFiles") ) {
2520
2521     // try with two parameters (new syntax)
2522     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"saveFiles",
2523                                            (char*)"ss",
2524                                            files.first().toLatin1().constData(),
2525                                            url.toLatin1().constData() ) );
2526     if ( !res )
2527       // try with single parameter (old syntax)
2528       res = PyObject_CallMethod( myPyModule, (char*)"saveFiles",
2529                                  (char*)"s", files.first().toLatin1().constData() );
2530     
2531     if ( !res ) {
2532       PyErr_Print();
2533     }
2534     else {
2535       // parse the return value
2536       // result can be one string...
2537       if ( PyString_Check( res ) ) {
2538         QString astr = PyString_AsString( res );
2539         files.append( astr );
2540       }
2541       //also result can be a list...
2542       else if ( PyList_Check( res ) ) {
2543         int size = PyList_Size( res );
2544         for ( int i = 0; i < size; i++ ) {
2545           PyObject* value = PyList_GetItem( res, i );
2546           if ( value && PyString_Check( value ) ) {
2547             files.append( PyString_AsString( value ) );
2548           }
2549         }
2550       }
2551     }
2552   }
2553 }
2554
2555 /*!
2556   \brief Module data loading callback function.
2557   \internal
2558   \param files list of files where module data is stored
2559   \param url study URL
2560   \param opened output success flag
2561 */
2562 void PyModuleHelper::internalLoad( const QStringList& files, const QString& url, bool& opened )
2563 {
2564   FuncMsg fmsg( "--- PyModuleHelper::internalLoad()" );
2565
2566   // Python interpreter should be initialized and Python module should be
2567   // import first
2568   if ( !myInterp || !myPyModule || files.isEmpty() )
2569     return;
2570
2571   QStringList* theList = new QStringList( files );
2572
2573 #if SIP_VERSION < 0x040800
2574   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList ) );
2575 #else
2576   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL ) );
2577 #endif
2578   if ( PyObject_HasAttrString(myPyModule , (char*)"openFiles") ) {
2579
2580     // try with two parameters (new syntax)
2581     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"openFiles",
2582                                            (char*)"Os", sipList.get(),
2583                                            url.toLatin1().constData() ) );
2584
2585     if ( !res )
2586       // try with single parameter (old syntax)
2587       res = PyObject_CallMethod( myPyModule, (char*)"openFiles",
2588                                  (char*)"O", sipList.get() );
2589
2590     if ( !res || !PyBool_Check( res ) ) {
2591       PyErr_Print();
2592       opened = false;
2593     }
2594     else {
2595       opened = PyObject_IsTrue( res );
2596     }
2597   }
2598 }
2599
2600 /*!
2601   \brief Module dump python callback function.
2602   \internal
2603   \param files output list of files where module stores python script
2604 */
2605 void PyModuleHelper::internalDumpPython( QStringList& files )
2606 {
2607   FuncMsg fmsg( "--- PyModuleHelper::internalDumpPython()" );
2608
2609   // Python interpreter should be initialized and Python module should be
2610   // import first
2611   // files list should contain a path to the temporary directory
2612   if ( !myInterp || !myPyModule || files.isEmpty() )
2613     return;
2614
2615   if ( PyObject_HasAttrString(myPyModule, (char*)"dumpStudy") ) {
2616     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dumpStudy",
2617                                            (char*)"s", files.first().toLatin1().constData()));
2618
2619     if ( !res ) {
2620       PyErr_Print();
2621     }
2622     else {
2623       // parse the return value
2624       // result can be one string...
2625       if ( PyString_Check( res ) ) {
2626         QString astr = PyString_AsString( res );
2627         //SCRUTE(astr);
2628         files.append(astr);
2629       }
2630       //also result can be a list...
2631       else if ( PyList_Check( res ) ) {
2632         int size = PyList_Size( res );
2633         for ( int i = 0; i < size; i++ ) {
2634           PyObject* value = PyList_GetItem( res, i );
2635           if( value && PyString_Check( value ) ) {
2636             files.append( PyString_AsString( value ) );
2637           }
2638         }
2639       }
2640     }
2641   }
2642 }
2643
2644 /*!
2645   \brief Check data object's 'draggable' status callback function.
2646   \internal
2647   \param what data object being tested
2648   \return \c true if object can be dragged or \c false otherwise
2649 */
2650 bool PyModuleHelper::internalIsDraggable( LightApp_DataObject* what )
2651 {
2652   FuncMsg fmsg( "--- PyModuleHelper::internalIsDraggable()" );
2653
2654   // Python interpreter should be initialized and Python module should be
2655   // import first
2656   if ( !myInterp || !myPyModule || !what )
2657     return false;
2658
2659   bool draggable = false;
2660
2661   if ( PyObject_HasAttrString(myPyModule , (char*)"isDraggable") ) {
2662     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDraggable",
2663                       (char*)"s", what->entry().toLatin1().constData() ) );
2664     if( !res || !PyBool_Check( res )) {
2665       PyErr_Print();
2666       draggable = false;
2667     }
2668     else{
2669       draggable = PyObject_IsTrue( res );
2670     }
2671   }
2672
2673   return draggable;
2674 }
2675
2676 /*!
2677   \brief Check data object's 'drop allowed' status callback function.
2678   \internal
2679   \param where data object being tested
2680   \return \c true if if drop operation is supported by object or \c false otherwise
2681 */
2682 bool PyModuleHelper::internalIsDropAccepted( LightApp_DataObject* where )
2683 {
2684   FuncMsg fmsg( "--- PyModuleHelper::internalIsDropAccepted()" );
2685
2686   // Python interpreter should be initialized and Python module should be
2687   // import first
2688   if ( !myInterp || !myPyModule || !where )
2689     return false;
2690
2691   bool dropAccepted = false;
2692
2693   if ( PyObject_HasAttrString(myPyModule , (char*)"isDropAccepted") ) {
2694     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDropAccepted",
2695                       (char*)"s", where->entry().toLatin1().constData() ) );
2696     if( !res || !PyBool_Check( res )) {
2697       PyErr_Print();
2698       dropAccepted = false;
2699     }
2700     else{
2701       dropAccepted = PyObject_IsTrue( res );
2702     }
2703   }
2704
2705   return dropAccepted;
2706 }
2707
2708 /*!
2709   \brief Data dropping callback function.
2710   \internal
2711   \param what list of data objects being dropped
2712   \param where target data object for drop operation
2713   \param row line (child item index) where drop operation is performed to
2714   \param action current drop action (copy or move)
2715 */
2716 void PyModuleHelper::internalDropObjects( const DataObjectList& what, SUIT_DataObject* where,
2717                                           const int row, Qt::DropAction action )
2718 {
2719   FuncMsg fmsg( "--- PyModuleHelper::internalDropObjects()" );
2720
2721   // Python interpreter should be initialized and Python module should be
2722   // import first
2723   if ( !myInterp || !myPyModule || what.isEmpty() || !where )
2724     return;
2725
2726   QStringList* theList = new QStringList();
2727
2728   LightApp_DataObject* whereObject = dynamic_cast<LightApp_DataObject*>( where );
2729   if ( !whereObject ) return;
2730   
2731   for ( int i = 0; i < what.count(); i++ ) {
2732     LightApp_DataObject* dataObject = dynamic_cast<LightApp_DataObject*>( what[i] );
2733     if ( dataObject ) theList->append( dataObject->entry() );
2734   }
2735
2736 #if SIP_VERSION < 0x040800
2737   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList) );
2738 #else
2739   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL) );
2740 #endif
2741   if ( PyObject_HasAttrString(myPyModule, (char*)"dropObjects") ) {
2742       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dropObjects", (char*)"Osii",
2743                         sipList.get(),
2744                         whereObject->entry().toLatin1().constData(),
2745                         row, action ) );
2746     
2747     if( !res ) {
2748       PyErr_Print();
2749     }
2750   }
2751 }
2752
2753 /*!
2754   \brief Get engine IOR callback function
2755   \internal
2756   
2757   Tries to get engine IOR from the Python module using engineIOR() function.
2758   That function can load module engine using appropriate container if required.
2759
2760   \return engine IOR or empty string if it is not provided by Python module 
2761 */
2762 QString PyModuleHelper::internalEngineIOR() const
2763 {
2764   FuncMsg fmsg( "--- PyModuleHelper::internalEngineIOR()" );
2765
2766   QString ior;
2767
2768   // Python interpreter should be initialized and Python module should be
2769   // import first
2770   if ( myInterp && myModule ) {
2771     if ( PyObject_HasAttrString( myPyModule , "engineIOR" ) ) {
2772       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"engineIOR", (char*)"" ) );
2773       if ( !res ) {
2774           PyErr_Print();
2775       }
2776       else {
2777         // parse the return value, result chould be string
2778         if ( PyString_Check( res ) ) {
2779           ior = PyString_AsString( res );
2780         }
2781       }
2782     }
2783   }
2784   return ior;
2785 }
2786
2787 /*!
2788   \brief Connects signals about activating and cloning view on internal slots
2789   \param view view being connected
2790 */
2791 void PyModuleHelper::connectView( SUIT_ViewWindow* view )
2792 {
2793   SUIT_ViewManager* viewMgr = view->getViewManager();
2794   SUIT_ViewModel* viewModel = viewMgr ? viewMgr->getViewModel() : 0;
2795       
2796   // Connect tryCloseView() and deleteView() signals
2797   if ( viewMgr ) {
2798     connect( viewMgr, SIGNAL( tryCloseView( SUIT_ViewWindow* ) ),
2799              this, SLOT( tryCloseView( SUIT_ViewWindow* ) ),
2800              Qt::UniqueConnection );
2801     connect( viewMgr, SIGNAL( deleteView( SUIT_ViewWindow* ) ),
2802              this, SLOT( closeView( SUIT_ViewWindow* ) ),
2803              Qt::UniqueConnection );
2804   }
2805   
2806   // Connect cloneView() signal of an OCC View
2807   if ( view->inherits( "OCCViewer_ViewWindow" ) ) {
2808     connect( view, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2809              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2810              Qt::UniqueConnection );
2811   }
2812   // Connect cloneView() signal of Plot2d View 
2813   else if ( viewModel && viewModel->inherits( "Plot2d_Viewer" ) ) {
2814     connect( viewModel, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2815              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2816              Qt::UniqueConnection );
2817   }
2818 }
2819
2820
2821
2822 void PyModuleHelper::internalOBClickedPython( const QString& theObj, int theColumn)
2823 {
2824   FuncMsg fmsg( "--- PyModuleHelper::internalOBClickedPython()" );
2825
2826   // Python interpreter should be initialized and Python module should be
2827   // import first
2828   if ( !myInterp || !myPyModule )
2829     return; // Error
2830
2831   if ( PyObject_HasAttrString( myPyModule, (char*)"onObjectBrowserClicked" ) ) {
2832     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"onObjectBrowserClicked", (char*)"si", theObj.toLatin1().constData(), theColumn ) );
2833     if( !res ) {
2834       PyErr_Print();
2835     }
2836   }
2837 }
2838
2839
2840
2841 void PyModuleHelper::onObjectBrowserClicked(SUIT_DataObject* theObj, int theColumn)
2842 {
2843   FuncMsg fmsg( "PyModuleHelper::onObjectBrowserClicked()" );
2844
2845   // temporary set myInitModule because dumpPython() method
2846   // might be called by the framework when this module is inactive,
2847   // but still it should be possible to access this module's data
2848   // from Python
2849   InitLocker lock( myModule );
2850
2851   class PythonReq: public PyInterp_LockRequest
2852   {
2853   public:     
2854     PythonReq( PyInterp_Interp* _py_interp,
2855                PyModuleHelper*  _helper,
2856                const QString& _entry,
2857                int     _column )
2858       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
2859         myHelper( _helper ) ,
2860         myEntry( _entry ),
2861         myColumn( _column )
2862     {}
2863   protected:
2864     virtual void execute()
2865     {
2866       myHelper->internalOBClickedPython( myEntry, myColumn );
2867     }
2868   private:
2869     PyModuleHelper* myHelper;
2870     int    myColumn;
2871     QString myEntry;
2872   };
2873   
2874   // Posting the request only if dispatcher is not busy!
2875   // Executing the request synchronously
2876   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( theObj );
2877   if ( (!PyInterp_Dispatcher::Get()->IsBusy()) && data_object )
2878     PyInterp_Dispatcher::Get()->Exec( new PythonReq( myInterp, this, data_object->entry(), theColumn ) );
2879 }
2880