Salome HOME
Update copyrights 2014.
[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     QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, false ) );
143 }
144
145 void staticCallbackStderr( void* data, char* c )
146 {
147   if(!((PyConsole_Editor*)data)->isSuppressOutput())
148     QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, true ) );
149 }
150
151
152 /*!
153   \brief Constructor. 
154   
155   Creates python editor window.
156   \param theInterp python interper
157   \param theParent parent widget
158 */
159 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, 
160                                     QWidget*          theParent )
161 : QTextEdit( theParent ),
162   myInterp( 0 ),
163   myCmdInHistory( -1 ),
164   myEventLoop( 0 ),
165   myShowBanner( true ),
166   myIsSync( false ),
167   myIsSuppressOutput( false )
168 {
169   QString fntSet( "" );
170   QFont aFont = SUIT_Tools::stringToFont( fntSet );
171   setFont( aFont );
172   setUndoRedoEnabled( false );
173
174   myPrompt = READY_PROMPT;
175   setLineWrapMode( QTextEdit::WidgetWidth );
176   setWordWrapMode( QTextOption::WrapAnywhere );
177   setAcceptRichText( false );
178
179   theInterp->setvoutcb( staticCallbackStdout, this );
180   theInterp->setverrcb( staticCallbackStderr, this );
181
182   // san - This is necessary for troubleless initialization
183   onPyInterpChanged( theInterp );
184 }
185
186 /*!
187   \brief Destructor.
188
189   Does nothing for the moment.
190 */
191 PyConsole_Editor::~PyConsole_Editor()
192 {
193 }
194
195 /*!
196   \brief Get synchronous mode flag value.
197   
198   \sa setIsSync()
199   \return True if python console works in synchronous mode
200 */
201 bool PyConsole_Editor::isSync() const
202 {
203   return myIsSync;
204 }
205
206 /*!
207   \brief Set synchronous mode flag value.
208
209   In synhronous mode the Python commands are executed in the GUI thread
210   and the GUI is blocked until the command is finished. In the asynchronous
211   mode each Python command is executed in the separate thread that does not
212   block the main GUI loop.
213
214   \param on synhronous mode flag
215 */
216 void PyConsole_Editor::setIsSync( const bool on )
217 {
218   myIsSync = on;
219 }
220
221 /*!
222   \brief Get suppress output flag value.
223   
224   \sa setIsSuppressOutput()
225   \return \c true if python console output is suppressed.
226 */
227 bool PyConsole_Editor::isSuppressOutput() const
228 {
229   return myIsSuppressOutput;
230 }
231
232 /*!
233   \brief Set suppress output flag value.
234
235   In case if suppress output flag is true, the python 
236   console output suppressed.
237
238   \param on suppress output flag
239 */
240 void PyConsole_Editor::setIsSuppressOutput( const bool on )
241 {
242   myIsSuppressOutput = on;
243 }
244
245 /*!
246   \brief Get 'show banner' flag value.
247   
248   \sa setIsShowBanner()
249   \return \c true if python console shows banner
250 */
251 bool PyConsole_Editor::isShowBanner() const
252 {
253   return myShowBanner;
254 }
255
256 /*!
257   \brief Set 'show banner' flag value.
258
259   The banner is shown in the top of the python console window.
260
261   \sa isShowBanner()
262   \param on 'show banner' flag
263 */
264 void PyConsole_Editor::setIsShowBanner( const bool on )
265 {
266   if ( myShowBanner != on ) {
267     myShowBanner = on;
268     clear();
269   }
270 }
271
272 /*!
273   \brief Get size hint for the Python console window
274   \return size hint value
275 */
276 QSize PyConsole_Editor::sizeHint() const
277 {
278   QFontMetrics fm( font() );
279   int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1;
280   QSize s(100, fm.lineSpacing()*nbLines);
281   return s;
282 }
283
284 /*!
285   \brief Put the string \a str to the python editor.
286   \param str string to be put in the command line of the editor
287   \param newBlock if True, then the string is printed on a new line
288   \param isError if true, the text is printed in dark red
289 */
290 void PyConsole_Editor::addText( const QString& str, 
291                                 const bool     newBlock,
292                                 const bool    isError)
293 {
294   QTextCursor theCursor(textCursor());
295   QTextCharFormat cf;
296
297   moveCursor( QTextCursor::End );
298   if ( newBlock )
299     theCursor.insertBlock();
300   if (isError)
301       cf.setForeground(QBrush(Qt::red));
302   else
303       cf.setForeground(QBrush(Qt::black));
304   theCursor.insertText( str, cf);
305   moveCursor( QTextCursor::End );
306   ensureCursorVisible();
307 }
308
309 /*!
310   \brief Convenient method for executing a Python command,
311   as if the user typed it manually.
312   \param command python command to be executed
313
314   !!! WARNING: doesn't work properly with multi-line commands. !!!
315 */
316 void PyConsole_Editor::exec( const QString& command )
317 {
318   if ( isReadOnly() ) {
319     // some interactive command is being executed in this editor...
320     // shedule the command to the queue
321     myQueue.push_back( command );
322     return;
323   }
324   // remove last line
325   moveCursor( QTextCursor::End );
326   moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
327   textCursor().removeSelectedText();
328   // set "ready" prompt
329   myPrompt = READY_PROMPT;
330   // clear command buffer
331   myCommandBuffer.truncate( 0 );
332   // unset history browsing mode
333   myCmdInHistory = -1;
334   // print command line by line
335   QString cmd = command;
336   if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
337   QStringList lines = command.split( "\n" );
338   for ( int i = 0; i < lines.size(); i++ ) {
339     if ( !lines[i].trimmed().isEmpty() )
340       myHistory.push_back( lines[i] );
341     addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
342   }
343   // IPAL20182
344   addText( "", true );
345   // set read-only mode
346   setReadOnly( true );
347   // set busy cursor
348   setCursor( Qt::BusyCursor );
349   
350   // post a request to execute Python command;
351   // editor will be informed via a custom event that execution has been completed
352   PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) );
353 }
354
355 /*!
356   \brief Create request to the python dispatcher for the command execution.
357
358   \param command python command to be executed
359  */
360 PyInterp_Request* PyConsole_Editor::createRequest( const QString& command )
361 {
362   return new ExecCommand( myInterp, command, this, isSync() );
363 }
364
365 /*!
366   \brief Execute command in the python interpreter
367   and wait until it is finished.
368
369   \param command python command to be executed
370  */
371 void PyConsole_Editor::execAndWait( const QString& command )
372 {
373   // already running ?
374   if( myEventLoop )
375     return;
376
377   // create new event loop
378   myEventLoop = new QEventLoop( this );
379   // execute command
380   exec( command );
381   // run event loop
382   myEventLoop->exec();
383   // delete event loop after command is processed
384   delete myEventLoop;
385   myEventLoop = 0;
386 }
387
388 /*!
389   \brief Process "Enter" key press event. 
390   
391   Execute the command entered by the user.
392 */
393 void PyConsole_Editor::handleReturn()
394 {
395   // Position cursor at the end
396   QTextCursor curs(textCursor());
397   curs.movePosition(QTextCursor::End);
398   setTextCursor(curs);
399
400   // get last line
401   QTextBlock par = document()->end().previous();
402   if ( !par.isValid() ) return;
403
404   // get command
405   QString cmd = par.text().remove( 0, promptSize() );
406   // extend the command buffer with the current command 
407   myCommandBuffer.append( cmd );
408   // add command to the history
409   if ( !cmd.trimmed().isEmpty() )
410     myHistory.push_back( cmd );
411
412   // IPAL19397
413   addText( "", true ); 
414   
415   // set read-only mode
416   setReadOnly( true );
417   // set busy cursor
418   setCursor( Qt::BusyCursor );
419   
420   // post a request to execute Python command;
421   // editor will be informed via a custom event that execution has been completed
422   PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
423 }
424
425 /*!
426   \brief Process drop event.
427
428   Paste dragged text.
429   \param event drop event
430 */
431 void PyConsole_Editor::dropEvent( QDropEvent* event )
432 {
433   // get the initial drop position
434   QPoint pos = event->pos();
435   QTextCursor cur = cursorForPosition( event->pos() );
436   // if the position is not in the last line move it to the end of the command line
437   if ( cur.position() < document()->end().previous().position() + promptSize() ) {
438     moveCursor( QTextCursor::End );
439     pos = cursorRect().center();
440   }
441   // create new drop event and use it instead of the original
442   QDropEvent de( pos,
443                  event->possibleActions(),
444                  event->mimeData(),
445                  event->mouseButtons(),
446                  event->keyboardModifiers(),
447                  event->type() );
448   QTextEdit::dropEvent( &de );
449   // accept the original event
450   event->acceptProposedAction();
451 }
452
453 /*!
454   \brief Process mouse button release event.
455
456   Left mouse button: copy selection to the clipboard.
457   Middle mouse button: paste clipboard's contents.
458   \param event mouse event
459 */
460 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
461 {
462   if ( event->button() == Qt::LeftButton ) {
463     QTextEdit::mouseReleaseEvent( event );
464     //copy();
465   }
466   else if ( event->button() == Qt::MidButton ) {
467     QTextCursor cur = cursorForPosition( event->pos() );
468     // if the position is not in the last line move it to the end of the command line
469     if ( cur.position() < document()->end().previous().position() + promptSize() ) {
470       moveCursor( QTextCursor::End );
471     }
472     else {
473       setTextCursor( cur );
474     }
475     const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? 
476                                                                QClipboard::Selection : QClipboard::Clipboard );
477     if ( md )
478       insertFromMimeData( md );
479   }
480   else {
481     QTextEdit::mouseReleaseEvent( event );
482   }
483 }
484
485 /*!
486   \brief Check if the string is command.
487   
488   Return True if the string \a str is likely to be the command
489   (i.e. it is started from the '>>>' or '...').
490   \param str string to be checked
491 */
492 bool PyConsole_Editor::isCommand( const QString& str ) const
493 {
494   return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
495 }
496
497 /*!
498   \brief Handle keyboard event.
499
500   Implement navigation, history browsing, copy/paste and other common
501   operations.
502   \param event keyboard event
503 */
504 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
505 {
506   // get cursor position
507   QTextCursor cur = textCursor();
508   int curLine = cur.blockNumber();
509   int curCol  = cur.columnNumber();
510
511   // get last edited line
512   int endLine = document()->blockCount()-1;
513
514   // get pressed key code
515   int aKey = event->key();
516
517   // check if <Ctrl> is pressed
518   bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
519   // check if <Shift> is pressed
520   bool shftPressed = event->modifiers() & Qt::ShiftModifier;
521
522   if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
523     // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
524     myCommandBuffer.truncate( 0 );
525     myPrompt = READY_PROMPT;
526     addText( myPrompt, true );
527     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
528     return;
529   }
530   else if ( ctrlPressed && aKey == Qt::Key_C ) {
531     // process <Ctrl>+<C> key-binding : copy
532     copy();
533     return;
534   }
535   else if ( ctrlPressed && aKey == Qt::Key_X ) {
536     // process <Ctrl>+<X> key-binding : cut
537     cut();
538     return;
539   }
540   else if ( ctrlPressed && aKey == Qt::Key_V ) {
541     // process <Ctrl>+<V> key-binding : paste
542     paste();
543     return;
544   }
545
546   // check for printed key
547   // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
548   // Better:
549   aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
550
551   switch ( aKey ) {
552   case 0 :
553     // any printed key: just print it
554     {
555       if ( curLine < endLine || curCol < promptSize() ) {
556         moveCursor( QTextCursor::End );
557       }
558       QTextEdit::keyPressEvent( event );
559       break;
560     }
561   case Qt::Key_Return:
562   case Qt::Key_Enter:
563     // <Enter> key: process the current command
564     {
565       handleReturn();
566       break;
567     }
568   case Qt::Key_Up:
569     // <Up> arrow key: process as follows:
570     // - without <Ctrl>, <Shift> modifiers: previous command in history
571     // - with <Ctrl> modifier key pressed:  move cursor one row up without selection
572     // - with <Shift> modifier key pressed: move cursor one row up with selection
573     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
574     {
575       if ( ctrlPressed && shftPressed ) {
576         int value   = verticalScrollBar()->value();
577         int spacing = fontMetrics().lineSpacing();
578         verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
579       }
580       else if ( shftPressed || ctrlPressed ) {
581         if ( curLine > 0 )
582           moveCursor( QTextCursor::Up, 
583                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
584       }
585       else { 
586         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
587           // set history browsing mode
588           myCmdInHistory = myHistory.count();
589           // remember current command
590           QTextBlock par = document()->end().previous();
591           myCurrentCommand = par.text().remove( 0, promptSize() );
592         }
593         if ( myCmdInHistory > 0 ) {
594           myCmdInHistory--;
595           // get previous command in the history
596           QString previousCommand = myHistory.at( myCmdInHistory );
597           // print previous command
598           moveCursor( QTextCursor::End );
599           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
600           textCursor().removeSelectedText();
601           addText( myPrompt + previousCommand ); 
602           // move cursor to the end
603           moveCursor( QTextCursor::End );
604         }
605       }
606       break;
607     }
608   case Qt::Key_Down:
609     // <Down> arrow key: process as follows:
610     // - without <Ctrl>, <Shift> modifiers: next command in history
611     // - with <Ctrl> modifier key pressed:  move cursor one row down without selection
612     // - with <Shift> modifier key pressed: move cursor one row down with selection
613     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
614     {
615       if ( ctrlPressed && shftPressed ) {
616         int value   = verticalScrollBar()->value();
617         int maxval  = verticalScrollBar()->maximum();
618         int spacing = fontMetrics().lineSpacing();
619         verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
620       }
621       else if ( shftPressed || ctrlPressed) {
622         if ( curLine < endLine )
623           moveCursor( QTextCursor::Down, 
624                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
625       }
626       else { 
627         if ( myCmdInHistory >= 0 ) {
628           // get next command in the history
629           myCmdInHistory++;
630           QString nextCommand;
631           if ( myCmdInHistory < myHistory.count() ) {
632             // next command in history
633             nextCommand = myHistory.at( myCmdInHistory );
634           }
635           else {
636             // end of history is reached
637             // last printed command
638             nextCommand = myCurrentCommand;
639             // unset history browsing mode
640             myCmdInHistory = -1;
641           }
642           // print next or current command
643           moveCursor( QTextCursor::End );
644           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
645           textCursor().removeSelectedText();
646           addText( myPrompt + nextCommand );
647           // move cursor to the end
648           moveCursor( QTextCursor::End );
649         }
650       }
651       break;
652     }
653   case Qt::Key_Left:
654     // <Left> arrow key: process as follows:
655     // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
656     // - with <Ctrl> modifier key pressed:  move one word left (taking into account prompt)
657     // - with <Shift> modifier key pressed: move one symbol left with selection
658     // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
659     {
660       QString txt = textCursor().block().text();
661       if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
662         moveCursor( QTextCursor::Up );
663         moveCursor( QTextCursor::EndOfBlock );
664       }
665       else {
666         QTextEdit::keyPressEvent( event );
667       }
668       break;
669     }
670   case Qt::Key_Right:
671     // <Right> arrow key: process as follows:
672     // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
673     // - with <Ctrl> modifier key pressed:  move one word right (taking into account prompt)
674     // - with <Shift> modifier key pressed: move one symbol right with selection
675     // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
676     {
677       QString txt = textCursor().block().text();
678       if ( !shftPressed ) {
679         if ( curCol < txt.length() ) {
680           if ( isCommand( txt ) && curCol < promptSize() ) {
681             cur.setPosition( cur.block().position() + promptSize() );
682             setTextCursor( cur );
683             break;
684           }
685         }
686         else {
687           if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
688             cur.setPosition( cur.position() + promptSize()+1 );
689             setTextCursor( cur );
690             horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
691             break;
692           }
693         }
694       }
695       QTextEdit::keyPressEvent( event );
696       break;
697     }
698   case Qt::Key_PageUp:
699     // <PageUp> key: process as follows:
700     // - without <Ctrl>, <Shift> modifiers: first command in history
701     // - with <Ctrl> modifier key pressed:  move cursor one page up without selection
702     // - with <Shift> modifier key pressed: move cursor one page up with selection
703     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
704     {
705       if ( ctrlPressed && shftPressed ) {
706         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
707       }
708       else if ( shftPressed || ctrlPressed ) {
709         bool moved = false;
710         qreal lastY = cursorRect( cur ).top();
711         qreal distance = 0;
712         // move using movePosition to keep the cursor's x
713         do {
714           qreal y = cursorRect( cur ).top();
715           distance += qAbs( y - lastY );
716           lastY = y;
717           moved = cur.movePosition( QTextCursor::Up, 
718                                     shftPressed ? QTextCursor::KeepAnchor : 
719                                                   QTextCursor::MoveAnchor );
720         } while ( moved && distance < viewport()->height() );
721         if ( moved ) {
722           cur.movePosition( QTextCursor::Down, 
723                             shftPressed ? QTextCursor::KeepAnchor : 
724                                           QTextCursor::MoveAnchor );
725           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
726         }
727         setTextCursor( cur );
728       }
729       else { 
730         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
731           // set history browsing mode
732           myCmdInHistory = myHistory.count();
733           // remember current command
734           QTextBlock par = document()->end().previous();
735           myCurrentCommand = par.text().remove( 0, promptSize() );
736         }
737         if ( myCmdInHistory > 0 ) {
738           myCmdInHistory = 0;
739           // get very first command in the history
740           QString firstCommand = myHistory.at( myCmdInHistory );
741           // print first command
742           moveCursor( QTextCursor::End );
743           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
744           textCursor().removeSelectedText();
745           addText( myPrompt + firstCommand ); 
746           // move cursor to the end
747           moveCursor( QTextCursor::End );
748         }
749       }
750       break;
751     }
752   case Qt::Key_PageDown:
753     // <PageDown> key: process as follows:
754     // - without <Ctrl>, <Shift> modifiers: last command in history
755     // - with <Ctrl> modifier key pressed:  move cursor one page down without selection
756     // - with <Shift> modifier key pressed: move cursor one page down with selection
757     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
758     {
759       if ( ctrlPressed && shftPressed ) {
760         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
761       }
762       else if ( shftPressed || ctrlPressed ) {
763         bool moved = false;
764         qreal lastY = cursorRect( cur ).top();
765         qreal distance = 0;
766         // move using movePosition to keep the cursor's x
767         do {
768           qreal y = cursorRect( cur ).top();
769           distance += qAbs( y - lastY );
770           lastY = y;
771           moved = cur.movePosition( QTextCursor::Down, 
772                                     shftPressed ? QTextCursor::KeepAnchor : 
773                                                   QTextCursor::MoveAnchor );
774         } while ( moved && distance < viewport()->height() );
775         if ( moved ) {
776           cur.movePosition( QTextCursor::Up, 
777                             shftPressed ? QTextCursor::KeepAnchor : 
778                                           QTextCursor::MoveAnchor );
779           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
780         }
781         setTextCursor( cur );
782       }
783       else { 
784         if ( myCmdInHistory >= 0 ) {
785           // unset history browsing mode
786           myCmdInHistory = -1;
787           // print current command
788           moveCursor( QTextCursor::End );
789           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
790           textCursor().removeSelectedText();
791           addText( myPrompt + myCurrentCommand ); 
792           // move cursor to the end
793           moveCursor( QTextCursor::End );
794         }
795       }
796       break;
797     }
798   case Qt::Key_Home: 
799     // <Home> key: process as follows:
800     // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
801     // - with <Ctrl> modifier key pressed:  move cursor to the very first symbol without selection
802     // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
803     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
804     {
805       if ( ctrlPressed ) { 
806         moveCursor( QTextCursor::Start, 
807                     shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
808       }
809       else {
810         QString txt = textCursor().block().text();
811         if ( isCommand( txt ) ) {
812           if ( shftPressed ) {
813             if ( curCol > promptSize() ) {
814               cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
815               cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
816             }
817           }
818           else {
819             cur.movePosition( QTextCursor::StartOfLine );
820             cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
821           }
822           setTextCursor( cur );
823         }
824         else {
825           moveCursor( QTextCursor::StartOfBlock, 
826                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
827         }
828         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
829       }
830       break;
831     }
832   case Qt::Key_End:
833     // <End> key: process as follows:
834     // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
835     // - with <Ctrl> modifier key pressed:  move cursor to the very last symbol without selection
836     // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
837     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
838     {
839       moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, 
840                   shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
841       break;
842     }  
843   case Qt::Key_Backspace :
844     // <Backspace> key: process as follows
845     // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
846     // - with <Shift> modifier key pressed: delete previous word
847     // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
848     // works only for last (command) line
849     {
850       if ( cur.hasSelection() ) {
851         cut();
852       }
853       else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
854         if ( shftPressed ) {
855           moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
856           textCursor().removeSelectedText();
857         }
858         else if ( ctrlPressed ) {
859           cur.setPosition( document()->end().previous().position() + promptSize(),
860                            QTextCursor::KeepAnchor );
861           setTextCursor( cur );
862           textCursor().removeSelectedText();
863         }
864         else {
865           QTextEdit::keyPressEvent( event );
866         }
867       }
868       else {
869         cur.setPosition( document()->end().previous().position() + promptSize() );
870         setTextCursor( cur );
871         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
872       }
873       break;
874     }
875   case Qt::Key_Delete :
876     // <Delete> key: process as follows
877     // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
878     // - with <Shift> modifier key pressed: delete next word
879     // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
880     // works only for last (command) line
881     {
882       if ( cur.hasSelection() ) {
883         cut();
884       }
885       else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
886         if ( shftPressed ) {
887           moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
888           textCursor().removeSelectedText();
889         }
890         else if ( ctrlPressed ) {
891           moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
892           textCursor().removeSelectedText();
893         }
894         else {
895           QTextEdit::keyPressEvent( event );
896         }
897       }
898       else {
899         cur.setPosition( document()->end().previous().position() + promptSize() );
900         setTextCursor( cur );
901         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
902       }
903       break;
904     }
905   case Qt::Key_Insert :
906     // <Insert> key: process as follows
907     // - with <Ctrl> modifier key pressed:  copy()
908     // - with <Shift> modifier key pressed: paste() to the command line
909     {
910       if ( ctrlPressed ) {
911         copy();
912       }
913       else if ( shftPressed ) {
914         paste();
915       }
916       else
917         QTextEdit::keyPressEvent( event );
918       break;
919     }
920   }
921 }
922
923 /*!
924   \brief Handle notification event coming from Python dispatcher.
925   \param event notification event
926 */
927 void PyConsole_Editor::customEvent( QEvent* event )
928 {
929   switch( event->type() )
930   {
931   case PrintEvent::EVENT_ID:
932     {
933       PrintEvent* pe=(PrintEvent*)event;
934       addText( pe->text(), false, pe->isError());
935       return;
936     }
937   case PyInterp_Event::ES_OK:
938   case PyInterp_Event::ES_ERROR:
939   {
940     // clear command buffer
941     myCommandBuffer.truncate( 0 );
942     // add caret return line if necessary
943     QTextBlock par = document()->end().previous();
944     QString txt = par.text();
945     txt.truncate( txt.length() - 1 );
946     // IPAL19397 : addText moved to handleReturn() method
947     //if ( !txt.isEmpty() )
948     //  addText( "", true );
949     // set "ready" prompt
950     myPrompt = READY_PROMPT;
951     addText( myPrompt );
952     // unset busy cursor
953     unsetCursor();
954     // stop event loop (if running)
955     if ( myEventLoop )
956       myEventLoop->exit();
957     break;
958   }
959   case PyInterp_Event::ES_INCOMPLETE:
960   {
961     // extend command buffer (multi-line command)
962     myCommandBuffer.append( "\n" );
963     // add caret return line if necessary
964     QTextBlock par = document()->end().previous();
965     QString txt = par.text();
966     txt.truncate( txt.length() - 1 );
967     // IPAL19397 : addText moved to handleReturn() method
968     //if ( !txt.isEmpty() )
969     //  addText( "", true );
970     // set "dot" prompt
971     myPrompt = DOTS_PROMPT;
972     addText( myPrompt/*, true*/ ); // IPAL19397
973     // unset busy cursor
974     unsetCursor();
975     // stop event loop (if running)
976     if ( myEventLoop )
977       myEventLoop->exit();
978     break;
979   }
980   default:
981     QTextEdit::customEvent( event );
982   }
983   
984   // unset read-only state
985   setReadOnly( false );
986   // unset history browsing mode
987   myCmdInHistory = -1;
988
989   if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
990   {
991     // process the next sheduled command from the queue (if there is any)
992     QString nextcmd = myQueue[0];
993     myQueue.pop_front();
994     exec( nextcmd );
995   }
996 }
997
998 /*!
999   \brief Handle Python interpreter change.
1000
1001   Perform initialization actions if the interpreter is changed.
1002   \param interp python interpreter is being set
1003 */
1004 void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
1005 {
1006   if ( myInterp != interp 
1007        // Force read-only state and wait cursor when myInterp is NULL
1008       || !myInterp ) {
1009     myInterp = interp;
1010     if ( myInterp ) {
1011       // print banner
1012       myBanner = myInterp->getbanner().c_str();
1013       if ( isShowBanner() )
1014         addText( myBanner );
1015       // clear command buffer
1016       myCommandBuffer.truncate(0);
1017       // unset read-only state
1018       setReadOnly( false );
1019       // unset history browsing mode
1020       myCmdInHistory = -1;
1021       // add prompt
1022       addText( myPrompt );
1023       // unset busy cursor
1024       viewport()->unsetCursor();
1025       // stop event loop (if running)
1026       if( myEventLoop)
1027         myEventLoop->exit();
1028     }
1029     else {
1030       // clear contents
1031       clear();
1032       // set read-only state
1033       setReadOnly( true );
1034       // set busy cursor
1035       setCursor( Qt::WaitCursor );
1036     }
1037   }
1038 }
1039
1040 /*!
1041   \brief "Copy" operation.
1042   
1043   Reimplemented from Qt.
1044   Warning! In Qt4 this method is not virtual.
1045  */
1046 void PyConsole_Editor::cut()
1047 {
1048   QTextCursor cur = textCursor();
1049   if ( cur.hasSelection() ) {
1050     QApplication::clipboard()->setText( cur.selectedText() );
1051     int startSelection = cur.selectionStart();
1052     if ( startSelection < document()->end().previous().position() + promptSize() )
1053       startSelection = document()->end().previous().position() + promptSize();
1054     int endSelection = cur.selectionEnd();
1055     if ( endSelection < document()->end().previous().position() + promptSize() )
1056       endSelection = document()->end().previous().position() + promptSize();
1057     cur.setPosition( startSelection );
1058     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1059     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1060     setTextCursor( cur );
1061     textCursor().removeSelectedText();
1062   }
1063 }
1064
1065 /*!
1066   \brief "Paste" operation.
1067
1068   Reimplemented from Qt.
1069   Warning! In Qt4 this method is not virtual.
1070  */
1071 void PyConsole_Editor::paste()
1072 {
1073   QTextCursor cur = textCursor();
1074   if ( cur.hasSelection() ) {
1075     int startSelection = cur.selectionStart();
1076     if ( startSelection < document()->end().previous().position() + promptSize() )
1077       startSelection = document()->end().previous().position() + promptSize();
1078     int endSelection = cur.selectionEnd();
1079     if ( endSelection < document()->end().previous().position() + promptSize() )
1080       endSelection = document()->end().previous().position() + promptSize();
1081     cur.setPosition( startSelection );
1082     cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1083     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1084     setTextCursor( cur );
1085     textCursor().removeSelectedText();
1086   }
1087   if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1088     moveCursor( QTextCursor::End );
1089   QTextEdit::paste();
1090 }
1091
1092 /*!
1093   \brief "Clear" operation.
1094
1095   Reimplemented from Qt.
1096   Warning! In Qt4 this method is not virtual.
1097  */
1098 void PyConsole_Editor::clear()
1099 {
1100   QTextEdit::clear();
1101   if ( isShowBanner() )
1102     addText( myBanner );
1103   myPrompt = READY_PROMPT;
1104   addText( myPrompt );
1105 }
1106
1107 /*!
1108   \brief "Dump commands" operation.
1109  */
1110 void PyConsole_Editor::dump()
1111 {
1112   QStringList aFilters;
1113   aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
1114   
1115   QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1116                                      aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
1117                                      false, true, new DumpCommandsFileValidator( this ) );
1118   if ( fileName != "" ) {
1119     QFile file( fileName ); 
1120     if ( !file.open( QFile::WriteOnly ) )
1121       return;
1122
1123     QTextStream out (&file);
1124   
1125     for( int i=0; i<myHistory.count(); i++ ) {
1126          out<<myHistory[i]<<endl;
1127     }
1128     file.close();
1129   }
1130 }