Salome HOME
Merge branch 'master' into V7_5_BR
[modules/gui.git] / src / PyConsole / PyConsole_Editor.cxx
index ae6563bb3fe11caff3d1b2d1086daafe44d7d25a..aeb61f965e0cc5dd56d67116a539bd09eb3d5050 100644 (file)
@@ -1,24 +1,25 @@
-//  Copyright (C) 2007-2008  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2014  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
+// 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.
+// 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.
+// 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
+// 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
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
 //
+
 //  SALOME SALOMEGUI : implementation of desktop and GUI kernel
 // File   : PyConsole_Editor.cxx
 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
   - <Ctrl><C>            : copy
   - <Ctrl><X>            : cut
   - <Ctrl><V>            : paste
-
-  TODO:
-  - paste multiline text: process each line as separate command
-    (including mouse middle-button click pasting)
-  - the same for drag-n-drop of multiline text
 */
 
 #include "PyConsole_Interp.h"   // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!!
 #include "PyConsole_Editor.h"
-#include <PyInterp_Dispatcher.h>
+#include "PyConsole_Event.h"
+#include "PyInterp_Event.h"
+#include "PyInterp_Dispatcher.h"
+#include "PyConsole_Request.h"
 
 #include <SUIT_Tools.h>
+#include <SUIT_FileDlg.h>
+#include <SUIT_MessageBox.h>
+#include <SUIT_FileValidator.h>
 
 #include <QApplication>
 #include <QClipboard>
 #include <QTextBlock>
 #include <QTextCursor>
 #include <QTextDocument>
+#include <QTextStream>
+#include <QChar>
 
 static QString READY_PROMPT = ">>> ";
 static QString DOTS_PROMPT  = "... ";
-#define PROMPT_SIZE myPrompt.length()
-
-#define PRINT_EVENT 65432
-
-/*!
-  \class ExecCommand
-  \brief Python command execution request.
-  \internal
-*/
 
-class ExecCommand : public PyInterp_LockRequest
+class DumpCommandsFileValidator : public SUIT_FileValidator
 {
-public:
-  /*!
-    \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 sync        if True the request is processed synchronously 
-  */
-  ExecCommand( PyInterp_Interp*        theInterp, 
-              const QString&          theCommand,
-              PyConsole_Editor*       theListener, 
-              bool                    sync = false )
-    : PyInterp_LockRequest( theInterp, theListener, sync ),
-      myCommand( theCommand ), myState( PyInterp_Event::ES_OK )
-  {}
-
-protected:
-  /*!
-    \brief Execute the python command in the interpreter and 
-           get its execution status.
-  */
-  virtual void execute()
-  {
-    if ( myCommand != "" )
-    {
-      int ret = getInterp()->run( myCommand.toLatin1() );
-      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
-  */
-  virtual QEvent* createEvent() const
-  {
-    if ( IsSync() )
-      QCoreApplication::sendPostedEvents( listener(), PRINT_EVENT );
-    return new PyInterp_Event( myState, (PyInterp_Request*)this );    
-  }
-
-private:
-  QString myCommand;   //!< Python command
-  int     myState;     //!< Python command execution status
+ public:
+  DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {};
+  virtual ~DumpCommandsFileValidator() {};
+  virtual bool canSave( const QString& file, bool permissions );
 };
 
-/*!
-  \class PrintEvent
-  \brief Python command output backend event.
-  \internal
-*/
+bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
+{
+  QFileInfo fi( file );
+  if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) {
+    SUIT_MessageBox::critical( parent(),
+                               QObject::tr("WRN_WARNING"),
+                               QObject::tr("WRN_FILE_NAME_BAD") );
+    return false;
+  }
+  return SUIT_FileValidator::canSave( file, permissions);
+}
 
-class PrintEvent : public QEvent
+void staticCallbackStdout( void* data, char* c )
 {
-public:
-  /*!
-    \brief Constructor
-    \param c message text (python trace)
-  */
-  PrintEvent( const char* c ) : QEvent( (QEvent::Type)PRINT_EVENT ), myText( c ) {}
-  /*!
-    \brief Get message
-    \return message text (python trace)
-  */
-  QString text() const { return myText; }
-
-private:
-  QString myText; //!< Event message (python trace)
-};
+  if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
+    PyConsole_Editor* e = (PyConsole_Editor*)data;
+    e->putLog( QString::fromUtf8(c) );
+    QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), false ) );
+  }
+}
 
-void staticCallback( void* data, char* c )
+void staticCallbackStderr( void* data, char* c )
 {
-  QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c ) ); 
+  if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
+    PyConsole_Editor* e = (PyConsole_Editor*)data;
+    e->putLog( QString::fromUtf8(c) );
+    QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), true ) );
+  }
 }
 
+
 /*!
   \brief Constructor. 
   
@@ -211,12 +163,14 @@ void staticCallback( void* data, char* c )
   \param theParent parent widget
 */
 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, 
-                                   QWidget*          theParent )
+                                    QWidget*          theParent )
 : QTextEdit( theParent ),
   myInterp( 0 ),
   myCmdInHistory( -1 ),
   myEventLoop( 0 ),
-  myIsSync( false )
+  myShowBanner( true ),
+  myIsSync( true ),
+  myIsSuppressOutput( false )
 {
   QString fntSet( "" );
   QFont aFont = SUIT_Tools::stringToFont( fntSet );
@@ -228,8 +182,8 @@ PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
   setWordWrapMode( QTextOption::WrapAnywhere );
   setAcceptRichText( false );
 
-  theInterp->setvoutcb( staticCallback, this );
-  theInterp->setverrcb( staticCallback, this );
+  theInterp->setvoutcb( staticCallbackStdout, this );
+  theInterp->setverrcb( staticCallbackStderr, this );
 
   // san - This is necessary for troubleless initialization
   onPyInterpChanged( theInterp );
@@ -237,11 +191,18 @@ PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
 
 /*!
   \brief Destructor.
-
-  Does nothing for the moment.
 */
 PyConsole_Editor::~PyConsole_Editor()
 {
+  myInterp = 0;
+}
+
+/*!
+  \brief Get Python interpreter
+*/
+PyConsole_Interp* PyConsole_Editor::getInterp() const
+{
+  return myInterp;
 }
 
 /*!
@@ -270,18 +231,101 @@ 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 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 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() ? myBanner.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 True, then the string is printed on a new line
+  \param isError if true, the text is printed in dark red
 */
 void PyConsole_Editor::addText( const QString& str, 
-                               const bool     newBlock )
+                                const bool     newBlock,
+                                const bool    isError)
 {
+  QTextCursor theCursor(textCursor());
+  QTextCharFormat cf;
+
   moveCursor( QTextCursor::End );
   if ( newBlock )
-    textCursor().insertBlock();
-  textCursor().insertText( str );
+    theCursor.insertBlock();
+  if (isError)
+      cf.setForeground(QBrush(Qt::red));
+  else
+      cf.setForeground(QBrush(Qt::black));
+  theCursor.insertText( str, cf);
   moveCursor( QTextCursor::End );
   ensureCursorVisible();
 }
@@ -290,6 +334,8 @@ void PyConsole_Editor::addText( const QString& str,
   \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 )
 {
@@ -317,6 +363,7 @@ void PyConsole_Editor::exec( const QString& command )
     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 );
@@ -353,14 +400,21 @@ void PyConsole_Editor::execAndWait( const QString& command )
     return;
 
   // create new event loop
-  myEventLoop = new QEventLoop( this );
+  bool sync = isSync();
+  if ( !sync ) {
+    myEventLoop = new QEventLoop( this );
+  }
+
   // execute command
   exec( command );
-  // run event loop
-  myEventLoop->exec();
-  // delete event loop after command is processed
-  delete myEventLoop;
-  myEventLoop = 0;
+
+  if ( !sync ) {
+    // run event loop
+    myEventLoop->exec();
+    // delete event loop after command is processed
+    delete myEventLoop;
+    myEventLoop = 0;
+  }
 }
 
 /*!
@@ -370,17 +424,23 @@ void PyConsole_Editor::execAndWait( const QString& command )
 */
 void PyConsole_Editor::handleReturn()
 {
+  // Position cursor at the end
+  QTextCursor curs(textCursor());
+  curs.movePosition(QTextCursor::End);
+  setTextCursor(curs);
+
   // get last line
   QTextBlock par = document()->end().previous();
   if ( !par.isValid() ) return;
 
   // get command
-  QString cmd = par.text().remove( 0, PROMPT_SIZE );
+  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 ); 
@@ -407,17 +467,17 @@ void PyConsole_Editor::dropEvent( QDropEvent* event )
   QPoint pos = event->pos();
   QTextCursor cur = cursorForPosition( event->pos() );
   // if the position is not in the last line move it to the end of the command line
-  if ( cur.position() < document()->end().previous().position() + PROMPT_SIZE ) {
+  if ( cur.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() );
+                 event->possibleActions(),
+                 event->mimeData(),
+                 event->mouseButtons(),
+                 event->keyboardModifiers(),
+                 event->type() );
   QTextEdit::dropEvent( &de );
   // accept the original event
   event->acceptProposedAction();
@@ -434,24 +494,21 @@ void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
 {
   if ( event->button() == Qt::LeftButton ) {
     QTextEdit::mouseReleaseEvent( event );
-    copy();
+    //copy();
   }
   else if ( event->button() == Qt::MidButton ) {
-    QString text;
-    if ( QApplication::clipboard()->supportsSelection() )
-      text = QApplication::clipboard()->text( QClipboard::Selection );
-    if ( text.isEmpty() )
-      text = QApplication::clipboard()->text( QClipboard::Clipboard );
     QTextCursor cur = cursorForPosition( event->pos() );
     // if the position is not in the last line move it to the end of the command line
-    if ( cur.position() < document()->end().previous().position() + PROMPT_SIZE ) {
+    if ( cur.position() < document()->end().previous().position() + promptSize() ) {
       moveCursor( QTextCursor::End );
     }
     else {
       setTextCursor( cur );
     }
-    textCursor().clearSelection();
-    textCursor().insertText( text );
+    const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? 
+                                                              QClipboard::Selection : QClipboard::Clipboard );
+    if ( md )
+      insertFromMimeData( md );
   }
   else {
     QTextEdit::mouseReleaseEvent( event );
@@ -495,7 +552,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
   // check if <Shift> is pressed
   bool shftPressed = event->modifiers() & Qt::ShiftModifier;
 
-  if ( aKey == Qt::Key_Escape || ctrlPressed && aKey == -1 ) {
+  if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
     // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
     myCommandBuffer.truncate( 0 );
     myPrompt = READY_PROMPT;
@@ -520,14 +577,16 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
   }
 
   // check for printed key
-  aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
+  // #### 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 < PROMPT_SIZE ) {
-       moveCursor( QTextCursor::End );
+      if ( curLine < endLine || curCol < promptSize() ) {
+        moveCursor( QTextCursor::End );
       }
       QTextEdit::keyPressEvent( event );
       break;
@@ -547,35 +606,35 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> 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 );
+        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 );
+        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, PROMPT_SIZE );
-       }
-       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 );
-       }
+        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;
     }
@@ -587,40 +646,40 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> 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 );
+        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 );
+        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 );
-       }
+        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;
     }
@@ -632,12 +691,12 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
     {
       QString txt = textCursor().block().text();
-      if ( !shftPressed && isCommand( txt ) && curCol <= PROMPT_SIZE ) {
-       moveCursor( QTextCursor::Up );
-       moveCursor( QTextCursor::EndOfBlock );
+      if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
+        moveCursor( QTextCursor::Up );
+        moveCursor( QTextCursor::EndOfBlock );
       }
       else {
-       QTextEdit::keyPressEvent( event );
+        QTextEdit::keyPressEvent( event );
       }
       break;
     }
@@ -650,21 +709,21 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     {
       QString txt = textCursor().block().text();
       if ( !shftPressed ) {
-       if ( curCol < txt.length() ) {
-         if ( isCommand( txt ) && curCol < PROMPT_SIZE ) {
-           cur.setPosition( cur.block().position() + PROMPT_SIZE );
-           setTextCursor( cur );
-           break;
-         }
-       }
-       else {
-         if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
-           cur.setPosition( cur.position() + PROMPT_SIZE+1 );
-           setTextCursor( cur );
-           horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
-           break;
-         }
-       }
+        if ( curCol < txt.length() ) {
+          if ( isCommand( txt ) && curCol < promptSize() ) {
+            cur.setPosition( cur.block().position() + promptSize() );
+            setTextCursor( cur );
+            break;
+          }
+        }
+        else {
+          if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
+            cur.setPosition( cur.position() + promptSize()+1 );
+            setTextCursor( cur );
+            horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
+            break;
+          }
+        }
       }
       QTextEdit::keyPressEvent( event );
       break;
@@ -677,49 +736,49 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
     {
       if ( ctrlPressed && shftPressed ) {
-       verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
+        verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
       }
       else if ( shftPressed || ctrlPressed ) {
-       bool moved = false;
-       qreal lastY = cursorRect( cur ).top();
-       qreal distance = 0;
-       // move using movePosition to keep the cursor's x
-       do {
-         qreal y = cursorRect( cur ).top();
-         distance += qAbs( y - lastY );
-         lastY = y;
-         moved = cur.movePosition( QTextCursor::Up, 
-                                   shftPressed ? QTextCursor::KeepAnchor : 
-                                                 QTextCursor::MoveAnchor );
-       } while ( moved && distance < viewport()->height() );
-       if ( moved ) {
-         cur.movePosition( QTextCursor::Down, 
-                           shftPressed ? QTextCursor::KeepAnchor : 
-                                         QTextCursor::MoveAnchor );
-         verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
-       }
-       setTextCursor( cur );
+        bool moved = false;
+        qreal lastY = cursorRect( cur ).top();
+        qreal distance = 0;
+        // move using movePosition to keep the cursor's x
+        do {
+          qreal y = cursorRect( cur ).top();
+          distance += qAbs( y - lastY );
+          lastY = y;
+          moved = cur.movePosition( QTextCursor::Up, 
+                                    shftPressed ? QTextCursor::KeepAnchor : 
+                                                  QTextCursor::MoveAnchor );
+        } while ( moved && distance < viewport()->height() );
+        if ( moved ) {
+          cur.movePosition( QTextCursor::Down, 
+                            shftPressed ? QTextCursor::KeepAnchor : 
+                                          QTextCursor::MoveAnchor );
+          verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
+        }
+        setTextCursor( cur );
       }
       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, PROMPT_SIZE );
-       }
-       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 );
-       }
+        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;
     }
@@ -731,41 +790,41 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
     {
       if ( ctrlPressed && shftPressed ) {
-       verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
+        verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
       }
       else if ( shftPressed || ctrlPressed ) {
-       bool moved = false;
-       qreal lastY = cursorRect( cur ).top();
-       qreal distance = 0;
-       // move using movePosition to keep the cursor's x
-       do {
-         qreal y = cursorRect( cur ).top();
-         distance += qAbs( y - lastY );
-         lastY = y;
-         moved = cur.movePosition( QTextCursor::Down, 
-                                   shftPressed ? QTextCursor::KeepAnchor : 
-                                                 QTextCursor::MoveAnchor );
-       } while ( moved && distance < viewport()->height() );
-       if ( moved ) {
-         cur.movePosition( QTextCursor::Up, 
-                           shftPressed ? QTextCursor::KeepAnchor : 
-                                         QTextCursor::MoveAnchor );
-         verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
-       }
-       setTextCursor( cur );
+        bool moved = false;
+        qreal lastY = cursorRect( cur ).top();
+        qreal distance = 0;
+        // move using movePosition to keep the cursor's x
+        do {
+          qreal y = cursorRect( cur ).top();
+          distance += qAbs( y - lastY );
+          lastY = y;
+          moved = cur.movePosition( QTextCursor::Down, 
+                                    shftPressed ? QTextCursor::KeepAnchor : 
+                                                  QTextCursor::MoveAnchor );
+        } while ( moved && distance < viewport()->height() );
+        if ( moved ) {
+          cur.movePosition( QTextCursor::Up, 
+                            shftPressed ? QTextCursor::KeepAnchor : 
+                                          QTextCursor::MoveAnchor );
+          verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
+        }
+        setTextCursor( cur );
       }
       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 );
-       }
+        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;
     }
@@ -777,29 +836,29 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
     {
       if ( ctrlPressed ) { 
-       moveCursor( QTextCursor::Start, 
-                   shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
+        moveCursor( QTextCursor::Start, 
+                    shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
       }
       else {
-       QString txt = textCursor().block().text();
-       if ( isCommand( txt ) ) {
-         if ( shftPressed ) {
-           if ( curCol > PROMPT_SIZE ) {
-             cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
-             cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, PROMPT_SIZE );
-           }
-         }
-         else {
-           cur.movePosition( QTextCursor::StartOfLine );
-           cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PROMPT_SIZE );
-         }
-         setTextCursor( cur );
-       }
-       else {
-         moveCursor( QTextCursor::StartOfBlock, 
-                     shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
-       }
-       horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
+        QString txt = textCursor().block().text();
+        if ( isCommand( txt ) ) {
+          if ( shftPressed ) {
+            if ( curCol > promptSize() ) {
+              cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
+              cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
+            }
+          }
+          else {
+            cur.movePosition( QTextCursor::StartOfLine );
+            cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
+          }
+          setTextCursor( cur );
+        }
+        else {
+          moveCursor( QTextCursor::StartOfBlock, 
+                      shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
+        }
+        horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
       }
       break;
     }
@@ -811,7 +870,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
     {
       moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, 
-                 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
+                  shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
       break;
     }  
   case Qt::Key_Backspace :
@@ -822,27 +881,27 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // works only for last (command) line
     {
       if ( cur.hasSelection() ) {
-       cut();
+        cut();
       }
-      else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE ) {
-       if ( shftPressed ) {
-         moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
-         textCursor().removeSelectedText();
-       }
-       else if ( ctrlPressed ) {
-         cur.setPosition( document()->end().previous().position() + PROMPT_SIZE, 
-                          QTextCursor::KeepAnchor );
-         setTextCursor( cur );
-         textCursor().removeSelectedText();
-       }
-       else {
-         QTextEdit::keyPressEvent( event );
-       }
+      else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
+        if ( shftPressed ) {
+          moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
+          textCursor().removeSelectedText();
+        }
+        else if ( ctrlPressed ) {
+          cur.setPosition( document()->end().previous().position() + promptSize(),
+                           QTextCursor::KeepAnchor );
+          setTextCursor( cur );
+          textCursor().removeSelectedText();
+        }
+        else {
+          QTextEdit::keyPressEvent( event );
+        }
       }
       else {
-       cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
-       setTextCursor( cur );
-       horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
+        cur.setPosition( document()->end().previous().position() + promptSize() );
+        setTextCursor( cur );
+        horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
       }
       break;
     }
@@ -854,25 +913,25 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // works only for last (command) line
     {
       if ( cur.hasSelection() ) {
-       cut();
+        cut();
       }
-      else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE-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 if ( cur.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 {
-       cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
-       setTextCursor( cur );
-       horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
+        cur.setPosition( document()->end().previous().position() + promptSize() );
+        setTextCursor( cur );
+        horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
       }
       break;
     }
@@ -882,13 +941,13 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
     // - with <Shift> modifier key pressed: paste() to the command line
     {
       if ( ctrlPressed ) {
-       copy();
+        copy();
       }
       else if ( shftPressed ) {
-       paste();
+        paste();
       }
       else
-       QTextEdit::keyPressEvent( event );
+        QTextEdit::keyPressEvent( event );
       break;
     }
   }
@@ -902,10 +961,10 @@ void PyConsole_Editor::customEvent( QEvent* event )
 {
   switch( event->type() )
   {
-  case PRINT_EVENT:
+  case PrintEvent::EVENT_ID:
     {
       PrintEvent* pe=(PrintEvent*)event;
-      addText( pe->text() );
+      addText( pe->text(), false, pe->isError());
       return;
     }
   case PyInterp_Event::ES_OK:
@@ -984,7 +1043,8 @@ void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
     if ( myInterp ) {
       // print banner
       myBanner = myInterp->getbanner().c_str();
-      addText( myBanner );
+      if ( isShowBanner() )
+       addText( myBanner );
       // clear command buffer
       myCommandBuffer.truncate(0);
       // unset read-only state
@@ -997,7 +1057,7 @@ void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
       viewport()->unsetCursor();
       // stop event loop (if running)
       if( myEventLoop)
-       myEventLoop->exit();
+        myEventLoop->exit();
     }
     else {
       // clear contents
@@ -1022,11 +1082,11 @@ void PyConsole_Editor::cut()
   if ( cur.hasSelection() ) {
     QApplication::clipboard()->setText( cur.selectedText() );
     int startSelection = cur.selectionStart();
-    if ( startSelection < document()->end().previous().position() + PROMPT_SIZE )
-      startSelection = document()->end().previous().position() + PROMPT_SIZE;
+    if ( startSelection < document()->end().previous().position() + promptSize() )
+      startSelection = document()->end().previous().position() + promptSize();
     int endSelection = cur.selectionEnd();
-    if ( endSelection < document()->end().previous().position() + PROMPT_SIZE )
-      endSelection = document()->end().previous().position() + PROMPT_SIZE;
+    if ( endSelection < document()->end().previous().position() + promptSize() )
+      endSelection = document()->end().previous().position() + promptSize();
     cur.setPosition( startSelection );
     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
@@ -1046,18 +1106,18 @@ void PyConsole_Editor::paste()
   QTextCursor cur = textCursor();
   if ( cur.hasSelection() ) {
     int startSelection = cur.selectionStart();
-    if ( startSelection < document()->end().previous().position() + PROMPT_SIZE )
-      startSelection = document()->end().previous().position() + PROMPT_SIZE;
+    if ( startSelection < document()->end().previous().position() + promptSize() )
+      startSelection = document()->end().previous().position() + promptSize();
     int endSelection = cur.selectionEnd();
-    if ( endSelection < document()->end().previous().position() + PROMPT_SIZE )
-      endSelection = document()->end().previous().position() + PROMPT_SIZE;
+    if ( endSelection < document()->end().previous().position() + promptSize() )
+      endSelection = document()->end().previous().position() + promptSize();
     cur.setPosition( startSelection );
     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
     setTextCursor( cur );
     textCursor().removeSelectedText();
   }
-  if ( textCursor().position() < document()->end().previous().position() + PROMPT_SIZE )
+  if ( textCursor().position() < document()->end().previous().position() + promptSize() )
     moveCursor( QTextCursor::End );
   QTextEdit::paste();
 }
@@ -1071,7 +1131,105 @@ void PyConsole_Editor::paste()
 void PyConsole_Editor::clear()
 {
   QTextEdit::clear();
-  addText( myBanner );
+  if ( isShowBanner() )
+    addText( myBanner );
   myPrompt = READY_PROMPT;
   addText( myPrompt );
 }
+
+/*!
+  \brief "Dump commands" operation.
+ */
+void PyConsole_Editor::dump()
+{
+  QStringList aFilters;
+  aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
+  
+  QString fileName = SUIT_FileDlg::getFileName( this, QString(),
+                                               aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
+                                               false, true, new DumpCommandsFileValidator( this ) );
+  if ( !fileName.isEmpty() ) {
+    QFile file( fileName ); 
+    if ( !file.open( QFile::WriteOnly ) )
+      return;
+
+    QTextStream out (&file);
+  
+    for ( int i=0; i<myHistory.count(); i++ ) {
+      out << myHistory[i] << endl;
+    }
+    file.close();
+  }
+}
+/*!
+  \brief "Start log" operation.
+ */
+void PyConsole_Editor::startLog()
+{
+  QStringList aFilters;
+  aFilters.append( tr( "LOG_FILES_FILTER" ) );
+
+  while (1) {
+    QString fileName = SUIT_FileDlg::getFileName( this, QString(),
+                                                 aFilters, tr( "TOT_SAVE_PYLOG" ),
+                                                 false, true );
+    if ( !fileName.isEmpty() ) {
+      if ( startLog( fileName ) ) {
+       break;
+      }
+      else {
+       SUIT_MessageBox::critical( this,
+                                  QObject::tr("ERR_ERROR"),
+                                  QObject::tr("ERR_FILE_NOT_WRITABLE") );
+      }
+    }
+    else {
+      break;
+    }
+  }
+}
+
+/*!
+  \brief Start python trace logging
+  \param fileName the path to the log file
+  \sa stopLog()
+ */
+bool PyConsole_Editor::startLog( const QString& fileName )
+{
+  bool ok = false;
+  if ( !fileName.isEmpty() ) {
+    QFile file( fileName );
+    if ( file.open( QFile::WriteOnly ) ) {
+      file.close();
+      myLogFile = fileName;
+      ok = true;
+    }
+  }
+  return ok;
+}
+
+/*!
+  \brief "Stop log" operation.
+  \sa startLog()
+ */
+void PyConsole_Editor::stopLog()
+{
+  myLogFile = QString();
+}
+
+/*!
+  \brief Put string 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();
+  }
+}