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