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