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