From b975b2713ca3a0679420c1bb0041ee9bc69c5048 Mon Sep 17 00:00:00 2001 From: vsr Date: Mon, 22 May 2017 14:16:04 +0300 Subject: [PATCH] PyEditor: implement Find/Replace feature --- src/PyViewer/CMakeLists.txt | 2 + src/PyViewer/PyViewer_ViewWindow.cxx | 71 +- src/PyViewer/PyViewer_ViewWindow.h | 5 +- src/PyViewer/resources/PyViewer_images.ts | 8 + src/PyViewer/resources/PyViewer_msg_en.ts | 24 + src/PyViewer/resources/PyViewer_msg_fr.ts | 24 + src/PyViewer/resources/PyViewer_msg_ja.ts | 24 + tools/PyEditor/src/CMakeLists.txt | 4 + tools/PyEditor/src/PyEditor.cxx | 29 +- tools/PyEditor/src/PyEditor_FindTool.cxx | 612 ++++++++++++++++++ tools/PyEditor/src/PyEditor_FindTool.h | 89 +++ tools/PyEditor/src/PyEditor_Widget.cxx | 251 +++++++ tools/PyEditor/src/PyEditor_Widget.h | 85 +++ tools/PyEditor/src/PyEditor_Window.cxx | 89 ++- tools/PyEditor/src/PyEditor_Window.h | 12 +- tools/PyEditor/src/python/PyEditorPy.sip | 72 +++ tools/PyEditor/src/resources/PyEditor.qrc | 5 + tools/PyEditor/src/resources/about.txt | 15 +- .../PyEditor/src/resources/images/py_find.png | Bin 0 -> 19794 bytes .../src/resources/images/py_find_next.png | Bin 0 -> 472 bytes .../src/resources/images/py_find_previous.png | Bin 0 -> 474 bytes .../src/resources/images/py_replace.png | Bin 0 -> 60177 bytes .../src/resources/images/py_search.png | Bin 0 -> 1264 bytes .../resources/translations/PyEditor_msg_en.ts | 80 ++- .../resources/translations/PyEditor_msg_fr.ts | 80 ++- .../resources/translations/PyEditor_msg_ja.ts | 80 ++- 26 files changed, 1588 insertions(+), 73 deletions(-) create mode 100644 tools/PyEditor/src/PyEditor_FindTool.cxx create mode 100644 tools/PyEditor/src/PyEditor_FindTool.h create mode 100644 tools/PyEditor/src/PyEditor_Widget.cxx create mode 100644 tools/PyEditor/src/PyEditor_Widget.h create mode 100644 tools/PyEditor/src/resources/images/py_find.png create mode 100644 tools/PyEditor/src/resources/images/py_find_next.png create mode 100644 tools/PyEditor/src/resources/images/py_find_previous.png create mode 100644 tools/PyEditor/src/resources/images/py_replace.png create mode 100644 tools/PyEditor/src/resources/images/py_search.png diff --git a/src/PyViewer/CMakeLists.txt b/src/PyViewer/CMakeLists.txt index bc4fbe352..85bcc87ee 100644 --- a/src/PyViewer/CMakeLists.txt +++ b/src/PyViewer/CMakeLists.txt @@ -69,12 +69,14 @@ SET(_other_RESOURCES ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_copy.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_cut.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_delete.png + ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_find.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_help.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_new.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_open.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_paste.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_preferences.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_redo.png + ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_replace.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_save.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_save_as.png ${PROJECT_SOURCE_DIR}/tools/PyEditor/src/resources/images/py_select_all.png diff --git a/src/PyViewer/PyViewer_ViewWindow.cxx b/src/PyViewer/PyViewer_ViewWindow.cxx index f7c37be7e..a9e6c79c2 100644 --- a/src/PyViewer/PyViewer_ViewWindow.cxx +++ b/src/PyViewer/PyViewer_ViewWindow.cxx @@ -22,7 +22,7 @@ #include "PyViewer_ViewWindow.h" -#include "PyEditor_Editor.h" +#include "PyEditor_Widget.h" #include "PyEditor_SettingsDlg.h" #include "SUIT_Session.h" @@ -32,9 +32,11 @@ #include "QtxActionToolMgr.h" #include +#include #include #include #include +#include /*! \class PyViewer_ViewWindow @@ -48,9 +50,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : SUIT_ViewWindow( desktop ) { - // Create editor and set it as a central widget. - myTextEditor = new PyEditor_Editor( this ); - setCentralWidget( myTextEditor ); + // Create central widget. + myEditor = new PyEditor_Widget( this ); + setCentralWidget( myEditor ); // Create actions. SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr(); @@ -82,7 +84,7 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : action->setShortcut( QKeySequence::Save ); connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( modificationChanged( bool ) ), + connect( myEditor, SIGNAL( modificationChanged( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, SaveId ); @@ -101,9 +103,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_UNDO" ), 0, this ); action->setStatusTip( tr( "DSC_UNDO" ) ); action->setShortcut( QKeySequence::Undo ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( undo() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( undo() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( undoAvailable( bool ) ), + connect( myEditor, SIGNAL( undoAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, UndoId ); @@ -113,9 +115,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_REDO" ), 0, this ); action->setStatusTip( tr( "DSC_REDO" ) ); action->setShortcut( QKeySequence::Redo ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( redo() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( redo() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( redoAvailable( bool ) ), + connect( myEditor, SIGNAL( redoAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, RedoId ); @@ -125,9 +127,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_CUT" ), 0, this ); action->setStatusTip( tr( "DSC_CUT" ) ); action->setShortcut( QKeySequence::Cut ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( cut() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( cut() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, CutId ); @@ -137,9 +139,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_COPY" ), 0, this ); action->setStatusTip( tr( "DSC_COPY" ) ); action->setShortcut( QKeySequence::Copy ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( copy() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, CopyId ); @@ -149,7 +151,7 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_PASTE" ), 0, this ); action->setStatusTip( tr( "DSC_PASTE" ) ); action->setShortcut( QKeySequence::Paste ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( paste() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); toolMgr()->registerAction( action, PasteId ); // . Delete @@ -158,9 +160,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_DELETE" ), 0, this ); action->setStatusTip( tr( "DSC_DELETE" ) ); action->setShortcut( QKeySequence::Delete ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( deleteSelected() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( deleteSelected() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); toolMgr()->registerAction( action, DeleteId ); @@ -170,9 +172,29 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : tr( "ACT_SELECT_ALL" ), 0, this ); action->setStatusTip( tr( "DSC_SELECT_ALL" ) ); action->setShortcut( QKeySequence::SelectAll ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( selectAll() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); toolMgr()->registerAction( action, SelectAllId ); + // . Find + action = new QtxAction( tr( "TTP_FIND" ), + resMgr->loadPixmap( "PyViewer", tr( "ICON_FIND" ) ), + tr( "ACT_FIND" ), 0, this ); + action->setStatusTip( tr( "DSC_FIND" ) ); + action->setShortcut( QKeySequence::Find ); + action->setShortcutContext( Qt::WidgetShortcut ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( find() ) ); + toolMgr()->registerAction( action, FindId ); + + // . Replace + action = new QtxAction( tr( "TTP_REPLACE" ), + resMgr->loadPixmap( "PyViewer", tr( "ICON_REPLACE" ) ), + tr( "ACT_REPLACE" ), 0, this ); + action->setStatusTip( tr( "DSC_REPLACE" ) ); + action->setShortcut( QKeySequence::Replace ); + action->setShortcutContext( Qt::WidgetShortcut ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( replace() ) ); + toolMgr()->registerAction( action, ReplaceId ); + // . Preferences action = new QtxAction( tr( "TTP_PREFERENCES" ), resMgr->loadPixmap( "PyViewer", tr( "ICON_PREFERENCES" ) ), @@ -205,6 +227,9 @@ PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) : toolMgr()->append( DeleteId, idTB ); toolMgr()->append( SelectAllId, idTB ); toolMgr()->append( toolMgr()->separator(), idTB ); + toolMgr()->append( FindId, idTB ); + toolMgr()->append( ReplaceId, idTB ); + toolMgr()->append( toolMgr()->separator(), idTB ); toolMgr()->append( PreferencesId, idTB ); toolMgr()->append( toolMgr()->separator(), idTB ); toolMgr()->append( HelpId, idTB ); @@ -239,7 +264,7 @@ void PyViewer_ViewWindow::onNew() { if ( whetherSave() ) { - myTextEditor->clear(); + myEditor->clear(); setCurrentFile( QString() ); } } @@ -299,7 +324,7 @@ bool PyViewer_ViewWindow::onSaveAs() */ void PyViewer_ViewWindow::onPreferences() { - PyEditor_SettingsDlg dlg( myTextEditor, true, this ); + PyEditor_SettingsDlg dlg( myEditor->editor(), true, this ); connect( &dlg, SIGNAL( help() ), this, SLOT( onHelp() ) ); dlg.exec(); } @@ -311,7 +336,7 @@ void PyViewer_ViewWindow::onPreferences() void PyViewer_ViewWindow::setCurrentFile( const QString& filePath ) { myURL = filePath; - myTextEditor->document()->setModified( false ); + myEditor->setModified( false ); } /*! @@ -321,7 +346,7 @@ void PyViewer_ViewWindow::setCurrentFile( const QString& filePath ) */ bool PyViewer_ViewWindow::whetherSave() { - if ( myTextEditor->document()->isModified() ) + if ( myEditor->isModified() ) { QMessageBox::StandardButton answer = QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), @@ -358,7 +383,7 @@ void PyViewer_ViewWindow::loadFile( const QString& filePath ) QTextStream anInput( &aFile ); QApplication::setOverrideCursor( Qt::WaitCursor ); - myTextEditor->setPlainText( anInput.readAll() ); + myEditor->setText( anInput.readAll() ); QApplication::restoreOverrideCursor(); setCurrentFile( filePath ); @@ -382,7 +407,7 @@ bool PyViewer_ViewWindow::saveFile( const QString& filePath ) QTextStream anOutput( &aFile ); QApplication::setOverrideCursor( Qt::WaitCursor ); - anOutput << myTextEditor->toPlainText(); + anOutput << myEditor->text(); QApplication::restoreOverrideCursor(); setCurrentFile( filePath ); diff --git a/src/PyViewer/PyViewer_ViewWindow.h b/src/PyViewer/PyViewer_ViewWindow.h index b96ea8b2c..229721d68 100644 --- a/src/PyViewer/PyViewer_ViewWindow.h +++ b/src/PyViewer/PyViewer_ViewWindow.h @@ -27,7 +27,7 @@ #include -class PyEditor_Editor; +class PyEditor_Widget; class PYVIEWER_EXPORT PyViewer_ViewWindow : public SUIT_ViewWindow { @@ -36,6 +36,7 @@ class PYVIEWER_EXPORT PyViewer_ViewWindow : public SUIT_ViewWindow public: enum { NewId, OpenId, SaveId, SaveAsId, UndoId, RedoId, CutId, CopyId, PasteId, DeleteId, SelectAllId, + FindId, ReplaceId, PreferencesId, HelpId }; PyViewer_ViewWindow( SUIT_Desktop* = 0 ); @@ -61,7 +62,7 @@ private: QString defaultName() const; private: - PyEditor_Editor* myTextEditor; + PyEditor_Widget* myEditor; QString myURL; }; diff --git a/src/PyViewer/resources/PyViewer_images.ts b/src/PyViewer/resources/PyViewer_images.ts index 639f1b584..6ac464e6c 100644 --- a/src/PyViewer/resources/PyViewer_images.ts +++ b/src/PyViewer/resources/PyViewer_images.ts @@ -47,6 +47,14 @@ ICON_SELECT_ALL py_select_all.png + + ICON_FIND + py_find.png + + + ICON_REPLACE + py_replace.png + ICON_PREFERENCES py_preferences.png diff --git a/src/PyViewer/resources/PyViewer_msg_en.ts b/src/PyViewer/resources/PyViewer_msg_en.ts index 0f4355ee3..20e989a1a 100644 --- a/src/PyViewer/resources/PyViewer_msg_en.ts +++ b/src/PyViewer/resources/PyViewer_msg_en.ts @@ -146,6 +146,30 @@ DSC_SELECT_ALL Select all the contents + + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES Pre&ferences diff --git a/src/PyViewer/resources/PyViewer_msg_fr.ts b/src/PyViewer/resources/PyViewer_msg_fr.ts index c0fc973cd..7a52883c0 100644 --- a/src/PyViewer/resources/PyViewer_msg_fr.ts +++ b/src/PyViewer/resources/PyViewer_msg_fr.ts @@ -146,6 +146,30 @@ DSC_SELECT_ALL Sélectionne tout le contenu + + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES Préférences diff --git a/src/PyViewer/resources/PyViewer_msg_ja.ts b/src/PyViewer/resources/PyViewer_msg_ja.ts index 6b8691da3..35c4e054e 100644 --- a/src/PyViewer/resources/PyViewer_msg_ja.ts +++ b/src/PyViewer/resources/PyViewer_msg_ja.ts @@ -146,6 +146,30 @@ DSC_SELECT_ALL 全選択 + + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES 環境設定 (&f) diff --git a/tools/PyEditor/src/CMakeLists.txt b/tools/PyEditor/src/CMakeLists.txt index 2715e6ebd..53f50bbea 100644 --- a/tools/PyEditor/src/CMakeLists.txt +++ b/tools/PyEditor/src/CMakeLists.txt @@ -43,11 +43,13 @@ SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES}) # header files / to be processed by moc SET(_moc_HEADERS PyEditor_Editor.h + PyEditor_FindTool.h PyEditor_LineNumberArea.h PyEditor_Keywords.h PyEditor_Completer.h PyEditor_PyHighlighter.h PyEditor_SettingsDlg.h + PyEditor_Widget.h PyEditor_Window.h ) @@ -84,6 +86,7 @@ QT_ADD_RESOURCES(_rcc_SOURCES ${_rcc_RESOURCES}) # sources / static SET(_other_SOURCES PyEditor_Editor.cxx + PyEditor_FindTool.cxx PyEditor_LineNumberArea.cxx PyEditor_Keywords.cxx PyEditor_Completer.cxx @@ -91,6 +94,7 @@ SET(_other_SOURCES PyEditor_Settings.cxx PyEditor_SettingsDlg.cxx PyEditor_StdSettings.cxx + PyEditor_Widget.cxx PyEditor_Window.cxx ) diff --git a/tools/PyEditor/src/PyEditor.cxx b/tools/PyEditor/src/PyEditor.cxx index 03a29b8e9..5a213338d 100644 --- a/tools/PyEditor/src/PyEditor.cxx +++ b/tools/PyEditor/src/PyEditor.cxx @@ -24,6 +24,7 @@ #include "PyEditor_StdSettings.h" #include +#include #include #include #include @@ -67,11 +68,11 @@ int main( int argc, char *argv[] ) app.setOrganizationDomain( "www.salome-platform.org" ); app.setApplicationName( "pyeditor" ); + QLocale locale; + PyEditor_StdSettings* settings = new PyEditor_StdSettings(); PyEditor_Settings::setSettings( settings ); - QString language = settings->language(); - // Load Qt translations. QString qtDirTrSet = QLibraryInfo::location( QLibraryInfo::TranslationsPath ); QString qtDirTrEnv = qtTrDir(); @@ -85,7 +86,11 @@ int main( int argc, char *argv[] ) foreach( QString qtTrFile, qtTrFiles ) { foreach ( QString qtTrDir, qtTrDirs ) { QTranslator* translator = new QTranslator; - if ( translator->load( QString("%1_%2").arg( qtTrFile ).arg( language ), qtTrDir ) ) { + if ( translator->load( locale, QString("%1").arg( qtTrFile ), "_", qtTrDir ) ) { + app.installTranslator( translator ); + break; + } + else if ( translator->load( QString("%1_en").arg( qtTrFile ), qtTrDir ) ) { app.installTranslator( translator ); break; } @@ -97,13 +102,27 @@ int main( int argc, char *argv[] ) // Load application's translations. QTranslator translator; - if ( translator.load( QString( "PyEditor_msg_%1" ).arg( language ), resourceDir() ) ) + if ( translator.load( locale, QString( "PyEditor_msg" ), "_", resourceDir() ) ) app.installTranslator( &translator ); - + else if ( translator.load( QString( "PyEditor_msg_en" ), resourceDir() ) ) + app.installTranslator( &translator ); + + QCommandLineParser parser; + parser.setApplicationDescription( QApplication::translate( "PyEditor", "PROGRAM_DESCRIPTION" ) ); + parser.addHelpOption(); + parser.addPositionalArgument( QApplication::translate( "PyEditor", "FILE_PARAM_NAME" ), + QApplication::translate( "PyEditor", "FILE_PARAM_DESCRIPTION" ) ); + + parser.process( app ); + const QStringList args = parser.positionalArguments(); + PyEditor_Window window; window.setWindowIcon( QIcon( ":/images/py_editor.png" ) ); window.resize( 650, 700 ); window.show(); + + if ( args.count() > 0 ) + window.loadFile( args[0], false ); return app.exec(); } diff --git a/tools/PyEditor/src/PyEditor_FindTool.cxx b/tools/PyEditor/src/PyEditor_FindTool.cxx new file mode 100644 index 000000000..de6be82e5 --- /dev/null +++ b/tools/PyEditor/src/PyEditor_FindTool.cxx @@ -0,0 +1,612 @@ +// Copyright (C) 2015-2016 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_FindTool.cxx +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) +// + +#include "PyEditor_FindTool.h" +#include "PyEditor_Editor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \class PyEditor_FindTool + \brief Find / Replace widget for PyEditor +*/ + +/*! + \brief Constructor. + \param editor Python editor widget. + \param parent Parent widget. +*/ +PyEditor_FindTool::PyEditor_FindTool( PyEditor_Editor* editor, QWidget* parent ) + : QWidget( parent ), myEditor( editor ) +{ + QLabel* findLabel = new QLabel( tr( "FIND_LABEL" ), this ); + myFindEdit = new QLineEdit( this ); + myFindEdit->setClearButtonEnabled( true ); + myFindEdit->installEventFilter( this ); + connect( myFindEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( find( const QString& ) ) ); + connect( myFindEdit, SIGNAL( returnPressed() ), this, SLOT( findNext() ) ); + myFindEdit->setCompleter( new QCompleter( myFindEdit ) ); + myFindEdit->completer()->setModel( &myFindCompletion ); + + QLabel* replaceLabel = new QLabel( tr( "REPLACE_LABEL" ), this ); + myReplaceEdit = new QLineEdit( this ); + myReplaceEdit->setClearButtonEnabled( true ); + myReplaceEdit->installEventFilter( this ); + myReplaceEdit->setCompleter( new QCompleter( myReplaceEdit ) ); + myReplaceEdit->completer()->setModel( &myReplaceCompletion ); + + myInfoLabel = new QLabel( this ); + myInfoLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); + + QToolButton* prevBtn = new QToolButton( this ); + prevBtn->setIcon( QIcon( ":images/py_find_previous.png" ) ); + prevBtn->setAutoRaise( true ); + connect( prevBtn, SIGNAL( clicked() ), this, SLOT( findPrevious() ) ); + + QToolButton* nextBtn = new QToolButton( this ); + nextBtn->setIcon( QIcon( ":images/py_find_next.png" ) ); + nextBtn->setAutoRaise( true ); + connect( nextBtn, SIGNAL( clicked() ), this, SLOT( findNext() ) ); + + QToolButton* replaceBtn = new QToolButton(); + replaceBtn->setText( tr( "REPLACE_BTN" ) ); + replaceBtn->setAutoRaise( true ); + connect( replaceBtn, SIGNAL( clicked() ), this, SLOT( replace() ) ); + + QToolButton* replaceAllBtn = new QToolButton(); + replaceAllBtn->setText( tr( "REPLACE_ALL_BTN" ) ); + replaceAllBtn->setAutoRaise( true ); + connect( replaceAllBtn, SIGNAL( clicked() ), this, SLOT( replaceAll() ) ); + + QHBoxLayout* hl = new QHBoxLayout; + hl->setContentsMargins( 0, 0, 0, 0 ); + hl->setSpacing( 0 ); + hl->addWidget( prevBtn ); + hl->addWidget( nextBtn ); + + QGridLayout* l = new QGridLayout( this ); + l->setContentsMargins( 6, 2, 6, 2 ); + l->setSpacing( 2 ); + l->addWidget( findLabel, 0, 0 ); + l->addWidget( myFindEdit, 0, 1 ); + l->addLayout( hl, 0, 2 ); + l->addWidget( myInfoLabel, 0, 3 ); + l->addWidget( replaceLabel, 1, 0 ); + l->addWidget( myReplaceEdit, 1, 1 ); + l->addWidget( replaceBtn, 1, 2 ); + l->addWidget( replaceAllBtn, 1, 3 ); + + QAction* menuAction = myFindEdit->addAction( QIcon(":images/py_search.png"), QLineEdit::LeadingPosition ); + connect( menuAction, SIGNAL( triggered( bool ) ), this, SLOT( showMenu() ) ); + + addAction( new QAction( tr( "CASE_SENSITIVE_CHECK" ), this ) ); + addAction( new QAction( tr( "WHOLE_WORDS_CHECK" ), this ) ); + addAction( new QAction( tr( "REGEX_CHECK" ), this ) ); + addAction( new QAction( tr( "Find" ), this ) ); + addAction( new QAction( tr( "FindPrevious" ), this ) ); + addAction( new QAction( tr( "FindNext" ), this ) ); + addAction( new QAction( tr( "Replace" ), this ) ); + + foreach ( QAction* action, actions().mid( CaseSensitive, RegExp+1 ) ) + { + action->setCheckable( true ); + connect( action, SIGNAL( toggled( bool ) ), this, SLOT( update() ) ); + } + + QSignalMapper* mapper = new QSignalMapper( this ); + connect( mapper, SIGNAL( mapped( int ) ), this, SLOT( activate( int ) ) ); + + for ( int i = Find; i < actions().count(); i++ ) + { + QAction* action = actions()[i]; + action->setShortcuts( shortcuts( i ) ); + action->setShortcutContext( Qt::WidgetWithChildrenShortcut ); + connect( action, SIGNAL( triggered( bool ) ), mapper, SLOT( map() ) ); + mapper->setMapping( action, i ); + myEditor->addAction( action ); + } + + myEditor->installEventFilter( this ); + + hide(); +} + +/*! + \brief Process events for this widget, + \param e Event being processed. + \return true if event's processing should be stopped; false otherwise. +*/ +bool PyEditor_FindTool::event( QEvent* e ) +{ + if ( e->type() == QEvent::EnabledChange ) + { + updateShortcuts(); + } + else if ( e->type() == QEvent::KeyPress ) + { + QKeyEvent* ke = (QKeyEvent*)e; + switch ( ke->key() ) + { + case Qt::Key_Escape: + hide(); + break; + default: + break; + } + } + else if ( e->type() == QEvent::Hide ) + { + addCompletion( myFindEdit->text(), false ); + addCompletion( myReplaceEdit->text(), true ); + myEditor->setFocus(); + } + return QWidget::event( e ); +} + +/*! + \brief Filter events from watched objects. + \param o Object being watched. + \param e Event being processed. + \return true if event should be filtered out; false otherwise. +*/ +bool PyEditor_FindTool::eventFilter( QObject* o, QEvent* e ) +{ + if ( o == myFindEdit ) + { + if ( e->type() == QEvent::KeyPress ) + { + QKeyEvent* keyEvent = (QKeyEvent*)e; + if ( keyEvent->key() == Qt::Key_Escape && !myFindEdit->text().isEmpty() ) + { + addCompletion( myFindEdit->text(), false ); + myFindEdit->clear(); + return true; + } + } + } + else if ( o == myReplaceEdit ) + { + if ( e->type() == QEvent::KeyPress ) + { + QKeyEvent* keyEvent = (QKeyEvent*)e; + if ( keyEvent->key() == Qt::Key_Escape && !myReplaceEdit->text().isEmpty() ) + { + myReplaceEdit->clear(); + return true; + } + } + } + else if ( o == myEditor ) + { + if ( e->type() == QEvent::EnabledChange ) + { + setEnabled( myEditor->isEnabled() ); + } + else if ( e->type() == QEvent::Hide ) + { + hide(); + } + else if ( e->type() == QEvent::KeyPress ) + { + QKeyEvent* ke = (QKeyEvent*)e; + switch ( ke->key() ) + { + case Qt::Key_Escape: + if ( isVisible() ) + hide(); + break; + default: + break; + } + } + } + return QWidget::eventFilter( o, e ); +} + +/*! + \brief Slot: activate 'Find' dialog. +*/ +void PyEditor_FindTool::activateFind() +{ + activate( Find ); +} + +/*! + \brief Slot: activate 'Replace' dialog. +*/ +void PyEditor_FindTool::activateReplace() +{ + activate( Replace ); +} + +/*! + \brief Slot: show context menu with search options. + \internal +*/ +void PyEditor_FindTool::showMenu() +{ + QMenu::exec( actions().mid( CaseSensitive, RegExp+1 ), QCursor::pos() ); +} + +/*! + \brief Slot: find text being typed in the 'Find' control. + \param text Text entered by the user. + \internal +*/ +void PyEditor_FindTool::find( const QString& text ) +{ + find( text, 0 ); +} + +/*! + \brief Slot: find text entered in the 'Find' control. + \internal + \overload +*/ +void PyEditor_FindTool::find() +{ + find( myFindEdit->text(), 0 ); +} + +/*! + \brief Slot: find previous matched item; called when user presses 'Previous' button. + \internal +*/ +void PyEditor_FindTool::findPrevious() +{ + find( myFindEdit->text(), -1 ); +} + +/*! + \brief Slot: find next matched item; called when user presses 'Next' button. + \internal +*/ +void PyEditor_FindTool::findNext() +{ + find( myFindEdit->text(), 1 ); +} + +/*! + \brief Slot: replace currently selected match; called when user presses 'Replace' button. + \internal +*/ +void PyEditor_FindTool::replace() +{ + QString text = myFindEdit->text(); + QString replacement = myReplaceEdit->text(); + + QTextCursor editor = myEditor->textCursor(); + if ( editor.hasSelection() && editor.selectedText() == text ) + { + editor.beginEditBlock(); + editor.removeSelectedText(); + editor.insertText( replacement ); + editor.endEditBlock(); + find(); + } +} + +/*! + \brief Slot: replace all matches; called when user presses 'Replace All' button. + \internal +*/ +void PyEditor_FindTool::replaceAll() +{ + QString text = myFindEdit->text(); + QString replacement = myReplaceEdit->text(); + QList results = matches( text ); + if ( !results.isEmpty() ) + { + QTextCursor editor( myEditor->document() ); + editor.beginEditBlock(); + foreach ( QTextCursor cursor, results ) + { + editor.setPosition( cursor.anchor() ); + editor.setPosition( cursor.position(), QTextCursor::KeepAnchor ); + editor.removeSelectedText(); + editor.insertText( replacement ); + } + editor.endEditBlock(); + find(); + } +} + +/*! + \brief Slot: restart search; called when search options are changed. + \internal +*/ +void PyEditor_FindTool::update() +{ + find(); +} + +/*! + \brief Slot: activate action; called when user types corresponding shortcut. + \param action Action being activated. + \internal +*/ +void PyEditor_FindTool::activate( int action ) +{ + QTextCursor cursor = myEditor->textCursor(); + cursor.movePosition( QTextCursor::StartOfWord ); + cursor.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor ); + QString word = cursor.selectedText(); + + switch ( action ) + { + case Find: + case Replace: + showReplaceControls( action == Replace ); + show(); + if ( !word.isEmpty() ) { + myFindEdit->setText( word ); + myEditor->setTextCursor( cursor ); + } + myFindEdit->setFocus(); + myFindEdit->selectAll(); + find( myFindEdit->text() ); + break; + case FindPrevious: + findPrevious(); + break; + case FindNext: + findNext(); + break; + default: + break; + } +} + +/*! + \brief Get shortcuts for given action. + \param action Editor's action. + \return List of shortcuts. + \internal +*/ +QList PyEditor_FindTool::shortcuts( int action ) const +{ + QList bindings; + switch ( action ) + { + case Find: + bindings << QKeySequence( QKeySequence::Find ); + break; + case FindPrevious: + bindings << QKeySequence( QKeySequence::FindPrevious ); + break; + case FindNext: + bindings << QKeySequence( QKeySequence::FindNext ); + break; + case Replace: + bindings << QKeySequence( QKeySequence::Replace ); + bindings << QKeySequence( "Ctrl+H" ); + break; + default: + break; + } + return bindings; +} + +/*! + \brief Update shortcuts when widget is enabled / disabled. + \internal +*/ +void PyEditor_FindTool::updateShortcuts() +{ + foreach ( QAction* action, actions().mid( Find ) ) + { + action->setEnabled( isEnabled() && myEditor->isEnabled() ); + } +} + +/*! + \brief Show / hide 'Replace' controls. + \param on Visibility flag. + \internal +*/ +void PyEditor_FindTool::showReplaceControls( bool on ) +{ + QGridLayout* l = qobject_cast( layout() ); + for ( int j = 0; j < l->columnCount(); j++ ) + { + if ( l->itemAtPosition( 1, j )->widget() ) + l->itemAtPosition( 1, j )->widget()->setVisible( on ); + } +} + +/*! + \brief Set palette for 'Find' tool depending on results of search. + \param found Search result: true in case of success; false otherwise. + \internal +*/ +void PyEditor_FindTool::setSearchResult( bool found ) +{ + QPalette pal = myFindEdit->palette(); + QPalette ref = myReplaceEdit->palette(); + pal.setColor( QPalette::Active, QPalette::Text, + found ? ref.color( QPalette::Active, QPalette::Text ) : QColor( 255, 0, 0 ) ); + myFindEdit->setPalette( pal ); +} + +/*! + \brief Get 'Use regular expression' search option. + \return true if option is switched on; false otherwise. + \internal +*/ +bool PyEditor_FindTool::isRegExp() const +{ + return actions()[RegExp]->isChecked(); +} + +/*! + \brief Get 'Case sensitive search' search option. + \return true if option is switched on; false otherwise. + \internal +*/ +bool PyEditor_FindTool::isCaseSensitive() const +{ + return actions()[CaseSensitive]->isChecked(); +} + +/*! + \brief Get 'Whole words only' search option. + \return true if option is switched on; false otherwise. + \internal +*/ +bool PyEditor_FindTool::isWholeWord() const +{ + return actions()[WholeWord]->isChecked(); +} + +/*! + \brief Get search options. + \param back Search direction: backward if false; forward otherwise. + \return List of options + \internal +*/ +QTextDocument::FindFlags PyEditor_FindTool::searchFlags( bool back ) const +{ + QTextDocument::FindFlags flags = 0; + if ( isCaseSensitive() ) + flags |= QTextDocument::FindCaseSensitively; + if ( isWholeWord() ) + flags |= QTextDocument::FindWholeWords; + if ( back ) + flags |= QTextDocument::FindBackward; + return flags; +} + +/*! + \brief Get all matches from Python editor. + \param text Text being searched. + \return List of all matches. + \internal +*/ +QList PyEditor_FindTool::matches( const QString& text ) const +{ + QList results; + + QTextDocument* document = myEditor->document(); + + QTextCursor cursor( document ); + while ( !cursor.isNull() ) + { + cursor = isRegExp() ? + document->find( QRegExp( text, isCaseSensitive() ? + Qt::CaseSensitive : Qt::CaseInsensitive ), + cursor, searchFlags() ) : + document->find( text, cursor, searchFlags() ); + if ( !cursor.isNull() ) + results.append( cursor ); + } + return results; +} + +/*! + \brief Find specified text. + \param text Text being searched. + \param delta Search direction. + \internal +*/ +void PyEditor_FindTool::find( const QString& text, int delta ) +{ + QTextCursor cursor = myEditor->textCursor(); + int position = qMin( cursor.position(), cursor.anchor() ) + delta; + cursor.setPosition( position ); + myEditor->setTextCursor( cursor ); + + QList results = matches( text ); + + int index = -1; + if ( !results.isEmpty() ) + { + if ( delta >= 0 ) + { + // search forward + if ( position > results.last().anchor() ) + position = 0; + for ( int i = 0; i < results.count() && index == -1; i++ ) + { + QTextCursor result = results[i]; + if ( result.hasSelection() && position <= result.anchor() ) + { + index = i; + } + } + } + else + { + // search backward + if ( position < results.first().position() ) + position = results.last().position(); + + for ( int i = results.count()-1; i >= 0 && index == -1; i-- ) + { + QTextCursor result = results[i]; + if ( result.hasSelection() && position >= result.position() ) + { + index = i; + } + } + } + } + if ( index != -1 ) + { + myInfoLabel->setText( tr( "NB_MATCHED_LABEL" ).arg( index+1 ).arg( results.count() ) ); + myEditor->setTextCursor( results[index] ); + } + else + { + myInfoLabel->clear(); + cursor.clearSelection(); + myEditor->setTextCursor( cursor ); + } + + setSearchResult( text.isEmpty() || !results.isEmpty() ); +} + +/*! + \brief Add completion. + \param text Completeion being added. + \param replace true to add 'Replace' completion; false to add 'Find' completion. + \internal +*/ +void PyEditor_FindTool::addCompletion( const QString& text, bool replace ) +{ + QStringListModel& model = replace ? myReplaceCompletion : myFindCompletion; + + QStringList completions = model.stringList(); + if ( !text.isEmpty() and !completions.contains( text ) ) + { + completions.prepend( text ); + model.setStringList( completions ); + } +} diff --git a/tools/PyEditor/src/PyEditor_FindTool.h b/tools/PyEditor/src/PyEditor_FindTool.h new file mode 100644 index 000000000..e6501700e --- /dev/null +++ b/tools/PyEditor/src/PyEditor_FindTool.h @@ -0,0 +1,89 @@ +// Copyright (C) 2015-2016 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_FindTool.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) +// + +#ifndef PYEDITOR_FINDTOOL_H +#define PYEDITOR_FINDTOOL_H + +#include "PyEditor.h" + +#include +#include +#include + +class PyEditor_Editor; +class QAction; +class QLabel; +class QLineEdit; + +class PYEDITOR_EXPORT PyEditor_FindTool : public QWidget +{ +Q_OBJECT + + enum { CaseSensitive, WholeWord, RegExp, Find, FindPrevious, FindNext, Replace }; + +public: + PyEditor_FindTool( PyEditor_Editor*, QWidget* = 0 ); + + bool event( QEvent* ); + bool eventFilter( QObject*, QEvent* ); + +public slots: + void activateFind(); + void activateReplace(); + +private slots: + void showMenu(); + void find( const QString& ); + void find(); + void findPrevious(); + void findNext(); + void replace(); + void replaceAll(); + void update(); + void activate( int ); + +private: + QList shortcuts( int ) const; + void updateShortcuts(); + + void showReplaceControls( bool ); + void setSearchResult( bool ); + + bool isRegExp() const; + bool isCaseSensitive() const; + bool isWholeWord() const; + QTextDocument::FindFlags searchFlags( bool = false ) const; + + QList matches( const QString& ) const; + void find( const QString&, int ); + + void addCompletion( const QString&, bool ); + +private: + PyEditor_Editor* myEditor; + QLineEdit* myFindEdit; + QLineEdit* myReplaceEdit; + QLabel* myInfoLabel; + QStringListModel myFindCompletion, myReplaceCompletion; +}; + +#endif // PYEDITOR_FINDTOOL_H diff --git a/tools/PyEditor/src/PyEditor_Widget.cxx b/tools/PyEditor/src/PyEditor_Widget.cxx new file mode 100644 index 000000000..badf97329 --- /dev/null +++ b/tools/PyEditor/src/PyEditor_Widget.cxx @@ -0,0 +1,251 @@ +// Copyright (C) 2015-2016 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_Widget.cxx +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) +// + +#include "PyEditor_Editor.h" +#include "PyEditor_FindTool.h" +#include "PyEditor_Widget.h" + +#include + +/*! + \class PyEditor_Widget + \brief Wraps Python editor with the find/replace functionality to a single widget. +*/ + +/*! + \brief Constructor. + \param parent Parent widget. +*/ +PyEditor_Widget::PyEditor_Widget( QWidget* parent ) +{ + // Create editor. + myEditor = new PyEditor_Editor( this ); + + // Create find tool. + myFindTool = new PyEditor_FindTool( myEditor, this ); + + // Set-up layout + QVBoxLayout* layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->setSpacing( 3 ); + layout->addWidget( myEditor ); + layout->addWidget( myFindTool ); + + connect( myEditor, SIGNAL( modificationChanged( bool ) ), + this, SIGNAL( modificationChanged( bool ) ) ); + connect( myEditor, SIGNAL( undoAvailable( bool ) ), + this, SIGNAL( undoAvailable( bool ) ) ); + connect( myEditor, SIGNAL( redoAvailable( bool ) ), + this, SIGNAL( redoAvailable( bool ) ) ); + connect( myEditor, SIGNAL( copyAvailable( bool ) ), + this, SIGNAL( copyAvailable( bool ) ) ); + + connect( myEditor, SIGNAL( selectionChanged() ), + this, SIGNAL( selectionChanged() ) ); + connect( myEditor, SIGNAL( textChanged() ), + this, SIGNAL( textChanged() ) ); + connect( myEditor, SIGNAL( cursorPositionChanged() ), + this, SIGNAL( cursorPositionChanged() ) ); + + setFocusProxy( myEditor ); +} + +/*! + \brief Get editor. + \return Pointer to editor. +*/ +PyEditor_Editor* PyEditor_Widget::editor() +{ + return myEditor; +} + +/*! + \brief Get find tool. + \return Pointer to find tool. +*/ +PyEditor_FindTool* PyEditor_Widget::findTool() +{ + return myFindTool; +} + +/*! + \brief Get all custom keywords from editor. + \return List of keywords. +*/ +QStringList PyEditor_Widget::keywords() const +{ + return myEditor->keywords(); +} + +/*! + \brief Set custom keywords to editor. + \param keywords List of keywords. + \param type Type of keywords (group id). + \param color Color of keywords. +*/ +void PyEditor_Widget::appendKeywords( const QStringList& keywords, int type, const QColor& color ) +{ + myEditor->appendKeywords( keywords, type, color ); +} + +/*! + \brief Remove given custom keywords from editor. + \param keywords List of keywords to remove. +*/ +void PyEditor_Widget::removeKeywords( const QStringList& keywords ) +{ + myEditor->removeKeywords( keywords ); +} + +/*! + \brief Get current editor's completion policy. + \return Completion policy (see PyEditor_Editor::CompletionPolicy). +*/ +int PyEditor_Widget::completionPolicy() const +{ + return (int) myEditor->completionPolicy(); +} + +/*! + \brief Set editor's completion policy. + \param policy Completion policy (see PyEditor_Editor::CompletionPolicy). +*/ +void PyEditor_Widget::setCompletionPolicy( int policy ) +{ + myEditor->setCompletionPolicy( (PyEditor_Editor::CompletionPolicy) policy ); +} + +/*! + \brief Activate Find dialog. +*/ +void PyEditor_Widget::find() +{ + myFindTool->activateFind(); +} + +/*! + \brief Activate Replace dialog. +*/ +void PyEditor_Widget::replace() +{ + myFindTool->activateReplace(); +} + +/*! + \brief Undo last editor's operation. +*/ +void PyEditor_Widget::undo() +{ + myEditor->undo(); +} + +/*! + \brief Redo last undone editor's operation. +*/ +void PyEditor_Widget::redo() +{ + myEditor->redo(); +} + +/*! + \brief Cut text selected in editor and put it into clipboard. +*/ +void PyEditor_Widget::cut() +{ + myEditor->cut(); +} + +/*! + \brief Copy text selected in editor into clipboard. +*/ +void PyEditor_Widget::copy() +{ + myEditor->copy(); +} + +/*! + \brief Paste text from clipboard into editor. +*/ +void PyEditor_Widget::paste() +{ + myEditor->paste(); +} + +/*! + \brief Delete text selected in editor. +*/ +void PyEditor_Widget::deleteSelected() +{ + myEditor->deleteSelected(); +} + +/*! + \brief Select all text in editor. +*/ +void PyEditor_Widget::selectAll() +{ + myEditor->selectAll(); +} + +/*! + \brief Clear content of editor. +*/ +void PyEditor_Widget::clear() +{ + myEditor->clear(); +} + +/*! + \brief Set/clear modified flag of editor. + \param on 'Modified' flag's value. +*/ +void PyEditor_Widget::setModified( bool on ) +{ + myEditor->document()->setModified( on ); +} + +/*! + \brief Get modified flag of editor. + \return 'Modified' flag's value. +*/ +bool PyEditor_Widget::isModified() +{ + return myEditor->document()->isModified(); +} + +/*! + \brief Set text to editor. + \param text Text to be put into editor. +*/ +void PyEditor_Widget::setText( const QString& text ) +{ + myEditor->setPlainText( text ); +} + +/*! + \brief Get text from editor. + \return Current editor contents. +*/ +QString PyEditor_Widget::text() const +{ + return myEditor->toPlainText(); +} diff --git a/tools/PyEditor/src/PyEditor_Widget.h b/tools/PyEditor/src/PyEditor_Widget.h new file mode 100644 index 000000000..0927a9fe8 --- /dev/null +++ b/tools/PyEditor/src/PyEditor_Widget.h @@ -0,0 +1,85 @@ +// Copyright (C) 2015-2016 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_Widget.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) +// + +#ifndef PYEDITOR_WIDGET_H +#define PYEDITOR_WIDGET_H + +#include "PyEditor.h" + +#include + +class PyEditor_Editor; +class PyEditor_FindTool; + +class PYEDITOR_EXPORT PyEditor_Widget : public QWidget +{ + Q_OBJECT + +public: + PyEditor_Widget( QWidget* = 0 ); + + PyEditor_Editor* editor(); + PyEditor_FindTool* findTool(); + + bool isModified(); + + QString text() const; + + QStringList keywords() const; + void appendKeywords( const QStringList&, int, const QColor& = QColor() ); + void removeKeywords( const QStringList& ); + + int completionPolicy() const; + void setCompletionPolicy( int ); + +public slots: + void find(); + void replace(); + + void undo(); + void redo(); + void cut(); + void copy(); + void paste(); + void deleteSelected(); + void selectAll(); + void clear(); + + void setModified( bool ); + + void setText( const QString& ); + +signals: + void modificationChanged( bool ); + void undoAvailable( bool ); + void redoAvailable( bool ); + void copyAvailable( bool ); + void selectionChanged(); + void textChanged(); + void cursorPositionChanged(); + +private: + PyEditor_Editor* myEditor; + PyEditor_FindTool* myFindTool; +}; + +#endif // PYEDITOR_WIDGET_H diff --git a/tools/PyEditor/src/PyEditor_Window.cxx b/tools/PyEditor/src/PyEditor_Window.cxx index 74c0be042..125a27b81 100644 --- a/tools/PyEditor/src/PyEditor_Window.cxx +++ b/tools/PyEditor/src/PyEditor_Window.cxx @@ -21,12 +21,13 @@ // #include "PyEditor_Window.h" -#include "PyEditor_Editor.h" +#include "PyEditor_Widget.h" #include "PyEditor_Settings.h" #include "PyEditor_SettingsDlg.h" #include #include +#include #include #include #include @@ -48,9 +49,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : { Q_INIT_RESOURCE( PyEditor ); - // Create editor and set it as a central widget. - myTextEditor = new PyEditor_Editor( this ); - setCentralWidget( myTextEditor ); + // Create central widget. + myEditor = new PyEditor_Widget( this ); + setCentralWidget( myEditor ); // Create actions. QAction* action; @@ -81,7 +82,7 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setShortcut( QKeySequence::Save ); connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( modificationChanged( bool ) ), + connect( myEditor, SIGNAL( modificationChanged( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ SaveId ] = action; @@ -109,9 +110,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_UNDO" ) ); action->setStatusTip( tr( "DSC_UNDO" ) ); action->setShortcut( QKeySequence::Undo ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( undo() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( undo() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( undoAvailable( bool ) ), + connect( myEditor, SIGNAL( undoAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ UndoId ] = action; @@ -121,9 +122,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_REDO" ) ); action->setStatusTip( tr( "DSC_REDO" ) ); action->setShortcut( QKeySequence::Redo ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( redo() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( redo() ) ); action->setEnabled( false ); - connect( myTextEditor->document(), SIGNAL( redoAvailable( bool ) ), + connect( myEditor, SIGNAL( redoAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ RedoId ] = action; @@ -133,9 +134,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_CUT" ) ); action->setStatusTip( tr( "DSC_CUT" ) ); action->setShortcut( QKeySequence::Cut ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( cut() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( cut() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ CutId ] = action; @@ -145,9 +146,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_COPY" ) ); action->setStatusTip( tr( "DSC_COPY" ) ); action->setShortcut( QKeySequence::Copy ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( copy() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ CopyId ] = action; @@ -157,7 +158,7 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_PASTE" ) ); action->setStatusTip( tr( "DSC_PASTE" ) ); action->setShortcut( QKeySequence::Paste ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( paste() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); myActions[ PasteId ] = action; // . Delete @@ -166,9 +167,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_DELETE" ) ); action->setStatusTip( tr( "DSC_DELETE" ) ); action->setShortcut( QKeySequence::Delete ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( deleteSelected() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( deleteSelected() ) ); action->setEnabled( false ); - connect( myTextEditor, SIGNAL( copyAvailable( bool ) ), + connect( myEditor, SIGNAL( copyAvailable( bool ) ), action, SLOT( setEnabled( bool ) ) ); myActions[ DeleteId ] = action; @@ -178,9 +179,29 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : action->setToolTip( tr( "TTP_SELECT_ALL" ) ); action->setStatusTip( tr( "DSC_SELECT_ALL" ) ); action->setShortcut( QKeySequence::SelectAll ); - connect( action, SIGNAL( triggered( bool ) ), myTextEditor, SLOT( selectAll() ) ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); myActions[ SelectAllId ] = action; + // . Find + action = new QAction( QIcon( ":/images/py_find.png" ), + tr( "ACT_FIND" ), this ); + action->setToolTip( tr( "TTP_FIND" ) ); + action->setStatusTip( tr( "DSC_FIND" ) ); + action->setShortcut( QKeySequence::Find ); + action->setShortcutContext( Qt::WidgetShortcut ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( find() ) ); + myActions[ FindId ] = action; + + // . Replace + action = new QAction( QIcon( ":/images/py_replace.png" ), + tr( "ACT_REPLACE" ), this ); + action->setToolTip( tr( "TTP_REPLACE" ) ); + action->setStatusTip( tr( "DSC_REPLACE" ) ); + action->setShortcut( QKeySequence::Replace ); + action->setShortcutContext( Qt::WidgetShortcut ); + connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( replace() ) ); + myActions[ ReplaceId ] = action; + // . Preferences action = new QAction( QIcon( ":/images/py_preferences.png" ), tr( "ACT_PREFERENCES" ), this ); @@ -218,6 +239,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : menu->addSeparator(); menu->addAction( myActions[ SelectAllId ] ); menu->addSeparator(); + menu->addAction( myActions[ FindId ] ); + menu->addAction( myActions[ ReplaceId ] ); + menu->addSeparator(); menu->addAction( myActions[ PreferencesId ] ); menu = menuBar()->addMenu( tr( "MNU_HELP" ) ); @@ -242,6 +266,9 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : toolbar->addAction( myActions[ DeleteId ] ); toolbar->addAction( myActions[ SelectAllId ] ); toolbar->addSeparator(); + toolbar->addAction( myActions[ FindId ] ); + toolbar->addAction( myActions[ ReplaceId ] ); + toolbar->addSeparator(); toolbar->addAction( myActions[ PreferencesId ] ); toolbar->addSeparator(); toolbar->addAction( myActions[ HelpId ] ); @@ -250,7 +277,7 @@ PyEditor_Window::PyEditor_Window( QWidget* parent ) : setCurrentFile( QString() ); // Additional set-up for main window. - connect( myTextEditor->document(), SIGNAL( modificationChanged( bool ) ), + connect( myEditor, SIGNAL( modificationChanged( bool ) ), this, SLOT( setWindowModified( bool ) ) ); // Initialize status bar. @@ -283,7 +310,7 @@ void PyEditor_Window::onNew() { if ( whetherSave() ) { - myTextEditor->clear(); + myEditor->clear(); setCurrentFile( QString() ); } } @@ -343,7 +370,7 @@ bool PyEditor_Window::onSaveAs() */ void PyEditor_Window::onPreferences() { - PyEditor_SettingsDlg dlg( myTextEditor, true, this ); + PyEditor_SettingsDlg dlg( myEditor->editor(), true, this ); connect( &dlg, SIGNAL( help() ), this, SLOT( onHelp() ) ); dlg.exec(); } @@ -355,7 +382,7 @@ void PyEditor_Window::onPreferences() void PyEditor_Window::setCurrentFile( const QString& filePath ) { myURL = filePath; - myTextEditor->document()->setModified( false ); + myEditor->setModified( false ); setWindowModified( false ); @@ -369,7 +396,7 @@ void PyEditor_Window::setCurrentFile( const QString& filePath ) */ bool PyEditor_Window::whetherSave() { - if ( myTextEditor->document()->isModified() ) + if ( myEditor->isModified() ) { QMessageBox::StandardButton answer = QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), @@ -394,19 +421,20 @@ bool PyEditor_Window::whetherSave() \brief Open file. \param filePath file path */ -void PyEditor_Window::loadFile( const QString& filePath ) +void PyEditor_Window::loadFile( const QString& filePath, bool verbose ) { QFile aFile( filePath ); if ( !aFile.open(QFile::ReadOnly | QFile::Text) ) { - QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), - tr( "WRN_READ_FILE" ).arg( filePath ).arg( aFile.errorString() ) ); + if ( verbose ) + QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), + tr( "WRN_READ_FILE" ).arg( filePath ).arg( aFile.errorString() ) ); return; } QTextStream anInput( &aFile ); QApplication::setOverrideCursor( Qt::WaitCursor ); - myTextEditor->setPlainText( anInput.readAll() ); + myEditor->setText( anInput.readAll() ); QApplication::restoreOverrideCursor(); setCurrentFile( filePath ); @@ -419,19 +447,20 @@ void PyEditor_Window::loadFile( const QString& filePath ) \brief Save file. \param filePath file path */ -bool PyEditor_Window::saveFile( const QString& filePath ) +bool PyEditor_Window::saveFile( const QString& filePath, bool verbose ) { QFile aFile( filePath ); if ( !aFile.open( QFile::WriteOnly | QFile::Text ) ) { - QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), - tr( "WRN_WRITE_FILE" ).arg( filePath ).arg( aFile.errorString() ) ); + if ( verbose ) + QMessageBox::warning( this, tr( "NAME_PYEDITOR" ), + tr( "WRN_WRITE_FILE" ).arg( filePath ).arg( aFile.errorString() ) ); return false; } QTextStream anOutput( &aFile ); QApplication::setOverrideCursor( Qt::WaitCursor ); - anOutput << myTextEditor->toPlainText(); + anOutput << myEditor->text(); QApplication::restoreOverrideCursor(); setCurrentFile( filePath ); diff --git a/tools/PyEditor/src/PyEditor_Window.h b/tools/PyEditor/src/PyEditor_Window.h index 209739e77..1369db1cd 100644 --- a/tools/PyEditor/src/PyEditor_Window.h +++ b/tools/PyEditor/src/PyEditor_Window.h @@ -29,7 +29,7 @@ #include class QAction; -class PyEditor_Editor; +class PyEditor_Widget; class PYEDITOR_EXPORT PyEditor_Window : public QMainWindow { @@ -38,11 +38,15 @@ class PYEDITOR_EXPORT PyEditor_Window : public QMainWindow public: enum { NewId, OpenId, SaveId, SaveAsId, ExitId, UndoId, RedoId, CutId, CopyId, PasteId, DeleteId, SelectAllId, + FindId, ReplaceId, PreferencesId, HelpId }; PyEditor_Window( QWidget* = 0 ); ~PyEditor_Window(); + void loadFile( const QString&, bool = true ); + bool saveFile( const QString&, bool = true ); + protected: virtual void closeEvent( QCloseEvent* ); @@ -54,16 +58,12 @@ private Q_SLOTS: void onPreferences(); void onHelp(); -private: - void loadFile( const QString& ); - bool saveFile( const QString& ); - void setCurrentFile( const QString& ); bool whetherSave(); QString defaultName() const; private: - PyEditor_Editor* myTextEditor; + PyEditor_Widget* myEditor; QString myURL; QMap myActions; }; diff --git a/tools/PyEditor/src/python/PyEditorPy.sip b/tools/PyEditor/src/python/PyEditorPy.sip index 499ee9574..d6b530be9 100644 --- a/tools/PyEditor/src/python/PyEditorPy.sip +++ b/tools/PyEditor/src/python/PyEditorPy.sip @@ -103,3 +103,75 @@ private: PyEditor_Editor( const PyEditor_Editor& ); PyEditor_Editor& operator=( const PyEditor_Editor& ); }; + +class PyEditor_FindTool : public QWidget +{ +%TypeHeaderCode +#include +%End + +public: + explicit PyEditor_FindTool( PyEditor_Editor* /TransferThis/, QWidget* /TransferThis/ = 0 ); + +public slots: + void activateFind(); + void activateReplace(); + +private: + PyEditor_FindTool( const PyEditor_FindTool& ); + PyEditor_FindTool& operator=( const PyEditor_FindTool& ); +}; + +class PyEditor_Widget : public QWidget +{ +%TypeHeaderCode +#include +%End + +public: + explicit PyEditor_Widget( QWidget* /TransferThis/ = 0 ); + + PyEditor_Editor* editor(); + PyEditor_FindTool* findTool(); + + bool isModified(); + + QString text() const; + + QStringList keywords() const; + void appendKeywords( const QStringList&, int, const QColor& = QColor() ); + void removeKeywords( const QStringList& ); + + int completionPolicy() const; + void setCompletionPolicy( int ); + +public slots: + void find(); + void replace(); + + void undo(); + void redo(); + void cut(); + void copy(); + void paste(); + void deleteSelected(); + void selectAll(); + void clear(); + + void setModified( bool ); + + void setText( const QString& ); + +signals: + void modificationChanged( bool ); + void undoAvailable( bool ); + void redoAvailable( bool ); + void copyAvailable( bool ); + void selectionChanged(); + void textChanged(); + void cursorPositionChanged(); + +private: + PyEditor_Widget( const PyEditor_Widget& ); + PyEditor_Widget& operator=( const PyEditor_Widget& ); +}; diff --git a/tools/PyEditor/src/resources/PyEditor.qrc b/tools/PyEditor/src/resources/PyEditor.qrc index 7df3d3208..d2ab431f7 100644 --- a/tools/PyEditor/src/resources/PyEditor.qrc +++ b/tools/PyEditor/src/resources/PyEditor.qrc @@ -5,14 +5,19 @@ images/py_delete.png images/py_editor.png images/py_exit.png + images/py_find.png + images/py_find_next.png + images/py_find_previous.png images/py_help.png images/py_new.png images/py_open.png images/py_paste.png images/py_preferences.png images/py_redo.png + images/py_replace.png images/py_save.png images/py_save_as.png + images/py_search.png images/py_select_all.png images/py_undo.png about.txt diff --git a/tools/PyEditor/src/resources/about.txt b/tools/PyEditor/src/resources/about.txt index 75e476a52..9f914c70c 100644 --- a/tools/PyEditor/src/resources/about.txt +++ b/tools/PyEditor/src/resources/about.txt @@ -1,10 +1,16 @@ Python Editor
+

Python Editor is a simple program for writing Python scripts. +

+ +

Program provides standard editing operations like copy/cut/paste, undo/redo, select all, delete, etc. -Also it supports syntax highlighting and auto-indentation of Python code. +It supports syntax highlighting and auto-indentation of Python code. +

+

Most often used editing operations are available via the toolbar:

  • New: creates new document.
  • @@ -22,7 +28,13 @@ Most often used editing operations are available via the toolbar:
  • Preferences: opens Preferences dialog that allows specifying advanced parameters for the program.
  • Help: shows this help information.
+

+ +

+Also, editor supports standard Find and Replace operations. +

+

The behavior of the editor can be customized via the Preferences dialog. The following options can be customized:

  • Font settings: choose the font.
  • @@ -35,3 +47,4 @@ The behavior of the editor can be customized via the Preferences dialog.
  • Display tab delimiters: displays tab marks at a given number of white spaces.
  • Save settings as default: saves chosen options as a default ones. These settings will be restored after application restart.
+

diff --git a/tools/PyEditor/src/resources/images/py_find.png b/tools/PyEditor/src/resources/images/py_find.png new file mode 100644 index 0000000000000000000000000000000000000000..be705fd8322a1535f7f65bf9c56db04a56d4cd4c GIT binary patch literal 19794 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 KKbLh*2~7Z=%D>eB literal 0 HcmV?d00001 diff --git a/tools/PyEditor/src/resources/images/py_find_previous.png b/tools/PyEditor/src/resources/images/py_find_previous.png new file mode 100644 index 0000000000000000000000000000000000000000..d236de9272616ef411562e4be645bbc8ee13cfca GIT binary patch literal 474 zcmV<00VV#4P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^DS16z6k00Bx#L_t(oN5$7IP6JUChT)P_Ai97G5^N5G(1p0%(wWXIN-3%A z@ITpMtAp*&p_>n}!xqFZ@PSkJc^^BRi|84?P`cN1x98a59K<&G?Beo`wcFokhf@(b z!mf)(mu}y&!zqZY@fMTtK#1l2283ANSs=vnt^-0W?@Cw^ z%Ka~>Y6v<1hC(dwen5!j-4O|~yn8|+mUmYy#PXgCglOKAkr2;&Hf&QCf0Sxw4SXTc Q>Hq)$07*qoM6N<$f^7K9H2?qr literal 0 HcmV?d00001 diff --git a/tools/PyEditor/src/resources/images/py_replace.png b/tools/PyEditor/src/resources/images/py_replace.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0c0ec9a0476f1c326b6cdc9cfd050011a32e0b GIT binary patch literal 60177 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 literal 0 HcmV?d00001 diff --git a/tools/PyEditor/src/resources/images/py_search.png b/tools/PyEditor/src/resources/images/py_search.png new file mode 100644 index 0000000000000000000000000000000000000000..989d1afdb2b3f94d88b55a2135b068a2b9df2d58 GIT binary patch literal 1264 zcmVN2bZe?^J zG%heMF*7s+TY3Nh1Z_z~K~z{r?Urq5TU8jxcVCj5zKJxmbknY8FZw|T4qcraG~kBA zwvtK{a7)tSXuGmxvYIp}VG|LCQ^8h0Y^5MpCo*NjDS|LxCS&sj#BYN-UH1aATAl6I zHEC|L8UIi2@_J8_%aHAh5B$izx#v9p|9Q^2=iEhiB2uYTmOB#v8_*fBTCGA?SC{1T z`9#K;NY_Lp5)lUm2D115$Ubq!3*Rbt1uxc%VRfqzxjrm}ljA}-u~!PmHZ6`!)voS+ z-%M>O59Oo&no2Ho2B?GGZkNlke&aK;9J=zj5LU+^dKH|9@b~yV4t@gDU{VOj_b7pB z+xm?i>3*G^osy=Ki(4`vhN90x;8!7%JT0RtZemss?|**Zku2Drx9yD-69V zL=rHPIIirvbU*cTyWN!yP^pUrGd!`$AxlGZPeS}3B>qMj)Do9MVo^KVo`=zMwf>34 z?d|QA4bVLCk`Wl?G6QK3Pk*U26+b;>4k`_7xrPe^RXWRkK+zQ;+ zeW(fsE{H7N$9rB082A|d-xI@0lcuDF&OoU((A51Q8Td_P3p;qvD**#X!8@3S@&>$K zFW12GEeEie634(O?|C^^I^UwcXGE6RV+zg}?1nl6rPjbB9e$I*)R$0&b5`G}I$oPn zNTJwM=yw?#m_t*^h0Z{!DQMaJ9Ny_zKN7{U5MSrD;Zfh8egm*ACx5gAr$`Ry~vp_w%p`C)2@H)m&)_ehnY!zh0ZJleL6%Kki(6pH6e(3!A+ zQND|I9*JGsjOppa{TFzaM8#;{-48p$X3A&iEXYj3lAcdd_E9j@`6X?URffLRIrv0M($ zw%{1vAP>!~soHg6DV-e|;S7e>KpQ(G;n3-Y2w$AQ$+pV%fzsMqC{PJQQ(y|k9>U1~ za@!8u=W=-?ZC(T%l4lH#b|5?g%Ir%;zwW3k-DZ{zSr; z%Q~3Y3r4YtcDSC_SELECT_ALL Select all the contents
+ + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES Pre&ferences @@ -308,11 +332,13 @@ WRN_READ_FILE - Cannot read file %1:\n%2. + Cannot read file %1: +%2. WRN_WRITE_FILE - Cannot write file %1:\n%2. + Cannot write file %1: +%2. STS_READY @@ -331,4 +357,54 @@ Noname.py + + PyEditor + + PROGRAM_DESCRIPTION + Simple Python editor + + + FILE_PARAM_NAME + file + + + FILE_PARAM_DESCRIPTION + File to edit. + + + + PyEditor_FindTool + + FIND_LABEL + Find: + + + REPLACE_LABEL + Replace with: + + + REPLACE_BTN + Replace + + + REPLACE_ALL_BTN + Replace All + + + CASE_SENSITIVE_CHECK + Case Sensitive + + + WHOLE_WORDS_CHECK + Whole Words Only + + + REGEX_CHECK + Use Regular Expressions + + + NB_MATCHED_LABEL + %1 of %2 matches + + diff --git a/tools/PyEditor/src/resources/translations/PyEditor_msg_fr.ts b/tools/PyEditor/src/resources/translations/PyEditor_msg_fr.ts index 17aad7262..fc2decc6a 100644 --- a/tools/PyEditor/src/resources/translations/PyEditor_msg_fr.ts +++ b/tools/PyEditor/src/resources/translations/PyEditor_msg_fr.ts @@ -250,6 +250,30 @@ DSC_SELECT_ALL Sélectionne tout le contenu + + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES Préférences @@ -308,11 +332,13 @@ WRN_READ_FILE - Impossible de lire le fichier %1:\n%2. + Impossible de lire le fichier %1: +%2. WRN_WRITE_FILE - Impossible d'écrire le fichier %1:\n%2. + Impossible d'écrire le fichier %1: +%2. STS_READY @@ -331,4 +357,54 @@ Noname.py + + PyEditor + + PROGRAM_DESCRIPTION + Editeur python + + + FILE_PARAM_NAME + file + + + FILE_PARAM_DESCRIPTION + File to edit. + + + + PyEditor_FindTool + + FIND_LABEL + Find: + + + REPLACE_LABEL + Replace with: + + + REPLACE_BTN + Replace + + + REPLACE_ALL_BTN + Replace All + + + CASE_SENSITIVE_CHECK + Case Sensitive + + + WHOLE_WORDS_CHECK + Whole Words Only + + + REGEX_CHECK + Use Regular Expressions + + + NB_MATCHED_LABEL + %1 of %2 matches + + diff --git a/tools/PyEditor/src/resources/translations/PyEditor_msg_ja.ts b/tools/PyEditor/src/resources/translations/PyEditor_msg_ja.ts index 199ba7d46..ec1846668 100644 --- a/tools/PyEditor/src/resources/translations/PyEditor_msg_ja.ts +++ b/tools/PyEditor/src/resources/translations/PyEditor_msg_ja.ts @@ -250,6 +250,30 @@ DSC_SELECT_ALL 全選択 + + ACT_FIND + Find + + + TTP_FIND + Find + + + DSC_FIND + Find text + + + ACT_REPLACE + Replace + + + TTP_REPLACE + Find & Replace + + + DSC_REPLACE + Find and replace text + ACT_PREFERENCES 環境設定 (&f) @@ -308,11 +332,13 @@ WRN_READ_FILE - ファイルが読めません %1:\n%2. + ファイルが読めません %1: +%2. WRN_WRITE_FILE - ファイルが書き込めません %1:\n%2. + ファイルが書き込めません %1: +%2. STS_READY @@ -331,4 +357,54 @@ Noname. py + + PyEditor + + PROGRAM_DESCRIPTION + Pythonのエディタ + + + FILE_PARAM_NAME + file + + + FILE_PARAM_DESCRIPTION + File to edit. + + + + PyEditor_FindTool + + FIND_LABEL + Find: + + + REPLACE_LABEL + Replace with: + + + REPLACE_BTN + Replace + + + REPLACE_ALL_BTN + Replace All + + + CASE_SENSITIVE_CHECK + Case Sensitive + + + WHOLE_WORDS_CHECK + Whole Words Only + + + REGEX_CHECK + Use Regular Expressions + + + NB_MATCHED_LABEL + %1 of %2 matches + + -- 2.30.2