From: abn Date: Fri, 20 May 2016 12:51:16 +0000 (+0200) Subject: Modifying build procedure to expose PyConsole and PyInterp as tools. X-Git-Tag: V8_1_0a1~10 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=cb00147d363db68c5cefe98d1615be8f49af1772;p=modules%2Fgui.git Modifying build procedure to expose PyConsole and PyInterp as tools. --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17fd52a98..9d4490662 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,16 +118,14 @@ IF(SALOME_USE_PYVIEWER) ADD_SUBDIRECTORY(PyViewer) ENDIF(SALOME_USE_PYVIEWER) - ## # Python-based packages, part 1 (generic) ## -IF(SALOME_USE_PYCONSOLE) - ADD_SUBDIRECTORY(PyInterp) - ADD_SUBDIRECTORY(PyConsole) +IF(SALOME_USE_PYCONSOLE) ADD_SUBDIRECTORY(SALOME_PYQT) ENDIF(SALOME_USE_PYCONSOLE) + ## # Light SALOME packages ## diff --git a/src/LightApp/CMakeLists.txt b/src/LightApp/CMakeLists.txt index 770f9690f..de54a1970 100755 --- a/src/LightApp/CMakeLists.txt +++ b/src/LightApp/CMakeLists.txt @@ -81,8 +81,8 @@ ENDIF() IF(SALOME_USE_PYCONSOLE) INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_DIRS} - ${PROJECT_SOURCE_DIR}/src/PyConsole - ${PROJECT_SOURCE_DIR}/src/PyInterp + ${PROJECT_SOURCE_DIR}/tools/PyConsole/src + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src ${PROJECT_SOURCE_DIR}/src/SUITApp ) ENDIF() diff --git a/src/PVServerService/CMakeLists.txt b/src/PVServerService/CMakeLists.txt index 76cfef96a..7d34b87cf 100644 --- a/src/PVServerService/CMakeLists.txt +++ b/src/PVServerService/CMakeLists.txt @@ -25,7 +25,7 @@ ENDIF() INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${KERNEL_INCLUDE_DIRS} - ${PROJECT_SOURCE_DIR}/src/PyInterp + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src ) ADD_DEFINITIONS( diff --git a/src/PyConsole/CMakeLists.txt b/src/PyConsole/CMakeLists.txt deleted file mode 100755 index 713769e8b..000000000 --- a/src/PyConsole/CMakeLists.txt +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (C) 2012-2016 CEA/DEN, EDF R&D, 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 -# - -INCLUDE(UseQtExt) - -# --- options --- - -# additional include directories -INCLUDE_DIRECTORIES( - ${QT_INCLUDES} - ${PYTHON_INCLUDE_DIRS} - ${PROJECT_SOURCE_DIR}/src/PyInterp -) - -# additional preprocessor / compiler flags -ADD_DEFINITIONS(${QT_DEFINITIONS} ${PYTHON_DEFINITIONS}) - -# libraries to link to -SET(_link_LIBRARIES ${QT_LIBRARIES} ${PYTHON_LIBRARIES} PyInterp) - -# --- headers --- - -# header files / to be processed by moc -SET(_moc_HEADERS - PyConsole_Console.h - PyConsole_Editor.h -) - -# header files / no moc processing -SET(_other_HEADERS - PyConsole.h - PyConsole_Event.h - PyConsole_Interp.h - PyConsole_Request.h -) - -# header files / to install -SET(PyConsole_HEADERS ${_moc_HEADERS} ${_other_HEADERS}) - -# --- resources --- - -# resource files / to be processed by lrelease -SET(_ts_RESOURCES - resources/PyConsole_msg_en.ts - resources/PyConsole_msg_fr.ts - resources/PyConsole_msg_ja.ts -) - -# --- sources --- - -# sources / moc wrappings -QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS}) - -# sources / static -SET(_other_SOURCES - PyConsole_Console.cxx - PyConsole_Event.cxx - PyConsole_Interp.cxx - PyConsole_Request.cxx - PyConsole_Editor.cxx -) - -# sources / to compile -SET(PyConsole_SOURCES ${_other_SOURCES} ${_moc_SOURCES}) - -# --- rules --- - -ADD_LIBRARY(PyConsole ${PyConsole_SOURCES}) -TARGET_LINK_LIBRARIES(PyConsole ${_link_LIBRARIES}) -INSTALL(TARGETS PyConsole EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_LIBS}) - -INSTALL(FILES ${PyConsole_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS}) -QT_INSTALL_TS_RESOURCES("${_ts_RESOURCES}" "${SALOME_GUI_INSTALL_RES_DATA}") diff --git a/src/PyConsole/PyConsole.h b/src/PyConsole/PyConsole.h deleted file mode 100644 index 4c72bdfb9..000000000 --- a/src/PyConsole/PyConsole.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole.h -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -#if !defined ( PYCONSOLE_H ) -#define PYCONSOLE_H - -// ======================================================== -// set dllexport type for Win platform -#ifdef WIN32 -# if defined PYCONSOLE_EXPORTS || defined PyConsole_EXPORTS -# define PYCONSOLE_EXPORT __declspec(dllexport) -# else -# define PYCONSOLE_EXPORT __declspec(dllimport) -# endif -#else // WIN32 -# define PYCONSOLE_EXPORT -#endif // WIN32 - -// ======================================================== -// avoid warning messages -#ifdef WIN32 -#pragma warning (disable : 4786) -#pragma warning (disable : 4251) -#endif - -#endif // PYCONSOLE_H diff --git a/src/PyConsole/PyConsole_Console.cxx b/src/PyConsole/PyConsole_Console.cxx deleted file mode 100644 index 3aaa52ccb..000000000 --- a/src/PyConsole/PyConsole_Console.cxx +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Console.cxx -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -#include "PyConsole_Console.h" -#include "PyConsole_Interp.h" -#include "PyConsole_Editor.h" - -#include -#include -#include -#include -#include -#include - -/*! - \class PyConsole_Console - \brief Python console widget. - - To create a Python console, just use default contstructor, specifying only a parent widget: - \code - PyConsole_Console c(myWindow); - \endcode - - This will create a console with default editor and interpreter. - - To use custom editor and/or interpreter class with the console, you can use additional parameter - of the constructor; in this case you have to ensure that Python interpeter is initialized properly: - \code - PyConsole_Interp* interp = new PyConsole_Interp(); - interp->initialize(); - PyConsole_Console c(myWindow, new MyEditor(interp)); - \endcode -*/ - -/*! - \brief Constructor. - - Creates new python console widget. - \param parent parent widget - \param interp python interpreter -*/ -PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Editor* editor ) -: QWidget( parent ) -{ - // initialize Python interpretator - PyConsole_Interp* interp = editor ? editor->getInterp() : new PyConsole_Interp(); - interp->initialize(); - - // create editor console - QVBoxLayout* lay = new QVBoxLayout( this ); - lay->setMargin( 0 ); - myEditor = editor ? editor : new PyConsole_Editor( interp, this ); - myEditor->setContextMenuPolicy( Qt::NoContextMenu ); - lay->addWidget( myEditor ); - - // force synchronous mode - QString synchronous = qgetenv( "PYTHON_CONSOLE_SYNC" ); - if ( !synchronous.isEmpty() && synchronous.toInt() > 0 ) - setIsSync( true ); - - // create actions - createActions(); -} - -/*! - \brief Destructor. -*/ -PyConsole_Console::~PyConsole_Console() -{ -} - -/*! - \brief Get Python interpreter - \return pointer to Python interpreter -*/ -PyConsole_Interp* PyConsole_Console::getInterp() const -{ - return myEditor ? myEditor->getInterp() : 0; -} - -/*! - \brief Execute python command in the interpreter. - \param command string with command and arguments -*/ -void PyConsole_Console::exec( const QString& command ) -{ - if ( myEditor ) - myEditor->exec( command ); -} - -/*! - \brief Execute python command in the interpreter - and wait until it is finished. - - Block execution of main application until the python command is executed. - \param command string with command and arguments -*/ -void PyConsole_Console::execAndWait( const QString& command ) -{ - if ( myEditor ) - myEditor->execAndWait( command ); -} - -/*! - \brief Get synchronous mode flag value. - - \sa setIsSync() - \return \c true if python console works in synchronous mode -*/ -bool PyConsole_Console::isSync() const -{ - return myEditor ? myEditor->isSync() : false; -} - -/*! - \brief Set synchronous mode flag value. - - In synhronous mode the Python commands are executed in the GUI thread - and the GUI is blocked until the command is finished. In the asynchronous - mode each Python command is executed in the separate thread that does not - block the main GUI loop. - - \param on synhronous mode flag -*/ -void PyConsole_Console::setIsSync( const bool on ) -{ - if ( myEditor ) - myEditor->setIsSync( on ); -} - -/*! - \brief Get suppress output flag value. - - \sa setIsSuppressOutput() - \return \c true if python console output is suppressed. -*/ -bool PyConsole_Console::isSuppressOutput() const -{ - return myEditor ? myEditor->isSuppressOutput() : false; -} - -/*! - \brief Set suppress output flag value. - - In case if suppress output flag is \c true, the python - console output suppressed. - - \param on suppress output flag -*/ -void PyConsole_Console::setIsSuppressOutput( const bool on ) -{ - if ( myEditor ) - myEditor->setIsSuppressOutput( on ); -} - -/*! - \brief Get 'show banner' flag value. - - \sa setIsShowBanner() - \return \c true if python console shows banner -*/ -bool PyConsole_Console::isShowBanner() const -{ - return myEditor ? myEditor->isShowBanner() : false; -} - -/*! - \brief Set 'show banner' flag value. - - The banner is shown in the top of the python console window. - - \sa isShowBanner() - \param on 'show banner' flag -*/ -void PyConsole_Console::setIsShowBanner( const bool on ) -{ - if ( myEditor ) - myEditor->setIsShowBanner( on ); -} - -/*! - \brief Returns \c true if auto-completion feature is switched on - or \c false otherwise - \sa setAutoCompletion() -*/ -bool PyConsole_Console::autoCompletion() const -{ - return myEditor ? myEditor->autoCompletion() : false; -} - -/*! - \brief Switch on/off commands auto-completion feature - \sa autoCompletion() -*/ -void PyConsole_Console::setAutoCompletion( const bool on ) -{ - if ( myEditor ) - myEditor->setAutoCompletion( on ); -} - -/*! - \brief Change the python console's font. - \param f new font -*/ -void PyConsole_Console::setFont( const QFont& f ) -{ - if ( myEditor ) - myEditor->setFont( f ); -} - -/*! - \brief Get python console font. - \return current python console font -*/ -QFont PyConsole_Console::font() const -{ - return myEditor ? myEditor->font() : QFont(); -} - -/*! - \brief Set actions to be visible in the context popup menu. - - Actions, which IDs are set in \a flags parameter, will be shown in the - context popup menu. Other actions will not be shown. - - \param flags ORed together actions flags -*/ -void PyConsole_Console::setMenuActions( const int flags ) -{ - myActions[CopyId]->setVisible( flags & CopyId ); - myActions[PasteId]->setVisible( flags & PasteId ); - myActions[ClearId]->setVisible( flags & ClearId ); - myActions[SelectAllId]->setVisible( flags & SelectAllId ); - myActions[DumpCommandsId]->setVisible( flags & DumpCommandsId ); - myActions[StartLogId]->setVisible( flags & StartLogId ); - myActions[StopLogId]->setVisible( flags & StopLogId ); -} - -/*! - \brief Get menu actions which are currently visible in the context popup menu. - \return ORed together actions flags - \sa setMenuActions() -*/ -int PyConsole_Console::menuActions() const -{ - int ret = 0; - ret = ret | ( myActions[CopyId]->isVisible() ? CopyId : 0 ); - ret = ret | ( myActions[PasteId]->isVisible() ? PasteId : 0 ); - ret = ret | ( myActions[ClearId]->isVisible() ? ClearId : 0 ); - ret = ret | ( myActions[SelectAllId]->isVisible() ? SelectAllId : 0 ); - ret = ret | ( myActions[DumpCommandsId]->isVisible() ? DumpCommandsId : 0 ); - ret = ret | ( myActions[StartLogId]->isVisible() ? StartLogId : 0 ); - ret = ret | ( myActions[StopLogId]->isVisible() ? StopLogId : 0 ); - return ret; -} - -/*! - \brief Create menu actions. - - Create context popup menu actions. -*/ -void PyConsole_Console::createActions() -{ - QAction* a = new QAction( tr( "EDIT_COPY_CMD" ), this ); - a->setStatusTip( tr( "EDIT_COPY_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); - myActions.insert( CopyId, a ); - - a = new QAction( tr( "EDIT_PASTE_CMD" ), this ); - a->setStatusTip( tr( "EDIT_PASTE_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); - myActions.insert( PasteId, a ); - - a = new QAction( tr( "EDIT_CLEAR_CMD" ), this ); - a->setStatusTip( tr( "EDIT_CLEAR_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( clear() ) ); - myActions.insert( ClearId, a ); - - a = new QAction( tr( "EDIT_SELECTALL_CMD" ), this ); - a->setStatusTip( tr( "EDIT_SELECTALL_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); - myActions.insert( SelectAllId, a ); - - a = new QAction( tr( "EDIT_DUMPCOMMANDS_CMD" ), this ); - a->setStatusTip( tr( "EDIT_DUMPCOMMANDS_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( dump() ) ); - myActions.insert( DumpCommandsId, a ); - - a = new QAction( tr( "EDIT_STARTLOG_CMD" ), this ); - a->setStatusTip( tr( "EDIT_STARTLOG_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( startLog() ) ); - myActions.insert( StartLogId, a ); - - a = new QAction( tr( "EDIT_STOPLOG_CMD" ), this ); - a->setStatusTip( tr( "EDIT_STOPLOG_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( stopLog() ) ); - myActions.insert( StopLogId, a ); -} - -/*! - \brief Update menu actions. - - Update context popup menu action state. -*/ -void PyConsole_Console::updateActions() -{ - myActions[CopyId]->setEnabled( myEditor && myEditor->textCursor().hasSelection() ); - myActions[PasteId]->setEnabled( myEditor && !myEditor->isReadOnly() && !QApplication::clipboard()->text().isEmpty() ); - myActions[SelectAllId]->setEnabled( myEditor && !myEditor->document()->isEmpty() ); -} - -/*! - \brief Start python trace logging - \param fileName the path to the log file -*/ -void PyConsole_Console::startLog( const QString& fileName ) -{ - if ( myEditor ) - myEditor->startLog( fileName ); -} - -/*! - \brief Stop python trace logging -*/ -void PyConsole_Console::stopLog() -{ - if ( myEditor ) - myEditor->stopLog(); -} - -/*! - \brief Process context popup menu request - - Show the context popup menu. - - \param event context popup menu event -*/ -void PyConsole_Console::contextMenuEvent( QContextMenuEvent* event ) -{ - if ( !myEditor || myEditor->isReadOnly() ) - return; - - QMenu* menu = new QMenu( this ); - - menu->addAction( myActions[CopyId] ); - menu->addAction( myActions[PasteId] ); - menu->addAction( myActions[ClearId] ); - menu->addSeparator(); - menu->addAction( myActions[SelectAllId] ); - menu->addSeparator(); - menu->addAction( myActions[DumpCommandsId] ); - if ( !myEditor->isLogging() ) - menu->addAction( myActions[StartLogId] ); - else - menu->addAction( myActions[StopLogId] ); - - updateActions(); - - menu->exec( event->globalPos()); - - delete menu; -} diff --git a/src/PyConsole/PyConsole_Console.h b/src/PyConsole/PyConsole_Console.h deleted file mode 100644 index e0b111e53..000000000 --- a/src/PyConsole/PyConsole_Console.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Console.h -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -#ifndef PYCONSOLE_CONSOLE_H -#define PYCONSOLE_CONSOLE_H - -#include "PyConsole.h" - -#include -#include - -class QMenu; - -class PyConsole_Interp; -class PyConsole_Editor; - -class PYCONSOLE_EXPORT PyConsole_Console : public QWidget -{ - Q_OBJECT - -public: - //! Context popup menu actions flags - enum - { - CopyId = 0x01, //!< "Copy" menu action - PasteId = 0x02, //!< "Paste" menu action - ClearId = 0x04, //!< "Clear" menu action - SelectAllId = 0x08, //!< "Select All" menu action - DumpCommandsId = 0x10, //!< "DumpCommands" menu action - StartLogId = 0x20, //!< "Start log" menu action - StopLogId = 0x40, //!< "Stop log" menu action - All = 0xFF, //!< all menu actions - }; - -public: - PyConsole_Console( QWidget* parent, PyConsole_Editor* editor = 0 ); - virtual ~PyConsole_Console(); - - PyConsole_Interp* getInterp() const; - - QFont font() const; - virtual void setFont( const QFont& ); - - bool isSync() const; - void setIsSync( const bool ); - - bool isSuppressOutput() const; - void setIsSuppressOutput( const bool ); - - bool isShowBanner() const; - void setIsShowBanner( const bool ); - - void setAutoCompletion( bool ); - bool autoCompletion() const; - - void exec( const QString& ); - void execAndWait( const QString& ); - - void setMenuActions( const int ); - int menuActions() const; - - void startLog( const QString& ); - void stopLog(); - -protected: - void createActions(); - void updateActions(); - - virtual void contextMenuEvent( QContextMenuEvent* ); - -protected: - PyConsole_Editor* myEditor; //!< python console editor widget - QMap myActions; //!< menu actions list -}; - -#endif // PYCONSOLE_CONSOLE_H diff --git a/src/PyConsole/PyConsole_Editor.cxx b/src/PyConsole/PyConsole_Editor.cxx deleted file mode 100644 index 7d540f62f..000000000 --- a/src/PyConsole/PyConsole_Editor.cxx +++ /dev/null @@ -1,1700 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Editor.cxx -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -/*! - \class PyConsole_Editor - \brief Python command line interpreter front-end GUI widget. - - This class provides simple GUI interface to the Python interpreter, including basic - navigation operations, executing commands (both interactively and programmatically), - copy-paste operations, history of the commands and so on. - - Here below is the shortcut keyboard boundings used for navigation and other operations: - - : execute current command - - : clear current command - - : clear current command - - : previous command in the history - - : move cursor one row up with selection - - : move cursor one row up without selection - - : move cursor one row up with selection - - : next command in the history - - : move cursor one row down with selection - - : move cursor one row down without selection - - : move cursor one row down with selection - - : move one symbol left without selection - - : move one symbol left with selection - - : move one word left without selection - - : move one word left with selection - - : move one symbol right without selection - - : move one symbol right with selection - - : move one word right without selection - - : move one word right with selection - - : first command in the history - - : move one page up with selection - - : move one page up without selection - - : scroll one page up - - : last command in the history - - : move one page down with selection - - : move one page down without selection - - : scroll one page down - - : move to the beginning of the line without selection - - : move to the beginning of the line with selection - - : move to the very first symbol without selection - - : move to the very first symbol with selection - - : move to the end of the line without selection - - : move to the end of the line with selection - - : move to the very last symbol without selection - - : move to the very last symbol with selection - - : delete symbol before the cursor - / remove selected text and put it to the clipboard (cut) - - : delete previous word - / remove selected text and put it to the clipboard (cut) - - : delete text from the cursor to the beginning of the line - / remove selected text and put it to the clipboard (cut) - - : delete symbol after the cursor - / remove selected text and put it to the clipboard (cut) - - : delete next word - / remove selected text and put it to the clipboard (cut) - - : delete text from the cursor to the end of the line - / remove selected text and put it to the clipboard (cut) - - : copy - - : paste - - : paste - - : copy - - : cut - - : paste - - : performs auto-completion - - : undoes auto-completion -*/ - -#include "PyConsole_Editor.h" -#include "PyConsole_Interp.h" -#include "PyConsole_Event.h" -#include "PyInterp_Dispatcher.h" -#include "PyConsole_Request.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//VSR: uncomment below macro to support unicode text properly in SALOME -// current commented out due to regressions -//#define PAL22528_UNICODE - -namespace -{ - QString fromUtf8( const char* txt ) - { -#ifdef PAL22528_UNICODE - return QString::fromUtf8( txt ); -#else - return QString( txt ); -#endif - } -} - -static QString READY_PROMPT = ">>> "; -static QString DOTS_PROMPT = "... "; - -void staticCallbackStdout( void* data, char* c ) -{ - if(!((PyConsole_Editor*)data)->isSuppressOutput()) { - PyConsole_Editor* e = (PyConsole_Editor*)data; - QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), false ) ); - } -} - -void staticCallbackStderr( void* data, char* c ) -{ - if(!((PyConsole_Editor*)data)->isSuppressOutput()) { - PyConsole_Editor* e = (PyConsole_Editor*)data; - QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), true ) ); - } -} - -/*! - \brief Constructor. - - Creates python editor window. - \param theInterp python interper - \param theParent parent widget -*/ -PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, - QWidget* theParent ) -: QTextEdit( theParent ), - myInterp( theInterp ), - myCmdInHistory( -1 ), - myEventLoop( 0 ), - myShowBanner( true ), - myIsSync( true ), - myIsSuppressOutput( false ), - myMultiLinePaste( false ), - myAutoCompletion( false ), - myTabMode( false ), - myComplCursorPos( -1 ) -{ - setFont( QFont( "Courier", 11 ) ); // default font - setUndoRedoEnabled( false ); - - myPrompt = READY_PROMPT; - setLineWrapMode( QTextEdit::WidgetWidth ); - setWordWrapMode( QTextOption::WrapAnywhere ); - setAcceptRichText( false ); - - // set callbacks to interpeter - myInterp->setvoutcb( staticCallbackStdout, this ); - myInterp->setverrcb( staticCallbackStderr, this ); - // print banner - if ( isShowBanner() ) - addText( banner() ); - // clear command buffer - myCommandBuffer.truncate(0); - // unset read-only state - setReadOnly( false ); - // unset history browsing mode - myCmdInHistory = -1; - // add prompt - addText( myPrompt ); - // unset busy cursor - viewport()->unsetCursor(); -} - -/*! - \brief Destructor. -*/ -PyConsole_Editor::~PyConsole_Editor() -{ - myInterp = 0; -} - -/*! - \brief Get Python interpreter -*/ -PyConsole_Interp* PyConsole_Editor::getInterp() const -{ - return myInterp; -} - -/*! - \brief Get synchronous mode flag value. - - \sa setIsSync() - \return \c true if python console works in synchronous mode -*/ -bool PyConsole_Editor::isSync() const -{ - return myIsSync; -} - -/*! - \brief Set synchronous mode flag value. - - In synhronous mode the Python commands are executed in the GUI thread - and the GUI is blocked until the command is finished. In the asynchronous - mode each Python command is executed in the separate thread that does not - block the main GUI loop. - - \param on synhronous mode flag -*/ -void PyConsole_Editor::setIsSync( const bool on ) -{ - myIsSync = on; -} - -/*! - \brief Get suppress output flag value. - - \sa setIsSuppressOutput() - \return \c true if python console output is suppressed. -*/ -bool PyConsole_Editor::isSuppressOutput() const -{ - return myIsSuppressOutput; -} - -/*! - \brief Set suppress output flag value. - - In case if suppress output flag is \c true, the python - console output suppressed. - - \param on suppress output flag -*/ -void PyConsole_Editor::setIsSuppressOutput( const bool on ) -{ - myIsSuppressOutput = on; -} - -/*! - \brief Get 'show banner' flag value. - - \sa setIsShowBanner() - \return \c true if python console shows banner -*/ -bool PyConsole_Editor::isShowBanner() const -{ - return myShowBanner; -} - -/*! - \brief Set 'show banner' flag value. - - The banner is shown in the top of the python console window. - - \sa isShowBanner() - \param on 'show banner' flag -*/ -void PyConsole_Editor::setIsShowBanner( const bool on ) -{ - if ( myShowBanner != on ) { - myShowBanner = on; - clear(); - } -} - -/*! - \brief Switch on/off commands auto-completion feature - \sa autoCompletion() -*/ -void PyConsole_Editor::setAutoCompletion( bool on ) -{ - myAutoCompletion = on; - document()->setUndoRedoEnabled( myAutoCompletion ); -} - -/*! - \brief Returns \c true if auto-completion feature is switched on - or \c false otherwise - \sa setAutoCompletion() -*/ -bool PyConsole_Editor::autoCompletion() const -{ - return myAutoCompletion; -} - -/*! - \brief Check if trace logging is switched on. - - \sa startLog(), stopLog() - \return \c true if trace logging is switched on -*/ -bool PyConsole_Editor::isLogging() const -{ - return !myLogFile.isEmpty(); -} - -/*! - \brief Get size hint for the Python console window - \return size hint value -*/ -QSize PyConsole_Editor::sizeHint() const -{ - QFontMetrics fm( font() ); - int nbLines = ( isShowBanner() ? banner().split("\n").count() : 0 ) + 1; - QSize s(100, fm.lineSpacing()*nbLines); - return s; -} - -/*! - \brief Put the string \a str to the python editor. - \param str string to be put in the command line of the editor - \param newBlock if \c true, then the string is printed on a new line - \param isError if \c true, the text is printed in dark red -*/ -void PyConsole_Editor::addText( const QString& str, - const bool newBlock, - const bool isError ) -{ - QTextCursor aCursor = textCursor(); - QTextCharFormat cf; - - moveCursor( QTextCursor::End ); - if ( newBlock ) - aCursor.insertBlock(); - if ( isError ) - cf.setForeground( QBrush( Qt::red ) ); - else - cf.setForeground( QBrush( Qt::black ) ); - aCursor.insertText( str, cf ); - moveCursor( QTextCursor::End ); - ensureCursorVisible(); -} - -/*! - \brief Convenient method for executing a Python command, - as if the user typed it manually. - \param command python command to be executed - - !!! WARNING: doesn't work properly with multi-line commands. !!! -*/ -void PyConsole_Editor::exec( const QString& command ) -{ - if ( isReadOnly() ) { - // some interactive command is being executed in this editor... - // shedule the command to the queue - myQueue.push_back( command ); - return; - } - - // remove last line - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - - // set "ready" prompt - myPrompt = READY_PROMPT; - - // clear command buffer - myCommandBuffer.truncate( 0 ); - - // unset history browsing mode - myCmdInHistory = -1; - - // print command line by line - QString cmd = command; - if ( !cmd.endsWith( "\n" ) ) cmd += "\n"; - QStringList lines = command.split( "\n" ); - for ( int i = 0; i < lines.size(); i++ ) { - if ( !lines[i].trimmed().isEmpty() ) - myHistory.push_back( lines[i] ); - addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 ); - putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) ); - } - - // IPAL20182 - addText( "", true ); - - // set read-only mode - setReadOnly( true ); - - // set busy cursor - setCursor( Qt::BusyCursor ); - - // post a request to execute Python command; - // editor will be informed via a custom event that execution has been completed - PyInterp_Dispatcher::Get()->Exec( createCmdRequest( cmd ) ); -} - -/*! - \brief Create request to the python dispatcher for the command execution. - \param command python command to be executed - */ -PyInterp_Request* PyConsole_Editor::createCmdRequest( const QString& command ) -{ - return new PyConsole_ExecCommand( myInterp, command, this, isSync() ); -} - -/*! - \brief Create the Python request that will be posted to the interpreter to - get the completions. - \param input line entered by the user at the time was pressed - \return completion command - \sa CompletionCommand -*/ -PyInterp_Request* PyConsole_Editor::createTabRequest( const QString& input ) -{ - // valid separators - static QStringList separators; - if ( separators.isEmpty() ) { - separators << " " << "(" << "[" << "+" << "-" << "*" << "/" << ";" << "^" << "="; - } - - // parse input to extract on what part the dir() has to be executed - - QString input2( input ); - - // split up to the last syntaxical separator - int lastSp = -1; - foreach ( QString separator, separators ) { - int j = input2.lastIndexOf( separator ); - if ( j > lastSp ) - lastSp = j; - } - if ( lastSp >= 0 ) - input2 = input.mid( lastSp + 1 ); - - // detect a qualified name (with a point) - int lastPt = input2.lastIndexOf( "." ); - - if ( lastPt != -1 ) { - // split the 2 surrounding parts of the qualified name - myComplBeforePoint = input2.left( lastPt ); - myComplAfterPoint = input2.mid( lastPt+1 ); - } - else { - // no point found - do a global matching - // (the following will call dir() with an empty string) - myComplAfterPoint = input2; - myComplBeforePoint = ""; - } - - return new PyConsole_CompletionCommand( myInterp, myComplBeforePoint, - myComplAfterPoint, this, isSync() ); -} - -/*! - \brief Execute command in the python interpreter - and wait until it is finished. - - \param command python command to be executed - */ -void PyConsole_Editor::execAndWait( const QString& command ) -{ - // already running ? - if ( myEventLoop ) - return; - - // create new event loop - bool sync = isSync(); - if ( !sync ) { - myEventLoop = new QEventLoop( this ); - } - - // execute command - exec( command ); - - if ( !sync ) { - // run event loop - myEventLoop->exec(); - // delete event loop after command is processed - delete myEventLoop; - myEventLoop = 0; - } -} - -/*! - \brief Process key press event. - - Execute the command entered by the user. -*/ -void PyConsole_Editor::handleReturn() -{ - // Position cursor at the end - QTextCursor aCursor = textCursor(); - aCursor.movePosition( QTextCursor::End ); - setTextCursor( aCursor ); - - // get last line - QTextBlock par = document()->end().previous(); - if ( !par.isValid() ) return; - - // get command - QString cmd = par.text().remove( 0, promptSize() ); - - // extend the command buffer with the current command - myCommandBuffer.append( cmd ); - - // add command to the history - if ( !cmd.trimmed().isEmpty() ) - myHistory.push_back( cmd ); - putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) ); - - // IPAL19397 - addText( "", true ); - - // set read-only mode - setReadOnly( true ); - - // set busy cursor - setCursor( Qt::BusyCursor ); - - // post a request to execute Python command; - // editor will be informed via a custom event that execution has been completed - PyInterp_Dispatcher::Get()->Exec( createCmdRequest( myCommandBuffer ) ); -} - -/*! - \brief Process key press event. - - Perform auto-completion of the currently entered command, if this feature is enabled -*/ -void PyConsole_Editor::handleTab() -{ - if ( !autoCompletion() ) - return; // auto-completion feature is disabled - - if ( myTabMode ) - return; // already in tab mode - - QTextCursor aCursor = textCursor(); - - // move cursor to the end of input - aCursor.movePosition( QTextCursor::End ); - setTextCursor( aCursor ); - - // save cursor position if needed - if ( myComplCursorPos == -1 ) - myComplCursorPos = textCursor().position(); - - // get last line - QTextBlock par = document()->end().previous(); - if ( !par.isValid() ) return; // empty line - - // switch to completion mode - myTabMode = true; - - // get currently entered command - QString cmd = par.text().mid( promptSize() ); - - // post completion request - // editor will be informed that completion has been done via a custom event - PyInterp_Dispatcher::Get()->Exec( createTabRequest( cmd ) ); -} - -/*! - \brief Process key press event. - - Undoe last auto-completion -*/ -void PyConsole_Editor::handleBackTab() -{ - if ( !autoCompletion() ) - return; // auto-completion feature is disabled - - QTextCursor aCursor = textCursor(); - - if ( myComplCursorPos == -1 ) - return; // invalid cursor position - - // ensure cursor is at the end of command line - aCursor.setPosition( myComplCursorPos ); - aCursor.movePosition( QTextCursor::EndOfLine ); - //setCursor( aCursor ); - - // delete last completed text - int i = aCursor.position() - myComplCursorPos; - aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, i ); - aCursor.removeSelectedText(); - myComplCursorPos = -1; -} - -/*! - \brief Process drop event. - - Paste dragged text. - \param event drop event -*/ -void PyConsole_Editor::dropEvent( QDropEvent* event ) -{ - // get the initial drop position - QPoint pos = event->pos(); - QTextCursor aCursor = cursorForPosition( event->pos() ); - - // if the position is not in the last line move it to the end of the command line - if ( aCursor.position() < document()->end().previous().position() + promptSize() ) { - moveCursor( QTextCursor::End ); - pos = cursorRect().center(); - } - - // create new drop event and use it instead of the original - QDropEvent de( pos, - event->possibleActions(), - event->mimeData(), - event->mouseButtons(), - event->keyboardModifiers(), - event->type() ); - QTextEdit::dropEvent( &de ); - - // accept the original event - event->acceptProposedAction(); -} - -/*! - \brief Process mouse press event - - Clear the completion when any mouse button is pressed. - - \param e mouse press event -*/ -void PyConsole_Editor::mousePressEvent( QMouseEvent* event ) -{ - if ( autoCompletion() ) { - clearCompletion(); - myComplCursorPos = -1; - } - QTextEdit::mousePressEvent( event ); -} - -/*! - \brief Process mouse button release event. - - Left mouse button: copy selection to the clipboard. - Middle mouse button: paste clipboard's contents. - - \param event mouse event -*/ -void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event ) -{ - if ( event->button() == Qt::LeftButton ) { - QTextEdit::mouseReleaseEvent( event ); - } - else if ( event->button() == Qt::MidButton ) { - QTextCursor aCursor = cursorForPosition( event->pos() ); - // if the position is not in the last line move it to the end of the command line - if ( aCursor.position() < document()->end().previous().position() + promptSize() ) { - moveCursor( QTextCursor::End ); - } - else { - setTextCursor( aCursor ); - } - const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? - QClipboard::Selection : QClipboard::Clipboard ); - if ( md ) - insertFromMimeData( md ); - } - else { - QTextEdit::mouseReleaseEvent( event ); - } -} - -/*! - \brief Check if the string is command. - - Return \c true if the string \a str is likely to be the command - (i.e. it is started from the '>>>' or '...'). - \param str string to be checked -*/ -bool PyConsole_Editor::isCommand( const QString& str ) const -{ - return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT ); -} - -/*! - \brief Handle keyboard event. - - Implement navigation, history browsing, copy/paste and other common - operations. - - \param event keyboard event -*/ -void PyConsole_Editor::keyPressEvent( QKeyEvent* event ) -{ - // get cursor position - QTextCursor aCursor = textCursor(); - int curLine = aCursor.blockNumber(); - int curCol = aCursor.columnNumber(); - - // get last edited line - int endLine = document()->blockCount()-1; - - // get pressed key code - int aKey = event->key(); - - // check if is pressed - bool ctrlPressed = event->modifiers() & Qt::ControlModifier; - // check if is pressed - bool shftPressed = event->modifiers() & Qt::ShiftModifier; - - if ( autoCompletion() ) { - // auto-completion support - if ( aKey == Qt::Key_Tab && !shftPressed ) { - // process key - if ( !ctrlPressed ) { - handleTab(); - } - else { - clearCompletion(); - handleBackTab(); - } - return; - } - - // If is not pressed (or if something else is pressed with ), - // or if is not pressed alone, we have to clear completion - if ( !ctrlPressed || ( ctrlPressed && aKey != Qt::Key_Control ) ) { - clearCompletion(); - myComplCursorPos = -1; - } - - // Discard pressed alone: - if ( aKey == Qt::Key_Control ) - return; - } - - if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) { - // process + key-binding and key: clear current command - myCommandBuffer.truncate( 0 ); - myPrompt = READY_PROMPT; - addText( myPrompt, true ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_C ) { - // process + key-binding : copy - copy(); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_X ) { - // process + key-binding : cut - cut(); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_V ) { - // process + key-binding : paste - paste(); - return; - } - - // check for printed key - // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0; - // Better: - aKey = !(QChar(aKey).isPrint()) ? aKey : 0; - - switch ( aKey ) { - case 0 : - // any printed key: just print it - { - if ( curLine < endLine || curCol < promptSize() ) { - moveCursor( QTextCursor::End ); - } - QTextEdit::keyPressEvent( event ); - break; - } - case Qt::Key_Return: - case Qt::Key_Enter: - // key: process the current command - { - handleReturn(); - break; - } - case Qt::Key_Up: - // arrow key: process as follows: - // - without , modifiers: previous command in history - // - with modifier key pressed: move cursor one row up without selection - // - with modifier key pressed: move cursor one row up with selection - // - with + modifier keys pressed: scroll one row up - { - if ( ctrlPressed && shftPressed ) { - int value = verticalScrollBar()->value(); - int spacing = fontMetrics().lineSpacing(); - verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 ); - } - else if ( shftPressed || ctrlPressed ) { - if ( curLine > 0 ) - moveCursor( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { - // set history browsing mode - myCmdInHistory = myHistory.count(); - // remember current command - QTextBlock par = document()->end().previous(); - myCurrentCommand = par.text().remove( 0, promptSize() ); - } - if ( myCmdInHistory > 0 ) { - myCmdInHistory--; - // get previous command in the history - QString previousCommand = myHistory.at( myCmdInHistory ); - // print previous command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + previousCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Down: - // arrow key: process as follows: - // - without , modifiers: next command in history - // - with modifier key pressed: move cursor one row down without selection - // - with modifier key pressed: move cursor one row down with selection - // - with + modifier keys pressed: scroll one row down - { - if ( ctrlPressed && shftPressed ) { - int value = verticalScrollBar()->value(); - int maxval = verticalScrollBar()->maximum(); - int spacing = fontMetrics().lineSpacing(); - verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval ); - } - else if ( shftPressed || ctrlPressed) { - if ( curLine < endLine ) - moveCursor( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - if ( myCmdInHistory >= 0 ) { - // get next command in the history - myCmdInHistory++; - QString nextCommand; - if ( myCmdInHistory < myHistory.count() ) { - // next command in history - nextCommand = myHistory.at( myCmdInHistory ); - } - else { - // end of history is reached - // last printed command - nextCommand = myCurrentCommand; - // unset history browsing mode - myCmdInHistory = -1; - } - // print next or current command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + nextCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Left: - // arrow key: process as follows: - // - without , modifiers: move one symbol left (taking into account prompt) - // - with modifier key pressed: move one word left (taking into account prompt) - // - with modifier key pressed: move one symbol left with selection - // - with + modifier keys pressed: move one word left with selection - { - QString txt = textCursor().block().text(); - if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) { - moveCursor( QTextCursor::Up ); - moveCursor( QTextCursor::EndOfBlock ); - } - else { - QTextEdit::keyPressEvent( event ); - } - break; - } - case Qt::Key_Right: - // arrow key: process as follows: - // - without , modifiers: move one symbol right (taking into account prompt) - // - with modifier key pressed: move one word right (taking into account prompt) - // - with modifier key pressed: move one symbol right with selection - // - with + modifier keys pressed: move one word right with selection - { - QString txt = textCursor().block().text(); - if ( !shftPressed ) { - if ( curCol < txt.length() ) { - if ( isCommand( txt ) && curCol < promptSize() ) { - aCursor.setPosition( aCursor.block().position() + promptSize() ); - setTextCursor( aCursor ); - break; - } - } - else { - if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) { - aCursor.setPosition( aCursor.position() + promptSize()+1 ); - setTextCursor( aCursor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - break; - } - } - } - QTextEdit::keyPressEvent( event ); - break; - } - case Qt::Key_PageUp: - // key: process as follows: - // - without , modifiers: first command in history - // - with modifier key pressed: move cursor one page up without selection - // - with modifier key pressed: move cursor one page up with selection - // - with + modifier keys pressed: scroll one page up - { - if ( ctrlPressed && shftPressed ) { - verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); - } - else if ( shftPressed || ctrlPressed ) { - bool moved = false; - qreal lastY = cursorRect( aCursor ).top(); - qreal distance = 0; - // move using movePosition to keep the cursor's x - do { - qreal y = cursorRect( aCursor ).top(); - distance += qAbs( y - lastY ); - lastY = y; - moved = aCursor.movePosition( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - } while ( moved && distance < viewport()->height() ); - if ( moved ) { - aCursor.movePosition( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); - } - setTextCursor( aCursor ); - } - else { - if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { - // set history browsing mode - myCmdInHistory = myHistory.count(); - // remember current command - QTextBlock par = document()->end().previous(); - myCurrentCommand = par.text().remove( 0, promptSize() ); - } - if ( myCmdInHistory > 0 ) { - myCmdInHistory = 0; - // get very first command in the history - QString firstCommand = myHistory.at( myCmdInHistory ); - // print first command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + firstCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_PageDown: - // key: process as follows: - // - without , modifiers: last command in history - // - with modifier key pressed: move cursor one page down without selection - // - with modifier key pressed: move cursor one page down with selection - // - with + modifier keys pressed: scroll one page down - { - if ( ctrlPressed && shftPressed ) { - verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); - } - else if ( shftPressed || ctrlPressed ) { - bool moved = false; - qreal lastY = cursorRect( aCursor ).top(); - qreal distance = 0; - // move using movePosition to keep the cursor's x - do { - qreal y = cursorRect( aCursor ).top(); - distance += qAbs( y - lastY ); - lastY = y; - moved = aCursor.movePosition( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - } while ( moved && distance < viewport()->height() ); - if ( moved ) { - aCursor.movePosition( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); - } - setTextCursor( aCursor ); - } - else { - if ( myCmdInHistory >= 0 ) { - // unset history browsing mode - myCmdInHistory = -1; - // print current command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + myCurrentCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Home: - // key: process as follows: - // - without , modifiers: move cursor to the beginning of the current line without selection - // - with modifier key pressed: move cursor to the very first symbol without selection - // - with modifier key pressed: move cursor to the beginning of the current line with selection - // - with + modifier keys pressed: move cursor to the very first symbol with selection - { - if ( ctrlPressed ) { - moveCursor( QTextCursor::Start, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - QString txt = textCursor().block().text(); - if ( isCommand( txt ) ) { - if ( shftPressed ) { - if ( curCol > promptSize() ) { - aCursor.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor ); - aCursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() ); - } - } - else { - aCursor.movePosition( QTextCursor::StartOfLine ); - aCursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() ); - } - setTextCursor( aCursor ); - } - else { - moveCursor( QTextCursor::StartOfBlock, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_End: - // key: process as follows: - // - without , modifiers: move cursor to the end of the current line without selection - // - with modifier key pressed: move cursor to the very last symbol without selection - // - with modifier key pressed: move cursor to the end of the current line with selection - // - with + modifier keys pressed: move cursor to the very last symbol with selection - { - moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - break; - } - case Qt::Key_Backspace : - // key: process as follows - // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt) - // - with modifier key pressed: delete previous word - // - with modifier key pressed: delete text from the cursor to the line beginning - // works only for last (command) line - { - if ( aCursor.hasSelection() ) { - cut(); - } - else if ( aCursor.position() > document()->end().previous().position() + promptSize() ) { - if ( shftPressed ) { - moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else if ( ctrlPressed ) { - aCursor.setPosition( document()->end().previous().position() + promptSize(), - QTextCursor::KeepAnchor ); - setTextCursor( aCursor ); - textCursor().removeSelectedText(); - } - else { - QTextEdit::keyPressEvent( event ); - } - } - else { - aCursor.setPosition( document()->end().previous().position() + promptSize() ); - setTextCursor( aCursor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_Delete : - // key: process as follows - // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt) - // - with modifier key pressed: delete next word - // - with modifier key pressed: delete text from the cursor to the end of line - // works only for last (command) line - { - if ( aCursor.hasSelection() ) { - cut(); - } - else if ( aCursor.position() > document()->end().previous().position() + promptSize()-1 ) { - if ( shftPressed ) { - moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else if ( ctrlPressed ) { - moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else { - QTextEdit::keyPressEvent( event ); - } - } - else { - aCursor.setPosition( document()->end().previous().position() + promptSize() ); - setTextCursor( aCursor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_Insert : - // key: process as follows - // - with modifier key pressed: copy() - // - with modifier key pressed: paste() to the command line - { - if ( ctrlPressed ) { - copy(); - } - else if ( shftPressed ) { - paste(); - } - else - QTextEdit::keyPressEvent( event ); - break; - } - default: - break; - } -} - -/*! - \brief Handle notification event coming from Python dispatcher. - \param event notification event -*/ -void PyConsole_Editor::customEvent( QEvent* event ) -{ - switch( event->type() ) - { - case PyConsole_PrintEvent::EVENT_ID: - { - PyConsole_PrintEvent* pe = (PyConsole_PrintEvent*)event; - putLog( pe->text()); - addText( pe->text(), false, pe->isError() ); - return; - } - case PyConsole_CompletionEvent::EVENT_ID: - { - PyConsole_CompletionEvent* ce = (PyConsole_CompletionEvent*)event; - bool status = ce->status(); - QStringList matches = ce->matches(); - QString doc = ce->doc(); - - if ( status ) { - // completion was successful - QTextCursor aCursor = textCursor(); - - if ( matches.isEmpty() ) { - // completion successful but there are no matches. - myTabMode = false; - myComplCursorPos = -1; - return; - } - - if ( matches.size() == 1 ) { - // there's only one match - complete directly and update doc string window - aCursor.insertText( matches[0].mid( myComplAfterPoint.size() ) ); - myTabMode = false; - if ( doc.isEmpty() ) - emit updateDoc( formatDocHTML( QString( "(%1)\n" ).arg( tr( "NO_DOC_AVAILABLE" ) ) ) ); - else - emit updateDoc( formatDocHTML( doc ) ); - } - else { - // there are several matches - - // detect if there is a common base to all available completion - // in this case append this base to the text - QString base = extractCommon( matches ); - aCursor.insertText( base.mid( myComplAfterPoint.size() ) ); - - // if this happens to match exactly the first completion - // also provide doc - if ( base == matches[0] ) - emit updateDoc( formatDocHTML( doc ) ); - - // print all matching completion in a "undo-able" block - int cursorPos = aCursor.position(); - aCursor.insertBlock(); - aCursor.beginEditBlock(); - - // insert all matches - QTextCharFormat cf; - cf.setForeground( QBrush( Qt::darkGreen ) ); - aCursor.setCharFormat( cf ); - aCursor.insertText( formatCompletion( matches ) ); - aCursor.endEditBlock(); - - // position cursor where it was before inserting the completion list - aCursor.setPosition( cursorPos ); - setTextCursor( aCursor ); - } - } - else { - // completion failed - myTabMode = false; - myComplCursorPos = -1; - } - return; - } - case PyInterp_Event::ES_OK: - case PyInterp_Event::ES_ERROR: - { - // clear command buffer - myCommandBuffer.truncate( 0 ); - // add caret return line if necessary - QTextBlock par = document()->end().previous(); - QString txt = par.text(); - txt.truncate( txt.length() - 1 ); - // IPAL19397 : addText moved to handleReturn() method - //if ( !txt.isEmpty() ) - // addText( "", true ); - // set "ready" prompt - myPrompt = READY_PROMPT; - addText( myPrompt ); - // unset busy cursor - unsetCursor(); - // stop event loop (if running) - if ( myEventLoop ) - myEventLoop->exit(); - // if we are in multi_paste_mode, process the next item - multiLineProcessNextLine(); - break; - } - case PyInterp_Event::ES_INCOMPLETE: - { - // extend command buffer (multi-line command) - myCommandBuffer.append( "\n" ); - // add caret return line if necessary - QTextBlock par = document()->end().previous(); - QString txt = par.text(); - txt.truncate( txt.length() - 1 ); - // IPAL19397 : addText moved to handleReturn() method - //if ( !txt.isEmpty() ) - // addText( "", true ); - // set "dot" prompt - myPrompt = DOTS_PROMPT; - addText( myPrompt/*, true*/ ); // IPAL19397 - // unset busy cursor - unsetCursor(); - // stop event loop (if running) - if ( myEventLoop ) - myEventLoop->exit(); - // if we are in multi_paste_mode, process the next item - multiLineProcessNextLine(); - break; - } - default: - QTextEdit::customEvent( event ); - } - - // unset read-only state - setReadOnly( false ); - // unset history browsing mode - myCmdInHistory = -1; - - if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 ) - { - // process the next sheduled command from the queue (if there is any) - QString nextcmd = myQueue[0]; - myQueue.pop_front(); - exec( nextcmd ); - } -} - -/*! - \brief "Copy" operation. - - Reimplemented from Qt. - Warning! In Qt this method is not virtual. -*/ -void PyConsole_Editor::cut() -{ - QTextCursor aCursor = textCursor(); - if ( aCursor.hasSelection() ) { - QApplication::clipboard()->setText( aCursor.selectedText() ); - int startSelection = aCursor.selectionStart(); - if ( startSelection < document()->end().previous().position() + promptSize() ) - startSelection = document()->end().previous().position() + promptSize(); - int endSelection = aCursor.selectionEnd(); - if ( endSelection < document()->end().previous().position() + promptSize() ) - endSelection = document()->end().previous().position() + promptSize(); - aCursor.setPosition( startSelection ); - aCursor.setPosition( endSelection, QTextCursor::KeepAnchor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - setTextCursor( aCursor ); - textCursor().removeSelectedText(); - } -} - -/*! - \brief "Paste" operation. - - Reimplemented from Qt. - Warning! In Qt this method is not virtual. -*/ -void PyConsole_Editor::paste() -{ - QTextCursor aCursor = textCursor(); - if ( aCursor.hasSelection() ) { - int startSelection = aCursor.selectionStart(); - if ( startSelection < document()->end().previous().position() + promptSize() ) - startSelection = document()->end().previous().position() + promptSize(); - int endSelection = aCursor.selectionEnd(); - if ( endSelection < document()->end().previous().position() + promptSize() ) - endSelection = document()->end().previous().position() + promptSize(); - aCursor.setPosition( startSelection ); - aCursor.setPosition( endSelection, QTextCursor::KeepAnchor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - setTextCursor( aCursor ); - textCursor().removeSelectedText(); - } - if ( textCursor().position() < document()->end().previous().position() + promptSize() ) - moveCursor( QTextCursor::End ); - QTextEdit::paste(); -} - -/*! - \brief "Clear" operation. - - Reimplemented from Qt. - Warning! In Qt this method is not virtual. -*/ -void PyConsole_Editor::clear() -{ - QTextEdit::clear(); - if ( isShowBanner() ) - addText( banner() ); - myPrompt = READY_PROMPT; - addText( myPrompt ); -} - -/*! - \brief Dumps recorded Python commands to the file - \param fileName path to the dump file - \return \c true if dump operation succeeded or \c false otherwise -*/ -bool PyConsole_Editor::dump( const QString& fileName ) -{ - bool ok = false; - if ( !fileName.isEmpty() ) { - QFile file( fileName ); - if ( file.open( QFile::WriteOnly ) ) { - QTextStream out( &file ); - for ( int i = 0; i < myHistory.count(); i++ ) { - out << myHistory[i] << endl; - } - file.close(); - ok = true; - } - } - return ok; -} - -/*! - \brief Dump menu action slot -*/ -void PyConsole_Editor::dump() -{ - forever { - // get file name - QString fileName = getDumpFileName(); - - if ( fileName.isEmpty() ) - break; // cancelled - - if ( dump( fileName ) ) - break; - else - QMessageBox::warning( this, - tr( "WARNING" ), - tr( "ERR_FILE_NOT_WRITEABLE" ) ); - } -} - -/*! - \brief Get file name for Dump commands operation. - - This function can be redefined in successor classes to show application - specific dialog box. - - \return path to the dump file -*/ -QString PyConsole_Editor::getDumpFileName() -{ - return QFileDialog::getSaveFileName( this, - tr( "GET_DUMP_COMMANDS_FILENAME" ), - QString(), - QString( "%1 (*.py)" ).arg( tr( "PYTHON_SCRIPTS" ) ) ); -} - -/*! - \brief Get file name for Log Python trace operation. - - This function can be redefined in successor classes to show application - specific dialog box. - - \return path to the log file -*/ -QString PyConsole_Editor::getLogFileName() -{ - return QFileDialog::getSaveFileName( this, - tr( "GET_PYTHON_TRACE_FILENAME" ), - QString(), - QString( "%1 (*.log *.txt)" ).arg( tr( "LOG_FILES" ) ) ); -} - -/*! - \brief Start python trace logging - \param fileName the path to the log file - \return \c true if operation succeeded or \c false otherwise - (for example, if file is not writeable) - \sa stopLog() - */ -bool PyConsole_Editor::startLog( const QString& fileName ) -{ - // stop possibly already running logging - if ( isLogging() ) - stopLog(); - - bool ok = false; - if ( !fileName.isEmpty() ) { - QFile file( fileName ); - if ( file.open( QFile::WriteOnly ) ) { - file.close(); - myLogFile = fileName; - ok = true; - } - } - return ok; -} - -/*! - \brief Start log action slot -*/ -void PyConsole_Editor::startLog() -{ - forever { - // get file name - QString fileName = getLogFileName(); - - if ( fileName.isEmpty() ) - break; // cancelled - - if ( startLog( fileName ) ) - break; - else - QMessageBox::warning( this, - tr( "WARNING" ), - tr( "File is not writable" ) ); - } -} - -/*! - \brief Stop log action slot - - Stops Python trace logging. -*/ -void PyConsole_Editor::stopLog() -{ - myLogFile = QString(); -} - -/*! - \brief Put data to the log file -*/ -void PyConsole_Editor::putLog( const QString& s ) -{ - if ( !myLogFile.isEmpty() ) { - QFile file( myLogFile ); - if ( !file.open( QFile::Append ) ) - return; - - QTextStream out( &file ); - out << s; - - file.close(); - } -} - -/*! - \brief Handle properly multi-line pasting. Qt documentation recommends overriding this function. - If the pasted text doesn't contain a line return, no special treatment is done. - \param source -*/ -void PyConsole_Editor::insertFromMimeData(const QMimeData* source) -{ - if ( myMultiLinePaste ) - return; - - if ( source->hasText() ) { - QString s = source->text(); - if ( s.contains( "\n" ) ) - multilinePaste( s ); - else - QTextEdit::insertFromMimeData( source ); - } - else { - QTextEdit::insertFromMimeData( source ); - } -} - -/*! - Start multi-line paste operation - \internal -*/ -void PyConsole_Editor::multilinePaste( const QString& s ) -{ - // Turn on multi line pasting mode - myMultiLinePaste = true; - - // Split string data to lines - QString s2 = s; - s2.replace( "\r", "" ); // Windows string format converted to Unix style - QStringList lst = s2.split( QChar('\n'), QString::KeepEmptyParts ); - - // Perform the proper paste operation for the first line to handle the case where - // something was already there - QMimeData source; - source.setText( lst[0] ); - QTextEdit::insertFromMimeData( &source ); - - // Prepare what will have to be executed after the first line - myMultiLineContent.clear(); - for ( int i = 1; i < lst.size(); ++i ) - myMultiLineContent.enqueue( lst[i] ); - - // Trigger the execution of the first (mixed) line - handleReturn(); - - // See customEvent() and multiLineProcessNext() for the rest of the handling. -} - -/*! - \brief Process the next line in the queue of multi-line paste operation; called - from customEvent() function - \internal -*/ -void PyConsole_Editor::multiLineProcessNextLine() -{ - // not in multi-line paste mode - if ( !myMultiLinePaste || myMultiLineContent.isEmpty() ) - return; - - QString line = myMultiLineContent.dequeue(); - if ( myMultiLineContent.empty() ) - { - // this isa last line in the queue, just paste it - addText( line, false, false ); - myMultiLinePaste = false; - } - else - { - // paste the line and simulate a key stroke - addText( line, false, false ); - handleReturn(); - } -} - -/*! - \brief Clear results of completion -*/ -void PyConsole_Editor::clearCompletion() -{ - // delete completion text if present - if ( myTabMode ) { - // remove completion display - document()->undo(); - // remove trailing line return: - QTextCursor tc( textCursor() ); - tc.setPosition( document()->characterCount()-1 ); - setTextCursor( tc ); - textCursor().deletePreviousChar(); - // TODO: before wait for any event to be completed - } - myTabMode = false; -} - -/*! - \brief Format completion results - this is where we should create 3 columns etc ... - \param matches list of possible completions - \return result string -*/ -QString PyConsole_Editor::formatCompletion( const QStringList& matches ) const -{ - static const int MAX_COMPLETIONS = 70; - - QStringList result; - int sz = matches.size(); - - if ( sz > MAX_COMPLETIONS ) - result.append( QString( "[%1]" ).arg( tr( "TOO_MANY_MATCHES" ) ) ); - - for ( int i = 0; i < qMin( sz, MAX_COMPLETIONS); ++i ) - result.append( matches[i] ); - - return result.join( "\n" ); -} - -/*! - \brief Format the doc string in HTML format with the first line in bold blue - \param doc initial doc string - \return HTML string -*/ -QString PyConsole_Editor::formatDocHTML( const QString& doc ) const -{ - static const char* templ = "\n" \ - " " \ - " " \ - "\n" \ - "

" \ - "%1

" \ - "

%2

" \ - ""; - - QString fst, rest(""); - - // Extract first line of doc - int idx = doc.indexOf( "\n" ); - if ( idx > 0 ) { - fst = doc.left( idx ); - rest = doc.mid( idx+1 ); - } - else { - fst = doc; - } - - fst = fst.replace( "\n", " " ); - rest = rest.replace(" \n", " " ); - return QString( templ ).arg( fst ).arg( rest ); -} - -/*! - \fn void PyConsole_Editor::updateDoc( const QString& doc); - \brief Signal emitted by the editor widget when the doc string should be updated. - \param doc a HTML block with the formatted doc string. - \todo currently this signal is left uncaught. -*/ - -/*! - \brief Extract the common leading part of all strings in matches. - \param matches - \param result -*/ -QString PyConsole_Editor::extractCommon( const QStringList& matches ) const -{ - QString result = ""; - - if ( matches.size() > 1 ) { - int l = 0; - bool ok = true; - while ( ok && l+1 < matches[0].size() ) { - QString match = matches[0].left( l+1 ); - for ( int j = 1; j < matches.size() && ok; j++ ) - ok = matches[j].startsWith( match ); - if ( ok ) - l++; - } - result = matches[0].left( l ); - } - - return result; -} - -/*! - \brief Useful method to get banner from Python interpreter - \return banner -*/ -QString PyConsole_Editor::banner() const -{ - return myInterp->getBanner().c_str(); -} diff --git a/src/PyConsole/PyConsole_Editor.h b/src/PyConsole/PyConsole_Editor.h deleted file mode 100644 index 774e101af..000000000 --- a/src/PyConsole/PyConsole_Editor.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Editor.h -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -#ifndef PYCONSOLE_EDITOR_H -#define PYCONSOLE_EDITOR_H - -#include "PyConsole.h" - -#include -#include - -class PyConsole_Interp; -class PyInterp_Request; -class QEventLoop; - -class PYCONSOLE_EXPORT PyConsole_Editor : public QTextEdit -{ - Q_OBJECT; - -public: - PyConsole_Editor( PyConsole_Interp*, QWidget* = 0 ); - ~PyConsole_Editor(); - - PyConsole_Interp* getInterp() const; - - virtual void addText( const QString&, const bool = false, const bool = false ); - bool isCommand( const QString& ) const; - - virtual void exec( const QString& ); - void execAndWait( const QString& ); - - bool isSync() const; - void setIsSync( const bool ); - - bool isSuppressOutput() const; - void setIsSuppressOutput( const bool ); - - bool isShowBanner() const; - void setIsShowBanner( const bool ); - - void setAutoCompletion( bool ); - bool autoCompletion() const; - - bool isLogging() const; - - virtual QSize sizeHint() const; - - bool startLog( const QString& ); - bool dump( const QString& ); - -signals: - void updateDoc( const QString& ); - -public slots: - void cut(); - void paste(); - void clear(); - void dump(); - void startLog(); - void stopLog(); - -protected: - virtual void dropEvent( QDropEvent* ); - virtual void mousePressEvent( QMouseEvent* ); - virtual void mouseReleaseEvent( QMouseEvent* ); - virtual void keyPressEvent ( QKeyEvent* ); - virtual void customEvent( QEvent* ); - - virtual void insertFromMimeData( const QMimeData* ); - - void putLog( const QString& ); - - virtual QString getDumpFileName(); - virtual QString getLogFileName(); - -private: - void multilinePaste( const QString& ); - void multiLineProcessNextLine(); - - void handleReturn(); - void handleTab(); - void handleBackTab(); - void clearCompletion(); - QString formatCompletion( const QStringList& ) const; - QString formatDocHTML( const QString& ) const; - QString extractCommon( const QStringList& ) const; - - PyInterp_Request* createCmdRequest( const QString& ); - PyInterp_Request* createTabRequest( const QString& ); - - QString banner() const; - inline int promptSize() const { return myPrompt.size(); } - - PyConsole_Interp* myInterp; //!< python interpreter - QString myCommandBuffer; //!< python command buffer - QString myCurrentCommand; //!< currently being printed command - QString myPrompt; //!< current command line prompt - int myCmdInHistory; //!< current history command index - QString myLogFile; //!< current output log - QStringList myHistory; //!< commands history buffer - QEventLoop* myEventLoop; //!< internal event loop - bool myShowBanner; //!< 'show banner' flag - QStringList myQueue; //!< python commands queue - bool myIsSync; //!< synchronous mode flag - bool myIsSuppressOutput; //!< suppress output flag - bool myMultiLinePaste; //!< true when pasting several lines - QQueue myMultiLineContent; //!< queue of lines being pasted - bool myAutoCompletion; //!< auto-completion mode flag - bool myTabMode; //!< flag that is \c true when editor performs completion - QString myComplBeforePoint; //!< string on which the dir() command is executed - QString myComplAfterPoint; //!< string on which the results of the dir() are matched - int myComplCursorPos; //!< cursor position when is hit - -}; - -#endif // PYCONSOLE_EDITOR_H diff --git a/src/PyConsole/PyConsole_Event.cxx b/src/PyConsole/PyConsole_Event.cxx deleted file mode 100644 index 5c3f1fdf9..000000000 --- a/src/PyConsole/PyConsole_Event.cxx +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Event.cxx -// Author : Vadim SANDLER (Open CASCADE S.A.S), Adrien Bruneton (CEA/DEN) - -#include "PyConsole_Event.h" - -/*! - \class PyConsole_PrintEvent - \brief Python command output backend event. - \internal -*/ - -/*! - \brief Constructor - \param message message text (python trace) - \param isError default to \c false - if \c true indicates that an error is being printed. -*/ -PyConsole_PrintEvent::PyConsole_PrintEvent( const QString& message, bool isError ) - : QEvent( (QEvent::Type)EVENT_ID ), myText( message ), myError( isError ) -{ -} - -/*! - \brief Get message - \return message text (python trace) -*/ -QString PyConsole_PrintEvent::text() const -{ - return myText; -} - -/*! - \brief Get error flag - \return \c true if this is an error message -*/ -bool PyConsole_PrintEvent::isError() const -{ - return myError; -} - -/*! - \class PyConsole_CompletionEvent - \brief Python command completion event. - \internal -*/ - -/*! - \brief Constructor - \param request python request - \param s status of execution of completion command - \param ms command matches (completions) - \param d docstring of the match (in case if there is sinlge match) -*/ -PyConsole_CompletionEvent::PyConsole_CompletionEvent( PyInterp_Request* request, - bool s, - const QStringList& ms, - const QString& d ) - : PyInterp_Event( (QEvent::Type)EVENT_ID, request ), - myStatus( s ), myMatches( ms ), myDoc( d ) -{} - -/*! - \brief Get status of execution of completion command - \return execution status -*/ -bool PyConsole_CompletionEvent::status() const -{ - return myStatus; -} - -/*! - \brief Get matches (completions) - \return detected command matches (completions) -*/ -QStringList PyConsole_CompletionEvent::matches() const -{ - return myMatches; -} - -/*! - \brief Get docstring - \return docstring of the match (in case if there is sinlge match) -*/ -QString PyConsole_CompletionEvent::doc() const -{ - return myDoc; -} diff --git a/src/PyConsole/PyConsole_Event.h b/src/PyConsole/PyConsole_Event.h deleted file mode 100644 index d22b78b1a..000000000 --- a/src/PyConsole/PyConsole_Event.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Event.h -// Author : Vadim SANDLER (Open CASCADE S.A.S), Adrien Bruneton (CEA/DEN) - -#ifndef PYCONSOLE_EVENT_H -#define PYCONSOLE_EVENT_H - -#include "PyConsole.h" -#include "PyInterp_Event.h" - -#include -#include -#include - -class PyConsole_PrintEvent : public QEvent -{ -public: - static const int EVENT_ID = 65432; - - PyConsole_PrintEvent( const QString&, bool = false ); - - QString text() const; - bool isError() const; - -private: - QString myText; //!< Event message (python trace) - bool myError; //!< Set to \c true if an error msg is to be displayed -}; - -class PyConsole_CompletionEvent : public PyInterp_Event -{ -public: - static const int EVENT_ID = 65433; - - PyConsole_CompletionEvent( PyInterp_Request*, bool, const QStringList&, const QString& ); - - bool status() const; - QStringList matches() const; - QString doc() const; - -protected: - bool myStatus; //!< Status of execution - QStringList myMatches; //!< Command matches (completions) - QString myDoc; //!< Docstring of the match (in case if there is sinlge match) -}; - -#endif // PYCONSOLE_EVENT_H diff --git a/src/PyConsole/PyConsole_Interp.cxx b/src/PyConsole/PyConsole_Interp.cxx deleted file mode 100644 index 722413686..000000000 --- a/src/PyConsole/PyConsole_Interp.cxx +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Interp.cxx -// Author : Nicolas REJNERI (OPEN CASCADE), Adrien BRUNETON (CEA/DEN), Vadim SANDLER (OPEN CASCADE) - -#include "PyConsole_Interp.h" - -/*! - \class PyConsole_Interp - \brief Python interpreter to be embedded to the SALOME study's GUI. - - There is only one Python interpreter for the whole SALOME environment. - - Call the initialize() method defined in the base class PyInterp_Interp, - to initialize the interpreter after instance creation. - - The method initialize() calls virtuals methods - - initPython() to initialize global Python interpreter - - initContext() to initialize interpreter internal context - - initRun() to prepare interpreter for running commands - - See PyInterp_Interp class for more details. -*/ - -/*! - \brief Constructor. - - Creates new python interpreter. -*/ -PyConsole_Interp::PyConsole_Interp() - : PyInterp_Interp() -{ -} - -/*! - \brief Destructor. -*/ -PyConsole_Interp::~PyConsole_Interp() -{ -} - -/*! - \brief Performs specific actions before each Python command - - Sets the variable "__IN_SALOME_GUI_CONSOLE" to True. - This is not attached to a module (like salome_iapp.IN_SALOME_GUI_CONSOLE) - since modules are shared across all interpreters in SALOME. - - \note GIL is already acquired here. -*/ -int PyConsole_Interp::beforeRun() -{ - return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=True"); -} - -/*! - \brief Performs specific actions after each Python command - - Sets the variable "__IN_SALOME_GUI_CONSOLE" to False. - \sa beforeRun() - - \note GIL is already acquired here. -*/ -int PyConsole_Interp::afterRun() -{ - return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=False"); -} - -/*! - \brief Run Python dir() command to get matches. - \internal - \param dirArgument Python expression to pass to the dir command. The parsing of what the - user actually started typing is dedicated to the caller - \param startMatch string representing the begining of the patter to be completed. For example, when - the user types "a_string_variable.rsp ", this is "rsp". - \param[out] matches resulting list of matches - \param[out] docString resulting docstring of single match - \return \true if completion succeeded, \c false otherwise -*/ -bool PyConsole_Interp::runDirCommand( const QString& dirArgument, const QString& startMatch, - QStringList& matches, QString& docString ) -{ - static QStringList keywords; - if ( keywords.isEmpty() ) { - keywords << "and" << "as" << "assert" << "break" << "class" << "continue" - << "def" << "del" << "elif" << "else" << "except" << "exec" - << "finally" << "for" << "from" << "global" << "if" << "import" - << "in" << "is" << "lambda" << "not" << "or" << "pass" << "print" << "raise" - << "return" << "try" << "while" << "with" << "yield"; - } - - // run dir() command and extract completions - if ( !runDirAndExtract( dirArgument, startMatch, matches ) ) - return false; - - // If dirArgument is empty, we append the __builtins__ - if ( dirArgument.isEmpty() ) { - if ( !runDirAndExtract( QString( "__builtins__" ), startMatch, matches, false ) ) - return false; - - // ... and we match on Python's keywords as well - foreach( QString keyword, keywords ) { - if ( keyword.startsWith( startMatch ) ) - matches.append( keyword ); - } - } - - // Try to get doc string of the first match - if ( matches.size() > 0 ) { - QString cmd = QString( "%1.__doc__" ).arg( matches[0] ); - if ( !dirArgument.trimmed().isEmpty() ) - cmd.prepend( QString( "%1." ).arg( dirArgument ) ); - - PyObject* str = PyRun_String( cmd.toStdString().c_str(), Py_eval_input, _global_context, _local_context ); - if ( !str || str == Py_None || !PyString_Check( str ) ) - { - if ( !str ) - PyErr_Clear(); - } - else { - docString = QString( PyString_AsString( str ) ); - } - Py_XDECREF( str ); - } - return true; -} - -/*! - \internal - \sa runDirCommand() - \param dirArgument see runDirCommand() - \param startMatch see runDirCommand() - \param[out] result resulting list of matches - \param discardSwig if \c true, a regular expression is used to discard all static method generated - by SWIG. Typically: MEDCouplingUMesh_Blabla - \return \c true if the call to dir() and parsing of the result succeeded, \false otherwise. -*/ -bool PyConsole_Interp::runDirAndExtract( const QString& dirArgument, - const QString& startMatch, - QStringList& result, - bool discardSwig ) const -{ - QRegExp re( "^[A-Z].+_[A-Z]+[a-z]+.+$" ); // REX to discard SWIG static method, e.g. MEDCouplingUMesh_Blabla - - // Execute dir() command - QString command( "dir(" + dirArgument + ")" ); - PyObject* plst = PyRun_String( command.toStdString().c_str(), Py_eval_input, _global_context, _local_context ); - if ( !plst || plst == Py_None ) { - if ( !plst ) - PyErr_Clear(); - Py_XDECREF( plst ); - return false; - } - - // Check result - if ( !PySequence_Check( plst ) ) { - // Should never happen ... - Py_XDECREF( plst ); - return false; - } - - // Extract the returned list - int n = PySequence_Length( plst ); - for ( int i = 0; i < n; i++ ) { - PyObject* it; - it = PySequence_GetItem( plst, i ); - QString s( PyString_AsString( it ) ); - // if the method is not from swig, not static (guessed from the reg exp) and matches - // what is already there - if ( s.startsWith( startMatch ) ) { - if ( !discardSwig || ( !re.exactMatch( s ) && !s.contains( "swig" ) ) ) - result.append( s ); - } - Py_DECREF( it ); - } - Py_DECREF( plst ); - - return true; -} diff --git a/src/PyConsole/PyConsole_Interp.h b/src/PyConsole/PyConsole_Interp.h deleted file mode 100644 index 4275c350f..000000000 --- a/src/PyConsole/PyConsole_Interp.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyConsole_Interp.h -// Author : Nicolas REJNERI (OPEN CASCADE), Adrien BRUNETON (CEA/DEN), Vadim SANDLER (OPEN CASCADE) - -#ifndef PYCONSOLE_INTERP_H -#define PYCONSOLE_INTERP_H - -#include "PyConsole.h" -#include "PyInterp_Interp.h" - -#include - -class PYCONSOLE_EXPORT PyConsole_Interp : public PyInterp_Interp -{ - friend class PyConsole_CompletionCommand; - -public: - PyConsole_Interp(); - ~PyConsole_Interp(); - - virtual int afterRun(); - virtual int beforeRun(); - -private: - bool runDirCommand( const QString&, const QString&, QStringList&, QString& ); - bool runDirAndExtract( const QString&, const QString&, QStringList&, bool = true ) const; -}; - -#endif // PYCONSOLE_INTERP_H diff --git a/src/PyConsole/PyConsole_Request.cxx b/src/PyConsole/PyConsole_Request.cxx deleted file mode 100644 index aa41c259e..000000000 --- a/src/PyConsole/PyConsole_Request.cxx +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, 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 : PyConsole_Request.cxx -// Author : Vadim SANDLER (OPEN CASCADE), Adrien Bruneton (CEA/DEN) - -#include "PyConsole_Request.h" -#include "PyConsole_Interp.h" -#include "PyConsole_Event.h" - -#include - -/*! - \class PyConsole_ExecCommand - \brief Python command execution request. - \internal -*/ - -/*! - \brief Constructor. - - Creates new python command execution request. - - \param theInterp python interpreter - \param theCommand python command - \param theListener widget to get the notification messages - \param theSync if \c true, the request is processed synchronously -*/ -PyConsole_ExecCommand::PyConsole_ExecCommand( PyInterp_Interp* theInterp, - const QString& theCommand, - QObject* theListener, - bool theSync ) - : PyInterp_LockRequest( theInterp, theListener, theSync ), - myCommand( theCommand ), myState( PyInterp_Event::ES_OK ) -{} - -/*! - \brief Execute the python command in the interpreter and - get its execution status. -*/ -void PyConsole_ExecCommand::execute() -{ - if ( myCommand != "" ) { - int ret = getInterp()->run( myCommand.toLatin1().data() ); - if ( ret < 0 ) - myState = PyInterp_Event::ES_ERROR; - else if ( ret > 0 ) - myState = PyInterp_Event::ES_INCOMPLETE; - } -} - -/*! - \brief Create and return a notification event. - \return new notification event -*/ -QEvent* PyConsole_ExecCommand::createEvent() -{ - if ( IsSync() ) - QCoreApplication::sendPostedEvents( listener(), PyConsole_PrintEvent::EVENT_ID ); - return new PyInterp_Event( myState, this ); -} - -/*! - \class PyConsole_CompletionCommand - \brief Python command completion request. - \internal -*/ - -/*! - \brief Constructor. - - Creates a new python completion request. - - \param theInterp python interpreter - \param theInput string containing the dir() command to be executed - \param theStartMatch part to be matched with the results of the dir() command - \param theListener widget to get the notification messages - \param theSync if \c true the request is processed synchronously -*/ -PyConsole_CompletionCommand::PyConsole_CompletionCommand( PyInterp_Interp* theInterp, - const QString& theInput, - const QString& theStartMatch, - QObject* theListener, - bool theSync ) - : PyInterp_LockRequest( theInterp, theListener, theSync ), - myDirArg( theInput ), myStartMatch( theStartMatch ), myStatus( false ) -{} - -/*! - \brief Execute the completion command by invoking runDirCommand() function - of interpreter. -*/ -void PyConsole_CompletionCommand::execute() -{ - myStatus = static_cast( getInterp() )->runDirCommand( myDirArg, myStartMatch, myMatches, myDoc ); -} - -/*! - \brief Create and return completion event - \return new completion event - */ -QEvent* PyConsole_CompletionCommand::createEvent() -{ - return new PyConsole_CompletionEvent( this, myStatus, myMatches, myDoc ); -} diff --git a/src/PyConsole/PyConsole_Request.h b/src/PyConsole/PyConsole_Request.h deleted file mode 100644 index 827823b60..000000000 --- a/src/PyConsole/PyConsole_Request.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, 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 : PyConsole_Request.h -// Author : Vadim SANDLER (OPEN CASCADE), Adrien Bruneton (CEA/DEN) - -#ifndef PYCONSOLE_REQUEST_H -#define PYCONSOLE_REQUEST_H - -#include "PyInterp_Request.h" - -#include -#include - -class QEvent; -class PyInterp_Interp; - -class PyConsole_ExecCommand : public PyInterp_LockRequest -{ -public: - PyConsole_ExecCommand( PyInterp_Interp*, const QString&, QObject*, bool = false ); - -protected: - virtual void execute(); - virtual QEvent* createEvent(); - -private: - QString myCommand; //!< Python command - int myState; //!< Python command execution status -}; - -class PyConsole_CompletionCommand : public PyInterp_LockRequest -{ -public: - PyConsole_CompletionCommand( PyInterp_Interp*, const QString&, const QString&, QObject*, bool = false ); - -protected: - virtual void execute(); - virtual QEvent* createEvent(); - -private: - QString myDirArg; //!< String to be passed to the dir() comman - QString myStartMatch; //!< Begining of the command (as typed by the user) - bool myStatus; //!< Status of completion command execution - QStringList myMatches; //!< Matches - QString myDoc; //!< Docstring of single match -}; - -#endif // PYCONSOLE_REQUEST_H diff --git a/src/PyConsole/resources/PyConsole_msg_en.ts b/src/PyConsole/resources/PyConsole_msg_en.ts deleted file mode 100644 index 7fac57c1d..000000000 --- a/src/PyConsole/resources/PyConsole_msg_en.ts +++ /dev/null @@ -1,74 +0,0 @@ - - - - - PyConsole_Console - - - EDIT_COPY_CMD - &Copy - - - - EDIT_PASTE_CMD - &Paste - - - - EDIT_CLEAR_CMD - Clea&r - - - - EDIT_SELECTALL_CMD - Select &All - - - EDIT_DUMPCOMMANDS_CMD - D&ump Commands - - - EDIT_STARTLOG_CMD - Start &Log - - - EDIT_STOPLOG_CMD - Stop &Log - - - - PyConsole_Editor - - GET_DUMP_COMMANDS_FILENAME - Dump commands to file - - - GET_PYTHON_TRACE_FILENAME - Save Python trace to file - - - PYTHON_SCRIPTS - Python scripts - - - WARNING - Warning! - - - LOG_FILES - Log files - - - ERR_FILE_NOT_WRITEABLE - File is not writeable! - - - TOO_MANY_MATCHES - Too many matches! Displaying first ones only... - - - NO_DOC_AVAILABLE - no documentation available - - - diff --git a/src/PyConsole/resources/PyConsole_msg_fr.ts b/src/PyConsole/resources/PyConsole_msg_fr.ts deleted file mode 100644 index 9eaf24482..000000000 --- a/src/PyConsole/resources/PyConsole_msg_fr.ts +++ /dev/null @@ -1,74 +0,0 @@ - - - - - PyConsole_Console - - - EDIT_COPY_CMD - &Copier - - - - EDIT_PASTE_CMD - C&oller - - - - EDIT_CLEAR_CMD - &Effacer - - - - EDIT_SELECTALL_CMD - &Tout sélectionner - - - EDIT_DUMPCOMMANDS_CMD - &Générer le script des commandes - - - EDIT_STARTLOG_CMD - Démarrer une &trace - - - EDIT_STOPLOG_CMD - Arrêter la &trace - - - - PyConsole_Editor - - GET_DUMP_COMMANDS_FILENAME - Choisissez un fichier python où sauver le dump - - - GET_PYTHON_TRACE_FILENAME - Choisissez un fichier où sauver le log - - - PYTHON_SCRIPTS - Scripts Python - - - WARNING - Attention ! - - - LOG_FILES - Fichiers log - - - ERR_FILE_NOT_WRITEABLE - Le fichier n'a pas été écrit ! - - - TOO_MANY_MATCHES - Too many matches! Displaying first ones only... - - - NO_DOC_AVAILABLE - no documentation available - - - diff --git a/src/PyConsole/resources/PyConsole_msg_ja.ts b/src/PyConsole/resources/PyConsole_msg_ja.ts deleted file mode 100644 index 8861d5ed5..000000000 --- a/src/PyConsole/resources/PyConsole_msg_ja.ts +++ /dev/null @@ -1,74 +0,0 @@ - - - - - PyConsole_Console - - - EDIT_COPY_CMD - コピー(&C) - - - - EDIT_PASTE_CMD - 貼り付け(&P) - - - - EDIT_CLEAR_CMD - 削除(&r) - - - - EDIT_SELECTALL_CMD - すべて選択します。(&A) - - - EDIT_DUMPCOMMANDS_CMD - スクリプト コマンドを生成します。(&u) - - - EDIT_STARTLOG_CMD - ログの開始 (&L) - - - EDIT_STOPLOG_CMD - ログの停止 (&L) - - - - PyConsole_Editor - - GET_DUMP_COMMANDS_FILENAME - Dump commands to file - - - GET_PYTHON_TRACE_FILENAME - Save Python trace to file - - - PYTHON_SCRIPTS - Python scripts - - - WARNING - Warning! - - - LOG_FILES - Log files - - - ERR_FILE_NOT_WRITEABLE - File is not writeable! - - - TOO_MANY_MATCHES - Too many matches! Displaying first ones only... - - - NO_DOC_AVAILABLE - no documentation available - - - diff --git a/src/PyInterp/CMakeLists.txt b/src/PyInterp/CMakeLists.txt deleted file mode 100755 index d44a73ff3..000000000 --- a/src/PyInterp/CMakeLists.txt +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (C) 2012-2016 CEA/DEN, EDF R&D, 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 -# - -INCLUDE(UseQtExt) - -# --- options --- - -# additional include directories -INCLUDE_DIRECTORIES( - ${QT_INCLUDES} - ${PYTHON_INCLUDE_DIRS} -) - -# additional preprocessor / compiler flags -ADD_DEFINITIONS(${QT_DEFINITIONS} ${PYTHON_DEFINITIONS}) - -# libraries to link to -SET(_link_LIBRARIES ${QT_LIBRARIES} ${PYTHON_LIBRARIES}) - -# --- headers --- - -# header files / to be processed by moc -SET(_moc_HEADERS - PyInterp_Dispatcher.h -) - -# header files / no moc processing -SET(_other_HEADERS - PyInterp.h - PyInterp_Event.h - PyInterp_Interp.h - PyInterp_Request.h - PyInterp_Utils.h -) - -# header files / to install -SET(PyInterp_HEADERS ${_moc_HEADERS} ${_other_HEADERS}) - -# --- sources --- - -# sources / moc wrappings -QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS}) - -# sources / static -SET(_other_SOURCES - PyInterp_Dispatcher.cxx - PyInterp_Event.cxx - PyInterp_Interp.cxx - PyInterp_Request.cxx -) - -# sources / to compile -SET(PyInterp_SOURCES ${_other_SOURCES} ${_moc_SOURCES}) - -# --- rules --- - -ADD_LIBRARY(PyInterp ${PyInterp_SOURCES}) -TARGET_LINK_LIBRARIES(PyInterp ${_link_LIBRARIES}) -INSTALL(TARGETS PyInterp EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_LIBS}) - -INSTALL(FILES ${PyInterp_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS}) diff --git a/src/PyInterp/PyInterp.h b/src/PyInterp/PyInterp.h deleted file mode 100755 index 08271e032..000000000 --- a/src/PyInterp/PyInterp.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp.h -// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) - -#if !defined ( PYINTERP_H ) -#define PYINTERP_H - -// ======================================================== -// set dllexport type for Win platform -#ifdef WIN32 -# if defined PYINTERP_EXPORTS || defined PyInterp_EXPORTS -# define PYINTERP_EXPORT __declspec(dllexport) -# else -# define PYINTERP_EXPORT __declspec(dllimport) -# endif -#else // WIN32 -# define PYINTERP_EXPORT -#endif // WIN32 - -// ======================================================== - -#include - -// avoid warning messages -#ifdef WIN32 -#pragma warning (disable : 4786) -#pragma warning (disable : 4251) -#endif - -#endif // PYINTERP_H diff --git a/src/PyInterp/PyInterp_Dispatcher.cxx b/src/PyInterp/PyInterp_Dispatcher.cxx deleted file mode 100755 index b70214481..000000000 --- a/src/PyInterp/PyInterp_Dispatcher.cxx +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Dispatcher.cxx -// Author : Sergey Anikin (OPEN CASCADE S.A.S.) - -#include "PyInterp_Dispatcher.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! - -/** - \class PyInterp_Dispatcher - \brief Dispatcher of Python events; used to serialize requests to Python interpreter. -*/ - -PyInterp_Dispatcher* PyInterp_Dispatcher::myInstance = 0; - -PyInterp_Dispatcher* PyInterp_Dispatcher::Get() -{ - if ( !myInstance ) - myInstance = new PyInterp_Dispatcher(); - return myInstance; -} - -PyInterp_Dispatcher::PyInterp_Dispatcher() -: QThread() -{ -} - -PyInterp_Dispatcher::~PyInterp_Dispatcher() -{ - // Clear the request queue - myQueueMutex.lock(); - - QListIterator it( myQueue ); - while ( it.hasNext() ) - PyInterp_Request::Destroy( it.next() ); - myQueue.clear(); - - myQueueMutex.unlock(); - - // Wait for run() to finish - wait(); -} - -bool PyInterp_Dispatcher::IsBusy() const -{ - return isRunning(); -} - -void PyInterp_Dispatcher::Exec( PyInterp_Request* theRequest ) -{ - if ( !theRequest ) - return; - - if ( theRequest->IsSync() /*&& !IsBusy()*/) - { - // synchronous processing - processRequest( theRequest ); - } - else - { - // asynchronous processing - myQueueMutex.lock(); - - myQueue.enqueue( theRequest ); - if ( theRequest->listener() ) { - connect( theRequest->listener(), SIGNAL( destroyed( QObject* ) ), - this, SLOT( objectDestroyed( QObject* ) ) ); - } - - myQueueMutex.unlock(); - - if ( !IsBusy() ) - start(); - } -} - -void PyInterp_Dispatcher::run() -{ - PyInterp_Request* aRequest; - - // prepare for queue size check - myQueueMutex.lock(); - - while ( myQueue.size() ) - { - aRequest = myQueue.head(); - - // let other threads append their requests to the end of the queue - myQueueMutex.unlock(); - - // processRequest() may delete a request, so this pointer must not be used - // after request is processed! - processRequest( aRequest ); - - // prepare for removal of the first request in the queue - myQueueMutex.lock(); - - // IMPORTANT: the first item could have been removed by objectDestroyed() --> we have to check it - if ( myQueue.head() == aRequest ) // if it is still here --> remove it - myQueue.dequeue(); - } - - myQueueMutex.unlock(); -} - -void PyInterp_Dispatcher::processRequest( PyInterp_Request* theRequest ) -{ - theRequest->process(); -} - -void PyInterp_Dispatcher::objectDestroyed( QObject* o ) -{ - // prepare for modification of the queue - myQueueMutex.lock(); - - QMutableListIterator it( myQueue ); - while ( it.hasNext() ) - { - RequestPtr r = it.next(); - if ( o == r->listener() ) - { - r->setListener( 0 ); // to prevent event posting - it.remove(); - } - } - - myQueueMutex.unlock(); -} diff --git a/src/PyInterp/PyInterp_Dispatcher.h b/src/PyInterp/PyInterp_Dispatcher.h deleted file mode 100755 index c9b83847a..000000000 --- a/src/PyInterp/PyInterp_Dispatcher.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Dispatcher.h -// Author : Sergey Anikin (OPEN CASCADE S.A.S.) - -#ifndef PYINTERP_DISPATCHER_H -#define PYINTERP_DISPATCHER_H - -#include "PyInterp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! - -#include "PyInterp_Request.h" // full include instead of forward declaration - // everyone inc'ing the Dispatcher will get the requests for free. - -#include -#include -#include - -class PYINTERP_EXPORT PyInterp_Dispatcher : protected QThread -{ - PyInterp_Dispatcher(); // private constructor - Q_OBJECT -public: - static PyInterp_Dispatcher* Get(); - - virtual ~PyInterp_Dispatcher(); - - bool IsBusy() const; - void Exec( PyInterp_Request* ); - -private: - virtual void run(); - void processRequest( PyInterp_Request* ); - -private slots: - void objectDestroyed( QObject* ); - -private: - typedef PyInterp_Request* RequestPtr; - - QQueue myQueue; - QMutex myQueueMutex; - - static PyInterp_Dispatcher* myInstance; -}; - -#endif // PYINTERP_DISPATCHER_H diff --git a/src/PyInterp/PyInterp_Event.cxx b/src/PyInterp/PyInterp_Event.cxx deleted file mode 100644 index 478eb2c36..000000000 --- a/src/PyInterp/PyInterp_Event.cxx +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Event.cxx -// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) - -#include "PyInterp_Event.h" -#include "PyInterp_Request.h" - -/** - \class PyInterp_Event - \brief Events thrown by the interpreter having executed a command and indicating - the return status. -*/ - -PyInterp_Event::PyInterp_Event( int type, PyInterp_Request* request ) - : QEvent( (QEvent::Type)type ), myRequest( request ) -{ -} - -PyInterp_Event::~PyInterp_Event() -{ - PyInterp_Request::Destroy( myRequest ); - myRequest = 0; -} - -PyInterp_Request* PyInterp_Event::GetRequest() const -{ - return myRequest; -} - -PyInterp_Event::operator PyInterp_Request*() const -{ - return myRequest; -} diff --git a/src/PyInterp/PyInterp_Event.h b/src/PyInterp/PyInterp_Event.h deleted file mode 100644 index e812adbe1..000000000 --- a/src/PyInterp/PyInterp_Event.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Event.h -// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) - -#ifndef PYINTERP_EVENT_H -#define PYINTERP_EVENT_H - -#include "PyInterp.h" - -#include - -class PyInterp_Request; - -class PYINTERP_EXPORT PyInterp_Event : public QEvent -{ - PyInterp_Event(); - PyInterp_Event( const PyInterp_Event& ); - -public: - // Execution state - enum { - ES_NOTIFY = QEvent::User + 5000, - ES_OK, - ES_ERROR, - ES_INCOMPLETE, - ES_LAST }; - - PyInterp_Event( int type, PyInterp_Request* request ); - virtual ~PyInterp_Event(); - - PyInterp_Request* GetRequest() const; - operator PyInterp_Request*() const; - -private: - PyInterp_Request* myRequest; -}; - -#endif // PYINTERP_EVENT_H diff --git a/src/PyInterp/PyInterp_Interp.cxx b/src/PyInterp/PyInterp_Interp.cxx deleted file mode 100644 index 5946e8d7f..000000000 --- a/src/PyInterp/PyInterp_Interp.cxx +++ /dev/null @@ -1,607 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Interp.cxx -// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON - -#include "PyInterp_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! -#include "PyInterp_Utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define TOP_HISTORY_PY "--- top of history ---" -#define BEGIN_HISTORY_PY "--- begin of history ---" - -/* - The following functions are used to hook the Python - interpreter output. -*/ - -static void -PyStdOut_dealloc(PyStdOut *self) -{ - PyObject_Del(self); -} - -static PyObject* -PyStdOut_write(PyStdOut *self, PyObject *args) -{ - char *c; - int l; - if (!PyArg_ParseTuple(args, "t#:write",&c, &l)) - return NULL; - if(self->_cb==NULL) { - if ( self->_iscerr ) - std::cerr << c ; - else - std::cout << c ; - } - else { - self->_cb(self->_data,c); - } - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -PyStdOut_flush(PyStdOut *self) -{ - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef PyStdOut_methods[] = { - {"write", (PyCFunction)PyStdOut_write, METH_VARARGS, PyDoc_STR("write(string) -> None")}, - {"flush", (PyCFunction)PyStdOut_flush, METH_NOARGS, PyDoc_STR("flush() -> None")}, - {NULL, NULL} /* sentinel */ -}; - -static PyMemberDef PyStdOut_memberlist[] = { - {(char*)"softspace", T_INT, offsetof(PyStdOut, softspace), 0, - (char*)"flag indicating that a space needs to be printed; used by print"}, - {NULL} /* Sentinel */ -}; - -static PyTypeObject PyStdOut_Type = { - /* The ob_type field must be initialized in the module init function - * to be portable to Windows without using C++. */ - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ - "PyOut", /*tp_name*/ - sizeof(PyStdOut), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)PyStdOut_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr, /*tp_getattro*/ - /* softspace is writable: we must supply tp_setattro */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - PyStdOut_methods, /*tp_methods*/ - PyStdOut_memberlist, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - 0, /*tp_alloc*/ - 0, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - -#define PyStdOut_Check(v) ((v)->ob_type == &PyStdOut_Type) - -static PyStdOut* newPyStdOut( bool iscerr ) -{ - PyStdOut *self; - self = PyObject_New(PyStdOut, &PyStdOut_Type); - if (self == NULL) - return NULL; - self->softspace = 0; - self->_cb = NULL; - self->_iscerr = iscerr; - return self; -} - -/*! - \class PyInterp_Interp - \brief Generic embedded Python interpreter. -*/ - -int PyInterp_Interp::_argc = 1; -char* PyInterp_Interp::_argv[] = {(char*)""}; - -/*! - \brief Basic constructor. - - After construction the interpreter instance successor classes - must call virtual method initalize(). -*/ -PyInterp_Interp::PyInterp_Interp(): - _vout(0), _verr(0), _local_context(0), _global_context(0), _initialized(false) -{ -} - -/*! - \brief Destructor. -*/ -PyInterp_Interp::~PyInterp_Interp() -{ - destroy(); -} - -/*! - \brief Initialize embedded interpreter. - - This method shoud be called after construction of the interpreter. - The method initialize() calls virtuals methods - - initPython() to initialize global Python interpreter - - initContext() to initialize interpreter internal context - - initRun() to prepare interpreter for running commands - which should be implemented in the successor classes, according to the - embedded Python interpreter policy (mono or multi interpreter, etc). -*/ -void PyInterp_Interp::initialize() -{ - if ( initialized() ) - return; // prevent repeating intitialization - - _initialized = true; - - _history.clear(); // start a new list of user's commands - _ith = _history.begin(); - - initPython(); // This also inits the multi-threading for Python (but w/o acquiring GIL) - - // ---- The rest of the initialisation process is done hodling the GIL - PyLockWrapper lck; - - initContext(); - - // used to interpret & compile commands - this is really imported here - // and only added again (with PyImport_AddModule) later on - PyObjWrapper m(PyImport_ImportModule("codeop")); - if(!m) { - PyErr_Print(); - return; - } - - // Create python objects to capture stdout and stderr - _vout=(PyObject*)newPyStdOut( false ); // stdout - _verr=(PyObject*)newPyStdOut( true ); // stderr - - // All the initRun outputs are redirected to the standard output (console) - initRun(); -} - -void PyInterp_Interp::destroy() -{ - PyLockWrapper lck; - closeContext(); -} - -/*! - \brief Initialize Python interpreter. - - In case if Python is not initialized, it sets program name, initializes the single true Python - interpreter, sets program arguments, and initializes threads. - Otherwise, does nothing. This is important for light SALOME configuration, - as in full SALOME this is done at SalomeApp level. - \sa SalomeApp_PyInterp class and main() in SALOME_Session_Server - */ -void PyInterp_Interp::initPython() -{ - if (!Py_IsInitialized()){ - // Python is not initialized - Py_SetProgramName(_argv[0]); - Py_Initialize(); // Initialize the interpreter - PySys_SetArgv(_argc, _argv); - - PyEval_InitThreads(); // Create (and acquire) the Python global interpreter lock (GIL) - PyEval_ReleaseLock(); - } -} - -/*! - \brief Get embedded Python interpreter banner. - \return banner string - */ -std::string PyInterp_Interp::getBanner() const -{ - PyLockWrapper lck; - std::string aBanner("Python "); - aBanner = aBanner + Py_GetVersion() + " on " + Py_GetPlatform() ; - aBanner = aBanner + "\ntype help to get general information on environment\n"; - return aBanner; -} - -/*! - \brief Initialize run command. - - This method is used to prepare interpreter for running - Python commands. - - \return \c true on success and \c false on error -*/ -bool PyInterp_Interp::initRun() -{ - return true; -} - -/*! - * Initialize context dictionaries. GIL is held already. - * The code executed in an embedded interpreter is expected to be run at the module - * level, in which case local and global context have to be the same dictionary. - * See: http://stackoverflow.com/questions/12265756/c-python-running-python-code-within-a-context - * for an explanation. - */ -bool PyInterp_Interp::initContext() -{ - PyObject *m = PyImport_AddModule("__main__"); // interpreter main module (module context) - if(!m){ - PyErr_Print(); - return false; - } - _global_context = PyModule_GetDict(m); // get interpreter global variable context - Py_INCREF(_global_context); - _local_context = _global_context; - - int ret = PyRun_SimpleString("import salome_iapp;salome_iapp.IN_SALOME_GUI=True"); - - return ret == 0; -} - -/*! - * Destroy context dictionaries. GIL is held already. - */ -void PyInterp_Interp::closeContext() -{ - Py_XDECREF(_global_context); - // both _global_context and _local_context may point to the same Python object - if ( _global_context != _local_context) - Py_XDECREF(_local_context); -} - -/*! - \brief Compile Python command and evaluate it in the - python dictionary contexts if possible. This is not thread-safe. - This is the caller's responsability to make this thread-safe. - \internal - \param command Python command string - \return -1 on fatal error, 1 if command is incomplete and 0 - if command is executed successfully - */ -static int run_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) -{ - PyObject *m = PyImport_AddModule("codeop"); - if(!m) { - // Fatal error. No way to go on. - PyErr_Print(); - return -1; - } - - PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command)); - if(!v) { - // Error encountered. It should be SyntaxError, - //so we don't write out traceback - PyObjWrapper exception, value, tb; - PyErr_Fetch(&exception, &value, &tb); - PyErr_NormalizeException(&exception, &value, &tb); - PyErr_Display(exception, value, NULL); - return -1; - } - else if (v == Py_None) { - // Incomplete text we return 1 : we need a complete text to execute - return 1; - } - else { - PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,global_ctxt, local_ctxt)); - if(!r) { - // Execution error. We return -1 - PyErr_Print(); - return -1; - } - // The command has been successfully executed. Return 0 - return 0; - } -} - -void replaceAll(std::string& str, const std::string& from, const std::string& to) { - if(from.empty()) - return; - size_t start_pos = 0; - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' - } -} - -std::vector -__split(const std::string& str, char delimiter) -{ - std::vector internal; - std::stringstream ss(str); // Turn the string into a stream. - std::string tok; - - while (getline(ss, tok, delimiter)) { - internal.push_back(tok); - } - - return internal; -} - -std::string -__join(const std::vector& v, int begin=0, int end=-1) -{ - if (end == -1) - end = v.size(); - std::stringstream ss; - for (size_t i = begin; i < end; ++i) { - if (i != begin) - ss << ","; - ss << v[i]; - } - return ss.str(); -} - -std::vector -__getArgsList(std::string argsString) -{ - // Special process if some items of 'args:' list are themselves lists - // Note that an item can be a list, but not a list of lists... - // So we can have something like this: - // myscript.py args:[\'file1\',\'file2\'],\'val1\',\"done\",[1,2,3],[True,False],\"ok\",kwarg1=\'kwarg1\',kwarg2=\'kwarg2\',\'fin\' - // With such a call, argsString variable contains the string representing ['file1','file2'],'val1','done',[1,2,3],[True,False],'ok',kwarg1='kwarg1',kwarg2='kwarg2','fin' - // We have to split argsString to obtain a 9 string elements list - std::vector x = __split(argsString, ','); - bool containsList = (argsString.find('[') != std::string::npos); - if (containsList) { - std::vector listBeginIndices, listEndIndices; - for (int pos = 0; pos < x.size(); ++pos) { - if (x[pos][0] == '[') - listBeginIndices.push_back(pos); - else if (x[pos][x[pos].size()-1] == ']') - listEndIndices.push_back(pos); - } - std::vector extractedArgs; - int start = 0; - for (int pos = 0; pos < listBeginIndices.size(); ++pos) { - int lbeg = listBeginIndices[pos]; - int lend = listEndIndices[pos]; - if (lbeg > start) - for (int k = start; k < lbeg; ++k) - extractedArgs.push_back(x[k]); - extractedArgs.push_back(__join(x, lbeg, lend+1)); - start = lend+1; - } - if (start < x.size()) - for (int k = start; k < x.size(); ++k) - extractedArgs.push_back(x[k]); - return extractedArgs; - } - else { - return x; - } -} - -/*! - \brief Compile Python command and evaluate it in the - python dictionary context if possible. Command might correspond to - the execution of a script with optional arguments. - In this case, command is: - execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") - \internal - \param command Python command string - \param context Python context (dictionary) - \return -1 on fatal error, 1 if command is incomplete and 0 - if command is executed successfully - */ -static int compile_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) -{ - // First guess if command is execution of a script with args, or a simple Python command - std::string singleCommand = command; - std::string commandArgs = ""; - - QRegExp rx("execfile\\s*\\(.*(args:.*)\"\\s*\\)"); - if (rx.indexIn(command) != -1) { - commandArgs = rx.cap(1).remove(0,5).toStdString(); // arguments of command - singleCommand = rx.cap().remove(rx.cap(1)).remove(" ").toStdString(); // command for execution without arguments - } - - if (commandArgs.empty()) { - // process command: expression - // process command: execfile(r"/absolute/path/to/script.py") (no args) - return run_command(singleCommand.c_str(), global_ctxt, local_ctxt); - } - else { - // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") - std::string script = singleCommand.substr(11); // remove leading execfile(r" - script = script.substr(0, script.length()-2); // remove trailing ") - std::vector argList = __getArgsList(commandArgs); - - std::string preCommandBegin = "import sys; save_argv = sys.argv; sys.argv=["; - std::string preCommandEnd = "];"; - std::string completeCommand = preCommandBegin+"\""+script+"\","; - for (std::vector::iterator itr = argList.begin(); itr != argList.end(); ++itr) { - if (itr != argList.begin()) - completeCommand += ","; - completeCommand = completeCommand + "\"" + *itr + "\""; - } - completeCommand = completeCommand+preCommandEnd+singleCommand+";sys.argv=save_argv"; - return run_command(completeCommand.c_str(), global_ctxt, local_ctxt); - } -} - -/*! - \brief Run Python command - the command has to fit on a single line (no \n!). - Use ';' if you need multiple statements evaluated at once. - \param command Python command - \return command status -*/ -int PyInterp_Interp::run(const char *command) -{ - beforeRun(); - int ret = simpleRun(command); - afterRun(); - return ret; -} - -/** - * Called before a command is run (when calling run() method). Not thread-safe. Caller's responsability - * to acquire GIL if needed. - */ -int PyInterp_Interp::beforeRun() -{ - return 0; -} - -/** - * Called after a command is run (when calling run() method). Not thread-safe. Caller's responsability - * to acquire GIL if needed. - */ -int PyInterp_Interp::afterRun() -{ - return 0; -} - -/*! - \brief Run Python command (used internally). Not thread-safe. GIL acquisition is caller's responsability. - \param command Python command - \param addToHistory if \c true (default), the command is added to the commands history - \return command status -*/ -int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) -{ - if( addToHistory && strcmp(command,"") != 0 ) { - _history.push_back(command); - _ith = _history.end(); - } - - // Current stdout and stderr are saved - PyObject * oldOut = PySys_GetObject((char*)"stdout"); - PyObject * oldErr = PySys_GetObject((char*)"stderr"); - // Keep them alive (PySys_GetObject returned a *borrowed* ref!) - Py_INCREF(oldOut); - Py_INCREF(oldErr); - - // Redirect outputs to SALOME Python console before treatment - PySys_SetObject((char*)"stderr",_verr); - PySys_SetObject((char*)"stdout",_vout); - - int ier = compile_command(command, _global_context, _local_context); - - // Outputs are redirected to what they were before - PySys_SetObject((char*)"stdout",oldOut); - PySys_SetObject((char*)"stderr",oldErr); - - return ier; -} - -/*! - \brief Get previous command in the commands history. - \return previous command -*/ -const char * PyInterp_Interp::getPrevious() -{ - if(_ith != _history.begin()){ - _ith--; - return (*_ith).c_str(); - } - else - return BEGIN_HISTORY_PY; -} - -/*! - \brief Get next command in the commands history. - \return next command -*/ -const char * PyInterp_Interp::getNext() -{ - if(_ith != _history.end()){ - _ith++; - } - if (_ith == _history.end()) - return TOP_HISTORY_PY; - else - return (*_ith).c_str(); -} - -/*! - \brief Set Python standard output device hook. - \param cb callback function - \param data callback function parameters -*/ -void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data) -{ - ((PyStdOut*)_vout)->_cb=cb; - ((PyStdOut*)_vout)->_data=data; -} - -/*! - \brief Set Python standard error device hook. - \param cb callback function - \param data callback function parameters -*/ -void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data) -{ - ((PyStdOut*)_verr)->_cb=cb; - ((PyStdOut*)_verr)->_data=data; -} - -/*! - \bried Check if the interpreter is initialized - \internal -*/ -bool PyInterp_Interp::initialized() const -{ - return _initialized; -} diff --git a/src/PyInterp/PyInterp_Interp.h b/src/PyInterp/PyInterp_Interp.h deleted file mode 100644 index 88e551916..000000000 --- a/src/PyInterp/PyInterp_Interp.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Interp.h -// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON - -#ifndef PYINTERP_INTERP_H -#define PYINTERP_INTERP_H - -#include "PyInterp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! -#include "PyInterp_Utils.h" - -#include -#include - -typedef void PyOutChanged(void* data,char * c); - -typedef struct { - PyObject_HEAD - int softspace; - PyOutChanged* _cb; - void* _data; - bool _iscerr; -} PyStdOut; - -/** - * Main class representing a *virtual* Python interpreter. There is really only one true - * Python interpreter in the whole application (no call to Py_NewInterpreter), - * but the use of different execution contexts allow - * to split the execution lines, and hence to emulate (relatively) independent interpreters. - * This has some consequences: modules imported in one context are not re-imported in another context - * (only there namespace is made available when importing in another context). - * See also class PyConsole_Interp. - */ -class PYINTERP_EXPORT PyInterp_Interp -{ -public: - static int _argc; - static char* _argv[]; - - PyInterp_Interp(); - virtual ~PyInterp_Interp(); - - void initialize(); - void destroy(); - - virtual int run(const char *command); - virtual void initStudy() {}; - - std::string getBanner() const; - void setverrcb(PyOutChanged*, void*); - void setvoutcb(PyOutChanged*, void*); - - const char* getPrevious(); - const char* getNext(); - -protected: - /** Redirection of stdout and stderr */ - PyObject* _vout; - PyObject* _verr; - /** Execution context (local and global variables) */ - PyObject* _global_context; - PyObject* _local_context; - - std::list _history; - std::list::iterator _ith; - - virtual int beforeRun(); - virtual int afterRun(); - int simpleRun(const char* command, const bool addToHistory = true); - - virtual void initPython(); - - /** Initialize execution context. */ - virtual bool initContext(); - virtual bool initRun(); - virtual void closeContext(); - - bool initialized() const; - -private: - bool _initialized; -}; - -#endif // PYINTERP_INTERP_H diff --git a/src/PyInterp/PyInterp_Request.cxx b/src/PyInterp/PyInterp_Request.cxx deleted file mode 100644 index f8227f09c..000000000 --- a/src/PyInterp/PyInterp_Request.cxx +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Request.h -// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) - -#include "PyInterp_Request.h" -#include "PyInterp_Utils.h" - -#include - -void PyInterp_Request::process() -{ - safeExecute(); - - bool isSync = IsSync(); - - if ( !isSync ) - myMutex.lock(); - - if ( listener() ) - processEvent( listener() ); - - if ( !isSync ) - myMutex.unlock(); -} - -void PyInterp_Request::safeExecute() -{ - execute(); -} - -void PyInterp_Request::Destroy( PyInterp_Request* request ) -{ - // Lock and unlock the mutex to avoid errors on its deletion - request->myMutex.lock(); - request->myMutex.unlock(); - delete request; -} - -QEvent* PyInterp_Request::createEvent() -{ - return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, this ); -} - -void PyInterp_Request::processEvent( QObject* o ) -{ - if ( !o ) - return; - - QEvent* e = createEvent(); - if ( !e ) - return; - - if ( !IsSync() ) - QCoreApplication::postEvent( o, e ); - else - { - QCoreApplication::sendEvent( o, e ); - delete e; - } -} - -void PyInterp_Request::setListener( QObject* o ) -{ - myMutex.lock(); - myListener = o; - myMutex.unlock(); -} - -void PyInterp_LockRequest::safeExecute() -{ - PyLockWrapper aLock; // Acquire GIL - execute(); -} diff --git a/src/PyInterp/PyInterp_Request.h b/src/PyInterp/PyInterp_Request.h deleted file mode 100644 index cabb3d6b0..000000000 --- a/src/PyInterp/PyInterp_Request.h +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Request.h -// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) - -#ifndef PYINTERP_REQUEST_H -#define PYINTERP_REQUEST_H - -#include "PyInterp.h" -#include "PyInterp_Event.h" - -#include - -class QObject; -class PyInterp_Interp; - -/** - \class PyInterp_Request - \brief Base Python interpreter request; does not not acquire GIL during execution. - */ -class PYINTERP_EXPORT PyInterp_Request -{ - friend class PyInterp_Dispatcher; - -private: - PyInterp_Request(); - PyInterp_Request( const PyInterp_Request& ); - -protected: - // protected destructor - to control deletion of requests - virtual ~PyInterp_Request() {}; - -public: - // Constructor - PyInterp_Request( QObject* listener, bool sync = true ) - : myIsSync( sync ), myListener( listener ) {}; - - // Deletes a request - static void Destroy( PyInterp_Request* ); - - // Returns true if this request should be processed synchronously, - // without putting it to a queue - bool IsSync() const { return myIsSync; } - -protected: - // Performs safe execution of the request - virtual void safeExecute(); - - // Should be redefined in successors, contains actual request code - virtual void execute() = 0; - - // This method can be overridden to customize notification event creation - virtual QEvent* createEvent(); - - virtual void processEvent( QObject* ); - - // Provide access to the listener of this request - QObject* listener() const { return myListener; } - void setListener( QObject* ); - -private: - // Process request, invoked from Dispatcher - void process(); - -private: - QMutex myMutex; - bool myIsSync; - QObject* myListener; -}; - -/** - \class PyInterp_LockRequest - \brief Python interpreter request; automatically acquires GIL during execution. - */ -class PYINTERP_EXPORT PyInterp_LockRequest : public PyInterp_Request -{ -public: - // Constructor - PyInterp_LockRequest( PyInterp_Interp* interp, QObject* listener=0, bool sync=true ) - : PyInterp_Request( listener, sync ), myInterp( interp ) - {} - -protected: - // Get interpreter - PyInterp_Interp* getInterp() const { return myInterp; } - - // Performs safe execution of the request - virtual void safeExecute(); - -private: - PyInterp_Interp* myInterp; -}; - -#endif // PYINTERP_REQUEST_H diff --git a/src/PyInterp/PyInterp_Utils.h b/src/PyInterp/PyInterp_Utils.h deleted file mode 100644 index 8d6ce8c63..000000000 --- a/src/PyInterp/PyInterp_Utils.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : PyInterp_Utils.h -// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON - -#ifndef PYINTERP_UTILS_H -#define PYINTERP_UTILS_H - -#include "PyInterp.h" - -#ifdef _DEBUG_ - #include -#endif - -/** - * \class PyLockWrapper - * \brief Python GIL wrapper. - * - * Utility class wrapping the Python GIL acquisition. This makes use of the high level - * API (PyGILState_Ensure and PyGILState_Release), and is hence compatible with only - * one running Python interpreter (no call to Py_NewInterpreter()). - * When the class is instanciated the lock is acquired. It is released at destruction time. - * Copy construction (and hence assignation) is forbidden. - */ -class PYINTERP_EXPORT PyLockWrapper -{ -public: - /** - * \brief Constructor. Automatically acquires GIL. - */ - PyLockWrapper() - { - _gil_state = PyGILState_Ensure(); - // Save current thread state for later comparison - _state = PyGILState_GetThisThreadState(); - } - - /** - * \brief Destructor. Automatically releases GIL. - */ - ~PyLockWrapper() - { - PyThreadState* _currState = PyGILState_GetThisThreadState(); -#ifdef _DEBUG_ - if (_currState != _state) - { - std::cout << "!!!!!!!!! PyLockWrapper inconsistency - now entering infinite loop for debugging\n"; - while(1); - } -#endif - PyGILState_Release(_gil_state); - } - -private: - PyGILState_STATE _gil_state; - PyThreadState* _state; - - // "Rule of 3" - Forbid usage of copy operator and copy-constructor - PyLockWrapper(const PyLockWrapper & another); - const PyLockWrapper & operator=(const PyLockWrapper & another); -}; - -/** - * \class PyObjWrapper - * \brief Utility class to properly handle the reference counting required on Python objects. - */ -class PYINTERP_EXPORT PyObjWrapper -{ - PyObject* myObject; -public: - PyObjWrapper(PyObject* theObject) : myObject(theObject) {} - PyObjWrapper() : myObject(0) {} - virtual ~PyObjWrapper() { Py_XDECREF(myObject); } - - operator PyObject*() { return myObject; } - PyObject* operator->() { return myObject; } - PyObject* get() { return myObject; } - bool operator!() { return !myObject; } - bool operator==(PyObject* theObject) { return myObject == theObject; } - PyObject** operator&() { return &myObject; } - PyObjWrapper& operator=(PyObjWrapper* theObjWrapper) - { - Py_XDECREF(myObject); - myObject = theObjWrapper->myObject; - return *this; - } -}; - -#endif // PYINTERP_UTILS_H diff --git a/src/SALOME_PYQT/SALOME_PYQT_GUI/CMakeLists.txt b/src/SALOME_PYQT/SALOME_PYQT_GUI/CMakeLists.txt index a9d20317b..ded75b88f 100755 --- a/src/SALOME_PYQT/SALOME_PYQT_GUI/CMakeLists.txt +++ b/src/SALOME_PYQT/SALOME_PYQT_GUI/CMakeLists.txt @@ -35,12 +35,12 @@ INCLUDE_DIRECTORIES( ${OMNIORB_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/src/CAM ${PROJECT_SOURCE_DIR}/src/LightApp - ${PROJECT_SOURCE_DIR}/src/PyInterp ${PROJECT_SOURCE_DIR}/src/Qtx ${PROJECT_SOURCE_DIR}/src/SALOME_PYQT/SALOME_PYQT_GUILight ${PROJECT_SOURCE_DIR}/src/STD ${PROJECT_SOURCE_DIR}/src/SUIT ${PROJECT_SOURCE_DIR}/src/SalomeApp + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src ) # additional preprocessor / compiler flags @@ -85,7 +85,6 @@ SET(_other_SOURCES SALOME_PYQT_Module.cxx) SET(SalomePyQtGUI_SOURCES ${_other_SOURCES} ${_moc_SOURCES}) # --- rules --- - ADD_LIBRARY(SalomePyQtGUI ${SalomePyQtGUI_SOURCES}) TARGET_LINK_LIBRARIES(SalomePyQtGUI ${_link_LIBRARIES}) INSTALL(TARGETS SalomePyQtGUI DESTINATION ${SALOME_INSTALL_LIBS}) diff --git a/src/SALOME_PYQT/SALOME_PYQT_GUILight/CMakeLists.txt b/src/SALOME_PYQT/SALOME_PYQT_GUILight/CMakeLists.txt index ec0226395..5c452c83f 100755 --- a/src/SALOME_PYQT/SALOME_PYQT_GUILight/CMakeLists.txt +++ b/src/SALOME_PYQT/SALOME_PYQT_GUILight/CMakeLists.txt @@ -39,13 +39,13 @@ INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src/LightApp ${PROJECT_SOURCE_DIR}/src/OCCViewer ${PROJECT_SOURCE_DIR}/src/Plot2d - ${PROJECT_SOURCE_DIR}/src/PyConsole - ${PROJECT_SOURCE_DIR}/src/PyInterp ${PROJECT_SOURCE_DIR}/src/Qtx ${PROJECT_SOURCE_DIR}/src/STD ${PROJECT_SOURCE_DIR}/src/SUIT ${PROJECT_SOURCE_DIR}/src/SUITApp ${PROJECT_SOURCE_DIR}/src/ObjBrowser + ${PROJECT_SOURCE_DIR}/tools/PyConsole/src + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src ) # additional preprocessor / compiler flags diff --git a/src/SALOME_PYQT/SalomePyQt/CMakeLists.txt b/src/SALOME_PYQT/SalomePyQt/CMakeLists.txt index 3b2114600..3756b7ce8 100755 --- a/src/SALOME_PYQT/SalomePyQt/CMakeLists.txt +++ b/src/SALOME_PYQT/SalomePyQt/CMakeLists.txt @@ -40,12 +40,12 @@ INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src/LogWindow ${PROJECT_SOURCE_DIR}/src/OBJECT ${PROJECT_SOURCE_DIR}/src/ObjBrowser - ${PROJECT_SOURCE_DIR}/src/PyInterp - ${PROJECT_SOURCE_DIR}/src/PyConsole ${PROJECT_SOURCE_DIR}/src/Qtx ${PROJECT_SOURCE_DIR}/src/SALOME_PYQT/SALOME_PYQT_GUILight ${PROJECT_SOURCE_DIR}/src/STD ${PROJECT_SOURCE_DIR}/src/SUIT + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src + ${PROJECT_SOURCE_DIR}/tools/PyConsole/src ) IF(SALOME_USE_OCCVIEWER) diff --git a/src/SalomeApp/CMakeLists.txt b/src/SalomeApp/CMakeLists.txt index bf2fd3911..d65ba0b38 100755 --- a/src/SalomeApp/CMakeLists.txt +++ b/src/SalomeApp/CMakeLists.txt @@ -56,8 +56,8 @@ INCLUDE_DIRECTORIES( IF(SALOME_USE_PYCONSOLE) INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_DIRS} - ${PROJECT_SOURCE_DIR}/src/PyConsole - ${PROJECT_SOURCE_DIR}/src/PyInterp + ${PROJECT_SOURCE_DIR}/tools/PyConsole/src + ${PROJECT_SOURCE_DIR}/tools/PyInterp/src ) ENDIF() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f038c35b9..1ca8dc33b 100755 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -22,3 +22,18 @@ ADD_SUBDIRECTORY(dlgfactory) IF(SALOME_USE_VTKVIEWER) ADD_SUBDIRECTORY(vtkEDFOverloads) ENDIF() + +## +# Python-based packages, part 1 (generic) +## +IF(SALOME_USE_PYCONSOLE) + # Include sub-project PyConsole: + SET(TOOLS_EXPORT_NAME ${PROJECT_NAME}) + SET(PYCONSOLE_INSTALL_LIBS "${SALOME_INSTALL_LIBS}") + SET(PYCONSOLE_INSTALL_HEADERS "${SALOME_INSTALL_HEADERS}") + SET(PYCONSOLE_INSTALL_RES "${SALOME_GUI_INSTALL_RES_DATA}") + OPTION(PYCONSOLE_BUILD_WITH_QT5 "Build PYCONSOLE with Qt 5" ${SALOME_BUILD_WITH_QT5}) + + ADD_SUBDIRECTORY(../tools/PyConsole) # will bring in PyInterp automatically +ENDIF(SALOME_USE_PYCONSOLE) + diff --git a/tools/PyConsole/CMakeLists.txt b/tools/PyConsole/CMakeLists.txt new file mode 100644 index 000000000..136b81e9b --- /dev/null +++ b/tools/PyConsole/CMakeLists.txt @@ -0,0 +1,90 @@ +# Copyright (C) 2015-2016 CEA/DEN, EDF R&D +# +# 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 +# + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12 FATAL_ERROR) +PROJECT(PyConsole C CXX) + +# Versioning +# =========== +# Project name, upper case +STRING(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UC) + +################### +# To be changed once externalized CMake procedure: +SET(KERNEL_ROOT_DIR $ENV{KERNEL_ROOT_DIR} CACHE PATH "Path to the Salome KERNEL") +IF(EXISTS ${KERNEL_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${KERNEL_ROOT_DIR}/salome_adm/cmake_files") + INCLUDE(SalomeMacros) +ELSE(EXISTS ${KERNEL_ROOT_DIR}) + MESSAGE(FATAL_ERROR "We absolutely need a Salome KERNEL, please define KERNEL_ROOT_DIR") +ENDIF(EXISTS ${KERNEL_ROOT_DIR}) +# From GUI - again to be changed once externalized: +LIST(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../adm_local/cmake_files") +################### + +# Platform setup +# ============== +INCLUDE(SalomeSetupPlatform) + +# Options +# ======= +OPTION(PYCONSOLE_BUILD_WITH_QT5 "Build PYCONSOLE with Qt 5" ON) + +# +# Set list of prerequisites +# ========================= + +FIND_PACKAGE(SalomePythonInterp REQUIRED) +FIND_PACKAGE(SalomePythonLibs REQUIRED) + +# Qt +IF(NOT PYCONSOLE_BUILD_WITH_QT5) + FIND_PACKAGE(SalomeQt4 REQUIRED) +ELSE() + FIND_PACKAGE(SalomeQt5 REQUIRED) +ENDIF() + +# Detection report +SALOME_PACKAGE_REPORT_AND_CHECK() + +# Directories +# +# Directories have to be given after prerequisites (to be able to use +# Python version string for example). +# =========== +SET(PYCONSOLE_INSTALL_LIBS lib CACHE PATH "Install path: PyConsole libs") +SET(PYCONSOLE_INSTALL_HEADERS include CACHE PATH "Install path: PyConsole headers") + +SET(PYCONSOLE_INSTALL_RES share/resources CACHE PATH "Install path: PyConsole resources") + +# Tool dependencies +# ================= +# PyConsole depends on PyInterp: +SET(PYINTERP_INSTALL_LIBS "${PYCONSOLE_INSTALL_LIBS}") +SET(PYINTERP_INSTALL_HEADERS "${PYCONSOLE_INSTALL_HEADERS}") +SET(PYINTERP_INSTALL_RES "${PYCONSOLE_INSTALL_RES}") +OPTION(PYINTERP_BUILD_WITH_QT5 "Build PYCONSOLE with Qt 5" ${PYCONSOLE_BUILD_WITH_QT5}) + +ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/../PyInterp ${PROJECT_BINARY_DIR}/PyInterp) +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../PyInterp/src) + +# Sources +# ======== +ADD_SUBDIRECTORY(src) + diff --git a/tools/PyConsole/src/CMakeLists.txt b/tools/PyConsole/src/CMakeLists.txt new file mode 100755 index 000000000..113f808b3 --- /dev/null +++ b/tools/PyConsole/src/CMakeLists.txt @@ -0,0 +1,89 @@ +# Copyright (C) 2012-2016 CEA/DEN, EDF R&D, 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 +# + +INCLUDE(UseQtExt) + +# --- options --- + +# additional include directories +INCLUDE_DIRECTORIES( + ${QT_INCLUDES} + ${PYTHON_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}/src/PyInterp +) + +# additional preprocessor / compiler flags +ADD_DEFINITIONS(${QT_DEFINITIONS} ${PYTHON_DEFINITIONS}) + +# libraries to link to +SET(_link_LIBRARIES ${QT_LIBRARIES} ${PYTHON_LIBRARIES} PyInterp) + +# --- headers --- + +# header files / to be processed by moc +SET(_moc_HEADERS + PyConsole_Console.h + PyConsole_Editor.h +) + +# header files / no moc processing +SET(_other_HEADERS + PyConsole.h + PyConsole_Event.h + PyConsole_Interp.h + PyConsole_Request.h +) + +# header files / to install +SET(PyConsole_HEADERS ${_moc_HEADERS} ${_other_HEADERS}) + +# --- resources --- + +# resource files / to be processed by lrelease +SET(_ts_RESOURCES + resources/PyConsole_msg_en.ts + resources/PyConsole_msg_fr.ts + resources/PyConsole_msg_ja.ts +) + +# --- sources --- + +# sources / moc wrappings +QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS}) + +# sources / static +SET(_other_SOURCES + PyConsole_Console.cxx + PyConsole_Event.cxx + PyConsole_Interp.cxx + PyConsole_Request.cxx + PyConsole_Editor.cxx +) + +# sources / to compile +SET(PyConsole_SOURCES ${_other_SOURCES} ${_moc_SOURCES}) + +# --- rules --- + +ADD_LIBRARY(PyConsole ${PyConsole_SOURCES}) +TARGET_LINK_LIBRARIES(PyConsole ${_link_LIBRARIES}) +INSTALL(TARGETS PyConsole EXPORT ${TOOLS_EXPORT_NAME}TargetGroup DESTINATION ${PYCONSOLE_INSTALL_LIBS}) + +INSTALL(FILES ${PyConsole_HEADERS} DESTINATION ${PYCONSOLE_INSTALL_HEADERS}) +QT_INSTALL_TS_RESOURCES("${_ts_RESOURCES}" "${PYCONSOLE_INSTALL_RES}") diff --git a/tools/PyConsole/src/PyConsole.h b/tools/PyConsole/src/PyConsole.h new file mode 100644 index 000000000..4c72bdfb9 --- /dev/null +++ b/tools/PyConsole/src/PyConsole.h @@ -0,0 +1,47 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +#if !defined ( PYCONSOLE_H ) +#define PYCONSOLE_H + +// ======================================================== +// set dllexport type for Win platform +#ifdef WIN32 +# if defined PYCONSOLE_EXPORTS || defined PyConsole_EXPORTS +# define PYCONSOLE_EXPORT __declspec(dllexport) +# else +# define PYCONSOLE_EXPORT __declspec(dllimport) +# endif +#else // WIN32 +# define PYCONSOLE_EXPORT +#endif // WIN32 + +// ======================================================== +// avoid warning messages +#ifdef WIN32 +#pragma warning (disable : 4786) +#pragma warning (disable : 4251) +#endif + +#endif // PYCONSOLE_H diff --git a/tools/PyConsole/src/PyConsole_Console.cxx b/tools/PyConsole/src/PyConsole_Console.cxx new file mode 100644 index 000000000..3aaa52ccb --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Console.cxx @@ -0,0 +1,383 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Console.cxx +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +#include "PyConsole_Console.h" +#include "PyConsole_Interp.h" +#include "PyConsole_Editor.h" + +#include +#include +#include +#include +#include +#include + +/*! + \class PyConsole_Console + \brief Python console widget. + + To create a Python console, just use default contstructor, specifying only a parent widget: + \code + PyConsole_Console c(myWindow); + \endcode + + This will create a console with default editor and interpreter. + + To use custom editor and/or interpreter class with the console, you can use additional parameter + of the constructor; in this case you have to ensure that Python interpeter is initialized properly: + \code + PyConsole_Interp* interp = new PyConsole_Interp(); + interp->initialize(); + PyConsole_Console c(myWindow, new MyEditor(interp)); + \endcode +*/ + +/*! + \brief Constructor. + + Creates new python console widget. + \param parent parent widget + \param interp python interpreter +*/ +PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Editor* editor ) +: QWidget( parent ) +{ + // initialize Python interpretator + PyConsole_Interp* interp = editor ? editor->getInterp() : new PyConsole_Interp(); + interp->initialize(); + + // create editor console + QVBoxLayout* lay = new QVBoxLayout( this ); + lay->setMargin( 0 ); + myEditor = editor ? editor : new PyConsole_Editor( interp, this ); + myEditor->setContextMenuPolicy( Qt::NoContextMenu ); + lay->addWidget( myEditor ); + + // force synchronous mode + QString synchronous = qgetenv( "PYTHON_CONSOLE_SYNC" ); + if ( !synchronous.isEmpty() && synchronous.toInt() > 0 ) + setIsSync( true ); + + // create actions + createActions(); +} + +/*! + \brief Destructor. +*/ +PyConsole_Console::~PyConsole_Console() +{ +} + +/*! + \brief Get Python interpreter + \return pointer to Python interpreter +*/ +PyConsole_Interp* PyConsole_Console::getInterp() const +{ + return myEditor ? myEditor->getInterp() : 0; +} + +/*! + \brief Execute python command in the interpreter. + \param command string with command and arguments +*/ +void PyConsole_Console::exec( const QString& command ) +{ + if ( myEditor ) + myEditor->exec( command ); +} + +/*! + \brief Execute python command in the interpreter + and wait until it is finished. + + Block execution of main application until the python command is executed. + \param command string with command and arguments +*/ +void PyConsole_Console::execAndWait( const QString& command ) +{ + if ( myEditor ) + myEditor->execAndWait( command ); +} + +/*! + \brief Get synchronous mode flag value. + + \sa setIsSync() + \return \c true if python console works in synchronous mode +*/ +bool PyConsole_Console::isSync() const +{ + return myEditor ? myEditor->isSync() : false; +} + +/*! + \brief Set synchronous mode flag value. + + In synhronous mode the Python commands are executed in the GUI thread + and the GUI is blocked until the command is finished. In the asynchronous + mode each Python command is executed in the separate thread that does not + block the main GUI loop. + + \param on synhronous mode flag +*/ +void PyConsole_Console::setIsSync( const bool on ) +{ + if ( myEditor ) + myEditor->setIsSync( on ); +} + +/*! + \brief Get suppress output flag value. + + \sa setIsSuppressOutput() + \return \c true if python console output is suppressed. +*/ +bool PyConsole_Console::isSuppressOutput() const +{ + return myEditor ? myEditor->isSuppressOutput() : false; +} + +/*! + \brief Set suppress output flag value. + + In case if suppress output flag is \c true, the python + console output suppressed. + + \param on suppress output flag +*/ +void PyConsole_Console::setIsSuppressOutput( const bool on ) +{ + if ( myEditor ) + myEditor->setIsSuppressOutput( on ); +} + +/*! + \brief Get 'show banner' flag value. + + \sa setIsShowBanner() + \return \c true if python console shows banner +*/ +bool PyConsole_Console::isShowBanner() const +{ + return myEditor ? myEditor->isShowBanner() : false; +} + +/*! + \brief Set 'show banner' flag value. + + The banner is shown in the top of the python console window. + + \sa isShowBanner() + \param on 'show banner' flag +*/ +void PyConsole_Console::setIsShowBanner( const bool on ) +{ + if ( myEditor ) + myEditor->setIsShowBanner( on ); +} + +/*! + \brief Returns \c true if auto-completion feature is switched on + or \c false otherwise + \sa setAutoCompletion() +*/ +bool PyConsole_Console::autoCompletion() const +{ + return myEditor ? myEditor->autoCompletion() : false; +} + +/*! + \brief Switch on/off commands auto-completion feature + \sa autoCompletion() +*/ +void PyConsole_Console::setAutoCompletion( const bool on ) +{ + if ( myEditor ) + myEditor->setAutoCompletion( on ); +} + +/*! + \brief Change the python console's font. + \param f new font +*/ +void PyConsole_Console::setFont( const QFont& f ) +{ + if ( myEditor ) + myEditor->setFont( f ); +} + +/*! + \brief Get python console font. + \return current python console font +*/ +QFont PyConsole_Console::font() const +{ + return myEditor ? myEditor->font() : QFont(); +} + +/*! + \brief Set actions to be visible in the context popup menu. + + Actions, which IDs are set in \a flags parameter, will be shown in the + context popup menu. Other actions will not be shown. + + \param flags ORed together actions flags +*/ +void PyConsole_Console::setMenuActions( const int flags ) +{ + myActions[CopyId]->setVisible( flags & CopyId ); + myActions[PasteId]->setVisible( flags & PasteId ); + myActions[ClearId]->setVisible( flags & ClearId ); + myActions[SelectAllId]->setVisible( flags & SelectAllId ); + myActions[DumpCommandsId]->setVisible( flags & DumpCommandsId ); + myActions[StartLogId]->setVisible( flags & StartLogId ); + myActions[StopLogId]->setVisible( flags & StopLogId ); +} + +/*! + \brief Get menu actions which are currently visible in the context popup menu. + \return ORed together actions flags + \sa setMenuActions() +*/ +int PyConsole_Console::menuActions() const +{ + int ret = 0; + ret = ret | ( myActions[CopyId]->isVisible() ? CopyId : 0 ); + ret = ret | ( myActions[PasteId]->isVisible() ? PasteId : 0 ); + ret = ret | ( myActions[ClearId]->isVisible() ? ClearId : 0 ); + ret = ret | ( myActions[SelectAllId]->isVisible() ? SelectAllId : 0 ); + ret = ret | ( myActions[DumpCommandsId]->isVisible() ? DumpCommandsId : 0 ); + ret = ret | ( myActions[StartLogId]->isVisible() ? StartLogId : 0 ); + ret = ret | ( myActions[StopLogId]->isVisible() ? StopLogId : 0 ); + return ret; +} + +/*! + \brief Create menu actions. + + Create context popup menu actions. +*/ +void PyConsole_Console::createActions() +{ + QAction* a = new QAction( tr( "EDIT_COPY_CMD" ), this ); + a->setStatusTip( tr( "EDIT_COPY_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); + myActions.insert( CopyId, a ); + + a = new QAction( tr( "EDIT_PASTE_CMD" ), this ); + a->setStatusTip( tr( "EDIT_PASTE_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); + myActions.insert( PasteId, a ); + + a = new QAction( tr( "EDIT_CLEAR_CMD" ), this ); + a->setStatusTip( tr( "EDIT_CLEAR_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( clear() ) ); + myActions.insert( ClearId, a ); + + a = new QAction( tr( "EDIT_SELECTALL_CMD" ), this ); + a->setStatusTip( tr( "EDIT_SELECTALL_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); + myActions.insert( SelectAllId, a ); + + a = new QAction( tr( "EDIT_DUMPCOMMANDS_CMD" ), this ); + a->setStatusTip( tr( "EDIT_DUMPCOMMANDS_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( dump() ) ); + myActions.insert( DumpCommandsId, a ); + + a = new QAction( tr( "EDIT_STARTLOG_CMD" ), this ); + a->setStatusTip( tr( "EDIT_STARTLOG_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( startLog() ) ); + myActions.insert( StartLogId, a ); + + a = new QAction( tr( "EDIT_STOPLOG_CMD" ), this ); + a->setStatusTip( tr( "EDIT_STOPLOG_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( stopLog() ) ); + myActions.insert( StopLogId, a ); +} + +/*! + \brief Update menu actions. + + Update context popup menu action state. +*/ +void PyConsole_Console::updateActions() +{ + myActions[CopyId]->setEnabled( myEditor && myEditor->textCursor().hasSelection() ); + myActions[PasteId]->setEnabled( myEditor && !myEditor->isReadOnly() && !QApplication::clipboard()->text().isEmpty() ); + myActions[SelectAllId]->setEnabled( myEditor && !myEditor->document()->isEmpty() ); +} + +/*! + \brief Start python trace logging + \param fileName the path to the log file +*/ +void PyConsole_Console::startLog( const QString& fileName ) +{ + if ( myEditor ) + myEditor->startLog( fileName ); +} + +/*! + \brief Stop python trace logging +*/ +void PyConsole_Console::stopLog() +{ + if ( myEditor ) + myEditor->stopLog(); +} + +/*! + \brief Process context popup menu request + + Show the context popup menu. + + \param event context popup menu event +*/ +void PyConsole_Console::contextMenuEvent( QContextMenuEvent* event ) +{ + if ( !myEditor || myEditor->isReadOnly() ) + return; + + QMenu* menu = new QMenu( this ); + + menu->addAction( myActions[CopyId] ); + menu->addAction( myActions[PasteId] ); + menu->addAction( myActions[ClearId] ); + menu->addSeparator(); + menu->addAction( myActions[SelectAllId] ); + menu->addSeparator(); + menu->addAction( myActions[DumpCommandsId] ); + if ( !myEditor->isLogging() ) + menu->addAction( myActions[StartLogId] ); + else + menu->addAction( myActions[StopLogId] ); + + updateActions(); + + menu->exec( event->globalPos()); + + delete menu; +} diff --git a/tools/PyConsole/src/PyConsole_Console.h b/tools/PyConsole/src/PyConsole_Console.h new file mode 100644 index 000000000..e0b111e53 --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Console.h @@ -0,0 +1,97 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Console.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +#ifndef PYCONSOLE_CONSOLE_H +#define PYCONSOLE_CONSOLE_H + +#include "PyConsole.h" + +#include +#include + +class QMenu; + +class PyConsole_Interp; +class PyConsole_Editor; + +class PYCONSOLE_EXPORT PyConsole_Console : public QWidget +{ + Q_OBJECT + +public: + //! Context popup menu actions flags + enum + { + CopyId = 0x01, //!< "Copy" menu action + PasteId = 0x02, //!< "Paste" menu action + ClearId = 0x04, //!< "Clear" menu action + SelectAllId = 0x08, //!< "Select All" menu action + DumpCommandsId = 0x10, //!< "DumpCommands" menu action + StartLogId = 0x20, //!< "Start log" menu action + StopLogId = 0x40, //!< "Stop log" menu action + All = 0xFF, //!< all menu actions + }; + +public: + PyConsole_Console( QWidget* parent, PyConsole_Editor* editor = 0 ); + virtual ~PyConsole_Console(); + + PyConsole_Interp* getInterp() const; + + QFont font() const; + virtual void setFont( const QFont& ); + + bool isSync() const; + void setIsSync( const bool ); + + bool isSuppressOutput() const; + void setIsSuppressOutput( const bool ); + + bool isShowBanner() const; + void setIsShowBanner( const bool ); + + void setAutoCompletion( bool ); + bool autoCompletion() const; + + void exec( const QString& ); + void execAndWait( const QString& ); + + void setMenuActions( const int ); + int menuActions() const; + + void startLog( const QString& ); + void stopLog(); + +protected: + void createActions(); + void updateActions(); + + virtual void contextMenuEvent( QContextMenuEvent* ); + +protected: + PyConsole_Editor* myEditor; //!< python console editor widget + QMap myActions; //!< menu actions list +}; + +#endif // PYCONSOLE_CONSOLE_H diff --git a/tools/PyConsole/src/PyConsole_Editor.cxx b/tools/PyConsole/src/PyConsole_Editor.cxx new file mode 100644 index 000000000..7d540f62f --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Editor.cxx @@ -0,0 +1,1700 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Editor.cxx +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +/*! + \class PyConsole_Editor + \brief Python command line interpreter front-end GUI widget. + + This class provides simple GUI interface to the Python interpreter, including basic + navigation operations, executing commands (both interactively and programmatically), + copy-paste operations, history of the commands and so on. + + Here below is the shortcut keyboard boundings used for navigation and other operations: + - : execute current command + - : clear current command + - : clear current command + - : previous command in the history + - : move cursor one row up with selection + - : move cursor one row up without selection + - : move cursor one row up with selection + - : next command in the history + - : move cursor one row down with selection + - : move cursor one row down without selection + - : move cursor one row down with selection + - : move one symbol left without selection + - : move one symbol left with selection + - : move one word left without selection + - : move one word left with selection + - : move one symbol right without selection + - : move one symbol right with selection + - : move one word right without selection + - : move one word right with selection + - : first command in the history + - : move one page up with selection + - : move one page up without selection + - : scroll one page up + - : last command in the history + - : move one page down with selection + - : move one page down without selection + - : scroll one page down + - : move to the beginning of the line without selection + - : move to the beginning of the line with selection + - : move to the very first symbol without selection + - : move to the very first symbol with selection + - : move to the end of the line without selection + - : move to the end of the line with selection + - : move to the very last symbol without selection + - : move to the very last symbol with selection + - : delete symbol before the cursor + / remove selected text and put it to the clipboard (cut) + - : delete previous word + / remove selected text and put it to the clipboard (cut) + - : delete text from the cursor to the beginning of the line + / remove selected text and put it to the clipboard (cut) + - : delete symbol after the cursor + / remove selected text and put it to the clipboard (cut) + - : delete next word + / remove selected text and put it to the clipboard (cut) + - : delete text from the cursor to the end of the line + / remove selected text and put it to the clipboard (cut) + - : copy + - : paste + - : paste + - : copy + - : cut + - : paste + - : performs auto-completion + - : undoes auto-completion +*/ + +#include "PyConsole_Editor.h" +#include "PyConsole_Interp.h" +#include "PyConsole_Event.h" +#include "PyInterp_Dispatcher.h" +#include "PyConsole_Request.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//VSR: uncomment below macro to support unicode text properly in SALOME +// current commented out due to regressions +//#define PAL22528_UNICODE + +namespace +{ + QString fromUtf8( const char* txt ) + { +#ifdef PAL22528_UNICODE + return QString::fromUtf8( txt ); +#else + return QString( txt ); +#endif + } +} + +static QString READY_PROMPT = ">>> "; +static QString DOTS_PROMPT = "... "; + +void staticCallbackStdout( void* data, char* c ) +{ + if(!((PyConsole_Editor*)data)->isSuppressOutput()) { + PyConsole_Editor* e = (PyConsole_Editor*)data; + QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), false ) ); + } +} + +void staticCallbackStderr( void* data, char* c ) +{ + if(!((PyConsole_Editor*)data)->isSuppressOutput()) { + PyConsole_Editor* e = (PyConsole_Editor*)data; + QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), true ) ); + } +} + +/*! + \brief Constructor. + + Creates python editor window. + \param theInterp python interper + \param theParent parent widget +*/ +PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, + QWidget* theParent ) +: QTextEdit( theParent ), + myInterp( theInterp ), + myCmdInHistory( -1 ), + myEventLoop( 0 ), + myShowBanner( true ), + myIsSync( true ), + myIsSuppressOutput( false ), + myMultiLinePaste( false ), + myAutoCompletion( false ), + myTabMode( false ), + myComplCursorPos( -1 ) +{ + setFont( QFont( "Courier", 11 ) ); // default font + setUndoRedoEnabled( false ); + + myPrompt = READY_PROMPT; + setLineWrapMode( QTextEdit::WidgetWidth ); + setWordWrapMode( QTextOption::WrapAnywhere ); + setAcceptRichText( false ); + + // set callbacks to interpeter + myInterp->setvoutcb( staticCallbackStdout, this ); + myInterp->setverrcb( staticCallbackStderr, this ); + // print banner + if ( isShowBanner() ) + addText( banner() ); + // clear command buffer + myCommandBuffer.truncate(0); + // unset read-only state + setReadOnly( false ); + // unset history browsing mode + myCmdInHistory = -1; + // add prompt + addText( myPrompt ); + // unset busy cursor + viewport()->unsetCursor(); +} + +/*! + \brief Destructor. +*/ +PyConsole_Editor::~PyConsole_Editor() +{ + myInterp = 0; +} + +/*! + \brief Get Python interpreter +*/ +PyConsole_Interp* PyConsole_Editor::getInterp() const +{ + return myInterp; +} + +/*! + \brief Get synchronous mode flag value. + + \sa setIsSync() + \return \c true if python console works in synchronous mode +*/ +bool PyConsole_Editor::isSync() const +{ + return myIsSync; +} + +/*! + \brief Set synchronous mode flag value. + + In synhronous mode the Python commands are executed in the GUI thread + and the GUI is blocked until the command is finished. In the asynchronous + mode each Python command is executed in the separate thread that does not + block the main GUI loop. + + \param on synhronous mode flag +*/ +void PyConsole_Editor::setIsSync( const bool on ) +{ + myIsSync = on; +} + +/*! + \brief Get suppress output flag value. + + \sa setIsSuppressOutput() + \return \c true if python console output is suppressed. +*/ +bool PyConsole_Editor::isSuppressOutput() const +{ + return myIsSuppressOutput; +} + +/*! + \brief Set suppress output flag value. + + In case if suppress output flag is \c true, the python + console output suppressed. + + \param on suppress output flag +*/ +void PyConsole_Editor::setIsSuppressOutput( const bool on ) +{ + myIsSuppressOutput = on; +} + +/*! + \brief Get 'show banner' flag value. + + \sa setIsShowBanner() + \return \c true if python console shows banner +*/ +bool PyConsole_Editor::isShowBanner() const +{ + return myShowBanner; +} + +/*! + \brief Set 'show banner' flag value. + + The banner is shown in the top of the python console window. + + \sa isShowBanner() + \param on 'show banner' flag +*/ +void PyConsole_Editor::setIsShowBanner( const bool on ) +{ + if ( myShowBanner != on ) { + myShowBanner = on; + clear(); + } +} + +/*! + \brief Switch on/off commands auto-completion feature + \sa autoCompletion() +*/ +void PyConsole_Editor::setAutoCompletion( bool on ) +{ + myAutoCompletion = on; + document()->setUndoRedoEnabled( myAutoCompletion ); +} + +/*! + \brief Returns \c true if auto-completion feature is switched on + or \c false otherwise + \sa setAutoCompletion() +*/ +bool PyConsole_Editor::autoCompletion() const +{ + return myAutoCompletion; +} + +/*! + \brief Check if trace logging is switched on. + + \sa startLog(), stopLog() + \return \c true if trace logging is switched on +*/ +bool PyConsole_Editor::isLogging() const +{ + return !myLogFile.isEmpty(); +} + +/*! + \brief Get size hint for the Python console window + \return size hint value +*/ +QSize PyConsole_Editor::sizeHint() const +{ + QFontMetrics fm( font() ); + int nbLines = ( isShowBanner() ? banner().split("\n").count() : 0 ) + 1; + QSize s(100, fm.lineSpacing()*nbLines); + return s; +} + +/*! + \brief Put the string \a str to the python editor. + \param str string to be put in the command line of the editor + \param newBlock if \c true, then the string is printed on a new line + \param isError if \c true, the text is printed in dark red +*/ +void PyConsole_Editor::addText( const QString& str, + const bool newBlock, + const bool isError ) +{ + QTextCursor aCursor = textCursor(); + QTextCharFormat cf; + + moveCursor( QTextCursor::End ); + if ( newBlock ) + aCursor.insertBlock(); + if ( isError ) + cf.setForeground( QBrush( Qt::red ) ); + else + cf.setForeground( QBrush( Qt::black ) ); + aCursor.insertText( str, cf ); + moveCursor( QTextCursor::End ); + ensureCursorVisible(); +} + +/*! + \brief Convenient method for executing a Python command, + as if the user typed it manually. + \param command python command to be executed + + !!! WARNING: doesn't work properly with multi-line commands. !!! +*/ +void PyConsole_Editor::exec( const QString& command ) +{ + if ( isReadOnly() ) { + // some interactive command is being executed in this editor... + // shedule the command to the queue + myQueue.push_back( command ); + return; + } + + // remove last line + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + + // set "ready" prompt + myPrompt = READY_PROMPT; + + // clear command buffer + myCommandBuffer.truncate( 0 ); + + // unset history browsing mode + myCmdInHistory = -1; + + // print command line by line + QString cmd = command; + if ( !cmd.endsWith( "\n" ) ) cmd += "\n"; + QStringList lines = command.split( "\n" ); + for ( int i = 0; i < lines.size(); i++ ) { + if ( !lines[i].trimmed().isEmpty() ) + myHistory.push_back( lines[i] ); + addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 ); + putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) ); + } + + // IPAL20182 + addText( "", true ); + + // set read-only mode + setReadOnly( true ); + + // set busy cursor + setCursor( Qt::BusyCursor ); + + // post a request to execute Python command; + // editor will be informed via a custom event that execution has been completed + PyInterp_Dispatcher::Get()->Exec( createCmdRequest( cmd ) ); +} + +/*! + \brief Create request to the python dispatcher for the command execution. + \param command python command to be executed + */ +PyInterp_Request* PyConsole_Editor::createCmdRequest( const QString& command ) +{ + return new PyConsole_ExecCommand( myInterp, command, this, isSync() ); +} + +/*! + \brief Create the Python request that will be posted to the interpreter to + get the completions. + \param input line entered by the user at the time was pressed + \return completion command + \sa CompletionCommand +*/ +PyInterp_Request* PyConsole_Editor::createTabRequest( const QString& input ) +{ + // valid separators + static QStringList separators; + if ( separators.isEmpty() ) { + separators << " " << "(" << "[" << "+" << "-" << "*" << "/" << ";" << "^" << "="; + } + + // parse input to extract on what part the dir() has to be executed + + QString input2( input ); + + // split up to the last syntaxical separator + int lastSp = -1; + foreach ( QString separator, separators ) { + int j = input2.lastIndexOf( separator ); + if ( j > lastSp ) + lastSp = j; + } + if ( lastSp >= 0 ) + input2 = input.mid( lastSp + 1 ); + + // detect a qualified name (with a point) + int lastPt = input2.lastIndexOf( "." ); + + if ( lastPt != -1 ) { + // split the 2 surrounding parts of the qualified name + myComplBeforePoint = input2.left( lastPt ); + myComplAfterPoint = input2.mid( lastPt+1 ); + } + else { + // no point found - do a global matching + // (the following will call dir() with an empty string) + myComplAfterPoint = input2; + myComplBeforePoint = ""; + } + + return new PyConsole_CompletionCommand( myInterp, myComplBeforePoint, + myComplAfterPoint, this, isSync() ); +} + +/*! + \brief Execute command in the python interpreter + and wait until it is finished. + + \param command python command to be executed + */ +void PyConsole_Editor::execAndWait( const QString& command ) +{ + // already running ? + if ( myEventLoop ) + return; + + // create new event loop + bool sync = isSync(); + if ( !sync ) { + myEventLoop = new QEventLoop( this ); + } + + // execute command + exec( command ); + + if ( !sync ) { + // run event loop + myEventLoop->exec(); + // delete event loop after command is processed + delete myEventLoop; + myEventLoop = 0; + } +} + +/*! + \brief Process key press event. + + Execute the command entered by the user. +*/ +void PyConsole_Editor::handleReturn() +{ + // Position cursor at the end + QTextCursor aCursor = textCursor(); + aCursor.movePosition( QTextCursor::End ); + setTextCursor( aCursor ); + + // get last line + QTextBlock par = document()->end().previous(); + if ( !par.isValid() ) return; + + // get command + QString cmd = par.text().remove( 0, promptSize() ); + + // extend the command buffer with the current command + myCommandBuffer.append( cmd ); + + // add command to the history + if ( !cmd.trimmed().isEmpty() ) + myHistory.push_back( cmd ); + putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) ); + + // IPAL19397 + addText( "", true ); + + // set read-only mode + setReadOnly( true ); + + // set busy cursor + setCursor( Qt::BusyCursor ); + + // post a request to execute Python command; + // editor will be informed via a custom event that execution has been completed + PyInterp_Dispatcher::Get()->Exec( createCmdRequest( myCommandBuffer ) ); +} + +/*! + \brief Process key press event. + + Perform auto-completion of the currently entered command, if this feature is enabled +*/ +void PyConsole_Editor::handleTab() +{ + if ( !autoCompletion() ) + return; // auto-completion feature is disabled + + if ( myTabMode ) + return; // already in tab mode + + QTextCursor aCursor = textCursor(); + + // move cursor to the end of input + aCursor.movePosition( QTextCursor::End ); + setTextCursor( aCursor ); + + // save cursor position if needed + if ( myComplCursorPos == -1 ) + myComplCursorPos = textCursor().position(); + + // get last line + QTextBlock par = document()->end().previous(); + if ( !par.isValid() ) return; // empty line + + // switch to completion mode + myTabMode = true; + + // get currently entered command + QString cmd = par.text().mid( promptSize() ); + + // post completion request + // editor will be informed that completion has been done via a custom event + PyInterp_Dispatcher::Get()->Exec( createTabRequest( cmd ) ); +} + +/*! + \brief Process key press event. + + Undoe last auto-completion +*/ +void PyConsole_Editor::handleBackTab() +{ + if ( !autoCompletion() ) + return; // auto-completion feature is disabled + + QTextCursor aCursor = textCursor(); + + if ( myComplCursorPos == -1 ) + return; // invalid cursor position + + // ensure cursor is at the end of command line + aCursor.setPosition( myComplCursorPos ); + aCursor.movePosition( QTextCursor::EndOfLine ); + //setCursor( aCursor ); + + // delete last completed text + int i = aCursor.position() - myComplCursorPos; + aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, i ); + aCursor.removeSelectedText(); + myComplCursorPos = -1; +} + +/*! + \brief Process drop event. + + Paste dragged text. + \param event drop event +*/ +void PyConsole_Editor::dropEvent( QDropEvent* event ) +{ + // get the initial drop position + QPoint pos = event->pos(); + QTextCursor aCursor = cursorForPosition( event->pos() ); + + // if the position is not in the last line move it to the end of the command line + if ( aCursor.position() < document()->end().previous().position() + promptSize() ) { + moveCursor( QTextCursor::End ); + pos = cursorRect().center(); + } + + // create new drop event and use it instead of the original + QDropEvent de( pos, + event->possibleActions(), + event->mimeData(), + event->mouseButtons(), + event->keyboardModifiers(), + event->type() ); + QTextEdit::dropEvent( &de ); + + // accept the original event + event->acceptProposedAction(); +} + +/*! + \brief Process mouse press event + + Clear the completion when any mouse button is pressed. + + \param e mouse press event +*/ +void PyConsole_Editor::mousePressEvent( QMouseEvent* event ) +{ + if ( autoCompletion() ) { + clearCompletion(); + myComplCursorPos = -1; + } + QTextEdit::mousePressEvent( event ); +} + +/*! + \brief Process mouse button release event. + + Left mouse button: copy selection to the clipboard. + Middle mouse button: paste clipboard's contents. + + \param event mouse event +*/ +void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event ) +{ + if ( event->button() == Qt::LeftButton ) { + QTextEdit::mouseReleaseEvent( event ); + } + else if ( event->button() == Qt::MidButton ) { + QTextCursor aCursor = cursorForPosition( event->pos() ); + // if the position is not in the last line move it to the end of the command line + if ( aCursor.position() < document()->end().previous().position() + promptSize() ) { + moveCursor( QTextCursor::End ); + } + else { + setTextCursor( aCursor ); + } + const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? + QClipboard::Selection : QClipboard::Clipboard ); + if ( md ) + insertFromMimeData( md ); + } + else { + QTextEdit::mouseReleaseEvent( event ); + } +} + +/*! + \brief Check if the string is command. + + Return \c true if the string \a str is likely to be the command + (i.e. it is started from the '>>>' or '...'). + \param str string to be checked +*/ +bool PyConsole_Editor::isCommand( const QString& str ) const +{ + return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT ); +} + +/*! + \brief Handle keyboard event. + + Implement navigation, history browsing, copy/paste and other common + operations. + + \param event keyboard event +*/ +void PyConsole_Editor::keyPressEvent( QKeyEvent* event ) +{ + // get cursor position + QTextCursor aCursor = textCursor(); + int curLine = aCursor.blockNumber(); + int curCol = aCursor.columnNumber(); + + // get last edited line + int endLine = document()->blockCount()-1; + + // get pressed key code + int aKey = event->key(); + + // check if is pressed + bool ctrlPressed = event->modifiers() & Qt::ControlModifier; + // check if is pressed + bool shftPressed = event->modifiers() & Qt::ShiftModifier; + + if ( autoCompletion() ) { + // auto-completion support + if ( aKey == Qt::Key_Tab && !shftPressed ) { + // process key + if ( !ctrlPressed ) { + handleTab(); + } + else { + clearCompletion(); + handleBackTab(); + } + return; + } + + // If is not pressed (or if something else is pressed with ), + // or if is not pressed alone, we have to clear completion + if ( !ctrlPressed || ( ctrlPressed && aKey != Qt::Key_Control ) ) { + clearCompletion(); + myComplCursorPos = -1; + } + + // Discard pressed alone: + if ( aKey == Qt::Key_Control ) + return; + } + + if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) { + // process + key-binding and key: clear current command + myCommandBuffer.truncate( 0 ); + myPrompt = READY_PROMPT; + addText( myPrompt, true ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_C ) { + // process + key-binding : copy + copy(); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_X ) { + // process + key-binding : cut + cut(); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_V ) { + // process + key-binding : paste + paste(); + return; + } + + // check for printed key + // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0; + // Better: + aKey = !(QChar(aKey).isPrint()) ? aKey : 0; + + switch ( aKey ) { + case 0 : + // any printed key: just print it + { + if ( curLine < endLine || curCol < promptSize() ) { + moveCursor( QTextCursor::End ); + } + QTextEdit::keyPressEvent( event ); + break; + } + case Qt::Key_Return: + case Qt::Key_Enter: + // key: process the current command + { + handleReturn(); + break; + } + case Qt::Key_Up: + // arrow key: process as follows: + // - without , modifiers: previous command in history + // - with modifier key pressed: move cursor one row up without selection + // - with modifier key pressed: move cursor one row up with selection + // - with + modifier keys pressed: scroll one row up + { + if ( ctrlPressed && shftPressed ) { + int value = verticalScrollBar()->value(); + int spacing = fontMetrics().lineSpacing(); + verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 ); + } + else if ( shftPressed || ctrlPressed ) { + if ( curLine > 0 ) + moveCursor( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { + // set history browsing mode + myCmdInHistory = myHistory.count(); + // remember current command + QTextBlock par = document()->end().previous(); + myCurrentCommand = par.text().remove( 0, promptSize() ); + } + if ( myCmdInHistory > 0 ) { + myCmdInHistory--; + // get previous command in the history + QString previousCommand = myHistory.at( myCmdInHistory ); + // print previous command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + previousCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Down: + // arrow key: process as follows: + // - without , modifiers: next command in history + // - with modifier key pressed: move cursor one row down without selection + // - with modifier key pressed: move cursor one row down with selection + // - with + modifier keys pressed: scroll one row down + { + if ( ctrlPressed && shftPressed ) { + int value = verticalScrollBar()->value(); + int maxval = verticalScrollBar()->maximum(); + int spacing = fontMetrics().lineSpacing(); + verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval ); + } + else if ( shftPressed || ctrlPressed) { + if ( curLine < endLine ) + moveCursor( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + if ( myCmdInHistory >= 0 ) { + // get next command in the history + myCmdInHistory++; + QString nextCommand; + if ( myCmdInHistory < myHistory.count() ) { + // next command in history + nextCommand = myHistory.at( myCmdInHistory ); + } + else { + // end of history is reached + // last printed command + nextCommand = myCurrentCommand; + // unset history browsing mode + myCmdInHistory = -1; + } + // print next or current command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + nextCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Left: + // arrow key: process as follows: + // - without , modifiers: move one symbol left (taking into account prompt) + // - with modifier key pressed: move one word left (taking into account prompt) + // - with modifier key pressed: move one symbol left with selection + // - with + modifier keys pressed: move one word left with selection + { + QString txt = textCursor().block().text(); + if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) { + moveCursor( QTextCursor::Up ); + moveCursor( QTextCursor::EndOfBlock ); + } + else { + QTextEdit::keyPressEvent( event ); + } + break; + } + case Qt::Key_Right: + // arrow key: process as follows: + // - without , modifiers: move one symbol right (taking into account prompt) + // - with modifier key pressed: move one word right (taking into account prompt) + // - with modifier key pressed: move one symbol right with selection + // - with + modifier keys pressed: move one word right with selection + { + QString txt = textCursor().block().text(); + if ( !shftPressed ) { + if ( curCol < txt.length() ) { + if ( isCommand( txt ) && curCol < promptSize() ) { + aCursor.setPosition( aCursor.block().position() + promptSize() ); + setTextCursor( aCursor ); + break; + } + } + else { + if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) { + aCursor.setPosition( aCursor.position() + promptSize()+1 ); + setTextCursor( aCursor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + break; + } + } + } + QTextEdit::keyPressEvent( event ); + break; + } + case Qt::Key_PageUp: + // key: process as follows: + // - without , modifiers: first command in history + // - with modifier key pressed: move cursor one page up without selection + // - with modifier key pressed: move cursor one page up with selection + // - with + modifier keys pressed: scroll one page up + { + if ( ctrlPressed && shftPressed ) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); + } + else if ( shftPressed || ctrlPressed ) { + bool moved = false; + qreal lastY = cursorRect( aCursor ).top(); + qreal distance = 0; + // move using movePosition to keep the cursor's x + do { + qreal y = cursorRect( aCursor ).top(); + distance += qAbs( y - lastY ); + lastY = y; + moved = aCursor.movePosition( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + } while ( moved && distance < viewport()->height() ); + if ( moved ) { + aCursor.movePosition( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); + } + setTextCursor( aCursor ); + } + else { + if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { + // set history browsing mode + myCmdInHistory = myHistory.count(); + // remember current command + QTextBlock par = document()->end().previous(); + myCurrentCommand = par.text().remove( 0, promptSize() ); + } + if ( myCmdInHistory > 0 ) { + myCmdInHistory = 0; + // get very first command in the history + QString firstCommand = myHistory.at( myCmdInHistory ); + // print first command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + firstCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_PageDown: + // key: process as follows: + // - without , modifiers: last command in history + // - with modifier key pressed: move cursor one page down without selection + // - with modifier key pressed: move cursor one page down with selection + // - with + modifier keys pressed: scroll one page down + { + if ( ctrlPressed && shftPressed ) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); + } + else if ( shftPressed || ctrlPressed ) { + bool moved = false; + qreal lastY = cursorRect( aCursor ).top(); + qreal distance = 0; + // move using movePosition to keep the cursor's x + do { + qreal y = cursorRect( aCursor ).top(); + distance += qAbs( y - lastY ); + lastY = y; + moved = aCursor.movePosition( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + } while ( moved && distance < viewport()->height() ); + if ( moved ) { + aCursor.movePosition( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); + } + setTextCursor( aCursor ); + } + else { + if ( myCmdInHistory >= 0 ) { + // unset history browsing mode + myCmdInHistory = -1; + // print current command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + myCurrentCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Home: + // key: process as follows: + // - without , modifiers: move cursor to the beginning of the current line without selection + // - with modifier key pressed: move cursor to the very first symbol without selection + // - with modifier key pressed: move cursor to the beginning of the current line with selection + // - with + modifier keys pressed: move cursor to the very first symbol with selection + { + if ( ctrlPressed ) { + moveCursor( QTextCursor::Start, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + QString txt = textCursor().block().text(); + if ( isCommand( txt ) ) { + if ( shftPressed ) { + if ( curCol > promptSize() ) { + aCursor.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor ); + aCursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() ); + } + } + else { + aCursor.movePosition( QTextCursor::StartOfLine ); + aCursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() ); + } + setTextCursor( aCursor ); + } + else { + moveCursor( QTextCursor::StartOfBlock, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_End: + // key: process as follows: + // - without , modifiers: move cursor to the end of the current line without selection + // - with modifier key pressed: move cursor to the very last symbol without selection + // - with modifier key pressed: move cursor to the end of the current line with selection + // - with + modifier keys pressed: move cursor to the very last symbol with selection + { + moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + break; + } + case Qt::Key_Backspace : + // key: process as follows + // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt) + // - with modifier key pressed: delete previous word + // - with modifier key pressed: delete text from the cursor to the line beginning + // works only for last (command) line + { + if ( aCursor.hasSelection() ) { + cut(); + } + else if ( aCursor.position() > document()->end().previous().position() + promptSize() ) { + if ( shftPressed ) { + moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else if ( ctrlPressed ) { + aCursor.setPosition( document()->end().previous().position() + promptSize(), + QTextCursor::KeepAnchor ); + setTextCursor( aCursor ); + textCursor().removeSelectedText(); + } + else { + QTextEdit::keyPressEvent( event ); + } + } + else { + aCursor.setPosition( document()->end().previous().position() + promptSize() ); + setTextCursor( aCursor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_Delete : + // key: process as follows + // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt) + // - with modifier key pressed: delete next word + // - with modifier key pressed: delete text from the cursor to the end of line + // works only for last (command) line + { + if ( aCursor.hasSelection() ) { + cut(); + } + else if ( aCursor.position() > document()->end().previous().position() + promptSize()-1 ) { + if ( shftPressed ) { + moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else if ( ctrlPressed ) { + moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else { + QTextEdit::keyPressEvent( event ); + } + } + else { + aCursor.setPosition( document()->end().previous().position() + promptSize() ); + setTextCursor( aCursor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_Insert : + // key: process as follows + // - with modifier key pressed: copy() + // - with modifier key pressed: paste() to the command line + { + if ( ctrlPressed ) { + copy(); + } + else if ( shftPressed ) { + paste(); + } + else + QTextEdit::keyPressEvent( event ); + break; + } + default: + break; + } +} + +/*! + \brief Handle notification event coming from Python dispatcher. + \param event notification event +*/ +void PyConsole_Editor::customEvent( QEvent* event ) +{ + switch( event->type() ) + { + case PyConsole_PrintEvent::EVENT_ID: + { + PyConsole_PrintEvent* pe = (PyConsole_PrintEvent*)event; + putLog( pe->text()); + addText( pe->text(), false, pe->isError() ); + return; + } + case PyConsole_CompletionEvent::EVENT_ID: + { + PyConsole_CompletionEvent* ce = (PyConsole_CompletionEvent*)event; + bool status = ce->status(); + QStringList matches = ce->matches(); + QString doc = ce->doc(); + + if ( status ) { + // completion was successful + QTextCursor aCursor = textCursor(); + + if ( matches.isEmpty() ) { + // completion successful but there are no matches. + myTabMode = false; + myComplCursorPos = -1; + return; + } + + if ( matches.size() == 1 ) { + // there's only one match - complete directly and update doc string window + aCursor.insertText( matches[0].mid( myComplAfterPoint.size() ) ); + myTabMode = false; + if ( doc.isEmpty() ) + emit updateDoc( formatDocHTML( QString( "(%1)\n" ).arg( tr( "NO_DOC_AVAILABLE" ) ) ) ); + else + emit updateDoc( formatDocHTML( doc ) ); + } + else { + // there are several matches + + // detect if there is a common base to all available completion + // in this case append this base to the text + QString base = extractCommon( matches ); + aCursor.insertText( base.mid( myComplAfterPoint.size() ) ); + + // if this happens to match exactly the first completion + // also provide doc + if ( base == matches[0] ) + emit updateDoc( formatDocHTML( doc ) ); + + // print all matching completion in a "undo-able" block + int cursorPos = aCursor.position(); + aCursor.insertBlock(); + aCursor.beginEditBlock(); + + // insert all matches + QTextCharFormat cf; + cf.setForeground( QBrush( Qt::darkGreen ) ); + aCursor.setCharFormat( cf ); + aCursor.insertText( formatCompletion( matches ) ); + aCursor.endEditBlock(); + + // position cursor where it was before inserting the completion list + aCursor.setPosition( cursorPos ); + setTextCursor( aCursor ); + } + } + else { + // completion failed + myTabMode = false; + myComplCursorPos = -1; + } + return; + } + case PyInterp_Event::ES_OK: + case PyInterp_Event::ES_ERROR: + { + // clear command buffer + myCommandBuffer.truncate( 0 ); + // add caret return line if necessary + QTextBlock par = document()->end().previous(); + QString txt = par.text(); + txt.truncate( txt.length() - 1 ); + // IPAL19397 : addText moved to handleReturn() method + //if ( !txt.isEmpty() ) + // addText( "", true ); + // set "ready" prompt + myPrompt = READY_PROMPT; + addText( myPrompt ); + // unset busy cursor + unsetCursor(); + // stop event loop (if running) + if ( myEventLoop ) + myEventLoop->exit(); + // if we are in multi_paste_mode, process the next item + multiLineProcessNextLine(); + break; + } + case PyInterp_Event::ES_INCOMPLETE: + { + // extend command buffer (multi-line command) + myCommandBuffer.append( "\n" ); + // add caret return line if necessary + QTextBlock par = document()->end().previous(); + QString txt = par.text(); + txt.truncate( txt.length() - 1 ); + // IPAL19397 : addText moved to handleReturn() method + //if ( !txt.isEmpty() ) + // addText( "", true ); + // set "dot" prompt + myPrompt = DOTS_PROMPT; + addText( myPrompt/*, true*/ ); // IPAL19397 + // unset busy cursor + unsetCursor(); + // stop event loop (if running) + if ( myEventLoop ) + myEventLoop->exit(); + // if we are in multi_paste_mode, process the next item + multiLineProcessNextLine(); + break; + } + default: + QTextEdit::customEvent( event ); + } + + // unset read-only state + setReadOnly( false ); + // unset history browsing mode + myCmdInHistory = -1; + + if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 ) + { + // process the next sheduled command from the queue (if there is any) + QString nextcmd = myQueue[0]; + myQueue.pop_front(); + exec( nextcmd ); + } +} + +/*! + \brief "Copy" operation. + + Reimplemented from Qt. + Warning! In Qt this method is not virtual. +*/ +void PyConsole_Editor::cut() +{ + QTextCursor aCursor = textCursor(); + if ( aCursor.hasSelection() ) { + QApplication::clipboard()->setText( aCursor.selectedText() ); + int startSelection = aCursor.selectionStart(); + if ( startSelection < document()->end().previous().position() + promptSize() ) + startSelection = document()->end().previous().position() + promptSize(); + int endSelection = aCursor.selectionEnd(); + if ( endSelection < document()->end().previous().position() + promptSize() ) + endSelection = document()->end().previous().position() + promptSize(); + aCursor.setPosition( startSelection ); + aCursor.setPosition( endSelection, QTextCursor::KeepAnchor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + setTextCursor( aCursor ); + textCursor().removeSelectedText(); + } +} + +/*! + \brief "Paste" operation. + + Reimplemented from Qt. + Warning! In Qt this method is not virtual. +*/ +void PyConsole_Editor::paste() +{ + QTextCursor aCursor = textCursor(); + if ( aCursor.hasSelection() ) { + int startSelection = aCursor.selectionStart(); + if ( startSelection < document()->end().previous().position() + promptSize() ) + startSelection = document()->end().previous().position() + promptSize(); + int endSelection = aCursor.selectionEnd(); + if ( endSelection < document()->end().previous().position() + promptSize() ) + endSelection = document()->end().previous().position() + promptSize(); + aCursor.setPosition( startSelection ); + aCursor.setPosition( endSelection, QTextCursor::KeepAnchor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + setTextCursor( aCursor ); + textCursor().removeSelectedText(); + } + if ( textCursor().position() < document()->end().previous().position() + promptSize() ) + moveCursor( QTextCursor::End ); + QTextEdit::paste(); +} + +/*! + \brief "Clear" operation. + + Reimplemented from Qt. + Warning! In Qt this method is not virtual. +*/ +void PyConsole_Editor::clear() +{ + QTextEdit::clear(); + if ( isShowBanner() ) + addText( banner() ); + myPrompt = READY_PROMPT; + addText( myPrompt ); +} + +/*! + \brief Dumps recorded Python commands to the file + \param fileName path to the dump file + \return \c true if dump operation succeeded or \c false otherwise +*/ +bool PyConsole_Editor::dump( const QString& fileName ) +{ + bool ok = false; + if ( !fileName.isEmpty() ) { + QFile file( fileName ); + if ( file.open( QFile::WriteOnly ) ) { + QTextStream out( &file ); + for ( int i = 0; i < myHistory.count(); i++ ) { + out << myHistory[i] << endl; + } + file.close(); + ok = true; + } + } + return ok; +} + +/*! + \brief Dump menu action slot +*/ +void PyConsole_Editor::dump() +{ + forever { + // get file name + QString fileName = getDumpFileName(); + + if ( fileName.isEmpty() ) + break; // cancelled + + if ( dump( fileName ) ) + break; + else + QMessageBox::warning( this, + tr( "WARNING" ), + tr( "ERR_FILE_NOT_WRITEABLE" ) ); + } +} + +/*! + \brief Get file name for Dump commands operation. + + This function can be redefined in successor classes to show application + specific dialog box. + + \return path to the dump file +*/ +QString PyConsole_Editor::getDumpFileName() +{ + return QFileDialog::getSaveFileName( this, + tr( "GET_DUMP_COMMANDS_FILENAME" ), + QString(), + QString( "%1 (*.py)" ).arg( tr( "PYTHON_SCRIPTS" ) ) ); +} + +/*! + \brief Get file name for Log Python trace operation. + + This function can be redefined in successor classes to show application + specific dialog box. + + \return path to the log file +*/ +QString PyConsole_Editor::getLogFileName() +{ + return QFileDialog::getSaveFileName( this, + tr( "GET_PYTHON_TRACE_FILENAME" ), + QString(), + QString( "%1 (*.log *.txt)" ).arg( tr( "LOG_FILES" ) ) ); +} + +/*! + \brief Start python trace logging + \param fileName the path to the log file + \return \c true if operation succeeded or \c false otherwise + (for example, if file is not writeable) + \sa stopLog() + */ +bool PyConsole_Editor::startLog( const QString& fileName ) +{ + // stop possibly already running logging + if ( isLogging() ) + stopLog(); + + bool ok = false; + if ( !fileName.isEmpty() ) { + QFile file( fileName ); + if ( file.open( QFile::WriteOnly ) ) { + file.close(); + myLogFile = fileName; + ok = true; + } + } + return ok; +} + +/*! + \brief Start log action slot +*/ +void PyConsole_Editor::startLog() +{ + forever { + // get file name + QString fileName = getLogFileName(); + + if ( fileName.isEmpty() ) + break; // cancelled + + if ( startLog( fileName ) ) + break; + else + QMessageBox::warning( this, + tr( "WARNING" ), + tr( "File is not writable" ) ); + } +} + +/*! + \brief Stop log action slot + + Stops Python trace logging. +*/ +void PyConsole_Editor::stopLog() +{ + myLogFile = QString(); +} + +/*! + \brief Put data to the log file +*/ +void PyConsole_Editor::putLog( const QString& s ) +{ + if ( !myLogFile.isEmpty() ) { + QFile file( myLogFile ); + if ( !file.open( QFile::Append ) ) + return; + + QTextStream out( &file ); + out << s; + + file.close(); + } +} + +/*! + \brief Handle properly multi-line pasting. Qt documentation recommends overriding this function. + If the pasted text doesn't contain a line return, no special treatment is done. + \param source +*/ +void PyConsole_Editor::insertFromMimeData(const QMimeData* source) +{ + if ( myMultiLinePaste ) + return; + + if ( source->hasText() ) { + QString s = source->text(); + if ( s.contains( "\n" ) ) + multilinePaste( s ); + else + QTextEdit::insertFromMimeData( source ); + } + else { + QTextEdit::insertFromMimeData( source ); + } +} + +/*! + Start multi-line paste operation + \internal +*/ +void PyConsole_Editor::multilinePaste( const QString& s ) +{ + // Turn on multi line pasting mode + myMultiLinePaste = true; + + // Split string data to lines + QString s2 = s; + s2.replace( "\r", "" ); // Windows string format converted to Unix style + QStringList lst = s2.split( QChar('\n'), QString::KeepEmptyParts ); + + // Perform the proper paste operation for the first line to handle the case where + // something was already there + QMimeData source; + source.setText( lst[0] ); + QTextEdit::insertFromMimeData( &source ); + + // Prepare what will have to be executed after the first line + myMultiLineContent.clear(); + for ( int i = 1; i < lst.size(); ++i ) + myMultiLineContent.enqueue( lst[i] ); + + // Trigger the execution of the first (mixed) line + handleReturn(); + + // See customEvent() and multiLineProcessNext() for the rest of the handling. +} + +/*! + \brief Process the next line in the queue of multi-line paste operation; called + from customEvent() function + \internal +*/ +void PyConsole_Editor::multiLineProcessNextLine() +{ + // not in multi-line paste mode + if ( !myMultiLinePaste || myMultiLineContent.isEmpty() ) + return; + + QString line = myMultiLineContent.dequeue(); + if ( myMultiLineContent.empty() ) + { + // this isa last line in the queue, just paste it + addText( line, false, false ); + myMultiLinePaste = false; + } + else + { + // paste the line and simulate a key stroke + addText( line, false, false ); + handleReturn(); + } +} + +/*! + \brief Clear results of completion +*/ +void PyConsole_Editor::clearCompletion() +{ + // delete completion text if present + if ( myTabMode ) { + // remove completion display + document()->undo(); + // remove trailing line return: + QTextCursor tc( textCursor() ); + tc.setPosition( document()->characterCount()-1 ); + setTextCursor( tc ); + textCursor().deletePreviousChar(); + // TODO: before wait for any event to be completed + } + myTabMode = false; +} + +/*! + \brief Format completion results - this is where we should create 3 columns etc ... + \param matches list of possible completions + \return result string +*/ +QString PyConsole_Editor::formatCompletion( const QStringList& matches ) const +{ + static const int MAX_COMPLETIONS = 70; + + QStringList result; + int sz = matches.size(); + + if ( sz > MAX_COMPLETIONS ) + result.append( QString( "[%1]" ).arg( tr( "TOO_MANY_MATCHES" ) ) ); + + for ( int i = 0; i < qMin( sz, MAX_COMPLETIONS); ++i ) + result.append( matches[i] ); + + return result.join( "\n" ); +} + +/*! + \brief Format the doc string in HTML format with the first line in bold blue + \param doc initial doc string + \return HTML string +*/ +QString PyConsole_Editor::formatDocHTML( const QString& doc ) const +{ + static const char* templ = "\n" \ + " " \ + " " \ + "\n" \ + "

" \ + "%1

" \ + "

%2

" \ + ""; + + QString fst, rest(""); + + // Extract first line of doc + int idx = doc.indexOf( "\n" ); + if ( idx > 0 ) { + fst = doc.left( idx ); + rest = doc.mid( idx+1 ); + } + else { + fst = doc; + } + + fst = fst.replace( "\n", " " ); + rest = rest.replace(" \n", " " ); + return QString( templ ).arg( fst ).arg( rest ); +} + +/*! + \fn void PyConsole_Editor::updateDoc( const QString& doc); + \brief Signal emitted by the editor widget when the doc string should be updated. + \param doc a HTML block with the formatted doc string. + \todo currently this signal is left uncaught. +*/ + +/*! + \brief Extract the common leading part of all strings in matches. + \param matches + \param result +*/ +QString PyConsole_Editor::extractCommon( const QStringList& matches ) const +{ + QString result = ""; + + if ( matches.size() > 1 ) { + int l = 0; + bool ok = true; + while ( ok && l+1 < matches[0].size() ) { + QString match = matches[0].left( l+1 ); + for ( int j = 1; j < matches.size() && ok; j++ ) + ok = matches[j].startsWith( match ); + if ( ok ) + l++; + } + result = matches[0].left( l ); + } + + return result; +} + +/*! + \brief Useful method to get banner from Python interpreter + \return banner +*/ +QString PyConsole_Editor::banner() const +{ + return myInterp->getBanner().c_str(); +} diff --git a/tools/PyConsole/src/PyConsole_Editor.h b/tools/PyConsole/src/PyConsole_Editor.h new file mode 100644 index 000000000..774e101af --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Editor.h @@ -0,0 +1,137 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Editor.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +#ifndef PYCONSOLE_EDITOR_H +#define PYCONSOLE_EDITOR_H + +#include "PyConsole.h" + +#include +#include + +class PyConsole_Interp; +class PyInterp_Request; +class QEventLoop; + +class PYCONSOLE_EXPORT PyConsole_Editor : public QTextEdit +{ + Q_OBJECT; + +public: + PyConsole_Editor( PyConsole_Interp*, QWidget* = 0 ); + ~PyConsole_Editor(); + + PyConsole_Interp* getInterp() const; + + virtual void addText( const QString&, const bool = false, const bool = false ); + bool isCommand( const QString& ) const; + + virtual void exec( const QString& ); + void execAndWait( const QString& ); + + bool isSync() const; + void setIsSync( const bool ); + + bool isSuppressOutput() const; + void setIsSuppressOutput( const bool ); + + bool isShowBanner() const; + void setIsShowBanner( const bool ); + + void setAutoCompletion( bool ); + bool autoCompletion() const; + + bool isLogging() const; + + virtual QSize sizeHint() const; + + bool startLog( const QString& ); + bool dump( const QString& ); + +signals: + void updateDoc( const QString& ); + +public slots: + void cut(); + void paste(); + void clear(); + void dump(); + void startLog(); + void stopLog(); + +protected: + virtual void dropEvent( QDropEvent* ); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseReleaseEvent( QMouseEvent* ); + virtual void keyPressEvent ( QKeyEvent* ); + virtual void customEvent( QEvent* ); + + virtual void insertFromMimeData( const QMimeData* ); + + void putLog( const QString& ); + + virtual QString getDumpFileName(); + virtual QString getLogFileName(); + +private: + void multilinePaste( const QString& ); + void multiLineProcessNextLine(); + + void handleReturn(); + void handleTab(); + void handleBackTab(); + void clearCompletion(); + QString formatCompletion( const QStringList& ) const; + QString formatDocHTML( const QString& ) const; + QString extractCommon( const QStringList& ) const; + + PyInterp_Request* createCmdRequest( const QString& ); + PyInterp_Request* createTabRequest( const QString& ); + + QString banner() const; + inline int promptSize() const { return myPrompt.size(); } + + PyConsole_Interp* myInterp; //!< python interpreter + QString myCommandBuffer; //!< python command buffer + QString myCurrentCommand; //!< currently being printed command + QString myPrompt; //!< current command line prompt + int myCmdInHistory; //!< current history command index + QString myLogFile; //!< current output log + QStringList myHistory; //!< commands history buffer + QEventLoop* myEventLoop; //!< internal event loop + bool myShowBanner; //!< 'show banner' flag + QStringList myQueue; //!< python commands queue + bool myIsSync; //!< synchronous mode flag + bool myIsSuppressOutput; //!< suppress output flag + bool myMultiLinePaste; //!< true when pasting several lines + QQueue myMultiLineContent; //!< queue of lines being pasted + bool myAutoCompletion; //!< auto-completion mode flag + bool myTabMode; //!< flag that is \c true when editor performs completion + QString myComplBeforePoint; //!< string on which the dir() command is executed + QString myComplAfterPoint; //!< string on which the results of the dir() are matched + int myComplCursorPos; //!< cursor position when is hit + +}; + +#endif // PYCONSOLE_EDITOR_H diff --git a/tools/PyConsole/src/PyConsole_Event.cxx b/tools/PyConsole/src/PyConsole_Event.cxx new file mode 100644 index 000000000..5c3f1fdf9 --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Event.cxx @@ -0,0 +1,107 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Event.cxx +// Author : Vadim SANDLER (Open CASCADE S.A.S), Adrien Bruneton (CEA/DEN) + +#include "PyConsole_Event.h" + +/*! + \class PyConsole_PrintEvent + \brief Python command output backend event. + \internal +*/ + +/*! + \brief Constructor + \param message message text (python trace) + \param isError default to \c false - if \c true indicates that an error is being printed. +*/ +PyConsole_PrintEvent::PyConsole_PrintEvent( const QString& message, bool isError ) + : QEvent( (QEvent::Type)EVENT_ID ), myText( message ), myError( isError ) +{ +} + +/*! + \brief Get message + \return message text (python trace) +*/ +QString PyConsole_PrintEvent::text() const +{ + return myText; +} + +/*! + \brief Get error flag + \return \c true if this is an error message +*/ +bool PyConsole_PrintEvent::isError() const +{ + return myError; +} + +/*! + \class PyConsole_CompletionEvent + \brief Python command completion event. + \internal +*/ + +/*! + \brief Constructor + \param request python request + \param s status of execution of completion command + \param ms command matches (completions) + \param d docstring of the match (in case if there is sinlge match) +*/ +PyConsole_CompletionEvent::PyConsole_CompletionEvent( PyInterp_Request* request, + bool s, + const QStringList& ms, + const QString& d ) + : PyInterp_Event( (QEvent::Type)EVENT_ID, request ), + myStatus( s ), myMatches( ms ), myDoc( d ) +{} + +/*! + \brief Get status of execution of completion command + \return execution status +*/ +bool PyConsole_CompletionEvent::status() const +{ + return myStatus; +} + +/*! + \brief Get matches (completions) + \return detected command matches (completions) +*/ +QStringList PyConsole_CompletionEvent::matches() const +{ + return myMatches; +} + +/*! + \brief Get docstring + \return docstring of the match (in case if there is sinlge match) +*/ +QString PyConsole_CompletionEvent::doc() const +{ + return myDoc; +} diff --git a/tools/PyConsole/src/PyConsole_Event.h b/tools/PyConsole/src/PyConsole_Event.h new file mode 100644 index 000000000..d22b78b1a --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Event.h @@ -0,0 +1,67 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Event.h +// Author : Vadim SANDLER (Open CASCADE S.A.S), Adrien Bruneton (CEA/DEN) + +#ifndef PYCONSOLE_EVENT_H +#define PYCONSOLE_EVENT_H + +#include "PyConsole.h" +#include "PyInterp_Event.h" + +#include +#include +#include + +class PyConsole_PrintEvent : public QEvent +{ +public: + static const int EVENT_ID = 65432; + + PyConsole_PrintEvent( const QString&, bool = false ); + + QString text() const; + bool isError() const; + +private: + QString myText; //!< Event message (python trace) + bool myError; //!< Set to \c true if an error msg is to be displayed +}; + +class PyConsole_CompletionEvent : public PyInterp_Event +{ +public: + static const int EVENT_ID = 65433; + + PyConsole_CompletionEvent( PyInterp_Request*, bool, const QStringList&, const QString& ); + + bool status() const; + QStringList matches() const; + QString doc() const; + +protected: + bool myStatus; //!< Status of execution + QStringList myMatches; //!< Command matches (completions) + QString myDoc; //!< Docstring of the match (in case if there is sinlge match) +}; + +#endif // PYCONSOLE_EVENT_H diff --git a/tools/PyConsole/src/PyConsole_Interp.cxx b/tools/PyConsole/src/PyConsole_Interp.cxx new file mode 100644 index 000000000..722413686 --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Interp.cxx @@ -0,0 +1,198 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Interp.cxx +// Author : Nicolas REJNERI (OPEN CASCADE), Adrien BRUNETON (CEA/DEN), Vadim SANDLER (OPEN CASCADE) + +#include "PyConsole_Interp.h" + +/*! + \class PyConsole_Interp + \brief Python interpreter to be embedded to the SALOME study's GUI. + + There is only one Python interpreter for the whole SALOME environment. + + Call the initialize() method defined in the base class PyInterp_Interp, + to initialize the interpreter after instance creation. + + The method initialize() calls virtuals methods + - initPython() to initialize global Python interpreter + - initContext() to initialize interpreter internal context + - initRun() to prepare interpreter for running commands + + See PyInterp_Interp class for more details. +*/ + +/*! + \brief Constructor. + + Creates new python interpreter. +*/ +PyConsole_Interp::PyConsole_Interp() + : PyInterp_Interp() +{ +} + +/*! + \brief Destructor. +*/ +PyConsole_Interp::~PyConsole_Interp() +{ +} + +/*! + \brief Performs specific actions before each Python command + + Sets the variable "__IN_SALOME_GUI_CONSOLE" to True. + This is not attached to a module (like salome_iapp.IN_SALOME_GUI_CONSOLE) + since modules are shared across all interpreters in SALOME. + + \note GIL is already acquired here. +*/ +int PyConsole_Interp::beforeRun() +{ + return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=True"); +} + +/*! + \brief Performs specific actions after each Python command + + Sets the variable "__IN_SALOME_GUI_CONSOLE" to False. + \sa beforeRun() + + \note GIL is already acquired here. +*/ +int PyConsole_Interp::afterRun() +{ + return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=False"); +} + +/*! + \brief Run Python dir() command to get matches. + \internal + \param dirArgument Python expression to pass to the dir command. The parsing of what the + user actually started typing is dedicated to the caller + \param startMatch string representing the begining of the patter to be completed. For example, when + the user types "a_string_variable.rsp ", this is "rsp". + \param[out] matches resulting list of matches + \param[out] docString resulting docstring of single match + \return \true if completion succeeded, \c false otherwise +*/ +bool PyConsole_Interp::runDirCommand( const QString& dirArgument, const QString& startMatch, + QStringList& matches, QString& docString ) +{ + static QStringList keywords; + if ( keywords.isEmpty() ) { + keywords << "and" << "as" << "assert" << "break" << "class" << "continue" + << "def" << "del" << "elif" << "else" << "except" << "exec" + << "finally" << "for" << "from" << "global" << "if" << "import" + << "in" << "is" << "lambda" << "not" << "or" << "pass" << "print" << "raise" + << "return" << "try" << "while" << "with" << "yield"; + } + + // run dir() command and extract completions + if ( !runDirAndExtract( dirArgument, startMatch, matches ) ) + return false; + + // If dirArgument is empty, we append the __builtins__ + if ( dirArgument.isEmpty() ) { + if ( !runDirAndExtract( QString( "__builtins__" ), startMatch, matches, false ) ) + return false; + + // ... and we match on Python's keywords as well + foreach( QString keyword, keywords ) { + if ( keyword.startsWith( startMatch ) ) + matches.append( keyword ); + } + } + + // Try to get doc string of the first match + if ( matches.size() > 0 ) { + QString cmd = QString( "%1.__doc__" ).arg( matches[0] ); + if ( !dirArgument.trimmed().isEmpty() ) + cmd.prepend( QString( "%1." ).arg( dirArgument ) ); + + PyObject* str = PyRun_String( cmd.toStdString().c_str(), Py_eval_input, _global_context, _local_context ); + if ( !str || str == Py_None || !PyString_Check( str ) ) + { + if ( !str ) + PyErr_Clear(); + } + else { + docString = QString( PyString_AsString( str ) ); + } + Py_XDECREF( str ); + } + return true; +} + +/*! + \internal + \sa runDirCommand() + \param dirArgument see runDirCommand() + \param startMatch see runDirCommand() + \param[out] result resulting list of matches + \param discardSwig if \c true, a regular expression is used to discard all static method generated + by SWIG. Typically: MEDCouplingUMesh_Blabla + \return \c true if the call to dir() and parsing of the result succeeded, \false otherwise. +*/ +bool PyConsole_Interp::runDirAndExtract( const QString& dirArgument, + const QString& startMatch, + QStringList& result, + bool discardSwig ) const +{ + QRegExp re( "^[A-Z].+_[A-Z]+[a-z]+.+$" ); // REX to discard SWIG static method, e.g. MEDCouplingUMesh_Blabla + + // Execute dir() command + QString command( "dir(" + dirArgument + ")" ); + PyObject* plst = PyRun_String( command.toStdString().c_str(), Py_eval_input, _global_context, _local_context ); + if ( !plst || plst == Py_None ) { + if ( !plst ) + PyErr_Clear(); + Py_XDECREF( plst ); + return false; + } + + // Check result + if ( !PySequence_Check( plst ) ) { + // Should never happen ... + Py_XDECREF( plst ); + return false; + } + + // Extract the returned list + int n = PySequence_Length( plst ); + for ( int i = 0; i < n; i++ ) { + PyObject* it; + it = PySequence_GetItem( plst, i ); + QString s( PyString_AsString( it ) ); + // if the method is not from swig, not static (guessed from the reg exp) and matches + // what is already there + if ( s.startsWith( startMatch ) ) { + if ( !discardSwig || ( !re.exactMatch( s ) && !s.contains( "swig" ) ) ) + result.append( s ); + } + Py_DECREF( it ); + } + Py_DECREF( plst ); + + return true; +} diff --git a/tools/PyConsole/src/PyConsole_Interp.h b/tools/PyConsole/src/PyConsole_Interp.h new file mode 100644 index 000000000..4275c350f --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Interp.h @@ -0,0 +1,49 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyConsole_Interp.h +// Author : Nicolas REJNERI (OPEN CASCADE), Adrien BRUNETON (CEA/DEN), Vadim SANDLER (OPEN CASCADE) + +#ifndef PYCONSOLE_INTERP_H +#define PYCONSOLE_INTERP_H + +#include "PyConsole.h" +#include "PyInterp_Interp.h" + +#include + +class PYCONSOLE_EXPORT PyConsole_Interp : public PyInterp_Interp +{ + friend class PyConsole_CompletionCommand; + +public: + PyConsole_Interp(); + ~PyConsole_Interp(); + + virtual int afterRun(); + virtual int beforeRun(); + +private: + bool runDirCommand( const QString&, const QString&, QStringList&, QString& ); + bool runDirAndExtract( const QString&, const QString&, QStringList&, bool = true ) const; +}; + +#endif // PYCONSOLE_INTERP_H diff --git a/tools/PyConsole/src/PyConsole_Request.cxx b/tools/PyConsole/src/PyConsole_Request.cxx new file mode 100644 index 000000000..aa41c259e --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Request.cxx @@ -0,0 +1,120 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, 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 : PyConsole_Request.cxx +// Author : Vadim SANDLER (OPEN CASCADE), Adrien Bruneton (CEA/DEN) + +#include "PyConsole_Request.h" +#include "PyConsole_Interp.h" +#include "PyConsole_Event.h" + +#include + +/*! + \class PyConsole_ExecCommand + \brief Python command execution request. + \internal +*/ + +/*! + \brief Constructor. + + Creates new python command execution request. + + \param theInterp python interpreter + \param theCommand python command + \param theListener widget to get the notification messages + \param theSync if \c true, the request is processed synchronously +*/ +PyConsole_ExecCommand::PyConsole_ExecCommand( PyInterp_Interp* theInterp, + const QString& theCommand, + QObject* theListener, + bool theSync ) + : PyInterp_LockRequest( theInterp, theListener, theSync ), + myCommand( theCommand ), myState( PyInterp_Event::ES_OK ) +{} + +/*! + \brief Execute the python command in the interpreter and + get its execution status. +*/ +void PyConsole_ExecCommand::execute() +{ + if ( myCommand != "" ) { + int ret = getInterp()->run( myCommand.toLatin1().data() ); + if ( ret < 0 ) + myState = PyInterp_Event::ES_ERROR; + else if ( ret > 0 ) + myState = PyInterp_Event::ES_INCOMPLETE; + } +} + +/*! + \brief Create and return a notification event. + \return new notification event +*/ +QEvent* PyConsole_ExecCommand::createEvent() +{ + if ( IsSync() ) + QCoreApplication::sendPostedEvents( listener(), PyConsole_PrintEvent::EVENT_ID ); + return new PyInterp_Event( myState, this ); +} + +/*! + \class PyConsole_CompletionCommand + \brief Python command completion request. + \internal +*/ + +/*! + \brief Constructor. + + Creates a new python completion request. + + \param theInterp python interpreter + \param theInput string containing the dir() command to be executed + \param theStartMatch part to be matched with the results of the dir() command + \param theListener widget to get the notification messages + \param theSync if \c true the request is processed synchronously +*/ +PyConsole_CompletionCommand::PyConsole_CompletionCommand( PyInterp_Interp* theInterp, + const QString& theInput, + const QString& theStartMatch, + QObject* theListener, + bool theSync ) + : PyInterp_LockRequest( theInterp, theListener, theSync ), + myDirArg( theInput ), myStartMatch( theStartMatch ), myStatus( false ) +{} + +/*! + \brief Execute the completion command by invoking runDirCommand() function + of interpreter. +*/ +void PyConsole_CompletionCommand::execute() +{ + myStatus = static_cast( getInterp() )->runDirCommand( myDirArg, myStartMatch, myMatches, myDoc ); +} + +/*! + \brief Create and return completion event + \return new completion event + */ +QEvent* PyConsole_CompletionCommand::createEvent() +{ + return new PyConsole_CompletionEvent( this, myStatus, myMatches, myDoc ); +} diff --git a/tools/PyConsole/src/PyConsole_Request.h b/tools/PyConsole/src/PyConsole_Request.h new file mode 100644 index 000000000..827823b60 --- /dev/null +++ b/tools/PyConsole/src/PyConsole_Request.h @@ -0,0 +1,64 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, 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 : PyConsole_Request.h +// Author : Vadim SANDLER (OPEN CASCADE), Adrien Bruneton (CEA/DEN) + +#ifndef PYCONSOLE_REQUEST_H +#define PYCONSOLE_REQUEST_H + +#include "PyInterp_Request.h" + +#include +#include + +class QEvent; +class PyInterp_Interp; + +class PyConsole_ExecCommand : public PyInterp_LockRequest +{ +public: + PyConsole_ExecCommand( PyInterp_Interp*, const QString&, QObject*, bool = false ); + +protected: + virtual void execute(); + virtual QEvent* createEvent(); + +private: + QString myCommand; //!< Python command + int myState; //!< Python command execution status +}; + +class PyConsole_CompletionCommand : public PyInterp_LockRequest +{ +public: + PyConsole_CompletionCommand( PyInterp_Interp*, const QString&, const QString&, QObject*, bool = false ); + +protected: + virtual void execute(); + virtual QEvent* createEvent(); + +private: + QString myDirArg; //!< String to be passed to the dir() comman + QString myStartMatch; //!< Begining of the command (as typed by the user) + bool myStatus; //!< Status of completion command execution + QStringList myMatches; //!< Matches + QString myDoc; //!< Docstring of single match +}; + +#endif // PYCONSOLE_REQUEST_H diff --git a/tools/PyConsole/src/resources/PyConsole_msg_en.ts b/tools/PyConsole/src/resources/PyConsole_msg_en.ts new file mode 100644 index 000000000..7fac57c1d --- /dev/null +++ b/tools/PyConsole/src/resources/PyConsole_msg_en.ts @@ -0,0 +1,74 @@ + + + + + PyConsole_Console + + + EDIT_COPY_CMD + &Copy + + + + EDIT_PASTE_CMD + &Paste + + + + EDIT_CLEAR_CMD + Clea&r + + + + EDIT_SELECTALL_CMD + Select &All + + + EDIT_DUMPCOMMANDS_CMD + D&ump Commands + + + EDIT_STARTLOG_CMD + Start &Log + + + EDIT_STOPLOG_CMD + Stop &Log + + + + PyConsole_Editor + + GET_DUMP_COMMANDS_FILENAME + Dump commands to file + + + GET_PYTHON_TRACE_FILENAME + Save Python trace to file + + + PYTHON_SCRIPTS + Python scripts + + + WARNING + Warning! + + + LOG_FILES + Log files + + + ERR_FILE_NOT_WRITEABLE + File is not writeable! + + + TOO_MANY_MATCHES + Too many matches! Displaying first ones only... + + + NO_DOC_AVAILABLE + no documentation available + + + diff --git a/tools/PyConsole/src/resources/PyConsole_msg_fr.ts b/tools/PyConsole/src/resources/PyConsole_msg_fr.ts new file mode 100644 index 000000000..9eaf24482 --- /dev/null +++ b/tools/PyConsole/src/resources/PyConsole_msg_fr.ts @@ -0,0 +1,74 @@ + + + + + PyConsole_Console + + + EDIT_COPY_CMD + &Copier + + + + EDIT_PASTE_CMD + C&oller + + + + EDIT_CLEAR_CMD + &Effacer + + + + EDIT_SELECTALL_CMD + &Tout sélectionner + + + EDIT_DUMPCOMMANDS_CMD + &Générer le script des commandes + + + EDIT_STARTLOG_CMD + Démarrer une &trace + + + EDIT_STOPLOG_CMD + Arrêter la &trace + + + + PyConsole_Editor + + GET_DUMP_COMMANDS_FILENAME + Choisissez un fichier python où sauver le dump + + + GET_PYTHON_TRACE_FILENAME + Choisissez un fichier où sauver le log + + + PYTHON_SCRIPTS + Scripts Python + + + WARNING + Attention ! + + + LOG_FILES + Fichiers log + + + ERR_FILE_NOT_WRITEABLE + Le fichier n'a pas été écrit ! + + + TOO_MANY_MATCHES + Too many matches! Displaying first ones only... + + + NO_DOC_AVAILABLE + no documentation available + + + diff --git a/tools/PyConsole/src/resources/PyConsole_msg_ja.ts b/tools/PyConsole/src/resources/PyConsole_msg_ja.ts new file mode 100644 index 000000000..8861d5ed5 --- /dev/null +++ b/tools/PyConsole/src/resources/PyConsole_msg_ja.ts @@ -0,0 +1,74 @@ + + + + + PyConsole_Console + + + EDIT_COPY_CMD + コピー(&C) + + + + EDIT_PASTE_CMD + 貼り付け(&P) + + + + EDIT_CLEAR_CMD + 削除(&r) + + + + EDIT_SELECTALL_CMD + すべて選択します。(&A) + + + EDIT_DUMPCOMMANDS_CMD + スクリプト コマンドを生成します。(&u) + + + EDIT_STARTLOG_CMD + ログの開始 (&L) + + + EDIT_STOPLOG_CMD + ログの停止 (&L) + + + + PyConsole_Editor + + GET_DUMP_COMMANDS_FILENAME + Dump commands to file + + + GET_PYTHON_TRACE_FILENAME + Save Python trace to file + + + PYTHON_SCRIPTS + Python scripts + + + WARNING + Warning! + + + LOG_FILES + Log files + + + ERR_FILE_NOT_WRITEABLE + File is not writeable! + + + TOO_MANY_MATCHES + Too many matches! Displaying first ones only... + + + NO_DOC_AVAILABLE + no documentation available + + + diff --git a/tools/PyInterp/CMakeLists.txt b/tools/PyInterp/CMakeLists.txt new file mode 100644 index 000000000..40c20ba81 --- /dev/null +++ b/tools/PyInterp/CMakeLists.txt @@ -0,0 +1,78 @@ +# Copyright (C) 2015-2016 CEA/DEN, EDF R&D +# +# 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 +# + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12 FATAL_ERROR) +PROJECT(PyInterp C CXX) + +# Versioning +# =========== +# Project name, upper case +STRING(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UC) + +# To be changed once externalized CMake procedure: +################### +SET(KERNEL_ROOT_DIR $ENV{KERNEL_ROOT_DIR} CACHE PATH "Path to the Salome KERNEL") +IF(EXISTS ${KERNEL_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${KERNEL_ROOT_DIR}/salome_adm/cmake_files") + INCLUDE(SalomeMacros) +ELSE(EXISTS ${KERNEL_ROOT_DIR}) + MESSAGE(FATAL_ERROR "We absolutely need a Salome KERNEL, please define KERNEL_ROOT_DIR") +ENDIF(EXISTS ${KERNEL_ROOT_DIR}) +# From GUI - again to be changed once externalized: +LIST(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../adm_local/cmake_files") +################### + +# Platform setup +# ============== +INCLUDE(SalomeSetupPlatform) + +# Options +# ======= +OPTION(PYINTERP_BUILD_WITH_QT5 "Build PYINTERP with Qt 5" ON) + +# +# Set list of prerequisites +# ========================= + +FIND_PACKAGE(SalomePythonInterp REQUIRED) +FIND_PACKAGE(SalomePythonLibs REQUIRED) +# Qt +IF(NOT PYINTERP_BUILD_WITH_QT5) + FIND_PACKAGE(SalomeQt4 REQUIRED) +ELSE() + FIND_PACKAGE(SalomeQt5 REQUIRED) +ENDIF() + +# Detection report +SALOME_PACKAGE_REPORT_AND_CHECK() + +# Directories +# +# Directories have to be given after prerequisites (to be able to use +# Python version string for example). +# =========== +SET(PYINTERP_INSTALL_LIBS lib CACHE PATH "Install path: PyInterp libs") +SET(PYINTERP_INSTALL_HEADERS include CACHE PATH "Install path: PyInterp headers") + +SET(PYINTERP_INSTALL_RES share/resources CACHE PATH "Install path: PyInterp resources") + +# Sources +# ======== +ADD_SUBDIRECTORY(src) + diff --git a/tools/PyInterp/src/CMakeLists.txt b/tools/PyInterp/src/CMakeLists.txt new file mode 100755 index 000000000..c29e7e5fd --- /dev/null +++ b/tools/PyInterp/src/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (C) 2012-2016 CEA/DEN, EDF R&D, 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 +# + +INCLUDE(UseQtExt) + +# --- options --- + +# additional include directories +INCLUDE_DIRECTORIES( + ${QT_INCLUDES} + ${PYTHON_INCLUDE_DIRS} +) + +# additional preprocessor / compiler flags +ADD_DEFINITIONS(${QT_DEFINITIONS} ${PYTHON_DEFINITIONS}) + +# libraries to link to +SET(_link_LIBRARIES ${QT_LIBRARIES} ${PYTHON_LIBRARIES}) + +# --- headers --- + +# header files / to be processed by moc +SET(_moc_HEADERS + PyInterp_Dispatcher.h +) + +# header files / no moc processing +SET(_other_HEADERS + PyInterp.h + PyInterp_Event.h + PyInterp_Interp.h + PyInterp_Request.h + PyInterp_Utils.h +) + +# header files / to install +SET(PyInterp_HEADERS ${_moc_HEADERS} ${_other_HEADERS}) + +# --- sources --- + +# sources / moc wrappings +QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS}) + +# sources / static +SET(_other_SOURCES + PyInterp_Dispatcher.cxx + PyInterp_Event.cxx + PyInterp_Interp.cxx + PyInterp_Request.cxx +) + +# sources / to compile +SET(PyInterp_SOURCES ${_other_SOURCES} ${_moc_SOURCES}) + +# --- rules --- + +ADD_LIBRARY(PyInterp ${PyInterp_SOURCES}) +TARGET_LINK_LIBRARIES(PyInterp ${_link_LIBRARIES}) +INSTALL(TARGETS PyInterp EXPORT ${TOOLS_EXPORT_NAME}TargetGroup DESTINATION ${PYINTERP_INSTALL_LIBS}) + +INSTALL(FILES ${PyInterp_HEADERS} DESTINATION ${PYINTERP_INSTALL_HEADERS}) diff --git a/tools/PyInterp/src/PyInterp.h b/tools/PyInterp/src/PyInterp.h new file mode 100755 index 000000000..08271e032 --- /dev/null +++ b/tools/PyInterp/src/PyInterp.h @@ -0,0 +1,50 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp.h +// Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) + +#if !defined ( PYINTERP_H ) +#define PYINTERP_H + +// ======================================================== +// set dllexport type for Win platform +#ifdef WIN32 +# if defined PYINTERP_EXPORTS || defined PyInterp_EXPORTS +# define PYINTERP_EXPORT __declspec(dllexport) +# else +# define PYINTERP_EXPORT __declspec(dllimport) +# endif +#else // WIN32 +# define PYINTERP_EXPORT +#endif // WIN32 + +// ======================================================== + +#include + +// avoid warning messages +#ifdef WIN32 +#pragma warning (disable : 4786) +#pragma warning (disable : 4251) +#endif + +#endif // PYINTERP_H diff --git a/tools/PyInterp/src/PyInterp_Dispatcher.cxx b/tools/PyInterp/src/PyInterp_Dispatcher.cxx new file mode 100755 index 000000000..b70214481 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Dispatcher.cxx @@ -0,0 +1,146 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Dispatcher.cxx +// Author : Sergey Anikin (OPEN CASCADE S.A.S.) + +#include "PyInterp_Dispatcher.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! + +/** + \class PyInterp_Dispatcher + \brief Dispatcher of Python events; used to serialize requests to Python interpreter. +*/ + +PyInterp_Dispatcher* PyInterp_Dispatcher::myInstance = 0; + +PyInterp_Dispatcher* PyInterp_Dispatcher::Get() +{ + if ( !myInstance ) + myInstance = new PyInterp_Dispatcher(); + return myInstance; +} + +PyInterp_Dispatcher::PyInterp_Dispatcher() +: QThread() +{ +} + +PyInterp_Dispatcher::~PyInterp_Dispatcher() +{ + // Clear the request queue + myQueueMutex.lock(); + + QListIterator it( myQueue ); + while ( it.hasNext() ) + PyInterp_Request::Destroy( it.next() ); + myQueue.clear(); + + myQueueMutex.unlock(); + + // Wait for run() to finish + wait(); +} + +bool PyInterp_Dispatcher::IsBusy() const +{ + return isRunning(); +} + +void PyInterp_Dispatcher::Exec( PyInterp_Request* theRequest ) +{ + if ( !theRequest ) + return; + + if ( theRequest->IsSync() /*&& !IsBusy()*/) + { + // synchronous processing + processRequest( theRequest ); + } + else + { + // asynchronous processing + myQueueMutex.lock(); + + myQueue.enqueue( theRequest ); + if ( theRequest->listener() ) { + connect( theRequest->listener(), SIGNAL( destroyed( QObject* ) ), + this, SLOT( objectDestroyed( QObject* ) ) ); + } + + myQueueMutex.unlock(); + + if ( !IsBusy() ) + start(); + } +} + +void PyInterp_Dispatcher::run() +{ + PyInterp_Request* aRequest; + + // prepare for queue size check + myQueueMutex.lock(); + + while ( myQueue.size() ) + { + aRequest = myQueue.head(); + + // let other threads append their requests to the end of the queue + myQueueMutex.unlock(); + + // processRequest() may delete a request, so this pointer must not be used + // after request is processed! + processRequest( aRequest ); + + // prepare for removal of the first request in the queue + myQueueMutex.lock(); + + // IMPORTANT: the first item could have been removed by objectDestroyed() --> we have to check it + if ( myQueue.head() == aRequest ) // if it is still here --> remove it + myQueue.dequeue(); + } + + myQueueMutex.unlock(); +} + +void PyInterp_Dispatcher::processRequest( PyInterp_Request* theRequest ) +{ + theRequest->process(); +} + +void PyInterp_Dispatcher::objectDestroyed( QObject* o ) +{ + // prepare for modification of the queue + myQueueMutex.lock(); + + QMutableListIterator it( myQueue ); + while ( it.hasNext() ) + { + RequestPtr r = it.next(); + if ( o == r->listener() ) + { + r->setListener( 0 ); // to prevent event posting + it.remove(); + } + } + + myQueueMutex.unlock(); +} diff --git a/tools/PyInterp/src/PyInterp_Dispatcher.h b/tools/PyInterp/src/PyInterp_Dispatcher.h new file mode 100755 index 000000000..c9b83847a --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Dispatcher.h @@ -0,0 +1,65 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Dispatcher.h +// Author : Sergey Anikin (OPEN CASCADE S.A.S.) + +#ifndef PYINTERP_DISPATCHER_H +#define PYINTERP_DISPATCHER_H + +#include "PyInterp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! + +#include "PyInterp_Request.h" // full include instead of forward declaration + // everyone inc'ing the Dispatcher will get the requests for free. + +#include +#include +#include + +class PYINTERP_EXPORT PyInterp_Dispatcher : protected QThread +{ + PyInterp_Dispatcher(); // private constructor + Q_OBJECT +public: + static PyInterp_Dispatcher* Get(); + + virtual ~PyInterp_Dispatcher(); + + bool IsBusy() const; + void Exec( PyInterp_Request* ); + +private: + virtual void run(); + void processRequest( PyInterp_Request* ); + +private slots: + void objectDestroyed( QObject* ); + +private: + typedef PyInterp_Request* RequestPtr; + + QQueue myQueue; + QMutex myQueueMutex; + + static PyInterp_Dispatcher* myInstance; +}; + +#endif // PYINTERP_DISPATCHER_H diff --git a/tools/PyInterp/src/PyInterp_Event.cxx b/tools/PyInterp/src/PyInterp_Event.cxx new file mode 100644 index 000000000..478eb2c36 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Event.cxx @@ -0,0 +1,53 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Event.cxx +// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) + +#include "PyInterp_Event.h" +#include "PyInterp_Request.h" + +/** + \class PyInterp_Event + \brief Events thrown by the interpreter having executed a command and indicating + the return status. +*/ + +PyInterp_Event::PyInterp_Event( int type, PyInterp_Request* request ) + : QEvent( (QEvent::Type)type ), myRequest( request ) +{ +} + +PyInterp_Event::~PyInterp_Event() +{ + PyInterp_Request::Destroy( myRequest ); + myRequest = 0; +} + +PyInterp_Request* PyInterp_Event::GetRequest() const +{ + return myRequest; +} + +PyInterp_Event::operator PyInterp_Request*() const +{ + return myRequest; +} diff --git a/tools/PyInterp/src/PyInterp_Event.h b/tools/PyInterp/src/PyInterp_Event.h new file mode 100644 index 000000000..e812adbe1 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Event.h @@ -0,0 +1,58 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Event.h +// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) + +#ifndef PYINTERP_EVENT_H +#define PYINTERP_EVENT_H + +#include "PyInterp.h" + +#include + +class PyInterp_Request; + +class PYINTERP_EXPORT PyInterp_Event : public QEvent +{ + PyInterp_Event(); + PyInterp_Event( const PyInterp_Event& ); + +public: + // Execution state + enum { + ES_NOTIFY = QEvent::User + 5000, + ES_OK, + ES_ERROR, + ES_INCOMPLETE, + ES_LAST }; + + PyInterp_Event( int type, PyInterp_Request* request ); + virtual ~PyInterp_Event(); + + PyInterp_Request* GetRequest() const; + operator PyInterp_Request*() const; + +private: + PyInterp_Request* myRequest; +}; + +#endif // PYINTERP_EVENT_H diff --git a/tools/PyInterp/src/PyInterp_Interp.cxx b/tools/PyInterp/src/PyInterp_Interp.cxx new file mode 100644 index 000000000..5946e8d7f --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Interp.cxx @@ -0,0 +1,607 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Interp.cxx +// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON + +#include "PyInterp_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include "PyInterp_Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TOP_HISTORY_PY "--- top of history ---" +#define BEGIN_HISTORY_PY "--- begin of history ---" + +/* + The following functions are used to hook the Python + interpreter output. +*/ + +static void +PyStdOut_dealloc(PyStdOut *self) +{ + PyObject_Del(self); +} + +static PyObject* +PyStdOut_write(PyStdOut *self, PyObject *args) +{ + char *c; + int l; + if (!PyArg_ParseTuple(args, "t#:write",&c, &l)) + return NULL; + if(self->_cb==NULL) { + if ( self->_iscerr ) + std::cerr << c ; + else + std::cout << c ; + } + else { + self->_cb(self->_data,c); + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +PyStdOut_flush(PyStdOut *self) +{ + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef PyStdOut_methods[] = { + {"write", (PyCFunction)PyStdOut_write, METH_VARARGS, PyDoc_STR("write(string) -> None")}, + {"flush", (PyCFunction)PyStdOut_flush, METH_NOARGS, PyDoc_STR("flush() -> None")}, + {NULL, NULL} /* sentinel */ +}; + +static PyMemberDef PyStdOut_memberlist[] = { + {(char*)"softspace", T_INT, offsetof(PyStdOut, softspace), 0, + (char*)"flag indicating that a space needs to be printed; used by print"}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject PyStdOut_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "PyOut", /*tp_name*/ + sizeof(PyStdOut), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)PyStdOut_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + PyObject_GenericGetAttr, /*tp_getattro*/ + /* softspace is writable: we must supply tp_setattro */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + PyStdOut_methods, /*tp_methods*/ + PyStdOut_memberlist, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +#define PyStdOut_Check(v) ((v)->ob_type == &PyStdOut_Type) + +static PyStdOut* newPyStdOut( bool iscerr ) +{ + PyStdOut *self; + self = PyObject_New(PyStdOut, &PyStdOut_Type); + if (self == NULL) + return NULL; + self->softspace = 0; + self->_cb = NULL; + self->_iscerr = iscerr; + return self; +} + +/*! + \class PyInterp_Interp + \brief Generic embedded Python interpreter. +*/ + +int PyInterp_Interp::_argc = 1; +char* PyInterp_Interp::_argv[] = {(char*)""}; + +/*! + \brief Basic constructor. + + After construction the interpreter instance successor classes + must call virtual method initalize(). +*/ +PyInterp_Interp::PyInterp_Interp(): + _vout(0), _verr(0), _local_context(0), _global_context(0), _initialized(false) +{ +} + +/*! + \brief Destructor. +*/ +PyInterp_Interp::~PyInterp_Interp() +{ + destroy(); +} + +/*! + \brief Initialize embedded interpreter. + + This method shoud be called after construction of the interpreter. + The method initialize() calls virtuals methods + - initPython() to initialize global Python interpreter + - initContext() to initialize interpreter internal context + - initRun() to prepare interpreter for running commands + which should be implemented in the successor classes, according to the + embedded Python interpreter policy (mono or multi interpreter, etc). +*/ +void PyInterp_Interp::initialize() +{ + if ( initialized() ) + return; // prevent repeating intitialization + + _initialized = true; + + _history.clear(); // start a new list of user's commands + _ith = _history.begin(); + + initPython(); // This also inits the multi-threading for Python (but w/o acquiring GIL) + + // ---- The rest of the initialisation process is done hodling the GIL + PyLockWrapper lck; + + initContext(); + + // used to interpret & compile commands - this is really imported here + // and only added again (with PyImport_AddModule) later on + PyObjWrapper m(PyImport_ImportModule("codeop")); + if(!m) { + PyErr_Print(); + return; + } + + // Create python objects to capture stdout and stderr + _vout=(PyObject*)newPyStdOut( false ); // stdout + _verr=(PyObject*)newPyStdOut( true ); // stderr + + // All the initRun outputs are redirected to the standard output (console) + initRun(); +} + +void PyInterp_Interp::destroy() +{ + PyLockWrapper lck; + closeContext(); +} + +/*! + \brief Initialize Python interpreter. + + In case if Python is not initialized, it sets program name, initializes the single true Python + interpreter, sets program arguments, and initializes threads. + Otherwise, does nothing. This is important for light SALOME configuration, + as in full SALOME this is done at SalomeApp level. + \sa SalomeApp_PyInterp class and main() in SALOME_Session_Server + */ +void PyInterp_Interp::initPython() +{ + if (!Py_IsInitialized()){ + // Python is not initialized + Py_SetProgramName(_argv[0]); + Py_Initialize(); // Initialize the interpreter + PySys_SetArgv(_argc, _argv); + + PyEval_InitThreads(); // Create (and acquire) the Python global interpreter lock (GIL) + PyEval_ReleaseLock(); + } +} + +/*! + \brief Get embedded Python interpreter banner. + \return banner string + */ +std::string PyInterp_Interp::getBanner() const +{ + PyLockWrapper lck; + std::string aBanner("Python "); + aBanner = aBanner + Py_GetVersion() + " on " + Py_GetPlatform() ; + aBanner = aBanner + "\ntype help to get general information on environment\n"; + return aBanner; +} + +/*! + \brief Initialize run command. + + This method is used to prepare interpreter for running + Python commands. + + \return \c true on success and \c false on error +*/ +bool PyInterp_Interp::initRun() +{ + return true; +} + +/*! + * Initialize context dictionaries. GIL is held already. + * The code executed in an embedded interpreter is expected to be run at the module + * level, in which case local and global context have to be the same dictionary. + * See: http://stackoverflow.com/questions/12265756/c-python-running-python-code-within-a-context + * for an explanation. + */ +bool PyInterp_Interp::initContext() +{ + PyObject *m = PyImport_AddModule("__main__"); // interpreter main module (module context) + if(!m){ + PyErr_Print(); + return false; + } + _global_context = PyModule_GetDict(m); // get interpreter global variable context + Py_INCREF(_global_context); + _local_context = _global_context; + + int ret = PyRun_SimpleString("import salome_iapp;salome_iapp.IN_SALOME_GUI=True"); + + return ret == 0; +} + +/*! + * Destroy context dictionaries. GIL is held already. + */ +void PyInterp_Interp::closeContext() +{ + Py_XDECREF(_global_context); + // both _global_context and _local_context may point to the same Python object + if ( _global_context != _local_context) + Py_XDECREF(_local_context); +} + +/*! + \brief Compile Python command and evaluate it in the + python dictionary contexts if possible. This is not thread-safe. + This is the caller's responsability to make this thread-safe. + \internal + \param command Python command string + \return -1 on fatal error, 1 if command is incomplete and 0 + if command is executed successfully + */ +static int run_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) +{ + PyObject *m = PyImport_AddModule("codeop"); + if(!m) { + // Fatal error. No way to go on. + PyErr_Print(); + return -1; + } + + PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command)); + if(!v) { + // Error encountered. It should be SyntaxError, + //so we don't write out traceback + PyObjWrapper exception, value, tb; + PyErr_Fetch(&exception, &value, &tb); + PyErr_NormalizeException(&exception, &value, &tb); + PyErr_Display(exception, value, NULL); + return -1; + } + else if (v == Py_None) { + // Incomplete text we return 1 : we need a complete text to execute + return 1; + } + else { + PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,global_ctxt, local_ctxt)); + if(!r) { + // Execution error. We return -1 + PyErr_Print(); + return -1; + } + // The command has been successfully executed. Return 0 + return 0; + } +} + +void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) + return; + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } +} + +std::vector +__split(const std::string& str, char delimiter) +{ + std::vector internal; + std::stringstream ss(str); // Turn the string into a stream. + std::string tok; + + while (getline(ss, tok, delimiter)) { + internal.push_back(tok); + } + + return internal; +} + +std::string +__join(const std::vector& v, int begin=0, int end=-1) +{ + if (end == -1) + end = v.size(); + std::stringstream ss; + for (size_t i = begin; i < end; ++i) { + if (i != begin) + ss << ","; + ss << v[i]; + } + return ss.str(); +} + +std::vector +__getArgsList(std::string argsString) +{ + // Special process if some items of 'args:' list are themselves lists + // Note that an item can be a list, but not a list of lists... + // So we can have something like this: + // myscript.py args:[\'file1\',\'file2\'],\'val1\',\"done\",[1,2,3],[True,False],\"ok\",kwarg1=\'kwarg1\',kwarg2=\'kwarg2\',\'fin\' + // With such a call, argsString variable contains the string representing ['file1','file2'],'val1','done',[1,2,3],[True,False],'ok',kwarg1='kwarg1',kwarg2='kwarg2','fin' + // We have to split argsString to obtain a 9 string elements list + std::vector x = __split(argsString, ','); + bool containsList = (argsString.find('[') != std::string::npos); + if (containsList) { + std::vector listBeginIndices, listEndIndices; + for (int pos = 0; pos < x.size(); ++pos) { + if (x[pos][0] == '[') + listBeginIndices.push_back(pos); + else if (x[pos][x[pos].size()-1] == ']') + listEndIndices.push_back(pos); + } + std::vector extractedArgs; + int start = 0; + for (int pos = 0; pos < listBeginIndices.size(); ++pos) { + int lbeg = listBeginIndices[pos]; + int lend = listEndIndices[pos]; + if (lbeg > start) + for (int k = start; k < lbeg; ++k) + extractedArgs.push_back(x[k]); + extractedArgs.push_back(__join(x, lbeg, lend+1)); + start = lend+1; + } + if (start < x.size()) + for (int k = start; k < x.size(); ++k) + extractedArgs.push_back(x[k]); + return extractedArgs; + } + else { + return x; + } +} + +/*! + \brief Compile Python command and evaluate it in the + python dictionary context if possible. Command might correspond to + the execution of a script with optional arguments. + In this case, command is: + execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") + \internal + \param command Python command string + \param context Python context (dictionary) + \return -1 on fatal error, 1 if command is incomplete and 0 + if command is executed successfully + */ +static int compile_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) +{ + // First guess if command is execution of a script with args, or a simple Python command + std::string singleCommand = command; + std::string commandArgs = ""; + + QRegExp rx("execfile\\s*\\(.*(args:.*)\"\\s*\\)"); + if (rx.indexIn(command) != -1) { + commandArgs = rx.cap(1).remove(0,5).toStdString(); // arguments of command + singleCommand = rx.cap().remove(rx.cap(1)).remove(" ").toStdString(); // command for execution without arguments + } + + if (commandArgs.empty()) { + // process command: expression + // process command: execfile(r"/absolute/path/to/script.py") (no args) + return run_command(singleCommand.c_str(), global_ctxt, local_ctxt); + } + else { + // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") + std::string script = singleCommand.substr(11); // remove leading execfile(r" + script = script.substr(0, script.length()-2); // remove trailing ") + std::vector argList = __getArgsList(commandArgs); + + std::string preCommandBegin = "import sys; save_argv = sys.argv; sys.argv=["; + std::string preCommandEnd = "];"; + std::string completeCommand = preCommandBegin+"\""+script+"\","; + for (std::vector::iterator itr = argList.begin(); itr != argList.end(); ++itr) { + if (itr != argList.begin()) + completeCommand += ","; + completeCommand = completeCommand + "\"" + *itr + "\""; + } + completeCommand = completeCommand+preCommandEnd+singleCommand+";sys.argv=save_argv"; + return run_command(completeCommand.c_str(), global_ctxt, local_ctxt); + } +} + +/*! + \brief Run Python command - the command has to fit on a single line (no \n!). + Use ';' if you need multiple statements evaluated at once. + \param command Python command + \return command status +*/ +int PyInterp_Interp::run(const char *command) +{ + beforeRun(); + int ret = simpleRun(command); + afterRun(); + return ret; +} + +/** + * Called before a command is run (when calling run() method). Not thread-safe. Caller's responsability + * to acquire GIL if needed. + */ +int PyInterp_Interp::beforeRun() +{ + return 0; +} + +/** + * Called after a command is run (when calling run() method). Not thread-safe. Caller's responsability + * to acquire GIL if needed. + */ +int PyInterp_Interp::afterRun() +{ + return 0; +} + +/*! + \brief Run Python command (used internally). Not thread-safe. GIL acquisition is caller's responsability. + \param command Python command + \param addToHistory if \c true (default), the command is added to the commands history + \return command status +*/ +int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) +{ + if( addToHistory && strcmp(command,"") != 0 ) { + _history.push_back(command); + _ith = _history.end(); + } + + // Current stdout and stderr are saved + PyObject * oldOut = PySys_GetObject((char*)"stdout"); + PyObject * oldErr = PySys_GetObject((char*)"stderr"); + // Keep them alive (PySys_GetObject returned a *borrowed* ref!) + Py_INCREF(oldOut); + Py_INCREF(oldErr); + + // Redirect outputs to SALOME Python console before treatment + PySys_SetObject((char*)"stderr",_verr); + PySys_SetObject((char*)"stdout",_vout); + + int ier = compile_command(command, _global_context, _local_context); + + // Outputs are redirected to what they were before + PySys_SetObject((char*)"stdout",oldOut); + PySys_SetObject((char*)"stderr",oldErr); + + return ier; +} + +/*! + \brief Get previous command in the commands history. + \return previous command +*/ +const char * PyInterp_Interp::getPrevious() +{ + if(_ith != _history.begin()){ + _ith--; + return (*_ith).c_str(); + } + else + return BEGIN_HISTORY_PY; +} + +/*! + \brief Get next command in the commands history. + \return next command +*/ +const char * PyInterp_Interp::getNext() +{ + if(_ith != _history.end()){ + _ith++; + } + if (_ith == _history.end()) + return TOP_HISTORY_PY; + else + return (*_ith).c_str(); +} + +/*! + \brief Set Python standard output device hook. + \param cb callback function + \param data callback function parameters +*/ +void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data) +{ + ((PyStdOut*)_vout)->_cb=cb; + ((PyStdOut*)_vout)->_data=data; +} + +/*! + \brief Set Python standard error device hook. + \param cb callback function + \param data callback function parameters +*/ +void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data) +{ + ((PyStdOut*)_verr)->_cb=cb; + ((PyStdOut*)_verr)->_data=data; +} + +/*! + \bried Check if the interpreter is initialized + \internal +*/ +bool PyInterp_Interp::initialized() const +{ + return _initialized; +} diff --git a/tools/PyInterp/src/PyInterp_Interp.h b/tools/PyInterp/src/PyInterp_Interp.h new file mode 100644 index 000000000..88e551916 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Interp.h @@ -0,0 +1,103 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Interp.h +// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON + +#ifndef PYINTERP_INTERP_H +#define PYINTERP_INTERP_H + +#include "PyInterp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include "PyInterp_Utils.h" + +#include +#include + +typedef void PyOutChanged(void* data,char * c); + +typedef struct { + PyObject_HEAD + int softspace; + PyOutChanged* _cb; + void* _data; + bool _iscerr; +} PyStdOut; + +/** + * Main class representing a *virtual* Python interpreter. There is really only one true + * Python interpreter in the whole application (no call to Py_NewInterpreter), + * but the use of different execution contexts allow + * to split the execution lines, and hence to emulate (relatively) independent interpreters. + * This has some consequences: modules imported in one context are not re-imported in another context + * (only there namespace is made available when importing in another context). + * See also class PyConsole_Interp. + */ +class PYINTERP_EXPORT PyInterp_Interp +{ +public: + static int _argc; + static char* _argv[]; + + PyInterp_Interp(); + virtual ~PyInterp_Interp(); + + void initialize(); + void destroy(); + + virtual int run(const char *command); + virtual void initStudy() {}; + + std::string getBanner() const; + void setverrcb(PyOutChanged*, void*); + void setvoutcb(PyOutChanged*, void*); + + const char* getPrevious(); + const char* getNext(); + +protected: + /** Redirection of stdout and stderr */ + PyObject* _vout; + PyObject* _verr; + /** Execution context (local and global variables) */ + PyObject* _global_context; + PyObject* _local_context; + + std::list _history; + std::list::iterator _ith; + + virtual int beforeRun(); + virtual int afterRun(); + int simpleRun(const char* command, const bool addToHistory = true); + + virtual void initPython(); + + /** Initialize execution context. */ + virtual bool initContext(); + virtual bool initRun(); + virtual void closeContext(); + + bool initialized() const; + +private: + bool _initialized; +}; + +#endif // PYINTERP_INTERP_H diff --git a/tools/PyInterp/src/PyInterp_Request.cxx b/tools/PyInterp/src/PyInterp_Request.cxx new file mode 100644 index 000000000..f8227f09c --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Request.cxx @@ -0,0 +1,93 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Request.h +// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) + +#include "PyInterp_Request.h" +#include "PyInterp_Utils.h" + +#include + +void PyInterp_Request::process() +{ + safeExecute(); + + bool isSync = IsSync(); + + if ( !isSync ) + myMutex.lock(); + + if ( listener() ) + processEvent( listener() ); + + if ( !isSync ) + myMutex.unlock(); +} + +void PyInterp_Request::safeExecute() +{ + execute(); +} + +void PyInterp_Request::Destroy( PyInterp_Request* request ) +{ + // Lock and unlock the mutex to avoid errors on its deletion + request->myMutex.lock(); + request->myMutex.unlock(); + delete request; +} + +QEvent* PyInterp_Request::createEvent() +{ + return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, this ); +} + +void PyInterp_Request::processEvent( QObject* o ) +{ + if ( !o ) + return; + + QEvent* e = createEvent(); + if ( !e ) + return; + + if ( !IsSync() ) + QCoreApplication::postEvent( o, e ); + else + { + QCoreApplication::sendEvent( o, e ); + delete e; + } +} + +void PyInterp_Request::setListener( QObject* o ) +{ + myMutex.lock(); + myListener = o; + myMutex.unlock(); +} + +void PyInterp_LockRequest::safeExecute() +{ + PyLockWrapper aLock; // Acquire GIL + execute(); +} diff --git a/tools/PyInterp/src/PyInterp_Request.h b/tools/PyInterp/src/PyInterp_Request.h new file mode 100644 index 000000000..cabb3d6b0 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Request.h @@ -0,0 +1,113 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Request.h +// Author : Sergey Anikin (OPEN CASCADE S.A.S.), Adrien Bruneton (CEA/DEN) + +#ifndef PYINTERP_REQUEST_H +#define PYINTERP_REQUEST_H + +#include "PyInterp.h" +#include "PyInterp_Event.h" + +#include + +class QObject; +class PyInterp_Interp; + +/** + \class PyInterp_Request + \brief Base Python interpreter request; does not not acquire GIL during execution. + */ +class PYINTERP_EXPORT PyInterp_Request +{ + friend class PyInterp_Dispatcher; + +private: + PyInterp_Request(); + PyInterp_Request( const PyInterp_Request& ); + +protected: + // protected destructor - to control deletion of requests + virtual ~PyInterp_Request() {}; + +public: + // Constructor + PyInterp_Request( QObject* listener, bool sync = true ) + : myIsSync( sync ), myListener( listener ) {}; + + // Deletes a request + static void Destroy( PyInterp_Request* ); + + // Returns true if this request should be processed synchronously, + // without putting it to a queue + bool IsSync() const { return myIsSync; } + +protected: + // Performs safe execution of the request + virtual void safeExecute(); + + // Should be redefined in successors, contains actual request code + virtual void execute() = 0; + + // This method can be overridden to customize notification event creation + virtual QEvent* createEvent(); + + virtual void processEvent( QObject* ); + + // Provide access to the listener of this request + QObject* listener() const { return myListener; } + void setListener( QObject* ); + +private: + // Process request, invoked from Dispatcher + void process(); + +private: + QMutex myMutex; + bool myIsSync; + QObject* myListener; +}; + +/** + \class PyInterp_LockRequest + \brief Python interpreter request; automatically acquires GIL during execution. + */ +class PYINTERP_EXPORT PyInterp_LockRequest : public PyInterp_Request +{ +public: + // Constructor + PyInterp_LockRequest( PyInterp_Interp* interp, QObject* listener=0, bool sync=true ) + : PyInterp_Request( listener, sync ), myInterp( interp ) + {} + +protected: + // Get interpreter + PyInterp_Interp* getInterp() const { return myInterp; } + + // Performs safe execution of the request + virtual void safeExecute(); + +private: + PyInterp_Interp* myInterp; +}; + +#endif // PYINTERP_REQUEST_H diff --git a/tools/PyInterp/src/PyInterp_Utils.h b/tools/PyInterp/src/PyInterp_Utils.h new file mode 100644 index 000000000..8d6ce8c63 --- /dev/null +++ b/tools/PyInterp/src/PyInterp_Utils.h @@ -0,0 +1,108 @@ +// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// 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 : PyInterp_Utils.h +// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON + +#ifndef PYINTERP_UTILS_H +#define PYINTERP_UTILS_H + +#include "PyInterp.h" + +#ifdef _DEBUG_ + #include +#endif + +/** + * \class PyLockWrapper + * \brief Python GIL wrapper. + * + * Utility class wrapping the Python GIL acquisition. This makes use of the high level + * API (PyGILState_Ensure and PyGILState_Release), and is hence compatible with only + * one running Python interpreter (no call to Py_NewInterpreter()). + * When the class is instanciated the lock is acquired. It is released at destruction time. + * Copy construction (and hence assignation) is forbidden. + */ +class PYINTERP_EXPORT PyLockWrapper +{ +public: + /** + * \brief Constructor. Automatically acquires GIL. + */ + PyLockWrapper() + { + _gil_state = PyGILState_Ensure(); + // Save current thread state for later comparison + _state = PyGILState_GetThisThreadState(); + } + + /** + * \brief Destructor. Automatically releases GIL. + */ + ~PyLockWrapper() + { + PyThreadState* _currState = PyGILState_GetThisThreadState(); +#ifdef _DEBUG_ + if (_currState != _state) + { + std::cout << "!!!!!!!!! PyLockWrapper inconsistency - now entering infinite loop for debugging\n"; + while(1); + } +#endif + PyGILState_Release(_gil_state); + } + +private: + PyGILState_STATE _gil_state; + PyThreadState* _state; + + // "Rule of 3" - Forbid usage of copy operator and copy-constructor + PyLockWrapper(const PyLockWrapper & another); + const PyLockWrapper & operator=(const PyLockWrapper & another); +}; + +/** + * \class PyObjWrapper + * \brief Utility class to properly handle the reference counting required on Python objects. + */ +class PYINTERP_EXPORT PyObjWrapper +{ + PyObject* myObject; +public: + PyObjWrapper(PyObject* theObject) : myObject(theObject) {} + PyObjWrapper() : myObject(0) {} + virtual ~PyObjWrapper() { Py_XDECREF(myObject); } + + operator PyObject*() { return myObject; } + PyObject* operator->() { return myObject; } + PyObject* get() { return myObject; } + bool operator!() { return !myObject; } + bool operator==(PyObject* theObject) { return myObject == theObject; } + PyObject** operator&() { return &myObject; } + PyObjWrapper& operator=(PyObjWrapper* theObjWrapper) + { + Py_XDECREF(myObject); + myObject = theObjWrapper->myObject; + return *this; + } +}; + +#endif // PYINTERP_UTILS_H