Salome HOME
Integrates the python editor as new SALOME viewer. Writes comments. Designs icons... V7_6_0a1
authormgn <mgn@opencascade.com>
Tue, 31 Mar 2015 16:52:45 +0000 (19:52 +0300)
committerrnv <rnv@opencascade.com>
Fri, 3 Apr 2015 11:56:38 +0000 (14:56 +0300)
50 files changed:
CMakeLists.txt
SalomeGUIConfig.cmake.in
src/CMakeLists.txt
src/LightApp/CMakeLists.txt
src/LightApp/LightApp_Application.cxx
src/LightApp/LightApp_Application.h
src/LightApp/resources/LightApp.xml
src/LightApp/resources/LightApp_msg_en.ts
src/PyEditor/CMakeLists.txt [new file with mode: 0644]
src/PyEditor/PyEditor.h [new file with mode: 0644]
src/PyEditor/PyEditor_Editor.cxx [new file with mode: 0644]
src/PyEditor/PyEditor_Editor.h [new file with mode: 0644]
src/PyEditor/PyEditor_LineNumberArea.cxx [new file with mode: 0644]
src/PyEditor/PyEditor_LineNumberArea.h [new file with mode: 0644]
src/PyEditor/PyEditor_PyHighlighter.cxx [new file with mode: 0644]
src/PyEditor/PyEditor_PyHighlighter.h [new file with mode: 0644]
src/PyEditor/PyEditor_Settings.cxx [new file with mode: 0644]
src/PyEditor/PyEditor_Settings.h [new file with mode: 0644]
src/PyEditor/PyEditor_SettingsDlg.cxx [new file with mode: 0644]
src/PyEditor/PyEditor_SettingsDlg.h [new file with mode: 0644]
src/PyEditor/resources/translations/PyEditor_msg_en.ts [new file with mode: 0644]
src/PyEditor/resources/translations/PyEditor_msg_fr.ts [new file with mode: 0644]
src/PyEditor/resources/translations/PyEditor_msg_ja.ts [new file with mode: 0644]
src/PyViewer/CMakeLists.txt [new file with mode: 0644]
src/PyViewer/PyViewer.cxx [new file with mode: 0644]
src/PyViewer/PyViewer.h [new file with mode: 0644]
src/PyViewer/PyViewer_ViewManager.cxx [new file with mode: 0644]
src/PyViewer/PyViewer_ViewManager.h [new file with mode: 0644]
src/PyViewer/PyViewer_ViewModel.cxx [new file with mode: 0644]
src/PyViewer/PyViewer_ViewModel.h [new file with mode: 0644]
src/PyViewer/PyViewer_ViewWindow.cxx [new file with mode: 0644]
src/PyViewer/PyViewer_ViewWindow.h [new file with mode: 0644]
src/PyViewer/resources/PyEditor.qrc [new file with mode: 0644]
src/PyViewer/resources/images/py_browser.png [new file with mode: 0644]
src/PyViewer/resources/images/py_close.png [new file with mode: 0644]
src/PyViewer/resources/images/py_copy.png [new file with mode: 0644]
src/PyViewer/resources/images/py_cut.png [new file with mode: 0644]
src/PyViewer/resources/images/py_delete.png [new file with mode: 0644]
src/PyViewer/resources/images/py_new.png [new file with mode: 0644]
src/PyViewer/resources/images/py_open.png [new file with mode: 0644]
src/PyViewer/resources/images/py_paste.png [new file with mode: 0644]
src/PyViewer/resources/images/py_preferences.png [new file with mode: 0644]
src/PyViewer/resources/images/py_redo.png [new file with mode: 0644]
src/PyViewer/resources/images/py_save.png [new file with mode: 0644]
src/PyViewer/resources/images/py_save_as.png [new file with mode: 0644]
src/PyViewer/resources/images/py_select_all.png [new file with mode: 0644]
src/PyViewer/resources/images/py_undo.png [new file with mode: 0644]
src/PyViewer/resources/translations/PyViewer_msg_en.ts [new file with mode: 0644]
src/PyViewer/resources/translations/PyViewer_msg_fr.ts [new file with mode: 0644]
src/PyViewer/resources/translations/PyViewer_msg_ja.ts [new file with mode: 0644]

index 0d83d6f62429effe4f4fbd8356b7be928202c2a5..5eb8ac30d81a9ca14b453e4c8118cbcd4944bafe 100755 (executable)
@@ -70,6 +70,7 @@ OPTION(SALOME_USE_VTKVIEWER "Enable VTK visualization (Mandatory in classic conf
 OPTION(SALOME_USE_OCCVIEWER "Enable OCC visualization (Mandatory in classic configurations)" ON)
 OPTION(SALOME_USE_GLVIEWER "Enable OpenGL visualization (Mandatory in classic configurations)" ON)
 OPTION(SALOME_USE_GRAPHICSVIEW "Enable GraphicsView visualization (Mandatory in classic configurations)" ON)
+OPTION(SALOME_USE_PYVIEWER "Enable Python viewer (Mandatory in classic configurations)" ON)
 OPTION(SALOME_USE_PLOT2DVIEWER "Enable Plot2D visualization (Mandatory in classic configurations)" ON)
 OPTION(SALOME_USE_PYCONSOLE "Enable Python GUI interface (Mandatory in classic configurations)" ON)
 OPTION(SALOME_USE_QXGRAPHVIEWER "Enable QX graph visualization (Mandatory in classic configurations)" ON)
@@ -80,7 +81,7 @@ OPTION(SALOME_USE_SINGLE_DESKTOP "Enable multiple document interface" ON)
 
 MARK_AS_ADVANCED(SALOME_LIGHT_ONLY SALOME_USE_VTKVIEWER SALOME_USE_GRAPHICSVIEW SALOME_USE_PVVIEWER)
 MARK_AS_ADVANCED(SALOME_USE_SALOMEOBJECT SALOME_USE_OCCVIEWER SALOME_USE_GLVIEWER SALOME_USE_PLOT2DVIEWER)
-MARK_AS_ADVANCED(SALOME_USE_PYCONSOLE SALOME_USE_QXGRAPHVIEWER)
+MARK_AS_ADVANCED(SALOME_USE_PYCONSOLE SALOME_USE_QXGRAPHVIEWER SALOME_USE_PYVIEWER)
 MARK_AS_ADVANCED(SALOME_USE_SINGLE_DESKTOP)
 
 # Prerequisites
@@ -179,6 +180,10 @@ IF (NOT SALOME_USE_GRAPHICSVIEW)
   ADD_DEFINITIONS("-DDISABLE_GRAPHICSVIEW")
 ENDIF()
 
+IF (NOT SALOME_USE_PYVIEWER)
+  ADD_DEFINITIONS("-DDISABLE_PYVIEWER")
+ENDIF()
+
 IF(SALOME_USE_PYCONSOLE)
   # Build with obsolete Python module's methods
   ADD_DEFINITIONS(-DCALL_OLD_METHODS)
@@ -331,6 +336,12 @@ IF(SALOME_USE_GRAPHICSVIEW)
        GraphicsView)
 ENDIF(SALOME_USE_GRAPHICSVIEW)
 
+# PyEditor/Viewer specific targets:
+IF(SALOME_USE_PYVIEWER)
+  LIST(APPEND _${PROJECT_NAME}_exposed_targets 
+       PyEditor PyViewer)
+ENDIF(SALOME_USE_PYVIEWER)
+
 # ParaView viewer specific targets:
 IF(SALOME_USE_PVVIEWER)
   LIST(APPEND _${PROJECT_NAME}_exposed_targets PVViewer)
index 7dcc4ab2d9ac9e2b7c46ff12da4fee79d87c0a15..c7ea0b97ae9e0d643b899245551339ea666736b1 100644 (file)
@@ -59,6 +59,7 @@ SET(SALOME_USE_PLOT2DVIEWER   @SALOME_USE_PLOT2DVIEWER@)
 SET(SALOME_USE_GRAPHICSVIEW   @SALOME_USE_GRAPHICSVIEW@)
 SET(SALOME_USE_QXGRAPHVIEWER  @SALOME_USE_QXGRAPHVIEWER@)
 SET(SALOME_USE_PVVIEWER       @SALOME_USE_PVVIEWER@)
+SET(SALOME_USE_PYVIEWER       @SALOME_USE_PYVIEWER@)
 SET(SALOME_USE_PYCONSOLE      @SALOME_USE_PYCONSOLE@)
 SET(SALOME_USE_SALOMEOBJECT   @SALOME_USE_SALOMEOBJECT@)
 SET(SALOME_USE_SINGLE_DESKTOP @SALOME_USE_SINGLE_DESKTOP@)
@@ -92,6 +93,9 @@ ENDIF()
 IF (NOT SALOME_USE_PVVIEWER)
   LIST(APPEND GUI_DEFINITIONS "-DDISABLE_PVVIEWER")
 ENDIF()
+IF(NOT SALOME_USE_PYVIEWER)
+  LIST(APPEND GUI_DEFINITIONS "-DDISABLE_PYVIEWER")
+ENDIF()
 IF(NOT SALOME_USE_PYCONSOLE)
   LIST(APPEND GUI_DEFINITIONS "-DDISABLE_PYCONSOLE")
 ENDIF()
@@ -188,6 +192,8 @@ SET(GUI_OpenGLUtils OpenGLUtils)
 SET(GUI_Plot2d Plot2d)
 SET(GUI_PyConsole PyConsole)
 SET(GUI_PyInterp PyInterp)
+SET(GUI_PyEditor PyEditor)
+SET(GUI_PyViewer PyViewer)
 SET(GUI_QDS QDS)
 SET(GUI_qtx qtx)
 SET(GUI_QxScene QxScene)
index 1d09d2d5b12893ab7e966075ff27c14ec8f3539b..f3d3f8aab79d925df03f258455e7a0c760f6a6e9 100755 (executable)
@@ -90,6 +90,13 @@ IF(SALOME_USE_PVVIEWER)
   SET(SUBDIRS_PVVIEWER PVViewer)
 ENDIF()
 
+##
+# Python Viewer
+##
+IF(SALOME_USE_PYVIEWER)
+  SET(SUBDIRS_PYVIEWER PyEditor PyViewer)
+ENDIF(SALOME_USE_PYVIEWER)
+
 
 ##
 # Python-based packages
@@ -127,6 +134,7 @@ SET(SUBDIRS
   ${SUBDIRS_QXGRAPHVIEWER}
   ${SUBDIRS_PVVIEWER}
   ${SUBDIRS_GRAPHICSVIEW}
+  ${SUBDIRS_PYVIEWER}
   ${SUBDIRS_PYCONSOLE}
   ${SUBDIRS_LIGHT}
   ${SUBDIRS_CORBA}
index 43e7b816e9af1c746187f5a5d89148958d621789..c295aa78f5266837e746f3de3dd2caf03c2d5c14 100755 (executable)
@@ -48,6 +48,10 @@ ENDIF()
 IF(SALOME_USE_GRAPHICSVIEW)
   INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/GraphicsView)
 ENDIF()
+IF(SALOME_USE_PYVIEWER)
+  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/PyEditor)
+  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/PyViewer)
+ENDIF()
 IF(SALOME_USE_OCCVIEWER)
   INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/OCCViewer)
   IF(SALOME_USE_SALOMEOBJECT)
@@ -109,6 +113,9 @@ ENDIF()
 IF(SALOME_USE_GRAPHICSVIEW)
   LIST(APPEND _link_LIBRARIES GraphicsView)
 ENDIF()
+IF(SALOME_USE_PYVIEWER)
+  LIST(APPEND _link_LIBRARIES PyEditor PyViewer)
+ENDIF()
 IF(SALOME_USE_OCCVIEWER)
   LIST(APPEND _link_LIBRARIES OCCViewer)
   IF(SALOME_USE_SALOMEOBJECT)
index b156836b3ee4df4a018b936c1d482dd09d130a4c..246a8ab9e625343d06a75a90f31db08c710dd8a9 100644 (file)
   #include "PVViewer_ViewModel.h"
 #endif
 
+#ifndef DISABLE_PYVIEWER
+  #include <PyViewer_ViewManager.h>
+  #include <PyViewer_ViewModel.h>
+  #include <PyViewer_ViewWindow.h>
+#endif
+
 
 #define VISIBILITY_COLUMN_WIDTH 25
 
@@ -750,6 +756,9 @@ void LightApp_Application::createActions()
 #ifndef DISABLE_PVVIEWER
   createActionForViewer( NewPVViewId, newWinMenu, QString::number( 6 ), Qt::ALT+Qt::Key_A );
 #endif
+#ifndef DISABLE_PYVIEWER
+  createActionForViewer( NewPyViewerId, newWinMenu, QString::number( 7 ), Qt::ALT+Qt::Key_Y );
+#endif
 
   createAction( RenameId, tr( "TOT_RENAME" ), QIcon(), tr( "MEN_DESK_RENAME" ), tr( "PRP_RENAME" ),
                 Qt::ALT+Qt::SHIFT+Qt::Key_R, desk, false, this, SLOT( onRenameWindow() ) );
@@ -874,6 +883,11 @@ void LightApp_Application::onNewWindow()
   case NewPVViewId:
     type = PVViewer_Viewer::Type();
     break;
+#endif
+#ifndef DISABLE_PYVIEWER
+  case NewPyViewerId:
+    type = PyViewer_Viewer::Type();
+    break;
 #endif
   }
 
@@ -1025,6 +1039,12 @@ void LightApp_Application::updateCommandsStatus()
   if( a )
     a->setEnabled( activeStudy() );
 #endif
+
+#ifndef DISABLE_PYVIEWER
+  a = action( NewPyViewerId );
+  if( a )
+    a->setEnabled( activeStudy() );
+#endif
 }
 
 /*!
@@ -1472,6 +1492,12 @@ SUIT_ViewManager* LightApp_Application::createViewManager( const QString& vmType
     }
   }
 #endif
+#ifndef DISABLE_PYVIEWER
+  if( vmType == PyViewer_Viewer::Type() )
+  {
+    viewMgr = new PyViewer_ViewManager( activeStudy(), desktop() );
+  }
+#endif
 #ifndef DISABLE_OCCVIEWER
   if( vmType == OCCViewer_Viewer::Type() )
   {
@@ -2593,6 +2619,51 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref )
 
   // .. "Plot2d viewer" group <<end>>
 
+  // .. "PyViewer" preferences tab <<start>>
+  int pyeditTab = pref->addPreference( tr( "PREF_TAB_PYEDITOR" ), salomeCat );
+  // ... "Font settings" group <<start>>
+  int pyFontGroup = pref->addPreference( tr( "PREF_GROUP_PY_FONT" ), pyeditTab );
+  pref->addPreference( tr( "PREF_PY_FONT" ), pyFontGroup,
+    LightApp_Preferences::Font, "PyEditor", "Font" );
+  // ... "Font settings" group <<end>>
+  // ... "Display settings" group <<start>>
+  int pyDispGroup = pref->addPreference( tr( "PREF_GROUP_PY_DISPLAY" ), pyeditTab );
+  pref->setItemProperty( "columns", 2, pyDispGroup );
+  // ... -> current line highlight
+  pref->addPreference( tr( "PREF_PY_CURRLINE_HIGHLIGHT" ), pyDispGroup,
+    LightApp_Preferences::Bool, "PyEditor", "HighlightCurrentLine" );
+  // ... -> text wrapping
+  pref->addPreference( tr( "PREF_PY_TEXT_WRAP" ), pyDispGroup,
+    LightApp_Preferences::Bool, "PyEditor", "TextWrapping" );
+  // ... -> center cursor on scroll
+  pref->addPreference( tr( "PREF_PY_CURSON_ON_SCROLL" ), pyDispGroup,
+    LightApp_Preferences::Bool, "PyEditor", "CenterCursorOnScroll" );
+  // ... -> line numbers area
+  pref->addPreference( tr( "PREF_PY_LINE_NUMBS_AREA" ), pyDispGroup,
+    LightApp_Preferences::Bool, "PyEditor", "LineNumberArea" );
+  // ... "Display settings" group <<end>>
+  // ... "Tab settings" group <<start>>
+  int pyTabGroup = pref->addPreference( tr( "PREF_GROUP_PY_TAB" ), pyeditTab );
+  pref->setItemProperty( "columns", 2, pyTabGroup );
+  // ... -> tab whitespaces
+  pref->addPreference( tr( "PREF_PY_TAB_WHITESPACES" ), pyTabGroup,
+    LightApp_Preferences::Bool, "PyEditor", "TabSpaceVisible" );
+  // ... -> tab size
+  pref->addPreference( tr( "PREF_PY_TAB_SIZE" ), pyTabGroup,
+    LightApp_Preferences::IntSpin, "PyEditor", "TabSize" );
+  // ... "Tab settings" group <<end>>
+  // ... "Vertical edge settings" group <<start>>
+  int pyVertEdgeGroup = pref->addPreference( tr( "PREF_GROUP_VERT_EDGE" ), pyeditTab );
+  pref->setItemProperty( "columns", 2, pyVertEdgeGroup );
+  // ... -> vertical edge
+  pref->addPreference( tr( "PREF_PY_VERT_EDGE" ), pyVertEdgeGroup,
+    LightApp_Preferences::Bool, "PyEditor", "VerticalEdge" );
+  // ... -> number of columns
+  pref->addPreference( tr( "PREF_PY_NUM_COLUMNS" ), pyVertEdgeGroup,
+    LightApp_Preferences::IntSpin, "PyEditor", "NumberColumns" );
+  // ... "Vertical edge settings" group <<end>>
+  // .. "PyEditor" preferences tab <<end>>
+
   // .. "Directories" preferences tab <<start>>
   int dirTab = pref->addPreference( tr( "PREF_TAB_DIRECTORIES" ), salomeCat );
   // ... --> quick directories list
@@ -3116,6 +3187,39 @@ void LightApp_Application::preferencesChanged( const QString& sec, const QString
     }
   }
 #endif
+
+#ifndef DISABLE_PYVIEWER
+  if ( sec == QString( "PyViewer" ) && ( param == QString( "HighlightCurrentLine" ) ||
+                                         param == QString( "LineNumberArea" ) ||
+                                         param == QString( "TextWrapping" ) ||
+                                         param == QString( "CenterCursorOnScroll" ) ||
+                                         param == QString( "TabSpaceVisible" ) ||
+                                         param == QString( "TabSize" ) ||
+                                         param == QString( "VerticalEdge" ) ||
+                                         param == QString( "NumberColumns" ) ||
+                                         param == QString( "Font" ) ) )
+  {
+    QList<SUIT_ViewManager*> lst;
+    viewManagers( PyViewer_Viewer::Type(), lst );
+    QListIterator<SUIT_ViewManager*> itPy( lst );
+    while ( itPy.hasNext() )
+    {
+      SUIT_ViewManager* viewMgr = itPy.next();
+      SUIT_ViewModel* vm = viewMgr->getViewModel();
+      if ( !vm || !vm->inherits( "PyViewer_Viewer" ) )
+        continue;
+
+      PyViewer_Viewer* pyEditVM = dynamic_cast<PyViewer_Viewer*>( vm );
+
+      viewMgr->setViewModel( vm );
+      PyViewer_ViewWindow* pyView = dynamic_cast<PyViewer_ViewWindow*>( viewMgr->getActiveView() );
+      if( pyView )
+      {
+        pyView->setPreferences();
+      }
+    }
+  }
+#endif
 }
 
 /*!
@@ -4071,6 +4175,9 @@ QStringList LightApp_Application::viewManagersTypes() const
 #ifndef DISABLE_PVVIEWER
   aTypesList<<PVViewer_Viewer::Type();
 #endif
+#ifndef DISABLE_PYVIEWER
+  aTypesList<<PyViewer_Viewer::Type();
+#endif
 #ifndef DISABLE_OCCVIEWER
   aTypesList<<OCCViewer_Viewer::Type();
 #endif
index 392fc791fa7699e3c3c62924895d77d2254f4722..d53467536c79efb0303c6ffe5f1bba6fbfa2143c 100644 (file)
@@ -88,7 +88,7 @@ public:
          CloseId, CloseAllId, GroupAllId,
          PreferencesId, MRUId, ModulesListId,
          NewGLViewId, NewPlot2dId, NewOCCViewId, NewVTKViewId,
-         NewQxSceneViewId, NewGraphicsViewId, NewPVViewId, StyleId, FullScreenId,
+         NewQxSceneViewId, NewGraphicsViewId, NewPVViewId, NewPyViewerId, StyleId, FullScreenId,
          UserID };
 
 protected:
index 26ca8daabaa018387300de67d76b1b516a83cbf4..c8034c4bcf412ea2996a52b318235140d0c25c6b 100644 (file)
   </section>
   <section name="resources">
     <!-- Resource directories (resource manager)-->
-    <parameter name="Qtx"          value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="Style"        value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="SUIT"         value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="STD"          value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="ViewerTools"  value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="Plot2d"       value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="SPlot2d"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="GLViewer"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="GraphicsView" value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="OCCViewer"    value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="VTKViewer"    value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-       <parameter name="PVViewer"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>    
+    <parameter name="Qtx"           value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="Style"         value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="SUIT"          value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="STD"           value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="ViewerTools"   value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="Plot2d"        value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="SPlot2d"       value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="GLViewer"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="GraphicsView"  value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="OCCViewer"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="VTKViewer"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="PVViewer"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
     <parameter name="QxSceneViewer" value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="PyConsole"    value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="SalomeApp"    value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="OB"           value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="CAM"          value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="LightApp"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="SVTK"         value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="ToolsGUI"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
-    <parameter name="LogWindow"    value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="PyEditor"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="PyViewer"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="PyConsole"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="SalomeApp"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="OB"            value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="CAM"           value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="LightApp"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="SVTK"          value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="ToolsGUI"      value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
+    <parameter name="LogWindow"     value="${GUI_ROOT_DIR}/share/salome/resources/gui"/>
   </section>
   <section name="desktop" >
     <!-- Default GUI desktop state, position, size -->
     <parameter name="Title"      value="63, 213, 255"  />
     <parameter name="NodeBody"   value="255, 249, 147" />
   </section>
+  <section name="PyEditor" >
+    <!-- PyEditor viewer preferences -->
+    <parameter name="Font"                 value="Courier, 10" />
+    <parameter name="HighlightCurrentLine" value="true" />
+    <parameter name="TextWrapping"         value="false" />
+    <parameter name="CenterCursorOnScroll" value="true" />
+    <parameter name="LineNumberArea"       value="true" />
+    <parameter name="TabSpaceVisible"      value="true" />
+    <parameter name="TabSize"              value="4" />
+    <parameter name="VerticalEdge"         value="true" />
+    <parameter name="NumberColumns"        value="90" />
+  </section>
   <section name="GUI" >
     <parameter name="documentation"     value="gui_help"/>
   </section>
index 5eb0f6e5a39a369b0e70c8c2de0fb0f45fc8ce19..be7ea9b40c7aba33d13ee34ffc684f15fda72086 100644 (file)
@@ -494,6 +494,10 @@ The changes will be applied on the next application session.</translation>
     <message>
         <source>NEW_WINDOW_6</source>
         <translation>ParaVie&amp;w view</translation>
+    </message>
+       <message>
+        <source>NEW_WINDOW_7</source>
+        <translation>P&amp;ython view</translation>
     </message>
     <message>
         <source>CREATING_NEW_WINDOW</source>
@@ -884,6 +888,62 @@ File does not exist</translation>
         <source>PREF_GROUP_SHORTCUTS</source>
         <translation>Shortcuts settings</translation>
     </message>
+  <message>
+    <source>PREF_TAB_PYEDITOR</source>
+    <translation>Python Viewer</translation>
+  </message>
+  <message>
+    <source>PREF_GROUP_PY_FONT</source>
+    <translation>Font settings</translation>
+  </message>
+  <message>
+    <source>PREF_PY_FONT</source>
+    <translation>Font</translation>
+  </message>
+  <message>
+    <source>PREF_GROUP_PY_DISPLAY</source>
+    <translation>Display settings</translation>
+  </message>
+  <message>
+    <source>PREF_PY_CURRLINE_HIGHLIGHT</source>
+    <translation>Enable current line highlight</translation>
+  </message>
+  <message>
+    <source>PREF_PY_TEXT_WRAP</source>
+    <translation>Enable text wrapping</translation>
+  </message>
+  <message>
+    <source>PREF_PY_CURSON_ON_SCROLL</source>
+    <translation>Center cursor on scroll</translation>
+  </message>
+  <message>
+    <source>PREF_PY_LINE_NUMBS_AREA</source>
+    <translation>Display line numbers area</translation>
+  </message>
+  <message>
+    <source>PREF_GROUP_PY_TAB</source>
+    <translation>Tab settings</translation>
+  </message>
+  <message>
+    <source>PREF_PY_TAB_WHITESPACES</source>
+    <translation>Display tab whitespaces</translation>
+  </message>
+  <message>
+    <source>PREF_PY_TAB_SIZE</source>
+    <translation>Tab size:</translation>
+  </message>
+  <message>
+    <source>PREF_GROUP_VERT_EDGE</source>
+    <translation>Vertical edge settings</translation>
+  </message>
+  <message>
+    <source>PREF_PY_VERT_EDGE</source>
+    <translation>Display vertical edge</translation>
+  </message>
+  <message>
+    <source>PREF_PY_NUM_COLUMNS</source>
+    <translation>Number of columns:</translation>
+  </message>
 </context>
 <context>
     <name>LightApp_Module</name>
diff --git a/src/PyEditor/CMakeLists.txt b/src/PyEditor/CMakeLists.txt
new file mode 100644 (file)
index 0000000..338c99d
--- /dev/null
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+#
+
+INCLUDE(UseQt4Ext)
+
+# --- options ---
+
+# additional include directories
+INCLUDE_DIRECTORIES(
+  ${QT_INCLUDES}
+  ${PROJECT_SOURCE_DIR}/src/Qtx
+)
+
+# additional preprocessor / compiler flags
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+
+# libraries to link to
+SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES} qtx)
+
+# --- headers ---
+
+# header files / to be processed by moc
+SET(_moc_HEADERS
+  PyEditor_Editor.h
+  PyEditor_LineNumberArea.h
+  PyEditor_PyHighlighter.h
+  PyEditor_SettingsDlg.h
+)
+
+# header files / no moc processing
+SET(_other_HEADERS
+  PyEditor.h
+  PyEditor_Settings.h
+)
+
+# header files / to install
+SET(PyEditor_HEADERS ${_moc_HEADERS} ${_other_HEADERS})
+
+# --- resources ---
+
+# resource files / to be processed by lrelease
+SET(RESOURCES_PATH resources)
+
+SET(_ts_RESOURCES
+  ${RESOURCES_PATH}/translations/PyEditor_msg_en.ts
+  ${RESOURCES_PATH}/translations/PyEditor_msg_fr.ts
+  ${RESOURCES_PATH}/translations/PyEditor_msg_ja.ts
+)
+
+# sources / moc wrappings
+QT4_WRAP_CPP(_moc_SOURCES ${_moc_HEADERS})
+
+# sources / static
+SET(_other_SOURCES
+  PyEditor_Editor.cxx
+  PyEditor_LineNumberArea.cxx
+  PyEditor_PyHighlighter.cxx
+  PyEditor_Settings.cxx
+  PyEditor_SettingsDlg.cxx
+)
+
+# sources / to compile
+SET(PyEditor_SOURCES ${_other_SOURCES} ${_moc_SOURCES})
+
+# --- rules ---
+ADD_LIBRARY(PyEditor ${PyEditor_SOURCES})
+TARGET_LINK_LIBRARIES(PyEditor ${_link_LIBRARIES})
+INSTALL(TARGETS PyEditor EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_LIBS})
+
+INSTALL(FILES ${PyEditor_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS})
+QT4_INSTALL_TS_RESOURCES("${_ts_RESOURCES}" "${SALOME_GUI_INSTALL_RES_DATA}")
diff --git a/src/PyEditor/PyEditor.h b/src/PyEditor/PyEditor.h
new file mode 100644 (file)
index 0000000..964c0b7
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifdef WIN32
+
+#if defined PYEDITOR_EXPORTS || defined PyEditor_EXPORTS
+#define PYEDITOR_EXPORT __declspec(dllexport)
+#else
+#define PYEDITOR_EXPORT __declspec(dllimport)
+#endif
+
+#else
+#define PYEDITOR_EXPORT
+#endif // WIN32
diff --git a/src/PyEditor/PyEditor_Editor.cxx b/src/PyEditor/PyEditor_Editor.cxx
new file mode 100644 (file)
index 0000000..3721c1d
--- /dev/null
@@ -0,0 +1,804 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_Editor.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+//Local includes
+#include "PyEditor_Editor.h"
+#include "PyEditor_LineNumberArea.h"
+#include "PyEditor_PyHighlighter.h"
+#include "PyEditor_Settings.h"
+
+//Qtx includes
+#include <QtxResourceMgr.h>
+
+//Qt includes
+#include <QPainter>
+#include <QTextBlock>
+
+/*!
+  \class PyEditor_Editor
+  \brief Viewer/Editor is used to edit and show advanced plain text.
+*/
+
+/*!
+  \brief Constructor.
+  \param isSingle flag determined single application or reccesed.
+  \param theParent parent widget
+*/
+PyEditor_Editor::PyEditor_Editor( bool isSingle, QtxResourceMgr* theMgr, QWidget* theParent )
+  : QPlainTextEdit( theParent )
+{
+  my_Settings = new PyEditor_Settings( theMgr, isSingle );
+
+  // Create line number area
+  my_LineNumberArea = new PyEditor_LineNumberArea( this );
+  my_LineNumberArea->setMouseTracking( true );
+
+  my_SyntaxHighlighter = new PyEditor_PyHighlighter( this->document() );
+
+  // Signals and slots
+  connect( this, SIGNAL( blockCountChanged( int ) ), this, SLOT( updateLineNumberAreaWidth( int ) ) );
+  connect( this, SIGNAL( updateRequest( QRect, int ) ), this, SLOT( updateLineNumberArea( QRect, int ) ) );
+  connect( this, SIGNAL( cursorPositionChanged() ), this, SLOT( updateHighlightCurrentLine() ) );
+  connect( this, SIGNAL( cursorPositionChanged() ), this, SLOT( matchParentheses() ) );
+
+  updateStatement();
+}
+
+/*!
+  \brief Destructor.
+*/
+PyEditor_Editor::~PyEditor_Editor()
+{
+}
+
+/*!
+  Updates editor.
+ */
+void PyEditor_Editor::updateStatement()
+{
+  //Set font size
+  QFont aFont = font();
+  aFont.setFamily( settings()->p_Font.family() );
+  aFont.setPointSize( settings()->p_Font.pointSize() );
+  setFont( aFont );
+
+  // Set line wrap mode
+  setLineWrapMode( settings()->p_TextWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap );
+
+  // Center the cursor on screen
+  setCenterOnScroll( settings()->p_CenterCursorOnScroll );
+
+  // Set size white spaces
+  setTabStopWidth( settings()->p_TabSize * 10 );
+
+  // Update current line highlight
+  updateHighlightCurrentLine();
+
+  // Update line numbers area
+  updateLineNumberAreaWidth( 0 );
+
+  my_SyntaxHighlighter->rehighlight();
+  viewport()->update();
+}
+
+/*!
+  SLOT: Delete the current selection's contents
+ */
+void PyEditor_Editor::deleteSelected()
+{
+  QTextCursor aCursor = textCursor();
+  if ( aCursor.hasSelection() )
+    aCursor.removeSelectedText();
+  setTextCursor( aCursor );
+}
+
+/*!
+  \brief Reimplemented calss is to receive key press events for the plain text widget.
+  \param theEvent event
+ */
+void PyEditor_Editor::keyPressEvent( QKeyEvent* theEvent )
+{
+  if ( theEvent->type() == QEvent::KeyPress )
+  {
+    int aKey = theEvent->key();
+    Qt::KeyboardModifiers aCtrl = theEvent->modifiers() & Qt::ControlModifier;
+    Qt::KeyboardModifiers aShift = theEvent->modifiers() & Qt::ShiftModifier;
+    
+    if ( aKey == Qt::Key_Tab || ( aKey == Qt::Key_Backtab || ( aKey == Qt::Key_Tab && aShift ) ) )
+    {
+      QTextCursor aCursor = textCursor();
+      aCursor.beginEditBlock();
+      tabIndentation( aKey == Qt::Key_Backtab );
+      aCursor.endEditBlock();
+      theEvent->accept();
+    }
+    else if ( aKey == Qt::Key_Enter || aKey == Qt::Key_Return )
+    {
+      QTextCursor aCursor = textCursor();
+      aCursor.beginEditBlock();
+      if ( lineIndent() == 0 )
+      {
+        QPlainTextEdit::keyPressEvent( theEvent );
+      }
+      aCursor.endEditBlock();
+      theEvent->accept();
+    }
+    else if ( theEvent == QKeySequence::MoveToStartOfLine || theEvent == QKeySequence::SelectStartOfLine )
+    {
+      QTextCursor aCursor = this->textCursor();
+      if ( QTextLayout* aLayout = aCursor.block().layout() )
+      {
+        if ( aLayout->lineForTextPosition( aCursor.position() - aCursor.block().position() ).lineNumber() == 0 )
+        {
+          handleHome( theEvent == QKeySequence::SelectStartOfLine );
+        }
+      }
+    }
+    else if ( ( aKey == Qt::Key_Colon || ( aKey == Qt::Key_Space && !aCtrl && !aShift ) ) &&
+              !textCursor().hasSelection() )
+    {
+      QTextCursor aCursor = textCursor();
+      aCursor.movePosition( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
+      
+      QString aSelectedText = aCursor.selectedText();
+      int numSpaces = findFirstNonSpace( aSelectedText );
+      int amountChars = aSelectedText.size() - findFirstNonSpace( aSelectedText );
+      QString aLeadingText = aSelectedText.right( amountChars );
+      
+      QStringList aReservedWords;
+      aReservedWords.append( "except" );
+      if ( aKey == Qt::Key_Colon )
+      {
+        aReservedWords.append( "else" );
+        aReservedWords.append( "finally" );
+      }
+      else if ( aKey == Qt::Key_Space )
+      {
+        aReservedWords.append( "elif" );
+      }
+      
+      if ( aReservedWords.contains( aLeadingText ) )
+      {
+        QString aPreviousText = aCursor.block().previous().text();
+        int numSpacesPrevious = findFirstNonSpace( aPreviousText );
+        if ( numSpaces == numSpacesPrevious )
+        {
+          tabIndentation( true );
+          aCursor.movePosition( QTextCursor::EndOfBlock );
+          setTextCursor( aCursor );
+        }
+      }
+      QPlainTextEdit::keyPressEvent( theEvent );
+    }
+    else
+    {
+      QPlainTextEdit::keyPressEvent( theEvent );
+    }
+  }
+}
+
+/*!
+  \brief Reimplemented calss is to receive plain text widget resize events
+  which are passed in the event parameter.
+  \param theEvent event
+ */
+void PyEditor_Editor::resizeEvent( QResizeEvent* theEvent )
+{
+  QPlainTextEdit::resizeEvent( theEvent );
+
+  // Change size geometry of line number area
+  QRect aContentsRect = contentsRect();
+  my_LineNumberArea->setGeometry(
+    QRect( aContentsRect.left(),
+           aContentsRect.top(),
+           lineNumberAreaWidth(),
+           aContentsRect.height() ) );
+}
+
+/*!
+  \brief Reimplemented calss is to receive paint events passed in theEvent.
+  \param theEvent event
+ */
+void PyEditor_Editor::paintEvent( QPaintEvent* theEvent )
+{
+  QPlainTextEdit::paintEvent( theEvent );
+
+  QTextBlock aBlock( firstVisibleBlock() );
+  QPointF anOffset( contentOffset() );
+  QPainter aPainter( this->viewport() );
+
+  int aTabSpaces = this->tabStopWidth() / 10;
+
+  // Visualization tab spaces
+  if ( settings()->p_TabSpaceVisible )
+  {
+    qreal aTop = blockBoundingGeometry( aBlock ).translated( anOffset ).top();
+    while ( aBlock.isValid() && aTop <= theEvent->rect().bottom() )
+    {
+      if ( aBlock.isVisible() && blockBoundingGeometry( aBlock ).translated( anOffset ).toRect().intersects( theEvent->rect() ) )
+      {
+        QString aText = aBlock.text();
+        if ( aText.contains( QRegExp( "\\w+" ) ) )
+          aText.remove( QRegExp( "(?!\\w+)\\s+$" ) );
+        
+        int aColumn = 0;
+        int anIndex = 0;
+        while ( anIndex != -1 )
+        {
+          anIndex = aText.indexOf( QRegExp( QString( "^\\s{%1}" ).arg( aTabSpaces ) ), 0 );
+          if ( anIndex != -1 )
+          {
+            aColumn = aColumn + aTabSpaces;
+            aText = aText.mid( aTabSpaces );
+    
+            if ( aText.startsWith( ' ' ) )
+            {
+              QTextCursor aCursor( aBlock );
+              aCursor.setPosition( aBlock.position() + aColumn );
+      
+              QRect aRect = cursorRect( aCursor );
+              aPainter.setPen( QPen( Qt::darkGray, 1, Qt::DotLine ) );
+              aPainter.drawLine( aRect.x() + 1, aRect.top(), aRect.x() + 1, aRect.bottom() );
+            }
+          }
+        }
+      }
+      aBlock = aBlock.next();
+    }
+  }
+  
+  // Vertical edge line
+  if ( settings()->p_VerticalEdge )
+  {
+    const QRect aRect = theEvent->rect();
+    const QFont aFont = currentCharFormat().font();
+    int aNumberColumn =  QFontMetrics( aFont ).averageCharWidth() * settings()->p_NumberColumns + anOffset.x() + document()->documentMargin();
+    aPainter.setPen( QPen( Qt::lightGray, 1, Qt::SolidLine ) );
+    aPainter.drawLine( aNumberColumn, aRect.top(), aNumberColumn, aRect.bottom() );
+  }
+}
+
+/*!
+  \return manager of setting values.
+ */
+PyEditor_Settings* PyEditor_Editor::settings()
+{
+  return my_Settings;
+}
+
+/*!
+  \brief Indenting and tabbing of the text
+  \param isShift flag defines reverse tab
+ */
+void PyEditor_Editor::tabIndentation( bool isShift )
+{
+  QTextCursor aCursor = textCursor();
+  int aTabSpaces = this->tabStopWidth()/10;
+
+  if ( !aCursor.hasSelection() )
+  {
+    if ( !isShift )
+    {
+      int N = aCursor.columnNumber() % aTabSpaces;
+      aCursor.insertText( QString( aTabSpaces - N, QLatin1Char( ' ' ) ) );
+    }
+    else
+    {
+      QTextBlock aCurrentBlock = document()->findBlock( aCursor.position() );
+      int anIndentPos = findFirstNonSpace( aCurrentBlock.text() );
+      aCursor.setPosition( aCurrentBlock.position() + anIndentPos );
+      setTextCursor( aCursor );
+      
+      //if ( aCurrCursorColumnPos <= anIndentPos )
+      //{
+      int aColumnPos = aCursor.columnNumber();
+      if ( aColumnPos != 0 )
+      {
+        int N = aCursor.columnNumber() % aTabSpaces;
+        if ( N == 0 ) N = aTabSpaces;
+        aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, N );
+        aCursor.removeSelectedText();
+      }
+      setTextCursor( aCursor );
+      //}
+    }
+  }
+  else
+  {
+    indentSelection( isShift );
+  }
+}
+
+/*!
+  \brief Indenting and tabbing of the selected text
+  \param isShift flag defines reverse tab
+ */
+void PyEditor_Editor::indentSelection( bool isShift )
+{
+  QTextCursor aCursor = this->textCursor();
+
+  int aCursorStart = aCursor.selectionStart();
+  int aCursorEnd = aCursor.selectionEnd();
+
+  QTextBlock aStartBlock = document()->findBlock( aCursorStart );
+  QTextBlock anEndBlock = document()->findBlock( aCursorEnd - 1 ).next();
+
+  int aTabSpaces = this->tabStopWidth()/10;
+
+  for ( QTextBlock aBlock = aStartBlock; aBlock.isValid() && aBlock != anEndBlock; aBlock = aBlock.next() )
+  {
+    QString aText = aBlock.text();
+    int anIndentPos = findFirstNonSpace( aText );
+    int N = ( anIndentPos % aTabSpaces );
+    
+    aCursor.setPosition( aBlock.position() + anIndentPos );
+    if ( !isShift )
+    {
+      aCursor.insertText( QString( aTabSpaces - N, QLatin1Char( ' ' ) ) );
+      setTextCursor( aCursor );
+    }
+    else
+    {
+      int aColumnPos = aCursor.columnNumber();
+      if ( aColumnPos != 0 )
+      {
+        int blockN = aColumnPos % aTabSpaces;
+        if ( blockN == 0 ) blockN = aTabSpaces;
+        aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, blockN );
+        aCursor.removeSelectedText();
+        setTextCursor( aCursor );
+      }
+    }
+  }
+
+  // Reselect the selected lines
+  aCursor.setPosition( aStartBlock.position() );
+  aCursor.setPosition( anEndBlock.previous().position(), QTextCursor::KeepAnchor );
+  aCursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
+  setTextCursor( aCursor );
+}
+
+/*!
+  \brief Finds the first non-white sapce in theText.
+  \param theText string
+  \return index of the first non-white space
+ */
+int PyEditor_Editor::findFirstNonSpace( const QString& theText )
+{
+  int i = 0;
+  while ( i < theText.size() )
+  {
+    if ( !theText.at(i).isSpace() )
+      return i;
+    ++i;
+  }
+  return i;
+}
+
+/*!
+  \brief Auto line indenting
+  \return error code
+ */
+int PyEditor_Editor::lineIndent()
+{
+  int aTabSpaces = this->tabStopWidth() / 10;
+
+  QTextCursor aCursor = textCursor();
+  aCursor.insertBlock();
+  setTextCursor( aCursor );
+
+  QTextBlock aCurrentBlock = aCursor.block();
+  if ( aCurrentBlock == document()->begin() )
+    return 0;
+
+  QTextBlock aPreviousBlock = aCurrentBlock.previous();
+
+  QString aPreviousText;
+  forever
+  {
+    if ( aPreviousBlock == document()->begin() )
+    {
+      aPreviousText = aPreviousBlock.text();
+      if ( aPreviousText.isEmpty() && aPreviousText.trimmed().isEmpty() )
+        return -1;
+      break;
+    }
+    
+    // If the text of this block is not empty then break the loop.
+    aPreviousText = aPreviousBlock.text();
+    if ( !aPreviousText.isEmpty() && !aPreviousText.trimmed().isEmpty() )
+      break;
+    
+    aPreviousBlock = aPreviousBlock.previous();
+  }
+  
+  int aTabIndentation = 0;
+  int anAmountIndentation = -1;
+  int i = 0;
+  while ( i < aPreviousText.size() )
+  {
+    if ( !aPreviousText.at(i).isSpace() )
+    {
+      anAmountIndentation = findFirstNonSpace( aPreviousText );
+      break;
+    }
+    else
+    {
+      ++aTabIndentation;
+    }
+    ++i;
+  }
+  
+  if ( anAmountIndentation == -1 )
+  {
+    if ( aTabIndentation > 0 )
+      anAmountIndentation = aTabIndentation;
+    else
+      return 0;
+  }
+  
+  const QString aPreviousTrimmed = aPreviousText.trimmed();
+  if ( aPreviousTrimmed.endsWith( ":" ) )
+  {
+    anAmountIndentation += aTabSpaces;
+  }
+  else
+  {
+    if ( aPreviousTrimmed == "continue"
+      || aPreviousTrimmed == "break"
+      || aPreviousTrimmed == "pass"
+      || aPreviousTrimmed == "return"
+      || aPreviousTrimmed == "raise"
+      || aPreviousTrimmed.startsWith( "raise " )
+      || aPreviousTrimmed.startsWith( "return " ) )
+      anAmountIndentation -= aTabSpaces;
+  }
+  
+  aCursor.insertText( QString( anAmountIndentation, QLatin1Char(' ') ) );
+  setTextCursor( aCursor );
+  
+  return 1;
+}
+
+/*!
+  \brief Set text cursor on home position.
+ */
+void PyEditor_Editor::handleHome( bool isExtendLine )
+{
+  QTextCursor aCursor = textCursor();
+  QTextCursor::MoveMode aMode = QTextCursor::MoveAnchor;
+
+  if ( isExtendLine )
+    aMode = QTextCursor::KeepAnchor;
+
+  int anInitPos = aCursor.position();
+  int aBlockPos = aCursor.block().position();
+
+  QChar aCharacter = document()->characterAt( aBlockPos );
+  while ( aCharacter.category() == QChar::Separator_Space )
+  {
+    ++aBlockPos;
+    if ( aBlockPos == anInitPos )
+      break;
+    aCharacter = document()->characterAt( aBlockPos );
+  }
+  
+  if ( aBlockPos == anInitPos )
+    aBlockPos = aCursor.block().position();
+  
+  aCursor.setPosition( aBlockPos, aMode );
+  setTextCursor( aCursor );
+}
+
+/*!
+  SLOT: Updates the highlight current line.
+ */
+void PyEditor_Editor::updateHighlightCurrentLine()
+{
+  QList<QTextEdit::ExtraSelection> anExtraSelections;
+  if ( !isReadOnly() && settings()->p_HighlightCurrentLine )
+  {
+    QTextEdit::ExtraSelection selection;
+    
+    QColor lineColor = QColor( Qt::gray ).lighter( 155 );
+    
+    selection.format.setBackground( lineColor );
+    selection.format.setProperty( QTextFormat::FullWidthSelection, QVariant( true ) );
+    selection.cursor = textCursor();
+    selection.cursor.clearSelection();
+    anExtraSelections.append( selection );
+  }
+  setExtraSelections( anExtraSelections );
+}
+
+/*!
+  \brief Creates line number area.
+  \param theEvent event for paint events.
+ */
+void PyEditor_Editor::lineNumberAreaPaintEvent( QPaintEvent* theEvent )
+{
+  QPainter aPainter( my_LineNumberArea );
+  aPainter.fillRect( theEvent->rect(), QColor( Qt::lightGray ).lighter( 125 ) );
+
+  QTextBlock aBlock = firstVisibleBlock();
+  int aBlockNumber = aBlock.blockNumber();
+  int aTop = (int)blockBoundingGeometry( aBlock ).translated( contentOffset() ).top();
+  int aBottom = aTop + (int)blockBoundingRect( aBlock ).height();
+  int aCurrentLine = document()->findBlock( textCursor().position() ).blockNumber();
+
+  QFont aFont = aPainter.font();
+  aPainter.setPen( this->palette().color( QPalette::Text ) );
+
+  while ( aBlock.isValid() && aTop <= theEvent->rect().bottom() )
+  {
+    if ( aBlock.isVisible() && aBottom >= theEvent->rect().top() )
+    {
+      if ( aBlockNumber == aCurrentLine )
+      {
+        aPainter.setPen( Qt::darkGray );
+        aFont.setBold( true );
+        aPainter.setFont( aFont );
+      }
+      else
+      {
+        aPainter.setPen( Qt::gray ) ;
+        aFont.setBold( false );
+        aPainter.setFont( aFont );
+      }
+      QString aNumber = QString::number( aBlockNumber + 1 );
+      aPainter.drawText( 0, aTop, my_LineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, aNumber );
+    }
+
+    aBlock = aBlock.next();
+    aTop = aBottom;
+    aBottom = aTop + (int)blockBoundingRect( aBlock ).height();
+    ++aBlockNumber;
+  }
+}
+
+/*!
+  \return width of line number area
+ */
+int PyEditor_Editor::lineNumberAreaWidth()
+{
+  int aSpace = 0;
+
+  int aDigits = 1;
+  int aMaximum = qMax( 1, blockCount() );
+  while ( aMaximum >= 10 )
+  {
+    aMaximum /= 10;
+    ++aDigits;
+  }
+
+  if ( settings()->p_LineNumberArea )
+    aSpace += 5 + fontMetrics().width( QLatin1Char( '9' ) ) * aDigits;
+  
+  return aSpace;
+}
+
+/*!
+  SLOT: Updates the width of line number area.
+ */
+void PyEditor_Editor::updateLineNumberAreaWidth( int /*theNewBlockCount*/ )
+{
+  setViewportMargins( lineNumberAreaWidth(), 0, 0, 0 );
+}
+
+/*!
+  SLOT: When the editor viewport has been scrolled.
+ */
+void PyEditor_Editor::updateLineNumberArea( const QRect& theRect, int theDY )
+{
+  if ( theDY )
+    my_LineNumberArea->scroll( 0, theDY );
+  else
+    my_LineNumberArea->update( 0, theRect.y(), my_LineNumberArea->width(), theRect.height() );
+
+  if ( theRect.contains( viewport()->rect() ) )
+    updateLineNumberAreaWidth( 0 );
+}
+
+/*!
+  \brief Parenthesis management.
+  SLOT: Walk through and check that we don't exceed 80 chars per line.
+ */
+void PyEditor_Editor::matchParentheses()
+{
+  PyEditor_PyHighlighter::TextBlockData* data =
+    static_cast<PyEditor_PyHighlighter::TextBlockData*>( textCursor().block().userData() );
+
+  if ( data )
+  {
+    QVector<PyEditor_PyHighlighter::ParenthesisInfo*> infoEntries = data->parentheses();
+    
+    int aPos = textCursor().block().position();
+    bool ignore = false;
+    for ( int i = 0; i < infoEntries.size(); ++i )
+    {
+      PyEditor_PyHighlighter::ParenthesisInfo* info = infoEntries.at(i);
+      
+      int currentColumnPosition = textCursor().columnNumber();
+      if ( info->position == currentColumnPosition - 1 && isLeftBrackets( info->character ) )
+      {
+        if ( matchLeftParenthesis( textCursor().block(), i + 1, 0 ) )
+          createParenthesisSelection( aPos + info->position );
+      }
+      else if ( info->position == currentColumnPosition && isLeftBrackets( info->character ) )
+      {
+        if ( !ignore )
+        {
+          if ( matchLeftParenthesis( textCursor().block(), i + 1, 0 ) )
+            createParenthesisSelection( aPos + info->position );
+        }
+      }
+      else if ( info->position == currentColumnPosition - 1 && isRightBrackets( info->character ) )
+      {
+        if ( matchRightParenthesis( textCursor().block(), i - 1, 0 ) )
+          createParenthesisSelection( aPos + info->position );
+        ignore = true;
+      }
+      else if ( info->position == currentColumnPosition && isRightBrackets( info->character ) )
+      {
+        if ( matchRightParenthesis( textCursor().block(), i - 1, 0 ) )
+          createParenthesisSelection( aPos + info->position );
+      }
+    }
+  }
+}
+
+/*!
+  \brief Matches the left brackets.
+  \param theCurrentBlock text block
+  \param theI index
+  \param theNumLeftParentheses number of left parentheses
+  \return \c true if the left match
+ */
+bool PyEditor_Editor::matchLeftParenthesis(
+  const QTextBlock& theCurrentBlock, int theI, int theNumLeftParentheses )
+{
+  PyEditor_PyHighlighter::TextBlockData* data =
+    static_cast<PyEditor_PyHighlighter::TextBlockData*>( theCurrentBlock.userData() );
+  QVector<PyEditor_PyHighlighter::ParenthesisInfo*> infos = data->parentheses();
+
+  int docPos = theCurrentBlock.position();
+  for ( ; theI < infos.size(); ++theI )
+  {
+    PyEditor_PyHighlighter::ParenthesisInfo* info = infos.at(theI);
+
+    if ( isLeftBrackets( info->character ) )
+    {
+      ++theNumLeftParentheses;
+      continue;
+    }
+
+    if ( isRightBrackets( info->character ) && theNumLeftParentheses == 0 )
+    {
+      createParenthesisSelection( docPos + info->position );
+      return true;
+    }
+    else
+      --theNumLeftParentheses;
+  }
+
+  QTextBlock nextBlock = theCurrentBlock.next();
+  if ( nextBlock.isValid() )
+    return matchLeftParenthesis( nextBlock, 0, theNumLeftParentheses );
+
+  return false;
+}
+
+/*!
+  \brief Matches the right brackets.
+  \param theCurrentBlock text block
+  \param theI index
+  \param theNumRightParentheses number of right parentheses
+  \return \c true if the right match
+ */
+bool PyEditor_Editor::matchRightParenthesis( const QTextBlock& theCurrentBlock, int theI, int theNumRightParentheses )
+{
+  PyEditor_PyHighlighter::TextBlockData* data = static_cast<PyEditor_PyHighlighter::TextBlockData*>( theCurrentBlock.userData() );
+  QVector<PyEditor_PyHighlighter::ParenthesisInfo*> parentheses = data->parentheses();
+
+  int docPos = theCurrentBlock.position();
+  for ( ; theI > -1 && parentheses.size() > 0; --theI )
+  {
+    PyEditor_PyHighlighter::ParenthesisInfo* info = parentheses.at(theI);
+    if ( isRightBrackets( info->character ) )
+    {
+      ++theNumRightParentheses;
+      continue;
+    }
+    if ( isLeftBrackets( info->character ) && theNumRightParentheses == 0 )
+    {
+      createParenthesisSelection( docPos + info->position );
+      return true;
+    }
+    else
+      --theNumRightParentheses;
+  }
+
+  QTextBlock prevBlock = theCurrentBlock.previous();
+  if ( prevBlock.isValid() )
+  {
+    PyEditor_PyHighlighter::TextBlockData* data = static_cast<PyEditor_PyHighlighter::TextBlockData*>( prevBlock.userData() );
+    QVector<PyEditor_PyHighlighter::ParenthesisInfo*> parentheses = data->parentheses();
+    return matchRightParenthesis( prevBlock, parentheses.size() - 1, theNumRightParentheses );
+  }
+
+  return false;
+}
+
+/*!
+  \brief Creates brackets selection.
+  \param thePosition position
+ */
+// Create brackets
+void PyEditor_Editor::createParenthesisSelection( int thePosition )
+{
+  QList<QTextEdit::ExtraSelection> selections = extraSelections();
+
+  QTextEdit::ExtraSelection selection;
+
+  QTextCharFormat format = selection.format;
+  format.setForeground( Qt::red );
+  format.setBackground( Qt::white );
+  selection.format = format;
+
+  QTextCursor cursor = textCursor();
+  cursor.setPosition( thePosition );
+  cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor );
+  selection.cursor = cursor;
+
+  selections.append( selection );
+  setExtraSelections( selections );
+}
+
+/*!
+  return true whether the left bracket
+ */
+bool PyEditor_Editor::isLeftBrackets( QChar theSymbol )
+{
+  return theSymbol == '(' || theSymbol == '{' || theSymbol == '[';
+}
+
+/*!
+  Appends a new paragraph with text to the end of the text edit.
+ */
+void PyEditor_Editor::append( const QString & text ) {
+  QPlainTextEdit::appendPlainText(text);
+}
+
+/*!
+  Sets the text edit's text.
+*/
+void PyEditor_Editor::setText( const QString & text ) {
+  QPlainTextEdit::appendPlainText(text);
+}
+
+/*!
+  return true whether the right bracket
+ */
+bool PyEditor_Editor::isRightBrackets( QChar theSymbol )
+{
+  return theSymbol == ')' || theSymbol == '}' || theSymbol == ']';
+}
diff --git a/src/PyEditor/PyEditor_Editor.h b/src/PyEditor/PyEditor_Editor.h
new file mode 100644 (file)
index 0000000..ced9467
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_Editor.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYEDITOR_EDITOR_H
+#define PYEDITOR_EDITOR_H
+
+#include <QPlainTextEdit>
+
+class PyEditor_PyHighlighter;
+class PyEditor_Settings;
+class QtxResourceMgr;
+
+class PyEditor_Editor : public QPlainTextEdit
+{
+  Q_OBJECT
+
+public:
+  PyEditor_Editor( bool isSingle = false, QtxResourceMgr* = 0,  QWidget* = 0 );
+  virtual ~PyEditor_Editor();
+  
+  void lineNumberAreaPaintEvent( QPaintEvent* );
+  int  lineNumberAreaWidth();
+
+  void updateStatement();
+  PyEditor_Settings* settings();
+
+public Q_SLOTS:
+  void deleteSelected();
+  void append ( const QString & );  
+  void setText ( const QString & text );
+protected:
+  virtual void keyPressEvent( QKeyEvent* );
+  virtual void resizeEvent( QResizeEvent* );
+  virtual void paintEvent( QPaintEvent* );
+    
+private Q_SLOTS:
+  void updateHighlightCurrentLine();
+  void matchParentheses();
+
+  void updateLineNumberAreaWidth( int );
+  void updateLineNumberArea( const QRect&, int );
+  
+private:
+  bool matchLeftParenthesis( const QTextBlock&, int, int );
+  bool matchRightParenthesis( const QTextBlock&, int, int );
+  void createParenthesisSelection( int );
+  bool isLeftBrackets( QChar );
+  bool isRightBrackets( QChar );
+  
+  void handleHome( bool );
+  int  lineIndent();
+  void tabIndentation( bool );
+  void indentSelection( bool );
+  
+  int findFirstNonSpace( const QString& );
+  
+  QWidget*                my_LineNumberArea;
+  PyEditor_PyHighlighter* my_SyntaxHighlighter;
+  PyEditor_Settings*      my_Settings;
+};
+
+#endif // PYEDITOR_EDITOR_H
diff --git a/src/PyEditor/PyEditor_LineNumberArea.cxx b/src/PyEditor/PyEditor_LineNumberArea.cxx
new file mode 100644 (file)
index 0000000..571d08c
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_LineNumberArea.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyEditor_LineNumberArea.h"
+
+#include "PyEditor_Editor.h"
+
+/*!
+  \class PyEditor_LineNumberArea
+  \brief Widget shows line number.
+*/
+
+/*!
+  \brief Constructor.
+  \param theCodeEditor parent widget
+*/
+PyEditor_LineNumberArea::PyEditor_LineNumberArea( PyEditor_Editor* theCodeEditor ) :
+  QWidget( theCodeEditor )
+{
+  my_CodeEditor = theCodeEditor;
+}
+
+QSize PyEditor_LineNumberArea::sizeHint() const
+{
+  return QSize( my_CodeEditor->lineNumberAreaWidth(), 0 );
+}
+
+void PyEditor_LineNumberArea::paintEvent( QPaintEvent* theEvent )
+{
+  my_CodeEditor->lineNumberAreaPaintEvent( theEvent );
+  QWidget::paintEvent( theEvent );
+}
diff --git a/src/PyEditor/PyEditor_LineNumberArea.h b/src/PyEditor/PyEditor_LineNumberArea.h
new file mode 100644 (file)
index 0000000..09781b8
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_LineNumberArea.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYEDITOR_LINENUMBERAREA_H
+#define PYEDITOR_LINENUMBERAREA_H
+
+#include <QWidget>
+
+class PyEditor_Editor;
+
+class PyEditor_LineNumberArea : public QWidget
+{
+  Q_OBJECT
+
+public:
+  explicit PyEditor_LineNumberArea( PyEditor_Editor* );
+
+  QSize sizeHint() const;
+  
+protected:
+  void paintEvent( QPaintEvent* );
+  
+private:
+  PyEditor_Editor* my_CodeEditor;
+};
+
+#endif // PYEDITOR_LINENUMBERAREA_H
diff --git a/src/PyEditor/PyEditor_PyHighlighter.cxx b/src/PyEditor/PyEditor_PyHighlighter.cxx
new file mode 100644 (file)
index 0000000..fb9eb03
--- /dev/null
@@ -0,0 +1,355 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_PyHighlighter.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyEditor_PyHighlighter.h"
+
+#define NORMAL 0
+#define TRIPLESINGLE 1
+#define TRIPLEDOUBLE 2
+
+/*!
+  \class PyEditor_PyHighlighter
+  \brief Python highlighter class defines the syntax highlighting rules.
+*/
+
+PyEditor_PyHighlighter::TextBlockData::TextBlockData()
+{
+}
+
+QVector<PyEditor_PyHighlighter::ParenthesisInfo*> PyEditor_PyHighlighter::TextBlockData::parentheses()
+{
+  return my_Parentheses;
+}
+
+void PyEditor_PyHighlighter::TextBlockData::insert( PyEditor_PyHighlighter::ParenthesisInfo* theInfo )
+{
+  int i = 0;
+  while ( i < my_Parentheses.size() && theInfo->position > my_Parentheses.at(i)->position )
+    ++i;
+
+  my_Parentheses.insert( i, theInfo );
+}
+
+/*!
+  \brief Constructor.
+  \param theDocument container for structured rich text documents.
+*/
+PyEditor_PyHighlighter::PyEditor_PyHighlighter( QTextDocument* theDocument )
+  : QSyntaxHighlighter( theDocument )
+{
+  initialize();
+}
+
+/*!
+  \brief Initialization rules.
+ */
+void PyEditor_PyHighlighter::initialize()
+{
+  HighlightingRule aRule;
+
+  // Keywords
+  keywordFormat.setForeground( Qt::blue );
+  QStringList aKeywords = keywords();
+  foreach ( const QString &keyword, aKeywords )
+  {
+    aRule.pattern = QRegExp( QString( "\\b%1\\b" ).arg( keyword ) );
+    aRule.format = keywordFormat;
+    aRule.capture = 0;
+    highlightingRules.append( aRule );
+  }
+
+  // Special keywords
+  specialFromat.setForeground( Qt::magenta );
+  QStringList aSpecialKeywords = specialKeywords();
+  foreach ( const QString &keyword, aSpecialKeywords )
+  {
+    aRule.pattern = QRegExp( QString( "\\b%1\\b" ).arg( keyword ) );
+    aRule.format = specialFromat;
+    aRule.capture = 0;
+    highlightingRules.append( aRule );
+  }
+
+  // Reference to the current instance of the class
+  referenceClassFormat.setForeground( QColor( 179, 143, 0 ) );
+  referenceClassFormat.setFontItalic( true );
+  aRule.pattern = QRegExp( "\\bself\\b" );
+  aRule.format = referenceClassFormat;
+  aRule.capture = 0;
+  highlightingRules.append( aRule );
+
+  // Numbers
+  numberFormat.setForeground( Qt::darkMagenta );
+  aRule.pattern = QRegExp( "\\b([-+])?(\\d+(\\.)?\\d*|\\d*(\\.)?\\d+)(([eE]([-+])?)?\\d+)?\\b" );
+  aRule.format = numberFormat;
+  aRule.capture = 0;
+  highlightingRules.append( aRule );
+
+  // String qoutation
+  quotationFormat.setForeground( Qt::darkGreen );
+  aRule.pattern = QRegExp( "(?:'[^']*'|\"[^\"]*\")" );
+  aRule.pattern.setMinimal( true );
+  aRule.format = quotationFormat;
+  aRule.capture = 0;
+  highlightingRules.append( aRule );
+
+  // Function names
+  functionFormat.setFontWeight( QFont::Bold );
+  aRule.pattern = QRegExp( "(?:def\\s*)(\\b[A-Za-z0-9_]+)(?=[\\W])" );
+  aRule.capture = 1;
+  aRule.format = functionFormat;
+  highlightingRules.append( aRule );
+
+  // Class names
+  classFormat.setForeground( Qt::darkBlue );
+  classFormat.setFontWeight( QFont::Bold );
+  aRule.pattern = QRegExp( "(?:class\\s*)(\\b[A-Za-z0-9_]+)(?=[\\W])" );
+  aRule.capture = 1;
+  aRule.format = classFormat;
+  highlightingRules.append( aRule );
+
+  // Multi line comments
+  multiLineCommentFormat.setForeground( Qt::darkRed );
+  tripleQuotesExpression = QRegExp( "(:?\"[\"]\".*\"[\"]\"|'''.*''')" );
+  aRule.pattern = tripleQuotesExpression;
+  aRule.pattern.setMinimal( true );
+  aRule.format = multiLineCommentFormat;
+  aRule.capture = 0;
+  highlightingRules.append( aRule );
+
+  tripleSingleExpression = QRegExp( "'''(?!\")" );
+  tripleDoubleExpression = QRegExp( "\"\"\"(?!')" );
+
+  // Single comments
+  singleLineCommentFormat.setForeground( Qt::darkGray );
+  aRule.pattern = QRegExp( "#[^\n]*" );
+  aRule.format = singleLineCommentFormat;
+  aRule.capture = 0;
+  highlightingRules.append( aRule );
+}
+
+/*!
+  \return string list of Python keywords.
+ */
+QStringList PyEditor_PyHighlighter::keywords()
+{
+  QStringList aKeywords;
+  aKeywords << "and"
+            << "as"
+            << "assert"
+            << "break"
+            << "class"
+            << "continue"
+            << "def"
+            << "elif"
+            << "else"
+            << "except"
+            << "exec"
+            << "finally"
+            << "False"
+            << "for"
+            << "from"
+            << "global"
+            << "if"
+            << "import"
+            << "in"
+            << "is"
+            << "lambda"
+            << "None"
+            << "not"
+            << "or"
+            << "pass"
+            << "print"
+            << "raise"
+            << "return"
+            << "True"
+            << "try"
+            << "while"
+            << "with"
+            << "yield";
+  return aKeywords;
+}
+
+/*!
+  \return string list of special Python keywords.
+ */
+QStringList PyEditor_PyHighlighter::specialKeywords()
+{
+  QStringList aSpecialKeywords;
+  aSpecialKeywords << "ArithmeticError"
+                   << "AssertionError"
+                   << "AttributeError"
+                   << "EnvironmentError"
+                   << "EOFError"
+                   << "Exception"
+                   << "FloatingPointError"
+                   << "ImportError"
+                   << "IndentationError"
+                   << "IndexError"
+                   << "IOError"
+                   << "KeyboardInterrupt"
+                   << "KeyError"
+                   << "LookupError"
+                   << "MemoryError"
+                   << "NameError"
+                   << "NotImplementedError"
+                   << "OSError"
+                   << "OverflowError"
+                   << "ReferenceError"
+                   << "RuntimeError"
+                   << "StandardError"
+                   << "StopIteration"
+                   << "SyntaxError"
+                   << "SystemError"
+                   << "SystemExit"
+                   << "TabError"
+                   << "TypeError"
+                   << "UnboundLocalError"
+                   << "UnicodeDecodeError"
+                   << "UnicodeEncodeError"
+                   << "UnicodeError"
+                   << "UnicodeTranslateError"
+                   << "ValueError"
+                   << "WindowsError"
+                   << "ZeroDivisionError"
+                   << "Warning"
+                   << "UserWarning"
+                   << "DeprecationWarning"
+                   << "PendingDeprecationWarning"
+                   << "SyntaxWarning"
+                   << "OverflowWarning"
+                   << "RuntimeWarning"
+                   << "FutureWarning";
+  return aSpecialKeywords;
+}
+
+void PyEditor_PyHighlighter::highlightBlock( const QString& theText )
+{
+  TextBlockData* aData = new TextBlockData;
+  
+  insertBracketsData( RoundBrackets, aData, theText );
+  insertBracketsData( CurlyBrackets, aData, theText );
+  insertBracketsData( SquareBrackets, aData, theText );
+
+  setCurrentBlockUserData( aData );
+
+  foreach ( const HighlightingRule& rule, highlightingRules )
+  {
+    QRegExp expression( rule.pattern );
+    int anIndex = expression.indexIn( theText );
+    while ( anIndex >= 0 )
+    {
+      anIndex = expression.pos( rule.capture );
+      int aLength = expression.cap( rule.capture ).length();
+      setFormat( anIndex, aLength, rule.format );
+      anIndex = expression.indexIn( theText, anIndex + aLength );
+    }
+  }
+
+  setCurrentBlockState( NORMAL );
+
+  if ( theText.indexOf( tripleQuotesExpression ) != -1 )
+    return;
+
+  QList<int> aTripleSingle;
+  aTripleSingle << theText.indexOf( tripleSingleExpression ) << TRIPLESINGLE;
+
+  QList<int> aTripleDouble;
+  aTripleDouble << theText.indexOf( tripleDoubleExpression ) << TRIPLEDOUBLE;
+  QList< QList<int> > aTripleExpressions;
+  aTripleExpressions << aTripleSingle << aTripleDouble;
+
+  for ( int i = 0; i < aTripleExpressions.length(); i++ )
+  {
+    QList<int> aBlock = aTripleExpressions[i];
+    int anIndex = aBlock[0];
+    int aState = aBlock[1];
+    if ( previousBlockState() == aState )
+    {
+      if ( anIndex == -1 )
+      {
+        anIndex = theText.length();
+        setCurrentBlockState( aState );
+      }
+      setFormat( 0, anIndex + 3, multiLineCommentFormat );
+    }
+    else if ( anIndex > -1 )
+    {
+      setCurrentBlockState( aState );
+      setFormat( anIndex, theText.length(), multiLineCommentFormat );
+    }
+  }
+}
+
+void PyEditor_PyHighlighter::insertBracketsData( char theLeftSymbol,
+                                                 char theRightSymbol,
+                                                 TextBlockData* theData,
+                                                 const QString& theText )
+{
+  int leftPosition = theText.indexOf( theLeftSymbol );
+  while( leftPosition != -1 )
+  {
+    ParenthesisInfo* info = new ParenthesisInfo();
+    info->character = theLeftSymbol;
+    info->position = leftPosition;
+
+    theData->insert( info );
+    leftPosition = theText.indexOf( theLeftSymbol, leftPosition + 1 );
+  }
+
+  int rightPosition = theText.indexOf( theRightSymbol );
+  while( rightPosition != -1 )
+  {
+    ParenthesisInfo* info = new ParenthesisInfo();
+    info->character = theRightSymbol;
+    info->position = rightPosition;
+
+    theData->insert( info );
+    rightPosition = theText.indexOf( theRightSymbol, rightPosition + 1 );
+  }
+}
+
+void PyEditor_PyHighlighter::insertBracketsData( Brackets theBrackets,
+                                                 TextBlockData* theData,
+                                                 const QString& theText )
+{
+  char leftChar = '0';
+  char rightChar = '0';
+  
+  switch( theBrackets )
+  {
+  case RoundBrackets:
+    leftChar = '(';
+    rightChar = ')';
+    break;
+  case CurlyBrackets:
+    leftChar = '{';
+    rightChar = '}';
+    break;
+  case SquareBrackets:
+    leftChar = '[';
+    rightChar = ']';
+    break;
+  }
+
+  insertBracketsData( leftChar, rightChar, theData, theText );
+}
diff --git a/src/PyEditor/PyEditor_PyHighlighter.h b/src/PyEditor/PyEditor_PyHighlighter.h
new file mode 100644 (file)
index 0000000..dda957f
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_PyHighlighter.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYEDITOR_PYHIGHLIGHTER_H
+#define PYEDITOR_PYHIGHLIGHTER_H
+
+#include <QSyntaxHighlighter>
+
+class QTextDocument;
+
+class PyEditor_PyHighlighter : public QSyntaxHighlighter
+{
+  Q_OBJECT
+
+public:
+
+  struct ParenthesisInfo
+  {
+    char character;
+    int position;
+  };
+
+  class TextBlockData : public QTextBlockUserData
+  {
+  public:
+    TextBlockData();
+
+    QVector<ParenthesisInfo*> parentheses();
+    void insert( ParenthesisInfo* );
+
+  private:
+    QVector<ParenthesisInfo*> my_Parentheses;
+  };
+
+public:
+  PyEditor_PyHighlighter( QTextDocument* = 0 );
+
+  void initialize();
+  QStringList keywords();
+  QStringList specialKeywords();
+
+protected:
+  struct HighlightingRule
+  {
+    QRegExp pattern;
+    QTextCharFormat format;
+    int capture;
+  };
+  QVector<HighlightingRule> highlightingRules;
+
+  enum Brackets { RoundBrackets, CurlyBrackets, SquareBrackets };
+
+  QRegExp tripleQuotesExpression;
+  QRegExp tripleSingleExpression;
+  QRegExp tripleDoubleExpression;
+
+  QTextCharFormat classFormat;
+  QTextCharFormat referenceClassFormat;
+  QTextCharFormat functionFormat;
+  QTextCharFormat keywordFormat;
+  QTextCharFormat specialFromat;
+  QTextCharFormat numberFormat;
+  QTextCharFormat singleLineCommentFormat;
+  QTextCharFormat multiLineCommentFormat;
+  QTextCharFormat quotationFormat;
+
+  void highlightBlock( const QString& );
+  void insertBracketsData( char, char, TextBlockData*, const QString& );
+  void insertBracketsData( Brackets, TextBlockData*, const QString& );
+};
+
+#endif // PYEDITOR_PYHIGHLIGHTER_H
diff --git a/src/PyEditor/PyEditor_Settings.cxx b/src/PyEditor/PyEditor_Settings.cxx
new file mode 100644 (file)
index 0000000..1230ebb
--- /dev/null
@@ -0,0 +1,169 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_Settings.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyEditor_Settings.h"
+
+#include <QtxResourceMgr.h>
+
+#include <QDir>
+#include <QFile>
+#include <QSettings>
+
+/*!
+  \class PyEditor_Settings
+  \brief Manager of setting values.
+*/
+
+/*!
+  \brief Constructor.
+  \param isSingle flag determined single application or reccesed.
+*/
+PyEditor_Settings::PyEditor_Settings( QtxResourceMgr* theMgr, bool isSingle )
+    : p_HighlightCurrentLine( true ),
+      p_LineNumberArea( true ),
+      p_TextWrapping( false ),
+      p_CenterCursorOnScroll( true ),
+      p_TabSpaceVisible( true ),
+      p_TabSize( 4 ),
+      p_VerticalEdge( true ),
+      p_NumberColumns( 80 ),
+      p_Font( "Courier", 10 ),
+      m_ResourceMgr(theMgr),
+      m_Single(isSingle)
+
+{
+  if ( m_Single )
+  {
+    m_Settings = new QSettings( "config.ini", QSettings::IniFormat );
+    if ( !QFile::exists( m_Settings->fileName() ) )
+      toSettings( PY_EDITOR );
+  }
+
+  readSettings();
+}
+
+/*!
+  \brief Reads the setting values.
+ */
+void PyEditor_Settings::readSettings()
+{
+  if ( isSingle() )
+    fromSettings( PY_EDITOR );
+  else
+    readPreferences();
+}
+
+/*!
+  \brief Writes the setting values.
+ */
+void PyEditor_Settings::writeSettings()
+{
+  if ( isSingle() )
+    toSettings( PY_EDITOR );
+  else
+    writePreferences();
+}
+
+/*!
+  \return \c true if the application is single
+ */
+bool PyEditor_Settings::isSingle()
+{
+  return m_Single;
+}
+
+/*!
+  \brief Loads the setting values from file settings.
+ */
+void PyEditor_Settings::fromSettings( const QString &theCategory )
+{
+  QString aGroup = theCategory;
+  aGroup += "/";
+
+  p_HighlightCurrentLine = m_Settings->value(aGroup + QLatin1String( HIGHLIGHT_CURRLINE ), p_HighlightCurrentLine).toBool();
+  p_LineNumberArea       = m_Settings->value(aGroup + QLatin1String( LINE_NUMBER_AREA ), p_LineNumberArea).toBool();
+  p_TextWrapping         = m_Settings->value(aGroup + QLatin1String( TEXT_WRAP ), p_TextWrapping).toBool();
+  p_CenterCursorOnScroll = m_Settings->value(aGroup + QLatin1String( CURSOR_SCROLL ), p_CenterCursorOnScroll).toBool();
+  p_TabSpaceVisible      = m_Settings->value(aGroup + QLatin1String( TAB_WHITESPACES ), p_TabSpaceVisible).toBool();
+  p_TabSize              = m_Settings->value(aGroup + QLatin1String( TAB_SIZE ), p_TabSize).toInt();
+  p_VerticalEdge         = m_Settings->value(aGroup + QLatin1String( VERTICAL_EDGE ), p_VerticalEdge).toBool();
+  p_NumberColumns        = m_Settings->value(aGroup + QLatin1String( NUM_COLUMNS ), p_NumberColumns).toInt();
+  p_Font = QFont( m_Settings->value(aGroup + QLatin1String( FONT_FAMILY ), p_Font.family()).toString(),
+    m_Settings->value(aGroup + QLatin1String( FONT_SIZE ), p_Font.pointSize()).toInt() );
+}
+
+/*!
+  \brief Saves the setting values into file settings.
+ */
+void PyEditor_Settings::toSettings( const QString &theCategory ) const
+{
+  m_Settings->beginGroup( theCategory );
+  m_Settings->setValue( QLatin1String( HIGHLIGHT_CURRLINE ), p_HighlightCurrentLine );
+  m_Settings->setValue( QLatin1String( LINE_NUMBER_AREA ),   p_LineNumberArea );
+  m_Settings->setValue( QLatin1String( TEXT_WRAP ),          p_TextWrapping );
+  m_Settings->setValue( QLatin1String( CURSOR_SCROLL ),      p_CenterCursorOnScroll );
+  m_Settings->setValue( QLatin1String( TAB_WHITESPACES ),    p_TabSpaceVisible );
+  m_Settings->setValue( QLatin1String( TAB_SIZE ),           p_TabSize );
+  m_Settings->setValue( QLatin1String( VERTICAL_EDGE ),      p_VerticalEdge );
+  m_Settings->setValue( QLatin1String( NUM_COLUMNS ),        p_NumberColumns );
+  m_Settings->setValue( QLatin1String( FONT_FAMILY ),        p_Font.family() );
+  m_Settings->setValue( QLatin1String( FONT_SIZE ),          p_Font.pointSize() );
+  m_Settings->endGroup();
+}
+
+/*!
+  \brief Loads the setting values from setting resources.
+ */
+void PyEditor_Settings::readPreferences()
+{
+  if(m_ResourceMgr) 
+    {
+      p_HighlightCurrentLine = m_ResourceMgr->booleanValue( "PyEditor", "HighlightCurrentLine", p_HighlightCurrentLine );
+      p_LineNumberArea       = m_ResourceMgr->booleanValue( "PyEditor", "LineNumberArea", p_LineNumberArea );
+      p_TextWrapping         = m_ResourceMgr->booleanValue( "PyEditor", "TextWrapping", p_TextWrapping );
+      p_CenterCursorOnScroll = m_ResourceMgr->booleanValue( "PyEditor", "CenterCursorOnScroll", p_CenterCursorOnScroll );
+      p_TabSpaceVisible      = m_ResourceMgr->booleanValue( "PyEditor", "TabSpaceVisible", p_TabSpaceVisible );
+      p_TabSize              = m_ResourceMgr->integerValue( "PyEditor", "TabSize", p_TabSize );
+      p_VerticalEdge         = m_ResourceMgr->booleanValue( "PyEditor", "VerticalEdge", p_VerticalEdge );
+      p_NumberColumns        = m_ResourceMgr->integerValue( "PyEditor", "NumberColumns", p_NumberColumns );
+      p_Font                 = m_ResourceMgr->fontValue( "PyEditor", "Font", p_Font );
+    }
+}
+
+/*!
+  \brief Saves the setting values into setting resources.
+ */
+void PyEditor_Settings::writePreferences()
+{
+  if(m_ResourceMgr) 
+    {
+      m_ResourceMgr->setValue( "PyEditor", "HighlightCurrentLine", p_HighlightCurrentLine );
+      m_ResourceMgr->setValue( "PyEditor", "LineNumberArea", p_LineNumberArea );
+      m_ResourceMgr->setValue( "PyEditor", "TextWrapping", p_TextWrapping );
+      m_ResourceMgr->setValue( "PyEditor", "CenterCursorOnScroll", p_CenterCursorOnScroll );
+      m_ResourceMgr->setValue( "PyEditor", "TabSpaceVisible", p_TabSpaceVisible );
+      m_ResourceMgr->setValue( "PyEditor", "TabSize", p_TabSize );
+      m_ResourceMgr->setValue( "PyEditor", "VerticalEdge", p_VerticalEdge );
+      m_ResourceMgr->setValue( "PyEditor", "NumberColumns", p_NumberColumns );
+      m_ResourceMgr->setValue( "PyEditor", "Font", p_Font );
+    }
+}
diff --git a/src/PyEditor/PyEditor_Settings.h b/src/PyEditor/PyEditor_Settings.h
new file mode 100644 (file)
index 0000000..5efa018
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_Settings.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYEDITOR_SETTINGS_H
+#define PYEDITOR_SETTINGS_H
+
+#include <QFont>
+
+class QSettings;
+class QtxResourceMgr;
+
+const char* const PY_EDITOR          = "PythonEditor";
+const char* const HIGHLIGHT_CURRLINE = "HighlightCurrentLine";
+const char* const LINE_NUMBER_AREA   = "LineNumberArea";
+const char* const TEXT_WRAP          = "TextWrapping";
+const char* const CURSOR_SCROLL      = "CenterCursorOnScroll";
+const char* const TAB_WHITESPACES    = "TabSpaceVisible";
+const char* const TAB_SIZE           = "TabSize";
+const char* const VERTICAL_EDGE      = "VerticalEdge";
+const char* const NUM_COLUMNS        = "NumberColumns";
+const char* const FONT_FAMILY        = "FontFamily";
+const char* const FONT_SIZE          = "FontSize";
+
+class PyEditor_Settings
+{
+public:
+  PyEditor_Settings( QtxResourceMgr* = 0 ,  bool isSingle = true );
+
+  void readSettings();
+  void writeSettings();
+
+  void fromSettings( const QString& );
+  void toSettings( const QString& ) const;
+
+  void readPreferences();
+  void writePreferences();
+
+  bool isSingle();
+  
+  // Display settings
+  bool p_HighlightCurrentLine;
+  bool p_TextWrapping;
+  bool p_CenterCursorOnScroll;
+  bool p_LineNumberArea;
+  
+  // Vertical edge settings
+  bool p_VerticalEdge;
+  int  p_NumberColumns;
+  
+  // Tab settings
+  bool p_TabSpaceVisible;
+  int  p_TabSize;
+  
+  // Font settings
+  QFont p_Font;
+
+private:
+  QSettings*      m_Settings;
+  QtxResourceMgr* m_ResourceMgr;
+  bool            m_Single;
+};
+
+#endif // PYEDITOR_SETTINGS_H
diff --git a/src/PyEditor/PyEditor_SettingsDlg.cxx b/src/PyEditor/PyEditor_SettingsDlg.cxx
new file mode 100644 (file)
index 0000000..9b685be
--- /dev/null
@@ -0,0 +1,343 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_SettingsDlg.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyEditor_SettingsDlg.h"
+
+#include "PyEditor_Editor.h"
+#include "PyEditor_Settings.h"
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QFontComboBox>
+#include <QGroupBox>
+#include <QLabel>
+#include <QSpinBox>
+#include <QVBoxLayout>
+
+
+/*!
+  \class PyEditor_SettingsDlg
+  \brief Dialog settings for python editor.
+*/
+
+/*!
+  \brief Constructor.
+  \param theEditor widget that is used to edit and display text
+  \param theParent parent widget
+*/
+PyEditor_SettingsDlg::PyEditor_SettingsDlg( PyEditor_Editor* theEditor, QWidget* theParent ) :
+  QDialog( theParent ),
+  my_Editor( theEditor )
+{
+  setWindowTitle( tr("TIT_PY_PREF") );
+  QVBoxLayout* aMainLayout = new QVBoxLayout( this );
+  
+  // . Font settings <start>
+  QGroupBox* aFontSetBox = new QGroupBox( tr( "GR_FONT_SET" ) );
+  QHBoxLayout* aFontSetLayout = new QHBoxLayout( aFontSetBox );
+  QLabel* aFontFamilyLabel = new QLabel( tr( "LBL_FONT_FAM" ) );
+  w_FontFamily = new QFontComboBox;
+  w_FontFamily->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+  QLabel* aFontSizeLabel = new QLabel( tr( "LBL_FONT_SIZE" ) );
+  w_FontSize = new QComboBox;
+  w_FontSize->setInsertPolicy( QComboBox::NoInsert );
+  w_FontSize->setValidator( new QIntValidator( 1, 250, w_FontSize ) );
+  w_FontSize->setEditable( true );
+  w_FontSize->setMinimumContentsLength( 3 );
+  aFontSetLayout->addWidget( aFontFamilyLabel );
+  aFontSetLayout->addWidget( w_FontFamily );
+  aFontSetLayout->addWidget( aFontSizeLabel );
+  aFontSetLayout->addWidget( w_FontSize );
+  /*connect( w_FontFamily,  SIGNAL( currentFontChanged( const QFont& ) ),
+           this, SLOT( onFontChanged( const QFont& ) ) );*/
+  // . Font settings <end>
+  
+  // . Display settings <start>
+  QGroupBox* aDisplaySetBox = new QGroupBox( tr( "GR_DISP_SET" ) );
+  QVBoxLayout* aDisplaySetLayout = new QVBoxLayout;
+  w_HighlightCurrentLine = new QCheckBox( tr( "LBL_CURRLINE_HIGHLIGHT" ) );
+  w_TextWrapping = new QCheckBox( tr( "LBL_TEXT_WRAP" ) );
+  w_CenterCursorOnScroll = new QCheckBox( tr( "LBL_CURSOR_SCROLL" ) );
+  w_LineNumberArea = new QCheckBox( tr( "LBL_LINE_NUMBS_AREA" ) );
+  aDisplaySetLayout->addWidget( w_HighlightCurrentLine );
+  aDisplaySetLayout->addWidget( w_TextWrapping );
+  aDisplaySetLayout->addWidget( w_CenterCursorOnScroll );
+  aDisplaySetLayout->addWidget( w_LineNumberArea );
+  aDisplaySetLayout->addStretch( 1 );
+  aDisplaySetBox->setLayout( aDisplaySetLayout );
+  // . Display settings <end>
+
+  QHBoxLayout* aTabVertEdgeLayout = new QHBoxLayout;
+
+  // . Tab settings <start>
+  QGroupBox* aTabSetBox = new QGroupBox( tr( "GR_TAB_SET" ) );
+  QVBoxLayout* aTabSetLayout = new QVBoxLayout;
+  w_TabSpaceVisible = new QCheckBox( tr( "LBL_TAB_SPACES" ) );
+  QHBoxLayout* aTabSizeLayout = new QHBoxLayout;
+  QLabel* aTabSizeLabel = new QLabel( tr( "LBL_TAB_SIZE" ) );
+  w_TabSize = new QSpinBox;
+  w_TabSize->setMinimum( 0 );
+  w_TabSize->setSingleStep( 1 );
+  aTabSizeLayout->addWidget( aTabSizeLabel );
+  aTabSizeLayout->addWidget( w_TabSize );
+  aTabSizeLayout->addStretch( 1 );
+  aTabSetLayout->addWidget( w_TabSpaceVisible );
+  aTabSetLayout->addLayout( aTabSizeLayout );
+  aTabSetBox->setLayout( aTabSetLayout );
+  // . Tab settings <end>
+
+  // . Vertical edge settings <start>
+  QGroupBox* aVertEdgeSetBox = new QGroupBox( tr( "GR_VERT_EDGE_SET" ) );
+  QVBoxLayout* aVertEdgeLayout = new QVBoxLayout;
+  w_VerticalEdge = new QCheckBox( tr( "LBL_VERT_EDGE" ) );
+  QHBoxLayout* aNumberColLayout = new QHBoxLayout;
+  lbl_NumColumns = new QLabel( tr( "LBL_NUM_COLUMNS" ) );
+  w_NumberColumns = new QSpinBox;
+  w_NumberColumns->setMinimum( 0 );
+  w_NumberColumns->setSingleStep( 1 );
+  aNumberColLayout->addWidget( lbl_NumColumns );
+  aNumberColLayout->addWidget( w_NumberColumns );
+  aNumberColLayout->addStretch( 1 );
+  aVertEdgeLayout->addWidget( w_VerticalEdge );
+  aVertEdgeLayout->addLayout( aNumberColLayout );
+  aVertEdgeSetBox->setLayout( aVertEdgeLayout );
+  connect( w_VerticalEdge, SIGNAL( clicked( bool ) ), 
+           this, SLOT( onVerticalEdgeChecked( bool ) ) );
+  // . Vertical edge settings <end>
+
+  aTabVertEdgeLayout->addWidget( aTabSetBox );
+  aTabVertEdgeLayout->addWidget( aVertEdgeSetBox );
+
+  // . "Set as default" check box
+  w_DefaultCheck = new QCheckBox( tr( "WDG_SET_AS_DEFAULT_CHECK" ), this );
+  
+  aMainLayout->addWidget( aFontSetBox );
+  aMainLayout->addWidget( aDisplaySetBox );
+  aMainLayout->addLayout( aTabVertEdgeLayout );
+  aMainLayout->addWidget( w_DefaultCheck );
+  aMainLayout->addStretch( 1 );
+  
+  QHBoxLayout* aButtonLayout = new QHBoxLayout;
+  w_ButtonBox = new QDialogButtonBox( Qt::Horizontal );
+  w_ButtonBox->setStandardButtons( QDialogButtonBox::Ok
+                                 | QDialogButtonBox::Cancel
+                                 | QDialogButtonBox::Apply );
+  aButtonLayout->addWidget( w_ButtonBox, 1, Qt::AlignRight );
+  aMainLayout->addLayout( aButtonLayout );
+
+  connect( w_ButtonBox, SIGNAL( clicked( QAbstractButton* ) ),
+           this, SLOT( onClick( QAbstractButton* ) ) );
+
+  settingsToGui();
+
+  onFontChanged( currentFont() );
+}
+
+/*!
+  \brief Set currently selected font.
+  \param fnt current font
+  \sa currentFont()
+*/
+void PyEditor_SettingsDlg::setCurrentFont( const QFont& fnt )
+{
+  w_FontFamily->blockSignals( true );
+  w_FontSize->blockSignals( true );
+  
+  setFontFamily( fnt.family() );
+  setFontSize( fnt.pointSize() );
+
+  w_FontFamily->blockSignals( false );
+  w_FontSize->blockSignals( false );
+}
+
+/*!
+  \brief Get currently selected font.
+  \return current font
+  \sa setCurrentFont()
+*/
+QFont PyEditor_SettingsDlg::currentFont() const
+{
+  return QFont( fontFamily(), fontSize() );
+}
+
+/*!
+  \brief Set font size.
+  \param s size value
+  \sa fontSize()
+*/
+void PyEditor_SettingsDlg::setFontSize( const int s )
+{
+  if ( s <= 0 )
+    return;
+
+  int idx = w_FontSize->findText( QString::number( s ) );
+  if ( idx != -1 )
+    w_FontSize->setCurrentIndex( idx );
+  else if ( w_FontSize->isEditable() )
+    w_FontSize->setEditText( QString::number( s ) );
+}
+
+/*!
+  \brief Get font size.
+  \return size value
+  \sa setFontSize()
+*/
+int PyEditor_SettingsDlg::fontSize() const
+{
+  bool ok;
+  int pSize = w_FontSize->currentText().toInt( &ok );
+  return ok ? pSize : 0;
+}
+
+/*!
+  \brief Set font family name.
+  \param theFamily new font family name
+  \sa fontFamily()
+*/
+void PyEditor_SettingsDlg::setFontFamily( const QString& theFamily )
+{
+  w_FontFamily->setCurrentFont( QFont( theFamily ) );
+  onFontChanged( w_FontFamily->currentFont() );
+}
+
+/*!
+  \brief Get font family name.
+  \return font family name
+  \sa setFontFamily()
+*/
+QString PyEditor_SettingsDlg::fontFamily() const
+{
+  return w_FontFamily->currentFont().family();
+}
+
+/*!
+  \brief Get "Set settings as default" check box value.
+  \return \c true if "Set settings as default" check box is on
+*/
+bool PyEditor_SettingsDlg::isSetAsDefault()
+{
+  return w_DefaultCheck->isChecked();
+}
+
+/*!
+  SLOT: Perform dialog actions
+  \param theButton button
+*/
+void PyEditor_SettingsDlg::onClick( QAbstractButton* theButton )
+{
+  QDialogButtonBox::ButtonRole aButtonRole = w_ButtonBox->buttonRole( theButton );
+  if ( aButtonRole == QDialogButtonBox::AcceptRole )
+  {
+    settingsFromGui();
+    setSettings();
+    accept();
+  }
+  else if ( aButtonRole == QDialogButtonBox::ApplyRole )
+  {
+    settingsFromGui();
+    setSettings();
+  }
+  else if ( aButtonRole == QDialogButtonBox::RejectRole )
+  {
+    reject();
+  }
+}
+
+/*!
+  SLOT: Changes the widget visibility depending on the set theState flag.
+  \param theState flag of visibility
+ */
+void PyEditor_SettingsDlg::onVerticalEdgeChecked( bool theState )
+{
+  lbl_NumColumns->setEnabled( theState );
+  w_NumberColumns->setEnabled( theState );
+}
+
+/*!
+  \brief Called when current font is changed.
+  \param theFont (not used)
+*/
+void PyEditor_SettingsDlg::onFontChanged( const QFont& /*theFont*/ )
+{
+  bool blocked = w_FontSize->signalsBlocked();
+  w_FontSize->blockSignals( true );
+
+  int s = fontSize();
+  w_FontSize->clear();
+
+  QList<int> szList = QFontDatabase().pointSizes( fontFamily() );
+  QStringList sizes;
+  for ( QList<int>::const_iterator it = szList.begin(); it != szList.end(); ++it )
+    sizes.append( QString::number( *it ) );
+  w_FontSize->addItems( sizes );
+
+  setFontSize( s );
+
+  w_FontSize->blockSignals( blocked );
+}
+
+/*!
+  \brief Sets settings from preferences dialog.
+ */
+void PyEditor_SettingsDlg::settingsFromGui()
+{
+  my_Editor->settings()->p_HighlightCurrentLine = w_HighlightCurrentLine->isChecked();
+  my_Editor->settings()->p_TextWrapping = w_TextWrapping->isChecked();
+  my_Editor->settings()->p_CenterCursorOnScroll = w_CenterCursorOnScroll->isChecked();
+  my_Editor->settings()->p_LineNumberArea = w_LineNumberArea->isChecked();
+  my_Editor->settings()->p_TabSpaceVisible = w_TabSpaceVisible->isChecked();
+  my_Editor->settings()->p_TabSize = w_TabSize->value();
+  my_Editor->settings()->p_VerticalEdge = w_VerticalEdge->isChecked();
+  my_Editor->settings()->p_NumberColumns = w_NumberColumns->value();
+  my_Editor->settings()->p_Font = currentFont();
+}
+
+/*!
+  \brief Sets settings into preferences dialog.
+ */
+void PyEditor_SettingsDlg::settingsToGui()
+{
+  w_HighlightCurrentLine->setChecked( my_Editor->settings()->p_HighlightCurrentLine );
+  w_TextWrapping->setChecked( my_Editor->settings()->p_TextWrapping );
+  w_CenterCursorOnScroll->setChecked( my_Editor->settings()->p_CenterCursorOnScroll );
+  w_LineNumberArea->setChecked( my_Editor->settings()->p_LineNumberArea );
+  w_TabSpaceVisible->setChecked( my_Editor->settings()->p_TabSpaceVisible );
+  w_TabSize->setValue( my_Editor->settings()->p_TabSize );
+  w_VerticalEdge->setChecked( my_Editor->settings()->p_VerticalEdge );
+  w_NumberColumns->setValue( my_Editor->settings()->p_NumberColumns );
+  setCurrentFont( my_Editor->settings()->p_Font );
+
+  onVerticalEdgeChecked( my_Editor->settings()->p_VerticalEdge );
+}
+
+/*!
+  \brief Sets settings into the setting resources or file
+  if the flag is set as default is true.
+ */
+void PyEditor_SettingsDlg::setSettings()
+{
+  if ( isSetAsDefault() )
+    my_Editor->settings()->writeSettings();
+
+  my_Editor->updateStatement();
+}
diff --git a/src/PyEditor/PyEditor_SettingsDlg.h b/src/PyEditor/PyEditor_SettingsDlg.h
new file mode 100644 (file)
index 0000000..f585822
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_SettingsDlg.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYEDITOR_SETTINGSDLG_H
+#define PYEDITOR_SETTINGSDLG_H
+
+#include <QDialog>
+
+class PyEditor_Editor;
+class QAbstractButton;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QFontComboBox;
+class QLabel;
+class QSpinBox;
+
+class PyEditor_SettingsDlg : public QDialog
+{
+  Q_OBJECT
+
+public:
+  explicit PyEditor_SettingsDlg( PyEditor_Editor*, QWidget* = 0 );
+
+  void    setCurrentFont( const QFont& );
+  QFont   currentFont() const;
+
+  void    setFontSize( const int );
+  int     fontSize() const;
+
+  void    setFontFamily( const QString& );
+  QString fontFamily() const;
+
+  bool    isSetAsDefault();
+
+public Q_SLOTS:
+  void onClick( QAbstractButton* );
+
+private Q_SLOTS:
+  void onVerticalEdgeChecked( bool );
+  void onFontChanged( const QFont& );
+
+private:
+  void settingsToGui();
+  void settingsFromGui();
+  void setSettings();
+  
+  QCheckBox*        w_HighlightCurrentLine;
+  QCheckBox*        w_TextWrapping;
+  QCheckBox*        w_CenterCursorOnScroll;
+  QCheckBox*        w_LineNumberArea;
+
+  QCheckBox*        w_TabSpaceVisible;
+  QSpinBox*         w_TabSize;
+
+  QCheckBox*        w_VerticalEdge;
+  QSpinBox*         w_NumberColumns;
+  QLabel*           lbl_NumColumns;
+
+  QFontComboBox*    w_FontFamily;
+  QComboBox*        w_FontSize;
+
+  QCheckBox*        w_DefaultCheck;
+
+  QDialogButtonBox* w_ButtonBox;
+
+  PyEditor_Editor*  my_Editor;
+};
+
+#endif // PYEDITOR_SETTINGSDLG_H
diff --git a/src/PyEditor/resources/translations/PyEditor_msg_en.ts b/src/PyEditor/resources/translations/PyEditor_msg_en.ts
new file mode 100644 (file)
index 0000000..047f661
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en">
+  <context>
+    <name>PyEditor_SettingsDlg</name>
+    <message>
+      <source>TIT_PY_PREF</source>
+      <translation>Preferences</translation>
+    </message>
+    <message>
+      <source>GR_FONT_SET</source>
+      <translation>Font settings</translation>
+    </message>
+    <message>
+      <source>LBL_FONT_FAM</source>
+      <translation>Family:</translation>
+    </message>
+    <message>
+      <source>LBL_FONT_SIZE</source>
+      <translation>Size:</translation>
+    </message>
+    <message>
+      <source>GR_DISP_SET</source>
+      <translation>Display settings</translation>
+    </message>
+    <message>
+      <source>LBL_CURRLINE_HIGHLIGHT</source>
+      <translation>Enable current line highlight</translation>
+    </message>
+    <message>
+      <source>LBL_TEXT_WRAP</source>
+      <translation>Enable text wrapping</translation>
+    </message>
+    <message>
+      <source>LBL_CURSOR_SCROLL</source>
+      <translation>Center cursor on scroll</translation>
+    </message>
+    <message>
+      <source>LBL_LINE_NUMBS_AREA</source>
+      <translation>Display line numbers area</translation>
+    </message>
+    <message>
+      <source>GR_TAB_SET</source>
+      <translation>Tab settings</translation>
+    </message>
+    <message>
+      <source>LBL_TAB_SPACES</source>
+      <translation>Display tab white spaces</translation>
+    </message>
+    <message>
+      <source>LBL_TAB_SIZE</source>
+      <translation>Tab size:</translation>
+    </message>
+    <message>
+      <source>GR_VERT_EDGE_SET</source>
+      <translation>Vertical edge settings</translation>
+    </message>
+    <message>
+      <source>LBL_VERT_EDGE</source>
+      <translation>Display vertical edge</translation>
+    </message>
+    <message>
+      <source>LBL_NUM_COLUMNS</source>
+      <translation>Number of columns:</translation>
+    </message>
+    <message>
+      <source>WDG_SET_AS_DEFAULT_CHECK</source>
+      <translation>Save settings as default</translation>
+    </message>
+  </context>
+</TS>
diff --git a/src/PyEditor/resources/translations/PyEditor_msg_fr.ts b/src/PyEditor/resources/translations/PyEditor_msg_fr.ts
new file mode 100644 (file)
index 0000000..e92905e
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr" sourcelanguage="en">
+<context>
+    <name>PyEditor_SettingsDlg</name>
+</context>
+</TS>
diff --git a/src/PyEditor/resources/translations/PyEditor_msg_ja.ts b/src/PyEditor/resources/translations/PyEditor_msg_ja.ts
new file mode 100644 (file)
index 0000000..7d5d4ad
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="ja" sourcelanguage="en">
+<context>
+    <name>PyEditor_SettingsDlg</name>
+</context>
+</TS>
diff --git a/src/PyViewer/CMakeLists.txt b/src/PyViewer/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2f99617
--- /dev/null
@@ -0,0 +1,92 @@
+# Copyright (C) 2015 OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+#
+
+INCLUDE(UseQt4Ext)
+
+# additional include directories
+INCLUDE_DIRECTORIES(
+  ${QT_INCLUDES}
+  ${PROJECT_SOURCE_DIR}/src/Qtx
+  ${PROJECT_SOURCE_DIR}/src/SUIT
+  ${PROJECT_SOURCE_DIR}/src/PyEditor
+)
+
+# additional preprocessor / compiler flags
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+
+# libraries to link to
+SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES} qtx suit PyEditor)
+
+# header files / to be processed by moc
+SET(_moc_HEADERS
+  PyViewer_ViewManager.h
+  PyViewer_ViewModel.h
+  PyViewer_ViewWindow.h
+)
+
+# header files / no moc processing
+SET(_other_HEADERS
+  PyViewer.h
+)
+
+# header files / to install
+SET(PyViewer_HEADERS ${_moc_HEADERS} ${_other_HEADERS})
+
+# resource files / to be processed by lrelease
+SET(RESOURCES_PATH resources)
+
+SET(_ts_RESOURCES
+  ${RESOURCES_PATH}/translations/PyViewer_msg_en.ts
+  ${RESOURCES_PATH}/translations/PyViewer_msg_fr.ts
+  ${RESOURCES_PATH}/translations/PyViewer_msg_ja.ts
+)
+
+# resource files / to be processed by rcc
+SET(_rcc_RESOURCES ${RESOURCES_PATH}/PyEditor.qrc)
+
+# sources / moc wrappings
+QT4_WRAP_CPP(_moc_SOURCES ${_moc_HEADERS})
+
+# sources / rcc wrappings
+QT4_ADD_RESOURCES(_rcc_SOURCES ${_rcc_RESOURCES})
+
+# sources / static
+SET(_other_SOURCES
+  PyViewer_ViewManager.cxx
+  PyViewer_ViewModel.cxx
+  PyViewer_ViewWindow.cxx
+)
+
+# sources / to compile
+SET(PyViewer_SOURCES ${_other_SOURCES} ${_moc_SOURCES})
+
+# --- rules ---
+ADD_LIBRARY(PyViewer ${PyViewer_SOURCES} ${_rcc_SOURCES})
+TARGET_LINK_LIBRARIES(PyViewer ${_link_LIBRARIES})
+INSTALL(TARGETS PyViewer EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_LIBS})
+
+ADD_EXECUTABLE(DummyPyEditor PyViewer.cxx)
+SET_TARGET_PROPERTIES(DummyPyEditor PROPERTIES OUTPUT_NAME "PyEditor")
+TARGET_LINK_LIBRARIES(DummyPyEditor ${_link_LIBRARIES} PyEditor PyViewer)
+INSTALL(TARGETS DummyPyEditor EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_BINS})
+
+INSTALL(FILES ${PyViewer_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS})
+QT4_INSTALL_TS_RESOURCES("${_ts_RESOURCES}" "${SALOME_GUI_INSTALL_RES_DATA}")
diff --git a/src/PyViewer/PyViewer.cxx b/src/PyViewer/PyViewer.cxx
new file mode 100644 (file)
index 0000000..8721a78
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include <QApplication>
+#include <QDir>
+#include <QLibraryInfo>
+#include <QLocale>
+
+#include "PyViewer_ViewWindow.h"
+
+#include <QtxTranslator.h>
+
+int main( int argc, char *argv[] )
+{
+    QApplication anApplication( argc, argv );
+
+    // Load translations
+    QtxTranslator aTranslatorEd , aTranslatorVi, aTranslatorQt;
+    QString aLanguage = QLocale::system().name().split('_', QString::SkipEmptyParts)[0];
+    if ( !aLanguage.isEmpty() )
+    {
+      if ( aTranslatorQt.load( QString( "qt_%1" ).arg( aLanguage ), QLibraryInfo::location( QLibraryInfo::TranslationsPath ) ) )
+        anApplication.installTranslator( &aTranslatorQt );
+
+      QDir appDir = QApplication::applicationDirPath();
+      appDir.cdUp(); appDir.cdUp();
+
+      if ( aTranslatorEd.load( QString( "PyEditor_msg_%1" ).arg( aLanguage ), appDir.filePath( "share/salome/resources/gui" ) ) )
+        anApplication.installTranslator( &aTranslatorEd );
+    
+      if ( aTranslatorVi.load( QString( "PyViewer_msg_%1" ).arg( aLanguage ), appDir.filePath( "share/salome/resources/gui" ) ) )
+        anApplication.installTranslator( &aTranslatorVi );
+    }
+
+    PyViewer_ViewWindow aViewWin;
+    aViewWin.resize( 650, 700 );
+    aViewWin.show();
+
+    return anApplication.exec();
+}
diff --git a/src/PyViewer/PyViewer.h b/src/PyViewer/PyViewer.h
new file mode 100644 (file)
index 0000000..cb481e4
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifdef WIN32
+
+#if defined PYVIEWER_EXPORTS || defined PyViewer_EXPORTS
+#define PYVIEWER_EXPORT __declspec(dllexport)
+#else
+#define PYVIEWER_EXPORT __declspec(dllimport)
+#endif
+
+#else
+#define PYVIEWER_EXPORT
+#endif // WIN32
diff --git a/src/PyViewer/PyViewer_ViewManager.cxx b/src/PyViewer/PyViewer_ViewManager.cxx
new file mode 100644 (file)
index 0000000..724eb13
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer_ViewManager.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyViewer_ViewManager.h"
+
+#include "PyViewer_ViewModel.h"
+
+/*!
+  \class PyViewer_ViewManager
+  \brief Python viewer view manager.
+*/
+
+/*!
+  \brief Constructor.
+  \param theStudy study
+  \param theDesktop parent desktop window
+*/
+PyViewer_ViewManager::PyViewer_ViewManager( SUIT_Study* theStudy,
+                                            SUIT_Desktop* theDesktop )
+: SUIT_ViewManager( theStudy, theDesktop, new PyViewer_Viewer() )
+{
+  setTitle( tr( "PYVIEWER_VIEW_TITLE" ) );
+}
+
+/*!
+  \brief Destructor.
+*/
+PyViewer_ViewManager::~PyViewer_ViewManager()
+{
+}
diff --git a/src/PyViewer/PyViewer_ViewManager.h b/src/PyViewer/PyViewer_ViewManager.h
new file mode 100644 (file)
index 0000000..5913292
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyEditor_ViewManager.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYVIEWER_VIEWMANAGER_H
+#define PYVIEWER_VIEWMANAGER_H
+
+#include "PyViewer.h"
+
+#include <SUIT_ViewManager.h>
+
+class PYVIEWER_EXPORT PyViewer_ViewManager : public SUIT_ViewManager
+{
+  Q_OBJECT
+
+public:
+  PyViewer_ViewManager( SUIT_Study* theStudy,
+                        SUIT_Desktop* theDesktop );
+  virtual ~PyViewer_ViewManager();
+};
+
+#endif // PYVIEWER_VIEWMANAGER_H
diff --git a/src/PyViewer/PyViewer_ViewModel.cxx b/src/PyViewer/PyViewer_ViewModel.cxx
new file mode 100644 (file)
index 0000000..f6c5ff5
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer_ViewModel.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyViewer_ViewModel.h"
+
+#include "PyViewer_ViewWindow.h"
+
+/*!
+  \class PyViewer_Viewer
+  \brief Python view model.
+*/
+
+/*!
+  \brief Constructor.
+*/
+PyViewer_Viewer::PyViewer_Viewer() : SUIT_ViewModel()
+{
+}
+
+/*!
+  \brief Destructor.
+*/
+PyViewer_Viewer::~PyViewer_Viewer()
+{
+}
+
+/*!
+  Create new instance of view window on desktop \a theDesktop.
+  \retval SUIT_ViewWindow* - created view window pointer.
+*/
+SUIT_ViewWindow* PyViewer_Viewer::createView( SUIT_Desktop* theDesktop )
+{
+  PyViewer_ViewWindow* aPyViewer = new PyViewer_ViewWindow( theDesktop, this );
+  initView( aPyViewer );
+  return aPyViewer;
+}
+
+/*!
+  Start initialization of view window
+  \param view - view window to be initialized
+*/
+void PyViewer_Viewer::initView( PyViewer_ViewWindow* theViewModel )
+{
+  if ( theViewModel )
+    theViewModel->initLayout();
+}
diff --git a/src/PyViewer/PyViewer_ViewModel.h b/src/PyViewer/PyViewer_ViewModel.h
new file mode 100644 (file)
index 0000000..9c5fb29
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer_ViewModel.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYVIEWER_VIEWMODEL_H
+#define PYVIEWER_VIEWMODEL_H
+
+#include "PyViewer.h"
+
+#include <SUIT_ViewModel.h>
+
+class PyViewer_ViewWindow;
+class SUIT_ViewWindow;
+class SUIT_Desktop;
+
+class PYVIEWER_EXPORT PyViewer_Viewer : public SUIT_ViewModel
+{
+  Q_OBJECT
+
+public:
+  PyViewer_Viewer();
+  virtual ~PyViewer_Viewer();
+
+  virtual SUIT_ViewWindow* createView( SUIT_Desktop* theDesktop );
+
+  virtual QString getType() const { return Type(); }
+  static  QString Type() { return "PyViewer"; }
+
+protected:
+  void initView( PyViewer_ViewWindow* theViewModel );
+};
+
+#endif // PYVIEWER_VIEWMODEL_H
+
diff --git a/src/PyViewer/PyViewer_ViewWindow.cxx b/src/PyViewer/PyViewer_ViewWindow.cxx
new file mode 100644 (file)
index 0000000..8b8bab8
--- /dev/null
@@ -0,0 +1,489 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer_ViewWindow.cxx
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#include "PyViewer_ViewWindow.h"
+
+#include "PyEditor_Editor.h"
+#include "PyEditor_Settings.h"
+#include "PyEditor_SettingsDlg.h"
+
+#include <SUIT_Session.h>
+#include <SUIT_ResourceMgr.h>
+
+#include <QtxAction.h>
+#include <QtxActionToolMgr.h>
+#include <QtxMultiAction.h>
+
+#include <QtGui>
+#include <QLocale>
+
+/*!
+  \class PyViewer_ViewWindow
+  \brief Python view window.
+*/
+
+/*!
+  \brief Constructor.
+  \param theParent parent widget
+*/
+PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* theDesktop , PyViewer_Viewer* theModel ) :
+  SUIT_ViewWindow(theDesktop),
+  myModel(theModel)
+{
+  my_IsExternal = (theDesktop == NULL);
+
+  if( isExternal() )
+    initLayout();
+}
+
+void PyViewer_ViewWindow::initLayout()
+{
+  my_TextEditor = new PyEditor_Editor( my_IsExternal ,SUIT_Session::session()->resourceMgr(), this );
+  setCentralWidget( my_TextEditor );
+
+  createActions();
+  createToolBar();
+  setCurrentFile( QString() );
+    
+  if ( isExternal() )
+    {
+      connect( my_TextEditor->document(), SIGNAL( modificationChanged( bool ) ),
+              this, SLOT( setWindowModified( bool ) ) );
+      
+      statusBar()->showMessage( tr("STS_READY") );
+    }  
+}
+
+/*!
+  \brief Destructor.
+ */
+PyViewer_ViewWindow::~PyViewer_ViewWindow()
+{
+  my_CurrentFile.clear();
+  delete my_TextEditor;
+}
+
+/*!
+  \return \c true if the application is external
+ */
+bool PyViewer_ViewWindow::isExternal()
+{
+  return my_IsExternal;
+}
+
+/*!
+  \brief Creates actions of Python view window.
+*/
+void PyViewer_ViewWindow::createActions()
+{
+  QtxActionToolMgr* aMgr = toolMgr();
+  QtxAction* anAction;
+
+  // 1. File operations
+  // 1.1. Create New action
+  anAction = new QtxAction( tr( "MNU_PY_NEW" ), QIcon( ":/images/py_new.png" ),
+                                 tr( "MNU_PY_NEW" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_NEW" ) );
+  anAction->setShortcuts( QKeySequence::New );
+  connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onNew() ) );
+  aMgr->registerAction( anAction, NewId );
+
+  // 1.2 Create Open action
+  anAction = new QtxAction( tr( "MNU_PY_OPEN" ), QIcon( ":/images/py_open.png" ),
+                                  tr( "MNU_PY_OPEN" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_OPEN" ) );
+  anAction->setShortcuts( QKeySequence::Open );
+  connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onOpen() ) );
+  aMgr->registerAction( anAction, OpenId );
+
+  // 1.3. Create Save action
+  anAction = new QtxAction( tr( "MNU_PY_SAVE" ), QIcon( ":/images/py_save.png" ),
+                                  tr( "MNU_PY_SAVE" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_SAVE" ) );
+  anAction->setShortcuts( QKeySequence::Save );
+  connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) );
+  aMgr->registerAction( anAction, SaveId );
+  // Set default statement for Save action
+  anAction->setEnabled( my_TextEditor->document()->isModified() );
+  connect( my_TextEditor->document(), SIGNAL( modificationChanged( bool ) ),
+    anAction, SLOT( setEnabled( bool ) ) );
+
+  // 1.4. Create SaveAs action
+  anAction = new QtxAction( tr( "MNU_PY_SAVEAS" ), QIcon( ":/images/py_save_as.png" ),
+                                    tr( "MNU_PY_SAVEAS" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_SAVEAS" ) );
+  anAction->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_S );
+  connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onSaveAs() ) );
+  aMgr->registerAction( anAction, SaveAsId );
+
+  // 1.5 Create multi-action for file operations
+  /*QtxMultiAction* aFileAction = new QtxMultiAction( this );
+  aFileAction->insertAction( aMgr->action( NewId ) );
+  aFileAction->insertAction( aMgr->action( OpenId ) );
+  aFileAction->insertAction( aMgr->action( SaveId ) );
+  aFileAction->insertAction( aMgr->action( SaveAsId ) );
+  aMgr->registerAction( aFileAction, FileOpId );*/
+
+  // 1.6. Create Close action
+  if (isExternal())
+  {
+    anAction = new QtxAction( tr( "MNU_PY_CLOSE" ), QIcon( ":/images/py_close.png" ),
+                              tr( "MNU_PY_CLOSE" ), 0, this );
+    anAction->setStatusTip( tr( "DSC_PY_CLOSE" ) );
+    anAction->setShortcut( Qt::CTRL + Qt::Key_Q );
+    connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( close() ) );
+    aMgr->registerAction( anAction, CloseId );
+  }
+
+  // 2. Edit operations
+  // 2.1. Create Undo action
+  anAction = new QtxAction( tr( "MNU_PY_UNDO" ), QIcon( ":/images/py_undo.png" ),
+                            tr( "MNU_PY_UNDO" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_UNDO" ) );
+  anAction->setShortcuts( QKeySequence::Undo );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( undo() ) );
+  aMgr->registerAction( anAction, UndoId );
+  // Set default statement for Undo action
+  anAction->setEnabled( my_TextEditor->document()->isUndoAvailable() );
+  connect( my_TextEditor->document(), SIGNAL( undoAvailable( bool ) ),
+           anAction, SLOT( setEnabled( bool ) ) );
+
+  // 2.2. Create Redo action
+  anAction = new QtxAction( tr( "MNU_PY_REDO" ), QIcon( ":/images/py_redo.png" ),
+                            tr( "MNU_PY_REDO" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_REDO" ) );
+  anAction->setShortcuts( QKeySequence::Redo );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( redo() ) );
+  aMgr->registerAction( anAction, RedoId );
+  // Set default statement for Redo action
+  anAction->setEnabled( my_TextEditor->document()->isRedoAvailable() );
+  connect( my_TextEditor->document(), SIGNAL( redoAvailable( bool ) ),
+           anAction, SLOT( setEnabled( bool ) ) );
+
+  // 2.3. Create Cut action
+  anAction = new QtxAction( tr( "MNU_PY_CUT" ), QIcon( ":/images/py_cut.png" ),
+                            tr( "MNU_PY_CUT" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_CUT" ) );
+  anAction->setShortcuts( QKeySequence::Cut );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( cut() ) );
+  aMgr->registerAction( anAction, CutId );
+  // Set default statement for Cut action
+  anAction->setEnabled( false );
+  connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
+           anAction, SLOT( setEnabled( bool ) ) );
+
+  // 2.4. Create Copy action
+  anAction = new QtxAction( tr( "MNU_PY_COPY" ), QIcon( ":/images/py_copy.png" ),
+                            tr( "MNU_PY_COPY" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_COPY" ) );
+  anAction->setShortcuts( QKeySequence::Copy );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( copy() ) );
+  aMgr->registerAction( anAction, CopyId );
+  // Set default statement for Copy action
+  anAction->setEnabled( false );
+  connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
+           anAction, SLOT( setEnabled( bool ) ) );
+
+  // 2.5. Create Paste action
+  anAction = new QtxAction( tr( "MNU_PY_PASTE" ), QIcon( ":/images/py_paste.png" ),
+                            tr( "MNU_PY_PASTE" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_PASTE" ) );
+  anAction->setShortcuts( QKeySequence::Paste );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( paste() ) );
+  aMgr->registerAction( anAction, PasteId );
+
+  // 2.6. Create Delete action
+  anAction = new QtxAction( tr( "MNU_PY_DELETE" ), QIcon( ":/images/py_delete.png" ),
+                            tr( "MNU_PY_DELETE" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_DELETE" ) );
+  anAction->setShortcuts( QKeySequence::Delete );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( deleteSelected() ) );
+  aMgr->registerAction( anAction, DeleteId );
+  // Set default statement for Delete action
+  anAction->setEnabled( false );
+  connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
+           anAction, SLOT( setEnabled( bool ) ) );
+
+  // 2.7. Create SelectAll action
+  anAction = new QtxAction( tr( "MNU_PY_SELECTALL" ), QIcon( ":/images/py_select_all.png" ),
+                            tr( "MNU_PY_SELECTALL" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_SELECT_ALL" ) );
+  anAction->setShortcuts( QKeySequence::SelectAll );
+  connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( selectAll() ) );
+  aMgr->registerAction( anAction, SelectAllId );
+
+  // 2.8. Create multi-action for edit operations
+  /*QtxMultiAction* anEditAction = new QtxMultiAction( this );
+  anEditAction->insertAction( aMgr->action( UndoId ) );
+  anEditAction->insertAction( aMgr->action( RedoId ) );
+  anEditAction->insertAction( aMgr->action( CutId ) );
+  anEditAction->insertAction( aMgr->action( CopyId ) );
+  anEditAction->insertAction( aMgr->action( PasteId ) );
+  anEditAction->insertAction( aMgr->action( DeleteId ) );
+  anEditAction->insertAction( aMgr->action( SelectAllId ) );
+  aMgr->registerAction( anEditAction, EditOpId );*/
+
+  // 3. Create Preference action
+  anAction = new QtxAction( tr( "MNU_PY_PREFERENCES" ), QIcon( ":/images/py_preferences.png" ),
+                            tr( "MNU_PY_PREFERENCES" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_PREFERENCES" ) );
+  connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onPreferences() ) );
+  aMgr->registerAction( anAction, PreferencesId );
+
+  // 4. Help operations
+
+  // 4.1. Create Help action
+  anAction = new QtxAction( tr( "MNU_PY_BROWSER" ), QIcon( ":/images/py_browser.png" ),
+                            tr( "MNU_PY_BROWSER" ), 0, this );
+  anAction->setStatusTip( tr( "DSC_PY_BROWSER" ) );
+  connect( anAction, SIGNAL( triggered() ), this, SLOT( onBrowser() ) );
+  aMgr->registerAction( anAction, BrowserId );
+
+  // 4.2. Create multi-action for help operations
+  /*QtxMultiAction* aHelpAction = new QtxMultiAction( this );
+  aHelpAction->insertAction( aMgr->action( BrowserId ) );
+  aMgr->registerAction( aHelpAction, HelpOpId );*/
+}
+
+/*!
+  \brief Create toolbar for the python view window.
+*/
+void PyViewer_ViewWindow::createToolBar()
+{
+  QtxActionToolMgr* aMgr = toolMgr();
+  int idTB = aMgr->createToolBar( tr("LBL_TOOLBAR_LABEL"),         // title (language-dependent)
+                                  QString( "PyEditorOperations" ), // name (language-independent)
+                                  false );                         // disable floatable toolbar
+  aMgr->append( NewId, idTB );
+  aMgr->append( OpenId, idTB );
+  aMgr->append( SaveId, idTB );
+  aMgr->append( SaveAsId, idTB );
+  if ( isExternal() )
+    aMgr->append( CloseId, idTB );
+  aMgr->append( aMgr->separator(), idTB );
+  aMgr->append( UndoId, idTB );
+  aMgr->append( RedoId, idTB );
+  aMgr->append( aMgr->separator(), idTB );
+  aMgr->append( CutId, idTB );
+  aMgr->append( CopyId, idTB );
+  aMgr->append( PasteId, idTB );
+  aMgr->append( DeleteId, idTB );
+  aMgr->append( SelectAllId, idTB );
+  aMgr->append( aMgr->separator(), idTB );
+  aMgr->append( PreferencesId, idTB );
+  aMgr->append( aMgr->separator(), idTB );
+  aMgr->append( BrowserId, idTB );
+
+}
+
+/*!
+  \brief Reimplemented class is to receive a window close request.
+  \param theEvent event
+*/
+void PyViewer_ViewWindow::closeEvent( QCloseEvent* theEvent )
+{
+  if ( whetherSave() )
+    theEvent->accept();
+  else
+    theEvent->ignore();
+}
+
+/*!
+  SLOT: Creates a new document
+ */
+void PyViewer_ViewWindow::onNew()
+{
+  if ( whetherSave() )
+  {
+    my_TextEditor->clear();
+    setCurrentFile( QString() );
+  }
+}
+
+/*!
+  SLOT: Open an existing python file
+ */
+void PyViewer_ViewWindow::onOpen()
+{
+  if ( whetherSave() )
+  {
+    QString aFilePath = QFileDialog::getOpenFileName( 
+      this, tr( "TIT_DLG_OPEN" ), QDir::currentPath(), "Python Files (*.py)" );
+
+    if ( !aFilePath.isEmpty() )
+      loadFile( aFilePath );
+  }
+}
+
+/*!
+  SLOT: Save the current file
+ */
+bool PyViewer_ViewWindow::onSave()
+{
+  if ( my_CurrentFile.isEmpty() )
+    return onSaveAs();
+  else
+    return saveFile( my_CurrentFile );
+}
+
+
+/*!
+  SLOT: Save the current file under a new name
+ */
+bool PyViewer_ViewWindow::onSaveAs()
+{
+  QString aFilePath = QFileDialog::getSaveFileName(
+    this, tr( "TIT_DLG_SAVEAS" ), QDir::currentPath(), "Python Files (*.py)" );
+
+  if ( !aFilePath.isEmpty() )
+    return saveFile( aFilePath );
+
+  return false;
+}
+
+/*!
+  SLOT: Open preferences dialog
+ */
+void PyViewer_ViewWindow::onPreferences()
+{
+  PyEditor_SettingsDlg aPage( my_TextEditor, this );
+  aPage.exec();
+}
+
+/*!
+  \brief Set preferece values for view.
+ */
+void PyViewer_ViewWindow::setPreferences()
+{
+  my_TextEditor->settings()->readSettings();
+  my_TextEditor->updateStatement();
+}
+
+/*!
+  \brief Associates the theFilePath with the python view.
+  \param theFilePath file path
+ */
+void PyViewer_ViewWindow::setCurrentFile( const QString &theFilePath )
+{
+  my_CurrentFile = theFilePath;
+  my_TextEditor->document()->setModified( false );
+
+  if ( isExternal() )
+  {
+    setWindowModified( false );
+
+    QString aShownName = my_CurrentFile;
+    if ( my_CurrentFile.isEmpty() )
+      aShownName = "untitled.py";
+    setWindowFilePath( aShownName );
+
+    // Set window title with associated file path
+    QFileInfo anInfo( aShownName );
+    setWindowTitle( "Python Viewer - " + anInfo.fileName() + "[*]" );
+  }
+}
+
+/*!
+  \brief Checks whether the file is modified.
+  If it has the modifications then ask the user to save it.
+  \return true if the document is saved.
+ */
+bool PyViewer_ViewWindow::whetherSave()
+{
+  if ( my_TextEditor->document()->isModified() )
+  {
+    QMessageBox::StandardButton aReturn;
+    aReturn = QMessageBox::warning(
+      this, tr( "TIT_DLG_SAVE" ),tr( "WRN_PY_SAVE_FILE" ),
+      QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );
+
+    if ( aReturn == QMessageBox::Save )
+      return onSave();
+    else if ( aReturn == QMessageBox::Cancel )
+      return false;
+  }
+  return true;
+}
+
+/*!
+  \brief Opens file.
+  \param theFilePath file path
+ */
+void PyViewer_ViewWindow::loadFile( const QString &theFilePath )
+{
+  QFile aFile( theFilePath );
+  if ( !aFile.open(QFile::ReadOnly | QFile::Text) )
+  {
+    QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
+      tr( "WRN_PY_READ_FILE" ).arg( theFilePath ).arg( aFile.errorString() ) );
+    return;
+  }
+
+  QTextStream anInput( &aFile );
+  QApplication::setOverrideCursor( Qt::WaitCursor );
+  my_TextEditor->setPlainText( anInput.readAll() );
+  QApplication::restoreOverrideCursor();
+
+  setCurrentFile( theFilePath );
+  aFile.close();
+  if ( isExternal() )
+    statusBar()->showMessage( tr( "STS_F_LOADED" ), 2000 );
+}
+
+/*!
+  \brief Saves file.
+  \param theFilePath file path
+ */
+bool PyViewer_ViewWindow::saveFile( const QString &theFilePath )
+{
+  QFile aFile( theFilePath );
+  if ( !aFile.open( QFile::WriteOnly | QFile::Text ) )
+  {
+    QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
+      tr( "WRN_PY_WRITE_FILE" ).arg( theFilePath ).arg( aFile.errorString() ) );
+    return false;
+  }
+
+  QTextStream anOutput( &aFile );
+  QApplication::setOverrideCursor( Qt::WaitCursor );
+  anOutput << my_TextEditor->toPlainText();
+  QApplication::restoreOverrideCursor();
+
+  setCurrentFile( theFilePath );
+  aFile.close();
+
+  if ( isExternal() )
+    statusBar()->showMessage( tr( "STS_F_SAVED" ), 2000 );
+
+  return true;
+}
+
+/*!
+  \brief Opens help browser with python view help information.
+ */
+void PyViewer_ViewWindow::onBrowser()
+{
+  QDir appDir = QApplication::applicationDirPath();
+  QStringList parameters;
+  parameters << QString( "--file=%1" ).arg( appDir.filePath( "pyeditor.html" ) );
+  QProcess::startDetached( "HelpBrowser", parameters );
+}
diff --git a/src/PyViewer/PyViewer_ViewWindow.h b/src/PyViewer/PyViewer_ViewWindow.h
new file mode 100644 (file)
index 0000000..3c3f27c
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (C) 2015 OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// File   : PyViewer_ViewWindow.h
+// Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
+//
+
+#ifndef PYVIEWER_VIEWWINDOW_H
+#define PYVIEWER_VIEWWINDOW_H
+
+#include "PyViewer.h"
+
+#include <SUIT_ViewWindow.h>
+
+class PyEditor_Editor;
+class PyViewer_Viewer;
+
+class PYVIEWER_EXPORT PyViewer_ViewWindow : public SUIT_ViewWindow
+{
+  Q_OBJECT
+
+public:
+  enum { NewId, OpenId, SaveId, SaveAsId, CloseId, FileOpId,
+         UndoId, RedoId, CutId, CopyId, PasteId, DeleteId, SelectAllId, EditOpId,
+         PreferencesId, BrowserId, HelpOpId };
+
+  PyViewer_ViewWindow( SUIT_Desktop* theDesktop = 0, PyViewer_Viewer* theModel = 0 );
+  ~PyViewer_ViewWindow();
+
+  virtual void initLayout();
+
+  bool         isExternal();
+  void         setPreferences();
+
+protected:
+  virtual void closeEvent( QCloseEvent* );
+
+private Q_SLOTS:
+  void        onNew();
+  void        onOpen();
+  bool        onSave();
+  bool        onSaveAs();
+  void        onPreferences();
+  void        onBrowser();
+
+private:
+  void        loadFile( const QString& );
+  bool        saveFile( const QString& );
+  
+  void        setCurrentFile( const QString& );
+  bool        whetherSave();
+
+private:
+  void        createActions();
+  void        createToolBar();
+
+private:
+  PyViewer_Viewer*  myModel;
+  PyEditor_Editor*  my_TextEditor;
+  QString           my_CurrentFile;
+  bool              my_IsExternal;
+};
+
+#endif // PYVIEWER_VIEWWINDOW_H
diff --git a/src/PyViewer/resources/PyEditor.qrc b/src/PyViewer/resources/PyEditor.qrc
new file mode 100644 (file)
index 0000000..2f703f9
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE RCC><RCC version="1.0">
+  <qresource>
+    <file>images/py_browser.png</file>
+    <file>images/py_close.png</file>
+    <file>images/py_copy.png</file>
+    <file>images/py_cut.png</file>
+    <file>images/py_delete.png</file>
+    <file>images/py_new.png</file>
+    <file>images/py_open.png</file>
+    <file>images/py_paste.png</file>
+    <file>images/py_preferences.png</file>
+    <file>images/py_redo.png</file>
+    <file>images/py_save.png</file>
+    <file>images/py_save_as.png</file>
+    <file>images/py_select_all.png</file>
+    <file>images/py_undo.png</file>
+  </qresource>
+</RCC>
diff --git a/src/PyViewer/resources/images/py_browser.png b/src/PyViewer/resources/images/py_browser.png
new file mode 100644 (file)
index 0000000..6fb0c81
Binary files /dev/null and b/src/PyViewer/resources/images/py_browser.png differ
diff --git a/src/PyViewer/resources/images/py_close.png b/src/PyViewer/resources/images/py_close.png
new file mode 100644 (file)
index 0000000..52efbf0
Binary files /dev/null and b/src/PyViewer/resources/images/py_close.png differ
diff --git a/src/PyViewer/resources/images/py_copy.png b/src/PyViewer/resources/images/py_copy.png
new file mode 100644 (file)
index 0000000..385b2c4
Binary files /dev/null and b/src/PyViewer/resources/images/py_copy.png differ
diff --git a/src/PyViewer/resources/images/py_cut.png b/src/PyViewer/resources/images/py_cut.png
new file mode 100644 (file)
index 0000000..77edc8a
Binary files /dev/null and b/src/PyViewer/resources/images/py_cut.png differ
diff --git a/src/PyViewer/resources/images/py_delete.png b/src/PyViewer/resources/images/py_delete.png
new file mode 100644 (file)
index 0000000..38581d1
Binary files /dev/null and b/src/PyViewer/resources/images/py_delete.png differ
diff --git a/src/PyViewer/resources/images/py_new.png b/src/PyViewer/resources/images/py_new.png
new file mode 100644 (file)
index 0000000..2a39dff
Binary files /dev/null and b/src/PyViewer/resources/images/py_new.png differ
diff --git a/src/PyViewer/resources/images/py_open.png b/src/PyViewer/resources/images/py_open.png
new file mode 100644 (file)
index 0000000..26e0a2c
Binary files /dev/null and b/src/PyViewer/resources/images/py_open.png differ
diff --git a/src/PyViewer/resources/images/py_paste.png b/src/PyViewer/resources/images/py_paste.png
new file mode 100644 (file)
index 0000000..09cae75
Binary files /dev/null and b/src/PyViewer/resources/images/py_paste.png differ
diff --git a/src/PyViewer/resources/images/py_preferences.png b/src/PyViewer/resources/images/py_preferences.png
new file mode 100644 (file)
index 0000000..f8c4c05
Binary files /dev/null and b/src/PyViewer/resources/images/py_preferences.png differ
diff --git a/src/PyViewer/resources/images/py_redo.png b/src/PyViewer/resources/images/py_redo.png
new file mode 100644 (file)
index 0000000..8e685b6
Binary files /dev/null and b/src/PyViewer/resources/images/py_redo.png differ
diff --git a/src/PyViewer/resources/images/py_save.png b/src/PyViewer/resources/images/py_save.png
new file mode 100644 (file)
index 0000000..7a70c34
Binary files /dev/null and b/src/PyViewer/resources/images/py_save.png differ
diff --git a/src/PyViewer/resources/images/py_save_as.png b/src/PyViewer/resources/images/py_save_as.png
new file mode 100644 (file)
index 0000000..4cb1147
Binary files /dev/null and b/src/PyViewer/resources/images/py_save_as.png differ
diff --git a/src/PyViewer/resources/images/py_select_all.png b/src/PyViewer/resources/images/py_select_all.png
new file mode 100644 (file)
index 0000000..d7b1c73
Binary files /dev/null and b/src/PyViewer/resources/images/py_select_all.png differ
diff --git a/src/PyViewer/resources/images/py_undo.png b/src/PyViewer/resources/images/py_undo.png
new file mode 100644 (file)
index 0000000..d6701f5
Binary files /dev/null and b/src/PyViewer/resources/images/py_undo.png differ
diff --git a/src/PyViewer/resources/translations/PyViewer_msg_en.ts b/src/PyViewer/resources/translations/PyViewer_msg_en.ts
new file mode 100644 (file)
index 0000000..26a7061
--- /dev/null
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en">
+  <context>
+      <name>PyViewer_ViewManager</name>
+      <message>
+          <source>PYVIEWER_VIEW_TITLE</source>
+          <translation>Python viewer:%M - viewer:%V</translation>
+      </message>
+  </context>
+  <context>
+    <name>PyViewer_ViewWindow</name>
+    <message>
+        <source>NAME_PYEDITOR</source>
+        <translation>Python Viewer</translation>
+    </message>
+    <message>
+        <source>LBL_TOOLBAR_LABEL</source>
+        <translation>Edit Operations</translation>
+    </message>
+    <message>
+        <source>MNU_PY_NEW</source>
+        <translation>New</translation>
+    </message>
+    <message>
+        <source>DSC_PY_NEW</source>
+        <translation>Create a new python file</translation>
+    </message>
+    <message>
+        <source>MNU_PY_OPEN</source>
+        <translation>Open</translation>
+    </message>
+    <message>
+        <source>DSC_PY_OPEN</source>
+        <translation>Open an existing python file</translation>
+    </message>
+    <message>
+        <source>MNU_PY_SAVE</source>
+        <translation>Save</translation>
+    </message>
+    <message>
+        <source>DSC_PY_SAVE</source>
+        <translation>Save the python document to disk</translation>
+    </message>
+    <message>
+        <source>MNU_PY_SAVEAS</source>
+        <translation>Save As...</translation>
+    </message>
+    <message>
+        <source>DSC_PY_SAVEAS</source>
+        <translation>Save the python document under a new name</translation>
+    </message>
+    <message>
+        <source>MNU_PY_CLOSE</source>
+        <translation>Close</translation>
+    </message>
+    <message>
+        <source>DSC_PY_CLOSE</source>
+        <translation>Close the python document</translation>
+    </message>
+    <message>
+        <source>MNU_PY_UNDO</source>
+        <translation>Undo</translation>
+    </message>
+    <message>
+        <source>DSC_PY_UNDO</source>
+        <translation>Undoes the last operation</translation>
+    </message>
+    <message>
+        <source>MNU_PY_REDO</source>
+        <translation>Redo</translation>
+    </message>
+    <message>
+        <source>DSC_PY_REDO</source>
+        <translation>Redoes the last operation</translation>
+    </message>
+    <message>
+        <source>MNU_PY_CUT</source>
+        <translation>Cut</translation>
+    </message>
+    <message>
+        <source>DSC_PY_CUT</source>
+        <translation>Cut the current selection&apos;s contents to the clipboard</translation>
+    </message>
+    <message>
+        <source>MNU_PY_COPY</source>
+        <translation>Copy</translation>
+    </message>
+    <message>
+        <source>DSC_PY_COPY</source>
+        <translation>Copy the current selection&apos;s contents to the clipboard</translation>
+    </message>
+    <message>
+        <source>MNU_PY_PASTE</source>
+        <translation>Paste</translation>
+    </message>
+    <message>
+        <source>DSC_PY_PASTE</source>
+        <translation>Paste the clipboard&apos;s contents into the current selection</translation>
+    </message>
+    <message>
+        <source>MNU_PY_DELETE</source>
+        <translation>Delete</translation>
+    </message>
+    <message>
+        <source>DSC_PY_DELETE</source>
+        <translation>Delete the current selection&apos;s contents</translation>
+    </message>
+    <message>
+        <source>MNU_PY_SELECTALL</source>
+        <translation>Select All</translation>
+    </message>
+    <message>
+        <source>DSC_PY_SELECT_ALL</source>
+        <translation>Select all the contents</translation>
+    </message>
+    <message>
+        <source>MNU_PY_PREFERENCES</source>
+        <translation>Preferences</translation>
+    </message>
+    <message>
+        <source>DSC_PY_PREFERENCES</source>
+        <translation>Show the Preferences box</translation>
+    </message>
+    <message>
+      <source>MNU_PY_BROWSER</source>
+      <translation>Help Browser</translation>
+    </message>
+    <message>
+      <source>DSC_PY_BROWSER</source>
+      <translation>Show the Help browser</translation>
+    </message>
+    <message>
+        <source>WRN_PY_READ_FILE</source>
+        <translation>Cannot read file %1:\n%2.</translation>
+    </message>
+    <message>
+        <source>WRN_PY_WRITE_FILE</source>
+        <translation>Cannot write file %1:\n%2.</translation>
+    </message>
+    <message>
+        <source>WRN_PY_SAVE_FILE</source>
+        <translation>The document has been modified.&lt;br&gt;Do you want to save your changes?</translation>
+    </message>
+    <message>
+        <source>TIT_DLG_SAVE</source>
+        <translation>Save file</translation>
+    </message>
+    <message>
+        <source>TIT_DLG_SAVEAS</source>
+        <translation>Save file as</translation>
+    </message>
+    <message>
+        <source>TIT_DLG_OPEN</source>
+        <translation>Open file</translation>
+    </message>
+    <message>
+      <source>STS_READY</source>
+      <translation>Ready</translation>
+    </message>
+    <message>
+      <source>STS_F_LOADED</source>
+      <translation>File is loaded</translation>
+    </message>
+    <message>
+      <source>STS_F_SAVED</source>
+      <translation>File is saved</translation>
+    </message>
+  </context>
+</TS>
diff --git a/src/PyViewer/resources/translations/PyViewer_msg_fr.ts b/src/PyViewer/resources/translations/PyViewer_msg_fr.ts
new file mode 100644 (file)
index 0000000..34ba464
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr" sourcelanguage="en">
+<context>
+    <name>PyViewer_ViewManager</name>
+    <message>
+        <source>PYVIEWER_VIEW_TITLE</source>
+        <translation>Éditeur de python:%M - viseur:%V</translation>
+    </message>
+</context>
+  <context>
+    <name>PyViewer_ViewWindow</name>
+</context>
+</TS>
diff --git a/src/PyViewer/resources/translations/PyViewer_msg_ja.ts b/src/PyViewer/resources/translations/PyViewer_msg_ja.ts
new file mode 100644 (file)
index 0000000..711b492
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="ja" sourcelanguage="en">
+<context>
+    <name>PyViewer_ViewManager</name>
+    <message>
+        <source>PYVIEWER_VIEW_TITLE</source>
+        <translation>Pythonのエディタ:%M - ビューア:%V</translation>
+    </message>
+</context>
+</context>
+<context>
+      <name>PyViewer_ViewWindow</name>
+</context>
+</TS>