Salome HOME
Merge remote-tracking branch 'origin/master' into V9_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 LightApp_Module*          PyModuleHelper::myInitModule = 0;
613
614 /*!
615   \brief Constructor
616   \param module owner module
617 */
618 PyModuleHelper::PyModuleHelper( LightApp_Module* module ) :
619   QObject( module ),
620   myModule( module ),
621   myPyModule( 0 ), 
622   myInterp( 0 ),
623   myXmlHandler ( 0 ),
624   myLastActivateStatus( true )
625 {
626   setObjectName( "python_module_helper" );
627 }
628
629 /*!
630   \brief Destructor
631 */
632 PyModuleHelper::~PyModuleHelper()
633 {
634   delete myXmlHandler;
635   if ( myInterp && myPyModule ) {
636     PyLockWrapper aLock; // Acquire GIL
637     Py_XDECREF( myPyModule );
638   }
639 }
640
641 /*!
642   \brief Get the module being initialized.
643   
644   This is a little trick :) needed to provide an access from Python
645   (SalomePyQt) to the module being currently activated. The problem
646   that during the process of module initialization (initialize() 
647   function) it is not yet available via application->activeModule()
648   call.
649   
650   This method returns valid pointer only if called in scope of
651   initialize() function or in several other specific cases.
652
653   \return the module being currently initialized
654 */
655 LightApp_Module* PyModuleHelper::getInitModule()
656 {
657   QMutexLocker ml( &myInitMutex );
658   return myInitModule;
659 }
660
661 /*!
662   \brief Get default menu group identifier
663   \return menu group ID (40 by default)
664 */
665 int PyModuleHelper::defaultMenuGroup()
666 {
667   return DEFAULT_GROUP; 
668 }
669
670 /*!
671   \brief Get owner module
672   \return owner module
673 */
674 LightApp_Module* PyModuleHelper::module() const
675 {
676   return myModule;
677 }
678
679 /*!
680   \brief Get Python GUI module object
681   \return python module
682 */
683 PyObject* PyModuleHelper::pythonModule() const
684 {
685   return myPyModule;
686 }
687
688 /*!
689   \brief Connect action to the internal actionActivated() slot.
690
691   Actions connected to internal actionActivated(), when activated, will
692   be forwarded to the Python GUI module OnGUIEvent() function.
693
694   \param a action being connected
695 */
696 void PyModuleHelper::connectAction( QAction* a )
697 {
698   if ( myModule && a )
699     QObject::connect( a, SIGNAL( triggered( bool ) ), 
700                       this, SLOT( actionActivated() ),
701                       Qt::UniqueConnection );
702 }
703
704 /*!
705   \brief Get the dockable windows associated with the module.
706   
707   To fill the list of windows the correspondind Python module's windows()
708   method is called during the module initialization.
709
710   By default, ObjectBrowser, PythonConsole and LogWindow windows are 
711   associated to the module.
712
713   Allowed dockable windows:
714   - LightApp_Application::WT_ObjectBrowser : object browser
715   - LightApp_Application::WT_PyConsole : python console
716   - LightApp_Application::WT_LogWindow : log messages output window
717
718   Dock area is defined by Qt::DockWidgetArea enumeration:
719   - Qt::TopDockWidgetArea : top dock area
720   - Qt::BottomDockWidgetArea : bottom dock area
721   - Qt::LeftDockWidgetArea : left dock area
722   - Qt::RightDockWidgetArea : right dock area
723
724   \return map of dockable windows in form { <window_type> : <dock_area> }
725 */
726 QMap<int, int> PyModuleHelper::windows() const
727 {
728   FuncMsg fmsg( "PyModuleHelper::windows()" );
729
730   return myWindowsMap;
731 }
732
733 /*!
734   \brief Define the compatible view windows associated with the module.
735
736   The associated view windows are opened automatically when the module
737   is activated.
738
739   To fill the list of views the correspondind Python module's views()
740   method is called during the module initialization.
741   By default, the list of view types is empty.
742
743   \return list of view windows types
744 */
745 QStringList PyModuleHelper::viewManagers() const
746 {
747   FuncMsg fmsg( "PyModuleHelper::viewManagers()" );
748
749   return myViewMgrList;
750 }
751
752 /*!
753   \brief Initialization of the Python-based SALOME module.
754   
755   This method can be used for creation of the menus, toolbars and 
756   other such stuff.
757   
758   There are two ways to do this:
759   1) for obsolete modules, the implementation of this method first tries to read
760   the <module>_<language>.xml resource file which contains a menu,
761   toolbars and popup menus description;
762   2) new modules can create menus by direct calling of the
763   corresponding methods of SalomePyQt Python API in the Python
764   module's initialize() method which is called from here.
765
766   \note SALOME supports two modes of modules loading:
767   - immediate (all the modules are created and initialized 
768   immediately when the application object is created);
769   - postponed modules loading (used currently); in this mode
770   the module is loaded only by explicit request.
771   If postponed modules loading is not used, the active
772   study might be not yet defined at this stage, so initialize()
773   method should not perform any study-based initialization.
774   Such actions can be better done in activate() function.
775
776   \param app parent application object
777 */
778 void PyModuleHelper::initialize( CAM_Application* app )
779 {
780   FuncMsg fmsg( "PyModuleHelper::initialize()" );
781
782   // temporarily store module being currently activated
783   // in the global variable to make it accessible from
784   // Python API
785   InitLocker lock( myModule );
786
787   // try to get XML resource file name
788   SUIT_ResourceMgr* resMgr = myModule->getApp()->resourceMgr();
789   if ( !myXmlHandler && resMgr ) {
790     // get current language
791     QString lang = resMgr->stringValue( "language", "language", "en" );
792     // get menu description file name
793     QString aFileName = QString( "%1_%2.xml" ).arg( myModule->name() ).arg( lang );
794     aFileName = resMgr->path( "resources", myModule->name(), aFileName );
795     if ( !aFileName.isEmpty() && QFile::exists( aFileName ) ) {
796       // create XML handler instance
797       myXmlHandler = new XmlHandler( this, aFileName );
798       // ask XML handler to create actions
799       myXmlHandler->createActions();
800     }
801   }
802
803   class InitializeReq : public PyInterp_Request
804   {
805   public:
806     InitializeReq( PyModuleHelper*  _helper,
807                    CAM_Application* _app )
808       : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
809         myHelper( _helper ),
810         myApp( _app )
811     {}
812   protected:
813     virtual void execute()
814     {
815       myHelper->internalInitialize( myApp );
816     }
817   private:
818     PyModuleHelper*  myHelper;
819     CAM_Application* myApp;
820   };
821
822   // post request
823   PyInterp_Dispatcher::Get()->Exec( new InitializeReq( this, app ) );
824 }
825
826 /*!
827   \brief Activation of the module.
828
829   This function is usually used in order to show the module's 
830   specific menus and toolbars, update actions state and perform
831   other such actions required when the module is activated.
832   
833   \note Returning \c false from this function prevents the 
834   module activation.
835
836   \param study parent study
837   \return \c true if activation is successful and \c false otherwise
838 */
839 bool PyModuleHelper::activate( SUIT_Study* study )
840 {
841   FuncMsg fmsg( "PyModuleHelper::activate()" );
842
843   // reset the activation status to the default value
844   myLastActivateStatus = true;
845
846   class ActivateReq : public PyInterp_Request
847   {
848   public:
849     ActivateReq( PyModuleHelper* _helper,
850                  SUIT_Study*     _study,
851                  bool            _customize )
852   : PyInterp_Request( 0, true ), // this request should be processed synchronously (sync == true)
853     myHelper( _helper ),
854     myStudy ( _study ),
855     myCustomize( _customize )
856   {}
857   protected:
858     virtual void execute()
859       {
860         if ( !myCustomize )
861           myHelper->internalActivate( myStudy );  // first activation stage
862         else
863           myHelper->internalCustomize( myStudy ); // second activation stage
864       }
865   private:
866     PyModuleHelper* myHelper;
867     SUIT_Study*     myStudy;
868     bool            myCustomize;
869   };
870
871   // post request for activation (customize=false)
872   PyInterp_Dispatcher::Get()->Exec( new ActivateReq( this, study, false ) );
873
874   // check activation status (can be set to false by internalActivate())
875   if ( myLastActivateStatus ) {
876     // activate menus, toolbars, etc
877     if ( myXmlHandler ) myXmlHandler->activateMenus( true );
878
879     // show menus / toolbars
880     myModule->setMenuShown( true );
881     myModule->setToolShown( true );
882
883     // post request for customization (customize=true)
884     PyInterp_Dispatcher::Get()->Exec( new ActivateReq( this, study, true ) );
885
886     // check activation status (can be set to false by internalCustomize())
887     if ( myLastActivateStatus ) {
888       // connect preferences changing signal
889       connect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
890                this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
891       
892       // connect active view change signal
893       SUIT_Desktop* d = study->application()->desktop();
894       connect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
895                this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
896       // if active window exists, call activeViewChanged() function;
897       // temporary solution: if a getActiveView() in SalomePyQt available
898       // we no longer need this 
899       SUIT_ViewWindow* view = d->activeWindow();
900       if ( view ) activeViewChanged( view );
901       // get all view currently opened in the study and connect their signals to 
902       // the corresponding slots of the class.
903       foreach ( view, d->windows() ) connectView( view );
904     }
905     else {
906       // hide menus / toolbars in case of error
907       myModule->setMenuShown( false );
908       myModule->setToolShown( false );
909     }
910   }
911
912   return myLastActivateStatus;
913 }
914
915 /*!
916   \brief Deactivation of the module.
917
918   This function is usually used in order to hide the module's 
919   specific menus and toolbars and perform other such actions
920   required when the module is deactivated.
921
922   \param study parent study
923   \return \c true if deactivation is successful and \c false otherwise
924 */
925 bool PyModuleHelper::deactivate( SUIT_Study* study )
926 {
927   FuncMsg fmsg( "PyModuleHelper::deactivate()" );
928
929   class DeactivateReq : public PyInterp_LockRequest
930   {
931   public:
932     DeactivateReq( PyInterp_Interp* _py_interp,
933                    PyModuleHelper*  _helper,
934                    SUIT_Study*      _study )
935       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
936         myHelper( _helper ),
937         myStudy ( _study )
938     {}
939   protected:
940     virtual void execute()
941     {
942       myHelper->internalDeactivate( myStudy );
943     }
944   private:
945     PyModuleHelper* myHelper;
946     SUIT_Study*     myStudy;
947   };
948
949   // post request
950   PyInterp_Dispatcher::Get()->Exec( new DeactivateReq( myInterp, this, study ) );
951
952   // disconnect preferences changing signal
953   disconnect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
954               this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
955   
956   // disconnect the SUIT_Desktop signal windowActivated()
957   SUIT_Desktop* d = study->application()->desktop();
958   disconnect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
959               this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
960
961   // deactivate menus, toolbars, etc
962   if ( myXmlHandler ) myXmlHandler->activateMenus( false );
963
964   // hide menus / toolbars
965   myModule->setMenuShown( false );
966   myModule->setToolShown( false );
967
968   return true;
969 }
970
971 /*!
972   \brief Close of the module.
973
974   This function is usually used in order to close the module's 
975   specific menus and toolbars and perform other such actions
976   required when the module is closed.
977 */
978 void PyModuleHelper::modelClosed( SUIT_Study* study )
979 {
980   FuncMsg fmsg( "PyModuleHelper::modelClosed()" );
981
982   class StudyClosedReq : public PyInterp_LockRequest
983   {
984   public:
985     StudyClosedReq( PyInterp_Interp* _py_interp,
986                    PyModuleHelper*  _helper,
987                    SUIT_Study*      _study )
988       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
989         myHelper( _helper ),
990         myStudy ( _study )
991     {}
992   protected:
993     virtual void execute()
994     {
995       myHelper->internalClosedStudy( myStudy );
996     }
997   private:
998     PyModuleHelper* myHelper;
999     SUIT_Study*     myStudy;
1000   };
1001
1002   // post request
1003   PyInterp_Dispatcher::Get()->Exec( new StudyClosedReq( myInterp, this, study ) );
1004
1005   // disconnect preferences changing signal
1006   disconnect( myModule->getApp(), SIGNAL( preferenceChanged( const QString&, const QString&, const QString& ) ),
1007               this,               SLOT(   preferenceChanged( const QString&, const QString&, const QString& ) ) );
1008   
1009   // disconnect the SUIT_Desktop signal windowActivated()
1010   SUIT_Desktop* d = study->application()->desktop();
1011   disconnect( d,     SIGNAL( windowActivated( SUIT_ViewWindow* ) ),
1012               this,  SLOT( activeViewChanged( SUIT_ViewWindow* ) ) );
1013
1014   // deactivate menus, toolbars, etc
1015   if ( myXmlHandler ) myXmlHandler->activateMenus( false );
1016
1017   // hide menus / toolbars
1018   myModule->setMenuShown( false );
1019   myModule->setToolShown( false );
1020 }
1021
1022
1023 /*!
1024   \brief Process module's preferences changing.
1025
1026   Called when the module's own preferences are changed.
1027   
1028   \param section preference resources section
1029   \param parameter preference resources parameter name
1030 */
1031 void PyModuleHelper::preferencesChanged( const QString& section, 
1032                                          const QString& parameter )
1033 {
1034   FuncMsg fmsg( "PyModuleHelper::preferencesChanged()" );
1035
1036   class PrefChangeReq : public PyInterp_LockRequest
1037   {
1038   public:
1039     PrefChangeReq( PyInterp_Interp* _py_interp,
1040                    PyModuleHelper*  _helper,
1041                    const QString&   _section,
1042                    const QString&   _parameter )
1043       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1044         myHelper ( _helper ),
1045         mySection( _section ),
1046         myParameter( _parameter ) 
1047     {}
1048   protected:
1049     virtual void execute()
1050     {
1051       myHelper->internalPreferencesChanged( mySection, myParameter );
1052     }
1053   private:
1054     PyModuleHelper* myHelper;
1055     QString         mySection, myParameter;
1056   };
1057
1058   // post the request only if dispatcher is not busy!
1059   // execute request synchronously
1060   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1061     PyInterp_Dispatcher::Get()->Exec( new PrefChangeReq( myInterp, this, section, parameter ) );
1062 }
1063
1064 /*!
1065   \brief Process application preferences changing.
1066
1067   Called when any application setting is changed.
1068
1069   \param module preference module
1070   \param section preference resources section
1071   \param parameter preference resources parameter name
1072 */
1073 void PyModuleHelper::preferenceChanged( const QString& module, 
1074                                         const QString& section,
1075                                         const QString& parameter )
1076 {
1077   FuncMsg fmsg( "PyModuleHelper::preferenceChanged()" );
1078
1079   // module's own preferences are processed by other preferencesChanged() method
1080   if ( module != myModule->moduleName() ) {
1081     // call helper
1082     preferencesChanged( section, parameter );
1083   }
1084 }
1085
1086 /*!
1087   \brief Process study activation.
1088   
1089   Called when study desktop is activated. Used for notifying the Python
1090   module about changing of the active study.
1091
1092   \param study study being activated
1093 */
1094 void PyModuleHelper::studyActivated( SUIT_Study* study )
1095 {
1096   FuncMsg fmsg( "PyModuleHelper::studyActivated()" );
1097
1098   // StudyChangedReq: request class for internal studyChanged() operation
1099   class StudyChangedReq : public PyInterp_Request
1100   {
1101   public:
1102     StudyChangedReq( PyModuleHelper* _helper,
1103                      SUIT_Study*     _study )
1104       : PyInterp_Request(0, true ), // this request should be processed synchronously (sync == true)
1105         myHelper( _helper ), 
1106         myStudy ( _study )
1107     {}
1108   protected:
1109     virtual void execute()
1110     {
1111       myHelper->internalStudyChanged( myStudy );
1112     }
1113   private:
1114     PyModuleHelper* myHelper;
1115     SUIT_Study*     myStudy;
1116   };
1117
1118   // post request
1119   PyInterp_Dispatcher::Get()->Exec( new StudyChangedReq( this, study ) );
1120 }
1121
1122 /*!
1123   \brief Process action activation.
1124   
1125   Called when action is activated. Used for notifying the Python
1126   module about any related action activation.
1127
1128   \sa connectAction()
1129 */
1130 void PyModuleHelper::actionActivated()
1131 {
1132   FuncMsg fmsg( "PyModuleHelper::actionActivated()" );
1133
1134   // perform synchronous request to Python event dispatcher
1135   class ActionReq : public PyInterp_LockRequest
1136   {
1137   public:
1138     ActionReq( PyInterp_Interp* _py_interp,
1139                PyModuleHelper*  _helper,
1140                int              _id )
1141       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1142         myHelper( _helper ),
1143         myId    ( _id  )
1144     {}
1145   protected:
1146     virtual void execute()
1147     {
1148       myHelper->internalActionActivated( myId );
1149     }
1150   private:
1151     PyModuleHelper* myHelper;
1152     int             myId;
1153   };
1154
1155   // get sender action
1156   QAction* action = qobject_cast<QAction*>( sender() );
1157   if ( !action )
1158     return;
1159
1160   // post request
1161   PyInterp_Dispatcher::Get()->Exec( new ActionReq( myInterp, this, myModule->actionId( action ) ) );
1162 }
1163
1164 /*!
1165   \brief update selection from other views or modules.
1166
1167   Called when selection is modified outside.
1168 */
1169 void PyModuleHelper::selectionUpdated(const QStringList& entries)
1170 {
1171   FuncMsg fmsg( "PyModuleHelper::selectionUpdated()" );
1172   MESSAGE("selectionUpdated");
1173
1174   // perform synchronous request to Python event dispatcher
1175   class SelectionReq : public PyInterp_LockRequest
1176   {
1177   public:
1178     SelectionReq( PyInterp_Interp* _py_interp,
1179                   PyModuleHelper*  _helper,
1180                   const QStringList& _entries )
1181       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1182         myHelper( _helper ),
1183         myEntries( _entries  )
1184     {
1185       MESSAGE("SelectionReq");
1186     }
1187   protected:
1188     virtual void execute()
1189     {
1190       MESSAGE("execute");
1191       myHelper->internalSelectionUpdated( myEntries );
1192     }
1193   private:
1194     PyModuleHelper* myHelper;
1195     const QStringList& myEntries;
1196   };
1197
1198   // post request
1199   PyInterp_Dispatcher::Get()->Exec( new SelectionReq( myInterp, this, entries ) );
1200 }
1201
1202 /*!
1203   \brief Process context popup menu request.
1204   
1205   Called when user activates popup menu in some window
1206   (view, object browser, etc).
1207
1208   \param context popup menu context (e.g. "ObjectBrowser")
1209   \param menu popup menu
1210 */
1211 void PyModuleHelper::contextMenu( const QString& context, QMenu* menu )
1212 {
1213   FuncMsg fmsg( "PyModuleHelper::contextMenu()" );
1214
1215   class ContextMenuReq : public PyInterp_LockRequest
1216   {
1217   public:
1218     ContextMenuReq( PyInterp_Interp* _py_interp,
1219                     PyModuleHelper*  _helper,
1220                     const QString&   _context,
1221                     QMenu*           _menu )
1222       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1223         myHelper ( _helper ),
1224         myContext( _context ),
1225         myMenu   ( _menu )
1226     {}
1227   protected:
1228     virtual void execute()
1229     {
1230       myHelper->internalContextMenu( myContext, myMenu );
1231     }
1232   private:
1233     PyModuleHelper* myHelper;
1234     QString         myContext;
1235     QMenu*          myMenu;
1236   };
1237
1238   // post request only if dispatcher is not busy!
1239   // execute request synchronously
1240   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1241     PyInterp_Dispatcher::Get()->Exec( new ContextMenuReq( myInterp, this, context, menu ) );
1242 }
1243
1244 /*!
1245   \brief Export preferences for the Python module.
1246   Called only once when the first instance of the module is created or
1247   when common Preferences dialog box is first time invoked.
1248 */
1249 void PyModuleHelper::createPreferences()
1250 {
1251   FuncMsg fmsg( "PyModuleHelper::createPreferences()" );
1252
1253   // temporary set myInitModule because createPreferences() method
1254   // might be called during the module intialization process
1255   InitLocker lock( myModule );
1256
1257   class CreatePrefReq : public PyInterp_LockRequest
1258   {
1259   public:
1260     CreatePrefReq( PyInterp_Interp* _py_interp,
1261                    PyModuleHelper*  _helper )
1262       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1263         myHelper( _helper )
1264     {}
1265   protected:
1266     virtual void execute()
1267     {
1268       myHelper->internalCreatePreferences();
1269     }
1270   private:
1271     PyModuleHelper* myHelper;
1272   };
1273
1274   // post request only if dispatcher is not busy!
1275   // execute request synchronously
1276   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1277     PyInterp_Dispatcher::Get()->Exec( new CreatePrefReq( myInterp, this ) );
1278 }
1279
1280 /*!
1281   \brief Signal handler windowActivated(SUIT_ViewWindow*) of SUIT_Desktop
1282
1283   Used to notify Python module that active view has been changed by the user.
1284
1285   \param view view being activated
1286 */
1287 void PyModuleHelper::activeViewChanged( SUIT_ViewWindow* view )
1288 {
1289   FuncMsg fmsg( "PyModuleHelper::activeViewChanged()" );
1290
1291   // perform synchronous request to Python event dispatcher
1292   class ActiveViewChangeReq : public PyInterp_LockRequest
1293   {
1294   public:
1295     ActiveViewChangeReq( PyInterp_Interp* _py_interp,
1296                          PyModuleHelper*  _helper,
1297                          SUIT_ViewWindow* _view )
1298       : PyInterp_LockRequest( _py_interp, 0, true ),
1299         myHelper( _helper ),
1300         myView( _view )
1301     {}
1302   protected:
1303     virtual void execute()
1304     {
1305       myHelper->internalActiveViewChanged( myView );
1306     }
1307   private:
1308     PyModuleHelper*  myHelper;
1309     SUIT_ViewWindow* myView;
1310   };
1311
1312   // connect view (if it is not connected yet)
1313   connectView( view );
1314   
1315   PyInterp_Dispatcher::Get()->Exec( new ActiveViewChangeReq( myInterp, this, view ) ); 
1316 }
1317
1318 /*!
1319   \brief Signal handler tryClose(SUIT_ViewWindow*) of a view
1320   \param view view being closed
1321 */
1322 void PyModuleHelper::tryCloseView( SUIT_ViewWindow* view )
1323 {
1324   FuncMsg fmsg( "PyModuleHelper::tryCloseView()" );
1325
1326   class TryCloseViewReq : public PyInterp_LockRequest
1327   {
1328   public:
1329     TryCloseViewReq( PyInterp_Interp* _py_interp,
1330                      PyModuleHelper*  _helper,
1331                      SUIT_ViewWindow* _view )
1332       : PyInterp_LockRequest( _py_interp, 0, true ),
1333         myHelper( _helper ), 
1334         myView( _view )
1335     {}
1336   protected:
1337     virtual void execute()
1338     {
1339       myHelper->internalTryCloseView( myView );
1340     }
1341   private:
1342     PyModuleHelper*  myHelper;
1343     SUIT_ViewWindow* myView;    
1344   };
1345
1346   PyInterp_Dispatcher::Get()->Exec( new TryCloseViewReq( myInterp, this, view ) );
1347 }
1348
1349 /*!
1350   \brief Signal handler closing(SUIT_ViewWindow*) of a view
1351   \param view view being closed
1352 */
1353 void PyModuleHelper::closeView( SUIT_ViewWindow* view )
1354 {
1355   FuncMsg fmsg( "PyModuleHelper::closeView()" );
1356
1357   class CloseViewReq : public PyInterp_LockRequest
1358   {
1359   public:
1360     CloseViewReq( PyInterp_Interp* _py_interp,
1361                   PyModuleHelper*  _helper,
1362                   SUIT_ViewWindow* _view )
1363       : PyInterp_LockRequest( _py_interp, 0, true ),
1364         myHelper( _helper ),
1365         myView( _view )
1366     {}
1367   protected:
1368     virtual void execute()
1369     {
1370       myHelper->internalCloseView( myView );
1371     }
1372   private:
1373     PyModuleHelper*  myHelper;
1374     SUIT_ViewWindow* myView;    
1375   };
1376
1377   PyInterp_Dispatcher::Get()->Exec( new CloseViewReq( myInterp, this, view ) );
1378 }
1379
1380 /*!
1381   \brief Signal handler cloneView() of OCCViewer_ViewWindow
1382   \param view view being cloned
1383 */
1384 void PyModuleHelper::cloneView( SUIT_ViewWindow* view )
1385 {
1386   FuncMsg fmsg( "PyModuleHelper::cloneView()" );
1387
1388   class CloneViewReq : public PyInterp_LockRequest
1389   {
1390   public:
1391     CloneViewReq( PyInterp_Interp* _py_interp,
1392                   PyModuleHelper*  _helper,
1393                   SUIT_ViewWindow* _view )
1394       : PyInterp_LockRequest( _py_interp, 0, true ),
1395         myHelper( _helper ),
1396         myView( _view )
1397     {}
1398   protected:
1399     virtual void execute()
1400     {
1401       myHelper->internalCloneView( myView );
1402     }
1403   private:
1404     PyModuleHelper*  myHelper;
1405     SUIT_ViewWindow* myView;
1406   };
1407   
1408   PyInterp_Dispatcher::Get()->Exec( new CloneViewReq( myInterp, this, view ) );
1409 }
1410
1411 /*!
1412   \brief Save module data. Called when user saves study.
1413   \param files output list of files where module stores data
1414   \param url study URL
1415 */
1416 void PyModuleHelper::save( QStringList& files, const QString& url )
1417 {
1418   FuncMsg fmsg( "PyModuleHelper::save()" );
1419
1420   // temporary set myInitModule because save() method
1421   // might be called by the framework when this module is inactive,
1422   // but still it should be possible to access this module's data
1423   // from Python
1424   InitLocker lock( myModule );
1425
1426   // perform synchronous request to Python event dispatcher
1427   class SaveReq: public PyInterp_LockRequest
1428   {
1429   public:     
1430     SaveReq( PyInterp_Interp* _py_interp,
1431              PyModuleHelper*  _helper,
1432              QStringList&     _files,
1433              const QString&   _url )
1434       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1435         myHelper( _helper ) ,
1436         myFiles( _files ),
1437         myUrl( _url )
1438     {}
1439   protected:
1440     virtual void execute()
1441     {
1442       myHelper->internalSave( myFiles, myUrl );
1443     }
1444   private:
1445     PyModuleHelper* myHelper;
1446     QStringList&    myFiles;
1447     QString         myUrl;
1448   };
1449   
1450   // Posting the request only if dispatcher is not busy!
1451   // Executing the request synchronously
1452   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1453     PyInterp_Dispatcher::Get()->Exec( new SaveReq( myInterp, this, files, url ) );
1454 }
1455
1456 /*
1457   \brief Load module data. Called when user opens study 
1458   and activates module.
1459   \param files list of files where module data is stored
1460   \param url study URL
1461   \return \c true if loading has been finished successfully or \c false otherwise
1462 */
1463 bool PyModuleHelper::load( const QStringList& files, const QString& url )
1464 {
1465   FuncMsg fmsg( "PyModuleHelper::load()" );
1466
1467   bool loaded = false;
1468
1469   class LoadReq: public PyInterp_LockRequest
1470   {
1471   public:
1472     LoadReq( PyInterp_Interp* _py_interp,
1473              PyModuleHelper*  _helper,
1474              QStringList      _files,
1475              const QString&   _url,
1476              bool&            _loaded )
1477       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1478         myHelper( _helper ) ,
1479         myFiles( _files ),
1480         myUrl( _url ),
1481         myLoaded( _loaded )
1482     {}
1483   protected:
1484     virtual void execute()
1485     {
1486       myHelper->internalLoad( myFiles, myUrl, myLoaded );
1487     }
1488   private:
1489     PyModuleHelper* myHelper;
1490     QStringList     myFiles;
1491     QString         myUrl;
1492     bool&           myLoaded;
1493   };
1494   
1495   // Posting the request only if dispatcher is not busy!
1496   // Executing the request synchronously
1497   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1498     PyInterp_Dispatcher::Get()->Exec( new LoadReq( myInterp, this, files, url, loaded ) );
1499
1500   return loaded;
1501 }
1502
1503 /*!
1504   \brief Dump module data to the Python script. 
1505   Called when user activates dump study operation.
1506   \param files output list of files where module stores python script
1507 */
1508 void PyModuleHelper::dumpPython( QStringList& files )
1509 {
1510   FuncMsg fmsg( "PyModuleHelper::dumpPython()" );
1511
1512   // temporary set myInitModule because dumpPython() method
1513   // might be called by the framework when this module is inactive,
1514   // but still it should be possible to access this module's data
1515   // from Python
1516   InitLocker lock( myModule );
1517
1518   class DumpPythonReq: public PyInterp_LockRequest
1519   {
1520   public:     
1521     DumpPythonReq( PyInterp_Interp* _py_interp,
1522                    PyModuleHelper*  _helper,
1523                    QStringList&     _files )
1524       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1525         myHelper( _helper ) ,
1526         myFiles( _files )
1527     {}
1528   protected:
1529     virtual void execute()
1530     {
1531       myHelper->internalDumpPython( myFiles );
1532     }
1533   private:
1534     PyModuleHelper* myHelper;
1535     QStringList&    myFiles;
1536   };
1537   
1538   // Posting the request only if dispatcher is not busy!
1539   // Executing the request synchronously
1540   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1541     PyInterp_Dispatcher::Get()->Exec( new DumpPythonReq( myInterp, this, files ) );
1542 }
1543
1544 /*!
1545   \brief Test if object \a what can be dragged by the user.
1546   \param what data object being tested
1547   \return \c true if object can be dragged or \c false otherwise
1548 */
1549 bool PyModuleHelper::isDraggable( const SUIT_DataObject* what ) const
1550 {
1551   FuncMsg fmsg( "PyModuleHelper::isDraggable()" );
1552
1553   bool draggable = false;
1554
1555   // perform synchronous request to Python event dispatcher
1556   class IsDraggableReq: public PyInterp_LockRequest
1557   {
1558   public:
1559     IsDraggableReq( PyInterp_Interp*     _py_interp,
1560                     PyModuleHelper*      _helper,
1561                     LightApp_DataObject* _data_object,
1562                     bool&                _is_draggable )
1563       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1564         myHelper( _helper ) ,
1565         myDataObject( _data_object ),
1566         myIsDraggable( _is_draggable )
1567     {}
1568   protected:
1569     virtual void execute()
1570     {
1571       myIsDraggable = myHelper->internalIsDraggable( myDataObject );
1572     }
1573   private:
1574     PyModuleHelper*      myHelper;
1575     LightApp_DataObject* myDataObject;
1576     bool&                myIsDraggable;
1577   };
1578
1579   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( what );
1580   if ( data_object ) {
1581     // Posting the request only if dispatcher is not busy!
1582     // Executing the request synchronously
1583     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1584       PyInterp_Dispatcher::Get()->Exec( new IsDraggableReq( myInterp,
1585                                         const_cast<PyModuleHelper*>( this ),
1586                                         const_cast<LightApp_DataObject*>( data_object ),
1587                                         draggable ) );
1588   }
1589   
1590   return draggable;
1591 }
1592
1593 /*!
1594   \brief Test if drop operation can be done on the \a where object.
1595   \param where data object being tested
1596   \return \c true if if drop operation is supported by object or \c false otherwise
1597 */
1598 bool PyModuleHelper::isDropAccepted( const SUIT_DataObject* where ) const
1599 {
1600   FuncMsg fmsg( "PyModuleHelper::isDropAccepted()" );
1601
1602   bool dropAccepted = false;
1603
1604   // perform synchronous request to Python event dispatcher
1605   class IsDropAcceptedReq: public PyInterp_LockRequest
1606   {
1607   public:
1608     IsDropAcceptedReq( PyInterp_Interp*     _py_interp,
1609                        PyModuleHelper*      _helper,
1610                        LightApp_DataObject* _data_object,
1611                        bool&                _is_drop_accepted )
1612       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1613         myHelper( _helper ) ,
1614         myDataObject( _data_object ),
1615         myIsDropAccepted( _is_drop_accepted )
1616     {}
1617   protected:
1618     virtual void execute()
1619     {
1620       myIsDropAccepted = myHelper->internalIsDropAccepted( myDataObject );
1621     }
1622   private:
1623     PyModuleHelper*      myHelper;
1624     LightApp_DataObject* myDataObject;
1625     bool&                myIsDropAccepted;
1626   };
1627   
1628   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( where );
1629   if ( data_object ) {
1630     // Posting the request only if dispatcher is not busy!
1631     // Executing the request synchronously
1632     if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1633       PyInterp_Dispatcher::Get()->Exec( new IsDropAcceptedReq( myInterp,
1634                                          const_cast<PyModuleHelper*>( this ),
1635                                          const_cast<LightApp_DataObject*>( data_object ),
1636                                          dropAccepted ) );
1637   }
1638
1639   return dropAccepted;
1640 }
1641
1642 /*!
1643   \brief Perform drop operation
1644   \param what list of data objects being dropped
1645   \param where target data object for drop operation
1646   \param row line (child item index) where drop operation is performed to
1647   \param action current drop action (copy or move)
1648 */
1649 void PyModuleHelper::dropObjects( const DataObjectList& what, SUIT_DataObject* where,
1650                                   const int row, Qt::DropAction action )
1651 {
1652   FuncMsg fmsg( "PyModuleHelper::dropObjects()" );
1653
1654   // perform synchronous request to Python event dispatcher
1655   class DropObjectsReq: public PyInterp_LockRequest
1656   {
1657   public:
1658     DropObjectsReq( PyInterp_Interp*      _py_interp,
1659                     PyModuleHelper*       _helper,
1660                     const DataObjectList& _what,
1661                     SUIT_DataObject*      _where,
1662                     const int             _row,
1663                     Qt::DropAction        _action )
1664       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1665         myHelper( _helper ) ,
1666         myWhat( _what ),
1667         myWhere( _where ),
1668         myRow( _row ),
1669         myAction ( _action )
1670     {}
1671   protected:
1672     virtual void execute()
1673     {
1674       myHelper->internalDropObjects( myWhat, myWhere, myRow, myAction );
1675     }
1676   private:
1677     PyModuleHelper*  myHelper;
1678     DataObjectList   myWhat;
1679     SUIT_DataObject* myWhere;
1680     int              myRow;
1681     Qt::DropAction   myAction;
1682   };
1683   
1684   // Posting the request only if dispatcher is not busy!
1685   // Executing the request synchronously
1686   if ( !PyInterp_Dispatcher::Get()->IsBusy() )
1687     PyInterp_Dispatcher::Get()->Exec( new DropObjectsReq( myInterp, this, what, where, row, action ) );
1688 }
1689
1690 /*!
1691   \brief Get module engine IOR
1692   \return engine IOR as it is supplied by GUI Python module
1693  */
1694 QString PyModuleHelper::engineIOR() const
1695 {
1696   FuncMsg fmsg( "PyModuleHelper::engineIOR()" );
1697
1698   class EngineIORReq : public PyInterp_LockRequest
1699   {
1700   public:
1701     EngineIORReq( PyInterp_Interp* _py_interp,
1702                   PyModuleHelper*  _helper,
1703                   QString&         _ior )
1704       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
1705         myHelper( _helper ),
1706         myIOR( _ior )
1707     {}
1708   protected:
1709     virtual void execute()
1710     {
1711       myIOR = myHelper->internalEngineIOR();
1712     }
1713   private:
1714     PyModuleHelper* myHelper;
1715     QString&        myIOR;
1716   };
1717
1718   static QString anIOR;
1719
1720   if ( anIOR.isEmpty() ) {
1721     // post request
1722     PyInterp_Dispatcher::Get()->Exec( new EngineIORReq( myInterp, 
1723                                       const_cast<PyModuleHelper*>( this ),
1724                                       anIOR ) );
1725   }
1726
1727   return anIOR;
1728 }
1729
1730 /*!
1731   \brief Initialize python subinterpreter (one per study).
1732   \internal
1733 */
1734 void PyModuleHelper::initInterp()
1735 {
1736   FuncMsg fmsg( "--- PyModuleHelper::initInterp()" );
1737
1738   QMutexLocker ml( &myInitMutex );
1739
1740   myInterp = new SALOME_PYQT_PyInterp();
1741   myInterp->initialize();
1742   
1743 #ifndef GUI_DISABLE_CORBA
1744   if ( !SUIT_PYTHON::initialized ) {
1745     // import 'salome' module and call 'salome_init' method;
1746     // do it only once on interpreter creation
1747     // ... first get python lock
1748     PyLockWrapper aLock; // Acquire GIL
1749     // ... then import a module
1750     PyObjWrapper aMod = PyImport_ImportModule( "salome" );
1751     if ( !aMod ) {
1752       // Error!
1753       PyErr_Print();
1754       return;
1755     }
1756     // ... then call a method
1757     int embedded = 1;
1758     PyObjWrapper aRes( PyObject_CallMethod( aMod, (char*)"salome_init", (char*)"i", embedded ) );
1759     if ( !aRes ) {
1760       // Error!
1761       PyErr_Print();
1762       return;
1763     }
1764   }
1765 #endif 
1766 }
1767
1768 /*!
1769   \brief Import Python GUI module and store reference to the module.
1770   \internal
1771
1772   Warning! initInterp() should be called first!!!
1773 */
1774 void PyModuleHelper::importModule()
1775 {
1776   FuncMsg fmsg( "--- PyModuleHelper::importModule()" );
1777
1778   // check if the subinterpreter is initialized
1779   if ( !myInterp ) {
1780     // Error! Python subinterpreter should be initialized first!
1781     myPyModule = 0;
1782     return;
1783   }
1784
1785   // import Python GUI module and put it in <myPyModule> attribute
1786   // ... first get python lock
1787   PyLockWrapper aLock; // Acquire GIL
1788   // ... then import a module
1789   QString aMod = QString( "%1GUI" ).arg( myModule->name() );
1790   try {
1791     myPyModule = PyImport_ImportModule( aMod.toLatin1().data() );
1792   }
1793   catch (...) {
1794   }
1795
1796   if ( !myPyModule ) {
1797     // Error!
1798     PyErr_Print();
1799     return;
1800   }
1801 }
1802
1803 /*!
1804   \brief Set study workspace to the Python module.
1805   \internal
1806
1807   Calls setWorkSpace() method of the Python module with 
1808   PyQt QWidget object to use with interpreter.
1809
1810   Attention! initInterp() and importModule() should be called first!!!
1811 */
1812 void PyModuleHelper::setWorkSpace()
1813 {
1814   FuncMsg fmsg( "--- PyModuleHelper::setWorkSpace()" );
1815
1816   if ( !IsCallOldMethods ) 
1817     return;
1818
1819   // check if the subinterpreter is initialized and Python module is imported
1820   if ( !myInterp || !myPyModule ) {
1821     // Error! Python subinterpreter should be initialized and module should be imported first!
1822     return;
1823   }
1824
1825   // call setWorkSpace() method
1826   // ... first get python lock
1827   PyLockWrapper aLock; // Acquire GIL
1828
1829   // ... then try to import SalomePyQt module. If it's not possible don't go on.
1830   PyObjWrapper aQtModule( PyImport_ImportModule( "SalomePyQt" ) );
1831   if( !aQtModule ) {
1832     // Error!
1833     PyErr_Print();
1834     return;
1835   }
1836
1837   // ... then get workspace object
1838   QWidget* aWorkspace = 0;
1839   if ( myModule->getApp()->desktop()->inherits( "STD_MDIDesktop" ) ) {
1840     STD_MDIDesktop* d = dynamic_cast<STD_MDIDesktop*>( myModule->getApp()->desktop() );
1841     if ( d )
1842       aWorkspace = d->workspace();
1843   }
1844   else if ( myModule->getApp()->desktop()->inherits( "STD_TabDesktop" ) ) {
1845     STD_TabDesktop* d = dynamic_cast<STD_TabDesktop*>( myModule->getApp()->desktop() );
1846     if ( d )
1847       aWorkspace = d->workstack();
1848   }
1849 #if SIP_VERSION < 0x040800
1850   PyObjWrapper pyws( sipBuildResult( 0, "M", aWorkspace, sipClass_QWidget) );
1851 #else 
1852  #if SIP_VERSION >= 0x041303
1853   static const sipTypeDef *sipType_QWidget = 0;
1854   if (!sipType_QWidget)
1855     sipType_QWidget = sipFindType("QWidget");
1856  #endif
1857   PyObjWrapper pyws( sipBuildResult( 0, "D", aWorkspace, sipType_QWidget , NULL) );
1858 #endif
1859   // ... and finally call Python module's setWorkSpace() method (obsolete)
1860   if ( PyObject_HasAttrString( myPyModule, (char*)"setWorkSpace" ) ) {
1861     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setWorkSpace", (char*)"O", pyws.get() ) );
1862     if( !res ) {
1863       PyErr_Print();
1864     }
1865   }
1866 }
1867
1868 /*!
1869   \brief Initialization callback function
1870   \internal
1871
1872   Performs the following actions:
1873   - initialize or get the Python interpreter (one per study)
1874   - import the Python module
1875   - pass the workspace widget to the Python module
1876   - call Python module's initialize() method
1877   - call Python module's windows() method
1878   - call Python module's views() method
1879
1880   \param app parent application object
1881 */
1882 void PyModuleHelper::internalInitialize( CAM_Application* app )
1883 {
1884   FuncMsg fmsg( "--- PyModuleHelper::internalInitialize()" );
1885
1886   // reset interpreter to NULL
1887   myInterp = 0;
1888
1889   // get study Id
1890   LightApp_Application* anApp = dynamic_cast<LightApp_Application*>( app );
1891   if ( !anApp )
1892     return;
1893   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( app->activeStudy() );
1894   if ( !aStudy )
1895     return;
1896
1897   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
1898   initInterp();
1899   if ( !myInterp )
1900     return; // Error
1901
1902   // import Python GUI module
1903   importModule();
1904   if ( !myPyModule )
1905     return; // Error
1906
1907   // then call Python module's initialize() method
1908   // ... first get python lock
1909   PyLockWrapper aLock; // Acquire GIL
1910
1911   // ... (the Python module is already imported)
1912   // ... finally call Python module's initialize() method
1913   if ( PyObject_HasAttrString( myPyModule, (char*)"initialize" ) ) {
1914     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"initialize", (char*)"" ) );
1915     if ( !res ) {
1916       PyErr_Print();
1917     }
1918   }
1919
1920   // get required dockable windows list from the Python module 
1921   // by calling windows() method
1922   // ... first put default values
1923   myWindowsMap.insert( LightApp_Application::WT_ObjectBrowser, Qt::LeftDockWidgetArea );
1924   myWindowsMap.insert( LightApp_Application::WT_PyConsole,     Qt::BottomDockWidgetArea );
1925   myWindowsMap.insert( LightApp_Application::WT_LogWindow,     Qt::BottomDockWidgetArea );
1926
1927   if ( PyObject_HasAttrString( myPyModule , (char*)"windows" ) ) {
1928     PyObjWrapper res1( PyObject_CallMethod( myPyModule, (char*)"windows", (char*)"" ) );
1929     if ( !res1 ) {
1930       PyErr_Print();
1931     }
1932     else {
1933       myWindowsMap.clear();
1934       if ( PyDict_Check( res1 ) ) {
1935         PyObject* key;
1936         PyObject* value;
1937         Py_ssize_t pos = 0;
1938         while ( PyDict_Next( res1, &pos, &key, &value ) ) {
1939           // parse the return value
1940           // it should be a map: {integer:integer}
1941           int aKey, aValue;
1942           if( key && PyLong_Check( key ) && value && PyLong_Check( value ) ) {
1943             aKey   = PyLong_AsLong( key );
1944             aValue = PyLong_AsLong( value );
1945             myWindowsMap[ aKey ] = aValue;
1946           }
1947         }
1948       }
1949     }
1950   }
1951
1952   // get compatible view windows types from the Python module 
1953   // by calling views() method
1954   if ( PyObject_HasAttrString( myPyModule , (char*)"views" ) ) {
1955     PyObjWrapper res2( PyObject_CallMethod( myPyModule, (char*)"views", (char*)"" ) );
1956     if ( !res2 ) {
1957       PyErr_Print();
1958     }
1959     else {
1960       // parse the return value
1961       // result can be one string...
1962       if ( PyUnicode_Check( res2 ) ) {
1963         myViewMgrList.append( PyUnicode_AsUTF8( res2 ) );
1964       }
1965       // ... or list of strings
1966       else if ( PyList_Check( res2 ) ) {
1967         int size = PyList_Size( res2 );
1968         for ( int i = 0; i < size; i++ ) {
1969           PyObject* value = PyList_GetItem( res2, i );
1970           if( value && PyUnicode_Check( value ) ) {
1971             myViewMgrList.append( PyUnicode_AsUTF8( value ) );
1972           }
1973         }
1974       }
1975     }
1976   }
1977 }
1978
1979 /*!
1980   \brief Activation callback function
1981   \internal
1982
1983   Performs the following actions:
1984   - initialize or get the Python interpreter (one per study)
1985   - import the Python GUI module
1986   - call Python module's activate() method
1987
1988   \param study parent study
1989 */
1990 void PyModuleHelper::internalActivate( SUIT_Study* study )
1991 {
1992   FuncMsg fmsg( "--- PyModuleHelper::internalActivate()" );
1993
1994   // get study Id
1995   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
1996   if ( !aStudy )
1997     return;
1998
1999   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2000   initInterp();
2001   if ( !myInterp ) {
2002     myLastActivateStatus = false;
2003     return; // Error
2004   }
2005
2006   // import Python GUI module
2007   importModule();
2008   if ( !myPyModule ) {
2009     myLastActivateStatus = false;
2010     return; // Error
2011   }
2012
2013   // get python lock
2014   PyLockWrapper aLock; // Acquire GIL
2015
2016   // call Python module's activate() method (for the new modules)
2017   if ( PyObject_HasAttrString( myPyModule , (char*)"activate" ) ) {
2018     PyObject* res1 = PyObject_CallMethod( myPyModule, (char*)"activate", (char*)"" );
2019     if ( !res1 || !PyBool_Check( res1 ) ) {
2020       PyErr_Print();
2021       // always true for old modules (no return value)
2022       myLastActivateStatus = true;
2023     }
2024     else {
2025       // detect return status
2026       myLastActivateStatus = PyObject_IsTrue( res1 );
2027     }
2028   } 
2029 }
2030
2031 /*!
2032   \brief Additional menu customization callback function
2033   \internal
2034
2035   Performs the following actions:
2036   - get the Python interpreter (one per study)
2037   - import the Python GUI module
2038   - call Python module's setSettings() method (obsolete function, 
2039   used for compatibility with old code)
2040
2041   \param study parent study
2042 */
2043 void PyModuleHelper::internalCustomize( SUIT_Study* study )
2044 {
2045   FuncMsg fmsg( "--- PyModuleHelper::internalCustomize()" );
2046
2047   // get study Id
2048   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2049   if ( !aStudy )
2050     return;
2051
2052   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2053   initInterp();
2054   if ( !myInterp ) {
2055     myLastActivateStatus = false;
2056     return; // Error
2057   }
2058
2059   // import Python GUI module
2060   importModule();
2061   if ( !myPyModule ) {
2062     myLastActivateStatus = false;
2063     return; // Error
2064   }
2065
2066   // call Python module's setWorkSpace() method (obsolete)
2067   setWorkSpace();
2068
2069   // get python lock
2070   PyLockWrapper aLock; // Acquire GIL
2071
2072   if ( IsCallOldMethods ) {
2073     // call Python module's setSettings() method (obsolete)
2074     if ( PyObject_HasAttrString( myPyModule , (char*)"setSettings" ) ) {
2075       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"setSettings", (char*)"" ) );
2076       if( !res ) {
2077         PyErr_Print();
2078       }
2079       myLastActivateStatus = myLastActivateStatus && true;
2080     }
2081   }
2082 }
2083
2084 /*!
2085   \brief Deactivation callback function
2086   \internal
2087
2088   Performs the following actions:
2089   - call Python module's deactivate() method
2090
2091   \param study parent study
2092 */
2093 void PyModuleHelper::internalDeactivate( SUIT_Study* study )
2094 {
2095   FuncMsg fmsg( "--- PyModuleHelper::internalDeactivate()" );
2096
2097   // check that Python subinterpreter is initialized and Python module is imported
2098   if ( !myInterp || !myPyModule ) {
2099     // Error! Python subinterpreter should be initialized and module should be imported first!
2100     return;
2101   }
2102   // then call Python module's deactivate() method
2103   if ( PyObject_HasAttrString( myPyModule , (char*)"deactivate" ) ) {
2104     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"deactivate", (char*)"" ) );
2105     if( !res ) {
2106       PyErr_Print();
2107     }
2108   }
2109 }
2110
2111 /*!
2112   \brief Internal closure:
2113
2114   Performs the following actions:
2115   - call Python module's closeStudy() method
2116
2117   \param theStudy parent study object
2118 */
2119 void PyModuleHelper::internalClosedStudy( SUIT_Study* theStudy )
2120 {
2121   FuncMsg fmsg( "--- PyModuleHelper::internalClosedStudy()" );
2122
2123   // Get study Id
2124   // get study Id
2125   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( theStudy );
2126   if ( !aStudy )
2127     return;
2128
2129   // check that Python subinterpreter is initialized and Python module is imported
2130   if ( !myInterp || !myPyModule ) {
2131     // Error! Python subinterpreter should be initialized and module should be imported first!
2132     return;
2133   }
2134   // then call Python module's deactivate() method
2135   if ( PyObject_HasAttrString( myPyModule , (char*)"closeStudy" ) ) {
2136     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"closeStudy", (char*)"i" ) );
2137     if( !res ) {
2138       PyErr_Print();
2139     }
2140   }
2141 }
2142
2143
2144
2145 /*!
2146   \brief Preference changing callback function.
2147   \internal
2148
2149   Performs the following actions:
2150   - call Python module's preferenceChanged() method
2151
2152   \param section resources section name
2153   \param setting resources parameter name
2154 */
2155 void PyModuleHelper::internalPreferencesChanged( const QString& section, const QString& setting )
2156 {
2157   FuncMsg fmsg( "--- PyModuleHelper::internalPreferencesChanged()" );
2158
2159   // check that Python subinterpreter is initialized and Python module is imported
2160   if ( !myInterp || !myPyModule ) {
2161     // Error! Python subinterpreter should be initialized and module should be imported first!
2162     return;
2163   }
2164
2165   if ( PyObject_HasAttrString( myPyModule, (char*)"preferenceChanged" ) ) {
2166     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2167                                            (char*)"preferenceChanged", 
2168                                            (char*)"ss", 
2169                                            section.toLatin1().constData(), 
2170                                            setting.toLatin1().constData() ) );
2171     if( !res ) {
2172       PyErr_Print();
2173     }
2174   }
2175 }
2176
2177 /*!
2178   \brief Active study change callback function.
2179   \internal
2180
2181   Called when active the study is actived (user brings its 
2182   desktop to top):
2183   - initialize or get the Python interpreter (one per study)
2184   - import the Python GUI module
2185   - call Python module's activeStudyChanged() method
2186
2187   \param study study being activated
2188 */
2189 void PyModuleHelper::internalStudyChanged( SUIT_Study* study )
2190 {
2191   FuncMsg fmsg( "--- PyModuleHelper::internalStudyChanged()" );
2192
2193   // get study Id
2194   LightApp_Study* aStudy = dynamic_cast<LightApp_Study*>( study );
2195   if ( !aStudy )
2196     return;
2197
2198   // initialize Python subinterpreter (on per study) and put it in <myInterp> variable
2199   initInterp();
2200   if ( !myInterp )
2201     return; // Error
2202
2203   // import Python GUI module
2204   importModule();
2205   if ( !myPyModule )
2206     return; // Error
2207
2208   // call Python module's setWorkSpace() method
2209   setWorkSpace();
2210
2211   // get python lock
2212   PyLockWrapper aLock; // Acquire GIL
2213
2214   // call Python module's activeStudyChanged() method
2215   if ( PyObject_HasAttrString( myPyModule, (char*)"activeStudyChanged" ) ) {
2216     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeStudyChanged", (char*)"i" ) );
2217     if( !res ) {
2218       PyErr_Print();
2219     }
2220   }
2221 }
2222
2223 /*!
2224   \brief GUI event handling callback function
2225   \internal 
2226
2227   Performs the following actions:
2228   - calls Python module's OnGUIEvent() method
2229
2230   \param id GUI action ID
2231 */
2232 void PyModuleHelper::internalActionActivated( int id )
2233 {
2234   FuncMsg fmsg( "--- PyModuleHelper::internalActionActivated()" );
2235   fmsg.message( QString( "action id = %1" ).arg( id ) );
2236
2237   // Python interpreter should be initialized and Python module should be
2238   // import first
2239   if ( !myInterp || !myPyModule )
2240     return; // Error
2241
2242   if ( PyObject_HasAttrString( myPyModule, (char*)"OnGUIEvent" ) ) {
2243     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"OnGUIEvent", (char*)"i", id ) );
2244     if( !res ) {
2245       PyErr_Print();
2246     }
2247   }
2248 }
2249
2250 /*!
2251   \brief update selection from other views or modules
2252   \internal
2253
2254   Performs the following actions:
2255   - calls Python module's onSelectionpdated(entries) method
2256
2257   \param list of entries
2258 */
2259 void PyModuleHelper::internalSelectionUpdated(const QStringList& entries)
2260 {
2261   FuncMsg fmsg("--- PyModuleHelper::internalSelectionUpdated()");
2262   MESSAGE("internalSelectionUpdated");
2263
2264   // Python interpreter should be initialized and Python module should be imported first
2265   if (!myInterp || !myPyModule)
2266     return; // Error
2267
2268   QStringList* theList = new QStringList(entries);
2269
2270 #if SIP_VERSION < 0x040800
2271   PyObjWrapper sipList(sipBuildResult(0, "M", theList, sipClass_QStringList));
2272 #else
2273  #if SIP_VERSION >= 0x041303
2274   static const sipTypeDef *sipType_QStringList = 0;
2275   if (!sipType_QStringList)
2276     sipType_QStringList = sipFindType("QStringList");
2277  #endif
2278   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL ) );
2279 #endif
2280   if (PyObject_HasAttrString(myPyModule, (char*) "onSelectionUpdated"))
2281     {
2282       MESSAGE("call onSelectionUpdated");
2283       PyObjWrapper res(PyObject_CallMethod(myPyModule, (char*) "onSelectionUpdated", (char*) "O", sipList.get()));
2284
2285       if (!res)
2286         {
2287           PyErr_Print();
2288         }
2289     }
2290 }
2291
2292 /*!
2293   \brief Context popup menu handling callback function
2294   \internal
2295
2296   Performs the following actions:
2297   - calls Python module's definePopup(...) method (obsolete function, 
2298   used for compatibility with old code) to define the popup menu context
2299   - parses XML resourses file (if exists) and fills the popup menu with the items)
2300   - calls Python module's customPopup(...) method (obsolete function, 
2301   used for compatibility with old code) to allow module to customize the popup menu
2302   - for new modules calls createPopupMenu() function to allow the 
2303   modules to build the popup menu by using insertItem(...) Qt functions.
2304
2305   \param context popup menu context
2306   \param menu popup menu
2307 */
2308 void PyModuleHelper::internalContextMenu( const QString& context, QMenu* menu )
2309 {
2310   FuncMsg fmsg( "--- PyModuleHelper::internalContextMenu()" );
2311   fmsg.message( QString( "context: %1" ).arg( context ) );
2312
2313   // Python interpreter should be initialized and Python module should be
2314   // import first
2315   if ( !myInterp || !myPyModule )
2316     return; // Error
2317
2318   QString aContext( "" ), aObject( "" ), aParent( context );
2319
2320   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"definePopup" ) ) {
2321     // call definePopup() Python module's function
2322     // this is obsolete function, used only for compatibility reasons
2323     PyObjWrapper res( PyObject_CallMethod( myPyModule,
2324                                            (char*)"definePopup",
2325                                            (char*)"sss",
2326                                            context.toLatin1().constData(),
2327                                            aObject.toLatin1().constData(),
2328                                            aParent.toLatin1().constData() ) );
2329     if( !res ) {
2330       PyErr_Print();
2331     }
2332     else {
2333       // parse return value
2334       char *co, *ob, *pa;
2335       if( PyArg_ParseTuple( res, "sss", &co, &ob, &pa ) ) {
2336         aContext = co;
2337         aObject  = ob;
2338         aParent  = pa;
2339       }
2340     }
2341   } // if ( IsCallOldMethods ... )
2342
2343   // first try to create menu via XML parser:
2344   // we create popup menus without help of QtxPopupMgr
2345   if ( myXmlHandler )
2346     myXmlHandler->createPopup( menu, aContext, aParent, aObject );
2347
2348 #if SIP_VERSION < 0x040800
2349   PyObjWrapper sipPopup( sipBuildResult( 0, "M", menu, sipClass_QMenu ) );
2350 #else
2351  #if SIP_VERSION >= 0x041303
2352   static const sipTypeDef *sipType_QMenu = 0;
2353   if (!sipType_QMenu)
2354     sipType_QMenu = sipFindType("QMenu");
2355  #endif
2356   PyObjWrapper sipPopup( sipBuildResult( 0, "D", menu, sipType_QMenu, NULL ) );
2357 #endif
2358
2359   // then call Python module's createPopupMenu() method (for new modules)
2360   if ( PyObject_HasAttrString( myPyModule, (char*)"createPopupMenu" ) ) {
2361     PyObjWrapper res1( PyObject_CallMethod( myPyModule,
2362                                             (char*)"createPopupMenu",
2363                                             (char*)"Os",
2364                                             sipPopup.get(),
2365                                             context.toLatin1().constData() ) );
2366     if( !res1 ) {
2367       PyErr_Print();
2368     }
2369   }
2370
2371   if ( IsCallOldMethods && PyObject_HasAttrString( myPyModule, (char*)"customPopup" ) ) {
2372     // call customPopup() Python module's function
2373     // this is obsolete function, used only for compatibility reasons
2374     PyObjWrapper res2( PyObject_CallMethod( myPyModule,
2375                                             (char*)"customPopup",
2376                                             (char*)"Osss",
2377                                             sipPopup.get(),
2378                                             aContext.toLatin1().constData(),
2379                                             aObject.toLatin1().constData(),
2380                                             aParent.toLatin1().constData() ) );
2381     if( !res2 ) {
2382       PyErr_Print();
2383     }
2384   }
2385 }
2386
2387 /*!
2388   \brief Preferences initialization callback function.
2389   \internal
2390
2391   Performs the following actions:
2392   - calls Python module's createPreferences() method
2393 */
2394 void PyModuleHelper::internalCreatePreferences()
2395 {
2396   FuncMsg fmsg( "--- PyModuleHelper::internalCreatePreferences()" );
2397
2398   // Python interpreter should be initialized and Python module should be
2399   // import first
2400   if ( !myInterp || !myPyModule )
2401     return; // Error
2402
2403   if ( PyObject_HasAttrString( myPyModule, (char*)"createPreferences" ) ) {
2404     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"createPreferences", (char*)"" ) );
2405     if( !res ) {
2406       PyErr_Print();
2407     }
2408   }
2409 }
2410
2411 /*!
2412   \brief Active view changing callback function
2413   \internal
2414   \param view view being activated
2415 */
2416 void PyModuleHelper::internalActiveViewChanged( SUIT_ViewWindow* view )
2417 {
2418   FuncMsg fmsg( "--- PyModuleHelper::internalActiveViewChanged()" );
2419
2420   if ( !myInterp || !myPyModule || !view ) 
2421     return;
2422   
2423   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2424
2425   if ( PyObject_HasAttrString( myPyModule, (char*)"activeViewChanged" ) ) {
2426     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"activeViewChanged", (char*)"i" , view->getId() ) );
2427     if ( !res ) {
2428       PyErr_Print();
2429     }
2430   }
2431 }
2432
2433 /*!
2434   \brief View closing callback function
2435   \internal
2436   \param view view user tries to close
2437 */
2438 void PyModuleHelper::internalTryCloseView( SUIT_ViewWindow* view )
2439 {
2440   FuncMsg fmsg( "--- PyModuleHelper::internalTryCloseView()" );
2441
2442   if ( !myInterp || !myPyModule || !view ) 
2443     return;  
2444
2445   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2446
2447   if ( PyObject_HasAttrString( myPyModule, (char*)"viewTryClose" ) ) 
2448   {
2449     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewTryClose", (char*)"i", view->getId() ) );
2450     if ( !res )
2451     {
2452       PyErr_Print();
2453     }
2454   }
2455 }
2456
2457 /*!
2458   \brief View closing callback function
2459   \internal
2460   \param view view being closed
2461 */
2462 void PyModuleHelper::internalCloseView( SUIT_ViewWindow* view )
2463 {
2464   FuncMsg fmsg( "--- PyModuleHelper::internalCloseView()" );
2465
2466   if ( !myInterp || !myPyModule || !view ) 
2467     return;  
2468
2469   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2470
2471   if ( PyObject_HasAttrString( myPyModule, (char*)"viewClosed" ) ) 
2472   {
2473     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewClosed", (char*)"i", view->getId() ) );
2474     if ( !res )
2475     {
2476       PyErr_Print();
2477     }
2478   }
2479 }
2480
2481 /*!
2482   \brief View cloning callback function
2483   \internal
2484   \param view view being cloned
2485 */
2486 void PyModuleHelper::internalCloneView( SUIT_ViewWindow* view )
2487 {
2488   FuncMsg fmsg( "--- PyModuleHelper::internalCloneView()" );
2489
2490   if ( !myInterp || !myPyModule || !view ) 
2491     return;  
2492
2493   fmsg.message( QString( "view id: %1" ).arg( view->getId() ) );
2494
2495   if ( PyObject_HasAttrString( myPyModule, (char*)"viewCloned" ) ) 
2496   {
2497     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"viewCloned", (char*)"i", view->getId() ) );
2498     if( !res )
2499       PyErr_Print();
2500   }
2501 }
2502
2503 /*!
2504   \brief Module data saving callback function.
2505   \internal
2506   \param files output list of files where module stores data
2507   \param url study URL
2508 */
2509 void PyModuleHelper::internalSave( QStringList& files, const QString& url )
2510 {
2511   FuncMsg fmsg( "--- PyModuleHelper::internalSave()" );
2512
2513   // Python interpreter should be initialized and Python module should be
2514   // import firs
2515   // files list should contain a path to the temporary directory as a first entry
2516   if ( !myInterp || !myPyModule || files.isEmpty() )
2517     return;
2518
2519   if ( PyObject_HasAttrString(myPyModule, (char*)"saveFiles") ) {
2520
2521     // try with two parameters (new syntax)
2522     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"saveFiles",
2523                                            (char*)"ss",
2524                                            files.first().toLatin1().constData(),
2525                                            url.toLatin1().constData() ) );
2526     if ( !res )
2527       // try with single parameter (old syntax)
2528       res = PyObject_CallMethod( myPyModule, (char*)"saveFiles",
2529                                  (char*)"s", files.first().toLatin1().constData() );
2530     
2531     if ( !res ) {
2532       PyErr_Print();
2533     }
2534     else {
2535       // parse the return value
2536       // result can be one string...
2537       if ( PyUnicode_Check( res ) ) {
2538         QString astr = PyUnicode_AsUTF8( res );
2539         files.append( astr );
2540       }
2541       //also result can be a list...
2542       else if ( PyList_Check( res ) ) {
2543         int size = PyList_Size( res );
2544         for ( int i = 0; i < size; i++ ) {
2545           PyObject* value = PyList_GetItem( res, i );
2546           if ( value && PyUnicode_Check( value ) ) {
2547             files.append( PyUnicode_AsUTF8( value ) );
2548           }
2549         }
2550       }
2551     }
2552   }
2553 }
2554
2555 /*!
2556   \brief Module data loading callback function.
2557   \internal
2558   \param files list of files where module data is stored
2559   \param url study URL
2560   \param opened output success flag
2561 */
2562 void PyModuleHelper::internalLoad( const QStringList& files, const QString& url, bool& opened )
2563 {
2564   FuncMsg fmsg( "--- PyModuleHelper::internalLoad()" );
2565
2566   // Python interpreter should be initialized and Python module should be
2567   // import first
2568   if ( !myInterp || !myPyModule || files.isEmpty() )
2569     return;
2570
2571   QStringList* theList = new QStringList( files );
2572
2573 #if SIP_VERSION < 0x040800
2574   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList ) );
2575 #else
2576  #if SIP_VERSION >= 0x041303
2577   static const sipTypeDef *sipType_QStringList = 0;
2578   if (!sipType_QStringList)
2579     sipType_QStringList = sipFindType("QStringList");
2580  #endif
2581   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL ) );
2582 #endif
2583   if ( PyObject_HasAttrString(myPyModule , (char*)"openFiles") ) {
2584
2585     // try with two parameters (new syntax)
2586     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"openFiles",
2587                                            (char*)"Os", sipList.get(),
2588                                            url.toLatin1().constData() ) );
2589
2590     if ( !res )
2591       // try with single parameter (old syntax)
2592       res = PyObject_CallMethod( myPyModule, (char*)"openFiles",
2593                                  (char*)"O", sipList.get() );
2594
2595     if ( !res || !PyBool_Check( res ) ) {
2596       PyErr_Print();
2597       opened = false;
2598     }
2599     else {
2600       opened = PyObject_IsTrue( res );
2601     }
2602   }
2603 }
2604
2605 /*!
2606   \brief Module dump python callback function.
2607   \internal
2608   \param files output list of files where module stores python script
2609 */
2610 void PyModuleHelper::internalDumpPython( QStringList& files )
2611 {
2612   FuncMsg fmsg( "--- PyModuleHelper::internalDumpPython()" );
2613
2614   // Python interpreter should be initialized and Python module should be
2615   // import first
2616   // files list should contain a path to the temporary directory
2617   if ( !myInterp || !myPyModule || files.isEmpty() )
2618     return;
2619
2620   if ( PyObject_HasAttrString(myPyModule, (char*)"dumpStudy") ) {
2621     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dumpStudy",
2622                                            (char*)"s", files.first().toLatin1().constData()));
2623
2624     if ( !res ) {
2625       PyErr_Print();
2626     }
2627     else {
2628       // parse the return value
2629       // result can be one string...
2630       if ( PyUnicode_Check( res ) ) {
2631         QString astr = PyUnicode_AsUTF8( res );
2632         //SCRUTE(astr);
2633         files.append(astr);
2634       }
2635       //also result can be a list...
2636       else if ( PyList_Check( res ) ) {
2637         int size = PyList_Size( res );
2638         for ( int i = 0; i < size; i++ ) {
2639           PyObject* value = PyList_GetItem( res, i );
2640           if( value && PyUnicode_Check( value ) ) {
2641             files.append( PyUnicode_AsUTF8( value ) );
2642           }
2643         }
2644       }
2645     }
2646   }
2647 }
2648
2649 /*!
2650   \brief Check data object's 'draggable' status callback function.
2651   \internal
2652   \param what data object being tested
2653   \return \c true if object can be dragged or \c false otherwise
2654 */
2655 bool PyModuleHelper::internalIsDraggable( LightApp_DataObject* what )
2656 {
2657   FuncMsg fmsg( "--- PyModuleHelper::internalIsDraggable()" );
2658
2659   // Python interpreter should be initialized and Python module should be
2660   // import first
2661   if ( !myInterp || !myPyModule || !what )
2662     return false;
2663
2664   bool draggable = false;
2665
2666   if ( PyObject_HasAttrString(myPyModule , (char*)"isDraggable") ) {
2667     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDraggable",
2668                       (char*)"s", what->entry().toLatin1().constData() ) );
2669     if( !res || !PyBool_Check( res )) {
2670       PyErr_Print();
2671       draggable = false;
2672     }
2673     else{
2674       draggable = PyObject_IsTrue( res );
2675     }
2676   }
2677
2678   return draggable;
2679 }
2680
2681 /*!
2682   \brief Check data object's 'drop allowed' status callback function.
2683   \internal
2684   \param where data object being tested
2685   \return \c true if if drop operation is supported by object or \c false otherwise
2686 */
2687 bool PyModuleHelper::internalIsDropAccepted( LightApp_DataObject* where )
2688 {
2689   FuncMsg fmsg( "--- PyModuleHelper::internalIsDropAccepted()" );
2690
2691   // Python interpreter should be initialized and Python module should be
2692   // import first
2693   if ( !myInterp || !myPyModule || !where )
2694     return false;
2695
2696   bool dropAccepted = false;
2697
2698   if ( PyObject_HasAttrString(myPyModule , (char*)"isDropAccepted") ) {
2699     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"isDropAccepted",
2700                       (char*)"s", where->entry().toLatin1().constData() ) );
2701     if( !res || !PyBool_Check( res )) {
2702       PyErr_Print();
2703       dropAccepted = false;
2704     }
2705     else{
2706       dropAccepted = PyObject_IsTrue( res );
2707     }
2708   }
2709
2710   return dropAccepted;
2711 }
2712
2713 /*!
2714   \brief Data dropping callback function.
2715   \internal
2716   \param what list of data objects being dropped
2717   \param where target data object for drop operation
2718   \param row line (child item index) where drop operation is performed to
2719   \param action current drop action (copy or move)
2720 */
2721 void PyModuleHelper::internalDropObjects( const DataObjectList& what, SUIT_DataObject* where,
2722                                           const int row, Qt::DropAction action )
2723 {
2724   FuncMsg fmsg( "--- PyModuleHelper::internalDropObjects()" );
2725
2726   // Python interpreter should be initialized and Python module should be
2727   // import first
2728   if ( !myInterp || !myPyModule || what.isEmpty() || !where )
2729     return;
2730
2731   QStringList* theList = new QStringList();
2732
2733   LightApp_DataObject* whereObject = dynamic_cast<LightApp_DataObject*>( where );
2734   if ( !whereObject ) return;
2735   
2736   for ( int i = 0; i < what.count(); i++ ) {
2737     LightApp_DataObject* dataObject = dynamic_cast<LightApp_DataObject*>( what[i] );
2738     if ( dataObject ) theList->append( dataObject->entry() );
2739   }
2740
2741 #if SIP_VERSION < 0x040800
2742   PyObjWrapper sipList( sipBuildResult( 0, "M", theList, sipClass_QStringList) );
2743 #else
2744  #if SIP_VERSION >= 0x041303
2745   static const sipTypeDef *sipType_QStringList = 0;
2746   if (!sipType_QStringList)
2747     sipType_QStringList = sipFindType("QStringList");
2748  #endif
2749   PyObjWrapper sipList( sipBuildResult( 0, "D", theList, sipType_QStringList, NULL) );
2750 #endif
2751   if ( PyObject_HasAttrString(myPyModule, (char*)"dropObjects") ) {
2752       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"dropObjects", (char*)"Osii",
2753                         sipList.get(),
2754                         whereObject->entry().toLatin1().constData(),
2755                         row, action ) );
2756     
2757     if( !res ) {
2758       PyErr_Print();
2759     }
2760   }
2761 }
2762
2763 /*!
2764   \brief Get engine IOR callback function
2765   \internal
2766   
2767   Tries to get engine IOR from the Python module using engineIOR() function.
2768   That function can load module engine using appropriate container if required.
2769
2770   \return engine IOR or empty string if it is not provided by Python module 
2771 */
2772 QString PyModuleHelper::internalEngineIOR() const
2773 {
2774   FuncMsg fmsg( "--- PyModuleHelper::internalEngineIOR()" );
2775
2776   QString ior;
2777
2778   // Python interpreter should be initialized and Python module should be
2779   // import first
2780   if ( myInterp && myModule ) {
2781     if ( PyObject_HasAttrString( myPyModule , "engineIOR" ) ) {
2782       PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"engineIOR", (char*)"" ) );
2783       if ( !res ) {
2784           PyErr_Print();
2785       }
2786       else {
2787         // parse the return value, result chould be string
2788         if ( PyUnicode_Check( res ) ) {
2789           ior = PyUnicode_AsUTF8( res );
2790         }
2791       }
2792     }
2793   }
2794   return ior;
2795 }
2796
2797 /*!
2798   \brief Connects signals about activating and cloning view on internal slots
2799   \param view view being connected
2800 */
2801 void PyModuleHelper::connectView( SUIT_ViewWindow* view )
2802 {
2803   SUIT_ViewManager* viewMgr = view->getViewManager();
2804   SUIT_ViewModel* viewModel = viewMgr ? viewMgr->getViewModel() : 0;
2805       
2806   // Connect tryCloseView() and deleteView() signals
2807   if ( viewMgr ) {
2808     connect( viewMgr, SIGNAL( tryCloseView( SUIT_ViewWindow* ) ),
2809              this, SLOT( tryCloseView( SUIT_ViewWindow* ) ),
2810              Qt::UniqueConnection );
2811     connect( viewMgr, SIGNAL( deleteView( SUIT_ViewWindow* ) ),
2812              this, SLOT( closeView( SUIT_ViewWindow* ) ),
2813              Qt::UniqueConnection );
2814   }
2815   
2816   // Connect cloneView() signal of an OCC View
2817   if ( view->inherits( "OCCViewer_ViewWindow" ) ) {
2818     connect( view, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2819              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2820              Qt::UniqueConnection );
2821   }
2822   // Connect cloneView() signal of Plot2d View 
2823   else if ( viewModel && viewModel->inherits( "Plot2d_Viewer" ) ) {
2824     connect( viewModel, SIGNAL( viewCloned( SUIT_ViewWindow* ) ), 
2825              this, SLOT( cloneView( SUIT_ViewWindow* ) ),
2826              Qt::UniqueConnection );
2827   }
2828 }
2829
2830
2831
2832 void PyModuleHelper::internalOBClickedPython( const QString& theObj, int theColumn)
2833 {
2834   FuncMsg fmsg( "--- PyModuleHelper::internalOBClickedPython()" );
2835
2836   // Python interpreter should be initialized and Python module should be
2837   // import first
2838   if ( !myInterp || !myPyModule )
2839     return; // Error
2840
2841   if ( PyObject_HasAttrString( myPyModule, (char*)"onObjectBrowserClicked" ) ) {
2842     PyObjWrapper res( PyObject_CallMethod( myPyModule, (char*)"onObjectBrowserClicked", (char*)"si", theObj.toLatin1().constData(), theColumn ) );
2843     if( !res ) {
2844       PyErr_Print();
2845     }
2846   }
2847 }
2848
2849
2850
2851 void PyModuleHelper::onObjectBrowserClicked(SUIT_DataObject* theObj, int theColumn)
2852 {
2853   FuncMsg fmsg( "PyModuleHelper::onObjectBrowserClicked()" );
2854
2855   // temporary set myInitModule because dumpPython() method
2856   // might be called by the framework when this module is inactive,
2857   // but still it should be possible to access this module's data
2858   // from Python
2859   InitLocker lock( myModule );
2860
2861   class PythonReq: public PyInterp_LockRequest
2862   {
2863   public:     
2864     PythonReq( PyInterp_Interp* _py_interp,
2865                PyModuleHelper*  _helper,
2866                const QString& _entry,
2867                int     _column )
2868       : PyInterp_LockRequest( _py_interp, 0, true ), // this request should be processed synchronously (sync == true)
2869         myHelper( _helper ) ,
2870         myEntry( _entry ),
2871         myColumn( _column )
2872     {}
2873   protected:
2874     virtual void execute()
2875     {
2876       myHelper->internalOBClickedPython( myEntry, myColumn );
2877     }
2878   private:
2879     PyModuleHelper* myHelper;
2880     int    myColumn;
2881     QString myEntry;
2882   };
2883   
2884   // Posting the request only if dispatcher is not busy!
2885   // Executing the request synchronously
2886   const LightApp_DataObject* data_object = dynamic_cast<const LightApp_DataObject*>( theObj );
2887   if ( (!PyInterp_Dispatcher::Get()->IsBusy()) && data_object )
2888     PyInterp_Dispatcher::Get()->Exec( new PythonReq( myInterp, this, data_object->entry(), theColumn ) );
2889 }
2890