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