Salome HOME
Merge remote branch 'origin/master'
[modules/gui.git] / src / PyConsole / PyConsole_Editor.cxx
index 3414e26a62b56da147bc90712864a82c07231fdc..2311b58d46bb29a3e2b48be8b4b7cd2020572401 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2013  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2015  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
@@ -6,7 +6,7 @@
 // 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.
+// 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
   - <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 <QTextCursor>
 #include <QTextDocument>
 #include <QTextStream>
+#include <QChar>
 
-static QString READY_PROMPT = ">>> ";
-static QString DOTS_PROMPT  = "... ";
-#define PROMPT_SIZE myPrompt.length()
+//VSR: uncomment below macro to support unicode text properly in SALOME
+//     current commented out due to regressions
+//#define PAL22528_UNICODE
 
-#define PRINT_EVENT 65432
+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  = "... ";
 
 class DumpCommandsFileValidator : public SUIT_FileValidator
 {
@@ -141,95 +152,25 @@ bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
   return SUIT_FileValidator::canSave( file, permissions);
 }
 
-/*!
-  \class ExecCommand
-  \brief Python command execution request.
-  \internal
-*/
-
-class ExecCommand : public PyInterp_LockRequest
+void staticCallbackStdout( void* data, char* c )
 {
-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.toUtf8().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
-  */
-  virtual QEvent* createEvent() const
-  {
-    if ( IsSync() )
-      QCoreApplication::sendPostedEvents( listener(), PRINT_EVENT );
-    return new PyInterp_Event( myState, (PyInterp_Request*)this );    
+  if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
+    PyConsole_Editor* e = (PyConsole_Editor*)data;
+    e->putLog( fromUtf8(c) );
+    QApplication::postEvent( e, new PrintEvent( fromUtf8(c), false ) );
   }
+}
 
-private:
-  QString myCommand;   //!< Python command
-  int     myState;     //!< Python command execution status
-};
-
-/*!
-  \class PrintEvent
-  \brief Python command output backend event.
-  \internal
-*/
-
-class PrintEvent : public QEvent
-{
-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)
-};
-
-void staticCallback( void* data, char* c )
+void staticCallbackStderr( void* data, char* c )
 {
-  if(!((PyConsole_Editor*)data)->isSuppressOutput())
-    QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c ) ); 
+  if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
+    PyConsole_Editor* e = (PyConsole_Editor*)data;
+    e->putLog( fromUtf8(c) );
+    QApplication::postEvent( e, new PrintEvent( fromUtf8(c), true ) );
+  }
 }
 
+
 /*!
   \brief Constructor. 
   
@@ -244,7 +185,7 @@ PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
   myCmdInHistory( -1 ),
   myEventLoop( 0 ),
   myShowBanner( true ),
-  myIsSync( false ),
+  myIsSync( true ),
   myIsSuppressOutput( false )
 {
   QString fntSet( "" );
@@ -257,8 +198,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 );
@@ -266,11 +207,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;
 }
 
 /*!
@@ -350,6 +298,17 @@ void PyConsole_Editor::setIsShowBanner( const bool on )
   }
 }
 
+/*!
+  \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
@@ -366,14 +325,23 @@ QSize PyConsole_Editor::sizeHint() const
   \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();
 }
@@ -382,6 +350,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 )
 {
@@ -409,6 +379,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 );
@@ -445,14 +416,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;
+  }
 }
 
 /*!
@@ -462,17 +440,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 ); 
@@ -499,7 +483,7 @@ 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();
   }
@@ -529,21 +513,18 @@ void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
     //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 );
@@ -612,13 +593,15 @@ 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 ) {
+      if ( curLine < endLine || curCol < promptSize() ) {
         moveCursor( QTextCursor::End );
       }
       QTextEdit::keyPressEvent( event );
@@ -654,7 +637,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
           myCmdInHistory = myHistory.count();
           // remember current command
           QTextBlock par = document()->end().previous();
-          myCurrentCommand = par.text().remove( 0, PROMPT_SIZE );
+          myCurrentCommand = par.text().remove( 0, promptSize() );
         }
         if ( myCmdInHistory > 0 ) {
           myCmdInHistory--;
@@ -724,7 +707,7 @@ 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 ) {
+      if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
         moveCursor( QTextCursor::Up );
         moveCursor( QTextCursor::EndOfBlock );
       }
@@ -743,15 +726,15 @@ 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 );
+          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() + PROMPT_SIZE+1 );
+            cur.setPosition( cur.position() + promptSize()+1 );
             setTextCursor( cur );
             horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
             break;
@@ -798,7 +781,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
           myCmdInHistory = myHistory.count();
           // remember current command
           QTextBlock par = document()->end().previous();
-          myCurrentCommand = par.text().remove( 0, PROMPT_SIZE );
+          myCurrentCommand = par.text().remove( 0, promptSize() );
         }
         if ( myCmdInHistory > 0 ) {
           myCmdInHistory = 0;
@@ -876,14 +859,14 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
         QString txt = textCursor().block().text();
         if ( isCommand( txt ) ) {
           if ( shftPressed ) {
-            if ( curCol > PROMPT_SIZE ) {
+            if ( curCol > promptSize() ) {
               cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
-              cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, PROMPT_SIZE );
+              cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
             }
           }
           else {
             cur.movePosition( QTextCursor::StartOfLine );
-            cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PROMPT_SIZE );
+            cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
           }
           setTextCursor( cur );
         }
@@ -916,13 +899,13 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
       if ( cur.hasSelection() ) {
         cut();
       }
-      else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE ) {
+      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() + PROMPT_SIZE, 
+          cur.setPosition( document()->end().previous().position() + promptSize(),
                            QTextCursor::KeepAnchor );
           setTextCursor( cur );
           textCursor().removeSelectedText();
@@ -932,7 +915,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
         }
       }
       else {
-        cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
+        cur.setPosition( document()->end().previous().position() + promptSize() );
         setTextCursor( cur );
         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
       }
@@ -948,7 +931,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
       if ( cur.hasSelection() ) {
         cut();
       }
-      else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE-1 ) {
+      else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
         if ( shftPressed ) {
           moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
           textCursor().removeSelectedText();
@@ -962,7 +945,7 @@ void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
         }
       }
       else {
-        cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
+        cur.setPosition( document()->end().previous().position() + promptSize() );
         setTextCursor( cur );
         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
       }
@@ -994,10 +977,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:
@@ -1115,11 +1098,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() );
@@ -1139,18 +1122,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();
 }
@@ -1179,18 +1162,90 @@ void PyConsole_Editor::dump()
   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 != "" ) {
+                                               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;
+    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();
   }
 }