From: mgn Date: Tue, 31 Mar 2015 16:52:45 +0000 (+0300) Subject: Integrates the python editor as new SALOME viewer. Writes comments. Designs icons... X-Git-Tag: V7_6_0a1^0 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=185b5a4dd2162e5addd1a08170c674e5a01a75d2;p=modules%2Fgui.git Integrates the python editor as new SALOME viewer. Writes comments. Designs icons. Re-packaging. --- 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 000000000..6fb0c81b7 Binary files /dev/null and b/src/PyViewer/resources/images/py_browser.png differ diff --git a/src/PyViewer/resources/images/py_close.png b/src/PyViewer/resources/images/py_close.png new file mode 100644 index 000000000..52efbf028 Binary files /dev/null and b/src/PyViewer/resources/images/py_close.png differ diff --git a/src/PyViewer/resources/images/py_copy.png b/src/PyViewer/resources/images/py_copy.png new file mode 100644 index 000000000..385b2c4e8 Binary files /dev/null and b/src/PyViewer/resources/images/py_copy.png differ diff --git a/src/PyViewer/resources/images/py_cut.png b/src/PyViewer/resources/images/py_cut.png new file mode 100644 index 000000000..77edc8ab8 Binary files /dev/null and b/src/PyViewer/resources/images/py_cut.png differ diff --git a/src/PyViewer/resources/images/py_delete.png b/src/PyViewer/resources/images/py_delete.png new file mode 100644 index 000000000..38581d16f Binary files /dev/null and b/src/PyViewer/resources/images/py_delete.png differ diff --git a/src/PyViewer/resources/images/py_new.png b/src/PyViewer/resources/images/py_new.png new file mode 100644 index 000000000..2a39dff50 Binary files /dev/null and b/src/PyViewer/resources/images/py_new.png differ diff --git a/src/PyViewer/resources/images/py_open.png b/src/PyViewer/resources/images/py_open.png new file mode 100644 index 000000000..26e0a2ce4 Binary files /dev/null and b/src/PyViewer/resources/images/py_open.png differ diff --git a/src/PyViewer/resources/images/py_paste.png b/src/PyViewer/resources/images/py_paste.png new file mode 100644 index 000000000..09cae7563 Binary files /dev/null and b/src/PyViewer/resources/images/py_paste.png differ diff --git a/src/PyViewer/resources/images/py_preferences.png b/src/PyViewer/resources/images/py_preferences.png new file mode 100644 index 000000000..f8c4c05ff Binary files /dev/null and b/src/PyViewer/resources/images/py_preferences.png differ diff --git a/src/PyViewer/resources/images/py_redo.png b/src/PyViewer/resources/images/py_redo.png new file mode 100644 index 000000000..8e685b684 Binary files /dev/null and b/src/PyViewer/resources/images/py_redo.png differ diff --git a/src/PyViewer/resources/images/py_save.png b/src/PyViewer/resources/images/py_save.png new file mode 100644 index 000000000..7a70c3475 Binary files /dev/null and b/src/PyViewer/resources/images/py_save.png differ diff --git a/src/PyViewer/resources/images/py_save_as.png b/src/PyViewer/resources/images/py_save_as.png new file mode 100644 index 000000000..4cb114750 Binary files /dev/null and b/src/PyViewer/resources/images/py_save_as.png differ diff --git a/src/PyViewer/resources/images/py_select_all.png b/src/PyViewer/resources/images/py_select_all.png new file mode 100644 index 000000000..d7b1c730f Binary files /dev/null and b/src/PyViewer/resources/images/py_select_all.png differ diff --git a/src/PyViewer/resources/images/py_undo.png b/src/PyViewer/resources/images/py_undo.png new file mode 100644 index 000000000..d6701f502 Binary files /dev/null and b/src/PyViewer/resources/images/py_undo.png differ diff --git a/src/PyViewer/resources/translations/PyViewer_msg_en.ts b/src/PyViewer/resources/translations/PyViewer_msg_en.ts new file mode 100644 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 + +