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