]> SALOME platform Git repositories - modules/gui.git/blob - src/PyConsoleBase/PyConsole_EditorBase.cxx
Salome HOME
Split PyConsole in order to be used easily outside GUI of SALOME.
[modules/gui.git] / src / PyConsoleBase / PyConsole_EditorBase.cxx
1 // Copyright (C) 2007-2015  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 //  SALOME SALOMEGUI : implementation of desktop and GUI kernel
24 // File   : PyConsole_Editor.cxx
25 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
26 //
27 /*!
28   \class PyConsole_Editor
29   \brief Python command line interpreter front-end GUI widget.
30   
31   This class provides simple GUI interface to the Python interpreter, including basic 
32   navigation operations, executing commands (both interactively and programmatically), 
33   copy-paste operations, history of the commands and so on.
34
35   Here below is the shortcut keyboard boundings used for navigation and other operations:
36   - <Enter>              : execute current command
37   - <Ctrl><Break>        : clear current command
38   - <Escape>             : clear current command
39   - <Up>                 : previous command in the history
40   - <Shift><Up>          : move cursor one row up with selection
41   - <Ctrl><Up>           : move cursor one row up without selection
42   - <Ctrl><Shift><Up>    : move cursor one row up with selection
43   - <Down>               : next command in the history
44   - <Shift><Down>        : move cursor one row down with selection
45   - <Ctrl><Down>         : move cursor one row down without selection
46   - <Ctrl><Shift><Down>  : move cursor one row down with selection
47   - <Left>               : move one symbol left without selection
48   - <Shift><Left>        : move one symbol left with selection
49   - <Ctrl><Left>         : move one word left without selection
50   - <Ctrl><Shift><Left>  : move one word left with selection
51   - <Right>              : move one symbol right without selection
52   - <Shift><Right>       : move one symbol right with selection
53   - <Ctrl><Right>        : move one word right without selection
54   - <Ctrl><Shift><Right> : move one word right with selection
55   - <PgUp>               : first command in the history
56   - <Shift><PgUp>        : move one page up with selection
57   - <Ctrl><PgUp>         : move one page up without selection
58   - <Ctrl><Shift><PgUp>  : scroll one page up
59   - <PgDn>               : last command in the history
60   - <Shift><PgDn>        : move one page down with selection
61   - <Ctrl><PgDn>         : move one page down without selection
62   - <Ctrl><Shift><PgDn>  : scroll one page down
63   - <Home>               : move to the beginning of the line without selection
64   - <Shift><Home>        : move to the beginning of the line with selection
65   - <Ctrl><Home>         : move to the very first symbol without selection
66   - <Ctrl><Shift><Home>  : move to the very first symbol with selection
67   - <End>                : move to the end of the line without selection
68   - <Shift><End>         : move to the end of the line with selection
69   - <Ctrl><End>          : move to the very last symbol without selection
70   - <Ctrl><Shift><End>   : move to the very last symbol with selection
71   - <Backspace>          : delete symbol before the cursor
72                            / remove selected text and put it to the clipboard (cut)
73   - <Shift><Backspace>   : delete previous word
74                            / remove selected text and put it to the clipboard (cut)
75   - <Ctrl><Backspace>    : delete text from the cursor to the beginning of the line 
76                            / remove selected text and put it to the clipboard (cut)
77   - <Delete>             : delete symbol after the cursor 
78                            / remove selected text and put it to the clipboard (cut)
79   - <Shift><Delete>      : delete next word
80                            / remove selected text and put it to the clipboard (cut)
81   - <Ctrl><Delete>       : delete text from the cursor to the end of the line
82                            / remove selected text and put it to the clipboard (cut)
83   - <Ctrl><Insert>       : copy
84   - <Shift><Insert>      : paste
85   - <Ctrl><V>            : paste
86   - <Ctrl><C>            : copy
87   - <Ctrl><X>            : cut
88   - <Ctrl><V>            : paste
89 */
90
91 #include "PyConsole_Interp.h"   // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!!
92 #include "PyConsole_EditorBase.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Event.h"
95 #include "PyInterp_Dispatcher.h"
96 #include "PyConsole_Request.h"
97
98 #include <Qtx.h>
99
100 #include <QApplication>
101 #include <QClipboard>
102 #include <QDropEvent>
103 #include <QEvent>
104 #include <QKeyEvent>
105 #include <QMouseEvent>
106 #include <QScrollBar>
107 #include <QTextBlock>
108 #include <QTextCursor>
109 #include <QTextDocument>
110 #include <QTextStream>
111 #include <QChar>
112 #include <QFileInfo>
113 #include <QFileDialog>
114 #include <QMessageBox>
115
116 //VSR: uncomment below macro to support unicode text properly in SALOME
117 //     current commented out due to regressions
118 //#define PAL22528_UNICODE
119
120 namespace
121 {
122   QString fromUtf8( const char* txt )
123   {
124 #ifdef PAL22528_UNICODE
125     return QString::fromUtf8( txt );
126 #else
127     return QString( txt );
128 #endif
129   }
130 }
131
132 static QString READY_PROMPT = ">>> ";
133 static QString DOTS_PROMPT  = "... ";
134
135 void staticCallbackStdout( void* data, char* c )
136 {
137   if(!((PyConsole_EditorBase*)data)->isSuppressOutput()) {
138     PyConsole_EditorBase* e = (PyConsole_EditorBase*)data;
139     e->putLog( fromUtf8(c) );
140     QApplication::postEvent( e, new PrintEvent( fromUtf8(c), false ) );
141   }
142 }
143
144 void staticCallbackStderr( void* data, char* c )
145 {
146   if(!((PyConsole_EditorBase*)data)->isSuppressOutput()) {
147     PyConsole_EditorBase* e = (PyConsole_EditorBase*)data;
148     e->putLog( fromUtf8(c) );
149     QApplication::postEvent( e, new PrintEvent( fromUtf8(c), true ) );
150   }
151 }
152
153
154 /*!
155   \brief Constructor. 
156   
157   Creates python editor window.
158   \param theInterp python interper
159   \param theParent parent widget
160 */
161 PyConsole_EditorBase::PyConsole_EditorBase( PyConsole_Interp* theInterp, 
162                                             QWidget*          theParent )
163 : QTextEdit( theParent ),
164   myInterp( 0 ),
165   myCmdInHistory( -1 ),
166   myEventLoop( 0 ),
167   myShowBanner( true ),
168   myIsSync( true ),
169   myIsSuppressOutput( false )
170 {
171   QString fntSet( "" );
172   QFont aFont ( Qtx::stringToFont(fntSet) );
173   setFont( aFont );
174   setUndoRedoEnabled( false );
175
176   myPrompt = READY_PROMPT;
177   setLineWrapMode( QTextEdit::WidgetWidth );
178   setWordWrapMode( QTextOption::WrapAnywhere );
179   setAcceptRichText( false );
180
181   theInterp->setvoutcb( staticCallbackStdout, this );
182   theInterp->setverrcb( staticCallbackStderr, this );
183
184   // san - This is necessary for troubleless initialization
185   onPyInterpChanged( theInterp );
186 }
187
188 /*!
189   \brief Destructor.
190 */
191 PyConsole_EditorBase::~PyConsole_EditorBase()
192 {
193   myInterp = 0;
194 }
195
196 /*!
197   \brief Get Python interpreter
198 */
199 PyConsole_Interp* PyConsole_EditorBase::getInterp() const
200 {
201   return myInterp;
202 }
203
204 /*!
205   \brief Get synchronous mode flag value.
206   
207   \sa setIsSync()
208   \return True if python console works in synchronous mode
209 */
210 bool PyConsole_EditorBase::isSync() const
211 {
212   return myIsSync;
213 }
214
215 /*!
216   \brief Set synchronous mode flag value.
217
218   In synhronous mode the Python commands are executed in the GUI thread
219   and the GUI is blocked until the command is finished. In the asynchronous
220   mode each Python command is executed in the separate thread that does not
221   block the main GUI loop.
222
223   \param on synhronous mode flag
224 */
225 void PyConsole_EditorBase::setIsSync( const bool on )
226 {
227   myIsSync = on;
228 }
229
230 /*!
231   \brief Get suppress output flag value.
232   
233   \sa setIsSuppressOutput()
234   \return \c true if python console output is suppressed.
235 */
236 bool PyConsole_EditorBase::isSuppressOutput() const
237 {
238   return myIsSuppressOutput;
239 }
240
241 /*!
242   \brief Set suppress output flag value.
243
244   In case if suppress output flag is true, the python 
245   console output suppressed.
246
247   \param on suppress output flag
248 */
249 void PyConsole_EditorBase::setIsSuppressOutput( const bool on )
250 {
251   myIsSuppressOutput = on;
252 }
253
254 /*!
255   \brief Get 'show banner' flag value.
256   
257   \sa setIsShowBanner()
258   \return \c true if python console shows banner
259 */
260 bool PyConsole_EditorBase::isShowBanner() const
261 {
262   return myShowBanner;
263 }
264
265 /*!
266   \brief Set 'show banner' flag value.
267
268   The banner is shown in the top of the python console window.
269
270   \sa isShowBanner()
271   \param on 'show banner' flag
272 */
273 void PyConsole_EditorBase::setIsShowBanner( const bool on )
274 {
275   if ( myShowBanner != on ) {
276     myShowBanner = on;
277     clear();
278   }
279 }
280
281 /*!
282   \brief Check if trace logging is switched on.
283   
284   \sa startLog(), stopLog()
285   \return \c true if trace logging is switched on
286 */
287 bool PyConsole_EditorBase::isLogging() const
288 {
289   return !myLogFile.isEmpty();
290 }
291
292 /*!
293   \brief Get size hint for the Python console window
294   \return size hint value
295 */
296 QSize PyConsole_EditorBase::sizeHint() const
297 {
298   QFontMetrics fm( font() );
299   int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1;
300   QSize s(100, fm.lineSpacing()*nbLines);
301   return s;
302 }
303
304 /*!
305   \brief Put the string \a str to the python editor.
306   \param str string to be put in the command line of the editor
307   \param newBlock if True, then the string is printed on a new line
308   \param isError if true, the text is printed in dark red
309 */
310 void PyConsole_EditorBase::addText( const QString& str, 
311                                 const bool     newBlock,
312                                 const bool    isError)
313 {
314   QTextCursor theCursor(textCursor());
315   QTextCharFormat cf;
316
317   moveCursor( QTextCursor::End );
318   if ( newBlock )
319     theCursor.insertBlock();
320   if (isError)
321       cf.setForeground(QBrush(Qt::red));
322   else
323       cf.setForeground(QBrush(Qt::black));
324   theCursor.insertText( str, cf);
325   moveCursor( QTextCursor::End );
326   ensureCursorVisible();
327 }
328
329 /*!
330   \brief Convenient method for executing a Python command,
331   as if the user typed it manually.
332   \param command python command to be executed
333
334   !!! WARNING: doesn't work properly with multi-line commands. !!!
335 */
336 void PyConsole_EditorBase::exec( const QString& command )
337 {
338   if ( isReadOnly() ) {
339     // some interactive command is being executed in this editor...
340     // shedule the command to the queue
341     myQueue.push_back( command );
342     return;
343   }
344   // remove last line
345   moveCursor( QTextCursor::End );
346   moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
347   textCursor().removeSelectedText();
348   // set "ready" prompt
349   myPrompt = READY_PROMPT;
350   // clear command buffer
351   myCommandBuffer.truncate( 0 );
352   // unset history browsing mode
353   myCmdInHistory = -1;
354   // print command line by line
355   QString cmd = command;
356   if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
357   QStringList lines = command.split( "\n" );
358   for ( int i = 0; i < lines.size(); i++ ) {
359     if ( !lines[i].trimmed().isEmpty() )
360       myHistory.push_back( lines[i] );
361     addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
362     putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
363   }
364   // IPAL20182
365   addText( "", true );
366   // set read-only mode
367   setReadOnly( true );
368   // set busy cursor
369   setCursor( Qt::BusyCursor );
370   
371   // post a request to execute Python command;
372   // editor will be informed via a custom event that execution has been completed
373   PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) );
374 }
375
376 /*!
377   \brief Create request to the python dispatcher for the command execution.
378
379   \param command python command to be executed
380  */
381 PyInterp_Request* PyConsole_EditorBase::createRequest( const QString& command )
382 {
383   return new ExecCommand( myInterp, command, this, isSync() );
384 }
385
386 /*!
387   \brief Execute command in the python interpreter
388   and wait until it is finished.
389
390   \param command python command to be executed
391  */
392 void PyConsole_EditorBase::execAndWait( const QString& command )
393 {
394   // already running ?
395   if( myEventLoop )
396     return;
397
398   // create new event loop
399   bool sync = isSync();
400   if ( !sync ) {
401     myEventLoop = new QEventLoop( this );
402   }
403
404   // execute command
405   exec( command );
406
407   if ( !sync ) {
408     // run event loop
409     myEventLoop->exec();
410     // delete event loop after command is processed
411     delete myEventLoop;
412     myEventLoop = 0;
413   }
414 }
415
416 /*!
417   \brief Process "Enter" key press event. 
418   
419   Execute the command entered by the user.
420 */
421 void PyConsole_EditorBase::handleReturn()
422 {
423   // Position cursor at the end
424   QTextCursor curs(textCursor());
425   curs.movePosition(QTextCursor::End);
426   setTextCursor(curs);
427
428   // get last line
429   QTextBlock par = document()->end().previous();
430   if ( !par.isValid() ) return;
431
432   // get command
433   QString cmd = par.text().remove( 0, promptSize() );
434   // extend the command buffer with the current command 
435   myCommandBuffer.append( cmd );
436   // add command to the history
437   if ( !cmd.trimmed().isEmpty() )
438     myHistory.push_back( cmd );
439   putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
440
441   // IPAL19397
442   addText( "", true ); 
443   
444   // set read-only mode
445   setReadOnly( true );
446   // set busy cursor
447   setCursor( Qt::BusyCursor );
448   
449   // post a request to execute Python command;
450   // editor will be informed via a custom event that execution has been completed
451   PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
452 }
453
454 /*!
455   \brief Process drop event.
456
457   Paste dragged text.
458   \param event drop event
459 */
460 void PyConsole_EditorBase::dropEvent( QDropEvent* event )
461 {
462   // get the initial drop position
463   QPoint pos = event->pos();
464   QTextCursor cur = cursorForPosition( event->pos() );
465   // if the position is not in the last line move it to the end of the command line
466   if ( cur.position() < document()->end().previous().position() + promptSize() ) {
467     moveCursor( QTextCursor::End );
468     pos = cursorRect().center();
469   }
470   // create new drop event and use it instead of the original
471   QDropEvent de( pos,
472                  event->possibleActions(),
473                  event->mimeData(),
474                  event->mouseButtons(),
475                  event->keyboardModifiers(),
476                  event->type() );
477   QTextEdit::dropEvent( &de );
478   // accept the original event
479   event->acceptProposedAction();
480 }
481
482 /*!
483   \brief Process mouse button release event.
484
485   Left mouse button: copy selection to the clipboard.
486   Middle mouse button: paste clipboard's contents.
487   \param event mouse event
488 */
489 void PyConsole_EditorBase::mouseReleaseEvent( QMouseEvent* event )
490 {
491   if ( event->button() == Qt::LeftButton ) {
492     QTextEdit::mouseReleaseEvent( event );
493     //copy();
494   }
495   else if ( event->button() == Qt::MidButton ) {
496     QTextCursor cur = cursorForPosition( event->pos() );
497     // if the position is not in the last line move it to the end of the command line
498     if ( cur.position() < document()->end().previous().position() + promptSize() ) {
499       moveCursor( QTextCursor::End );
500     }
501     else {
502       setTextCursor( cur );
503     }
504     const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? 
505                                                                QClipboard::Selection : QClipboard::Clipboard );
506     if ( md )
507       insertFromMimeData( md );
508   }
509   else {
510     QTextEdit::mouseReleaseEvent( event );
511   }
512 }
513
514 /*!
515   \brief Check if the string is command.
516   
517   Return True if the string \a str is likely to be the command
518   (i.e. it is started from the '>>>' or '...').
519   \param str string to be checked
520 */
521 bool PyConsole_EditorBase::isCommand( const QString& str ) const
522 {
523   return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
524 }
525
526 /*!
527   \brief Handle keyboard event.
528
529   Implement navigation, history browsing, copy/paste and other common
530   operations.
531   \param event keyboard event
532 */
533 void PyConsole_EditorBase::keyPressEvent( QKeyEvent* event )
534 {
535   // get cursor position
536   QTextCursor cur = textCursor();
537   int curLine = cur.blockNumber();
538   int curCol  = cur.columnNumber();
539
540   // get last edited line
541   int endLine = document()->blockCount()-1;
542
543   // get pressed key code
544   int aKey = event->key();
545
546   // check if <Ctrl> is pressed
547   bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
548   // check if <Shift> is pressed
549   bool shftPressed = event->modifiers() & Qt::ShiftModifier;
550
551   if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
552     // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
553     myCommandBuffer.truncate( 0 );
554     myPrompt = READY_PROMPT;
555     addText( myPrompt, true );
556     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
557     return;
558   }
559   else if ( ctrlPressed && aKey == Qt::Key_C ) {
560     // process <Ctrl>+<C> key-binding : copy
561     copy();
562     return;
563   }
564   else if ( ctrlPressed && aKey == Qt::Key_X ) {
565     // process <Ctrl>+<X> key-binding : cut
566     cut();
567     return;
568   }
569   else if ( ctrlPressed && aKey == Qt::Key_V ) {
570     // process <Ctrl>+<V> key-binding : paste
571     paste();
572     return;
573   }
574
575   // check for printed key
576   // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
577   // Better:
578   aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
579
580   switch ( aKey ) {
581   case 0 :
582     // any printed key: just print it
583     {
584       if ( curLine < endLine || curCol < promptSize() ) {
585         moveCursor( QTextCursor::End );
586       }
587       QTextEdit::keyPressEvent( event );
588       break;
589     }
590   case Qt::Key_Return:
591   case Qt::Key_Enter:
592     // <Enter> key: process the current command
593     {
594       handleReturn();
595       break;
596     }
597   case Qt::Key_Up:
598     // <Up> arrow key: process as follows:
599     // - without <Ctrl>, <Shift> modifiers: previous command in history
600     // - with <Ctrl> modifier key pressed:  move cursor one row up without selection
601     // - with <Shift> modifier key pressed: move cursor one row up with selection
602     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
603     {
604       if ( ctrlPressed && shftPressed ) {
605         int value   = verticalScrollBar()->value();
606         int spacing = fontMetrics().lineSpacing();
607         verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
608       }
609       else if ( shftPressed || ctrlPressed ) {
610         if ( curLine > 0 )
611           moveCursor( QTextCursor::Up, 
612                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
613       }
614       else { 
615         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
616           // set history browsing mode
617           myCmdInHistory = myHistory.count();
618           // remember current command
619           QTextBlock par = document()->end().previous();
620           myCurrentCommand = par.text().remove( 0, promptSize() );
621         }
622         if ( myCmdInHistory > 0 ) {
623           myCmdInHistory--;
624           // get previous command in the history
625           QString previousCommand = myHistory.at( myCmdInHistory );
626           // print previous command
627           moveCursor( QTextCursor::End );
628           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
629           textCursor().removeSelectedText();
630           addText( myPrompt + previousCommand ); 
631           // move cursor to the end
632           moveCursor( QTextCursor::End );
633         }
634       }
635       break;
636     }
637   case Qt::Key_Down:
638     // <Down> arrow key: process as follows:
639     // - without <Ctrl>, <Shift> modifiers: next command in history
640     // - with <Ctrl> modifier key pressed:  move cursor one row down without selection
641     // - with <Shift> modifier key pressed: move cursor one row down with selection
642     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
643     {
644       if ( ctrlPressed && shftPressed ) {
645         int value   = verticalScrollBar()->value();
646         int maxval  = verticalScrollBar()->maximum();
647         int spacing = fontMetrics().lineSpacing();
648         verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
649       }
650       else if ( shftPressed || ctrlPressed) {
651         if ( curLine < endLine )
652           moveCursor( QTextCursor::Down, 
653                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
654       }
655       else { 
656         if ( myCmdInHistory >= 0 ) {
657           // get next command in the history
658           myCmdInHistory++;
659           QString nextCommand;
660           if ( myCmdInHistory < myHistory.count() ) {
661             // next command in history
662             nextCommand = myHistory.at( myCmdInHistory );
663           }
664           else {
665             // end of history is reached
666             // last printed command
667             nextCommand = myCurrentCommand;
668             // unset history browsing mode
669             myCmdInHistory = -1;
670           }
671           // print next or current command
672           moveCursor( QTextCursor::End );
673           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
674           textCursor().removeSelectedText();
675           addText( myPrompt + nextCommand );
676           // move cursor to the end
677           moveCursor( QTextCursor::End );
678         }
679       }
680       break;
681     }
682   case Qt::Key_Left:
683     // <Left> arrow key: process as follows:
684     // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
685     // - with <Ctrl> modifier key pressed:  move one word left (taking into account prompt)
686     // - with <Shift> modifier key pressed: move one symbol left with selection
687     // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
688     {
689       QString txt = textCursor().block().text();
690       if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
691         moveCursor( QTextCursor::Up );
692         moveCursor( QTextCursor::EndOfBlock );
693       }
694       else {
695         QTextEdit::keyPressEvent( event );
696       }
697       break;
698     }
699   case Qt::Key_Right:
700     // <Right> arrow key: process as follows:
701     // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
702     // - with <Ctrl> modifier key pressed:  move one word right (taking into account prompt)
703     // - with <Shift> modifier key pressed: move one symbol right with selection
704     // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
705     {
706       QString txt = textCursor().block().text();
707       if ( !shftPressed ) {
708         if ( curCol < txt.length() ) {
709           if ( isCommand( txt ) && curCol < promptSize() ) {
710             cur.setPosition( cur.block().position() + promptSize() );
711             setTextCursor( cur );
712             break;
713           }
714         }
715         else {
716           if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
717             cur.setPosition( cur.position() + promptSize()+1 );
718             setTextCursor( cur );
719             horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
720             break;
721           }
722         }
723       }
724       QTextEdit::keyPressEvent( event );
725       break;
726     }
727   case Qt::Key_PageUp:
728     // <PageUp> key: process as follows:
729     // - without <Ctrl>, <Shift> modifiers: first command in history
730     // - with <Ctrl> modifier key pressed:  move cursor one page up without selection
731     // - with <Shift> modifier key pressed: move cursor one page up with selection
732     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
733     {
734       if ( ctrlPressed && shftPressed ) {
735         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
736       }
737       else if ( shftPressed || ctrlPressed ) {
738         bool moved = false;
739         qreal lastY = cursorRect( cur ).top();
740         qreal distance = 0;
741         // move using movePosition to keep the cursor's x
742         do {
743           qreal y = cursorRect( cur ).top();
744           distance += qAbs( y - lastY );
745           lastY = y;
746           moved = cur.movePosition( QTextCursor::Up, 
747                                     shftPressed ? QTextCursor::KeepAnchor : 
748                                                   QTextCursor::MoveAnchor );
749         } while ( moved && distance < viewport()->height() );
750         if ( moved ) {
751           cur.movePosition( QTextCursor::Down, 
752                             shftPressed ? QTextCursor::KeepAnchor : 
753                                           QTextCursor::MoveAnchor );
754           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
755         }
756         setTextCursor( cur );
757       }
758       else { 
759         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
760           // set history browsing mode
761           myCmdInHistory = myHistory.count();
762           // remember current command
763           QTextBlock par = document()->end().previous();
764           myCurrentCommand = par.text().remove( 0, promptSize() );
765         }
766         if ( myCmdInHistory > 0 ) {
767           myCmdInHistory = 0;
768           // get very first command in the history
769           QString firstCommand = myHistory.at( myCmdInHistory );
770           // print first command
771           moveCursor( QTextCursor::End );
772           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
773           textCursor().removeSelectedText();
774           addText( myPrompt + firstCommand ); 
775           // move cursor to the end
776           moveCursor( QTextCursor::End );
777         }
778       }
779       break;
780     }
781   case Qt::Key_PageDown:
782     // <PageDown> key: process as follows:
783     // - without <Ctrl>, <Shift> modifiers: last command in history
784     // - with <Ctrl> modifier key pressed:  move cursor one page down without selection
785     // - with <Shift> modifier key pressed: move cursor one page down with selection
786     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
787     {
788       if ( ctrlPressed && shftPressed ) {
789         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
790       }
791       else if ( shftPressed || ctrlPressed ) {
792         bool moved = false;
793         qreal lastY = cursorRect( cur ).top();
794         qreal distance = 0;
795         // move using movePosition to keep the cursor's x
796         do {
797           qreal y = cursorRect( cur ).top();
798           distance += qAbs( y - lastY );
799           lastY = y;
800           moved = cur.movePosition( QTextCursor::Down, 
801                                     shftPressed ? QTextCursor::KeepAnchor : 
802                                                   QTextCursor::MoveAnchor );
803         } while ( moved && distance < viewport()->height() );
804         if ( moved ) {
805           cur.movePosition( QTextCursor::Up, 
806                             shftPressed ? QTextCursor::KeepAnchor : 
807                                           QTextCursor::MoveAnchor );
808           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
809         }
810         setTextCursor( cur );
811       }
812       else { 
813         if ( myCmdInHistory >= 0 ) {
814           // unset history browsing mode
815           myCmdInHistory = -1;
816           // print current command
817           moveCursor( QTextCursor::End );
818           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
819           textCursor().removeSelectedText();
820           addText( myPrompt + myCurrentCommand ); 
821           // move cursor to the end
822           moveCursor( QTextCursor::End );
823         }
824       }
825       break;
826     }
827   case Qt::Key_Home: 
828     // <Home> key: process as follows:
829     // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
830     // - with <Ctrl> modifier key pressed:  move cursor to the very first symbol without selection
831     // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
832     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
833     {
834       if ( ctrlPressed ) { 
835         moveCursor( QTextCursor::Start, 
836                     shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
837       }
838       else {
839         QString txt = textCursor().block().text();
840         if ( isCommand( txt ) ) {
841           if ( shftPressed ) {
842             if ( curCol > promptSize() ) {
843               cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
844               cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
845             }
846           }
847           else {
848             cur.movePosition( QTextCursor::StartOfLine );
849             cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
850           }
851           setTextCursor( cur );
852         }
853         else {
854           moveCursor( QTextCursor::StartOfBlock, 
855                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
856         }
857         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
858       }
859       break;
860     }
861   case Qt::Key_End:
862     // <End> key: process as follows:
863     // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
864     // - with <Ctrl> modifier key pressed:  move cursor to the very last symbol without selection
865     // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
866     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
867     {
868       moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, 
869                   shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
870       break;
871     }  
872   case Qt::Key_Backspace :
873     // <Backspace> key: process as follows
874     // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
875     // - with <Shift> modifier key pressed: delete previous word
876     // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
877     // works only for last (command) line
878     {
879       if ( cur.hasSelection() ) {
880         cut();
881       }
882       else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
883         if ( shftPressed ) {
884           moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
885           textCursor().removeSelectedText();
886         }
887         else if ( ctrlPressed ) {
888           cur.setPosition( document()->end().previous().position() + promptSize(),
889                            QTextCursor::KeepAnchor );
890           setTextCursor( cur );
891           textCursor().removeSelectedText();
892         }
893         else {
894           QTextEdit::keyPressEvent( event );
895         }
896       }
897       else {
898         cur.setPosition( document()->end().previous().position() + promptSize() );
899         setTextCursor( cur );
900         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
901       }
902       break;
903     }
904   case Qt::Key_Delete :
905     // <Delete> key: process as follows
906     // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
907     // - with <Shift> modifier key pressed: delete next word
908     // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
909     // works only for last (command) line
910     {
911       if ( cur.hasSelection() ) {
912         cut();
913       }
914       else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
915         if ( shftPressed ) {
916           moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
917           textCursor().removeSelectedText();
918         }
919         else if ( ctrlPressed ) {
920           moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
921           textCursor().removeSelectedText();
922         }
923         else {
924           QTextEdit::keyPressEvent( event );
925         }
926       }
927       else {
928         cur.setPosition( document()->end().previous().position() + promptSize() );
929         setTextCursor( cur );
930         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
931       }
932       break;
933     }
934   case Qt::Key_Insert :
935     // <Insert> key: process as follows
936     // - with <Ctrl> modifier key pressed:  copy()
937     // - with <Shift> modifier key pressed: paste() to the command line
938     {
939       if ( ctrlPressed ) {
940         copy();
941       }
942       else if ( shftPressed ) {
943         paste();
944       }
945       else
946         QTextEdit::keyPressEvent( event );
947       break;
948     }
949   }
950 }
951
952 /*!
953   \brief Handle notification event coming from Python dispatcher.
954   \param event notification event
955 */
956 void PyConsole_EditorBase::customEvent( QEvent* event )
957 {
958   switch( event->type() )
959   {
960   case PrintEvent::EVENT_ID:
961     {
962       PrintEvent* pe=(PrintEvent*)event;
963       addText( pe->text(), false, pe->isError());
964       return;
965     }
966   case PyInterp_Event::ES_OK:
967   case PyInterp_Event::ES_ERROR:
968   {
969     // clear command buffer
970     myCommandBuffer.truncate( 0 );
971     // add caret return line if necessary
972     QTextBlock par = document()->end().previous();
973     QString txt = par.text();
974     txt.truncate( txt.length() - 1 );
975     // IPAL19397 : addText moved to handleReturn() method
976     //if ( !txt.isEmpty() )
977     //  addText( "", true );
978     // set "ready" prompt
979     myPrompt = READY_PROMPT;
980     addText( myPrompt );
981     // unset busy cursor
982     unsetCursor();
983     // stop event loop (if running)
984     if ( myEventLoop )
985       myEventLoop->exit();
986     break;
987   }
988   case PyInterp_Event::ES_INCOMPLETE:
989   {
990     // extend command buffer (multi-line command)
991     myCommandBuffer.append( "\n" );
992     // add caret return line if necessary
993     QTextBlock par = document()->end().previous();
994     QString txt = par.text();
995     txt.truncate( txt.length() - 1 );
996     // IPAL19397 : addText moved to handleReturn() method
997     //if ( !txt.isEmpty() )
998     //  addText( "", true );
999     // set "dot" prompt
1000     myPrompt = DOTS_PROMPT;
1001     addText( myPrompt/*, true*/ ); // IPAL19397
1002     // unset busy cursor
1003     unsetCursor();
1004     // stop event loop (if running)
1005     if ( myEventLoop )
1006       myEventLoop->exit();
1007     break;
1008   }
1009   default:
1010     QTextEdit::customEvent( event );
1011   }
1012   
1013   // unset read-only state
1014   setReadOnly( false );
1015   // unset history browsing mode
1016   myCmdInHistory = -1;
1017
1018   if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1019   {
1020     // process the next sheduled command from the queue (if there is any)
1021     QString nextcmd = myQueue[0];
1022     myQueue.pop_front();
1023     exec( nextcmd );
1024   }
1025 }
1026
1027 /*!
1028   \brief Handle Python interpreter change.
1029
1030   Perform initialization actions if the interpreter is changed.
1031   \param interp python interpreter is being set
1032 */
1033 void PyConsole_EditorBase::onPyInterpChanged( PyConsole_Interp* interp )
1034 {
1035   if ( myInterp != interp 
1036        // Force read-only state and wait cursor when myInterp is NULL
1037       || !myInterp ) {
1038     myInterp = interp;
1039     if ( myInterp ) {
1040       // print banner
1041       myBanner = myInterp->getbanner().c_str();
1042       if ( isShowBanner() )
1043         addText( myBanner );
1044       // clear command buffer
1045       myCommandBuffer.truncate(0);
1046       // unset read-only state
1047       setReadOnly( false );
1048       // unset history browsing mode
1049       myCmdInHistory = -1;
1050       // add prompt
1051       addText( myPrompt );
1052       // unset busy cursor
1053       viewport()->unsetCursor();
1054       // stop event loop (if running)
1055       if( myEventLoop)
1056         myEventLoop->exit();
1057     }
1058     else {
1059       // clear contents
1060       clear();
1061       // set read-only state
1062       setReadOnly( true );
1063       // set busy cursor
1064       setCursor( Qt::WaitCursor );
1065     }
1066   }
1067 }
1068
1069 /*!
1070   \brief "Copy" operation.
1071   
1072   Reimplemented from Qt.
1073   Warning! In Qt4 this method is not virtual.
1074  */
1075 void PyConsole_EditorBase::cut()
1076 {
1077   QTextCursor cur = textCursor();
1078   if ( cur.hasSelection() ) {
1079     QApplication::clipboard()->setText( cur.selectedText() );
1080     int startSelection = cur.selectionStart();
1081     if ( startSelection < document()->end().previous().position() + promptSize() )
1082       startSelection = document()->end().previous().position() + promptSize();
1083     int endSelection = cur.selectionEnd();
1084     if ( endSelection < document()->end().previous().position() + promptSize() )
1085       endSelection = document()->end().previous().position() + promptSize();
1086     cur.setPosition( startSelection );
1087     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1088     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1089     setTextCursor( cur );
1090     textCursor().removeSelectedText();
1091   }
1092 }
1093
1094 /*!
1095   \brief "Paste" operation.
1096
1097   Reimplemented from Qt.
1098   Warning! In Qt4 this method is not virtual.
1099  */
1100 void PyConsole_EditorBase::paste()
1101 {
1102   QTextCursor cur = textCursor();
1103   if ( cur.hasSelection() ) {
1104     int startSelection = cur.selectionStart();
1105     if ( startSelection < document()->end().previous().position() + promptSize() )
1106       startSelection = document()->end().previous().position() + promptSize();
1107     int endSelection = cur.selectionEnd();
1108     if ( endSelection < document()->end().previous().position() + promptSize() )
1109       endSelection = document()->end().previous().position() + promptSize();
1110     cur.setPosition( startSelection );
1111     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1112     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1113     setTextCursor( cur );
1114     textCursor().removeSelectedText();
1115   }
1116   if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1117     moveCursor( QTextCursor::End );
1118   QTextEdit::paste();
1119 }
1120
1121 /*!
1122   \brief "Clear" operation.
1123
1124   Reimplemented from Qt.
1125   Warning! In Qt4 this method is not virtual.
1126  */
1127 void PyConsole_EditorBase::clear()
1128 {
1129   QTextEdit::clear();
1130   if ( isShowBanner() )
1131     addText( myBanner );
1132   myPrompt = READY_PROMPT;
1133   addText( myPrompt );
1134 }
1135
1136 /*!
1137   \brief "Dump commands" operation.
1138  */
1139 void PyConsole_EditorBase::dumpImpl(const QString& fileName)
1140 {
1141   if ( !fileName.isEmpty() ) {
1142     QFile file( fileName ); 
1143     if ( !file.open( QFile::WriteOnly ) )
1144       return;
1145
1146     QTextStream out (&file);
1147   
1148     for ( int i=0; i<myHistory.count(); i++ ) {
1149       out << myHistory[i] << endl;
1150     }
1151     file.close();
1152   }
1153 }
1154
1155 /*!
1156   \brief "Dump commands" operation.
1157  */
1158 void PyConsole_EditorBase::dump(const QString& fileName)
1159 {
1160   dumpImpl(fileName);
1161 }
1162
1163 /*!
1164   \brief "Dump commands" operation.
1165  */
1166 void PyConsole_EditorBase::dump()
1167 {
1168   dumpSlot();
1169 }
1170
1171 void PyConsole_EditorBase::dumpSlot()
1172 {
1173   QString fileName(QFileDialog::getSaveFileName(this,tr("Choose python file where to store"),QString(),tr("Python scripts ext (*.py)")));
1174   if ( !fileName.isEmpty() )
1175     this->dump( fileName );
1176   else
1177     QMessageBox::warning(this,tr("WARNING"),tr("Python file has not been written"));
1178 }
1179
1180 /*!
1181   \brief Start python trace logging
1182   \param fileName the path to the log file
1183   \sa stopLog()
1184  */
1185 bool PyConsole_EditorBase::startLogImpl( const QString& fileName )
1186 {
1187   bool ok = false;
1188   if ( !fileName.isEmpty() ) {
1189     QFile file( fileName );
1190     if ( file.open( QFile::WriteOnly ) ) {
1191       file.close();
1192       myLogFile = fileName;
1193       ok = true;
1194     }
1195   }
1196   return ok;
1197 }
1198
1199
1200 /*!
1201   \brief Start python trace logging
1202   \param fileName the path to the log file
1203   \sa stopLog()
1204  */
1205 bool PyConsole_EditorBase::startLog( const QString& fileName )
1206 {
1207   return startLogImpl(fileName);
1208 }
1209
1210 /*!
1211   \brief Start python trace logging
1212   \sa stopLog()
1213  */
1214 void PyConsole_EditorBase::startLog()
1215 {
1216   startLogSlot();
1217 }
1218
1219 void PyConsole_EditorBase::startLogSlot()
1220 {
1221   while (1)
1222     {
1223       QString fileName(QFileDialog::getSaveFileName(this,tr("Choose python file where to store log"),QString(),tr("Log files ext (*.log *.txt)")));
1224       if ( !fileName.isEmpty() )
1225         {
1226           if ( startLogImpl( fileName ) )
1227             break;
1228           else
1229             QMessageBox::warning(this,tr("WARNING"),tr("Log file is not writable"));
1230         }
1231       else
1232         break;
1233     }
1234 }
1235
1236 /*!
1237   \brief "Stop log" operation.
1238   \sa startLog()
1239  */
1240 void PyConsole_EditorBase::stopLog()
1241 {
1242   myLogFile = QString();
1243 }
1244
1245 /*!
1246   \brief Put string to the log file
1247  */
1248 void PyConsole_EditorBase::putLog( const QString& s )
1249 {
1250   if ( !myLogFile.isEmpty() ) {
1251     QFile file( myLogFile );
1252     if ( !file.open( QFile::Append ) )
1253       return;
1254     
1255     QTextStream out (&file);
1256     out << s;
1257     
1258     file.close();
1259   }
1260 }