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