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