Salome HOME
Switching console to synchronous mode.
[modules/gui.git] / src / PyConsole / PyConsole_Editor.cxx
1 // Copyright (C) 2007-2014  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_Editor.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Event.h"
95 #include "PyInterp_Dispatcher.h"
96 #include "PyConsole_Request.h"
97
98 #include <SUIT_Tools.h>
99 #include <SUIT_FileDlg.h>
100 #include <SUIT_MessageBox.h>
101 #include <SUIT_FileValidator.h>
102
103 #include <QApplication>
104 #include <QClipboard>
105 #include <QDropEvent>
106 #include <QEvent>
107 #include <QKeyEvent>
108 #include <QMouseEvent>
109 #include <QScrollBar>
110 #include <QTextBlock>
111 #include <QTextCursor>
112 #include <QTextDocument>
113 #include <QTextStream>
114 #include <QChar>
115
116 static QString READY_PROMPT = ">>> ";
117 static QString DOTS_PROMPT  = "... ";
118
119 class DumpCommandsFileValidator : public SUIT_FileValidator
120 {
121  public:
122   DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {};
123   virtual ~DumpCommandsFileValidator() {};
124   virtual bool canSave( const QString& file, bool permissions );
125 };
126
127 bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
128 {
129   QFileInfo fi( file );
130   if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) {
131     SUIT_MessageBox::critical( parent(),
132                                QObject::tr("WRN_WARNING"),
133                                QObject::tr("WRN_FILE_NAME_BAD") );
134     return false;
135   }
136   return SUIT_FileValidator::canSave( file, permissions);
137 }
138
139 void staticCallbackStdout( void* data, char* c )
140 {
141   if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
142     PyConsole_Editor* e = (PyConsole_Editor*)data;
143     e->putLog( QString::fromUtf8(c) );
144     QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), false ) );
145   }
146 }
147
148 void staticCallbackStderr( void* data, char* c )
149 {
150   if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
151     PyConsole_Editor* e = (PyConsole_Editor*)data;
152     e->putLog( QString::fromUtf8(c) );
153     QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), true ) );
154   }
155 }
156
157
158 /*!
159   \brief Constructor. 
160   
161   Creates python editor window.
162   \param theInterp python interper
163   \param theParent parent widget
164 */
165 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, 
166                                     QWidget*          theParent )
167 : QTextEdit( theParent ),
168   myInterp( 0 ),
169   myCmdInHistory( -1 ),
170   myEventLoop( 0 ),
171   myShowBanner( true ),
172   myIsSync( true ),
173   myIsSuppressOutput( false )
174 {
175   QString fntSet( "" );
176   QFont aFont = SUIT_Tools::stringToFont( fntSet );
177   setFont( aFont );
178   setUndoRedoEnabled( false );
179
180   myPrompt = READY_PROMPT;
181   setLineWrapMode( QTextEdit::WidgetWidth );
182   setWordWrapMode( QTextOption::WrapAnywhere );
183   setAcceptRichText( false );
184
185   theInterp->setvoutcb( staticCallbackStdout, this );
186   theInterp->setverrcb( staticCallbackStderr, this );
187
188   // san - This is necessary for troubleless initialization
189   onPyInterpChanged( theInterp );
190 }
191
192 /*!
193   \brief Destructor.
194
195   Does nothing for the moment.
196 */
197 PyConsole_Editor::~PyConsole_Editor()
198 {
199   myInterp->destroy();
200   delete myInterp;
201   myInterp = 0;
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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::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_Editor::execAndWait( const QString& command )
393 {
394   // already running ?
395   if( myEventLoop )
396     return;
397
398   // create new event loop
399   myEventLoop = new QEventLoop( this );
400   // execute command
401   exec( command );
402   // run event loop
403   myEventLoop->exec();
404   // delete event loop after command is processed
405   delete myEventLoop;
406   myEventLoop = 0;
407 }
408
409 /*!
410   \brief Process "Enter" key press event. 
411   
412   Execute the command entered by the user.
413 */
414 void PyConsole_Editor::handleReturn()
415 {
416   // Position cursor at the end
417   QTextCursor curs(textCursor());
418   curs.movePosition(QTextCursor::End);
419   setTextCursor(curs);
420
421   // get last line
422   QTextBlock par = document()->end().previous();
423   if ( !par.isValid() ) return;
424
425   // get command
426   QString cmd = par.text().remove( 0, promptSize() );
427   // extend the command buffer with the current command 
428   myCommandBuffer.append( cmd );
429   // add command to the history
430   if ( !cmd.trimmed().isEmpty() )
431     myHistory.push_back( cmd );
432   putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
433
434   // IPAL19397
435   addText( "", true ); 
436   
437   // set read-only mode
438   setReadOnly( true );
439   // set busy cursor
440   setCursor( Qt::BusyCursor );
441   
442   // post a request to execute Python command;
443   // editor will be informed via a custom event that execution has been completed
444   PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
445 }
446
447 /*!
448   \brief Process drop event.
449
450   Paste dragged text.
451   \param event drop event
452 */
453 void PyConsole_Editor::dropEvent( QDropEvent* event )
454 {
455   // get the initial drop position
456   QPoint pos = event->pos();
457   QTextCursor cur = cursorForPosition( event->pos() );
458   // if the position is not in the last line move it to the end of the command line
459   if ( cur.position() < document()->end().previous().position() + promptSize() ) {
460     moveCursor( QTextCursor::End );
461     pos = cursorRect().center();
462   }
463   // create new drop event and use it instead of the original
464   QDropEvent de( pos,
465                  event->possibleActions(),
466                  event->mimeData(),
467                  event->mouseButtons(),
468                  event->keyboardModifiers(),
469                  event->type() );
470   QTextEdit::dropEvent( &de );
471   // accept the original event
472   event->acceptProposedAction();
473 }
474
475 /*!
476   \brief Process mouse button release event.
477
478   Left mouse button: copy selection to the clipboard.
479   Middle mouse button: paste clipboard's contents.
480   \param event mouse event
481 */
482 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
483 {
484   if ( event->button() == Qt::LeftButton ) {
485     QTextEdit::mouseReleaseEvent( event );
486     //copy();
487   }
488   else if ( event->button() == Qt::MidButton ) {
489     QTextCursor cur = cursorForPosition( event->pos() );
490     // if the position is not in the last line move it to the end of the command line
491     if ( cur.position() < document()->end().previous().position() + promptSize() ) {
492       moveCursor( QTextCursor::End );
493     }
494     else {
495       setTextCursor( cur );
496     }
497     const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? 
498                                                                QClipboard::Selection : QClipboard::Clipboard );
499     if ( md )
500       insertFromMimeData( md );
501   }
502   else {
503     QTextEdit::mouseReleaseEvent( event );
504   }
505 }
506
507 /*!
508   \brief Check if the string is command.
509   
510   Return True if the string \a str is likely to be the command
511   (i.e. it is started from the '>>>' or '...').
512   \param str string to be checked
513 */
514 bool PyConsole_Editor::isCommand( const QString& str ) const
515 {
516   return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
517 }
518
519 /*!
520   \brief Handle keyboard event.
521
522   Implement navigation, history browsing, copy/paste and other common
523   operations.
524   \param event keyboard event
525 */
526 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
527 {
528   // get cursor position
529   QTextCursor cur = textCursor();
530   int curLine = cur.blockNumber();
531   int curCol  = cur.columnNumber();
532
533   // get last edited line
534   int endLine = document()->blockCount()-1;
535
536   // get pressed key code
537   int aKey = event->key();
538
539   // check if <Ctrl> is pressed
540   bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
541   // check if <Shift> is pressed
542   bool shftPressed = event->modifiers() & Qt::ShiftModifier;
543
544   if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
545     // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
546     myCommandBuffer.truncate( 0 );
547     myPrompt = READY_PROMPT;
548     addText( myPrompt, true );
549     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
550     return;
551   }
552   else if ( ctrlPressed && aKey == Qt::Key_C ) {
553     // process <Ctrl>+<C> key-binding : copy
554     copy();
555     return;
556   }
557   else if ( ctrlPressed && aKey == Qt::Key_X ) {
558     // process <Ctrl>+<X> key-binding : cut
559     cut();
560     return;
561   }
562   else if ( ctrlPressed && aKey == Qt::Key_V ) {
563     // process <Ctrl>+<V> key-binding : paste
564     paste();
565     return;
566   }
567
568   // check for printed key
569   // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
570   // Better:
571   aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
572
573   switch ( aKey ) {
574   case 0 :
575     // any printed key: just print it
576     {
577       if ( curLine < endLine || curCol < promptSize() ) {
578         moveCursor( QTextCursor::End );
579       }
580       QTextEdit::keyPressEvent( event );
581       break;
582     }
583   case Qt::Key_Return:
584   case Qt::Key_Enter:
585     // <Enter> key: process the current command
586     {
587       handleReturn();
588       break;
589     }
590   case Qt::Key_Up:
591     // <Up> arrow key: process as follows:
592     // - without <Ctrl>, <Shift> modifiers: previous command in history
593     // - with <Ctrl> modifier key pressed:  move cursor one row up without selection
594     // - with <Shift> modifier key pressed: move cursor one row up with selection
595     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
596     {
597       if ( ctrlPressed && shftPressed ) {
598         int value   = verticalScrollBar()->value();
599         int spacing = fontMetrics().lineSpacing();
600         verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
601       }
602       else if ( shftPressed || ctrlPressed ) {
603         if ( curLine > 0 )
604           moveCursor( QTextCursor::Up, 
605                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
606       }
607       else { 
608         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
609           // set history browsing mode
610           myCmdInHistory = myHistory.count();
611           // remember current command
612           QTextBlock par = document()->end().previous();
613           myCurrentCommand = par.text().remove( 0, promptSize() );
614         }
615         if ( myCmdInHistory > 0 ) {
616           myCmdInHistory--;
617           // get previous command in the history
618           QString previousCommand = myHistory.at( myCmdInHistory );
619           // print previous command
620           moveCursor( QTextCursor::End );
621           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
622           textCursor().removeSelectedText();
623           addText( myPrompt + previousCommand ); 
624           // move cursor to the end
625           moveCursor( QTextCursor::End );
626         }
627       }
628       break;
629     }
630   case Qt::Key_Down:
631     // <Down> arrow key: process as follows:
632     // - without <Ctrl>, <Shift> modifiers: next command in history
633     // - with <Ctrl> modifier key pressed:  move cursor one row down without selection
634     // - with <Shift> modifier key pressed: move cursor one row down with selection
635     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
636     {
637       if ( ctrlPressed && shftPressed ) {
638         int value   = verticalScrollBar()->value();
639         int maxval  = verticalScrollBar()->maximum();
640         int spacing = fontMetrics().lineSpacing();
641         verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
642       }
643       else if ( shftPressed || ctrlPressed) {
644         if ( curLine < endLine )
645           moveCursor( QTextCursor::Down, 
646                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
647       }
648       else { 
649         if ( myCmdInHistory >= 0 ) {
650           // get next command in the history
651           myCmdInHistory++;
652           QString nextCommand;
653           if ( myCmdInHistory < myHistory.count() ) {
654             // next command in history
655             nextCommand = myHistory.at( myCmdInHistory );
656           }
657           else {
658             // end of history is reached
659             // last printed command
660             nextCommand = myCurrentCommand;
661             // unset history browsing mode
662             myCmdInHistory = -1;
663           }
664           // print next or current command
665           moveCursor( QTextCursor::End );
666           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
667           textCursor().removeSelectedText();
668           addText( myPrompt + nextCommand );
669           // move cursor to the end
670           moveCursor( QTextCursor::End );
671         }
672       }
673       break;
674     }
675   case Qt::Key_Left:
676     // <Left> arrow key: process as follows:
677     // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
678     // - with <Ctrl> modifier key pressed:  move one word left (taking into account prompt)
679     // - with <Shift> modifier key pressed: move one symbol left with selection
680     // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
681     {
682       QString txt = textCursor().block().text();
683       if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
684         moveCursor( QTextCursor::Up );
685         moveCursor( QTextCursor::EndOfBlock );
686       }
687       else {
688         QTextEdit::keyPressEvent( event );
689       }
690       break;
691     }
692   case Qt::Key_Right:
693     // <Right> arrow key: process as follows:
694     // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
695     // - with <Ctrl> modifier key pressed:  move one word right (taking into account prompt)
696     // - with <Shift> modifier key pressed: move one symbol right with selection
697     // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
698     {
699       QString txt = textCursor().block().text();
700       if ( !shftPressed ) {
701         if ( curCol < txt.length() ) {
702           if ( isCommand( txt ) && curCol < promptSize() ) {
703             cur.setPosition( cur.block().position() + promptSize() );
704             setTextCursor( cur );
705             break;
706           }
707         }
708         else {
709           if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
710             cur.setPosition( cur.position() + promptSize()+1 );
711             setTextCursor( cur );
712             horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
713             break;
714           }
715         }
716       }
717       QTextEdit::keyPressEvent( event );
718       break;
719     }
720   case Qt::Key_PageUp:
721     // <PageUp> key: process as follows:
722     // - without <Ctrl>, <Shift> modifiers: first command in history
723     // - with <Ctrl> modifier key pressed:  move cursor one page up without selection
724     // - with <Shift> modifier key pressed: move cursor one page up with selection
725     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
726     {
727       if ( ctrlPressed && shftPressed ) {
728         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
729       }
730       else if ( shftPressed || ctrlPressed ) {
731         bool moved = false;
732         qreal lastY = cursorRect( cur ).top();
733         qreal distance = 0;
734         // move using movePosition to keep the cursor's x
735         do {
736           qreal y = cursorRect( cur ).top();
737           distance += qAbs( y - lastY );
738           lastY = y;
739           moved = cur.movePosition( QTextCursor::Up, 
740                                     shftPressed ? QTextCursor::KeepAnchor : 
741                                                   QTextCursor::MoveAnchor );
742         } while ( moved && distance < viewport()->height() );
743         if ( moved ) {
744           cur.movePosition( QTextCursor::Down, 
745                             shftPressed ? QTextCursor::KeepAnchor : 
746                                           QTextCursor::MoveAnchor );
747           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
748         }
749         setTextCursor( cur );
750       }
751       else { 
752         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
753           // set history browsing mode
754           myCmdInHistory = myHistory.count();
755           // remember current command
756           QTextBlock par = document()->end().previous();
757           myCurrentCommand = par.text().remove( 0, promptSize() );
758         }
759         if ( myCmdInHistory > 0 ) {
760           myCmdInHistory = 0;
761           // get very first command in the history
762           QString firstCommand = myHistory.at( myCmdInHistory );
763           // print first command
764           moveCursor( QTextCursor::End );
765           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
766           textCursor().removeSelectedText();
767           addText( myPrompt + firstCommand ); 
768           // move cursor to the end
769           moveCursor( QTextCursor::End );
770         }
771       }
772       break;
773     }
774   case Qt::Key_PageDown:
775     // <PageDown> key: process as follows:
776     // - without <Ctrl>, <Shift> modifiers: last command in history
777     // - with <Ctrl> modifier key pressed:  move cursor one page down without selection
778     // - with <Shift> modifier key pressed: move cursor one page down with selection
779     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
780     {
781       if ( ctrlPressed && shftPressed ) {
782         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
783       }
784       else if ( shftPressed || ctrlPressed ) {
785         bool moved = false;
786         qreal lastY = cursorRect( cur ).top();
787         qreal distance = 0;
788         // move using movePosition to keep the cursor's x
789         do {
790           qreal y = cursorRect( cur ).top();
791           distance += qAbs( y - lastY );
792           lastY = y;
793           moved = cur.movePosition( QTextCursor::Down, 
794                                     shftPressed ? QTextCursor::KeepAnchor : 
795                                                   QTextCursor::MoveAnchor );
796         } while ( moved && distance < viewport()->height() );
797         if ( moved ) {
798           cur.movePosition( QTextCursor::Up, 
799                             shftPressed ? QTextCursor::KeepAnchor : 
800                                           QTextCursor::MoveAnchor );
801           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
802         }
803         setTextCursor( cur );
804       }
805       else { 
806         if ( myCmdInHistory >= 0 ) {
807           // unset history browsing mode
808           myCmdInHistory = -1;
809           // print current command
810           moveCursor( QTextCursor::End );
811           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
812           textCursor().removeSelectedText();
813           addText( myPrompt + myCurrentCommand ); 
814           // move cursor to the end
815           moveCursor( QTextCursor::End );
816         }
817       }
818       break;
819     }
820   case Qt::Key_Home: 
821     // <Home> key: process as follows:
822     // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
823     // - with <Ctrl> modifier key pressed:  move cursor to the very first symbol without selection
824     // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
825     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
826     {
827       if ( ctrlPressed ) { 
828         moveCursor( QTextCursor::Start, 
829                     shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
830       }
831       else {
832         QString txt = textCursor().block().text();
833         if ( isCommand( txt ) ) {
834           if ( shftPressed ) {
835             if ( curCol > promptSize() ) {
836               cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
837               cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
838             }
839           }
840           else {
841             cur.movePosition( QTextCursor::StartOfLine );
842             cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
843           }
844           setTextCursor( cur );
845         }
846         else {
847           moveCursor( QTextCursor::StartOfBlock, 
848                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
849         }
850         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
851       }
852       break;
853     }
854   case Qt::Key_End:
855     // <End> key: process as follows:
856     // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
857     // - with <Ctrl> modifier key pressed:  move cursor to the very last symbol without selection
858     // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
859     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
860     {
861       moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, 
862                   shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
863       break;
864     }  
865   case Qt::Key_Backspace :
866     // <Backspace> key: process as follows
867     // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
868     // - with <Shift> modifier key pressed: delete previous word
869     // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
870     // works only for last (command) line
871     {
872       if ( cur.hasSelection() ) {
873         cut();
874       }
875       else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
876         if ( shftPressed ) {
877           moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
878           textCursor().removeSelectedText();
879         }
880         else if ( ctrlPressed ) {
881           cur.setPosition( document()->end().previous().position() + promptSize(),
882                            QTextCursor::KeepAnchor );
883           setTextCursor( cur );
884           textCursor().removeSelectedText();
885         }
886         else {
887           QTextEdit::keyPressEvent( event );
888         }
889       }
890       else {
891         cur.setPosition( document()->end().previous().position() + promptSize() );
892         setTextCursor( cur );
893         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
894       }
895       break;
896     }
897   case Qt::Key_Delete :
898     // <Delete> key: process as follows
899     // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
900     // - with <Shift> modifier key pressed: delete next word
901     // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
902     // works only for last (command) line
903     {
904       if ( cur.hasSelection() ) {
905         cut();
906       }
907       else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
908         if ( shftPressed ) {
909           moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
910           textCursor().removeSelectedText();
911         }
912         else if ( ctrlPressed ) {
913           moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
914           textCursor().removeSelectedText();
915         }
916         else {
917           QTextEdit::keyPressEvent( event );
918         }
919       }
920       else {
921         cur.setPosition( document()->end().previous().position() + promptSize() );
922         setTextCursor( cur );
923         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
924       }
925       break;
926     }
927   case Qt::Key_Insert :
928     // <Insert> key: process as follows
929     // - with <Ctrl> modifier key pressed:  copy()
930     // - with <Shift> modifier key pressed: paste() to the command line
931     {
932       if ( ctrlPressed ) {
933         copy();
934       }
935       else if ( shftPressed ) {
936         paste();
937       }
938       else
939         QTextEdit::keyPressEvent( event );
940       break;
941     }
942   }
943 }
944
945 /*!
946   \brief Handle notification event coming from Python dispatcher.
947   \param event notification event
948 */
949 void PyConsole_Editor::customEvent( QEvent* event )
950 {
951   switch( event->type() )
952   {
953   case PrintEvent::EVENT_ID:
954     {
955       PrintEvent* pe=(PrintEvent*)event;
956       addText( pe->text(), false, pe->isError());
957       return;
958     }
959   case PyInterp_Event::ES_OK:
960   case PyInterp_Event::ES_ERROR:
961   {
962     // clear command buffer
963     myCommandBuffer.truncate( 0 );
964     // add caret return line if necessary
965     QTextBlock par = document()->end().previous();
966     QString txt = par.text();
967     txt.truncate( txt.length() - 1 );
968     // IPAL19397 : addText moved to handleReturn() method
969     //if ( !txt.isEmpty() )
970     //  addText( "", true );
971     // set "ready" prompt
972     myPrompt = READY_PROMPT;
973     addText( myPrompt );
974     // unset busy cursor
975     unsetCursor();
976     // stop event loop (if running)
977     if ( myEventLoop )
978       myEventLoop->exit();
979     break;
980   }
981   case PyInterp_Event::ES_INCOMPLETE:
982   {
983     // extend command buffer (multi-line command)
984     myCommandBuffer.append( "\n" );
985     // add caret return line if necessary
986     QTextBlock par = document()->end().previous();
987     QString txt = par.text();
988     txt.truncate( txt.length() - 1 );
989     // IPAL19397 : addText moved to handleReturn() method
990     //if ( !txt.isEmpty() )
991     //  addText( "", true );
992     // set "dot" prompt
993     myPrompt = DOTS_PROMPT;
994     addText( myPrompt/*, true*/ ); // IPAL19397
995     // unset busy cursor
996     unsetCursor();
997     // stop event loop (if running)
998     if ( myEventLoop )
999       myEventLoop->exit();
1000     break;
1001   }
1002   default:
1003     QTextEdit::customEvent( event );
1004   }
1005   
1006   // unset read-only state
1007   setReadOnly( false );
1008   // unset history browsing mode
1009   myCmdInHistory = -1;
1010
1011   if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1012   {
1013     // process the next sheduled command from the queue (if there is any)
1014     QString nextcmd = myQueue[0];
1015     myQueue.pop_front();
1016     exec( nextcmd );
1017   }
1018 }
1019
1020 /*!
1021   \brief Handle Python interpreter change.
1022
1023   Perform initialization actions if the interpreter is changed.
1024   \param interp python interpreter is being set
1025 */
1026 void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
1027 {
1028   if ( myInterp != interp 
1029        // Force read-only state and wait cursor when myInterp is NULL
1030       || !myInterp ) {
1031     myInterp = interp;
1032     if ( myInterp ) {
1033       // print banner
1034       myBanner = myInterp->getbanner().c_str();
1035       if ( isShowBanner() )
1036         addText( myBanner );
1037       // clear command buffer
1038       myCommandBuffer.truncate(0);
1039       // unset read-only state
1040       setReadOnly( false );
1041       // unset history browsing mode
1042       myCmdInHistory = -1;
1043       // add prompt
1044       addText( myPrompt );
1045       // unset busy cursor
1046       viewport()->unsetCursor();
1047       // stop event loop (if running)
1048       if( myEventLoop)
1049         myEventLoop->exit();
1050     }
1051     else {
1052       // clear contents
1053       clear();
1054       // set read-only state
1055       setReadOnly( true );
1056       // set busy cursor
1057       setCursor( Qt::WaitCursor );
1058     }
1059   }
1060 }
1061
1062 /*!
1063   \brief "Copy" operation.
1064   
1065   Reimplemented from Qt.
1066   Warning! In Qt4 this method is not virtual.
1067  */
1068 void PyConsole_Editor::cut()
1069 {
1070   QTextCursor cur = textCursor();
1071   if ( cur.hasSelection() ) {
1072     QApplication::clipboard()->setText( cur.selectedText() );
1073     int startSelection = cur.selectionStart();
1074     if ( startSelection < document()->end().previous().position() + promptSize() )
1075       startSelection = document()->end().previous().position() + promptSize();
1076     int endSelection = cur.selectionEnd();
1077     if ( endSelection < document()->end().previous().position() + promptSize() )
1078       endSelection = document()->end().previous().position() + promptSize();
1079     cur.setPosition( startSelection );
1080     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1081     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1082     setTextCursor( cur );
1083     textCursor().removeSelectedText();
1084   }
1085 }
1086
1087 /*!
1088   \brief "Paste" operation.
1089
1090   Reimplemented from Qt.
1091   Warning! In Qt4 this method is not virtual.
1092  */
1093 void PyConsole_Editor::paste()
1094 {
1095   QTextCursor cur = textCursor();
1096   if ( cur.hasSelection() ) {
1097     int startSelection = cur.selectionStart();
1098     if ( startSelection < document()->end().previous().position() + promptSize() )
1099       startSelection = document()->end().previous().position() + promptSize();
1100     int endSelection = cur.selectionEnd();
1101     if ( endSelection < document()->end().previous().position() + promptSize() )
1102       endSelection = document()->end().previous().position() + promptSize();
1103     cur.setPosition( startSelection );
1104     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1105     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1106     setTextCursor( cur );
1107     textCursor().removeSelectedText();
1108   }
1109   if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1110     moveCursor( QTextCursor::End );
1111   QTextEdit::paste();
1112 }
1113
1114 /*!
1115   \brief "Clear" operation.
1116
1117   Reimplemented from Qt.
1118   Warning! In Qt4 this method is not virtual.
1119  */
1120 void PyConsole_Editor::clear()
1121 {
1122   QTextEdit::clear();
1123   if ( isShowBanner() )
1124     addText( myBanner );
1125   myPrompt = READY_PROMPT;
1126   addText( myPrompt );
1127 }
1128
1129 /*!
1130   \brief "Dump commands" operation.
1131  */
1132 void PyConsole_Editor::dump()
1133 {
1134   QStringList aFilters;
1135   aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
1136   
1137   QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1138                                                 aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
1139                                                 false, true, new DumpCommandsFileValidator( this ) );
1140   if ( !fileName.isEmpty() ) {
1141     QFile file( fileName ); 
1142     if ( !file.open( QFile::WriteOnly ) )
1143       return;
1144
1145     QTextStream out (&file);
1146   
1147     for ( int i=0; i<myHistory.count(); i++ ) {
1148       out << myHistory[i] << endl;
1149     }
1150     file.close();
1151   }
1152 }
1153 /*!
1154   \brief "Start log" operation.
1155  */
1156 void PyConsole_Editor::startLog()
1157 {
1158   QStringList aFilters;
1159   aFilters.append( tr( "LOG_FILES_FILTER" ) );
1160
1161   while (1) {
1162     QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1163                                                   aFilters, tr( "TOT_SAVE_PYLOG" ),
1164                                                   false, true );
1165     if ( !fileName.isEmpty() ) {
1166       if ( startLog( fileName ) ) {
1167         break;
1168       }
1169       else {
1170         SUIT_MessageBox::critical( this,
1171                                    QObject::tr("ERR_ERROR"),
1172                                    QObject::tr("ERR_FILE_NOT_WRITABLE") );
1173       }
1174     }
1175     else {
1176       break;
1177     }
1178   }
1179 }
1180
1181 /*!
1182   \brief Start python trace logging
1183   \param fileName the path to the log file
1184   \sa stopLog()
1185  */
1186 bool PyConsole_Editor::startLog( const QString& fileName )
1187 {
1188   bool ok = false;
1189   if ( !fileName.isEmpty() ) {
1190     QFile file( fileName );
1191     if ( file.open( QFile::WriteOnly ) ) {
1192       file.close();
1193       myLogFile = fileName;
1194       ok = true;
1195     }
1196   }
1197   return ok;
1198 }
1199
1200 /*!
1201   \brief "Stop log" operation.
1202   \sa startLog()
1203  */
1204 void PyConsole_Editor::stopLog()
1205 {
1206   myLogFile = QString();
1207 }
1208
1209 /*!
1210   \brief Put string to the log file
1211  */
1212 void PyConsole_Editor::putLog( const QString& s )
1213 {
1214   if ( !myLogFile.isEmpty() ) {
1215     QFile file( myLogFile );
1216     if ( !file.open( QFile::Append ) )
1217       return;
1218     
1219     QTextStream out (&file);
1220     out << s;
1221     
1222     file.close();
1223   }
1224 }