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