]> SALOME platform Git repositories - modules/gui.git/blob - tools/PyConsole/src/PyConsole_Editor.cxx
Salome HOME
7d540f62f298efc5c2e6cc8d418e9b1a42e7a06b
[modules/gui.git] / tools / PyConsole / src / PyConsole_Editor.cxx
1 // Copyright (C) 2007-2016  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 // File   : PyConsole_Editor.cxx
23 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
24
25 /*!
26   \class PyConsole_Editor
27   \brief Python command line interpreter front-end GUI widget.
28   
29   This class provides simple GUI interface to the Python interpreter, including basic 
30   navigation operations, executing commands (both interactively and programmatically), 
31   copy-paste operations, history of the commands and so on.
32
33   Here below is the shortcut keyboard boundings used for navigation and other operations:
34   - <Enter>              : execute current command
35   - <Ctrl><Break>        : clear current command
36   - <Escape>             : clear current command
37   - <Up>                 : previous command in the history
38   - <Shift><Up>          : move cursor one row up with selection
39   - <Ctrl><Up>           : move cursor one row up without selection
40   - <Ctrl><Shift><Up>    : move cursor one row up with selection
41   - <Down>               : next command in the history
42   - <Shift><Down>        : move cursor one row down with selection
43   - <Ctrl><Down>         : move cursor one row down without selection
44   - <Ctrl><Shift><Down>  : move cursor one row down with selection
45   - <Left>               : move one symbol left without selection
46   - <Shift><Left>        : move one symbol left with selection
47   - <Ctrl><Left>         : move one word left without selection
48   - <Ctrl><Shift><Left>  : move one word left with selection
49   - <Right>              : move one symbol right without selection
50   - <Shift><Right>       : move one symbol right with selection
51   - <Ctrl><Right>        : move one word right without selection
52   - <Ctrl><Shift><Right> : move one word right with selection
53   - <PgUp>               : first command in the history
54   - <Shift><PgUp>        : move one page up with selection
55   - <Ctrl><PgUp>         : move one page up without selection
56   - <Ctrl><Shift><PgUp>  : scroll one page up
57   - <PgDn>               : last command in the history
58   - <Shift><PgDn>        : move one page down with selection
59   - <Ctrl><PgDn>         : move one page down without selection
60   - <Ctrl><Shift><PgDn>  : scroll one page down
61   - <Home>               : move to the beginning of the line without selection
62   - <Shift><Home>        : move to the beginning of the line with selection
63   - <Ctrl><Home>         : move to the very first symbol without selection
64   - <Ctrl><Shift><Home>  : move to the very first symbol with selection
65   - <End>                : move to the end of the line without selection
66   - <Shift><End>         : move to the end of the line with selection
67   - <Ctrl><End>          : move to the very last symbol without selection
68   - <Ctrl><Shift><End>   : move to the very last symbol with selection
69   - <Backspace>          : delete symbol before the cursor
70                            / remove selected text and put it to the clipboard (cut)
71   - <Shift><Backspace>   : delete previous word
72                            / remove selected text and put it to the clipboard (cut)
73   - <Ctrl><Backspace>    : delete text from the cursor to the beginning of the line 
74                            / remove selected text and put it to the clipboard (cut)
75   - <Delete>             : delete symbol after the cursor 
76                            / remove selected text and put it to the clipboard (cut)
77   - <Shift><Delete>      : delete next word
78                            / remove selected text and put it to the clipboard (cut)
79   - <Ctrl><Delete>       : delete text from the cursor to the end of the line
80                            / remove selected text and put it to the clipboard (cut)
81   - <Ctrl><Insert>       : copy
82   - <Shift><Insert>      : paste
83   - <Ctrl><V>            : paste
84   - <Ctrl><C>            : copy
85   - <Ctrl><X>            : cut
86   - <Ctrl><V>            : paste
87   - <Tab>                : performs auto-completion
88   - <Ctrl><Tab>          : undoes auto-completion
89 */
90
91 #include "PyConsole_Editor.h"
92 #include "PyConsole_Interp.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Dispatcher.h"
95 #include "PyConsole_Request.h"
96
97 #include <QApplication>
98 #include <QClipboard>
99 #include <QDropEvent>
100 #include <QEvent>
101 #include <QKeyEvent>
102 #include <QMimeData>
103 #include <QMouseEvent>
104 #include <QScrollBar>
105 #include <QTextBlock>
106 #include <QTextCursor>
107 #include <QTextStream>
108 #include <QChar>
109 #include <QFileDialog>
110 #include <QMessageBox>
111
112 //VSR: uncomment below macro to support unicode text properly in SALOME
113 //     current commented out due to regressions
114 //#define PAL22528_UNICODE
115
116 namespace
117 {
118   QString fromUtf8( const char* txt )
119   {
120 #ifdef PAL22528_UNICODE
121     return QString::fromUtf8( txt );
122 #else
123     return QString( txt );
124 #endif
125   }
126 }
127
128 static QString READY_PROMPT = ">>> ";
129 static QString DOTS_PROMPT  = "... ";
130
131 void staticCallbackStdout( void* data, char* c )
132 {
133   if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
134     PyConsole_Editor* e = (PyConsole_Editor*)data;
135     QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), false ) );
136   }
137 }
138
139 void staticCallbackStderr( void* data, char* c )
140 {
141   if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
142     PyConsole_Editor* e = (PyConsole_Editor*)data;
143     QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), true ) );
144   }
145 }
146
147 /*!
148   \brief Constructor. 
149   
150   Creates python editor window.
151   \param theInterp python interper
152   \param theParent parent widget
153 */
154 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, 
155                                     QWidget*          theParent )
156 : QTextEdit( theParent ),
157   myInterp( theInterp ),
158   myCmdInHistory( -1 ),
159   myEventLoop( 0 ),
160   myShowBanner( true ),
161   myIsSync( true ),
162   myIsSuppressOutput( false ),
163   myMultiLinePaste( false ),
164   myAutoCompletion( false ),
165   myTabMode( false ),
166   myComplCursorPos( -1 )
167 {
168   setFont( QFont( "Courier", 11 ) ); // default font
169   setUndoRedoEnabled( false );
170
171   myPrompt = READY_PROMPT;
172   setLineWrapMode( QTextEdit::WidgetWidth );
173   setWordWrapMode( QTextOption::WrapAnywhere );
174   setAcceptRichText( false );
175
176   // set callbacks to interpeter
177   myInterp->setvoutcb( staticCallbackStdout, this );
178   myInterp->setverrcb( staticCallbackStderr, this );
179   // print banner
180   if ( isShowBanner() )
181     addText( banner() );
182   // clear command buffer
183   myCommandBuffer.truncate(0);
184   // unset read-only state
185   setReadOnly( false );
186   // unset history browsing mode
187   myCmdInHistory = -1;
188   // add prompt
189   addText( myPrompt );
190   // unset busy cursor
191   viewport()->unsetCursor();
192 }
193
194 /*!
195   \brief Destructor.
196 */
197 PyConsole_Editor::~PyConsole_Editor()
198 {
199   myInterp = 0;
200 }
201
202 /*!
203   \brief Get Python interpreter
204 */
205 PyConsole_Interp* PyConsole_Editor::getInterp() const
206 {
207   return myInterp;
208 }
209
210 /*!
211   \brief Get synchronous mode flag value.
212   
213   \sa setIsSync()
214   \return \c true if python console works in synchronous mode
215 */
216 bool PyConsole_Editor::isSync() const
217 {
218   return myIsSync;
219 }
220
221 /*!
222   \brief Set synchronous mode flag value.
223
224   In synhronous mode the Python commands are executed in the GUI thread
225   and the GUI is blocked until the command is finished. In the asynchronous
226   mode each Python command is executed in the separate thread that does not
227   block the main GUI loop.
228
229   \param on synhronous mode flag
230 */
231 void PyConsole_Editor::setIsSync( const bool on )
232 {
233   myIsSync = on;
234 }
235
236 /*!
237   \brief Get suppress output flag value.
238   
239   \sa setIsSuppressOutput()
240   \return \c true if python console output is suppressed.
241 */
242 bool PyConsole_Editor::isSuppressOutput() const
243 {
244   return myIsSuppressOutput;
245 }
246
247 /*!
248   \brief Set suppress output flag value.
249
250   In case if suppress output flag is \c true, the python 
251   console output suppressed.
252
253   \param on suppress output flag
254 */
255 void PyConsole_Editor::setIsSuppressOutput( const bool on )
256 {
257   myIsSuppressOutput = on;
258 }
259
260 /*!
261   \brief Get 'show banner' flag value.
262   
263   \sa setIsShowBanner()
264   \return \c true if python console shows banner
265 */
266 bool PyConsole_Editor::isShowBanner() const
267 {
268   return myShowBanner;
269 }
270
271 /*!
272   \brief Set 'show banner' flag value.
273
274   The banner is shown in the top of the python console window.
275
276   \sa isShowBanner()
277   \param on 'show banner' flag
278 */
279 void PyConsole_Editor::setIsShowBanner( const bool on )
280 {
281   if ( myShowBanner != on ) {
282     myShowBanner = on;
283     clear();
284   }
285 }
286
287 /*!
288   \brief Switch on/off commands auto-completion feature
289   \sa autoCompletion()
290 */
291 void PyConsole_Editor::setAutoCompletion( bool on )
292 {
293   myAutoCompletion = on;
294   document()->setUndoRedoEnabled( myAutoCompletion );
295 }
296   
297 /*!
298   \brief Returns \c true if auto-completion feature is switched on
299   or \c false otherwise
300   \sa setAutoCompletion()
301 */
302 bool PyConsole_Editor::autoCompletion() const
303 {
304   return myAutoCompletion;
305 }
306
307 /*!
308   \brief Check if trace logging is switched on.
309   
310   \sa startLog(), stopLog()
311   \return \c true if trace logging is switched on
312 */
313 bool PyConsole_Editor::isLogging() const
314 {
315   return !myLogFile.isEmpty();
316 }
317
318 /*!
319   \brief Get size hint for the Python console window
320   \return size hint value
321 */
322 QSize PyConsole_Editor::sizeHint() const
323 {
324   QFontMetrics fm( font() );
325   int nbLines = ( isShowBanner() ? banner().split("\n").count() : 0 ) + 1;
326   QSize s(100, fm.lineSpacing()*nbLines);
327   return s;
328 }
329
330 /*!
331   \brief Put the string \a str to the python editor.
332   \param str string to be put in the command line of the editor
333   \param newBlock if \c true, then the string is printed on a new line
334   \param isError if \c true, the text is printed in dark red
335 */
336 void PyConsole_Editor::addText( const QString& str, 
337                                 const bool     newBlock,
338                                 const bool     isError )
339 {
340   QTextCursor aCursor = textCursor();
341   QTextCharFormat cf;
342
343   moveCursor( QTextCursor::End );
344   if ( newBlock )
345     aCursor.insertBlock();
346   if ( isError )
347     cf.setForeground( QBrush( Qt::red ) );
348   else
349     cf.setForeground( QBrush( Qt::black ) );
350   aCursor.insertText( str, cf );
351   moveCursor( QTextCursor::End );
352   ensureCursorVisible();
353 }
354
355 /*!
356   \brief Convenient method for executing a Python command,
357   as if the user typed it manually.
358   \param command python command to be executed
359
360   !!! WARNING: doesn't work properly with multi-line commands. !!!
361 */
362 void PyConsole_Editor::exec( const QString& command )
363 {
364   if ( isReadOnly() ) {
365     // some interactive command is being executed in this editor...
366     // shedule the command to the queue
367     myQueue.push_back( command );
368     return;
369   }
370
371   // remove last line
372   moveCursor( QTextCursor::End );
373   moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
374   textCursor().removeSelectedText();
375
376   // set "ready" prompt
377   myPrompt = READY_PROMPT;
378
379   // clear command buffer
380   myCommandBuffer.truncate( 0 );
381
382   // unset history browsing mode
383   myCmdInHistory = -1;
384
385   // print command line by line
386   QString cmd = command;
387   if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
388   QStringList lines = command.split( "\n" );
389   for ( int i = 0; i < lines.size(); i++ ) {
390     if ( !lines[i].trimmed().isEmpty() )
391       myHistory.push_back( lines[i] );
392     addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
393     putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
394   }
395
396   // IPAL20182
397   addText( "", true );
398
399   // set read-only mode
400   setReadOnly( true );
401
402   // set busy cursor
403   setCursor( Qt::BusyCursor );
404   
405   // post a request to execute Python command;
406   // editor will be informed via a custom event that execution has been completed
407   PyInterp_Dispatcher::Get()->Exec( createCmdRequest( cmd ) );
408 }
409
410 /*!
411   \brief Create request to the python dispatcher for the command execution.
412   \param command python command to be executed
413  */
414 PyInterp_Request* PyConsole_Editor::createCmdRequest( const QString& command )
415 {
416   return new PyConsole_ExecCommand( myInterp, command, this, isSync() );
417 }
418
419 /*!
420   \brief Create the Python request that will be posted to the interpreter to
421   get the completions.
422   \param input line entered by the user at the time <TAB> was pressed
423   \return completion command
424   \sa CompletionCommand
425 */
426 PyInterp_Request* PyConsole_Editor::createTabRequest( const QString& input )
427 {
428   // valid separators
429   static QStringList separators;
430   if ( separators.isEmpty() ) {
431     separators << " " << "(" << "[" << "+" << "-" << "*" << "/" << ";" << "^" << "=";
432   }
433
434   // parse input to extract on what part the dir() has to be executed
435
436   QString input2( input );
437
438   // split up to the last syntaxical separator
439   int lastSp = -1;
440   foreach ( QString separator, separators ) {
441     int j = input2.lastIndexOf( separator );
442     if ( j > lastSp )
443       lastSp = j;
444   }
445   if ( lastSp >= 0 )
446     input2 = input.mid( lastSp + 1 );
447
448   // detect a qualified name (with a point)
449   int lastPt = input2.lastIndexOf( "." );
450
451   if ( lastPt != -1 ) {
452     // split the 2 surrounding parts of the qualified name
453     myComplBeforePoint = input2.left( lastPt );
454     myComplAfterPoint = input2.mid( lastPt+1 );
455   }
456   else {
457     // no point found - do a global matching
458     // (the following will call dir() with an empty string)
459     myComplAfterPoint = input2;
460     myComplBeforePoint = "";
461   }
462
463   return new PyConsole_CompletionCommand( myInterp, myComplBeforePoint,
464                                           myComplAfterPoint, this, isSync() );
465 }
466
467 /*!
468   \brief Execute command in the python interpreter
469   and wait until it is finished.
470
471   \param command python command to be executed
472  */
473 void PyConsole_Editor::execAndWait( const QString& command )
474 {
475   // already running ?
476   if ( myEventLoop )
477     return;
478
479   // create new event loop
480   bool sync = isSync();
481   if ( !sync ) {
482     myEventLoop = new QEventLoop( this );
483   }
484
485   // execute command
486   exec( command );
487
488   if ( !sync ) {
489     // run event loop
490     myEventLoop->exec();
491     // delete event loop after command is processed
492     delete myEventLoop;
493     myEventLoop = 0;
494   }
495 }
496
497 /*!
498   \brief Process <Enter> key press event. 
499   
500   Execute the command entered by the user.
501 */
502 void PyConsole_Editor::handleReturn()
503 {
504   // Position cursor at the end
505   QTextCursor aCursor = textCursor();
506   aCursor.movePosition( QTextCursor::End );
507   setTextCursor( aCursor );
508
509   // get last line
510   QTextBlock par = document()->end().previous();
511   if ( !par.isValid() ) return;
512
513   // get command
514   QString cmd = par.text().remove( 0, promptSize() );
515
516   // extend the command buffer with the current command 
517   myCommandBuffer.append( cmd );
518
519   // add command to the history
520   if ( !cmd.trimmed().isEmpty() )
521     myHistory.push_back( cmd );
522   putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
523
524   // IPAL19397
525   addText( "", true ); 
526   
527   // set read-only mode
528   setReadOnly( true );
529
530   // set busy cursor
531   setCursor( Qt::BusyCursor );
532   
533   // post a request to execute Python command;
534   // editor will be informed via a custom event that execution has been completed
535   PyInterp_Dispatcher::Get()->Exec( createCmdRequest( myCommandBuffer ) );
536 }
537
538 /*!
539   \brief Process <Tab> key press event. 
540   
541   Perform auto-completion of the currently entered command, if this feature is enabled
542 */
543 void PyConsole_Editor::handleTab()
544 {
545   if ( !autoCompletion() )
546     return; // auto-completion feature is disabled
547
548   if ( myTabMode )
549     return; // already in tab mode
550
551   QTextCursor aCursor = textCursor();
552
553   // move cursor to the end of input
554   aCursor.movePosition( QTextCursor::End );
555   setTextCursor( aCursor );
556
557   // save cursor position if needed
558   if ( myComplCursorPos == -1 )
559     myComplCursorPos = textCursor().position();
560
561   // get last line
562   QTextBlock par = document()->end().previous();
563   if ( !par.isValid() ) return; // empty line
564
565   // switch to completion mode
566   myTabMode = true;
567
568   // get currently entered command
569   QString cmd = par.text().mid( promptSize() );
570
571   // post completion request
572   // editor will be informed that completion has been done via a custom event 
573   PyInterp_Dispatcher::Get()->Exec( createTabRequest( cmd ) );
574 }
575
576 /*!
577   \brief Process <Ctrl><Tab> key press event. 
578   
579   Undoe last auto-completion
580 */
581 void PyConsole_Editor::handleBackTab()
582 {
583   if ( !autoCompletion() )
584     return; // auto-completion feature is disabled
585
586   QTextCursor aCursor = textCursor();
587
588   if ( myComplCursorPos == -1 )
589     return; // invalid cursor position
590
591   // ensure cursor is at the end of command line
592   aCursor.setPosition( myComplCursorPos );
593   aCursor.movePosition( QTextCursor::EndOfLine );
594   //setCursor( aCursor );
595
596   // delete last completed text
597   int i = aCursor.position() - myComplCursorPos;
598   aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, i );
599   aCursor.removeSelectedText();
600   myComplCursorPos = -1;
601 }
602
603 /*!
604   \brief Process drop event.
605
606   Paste dragged text.
607   \param event drop event
608 */
609 void PyConsole_Editor::dropEvent( QDropEvent* event )
610 {
611   // get the initial drop position
612   QPoint pos = event->pos();
613   QTextCursor aCursor = cursorForPosition( event->pos() );
614
615   // if the position is not in the last line move it to the end of the command line
616   if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
617     moveCursor( QTextCursor::End );
618     pos = cursorRect().center();
619   }
620
621   // create new drop event and use it instead of the original
622   QDropEvent de( pos,
623                  event->possibleActions(),
624                  event->mimeData(),
625                  event->mouseButtons(),
626                  event->keyboardModifiers(),
627                  event->type() );
628   QTextEdit::dropEvent( &de );
629
630   // accept the original event
631   event->acceptProposedAction();
632 }
633
634 /*!
635   \brief Process mouse press event
636
637   Clear the completion when any mouse button is pressed.
638
639   \param e mouse press event
640 */
641 void PyConsole_Editor::mousePressEvent( QMouseEvent* event )
642 {
643   if ( autoCompletion() ) {
644     clearCompletion();
645     myComplCursorPos = -1;
646   }
647   QTextEdit::mousePressEvent( event );
648 }
649
650 /*!
651   \brief Process mouse button release event.
652
653   Left mouse button: copy selection to the clipboard.
654   Middle mouse button: paste clipboard's contents.
655
656   \param event mouse event
657 */
658 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
659 {
660   if ( event->button() == Qt::LeftButton ) {
661     QTextEdit::mouseReleaseEvent( event );
662   }
663   else if ( event->button() == Qt::MidButton ) {
664     QTextCursor aCursor = cursorForPosition( event->pos() );
665     // if the position is not in the last line move it to the end of the command line
666     if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
667       moveCursor( QTextCursor::End );
668     }
669     else {
670       setTextCursor( aCursor );
671     }
672     const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? 
673                                                                QClipboard::Selection : QClipboard::Clipboard );
674     if ( md )
675       insertFromMimeData( md );
676   }
677   else {
678     QTextEdit::mouseReleaseEvent( event );
679   }
680 }
681
682 /*!
683   \brief Check if the string is command.
684   
685   Return \c true if the string \a str is likely to be the command
686   (i.e. it is started from the '>>>' or '...').
687   \param str string to be checked
688 */
689 bool PyConsole_Editor::isCommand( const QString& str ) const
690 {
691   return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
692 }
693
694 /*!
695   \brief Handle keyboard event.
696
697   Implement navigation, history browsing, copy/paste and other common
698   operations.
699
700   \param event keyboard event
701 */
702 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
703 {
704   // get cursor position
705   QTextCursor aCursor = textCursor();
706   int curLine = aCursor.blockNumber();
707   int curCol  = aCursor.columnNumber();
708
709   // get last edited line
710   int endLine = document()->blockCount()-1;
711
712   // get pressed key code
713   int aKey = event->key();
714
715   // check if <Ctrl> is pressed
716   bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
717   // check if <Shift> is pressed
718   bool shftPressed = event->modifiers() & Qt::ShiftModifier;
719
720   if ( autoCompletion() ) {
721     // auto-completion support
722     if ( aKey == Qt::Key_Tab && !shftPressed ) {
723       // process <Tab> key
724       if ( !ctrlPressed ) {
725         handleTab();
726       }
727       else {
728         clearCompletion();
729         handleBackTab();
730       }
731       return;
732     }
733
734     // If <Ctrl> is not pressed (or if something else is pressed with <Ctrl>),
735     // or if <Ctrl> is not pressed alone, we have to clear completion
736     if ( !ctrlPressed || ( ctrlPressed && aKey != Qt::Key_Control ) ) {
737       clearCompletion();
738       myComplCursorPos = -1;
739     }
740     
741     // Discard <Ctrl> pressed alone:
742     if ( aKey == Qt::Key_Control )
743       return;
744   }
745
746   if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
747     // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
748     myCommandBuffer.truncate( 0 );
749     myPrompt = READY_PROMPT;
750     addText( myPrompt, true );
751     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
752     return;
753   }
754   else if ( ctrlPressed && aKey == Qt::Key_C ) {
755     // process <Ctrl>+<C> key-binding : copy
756     copy();
757     return;
758   }
759   else if ( ctrlPressed && aKey == Qt::Key_X ) {
760     // process <Ctrl>+<X> key-binding : cut
761     cut();
762     return;
763   }
764   else if ( ctrlPressed && aKey == Qt::Key_V ) {
765     // process <Ctrl>+<V> key-binding : paste
766     paste();
767     return;
768   }
769
770   // check for printed key
771   // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
772   // Better:
773   aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
774
775   switch ( aKey ) {
776   case 0 :
777     // any printed key: just print it
778     {
779       if ( curLine < endLine || curCol < promptSize() ) {
780         moveCursor( QTextCursor::End );
781       }
782       QTextEdit::keyPressEvent( event );
783       break;
784     }
785   case Qt::Key_Return:
786   case Qt::Key_Enter:
787     // <Enter> key: process the current command
788     {
789       handleReturn();
790       break;
791     }
792   case Qt::Key_Up:
793     // <Up> arrow key: process as follows:
794     // - without <Ctrl>, <Shift> modifiers: previous command in history
795     // - with <Ctrl> modifier key pressed:  move cursor one row up without selection
796     // - with <Shift> modifier key pressed: move cursor one row up with selection
797     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
798     {
799       if ( ctrlPressed && shftPressed ) {
800         int value   = verticalScrollBar()->value();
801         int spacing = fontMetrics().lineSpacing();
802         verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
803       }
804       else if ( shftPressed || ctrlPressed ) {
805         if ( curLine > 0 )
806           moveCursor( QTextCursor::Up, 
807                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
808       }
809       else { 
810         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
811           // set history browsing mode
812           myCmdInHistory = myHistory.count();
813           // remember current command
814           QTextBlock par = document()->end().previous();
815           myCurrentCommand = par.text().remove( 0, promptSize() );
816         }
817         if ( myCmdInHistory > 0 ) {
818           myCmdInHistory--;
819           // get previous command in the history
820           QString previousCommand = myHistory.at( myCmdInHistory );
821           // print previous command
822           moveCursor( QTextCursor::End );
823           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
824           textCursor().removeSelectedText();
825           addText( myPrompt + previousCommand ); 
826           // move cursor to the end
827           moveCursor( QTextCursor::End );
828         }
829       }
830       break;
831     }
832   case Qt::Key_Down:
833     // <Down> arrow key: process as follows:
834     // - without <Ctrl>, <Shift> modifiers: next command in history
835     // - with <Ctrl> modifier key pressed:  move cursor one row down without selection
836     // - with <Shift> modifier key pressed: move cursor one row down with selection
837     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
838     {
839       if ( ctrlPressed && shftPressed ) {
840         int value   = verticalScrollBar()->value();
841         int maxval  = verticalScrollBar()->maximum();
842         int spacing = fontMetrics().lineSpacing();
843         verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
844       }
845       else if ( shftPressed || ctrlPressed) {
846         if ( curLine < endLine )
847           moveCursor( QTextCursor::Down, 
848                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
849       }
850       else { 
851         if ( myCmdInHistory >= 0 ) {
852           // get next command in the history
853           myCmdInHistory++;
854           QString nextCommand;
855           if ( myCmdInHistory < myHistory.count() ) {
856             // next command in history
857             nextCommand = myHistory.at( myCmdInHistory );
858           }
859           else {
860             // end of history is reached
861             // last printed command
862             nextCommand = myCurrentCommand;
863             // unset history browsing mode
864             myCmdInHistory = -1;
865           }
866           // print next or current command
867           moveCursor( QTextCursor::End );
868           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
869           textCursor().removeSelectedText();
870           addText( myPrompt + nextCommand );
871           // move cursor to the end
872           moveCursor( QTextCursor::End );
873         }
874       }
875       break;
876     }
877   case Qt::Key_Left:
878     // <Left> arrow key: process as follows:
879     // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
880     // - with <Ctrl> modifier key pressed:  move one word left (taking into account prompt)
881     // - with <Shift> modifier key pressed: move one symbol left with selection
882     // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
883     {
884       QString txt = textCursor().block().text();
885       if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
886         moveCursor( QTextCursor::Up );
887         moveCursor( QTextCursor::EndOfBlock );
888       }
889       else {
890         QTextEdit::keyPressEvent( event );
891       }
892       break;
893     }
894   case Qt::Key_Right:
895     // <Right> arrow key: process as follows:
896     // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
897     // - with <Ctrl> modifier key pressed:  move one word right (taking into account prompt)
898     // - with <Shift> modifier key pressed: move one symbol right with selection
899     // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
900     {
901       QString txt = textCursor().block().text();
902       if ( !shftPressed ) {
903         if ( curCol < txt.length() ) {
904           if ( isCommand( txt ) && curCol < promptSize() ) {
905             aCursor.setPosition( aCursor.block().position() + promptSize() );
906             setTextCursor( aCursor );
907             break;
908           }
909         }
910         else {
911           if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
912             aCursor.setPosition( aCursor.position() + promptSize()+1 );
913             setTextCursor( aCursor );
914             horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
915             break;
916           }
917         }
918       }
919       QTextEdit::keyPressEvent( event );
920       break;
921     }
922   case Qt::Key_PageUp:
923     // <PageUp> key: process as follows:
924     // - without <Ctrl>, <Shift> modifiers: first command in history
925     // - with <Ctrl> modifier key pressed:  move cursor one page up without selection
926     // - with <Shift> modifier key pressed: move cursor one page up with selection
927     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
928     {
929       if ( ctrlPressed && shftPressed ) {
930         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
931       }
932       else if ( shftPressed || ctrlPressed ) {
933         bool moved = false;
934         qreal lastY = cursorRect( aCursor ).top();
935         qreal distance = 0;
936         // move using movePosition to keep the cursor's x
937         do {
938           qreal y = cursorRect( aCursor ).top();
939           distance += qAbs( y - lastY );
940           lastY = y;
941           moved = aCursor.movePosition( QTextCursor::Up, 
942                                         shftPressed ? QTextCursor::KeepAnchor : 
943                                         QTextCursor::MoveAnchor );
944         } while ( moved && distance < viewport()->height() );
945         if ( moved ) {
946           aCursor.movePosition( QTextCursor::Down, 
947                                 shftPressed ? QTextCursor::KeepAnchor : 
948                                 QTextCursor::MoveAnchor );
949           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
950         }
951         setTextCursor( aCursor );
952       }
953       else { 
954         if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
955           // set history browsing mode
956           myCmdInHistory = myHistory.count();
957           // remember current command
958           QTextBlock par = document()->end().previous();
959           myCurrentCommand = par.text().remove( 0, promptSize() );
960         }
961         if ( myCmdInHistory > 0 ) {
962           myCmdInHistory = 0;
963           // get very first command in the history
964           QString firstCommand = myHistory.at( myCmdInHistory );
965           // print first command
966           moveCursor( QTextCursor::End );
967           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
968           textCursor().removeSelectedText();
969           addText( myPrompt + firstCommand ); 
970           // move cursor to the end
971           moveCursor( QTextCursor::End );
972         }
973       }
974       break;
975     }
976   case Qt::Key_PageDown:
977     // <PageDown> key: process as follows:
978     // - without <Ctrl>, <Shift> modifiers: last command in history
979     // - with <Ctrl> modifier key pressed:  move cursor one page down without selection
980     // - with <Shift> modifier key pressed: move cursor one page down with selection
981     // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
982     {
983       if ( ctrlPressed && shftPressed ) {
984         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
985       }
986       else if ( shftPressed || ctrlPressed ) {
987         bool moved = false;
988         qreal lastY = cursorRect( aCursor ).top();
989         qreal distance = 0;
990         // move using movePosition to keep the cursor's x
991         do {
992           qreal y = cursorRect( aCursor ).top();
993           distance += qAbs( y - lastY );
994           lastY = y;
995           moved = aCursor.movePosition( QTextCursor::Down, 
996                                         shftPressed ? QTextCursor::KeepAnchor : 
997                                         QTextCursor::MoveAnchor );
998         } while ( moved && distance < viewport()->height() );
999         if ( moved ) {
1000           aCursor.movePosition( QTextCursor::Up, 
1001                                 shftPressed ? QTextCursor::KeepAnchor : 
1002                                 QTextCursor::MoveAnchor );
1003           verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
1004         }
1005         setTextCursor( aCursor );
1006       }
1007       else { 
1008         if ( myCmdInHistory >= 0 ) {
1009           // unset history browsing mode
1010           myCmdInHistory = -1;
1011           // print current command
1012           moveCursor( QTextCursor::End );
1013           moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
1014           textCursor().removeSelectedText();
1015           addText( myPrompt + myCurrentCommand ); 
1016           // move cursor to the end
1017           moveCursor( QTextCursor::End );
1018         }
1019       }
1020       break;
1021     }
1022   case Qt::Key_Home: 
1023     // <Home> key: process as follows:
1024     // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
1025     // - with <Ctrl> modifier key pressed:  move cursor to the very first symbol without selection
1026     // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
1027     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
1028     {
1029       if ( ctrlPressed ) { 
1030         moveCursor( QTextCursor::Start, 
1031                     shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1032       }
1033       else {
1034         QString txt = textCursor().block().text();
1035         if ( isCommand( txt ) ) {
1036           if ( shftPressed ) {
1037             if ( curCol > promptSize() ) {
1038               aCursor.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
1039               aCursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
1040             }
1041           }
1042           else {
1043             aCursor.movePosition( QTextCursor::StartOfLine );
1044             aCursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
1045           }
1046           setTextCursor( aCursor );
1047         }
1048         else {
1049           moveCursor( QTextCursor::StartOfBlock, 
1050                       shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1051         }
1052         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1053       }
1054       break;
1055     }
1056   case Qt::Key_End:
1057     // <End> key: process as follows:
1058     // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
1059     // - with <Ctrl> modifier key pressed:  move cursor to the very last symbol without selection
1060     // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
1061     // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
1062     {
1063       moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, 
1064                   shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1065       break;
1066     }  
1067   case Qt::Key_Backspace :
1068     // <Backspace> key: process as follows
1069     // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
1070     // - with <Shift> modifier key pressed: delete previous word
1071     // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
1072     // works only for last (command) line
1073     {
1074       if ( aCursor.hasSelection() ) {
1075         cut();
1076       }
1077       else if ( aCursor.position() > document()->end().previous().position() + promptSize() ) {
1078         if ( shftPressed ) {
1079           moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
1080           textCursor().removeSelectedText();
1081         }
1082         else if ( ctrlPressed ) {
1083           aCursor.setPosition( document()->end().previous().position() + promptSize(),
1084                                QTextCursor::KeepAnchor );
1085           setTextCursor( aCursor );
1086           textCursor().removeSelectedText();
1087         }
1088         else {
1089           QTextEdit::keyPressEvent( event );
1090         }
1091       }
1092       else {
1093         aCursor.setPosition( document()->end().previous().position() + promptSize() );
1094         setTextCursor( aCursor );
1095         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1096       }
1097       break;
1098     }
1099   case Qt::Key_Delete :
1100     // <Delete> key: process as follows
1101     // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
1102     // - with <Shift> modifier key pressed: delete next word
1103     // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
1104     // works only for last (command) line
1105     {
1106       if ( aCursor.hasSelection() ) {
1107         cut();
1108       }
1109       else if ( aCursor.position() > document()->end().previous().position() + promptSize()-1 ) {
1110         if ( shftPressed ) {
1111           moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
1112           textCursor().removeSelectedText();
1113         }
1114         else if ( ctrlPressed ) {
1115           moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
1116           textCursor().removeSelectedText();
1117         }
1118         else {
1119           QTextEdit::keyPressEvent( event );
1120         }
1121       }
1122       else {
1123         aCursor.setPosition( document()->end().previous().position() + promptSize() );
1124         setTextCursor( aCursor );
1125         horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1126       }
1127       break;
1128     }
1129   case Qt::Key_Insert :
1130     // <Insert> key: process as follows
1131     // - with <Ctrl> modifier key pressed:  copy()
1132     // - with <Shift> modifier key pressed: paste() to the command line
1133     {
1134       if ( ctrlPressed ) {
1135         copy();
1136       }
1137       else if ( shftPressed ) {
1138         paste();
1139       }
1140       else
1141         QTextEdit::keyPressEvent( event );
1142       break;
1143     }
1144   default:
1145     break;
1146   }
1147 }
1148
1149 /*!
1150   \brief Handle notification event coming from Python dispatcher.
1151   \param event notification event
1152 */
1153 void PyConsole_Editor::customEvent( QEvent* event )
1154 {
1155   switch( event->type() )
1156   {
1157   case PyConsole_PrintEvent::EVENT_ID:
1158   {
1159     PyConsole_PrintEvent* pe = (PyConsole_PrintEvent*)event;
1160     putLog( pe->text());
1161     addText( pe->text(), false, pe->isError() );
1162     return;
1163   }
1164   case PyConsole_CompletionEvent::EVENT_ID:
1165   {
1166     PyConsole_CompletionEvent* ce = (PyConsole_CompletionEvent*)event;
1167     bool status = ce->status();
1168     QStringList matches = ce->matches();
1169     QString doc = ce->doc();
1170
1171     if ( status ) {
1172       // completion was successful
1173       QTextCursor aCursor = textCursor();
1174
1175       if ( matches.isEmpty() ) {
1176         // completion successful but there are no matches.
1177         myTabMode = false;
1178         myComplCursorPos = -1;
1179         return;
1180       }
1181       
1182       if ( matches.size() == 1 ) {
1183         // there's only one match - complete directly and update doc string window
1184         aCursor.insertText( matches[0].mid( myComplAfterPoint.size() ) );
1185         myTabMode = false;
1186         if ( doc.isEmpty() )
1187           emit updateDoc( formatDocHTML( QString( "(%1)\n" ).arg( tr( "NO_DOC_AVAILABLE" ) ) ) );
1188         else
1189           emit updateDoc( formatDocHTML( doc ) );
1190       }
1191       else {
1192         // there are several matches
1193         
1194         // detect if there is a common base to all available completion
1195         // in this case append this base to the text
1196         QString base = extractCommon( matches );
1197         aCursor.insertText( base.mid( myComplAfterPoint.size() ) );
1198         
1199         // if this happens to match exactly the first completion
1200         // also provide doc
1201         if ( base == matches[0] )
1202           emit updateDoc( formatDocHTML( doc ) );
1203         
1204         // print all matching completion in a "undo-able" block
1205         int cursorPos = aCursor.position();
1206         aCursor.insertBlock();
1207         aCursor.beginEditBlock();
1208         
1209         // insert all matches
1210         QTextCharFormat cf;
1211         cf.setForeground( QBrush( Qt::darkGreen ) );
1212         aCursor.setCharFormat( cf );
1213         aCursor.insertText( formatCompletion( matches ) );
1214         aCursor.endEditBlock();
1215         
1216         // position cursor where it was before inserting the completion list
1217         aCursor.setPosition( cursorPos );
1218         setTextCursor( aCursor );
1219       }
1220     }
1221     else {
1222       // completion failed
1223       myTabMode = false;
1224       myComplCursorPos = -1;
1225     }
1226     return;
1227   }
1228   case PyInterp_Event::ES_OK:
1229   case PyInterp_Event::ES_ERROR:
1230   {
1231     // clear command buffer
1232     myCommandBuffer.truncate( 0 );
1233     // add caret return line if necessary
1234     QTextBlock par = document()->end().previous();
1235     QString txt = par.text();
1236     txt.truncate( txt.length() - 1 );
1237     // IPAL19397 : addText moved to handleReturn() method
1238     //if ( !txt.isEmpty() )
1239     //  addText( "", true );
1240     // set "ready" prompt
1241     myPrompt = READY_PROMPT;
1242     addText( myPrompt );
1243     // unset busy cursor
1244     unsetCursor();
1245     // stop event loop (if running)
1246     if ( myEventLoop )
1247       myEventLoop->exit();
1248     // if we are in multi_paste_mode, process the next item
1249     multiLineProcessNextLine();
1250     break;
1251   }
1252   case PyInterp_Event::ES_INCOMPLETE:
1253   {
1254     // extend command buffer (multi-line command)
1255     myCommandBuffer.append( "\n" );
1256     // add caret return line if necessary
1257     QTextBlock par = document()->end().previous();
1258     QString txt = par.text();
1259     txt.truncate( txt.length() - 1 );
1260     // IPAL19397 : addText moved to handleReturn() method
1261     //if ( !txt.isEmpty() )
1262     //  addText( "", true );
1263     // set "dot" prompt
1264     myPrompt = DOTS_PROMPT;
1265     addText( myPrompt/*, true*/ ); // IPAL19397
1266     // unset busy cursor
1267     unsetCursor();
1268     // stop event loop (if running)
1269     if ( myEventLoop )
1270       myEventLoop->exit();
1271     // if we are in multi_paste_mode, process the next item
1272     multiLineProcessNextLine();
1273     break;
1274   }
1275   default:
1276     QTextEdit::customEvent( event );
1277   }
1278   
1279   // unset read-only state
1280   setReadOnly( false );
1281   // unset history browsing mode
1282   myCmdInHistory = -1;
1283
1284   if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1285   {
1286     // process the next sheduled command from the queue (if there is any)
1287     QString nextcmd = myQueue[0];
1288     myQueue.pop_front();
1289     exec( nextcmd );
1290   }
1291 }
1292
1293 /*!
1294   \brief "Copy" operation.
1295   
1296   Reimplemented from Qt.
1297   Warning! In Qt this method is not virtual.
1298 */
1299 void PyConsole_Editor::cut()
1300 {
1301   QTextCursor aCursor = textCursor();
1302   if ( aCursor.hasSelection() ) {
1303     QApplication::clipboard()->setText( aCursor.selectedText() );
1304     int startSelection = aCursor.selectionStart();
1305     if ( startSelection < document()->end().previous().position() + promptSize() )
1306       startSelection = document()->end().previous().position() + promptSize();
1307     int endSelection = aCursor.selectionEnd();
1308     if ( endSelection < document()->end().previous().position() + promptSize() )
1309       endSelection = document()->end().previous().position() + promptSize();
1310     aCursor.setPosition( startSelection );
1311     aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1312     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1313     setTextCursor( aCursor );
1314     textCursor().removeSelectedText();
1315   }
1316 }
1317
1318 /*!
1319   \brief "Paste" operation.
1320
1321   Reimplemented from Qt.
1322   Warning! In Qt this method is not virtual.
1323 */
1324 void PyConsole_Editor::paste()
1325 {
1326   QTextCursor aCursor = textCursor();
1327   if ( aCursor.hasSelection() ) {
1328     int startSelection = aCursor.selectionStart();
1329     if ( startSelection < document()->end().previous().position() + promptSize() )
1330       startSelection = document()->end().previous().position() + promptSize();
1331     int endSelection = aCursor.selectionEnd();
1332     if ( endSelection < document()->end().previous().position() + promptSize() )
1333       endSelection = document()->end().previous().position() + promptSize();
1334     aCursor.setPosition( startSelection );
1335     aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1336     horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1337     setTextCursor( aCursor );
1338     textCursor().removeSelectedText();
1339   }
1340   if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1341     moveCursor( QTextCursor::End );
1342   QTextEdit::paste();
1343 }
1344
1345 /*!
1346   \brief "Clear" operation.
1347
1348   Reimplemented from Qt.
1349   Warning! In Qt this method is not virtual.
1350 */
1351 void PyConsole_Editor::clear()
1352 {
1353   QTextEdit::clear();
1354   if ( isShowBanner() )
1355     addText( banner() );
1356   myPrompt = READY_PROMPT;
1357   addText( myPrompt );
1358 }
1359
1360 /*!
1361   \brief Dumps recorded Python commands to the file
1362   \param fileName path to the dump file
1363   \return \c true if dump operation succeeded or \c false otherwise
1364 */
1365 bool PyConsole_Editor::dump( const QString& fileName )
1366 {
1367   bool ok = false;
1368   if ( !fileName.isEmpty() ) {
1369     QFile file( fileName ); 
1370     if ( file.open( QFile::WriteOnly ) ) {
1371       QTextStream out( &file );
1372       for ( int i = 0; i < myHistory.count(); i++ ) {
1373         out << myHistory[i] << endl;
1374       }
1375       file.close();
1376       ok = true;
1377     }
1378   }
1379   return ok;
1380 }
1381
1382 /*!
1383   \brief Dump menu action slot
1384 */
1385 void PyConsole_Editor::dump()
1386 {
1387   forever {
1388     // get file name
1389     QString fileName = getDumpFileName();
1390
1391     if ( fileName.isEmpty() )
1392       break; // cancelled
1393     
1394     if ( dump( fileName ) )
1395       break;
1396     else
1397       QMessageBox::warning( this,
1398                             tr( "WARNING" ),
1399                             tr( "ERR_FILE_NOT_WRITEABLE" ) );
1400   }
1401 }
1402
1403 /*!
1404   \brief Get file name for Dump commands operation.
1405   
1406   This function can be redefined in successor classes to show application
1407   specific dialog box.
1408
1409   \return path to the dump file
1410 */
1411 QString PyConsole_Editor::getDumpFileName()
1412 {
1413   return QFileDialog::getSaveFileName( this,
1414                                        tr( "GET_DUMP_COMMANDS_FILENAME" ),
1415                                        QString(),
1416                                        QString( "%1 (*.py)" ).arg( tr( "PYTHON_SCRIPTS" ) ) );
1417 }
1418
1419 /*!
1420   \brief Get file name for Log Python trace operation.
1421   
1422   This function can be redefined in successor classes to show application
1423   specific dialog box.
1424
1425   \return path to the log file
1426 */
1427 QString PyConsole_Editor::getLogFileName()
1428 {
1429   return QFileDialog::getSaveFileName( this,
1430                                        tr( "GET_PYTHON_TRACE_FILENAME" ),
1431                                        QString(),
1432                                        QString( "%1 (*.log *.txt)" ).arg( tr( "LOG_FILES" ) ) );
1433 }
1434
1435 /*!
1436   \brief Start python trace logging
1437   \param fileName the path to the log file
1438   \return \c true if operation succeeded or \c false otherwise
1439           (for example, if file is not writeable)
1440   \sa stopLog()
1441  */
1442 bool PyConsole_Editor::startLog( const QString& fileName )
1443 {
1444   // stop possibly already running logging
1445   if ( isLogging() )
1446     stopLog();
1447
1448   bool ok = false;
1449   if ( !fileName.isEmpty() ) {
1450     QFile file( fileName );
1451     if ( file.open( QFile::WriteOnly ) ) {
1452       file.close();
1453       myLogFile = fileName;
1454       ok = true;
1455     }
1456   }
1457   return ok;
1458 }
1459
1460 /*!
1461   \brief Start log action slot
1462 */
1463 void PyConsole_Editor::startLog()
1464 {
1465   forever {
1466     // get file name
1467     QString fileName = getLogFileName();
1468
1469     if ( fileName.isEmpty() )
1470       break; // cancelled
1471     
1472     if ( startLog( fileName ) )
1473       break;
1474     else
1475       QMessageBox::warning( this,
1476                             tr( "WARNING" ),
1477                             tr( "File is not writable" ) );
1478   }
1479 }
1480
1481 /*!
1482   \brief Stop log action slot
1483   
1484   Stops Python trace logging.
1485 */
1486 void PyConsole_Editor::stopLog()
1487 {
1488   myLogFile = QString();
1489 }
1490
1491 /*!
1492   \brief Put data to the log file
1493 */
1494 void PyConsole_Editor::putLog( const QString& s )
1495 {
1496   if ( !myLogFile.isEmpty() ) {
1497     QFile file( myLogFile );
1498     if ( !file.open( QFile::Append ) )
1499       return;
1500     
1501     QTextStream out( &file );
1502     out << s;
1503     
1504     file.close();
1505   }
1506 }
1507
1508 /*!
1509   \brief Handle properly multi-line pasting. Qt documentation recommends overriding this function.
1510   If the pasted text doesn't contain a line return, no special treatment is done.
1511   \param source
1512 */
1513 void PyConsole_Editor::insertFromMimeData(const QMimeData* source)
1514 {
1515   if ( myMultiLinePaste )
1516     return;
1517
1518   if ( source->hasText() ) {
1519     QString s = source->text();
1520     if ( s.contains( "\n" ) )
1521       multilinePaste( s );
1522     else
1523       QTextEdit::insertFromMimeData( source );
1524   }
1525   else {
1526     QTextEdit::insertFromMimeData( source );
1527   }
1528 }
1529
1530 /*!
1531   Start multi-line paste operation
1532   \internal
1533 */
1534 void PyConsole_Editor::multilinePaste( const QString& s )
1535 {
1536   // Turn on multi line pasting mode
1537   myMultiLinePaste = true;
1538
1539   // Split string data to lines
1540   QString s2 = s;
1541   s2.replace( "\r", "" ); // Windows string format converted to Unix style
1542   QStringList lst = s2.split( QChar('\n'), QString::KeepEmptyParts );
1543
1544   // Perform the proper paste operation for the first line to handle the case where
1545   // something was already there
1546   QMimeData source;
1547   source.setText( lst[0] );
1548   QTextEdit::insertFromMimeData( &source );
1549
1550   // Prepare what will have to be executed after the first line
1551   myMultiLineContent.clear();
1552   for ( int i = 1; i < lst.size(); ++i )
1553     myMultiLineContent.enqueue( lst[i] );
1554
1555   // Trigger the execution of the first (mixed) line
1556   handleReturn();
1557
1558   // See customEvent() and multiLineProcessNext() for the rest of the handling.
1559 }
1560
1561 /*!
1562   \brief Process the next line in the queue of multi-line paste operation; called
1563   from customEvent() function
1564   \internal
1565 */
1566 void PyConsole_Editor::multiLineProcessNextLine()
1567 {
1568   // not in multi-line paste mode
1569   if ( !myMultiLinePaste || myMultiLineContent.isEmpty() )
1570     return;
1571
1572   QString line = myMultiLineContent.dequeue();
1573   if ( myMultiLineContent.empty() )
1574   {
1575     // this isa last line in the queue, just paste it
1576     addText( line, false, false );
1577     myMultiLinePaste = false;
1578   }
1579   else
1580   {
1581     // paste the line and simulate a <RETURN> key stroke
1582     addText( line, false, false );
1583     handleReturn();
1584   }
1585 }
1586
1587 /*!
1588   \brief Clear results of completion
1589 */
1590 void PyConsole_Editor::clearCompletion()
1591 {
1592   // delete completion text if present
1593   if ( myTabMode ) {
1594     // remove completion display
1595     document()->undo();
1596     // remove trailing line return:
1597     QTextCursor tc( textCursor() );
1598     tc.setPosition( document()->characterCount()-1 );
1599     setTextCursor( tc );
1600     textCursor().deletePreviousChar();
1601     // TODO: before wait for any <Tab> event to be completed
1602   }
1603   myTabMode = false;
1604 }
1605
1606 /*!
1607  \brief Format completion results - this is where we should create 3 columns etc ...
1608  \param matches list of possible completions
1609  \return result string
1610 */
1611 QString PyConsole_Editor::formatCompletion( const QStringList& matches ) const
1612 {
1613   static const int MAX_COMPLETIONS = 70;
1614
1615   QStringList result;
1616   int sz = matches.size();
1617
1618   if ( sz > MAX_COMPLETIONS )
1619     result.append( QString( "[%1]" ).arg( tr( "TOO_MANY_MATCHES" ) ) );
1620
1621   for ( int i = 0; i < qMin( sz, MAX_COMPLETIONS); ++i )
1622     result.append( matches[i] );
1623
1624   return result.join( "\n" );
1625 }
1626
1627 /*!
1628   \brief Format the doc string in HTML format with the first line in bold blue
1629   \param doc initial doc string
1630   \return HTML string
1631 */
1632 QString PyConsole_Editor::formatDocHTML( const QString& doc ) const
1633 {
1634   static const char* templ = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " \
1635     "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" \
1636     "<html><head><meta name=\"qrichtext\" content=\"1\" /> " \
1637     "<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> " \
1638     "</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n" \
1639     "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> " \
1640     "<span style=\" font-weight:600; color:#0000ff;\">%1</span></p> " \
1641     "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p> " \
1642     "</body></html>";
1643   
1644   QString fst, rest("");
1645
1646   // Extract first line of doc
1647   int idx = doc.indexOf( "\n" );
1648   if ( idx > 0 ) {
1649     fst = doc.left( idx );
1650     rest = doc.mid( idx+1 );
1651   }
1652   else {
1653     fst = doc;
1654   }
1655
1656   fst = fst.replace( "\n", " " );
1657   rest = rest.replace(" \n", " " );
1658   return QString( templ ).arg( fst ).arg( rest );
1659 }
1660
1661 /*!
1662   \fn void PyConsole_Editor::updateDoc( const QString& doc);
1663   \brief Signal emitted by the editor widget when the doc string should be updated.
1664   \param doc a HTML block with the formatted doc string.
1665   \todo currently this signal is left uncaught.
1666 */
1667
1668 /*!
1669  \brief Extract the common leading part of all strings in matches.
1670  \param matches
1671  \param result
1672 */
1673 QString PyConsole_Editor::extractCommon( const QStringList& matches ) const
1674 {
1675   QString result = "";
1676   
1677   if ( matches.size() > 1 ) {
1678     int l = 0;
1679     bool ok = true;
1680     while ( ok && l+1 < matches[0].size() ) {
1681       QString match = matches[0].left( l+1 );
1682       for ( int j = 1; j < matches.size() && ok; j++ )
1683         ok = matches[j].startsWith( match );
1684       if ( ok )
1685         l++;
1686     }
1687     result = matches[0].left( l );
1688   }
1689
1690   return result;
1691 }
1692
1693 /*!
1694   \brief Useful method to get banner from Python interpreter
1695   \return banner
1696 */
1697 QString PyConsole_Editor::banner() const
1698 {
1699   return myInterp->getBanner().c_str();
1700 }