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