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