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