From 185b5a4dd2162e5addd1a08170c674e5a01a75d2 Mon Sep 17 00:00:00 2001 From: mgn Date: Tue, 31 Mar 2015 19:52:45 +0300 Subject: [PATCH] Integrates the python editor as new SALOME viewer. Writes comments. Designs icons. Re-packaging. --- CMakeLists.txt | 13 +- SalomeGUIConfig.cmake.in | 6 + src/CMakeLists.txt | 8 + src/LightApp/CMakeLists.txt | 7 + src/LightApp/LightApp_Application.cxx | 107 +++ src/LightApp/LightApp_Application.h | 2 +- src/LightApp/resources/LightApp.xml | 54 +- src/LightApp/resources/LightApp_msg_en.ts | 60 ++ src/PyEditor/CMakeLists.txt | 89 ++ src/PyEditor/PyEditor.h | 33 + src/PyEditor/PyEditor_Editor.cxx | 804 ++++++++++++++++++ src/PyEditor/PyEditor_Editor.h | 81 ++ src/PyEditor/PyEditor_LineNumberArea.cxx | 51 ++ src/PyEditor/PyEditor_LineNumberArea.h | 46 + src/PyEditor/PyEditor_PyHighlighter.cxx | 355 ++++++++ src/PyEditor/PyEditor_PyHighlighter.h | 91 ++ src/PyEditor/PyEditor_Settings.cxx | 169 ++++ src/PyEditor/PyEditor_Settings.h | 82 ++ src/PyEditor/PyEditor_SettingsDlg.cxx | 343 ++++++++ src/PyEditor/PyEditor_SettingsDlg.h | 89 ++ .../resources/translations/PyEditor_msg_en.ts | 71 ++ .../resources/translations/PyEditor_msg_fr.ts | 7 + .../resources/translations/PyEditor_msg_ja.ts | 7 + src/PyViewer/CMakeLists.txt | 92 ++ src/PyViewer/PyViewer.cxx | 59 ++ src/PyViewer/PyViewer.h | 33 + src/PyViewer/PyViewer_ViewManager.cxx | 49 ++ src/PyViewer/PyViewer_ViewManager.h | 40 + src/PyViewer/PyViewer_ViewModel.cxx | 65 ++ src/PyViewer/PyViewer_ViewModel.h | 52 ++ src/PyViewer/PyViewer_ViewWindow.cxx | 489 +++++++++++ src/PyViewer/PyViewer_ViewWindow.h | 79 ++ src/PyViewer/resources/PyEditor.qrc | 18 + src/PyViewer/resources/images/py_browser.png | Bin 0 -> 17169 bytes src/PyViewer/resources/images/py_close.png | Bin 0 -> 8836 bytes src/PyViewer/resources/images/py_copy.png | Bin 0 -> 5717 bytes src/PyViewer/resources/images/py_cut.png | Bin 0 -> 16894 bytes src/PyViewer/resources/images/py_delete.png | Bin 0 -> 9010 bytes src/PyViewer/resources/images/py_new.png | Bin 0 -> 17173 bytes src/PyViewer/resources/images/py_open.png | Bin 0 -> 13533 bytes src/PyViewer/resources/images/py_paste.png | Bin 0 -> 8660 bytes .../resources/images/py_preferences.png | Bin 0 -> 21904 bytes src/PyViewer/resources/images/py_redo.png | Bin 0 -> 9741 bytes src/PyViewer/resources/images/py_save.png | Bin 0 -> 5063 bytes src/PyViewer/resources/images/py_save_as.png | Bin 0 -> 10555 bytes .../resources/images/py_select_all.png | Bin 0 -> 3888 bytes src/PyViewer/resources/images/py_undo.png | Bin 0 -> 9449 bytes .../resources/translations/PyViewer_msg_en.ts | 170 ++++ .../resources/translations/PyViewer_msg_fr.ts | 14 + .../resources/translations/PyViewer_msg_ja.ts | 15 + 50 files changed, 3728 insertions(+), 22 deletions(-) create mode 100644 src/PyEditor/CMakeLists.txt create mode 100644 src/PyEditor/PyEditor.h create mode 100644 src/PyEditor/PyEditor_Editor.cxx create mode 100644 src/PyEditor/PyEditor_Editor.h create mode 100644 src/PyEditor/PyEditor_LineNumberArea.cxx create mode 100644 src/PyEditor/PyEditor_LineNumberArea.h create mode 100644 src/PyEditor/PyEditor_PyHighlighter.cxx create mode 100644 src/PyEditor/PyEditor_PyHighlighter.h create mode 100644 src/PyEditor/PyEditor_Settings.cxx create mode 100644 src/PyEditor/PyEditor_Settings.h create mode 100644 src/PyEditor/PyEditor_SettingsDlg.cxx create mode 100644 src/PyEditor/PyEditor_SettingsDlg.h create mode 100644 src/PyEditor/resources/translations/PyEditor_msg_en.ts create mode 100644 src/PyEditor/resources/translations/PyEditor_msg_fr.ts create mode 100644 src/PyEditor/resources/translations/PyEditor_msg_ja.ts create mode 100644 src/PyViewer/CMakeLists.txt create mode 100644 src/PyViewer/PyViewer.cxx create mode 100644 src/PyViewer/PyViewer.h create mode 100644 src/PyViewer/PyViewer_ViewManager.cxx create mode 100644 src/PyViewer/PyViewer_ViewManager.h create mode 100644 src/PyViewer/PyViewer_ViewModel.cxx create mode 100644 src/PyViewer/PyViewer_ViewModel.h create mode 100644 src/PyViewer/PyViewer_ViewWindow.cxx create mode 100644 src/PyViewer/PyViewer_ViewWindow.h create mode 100644 src/PyViewer/resources/PyEditor.qrc create mode 100644 src/PyViewer/resources/images/py_browser.png create mode 100644 src/PyViewer/resources/images/py_close.png create mode 100644 src/PyViewer/resources/images/py_copy.png create mode 100644 src/PyViewer/resources/images/py_cut.png create mode 100644 src/PyViewer/resources/images/py_delete.png create mode 100644 src/PyViewer/resources/images/py_new.png create mode 100644 src/PyViewer/resources/images/py_open.png create mode 100644 src/PyViewer/resources/images/py_paste.png create mode 100644 src/PyViewer/resources/images/py_preferences.png create mode 100644 src/PyViewer/resources/images/py_redo.png create mode 100644 src/PyViewer/resources/images/py_save.png create mode 100644 src/PyViewer/resources/images/py_save_as.png create mode 100644 src/PyViewer/resources/images/py_select_all.png create mode 100644 src/PyViewer/resources/images/py_undo.png create mode 100644 src/PyViewer/resources/translations/PyViewer_msg_en.ts create mode 100644 src/PyViewer/resources/translations/PyViewer_msg_fr.ts create mode 100644 src/PyViewer/resources/translations/PyViewer_msg_ja.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d83d6f62..5eb8ac30d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/SalomeGUIConfig.cmake.in b/SalomeGUIConfig.cmake.in index 7dcc4ab2d..c7ea0b97a 100644 --- a/SalomeGUIConfig.cmake.in +++ b/SalomeGUIConfig.cmake.in @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1d09d2d5b..f3d3f8aab 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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} diff --git a/src/LightApp/CMakeLists.txt b/src/LightApp/CMakeLists.txt index 43e7b816e..c295aa78f 100755 --- a/src/LightApp/CMakeLists.txt +++ b/src/LightApp/CMakeLists.txt @@ -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) diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index b156836b3..246a8ab9e 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -154,6 +154,12 @@ #include "PVViewer_ViewModel.h" #endif +#ifndef DISABLE_PYVIEWER + #include + #include + #include +#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 <> + // .. "PyViewer" preferences tab <> + int pyeditTab = pref->addPreference( tr( "PREF_TAB_PYEDITOR" ), salomeCat ); + // ... "Font settings" group <> + 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 <> + // ... "Display settings" group <> + 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 <> + // ... "Tab settings" group <> + 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 <> + // ... "Vertical edge settings" group <> + 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 <> + // .. "PyEditor" preferences tab <> + // .. "Directories" preferences tab <> 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 lst; + viewManagers( PyViewer_Viewer::Type(), lst ); + QListIterator 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( vm ); + + viewMgr->setViewModel( vm ); + PyViewer_ViewWindow* pyView = dynamic_cast( viewMgr->getActiveView() ); + if( pyView ) + { + pyView->setPreferences(); + } + } + } +#endif } /*! @@ -4071,6 +4175,9 @@ QStringList LightApp_Application::viewManagersTypes() const #ifndef DISABLE_PVVIEWER aTypesList<
- - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + +
@@ -200,6 +202,18 @@
+
+ + + + + + + + + + +
diff --git a/src/LightApp/resources/LightApp_msg_en.ts b/src/LightApp/resources/LightApp_msg_en.ts index 5eb0f6e5a..be7ea9b40 100644 --- a/src/LightApp/resources/LightApp_msg_en.ts +++ b/src/LightApp/resources/LightApp_msg_en.ts @@ -494,6 +494,10 @@ The changes will be applied on the next application session. NEW_WINDOW_6 ParaVie&w view + + + NEW_WINDOW_7 + P&ython view CREATING_NEW_WINDOW @@ -884,6 +888,62 @@ File does not exist PREF_GROUP_SHORTCUTS Shortcuts settings + + PREF_TAB_PYEDITOR + Python Viewer + + + PREF_GROUP_PY_FONT + Font settings + + + PREF_PY_FONT + Font + + + PREF_GROUP_PY_DISPLAY + Display settings + + + PREF_PY_CURRLINE_HIGHLIGHT + Enable current line highlight + + + PREF_PY_TEXT_WRAP + Enable text wrapping + + + PREF_PY_CURSON_ON_SCROLL + Center cursor on scroll + + + PREF_PY_LINE_NUMBS_AREA + Display line numbers area + + + PREF_GROUP_PY_TAB + Tab settings + + + PREF_PY_TAB_WHITESPACES + Display tab whitespaces + + + PREF_PY_TAB_SIZE + Tab size: + + + PREF_GROUP_VERT_EDGE + Vertical edge settings + + + PREF_PY_VERT_EDGE + Display vertical edge + + + PREF_PY_NUM_COLUMNS + Number of columns: + LightApp_Module diff --git a/src/PyEditor/CMakeLists.txt b/src/PyEditor/CMakeLists.txt new file mode 100644 index 000000000..338c99da0 --- /dev/null +++ b/src/PyEditor/CMakeLists.txt @@ -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 index 000000000..964c0b76c --- /dev/null +++ b/src/PyEditor/PyEditor.h @@ -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 index 000000000..3721c1d10 --- /dev/null +++ b/src/PyEditor/PyEditor_Editor.cxx @@ -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 + +//Qt includes +#include +#include + +/*! + \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 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( textCursor().block().userData() ); + + if ( data ) + { + QVector 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( theCurrentBlock.userData() ); + QVector 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( theCurrentBlock.userData() ); + QVector 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( prevBlock.userData() ); + QVector 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 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 index 000000000..ced9467a4 --- /dev/null +++ b/src/PyEditor/PyEditor_Editor.h @@ -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 + +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 index 000000000..571d08cfc --- /dev/null +++ b/src/PyEditor/PyEditor_LineNumberArea.cxx @@ -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 index 000000000..09781b8e5 --- /dev/null +++ b/src/PyEditor/PyEditor_LineNumberArea.h @@ -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 + +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 index 000000000..fb9eb033f --- /dev/null +++ b/src/PyEditor/PyEditor_PyHighlighter.cxx @@ -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::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 aTripleSingle; + aTripleSingle << theText.indexOf( tripleSingleExpression ) << TRIPLESINGLE; + + QList aTripleDouble; + aTripleDouble << theText.indexOf( tripleDoubleExpression ) << TRIPLEDOUBLE; + + QList< QList > aTripleExpressions; + aTripleExpressions << aTripleSingle << aTripleDouble; + + for ( int i = 0; i < aTripleExpressions.length(); i++ ) + { + QList 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 index 000000000..dda957fb6 --- /dev/null +++ b/src/PyEditor/PyEditor_PyHighlighter.h @@ -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 + +class QTextDocument; + +class PyEditor_PyHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + + struct ParenthesisInfo + { + char character; + int position; + }; + + class TextBlockData : public QTextBlockUserData + { + public: + TextBlockData(); + + QVector parentheses(); + void insert( ParenthesisInfo* ); + + private: + QVector my_Parentheses; + }; + +public: + PyEditor_PyHighlighter( QTextDocument* = 0 ); + + void initialize(); + QStringList keywords(); + QStringList specialKeywords(); + +protected: + struct HighlightingRule + { + QRegExp pattern; + QTextCharFormat format; + int capture; + }; + QVector 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 index 000000000..1230ebbb7 --- /dev/null +++ b/src/PyEditor/PyEditor_Settings.cxx @@ -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 + +#include +#include +#include + +/*! + \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 index 000000000..5efa018c5 --- /dev/null +++ b/src/PyEditor/PyEditor_Settings.h @@ -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 + +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 index 000000000..9b685be09 --- /dev/null +++ b/src/PyEditor/PyEditor_SettingsDlg.cxx @@ -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 +#include +#include +#include +#include +#include +#include +#include + + +/*! + \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 + 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 + + // . Display settings + 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 + + QHBoxLayout* aTabVertEdgeLayout = new QHBoxLayout; + + // . Tab settings + 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 + + // . Vertical edge settings + 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 + + 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 szList = QFontDatabase().pointSizes( fontFamily() ); + QStringList sizes; + for ( QList::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 index 000000000..f585822ac --- /dev/null +++ b/src/PyEditor/PyEditor_SettingsDlg.h @@ -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 + +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 index 000000000..047f66180 --- /dev/null +++ b/src/PyEditor/resources/translations/PyEditor_msg_en.ts @@ -0,0 +1,71 @@ + + + + + PyEditor_SettingsDlg + + TIT_PY_PREF + Preferences + + + GR_FONT_SET + Font settings + + + LBL_FONT_FAM + Family: + + + LBL_FONT_SIZE + Size: + + + GR_DISP_SET + Display settings + + + LBL_CURRLINE_HIGHLIGHT + Enable current line highlight + + + LBL_TEXT_WRAP + Enable text wrapping + + + LBL_CURSOR_SCROLL + Center cursor on scroll + + + LBL_LINE_NUMBS_AREA + Display line numbers area + + + GR_TAB_SET + Tab settings + + + LBL_TAB_SPACES + Display tab white spaces + + + LBL_TAB_SIZE + Tab size: + + + GR_VERT_EDGE_SET + Vertical edge settings + + + LBL_VERT_EDGE + Display vertical edge + + + LBL_NUM_COLUMNS + Number of columns: + + + WDG_SET_AS_DEFAULT_CHECK + Save settings as default + + + diff --git a/src/PyEditor/resources/translations/PyEditor_msg_fr.ts b/src/PyEditor/resources/translations/PyEditor_msg_fr.ts new file mode 100644 index 000000000..e92905efc --- /dev/null +++ b/src/PyEditor/resources/translations/PyEditor_msg_fr.ts @@ -0,0 +1,7 @@ + + + + + PyEditor_SettingsDlg + + diff --git a/src/PyEditor/resources/translations/PyEditor_msg_ja.ts b/src/PyEditor/resources/translations/PyEditor_msg_ja.ts new file mode 100644 index 000000000..7d5d4ad80 --- /dev/null +++ b/src/PyEditor/resources/translations/PyEditor_msg_ja.ts @@ -0,0 +1,7 @@ + + + + + PyEditor_SettingsDlg + + diff --git a/src/PyViewer/CMakeLists.txt b/src/PyViewer/CMakeLists.txt new file mode 100644 index 000000000..2f9961782 --- /dev/null +++ b/src/PyViewer/CMakeLists.txt @@ -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 index 000000000..8721a78d1 --- /dev/null +++ b/src/PyViewer/PyViewer.cxx @@ -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 +#include +#include +#include + +#include "PyViewer_ViewWindow.h" + +#include + +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 index 000000000..cb481e4c2 --- /dev/null +++ b/src/PyViewer/PyViewer.h @@ -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 index 000000000..724eb1384 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewManager.cxx @@ -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 index 000000000..591329233 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewManager.h @@ -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 + +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 index 000000000..f6c5ff564 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewModel.cxx @@ -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 index 000000000..9c5fb2997 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewModel.h @@ -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 + +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 index 000000000..8b8bab816 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewWindow.cxx @@ -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 +#include + +#include +#include +#include + +#include +#include + +/*! + \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 index 000000000..3c3f27cc2 --- /dev/null +++ b/src/PyViewer/PyViewer_ViewWindow.h @@ -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 + +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 index 000000000..2f703f92c --- /dev/null +++ b/src/PyViewer/resources/PyEditor.qrc @@ -0,0 +1,18 @@ + + + images/py_browser.png + images/py_close.png + images/py_copy.png + images/py_cut.png + images/py_delete.png + images/py_new.png + images/py_open.png + images/py_paste.png + images/py_preferences.png + images/py_redo.png + images/py_save.png + images/py_save_as.png + images/py_select_all.png + images/py_undo.png + + diff --git a/src/PyViewer/resources/images/py_browser.png b/src/PyViewer/resources/images/py_browser.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb0c81b74b17631754d87d60e351eb57a71d90c GIT binary patch literal 17169 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 E2Q^x(r2qf` literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_copy.png b/src/PyViewer/resources/images/py_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..385b2c4e8440a8e68305272e429a0f55912e03ba GIT binary patch literal 5717 zcmd5=dsI_bx<4V&V68lKL{tb>4#o$NMq4#Ngh(w$9Pq(Qc?F|I1w{w~39lGiu@Z@% zh@c!Gwih3Smk&VX6-N!tfFV^;KnzxbiGWZNB82DcI9=VmOHlmom~Y|}BjOG~iHV8MhmJhxg8>_?t82%U6}XnWyS1OylpnugLt-!k9|$J zS~m5XY_=HQ5uR8y1r#CZRq%`(c|ws$HzGkw>QC04?&CxoGrA( zA|5*Ep}>eRG(33CA2YNEQW<;Bk_p+W3oy|x4n1%HXPV*_Bld4Po^WtN7&S2%4|zdfY>=%;Zx02H_VDHChQ-U3Vkiat%#k_t3iM zF5LRpuO%}P$bzUv81KF+IA;ZW0@eN4#?;UiLrvB_!NN%~O!Cz_Jz{agj&9d}fQL}l zG+S>yZZUF_PbOScWz(Iv#-$9~h^mu?btGB2oZ{|R_uXt9Rc76zfl1!l@#vK6^BCvT z*2%%8_Co!lf%I*wF7oEnoT zhtI=IxglMgQ4tcbR>R~og)K0tRB3X|eUPqQz>+5#{gs3Of5a7RU*uXq3?|89LWpiq zPgeFVbIi!T_smVYagFRtOBQ3ub+ycfIjQ?-r+C^i-JE1YM0SwgitFT(E;nSh*|)z8 zSPtH@TNskBy4e;CWhtW6XiPETzOrqTCeJ*x#!Yq!n5!bvk!z1B@yG}XfQMy&5WN8o5^{46^7;%prCbm$9iV2(ReIhljf?TY)_ZNCv1qYYxSuMhr zTqXvXGhz-}j*z843f+F>;5{&{U7yi~BBJ3J*eg%~t^7;_zgrNW^YVnhg9biiG4fbl zm5f_nQH;xn=kW?u$w&%IZ^~WH^FatS_aYADaV0}QChVm0IJUgOYr7e3sor|1@3`qW zy$uVAE{m`pSe_X)&(sC_;xi4T@W>EN#1o-4<(>)*qj%14R!Ku2=EB%vgyi-xHnGEn zAs)O=@SxI$-j~6Fuwe%R7BLYS`Y6vM%MZQG?1I;mvA&ZgTSQaw(Ejm-m zF@=%^B2q^63QD^CUBFssFh?|WBoQ_cK}s4s$1E7B?>_~r!D$Gk;<)v`iqWeA2g7}e zdPYnzk;MJ#I9jj147bi_K~;ZQJg(HO29V{DBZ*c%`jjPizhUgQU}SGj3uT6>H-#=1 zie7M70F5{nBQAxxtgtYu373GYmxJViG@gp6u1l)WHv5l{lTuXY{o8Y0DF=e;O~>Da z0<>u0;Qsy`LWQ_#xYXgY8wAM1o!1aFAq)uw4uS3FQIw_J^V@S-8EE$f?Min%BXB@W zsMv>IiMemx6G6`^RbmG2Cacl#GQ!ViMI?DWy(>NK97skXIE-|>Ezxd7KYYBO`|1nn z-F31^bC;tOO@Gu|bc2;Vcw1C|e}CK3sssF+j_F)ME2S`m`a`F6PpuHGWl!e6p-7Z^ z89emTtt^){NK)QbQ#`}bm&JHLYo<-M3a`zvbgxXXtbiNrWl`$|?sm*N_ocBj5>zKH zYW}d&?o2T1T8wuD4r@A+@_xAmyDuc;vs$Vh3{4%T<927sVRh_V656Es9yEInINk$f zX$BI4sP4R>c#s6XS~>jUEV&?*`VA>omKxiMPvsA95Y~}p0)R=K$#F=xEi>hFnSmvb z665_Bj66ED6r8A^>CW4=ePabCR{eM+geo1=!{xawr9buh8SlYN-J?ECZ3I6yWxaE0 zxCI%1xWs|I2NCee4fRRhi)rC#LL@+|#`7w3q1!diTo2^-t=OvRXMM{S;!iq*nD6-l3|w!-2|vVJ=vEA{XlmnnSvr9^S?hnV{&8|a=F>MP zmoWL~Ri$mNkl6$A?btWt>)Jf6A`Ya*MNCMoTlu-z=3al`&0i3Nqp2(SJAUF^3k!6P~@rZ=3c;8bj-6F_seY zRH;mUhQE1-6Ho4GsHUgg1wduCah+^Sn|(s2NXM003!0@1UPJeiKus9{u4xMP=P^G? zlYOE5x*Cq;s73tX3+tW{m{bN}Z*BoansiiuI)8=6?c9c@QM}>>DL(m3h}iv8S0;Zr zl|D_=n{h+q_aZrdf_6GjKE(#`!=07%0Mx@iOV=$5!)zx18f>Tqv$uNv?2CI&feOmA z8gOK+u0M_{5V5YW@9u&!eyL&dts2>W3d23JbOC~f0jxg(s&um0YXB??LoPqz|Ljer zuYwYk8FQHH#0k@L^8r-S!E}7IBLKu_6?`t;_#@|G!W3Rb*bamZ6~!are~p8I}&yqQHjBim_e@jc%! zSspo}?gACqfQrpoKH6L`q1WG%r41ByYMW=)1rbZ~eH%7Q(Il+fAgiR8<+IYRCYhI@ zWGNFtvsIwb(6#)|EnceYFcGdP;{0?TcptmkhY604LFNiW?zXt^^u)8|twe+5?eJBy z)E74N!h)5yRmYn@xQxxK%aE@f_d{;0(eg#C_T;x{jBngt1RQ}q4m|m-nhi1lpLPHN z-6|;RLI;BveN%Y-0e@4M=C|`$@_w-wWPU_$sDWonI8DVC@fT$?KH9l$_S^>>)O8zW zkJ?-VPKa)Acm7a7aV(nIOJytsBUPP!7>VU@Vj>2Ye;_VY4>9@yUN{&w>$3)dCHCc8 zd^ERrr$ zZZ#a7%_%RyO=3gh{y4k-7q@dxy|av2>?Mg9i_=3^UW$lJXZN8y-Q`N?*vg!)e~;+@ z^P&8{oM43Op`i7?jha&|#O*2vZ*z(io~ArBR8F|R4`kk}RwAX=xOO1%#?Ks+{V`SY zF9!R;-<<0ZzZ>xi5O5#-dZ`cNm?e5(6$-*475{%&J7atYboGl~{c7JI#g5jSSshHB z@&+k49^==_#oKEtwgB?62#$&j9Acb*e$;2VX5f%>%|9^J-_Pm)J?sBRk9gDZ={^j! zqtsI|^8?^xelLL##CH{_&bQeI*+xZ0y?F0CSf;%HrleH;yl>vGJMQ78<`|poC_{Gw z{bHbvei`0_)q&;^_pmK*8c>OkzVfeNE3`)J(^!n;72_d~N;6txvC)6O#Ylfo0Rsx? zdV)`tvty9{;zY=!sK|W$>KZZVSmaEbd0_N%!&_nW@?uErgUREU<9f3dHBOi?8uLLVdf7V{=vxF)Rmhv{KC-~h0H2`$@ZQ9F{9KqluP z=tb3N@hBwm391pSey*1h@3wByXRpr=xWcH*U?$m5q?*-@SbJ zSB`n)9-#9eM)0Jq@&G9IV^w4+9n|j9rOXls%8wkcM4o&tMa+SYeN~XF8tbz(mgBD! z5fo|>N3z;j>;-4ZSz-bvx}&)mUt&Uwyfj(*QSO*^TlW;MU?=Fa{6Et;<^^Vvr_=PN zudjehkwR5Yabn3Qw8#!X@f!tX`3)ntFs`06U3WFFk~r`A2IcybJ2uT`9fR{1$9Ke`jg0c2OC%VC2Q=yv2;A7pK46p`b_>yHxxYm%koo8+3r@vy^96a;w$P zyxp0m=0+iR)0YPM+m#Y3LV)&JnRHM*YEi6d$Q(`-HFr zNb;c1Y1eWn>d0HeVJ?#5yH2ufK&5$rxmvm3FtNEhN}8|_HwO_pXe6r-=`IsBDJLoF zdGu)!2QM*6St}a~5VV6;nWDIFvcZpxb{g5EeZm=$F0h4CPS?7F^E~jw8Z|mpMz|@& z<>R%T@F>uw?GEZd^Y%kv;APe{9oYjI@uHBm>d8Vd__9dHNOP9u1BscSn1*L=aUKHR zPEepa7Jd3SVC1$V&mY&aJ1v1cKy}?jcBIkvE1sSP_T{K$yjfGTFk~mygt2r}zG_zf z(cP}kI>+z5Jj*`F^aAyCyJl1;vpie~F-Zsx`(22300V6tI_T zqVJGMh(!OPxZ>aHEKUw#* zL{DD)A*LK$JDqk#CfiZn6VL{0Y;KaJ-_w;smGRZ(Qn<&fJPPaD~;}F$Q(LC~oqzj8AB>?)f&_ZFK@n zxte6A=;k2ZEnojMbBDA2OIlm4mFYe zHIiz)9y~~uWq0t$P@Uk+sN*c!H4x0tbh%I56IzoDpPIT4VK&b;sqJlmt5+BXXiZMQ w_8!&UKCZhcG_qELBlf>9Z2l8vP%?w}U0Jl6e94yvu7Dx>7N4)mHZypC17TFo*Z=?k literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_cut.png b/src/PyViewer/resources/images/py_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..77edc8ab81dda72efe8ed3f9150041cb6e5eaad5 GIT binary patch literal 16894 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
5B*g^33BnAD_F^ICtJgHR-3~^Q= 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 literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_delete.png b/src/PyViewer/resources/images/py_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..38581d16fdd8780e76784fda781111390d255347 GIT binary patch literal 9010 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

!pt< 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 X!(GeWuv6ggTVO;RXY1+%{+Is?w%XZD literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_new.png b/src/PyViewer/resources/images/py_new.png new file mode 100644 index 0000000000000000000000000000000000000000..2a39dff506cbf8d9bbbc0502b5d724fe41f9c451 GIT binary patch literal 17173 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 Qg#|=IMH^YBWc~F20lvd4m;e9( literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_open.png b/src/PyViewer/resources/images/py_open.png new file mode 100644 index 0000000000000000000000000000000000000000..26e0a2ce4450f2c48fa8d1001620a30be2fc4113 GIT binary patch literal 13533 zcmch;XH?To@F<)RAW8`qij;t8s8SR}AT$LjktV$vK&pTgAv9@0q=*O_nn-(SA|Rm) zQe#0xx^!s~1R^zpbkMu;|K4-nZ})z>p2HFMH#<8sJAKDTHx0B|nRu8W5C|(q=ejWj z0{u<KM~w;7{l=r#SHM<32i8egJ-y_5)QH%?tvI+&E23oQby! zF7URmGbAuDP|D5A!_V=ykF%7wuWRO-G7kiD7J|8c%`_-$bt>?=gmd=xFYn|6I!5|; zS??-Spd2O_&RE)5T)Xs|LiAd`uvD#;P~>3nT#{q3af$eh^ModE_kStG+ZH!JGH5;^ z%tgYE{uuc=oe?%P`zs!*Haa|-;n&-3QV(}s3l~>5^p?puiy^%5ETBLLD7RG!9EM;` zQcp*fF#Nyz)jpX5@gT6((+9x4pm*8s$}f*f!jen4rRZ&S30Mx~b;3xge1=}Wtc0QJ z3N!=~Lf6^7>~;A_|51Jz>sT(zMt}yAKOJqGvYSKtLkcGmt0ISt?I1Z?1S~K3W?q&P zox>IgwWa5StencynxbOJxtMC6a1EWWUzhwF(E`)ZAVn&ReGU2YQkp9?9R}BgKPw!5 z)xn}7FwGGp*;0#Pl^niv;>1s@(K$9#>#V8&8l}rQ59&KkwTb5{d={QEo!B zOez9vu+U=-VR{fZBkA*<(zNV zzY*f)-{^y`V74Do&Jt=fi+N znj8nYw-q#6qfKondp;)dW(_$vr>+&2G%yA6VFL7UConGz{lVGt@YZ9>Z(hK3Oad9G zX9z31;Ec-_ax|*`YuH;gnrD-WZe$5yj*l)gq6jC zYbduU-*sQ<0PTZ73BS6D08;(u*e2TPtY5_i+0H6`+ zL+Rslb<=GGHnt~VHQQW9BSOwepi8Bs|$ERx|eL z5Pw&8UR(25cH^Dq*w9^R8eqp)+20P2MrUsJYDp1@ev&?CG27!nTra#8g^mc$;W>q= z;+=)d@ zN2HR%r?m|22+t63$wPX~xqzyA#y^k-#=6TYTrf6~PJ?*Fxe_bR%)Gzn)81b8l&5P| ztnq$;lALge{*zEr^z`xsf>ib6L(_GzpKp`v{;!oiWHGPiR|%kx`l`)Xpr1XJ{VYe% zO!NiaRO{(mHB=|%&~b(PHm~}3GTfOk%x_uS^^Q1!5v&MA*H-nwBJq;eHMY>~ z&<1DPKHnn`U^C~*=SbXmQ|}H)()mszjUWDn-v0GCXDPXN1$gPFZ1Ru(t#RHzMNt3h zeh8yxWXO6D&|Ygz2P*2uI@aaIkfx_>fBBHtQyZCp>YjR1>Z_?BZ?&Dl$Uy8W3c649HtQlcr8EO$*V6-%J>fUgHPbLZwcO7}YR` zl~uA63ackCpz#h08tH`r_k2f;di#Y+uzz@vS1*A$;GV7wz6E{3627&oufH0PZnd&x z!Wdgg{7(FK=cnN_l-$RksPXjz4bd1t-0H6^3MAe!PuEx}GzFz}1^w_Jk)g#f=|_k> zOU_1=+(c4j3#M8(|Nr9#Cqy;2mnN(P4%}w%3-c2yQ6$oZA3tOd62Jub`tT2bUQv&@ zVaepSdY|+rXauuboA)^aLyHYsTSJ+8!UNnW%vT=8%xC`(qt^d1M%pL0g{82qb3E4| z6wX`1v-Anr6UaiZ`^lY!yP^UH4h1@d+Pj)nAFOI@dBby)oz&R6Ip2f8TQ`tFdL~=3 z{?y5Wez7DA$kV;)0)8E02TwfL*~`*va-13Hh+GFWtMU zuoJKA~%s+pbM$;dR{BIojIsng)l1K;OUk?L{8{E8rKk2%ir%R}<^o*1y zu+8YJPC9k=A)IJSwNa53LvM#9K_Zd1SVht<(2)2D*mB#;(Nqofz{TwWg+-NjcXh_X zwPT-m(LT*jgHQKyapSaRw3@G*i8C=SM>ii3J@<91MP z4E!mocV)QM502UTCuM6(w&IIT1V5SD~0!3>`-KxqJ_ zzYg8_yJ?F&Y{CN5Vb<^(fUdOn+93KVZIgeFmTL3B<$tm;%`T6G8_r#&5kCCQg9lzk z1N|p<=E=;^hT!4hS;9zp}4e+lS$f$xR3kK!Ny(^t}wd(B7Oq|LtXI5{GPVz1M>O z{8qSrb$<8e_m?E|t{~5r&oiFPD=aH?lPs@r8YhNh!C|@w7pOMm6vehbk5EmoY70=~ z!z4}Fin^}YOh4?~b@b-46v-1mg`aTvUQhD~qi>HXocx@DoyG)&-zgowQSDC>aIuw7 zVXJ3cV_Z>PuH*IT5)(3g1o*`Ivr^Sa$7^gwlAV(KO$Sq)(xGbE!B`OiFSeEA%1#UZ z-Ybv4ZK^#jBsQGdmGV*649>ky!1BC!MI@}yk$Gh+SY`L%KG=6NTw@aAISa0Ny6!b< z5N#9o!Fxcj-vz%YdEP0YwD};kL8u>|L$A!=U}Sma=t;CxiT((5ra+WhfT=cluvu$D zz(V;L_~iWNrV2#AghB3}<}U971nSrGD=_W`l3!(v7rrcN__cP79G8)C;D{G7NjX?} zHqiJ8@%NZO2Cgvzt_44>p|EEHCR?MhJBQRAHh zw`=22O7I}bJ*p&079SO<)Dhz@&x|#m!ZIh|cc2}COI6iZL|s<7_oxM=R?_3h*ccag zF#v$YV(#5I!lK5#3qIU=5-;zdO$S_nz|~)jl8;!2rM!C2m3Z~SrjJd&K=E2HNTxWc zi9e zeNWPopX-Efflt;gr~^))2y!W3C9`-RGf7b8_M+v$|EOHWZs3=|XPQ(}M(k4;9xN(pMb5v>1*QsNF`BJ)Fl zt(q^2lLau^ zZs5|(9`*Fvrv;JnTCq>ff2Oi83*kP|JjU?Xagywmlk@E(TrF$mX`jbd72|C)(6N-+ z#g!O4c`Ndc3fY0I9=~|Vb+9EtPPvCxh+z6Fn|Yx#+)(xKA~l~PItaY=3lBQw4|U_r z0E=8Dug{KRMP_Q~t8%gF6B6>Hc9A9v!AId)2%?mtjY-SNAYq?5GM~vnrk=|*eo& z?J5Yn>|j}Qi#ZC!tg^poP4N+`Y^yp4U_-X6pJfBbn(c*<2aj;3qltUa_A52z#-kB; zF~fT}9KQYc;6NH_RP2qMnV$;B?p0h|u+VrB$uQqB1sH4NygPYwK#)_$@5Q!SWJ`X{ zEA~F10rHx$mZWRdjBALlZM(8+OfNtsHRi9Zny-Q;k_z4%zWAMldG|?iL}cj(d-^W5%;nX zoQ=&smH-(`5M8^YdA;P%G3zl&{Lamanv2E#`3G`YfaH5Pj4LP)#EN|m{Z{2WM!G?M~YN+%FV0dcgi%UWT9gLJ;* zC)x1AUOun{$QYLb`<*Y0=k>S0^Qh!bm#XiiGz}r^F8mHh?<;+=$~|HQR{v%={Fegq z7IX8<+(ZMq)(6Si;!})4XQ@YIzW!?UoxiolfkVXp$l{cXpB6v;B>M{`d{990K|`w( zs%l>y6~MZ8!*F;RD+&N}k1YvDFG1n2?>&Dkx@IjsSQe?P%WpEeL!v++sBs~Z;=rXZ zz#~riC}IMFadC=X$5$FZU77}LE~cGyi2@FkH%K?bafxw_9ciC7(*0EuSHde5hq#!O zt$Swff>6Sx+(1}St5N^f-9bZb=ffRo4G&_04aHc#wyNJypg1a77b63#;@0uQ()g+^ z`T8Wp#Sb7s5na@VKks*l1tW+@jtH~>q0+9ug>&e$o-1mpQjZ4En^!wXr!Jq$% z0Qy5CvHz2L#eGLs<`!#zvERD+u#|Qn=Dft{);XTG)}^h{3S!V9aY7tTN*GbjzXLYh z?C_v?97Tr58XHTj+~!D{@%PWjv*SemCn0qtE;m8Hd)kLz)2(j@&9-Z0O47XB`l(_j zFE{ZGZ*df}F2+wI#NslMiSvFZa4J?(d6w_Dv}G$!>r_8>IA8vaQz)TIsXb@02)Gm_ zXuWvIEvzAy>4+&_{!IaHXUZ8t!;II8C#lrAyOI^3+qUB@|^lwlfb<{ckojbc=P6mRS-&XfoEidq=&NP zEB^{hUaiT6l*x39Tr)f{uHBIHpl{iYu%T{TpoyF%sX5^7U#VN zz`O@P?}(5Y=5UpzAuRhpDUV2S-R{En)U6mI3rJMV6d8Br2`lmg*B+!E!H)hy>j9}z zKqp_%hZ_Nh&DJasWZfBD+kh^Q0#|7LELkl6kayr({?_1tmg(l^;&GBAAm=gZ0NBVw zO_@o)pvzR@_?B@wtbgP9rUxlkObXT==BZJocQ2c`*9WS#(m*KnJ~;|CzE8O*XSDEh z`|TBm946&DU@G|y_L^{Uasdg;>%0CHyN# zwrx7eeWqa=R6Aylbj=(=te60O+;g69m(lq5a%KHo$xh3nX%?+skLE)gIH>mVn(^Ct zmcy0CoQmktWxg}{?kLsW+Rn$B?LKN2=WxG@1PxML;lp*6Q;n6Zs{>vRH- z5rTD-as$wBpeiD+-ykchdP|x0{u2$>?grsV3jJ^h3T!;|u6|>0Uu2+W?DLp#^tWSU zp*f?R%!hj}x8$J|v%_Uywc?)vPY@^W0@wPcs=w&{eOe1&>+e$#%BLdf8+u-k5=rK_ z+WtG5wz%v{5!%eO76;9z1K?AKflV~Qq#3uN0ZfVKCDVb>nXwPLA~gy$UYi0VVQgkn zpqE!N+O(tyfm(hSf(bCk#ifE>2hdnplcwu4^OAw|`lZiJlf(X81C{#fz!1$yNtNBh zcYDFLvtY41285ME%89N^ey;l=kx}_IIzsZu`D zG1?=k7Otr_l;HJw5>k}dQ<2LZag;SWsg}9!ccqz_v$?D;V8@cBA@jCz=&wqdeO}pP z)e}hjOCW7BAwO=|&-_-tk~zE0z;C+oyh~1SJ)7;2dsmgJZ?7klv7}$0<7+X>XGk~_ zM}Jgz$&vfZSE-BREOBto^_^+>@yk3PK1tu8ME#3Dk5_xPV{14)5pFFgf%DfdcU>g4 zk>}i&Vyc;2yutz;$#o>RA1$u>b9ny)_|wwg+GG!q(*P04(@meq)_6g9!?n*i0nI(T zj?|cLGE0S*F00{9quG+3{99|b%}71@8agJ%+j74@o2+&H(jkxjXb}vquxJPU6aRKO z_NL@vkhM>71{vWHZt-8FehdE|e?!v7dnfM%NQ6*@4`Dja3W+EsdGyC~(}=tLNc9)_ zUAp`g7(>r3u^UIi&ItFsvJ0(1GJ+swgq=4RJgzchu|9It zVUTDAY2zSbS4WwVav&!i$XOipg+>(LK@$KlACyiF^3my^IZk`fYrux z{oFi_UoU@{{Ae+2QLO48b{U0qTn7o`93BhI{2K9HTsT!>_{cESHxu=iY% zYA5X@xL@$fsH7eH^Z^!;Wwi0!Gbax=c{1p$ajsKd*%R>QQHhV$f?k+PKwY@G+PND+csH)G$rq>)sKl-Qrrs~!NUR)TKT`NecGn79CtvKvkICZpVqqkY59r_CD% zJ~R0{gEK!yn^!O5$i85MoHr5ezV6JgOcTLzH^U6m+P7%#vw$L~8(aO_mhn*T<=^@1b|6Q<5gy z)!P(<*Ar`KxNNdC30UBOp}G z1NiVppu@RbDHj)O4}^`x3JKZVKp%R0hXdN^axNRdMc^)D!ql;6S$SotFa70LZ>$1C zE$#1!JYYyyB&|M35}5D((UR%4`$QJt$AZTd3dEQ}iXrCOXjg^wI%uWxAOo{yYm!Isa71=jV@ zH@GbLuhI!fZwHp`z3^ubId{mT0JYhh90h^ajM$Og%j|8~{QsQ8W#vF|9!cjTiSv zQAfa)-^QI7{=^%ZsU7?3*+kT6|1iQoCvSAm_v+LU({4Qg2mk;&(;;*|KDdMzZ-sy% z!kJsJQjE4td}~wFjX4f~*A2BgUged)Q@2nW+DmDQZj}+CGX%N68t57JuzS3TINo4{ zOX2y(bE^xxN@i)<6DCdITO|CZu1-(z?_QBQS91ag4pc_tBTz%o1~{J|^(tm#x+l`H zs++5Z6`UAb26_SS)$JxZNNOOrk}voNP>%P827;cWAJ9&ZyPQb-lj&OUS5;m3FiF3j z3+t@-T*nOQ*y71y9-HexCbGZx%HfPO69*jWO;4}{!Kv>lqO1;iWpWa~7Zjbc5T6DF zO=W}E!$DLF(bRSWF0{NV7(^3Giu~`?d*~on+%X`FCLyXh*{vbun>!%w3A(SpF9C5= zwwSIj9+CF~r~*ubEY$F9!20gB>4~w-*&)U{$=@0J*QJtrjm}Gh>g+Li=({;Rk6Br|L4(c?Y{nl!P*`(RI-Nz zuI}0t^U)6Q^A*VAC=BkHKBnqyH~&>S<&(QjV^_-q6oF|dO+F88xP&nueWGme7;zIO z0z8xvNVNsmJ?ZZ%Z(8?uFdtc2<<^XidNND~jd(PH!h$Y9>L6E(xw?OthZiaj>jk|V z2s5(v4ALGG?CnPuWKYst)iN^Z5_>yfOi!E& zVy_gS?ldH)zX0K&Y?5wQ6BoyT3yTRy_#c2&!Hb2Do-aZEPRSk-!i-(5as=W%6!4Lp zQpvu5*V8MeE5rs83z5!m7`agQZoYp?;G%mj?fO0|AYT_uFn4F2K|=7&fg)u3_{AJa zzq&`!MDvnO7UWXEm*7T&M~JJ_N)UzP4X!x$67b?>UGzrwH@8{>;)LiL|AXdw;MqQq z79%}B`05O!nVVyX+YSCWywKrU^ws~Kt9;*_M&kdzXV4!Ccg078Z+^6I?3UUsfiY!Y zQcctxrYD}v>JJ5&sJ*!yRvZOXi(g(yVC@9bK~PryQ#O0{`Oh3CD%R5z#}#{g(4q%Vuo$seNj&lL& z=~=R(iDH_mbg}m?Ef5Sf_Pd74qe&@dc9dXmS8`9PfOcuI?3GZEq3W;csZN57^HeKn zjC^@{@5|9t!<~f2<$7LKfLk~dvhxU0^>E#IMRkN@xVL|E`aE95;yz2s^q*~o*U`i` z!B;^~4Va=Fd!R$0CYX`gxdKMyrQ6wawD&z?9Sw(*=6Dos<(gmeqCE4X#I#li?tS?KiC+( z*B4R`=eOE#Fs``;I}F_x0M}6v23*PD_W|fj@)Fah!;t^hb}%|-2dtexs>dUMV4iqk zRPVlLL<-PJB_NEd!~?9GJQy5r==&W`ssYZ-lb#?dLE_SICMW`z7;k1oscZdd^n!4H z;ZGzGOKDhMUy!uVI3aiVmRXjs_xl$|#;H;cIbk4UuQLOW$zte_#=s{llg|$*ikq&n zsH}U!C-a9c6)o&BcK%@WPNSt=NwW(j!a-bv-&}C}UsUJUR&G5Q2#vUtSJuOG?^{?F zqL)tfWOl(hP$BH>zinJ|C-ZI|MT7JygnqA5Y{I=Y+np${eIX>K)vB>1nm*pFp!f26jr*8miPVOP~8ks?j&{{%m*jvxYulOFcOX=5?^k8eO;u59#0 zfgWd{^ctNre5*UZ*Y6Oa*wo)wsu0IM&;8?rJ}4kbO8Cu8wwD;3hah9d$Z?QzCzbyOH-s33IEi%-)w9ZFkn>JB);)pDnFnetZZA z;DD4F2pc5l*kPbg)?SJQCAe?=Z6o=d`P&obVBE3hgqf-`vSIj}ET(RUmz;yLO7@bm zxk-BtQo(COLtypr0Qz&IRc%Kg$29+<6YMR!6!GYq_2c7IHdHdhjqb zXN4{e7pUiW!uP{KtM>WLNr`lr_k*_7Lj*}*EI#aX{BnP0_kGE&1O3Ir@K!Y&$_`ih zuG+xhs(Vv?#hdQif4KJ@BL{diF#7kzd|U~4->JlyoeuW)i2rMA%!`Dt=*w7bJt^F5 znM2OGN&0tOsdb8(hT3Xa*c@>y*h@MiHaYGSu#BOBl2JEUH`zD7w+;x_qJSOXPPY4R zI!m9ec^XZ`?Crj2Trm0Z!A2{#KOb0e&s&lPMp#YEu`-(JXp`jZ%y-p2E|c!>HPzpr z%JzZw2^Ygcp6T5PnZ*4Ou*C#OQQuV~&dGh$z>i*!?SB9;wEv}JLuDKiyRUI&K52%n zi<<2;$As>ZDJNc}11sJYq}mfYGo0C)eZa-rMed}AT`gI-@={?dJehK~5h@+~s#A*L zPilU5U)d}~H!Q08u`gH5(--z!@y3C!75DGO5MQEs#(+KA5-{TjyoWur(K$|wuvjyH zpOR1f$3wvkCK2TQ$+r-E$CV#BRZDmNNGN3s`j9%i@FjKv8UcAzhE6cm@D}a_MxuUo zx{0}c|G>ELc>AnJjol*TulzC?EsRR_ifA;~Hqs}h$+qyKo(-RfawzW?1qrexK{piA;MFSHHjW$S;oB!&MeB3`BewpyAppZv zuaon6mxz-1g3qx@afq+CRo@6pIuy;E_0DVXFkH&_1g!_Lfote5+>Nzyso=u(@e%X} zWvC~n5t&9=;c5zK9k`-KI%m~}_fu5UiJ)^n&OhA>T`16XVS(0cT9_IX{9~j0*m6o~ z8g<}J-kaBY#P7 zTk&;MP6VfAFcWci_MHFtHYvR`RzO(5iyQf}NCF4u3I6LH2_L;f(ATL>he(s6T?-fK z)W84_C$ccQxeL2o02{Ijo&hiVQ1WqhrEVSU8zrjA#&_n=v8qZ~ep=I$%V-VoqeK^U zhMI2Nk@~__mmJ1EIs&5p;3>{ViQk$j5lrCOnQrCy;hW!8MGND|UEw{d=*D+!{)fxp z6`eWkoE7Eqt2NwgZc2Cq7!!8s-kJ(mx7d$)Pq79viOU%0&`&4h52boWFU=Di|8l)TVX|)6A$Cs&N zYO40c!f;~|fVKAops_Z`J{$1KsWdO;GG$l%GTZc>@8w*&dlk24n zV^5PM;(cdsZnT~4B(mt6y=ktO_Hn|oMBf+e{BblOxa3|wQEXnJP!}XVn(do1q(AL= zdi^fhl6*jVNz!+te5vzCI&+%hRT;%}rwvB(2>XPa#ih(Q{aj#{ z%{_i?YFuF12gHM9fpSV2p>r6O%mztfkdjn1XEv9iWxl$fi_Y29ja|}Syv(ltNjIf8=d4irnzQU0$=gg2KnCly2FUys5e=AIi zL?z3N%I%(`b`d`L#t9=SlotGONkN$|ydV zP28*d-{6*%;WFQlZ!%ZT{Qp)O&nV=*{}o~P{!H)xFvR2?UpBI%BWxyBZv9Ct@UL1W z?JQY#=Q-=?;qlkAzy4N!s%Ya;s7N^L3Es=xPZ>5@EWY7LY8!cC(?bSs!|3B!w2+)GiLjX0o<`n))wamZ3pV6nPDI_M0^BnB5Y z2O7ptjf^a{xm*bzI7b`N+7tj6LB49QH{H};s{J`+hb|H7WvZSdopMoYnTBIw^>x_r zB}q?dvg?l)_-jhp#`LEjEgJY-NLhr2PxksVJp=LEStHB(%8vz0+%~4CN%i@TxR1l6 z5PaV=z3?is>wJqo9uAGAyAX1^va%~OPBB4}CPt(mBlT zvFwddrUP{)wzDJTWap1Cp`>x^{{fwVwY~f{+{3^00i#+M@Zuw! zk;R8Bq|MUxr0abmC5w7}HI^-h@vonEFQZMv#X8B* zzJ22O0XHHL3A~0Kc%Cu`!dUwp5=dvurp!%YDE=8=78wZhMrGXMM850+D5pRZ718|6 z;Tn(GoO^uB^2`kxMIp2v7r@zgVSOqcU~DNUsQoN-m61}=EJz?4sp zp%wH^vKgAWc8_)b$fsPyg88qnjG&OUkBHA);8Qlvo9<*0(n-=H zMmU10w;&iCUVAFoJ0$)=gsA4bne6TC7j2BYiWhT&k4W0ROMU9;g>|1p6r1zvd#>?>pLn$0&j+@Q<>ZRefPoF=K;U%lN-VqU)>`b_Bg%rtU%l z0Jtnk>uVrLo{_7Ot1h+^ePE8WBa?2~)z(6q`YUF}jxkGR6YVk5!5bdI-}&)q)@JE1 ateErTuVHe54fwA}5R9h5b)vfcgZ~8$>F)0U literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_paste.png b/src/PyViewer/resources/images/py_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..09cae7563c26c604b8e40bbdfd075148cc1de84d GIT binary patch literal 8660 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 literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_redo.png b/src/PyViewer/resources/images/py_redo.png new file mode 100644 index 0000000000000000000000000000000000000000..8e685b684acf3c373286b092bdc0d4b50223d1b1 GIT binary patch literal 9741 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 literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_save_as.png b/src/PyViewer/resources/images/py_save_as.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb1147509c105e8073aa8c0f815a88b906732e9 GIT binary patch literal 10555 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 literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/images/py_undo.png b/src/PyViewer/resources/images/py_undo.png new file mode 100644 index 0000000000000000000000000000000000000000..d6701f502353de157774cf7c549aa1db19ab7774 GIT binary patch literal 9449 zcmcIKi9gf-|C>3=a#T{uh?bHonX^5FfhAppJ$`h(kSy&VA-*VFd8&`3e2 zY2hcRKCtlca6?L6J3NkJEn7oqMl|{ASqqv+~!of!}6BQK}Z(e=$oE)`on4-E-u?9wu7h!xvOYogeCZSZpU((C^G z%&16L&rqmasZp_Pdbq%P?Z>e$?kr9}vvjv=6-E_n35&o4FoKX4%dSOnE;Iyeu6-!3 zRbbna3RA9aE?~zSD!3Arr0yauHoVg|Yf;gcc?M^Per@gYYw9e%k&_PI6Npn>gC9dM zB?Y2MXt|)UCu7a5Go|U#$xW1eeHvvewR=^>YDu)$gb3qSsR=vo)Y~zK}V`V2Bv^B5&qNgAls!}~mPIDY$(%S17*?!^q z{?UQbM6SV9Dy2y%vQUEqG^y-W!5by2UM(U_{+9#Y_CGawx>)ltXy*1#>#Py!l@83~SyXbch5|71UVqIJO~kh~ zsJ%7gdLlCa1ZIFG-acZ4BwtN$U*N>}8WuF&-_dop0QppHdN~$# zfEc?9wLi4dCgY61(RHk5?eW4u0~sr+r*T}5b?m)sDr_=FqGZ!6tIAa+J4Dgc$Sv4j z1Kjm0ACAL>r8$nS<3kXY@s(b^>WkV0^gCf`0oI07=-!RP4*lbN@phJFAIHITfELbg z-mNmaPgKKqe@YCq@ZdOvTI%EIc7N8*2T?TZto3k-r|ST*pXaJ)`coK=%=Y#D<8<+M zH+3XA|Ep3KV!ximPGsIcu1IxTh1!So1_*wW{U4Q9z~ z;uhW?`-HY0U_G*G)uqp`_zTzDKfI#Uh__eyt5zM!h%eM~MMfY-y{9d+>ijzOX?caW zvl07|!J487DlUn=^O|Dzni4^7Rp1rHjRwyZ`Q@*M&$sQN`9hnWP0omSN+)acvV&@V zAF@0FxN#M$T^kYgb6`_=(yJJ{QE#MH3#;t}_YIJa8edrKnlx#{3Yoj= zuD{4`my4x(V}I#!+V#B0fS5il>3tRdL_cj6_IK~aCcu?pAID!lO!mz$WFVm7L@pGV zu5p(dy564Lwuk#1Abl(_-Mm3+%Z>I2*>MLMOOy#MBmAhw;bl}eJ>2VCf43ctLik>9 zuW#E^-?X&NCndXAf9kq;qxY#r0a#Ukq+O2kafw$T#PZ}b z1+wbEY1X1n=|j9B$yGJ6I=u3I3F{D$IQC=0J2f}hA03FgxW?;CbaU63!_3(1Z@Dr* zHQ7M#h6O!V{4I@?B=JUDi0) zF$5_QsHrl_LZaK3*O~+|Wlp192$M~|tY|C^nMRLBnfKgJTv&z5`o6@X+pVtDc5sx$ zCVyFcUX#kctBJ~b1ig0_3!iTm?WcAdF@%w;>IBgz}c^rOPgh~ zHIz{~j!PzSC@Eekk)c*O)2=@&0(JuqT@s?%9o1J8Aq%Lq*kqjk`h#DcZ9D$gB9zI= zExfh+5-x*d0-d+2AA6{0vTW@h3pV4uixh*UumS2Q^i@FaCUEvmVL>{xp*xXeCPwE2 z;(Mav%Kw1m)Of?t?=b4j1F>@15~oKRKJ|}F#LA{1wd5GP$(dbZzfDqf0)YuKR=%&E z&bU?YlFq6oH}URd*>-qw9GLZEjk;{d@+H0}0;Fc6XqO9@FsgX(qIPe=*<~#0?&>cM zYWEJDowGg-FFf*4Df%M⪼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 literal 0 HcmV?d00001 diff --git a/src/PyViewer/resources/translations/PyViewer_msg_en.ts b/src/PyViewer/resources/translations/PyViewer_msg_en.ts new file mode 100644 index 000000000..26a70613c --- /dev/null +++ b/src/PyViewer/resources/translations/PyViewer_msg_en.ts @@ -0,0 +1,170 @@ + + + + + PyViewer_ViewManager + + PYVIEWER_VIEW_TITLE + Python viewer:%M - viewer:%V + + + + PyViewer_ViewWindow + + NAME_PYEDITOR + Python Viewer + + + LBL_TOOLBAR_LABEL + Edit Operations + + + MNU_PY_NEW + New + + + DSC_PY_NEW + Create a new python file + + + MNU_PY_OPEN + Open + + + DSC_PY_OPEN + Open an existing python file + + + MNU_PY_SAVE + Save + + + DSC_PY_SAVE + Save the python document to disk + + + MNU_PY_SAVEAS + Save As... + + + DSC_PY_SAVEAS + Save the python document under a new name + + + MNU_PY_CLOSE + Close + + + DSC_PY_CLOSE + Close the python document + + + MNU_PY_UNDO + Undo + + + DSC_PY_UNDO + Undoes the last operation + + + MNU_PY_REDO + Redo + + + DSC_PY_REDO + Redoes the last operation + + + MNU_PY_CUT + Cut + + + DSC_PY_CUT + Cut the current selection's contents to the clipboard + + + MNU_PY_COPY + Copy + + + DSC_PY_COPY + Copy the current selection's contents to the clipboard + + + MNU_PY_PASTE + Paste + + + DSC_PY_PASTE + Paste the clipboard's contents into the current selection + + + MNU_PY_DELETE + Delete + + + DSC_PY_DELETE + Delete the current selection's contents + + + MNU_PY_SELECTALL + Select All + + + DSC_PY_SELECT_ALL + Select all the contents + + + MNU_PY_PREFERENCES + Preferences + + + DSC_PY_PREFERENCES + Show the Preferences box + + + MNU_PY_BROWSER + Help Browser + + + DSC_PY_BROWSER + Show the Help browser + + + WRN_PY_READ_FILE + Cannot read file %1:\n%2. + + + WRN_PY_WRITE_FILE + Cannot write file %1:\n%2. + + + WRN_PY_SAVE_FILE + The document has been modified.<br>Do you want to save your changes? + + + TIT_DLG_SAVE + Save file + + + TIT_DLG_SAVEAS + Save file as + + + TIT_DLG_OPEN + Open file + + + STS_READY + Ready + + + STS_F_LOADED + File is loaded + + + STS_F_SAVED + File is saved + + + diff --git a/src/PyViewer/resources/translations/PyViewer_msg_fr.ts b/src/PyViewer/resources/translations/PyViewer_msg_fr.ts new file mode 100644 index 000000000..34ba46406 --- /dev/null +++ b/src/PyViewer/resources/translations/PyViewer_msg_fr.ts @@ -0,0 +1,14 @@ + + + + + PyViewer_ViewManager + + PYVIEWER_VIEW_TITLE + Éditeur de python:%M - viseur:%V + + + + PyViewer_ViewWindow + + diff --git a/src/PyViewer/resources/translations/PyViewer_msg_ja.ts b/src/PyViewer/resources/translations/PyViewer_msg_ja.ts new file mode 100644 index 000000000..711b492f9 --- /dev/null +++ b/src/PyViewer/resources/translations/PyViewer_msg_ja.ts @@ -0,0 +1,15 @@ + + + + + PyViewer_ViewManager + + PYVIEWER_VIEW_TITLE + Pythonのエディタ:%M - ビューア:%V + + + + + PyViewer_ViewWindow + + -- 2.39.2