1 // Copyright (C) 2007-2015 CEA/DEN, EDF R&D, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
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.
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.
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
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 // SALOME SALOMEGUI : implementation of desktop and GUI kernel
24 // File : PyConsole_Editor.cxx
25 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
28 \class PyConsole_Editor
29 \brief Python command line interpreter front-end GUI widget.
31 This class provides simple GUI interface to the Python interpreter, including basic
32 navigation operations, executing commands (both interactively and programmatically),
33 copy-paste operations, history of the commands and so on.
35 Here below is the shortcut keyboard boundings used for navigation and other operations:
36 - <Enter> : execute current command
37 - <Ctrl><Break> : clear current command
38 - <Escape> : clear current command
39 - <Up> : previous command in the history
40 - <Shift><Up> : move cursor one row up with selection
41 - <Ctrl><Up> : move cursor one row up without selection
42 - <Ctrl><Shift><Up> : move cursor one row up with selection
43 - <Down> : next command in the history
44 - <Shift><Down> : move cursor one row down with selection
45 - <Ctrl><Down> : move cursor one row down without selection
46 - <Ctrl><Shift><Down> : move cursor one row down with selection
47 - <Left> : move one symbol left without selection
48 - <Shift><Left> : move one symbol left with selection
49 - <Ctrl><Left> : move one word left without selection
50 - <Ctrl><Shift><Left> : move one word left with selection
51 - <Right> : move one symbol right without selection
52 - <Shift><Right> : move one symbol right with selection
53 - <Ctrl><Right> : move one word right without selection
54 - <Ctrl><Shift><Right> : move one word right with selection
55 - <PgUp> : first command in the history
56 - <Shift><PgUp> : move one page up with selection
57 - <Ctrl><PgUp> : move one page up without selection
58 - <Ctrl><Shift><PgUp> : scroll one page up
59 - <PgDn> : last command in the history
60 - <Shift><PgDn> : move one page down with selection
61 - <Ctrl><PgDn> : move one page down without selection
62 - <Ctrl><Shift><PgDn> : scroll one page down
63 - <Home> : move to the beginning of the line without selection
64 - <Shift><Home> : move to the beginning of the line with selection
65 - <Ctrl><Home> : move to the very first symbol without selection
66 - <Ctrl><Shift><Home> : move to the very first symbol with selection
67 - <End> : move to the end of the line without selection
68 - <Shift><End> : move to the end of the line with selection
69 - <Ctrl><End> : move to the very last symbol without selection
70 - <Ctrl><Shift><End> : move to the very last symbol with selection
71 - <Backspace> : delete symbol before the cursor
72 / remove selected text and put it to the clipboard (cut)
73 - <Shift><Backspace> : delete previous word
74 / remove selected text and put it to the clipboard (cut)
75 - <Ctrl><Backspace> : delete text from the cursor to the beginning of the line
76 / remove selected text and put it to the clipboard (cut)
77 - <Delete> : delete symbol after the cursor
78 / remove selected text and put it to the clipboard (cut)
79 - <Shift><Delete> : delete next word
80 / remove selected text and put it to the clipboard (cut)
81 - <Ctrl><Delete> : delete text from the cursor to the end of the line
82 / remove selected text and put it to the clipboard (cut)
83 - <Ctrl><Insert> : copy
84 - <Shift><Insert> : paste
91 #include "PyConsole_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!!
92 #include "PyConsole_Editor.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Event.h"
95 #include "PyInterp_Dispatcher.h"
96 #include "PyConsole_Request.h"
98 #include <SUIT_Tools.h>
99 #include <SUIT_FileDlg.h>
100 #include <SUIT_MessageBox.h>
101 #include <SUIT_FileValidator.h>
103 #include <QApplication>
104 #include <QClipboard>
105 #include <QDropEvent>
108 #include <QMouseEvent>
109 #include <QScrollBar>
110 #include <QTextBlock>
111 #include <QTextCursor>
112 #include <QTextDocument>
113 #include <QTextStream>
116 //VSR: uncomment below macro to support unicode text properly in SALOME
117 // current commented out due to regressions
118 //#define PAL22528_UNICODE
122 QString fromUtf8( const char* txt )
124 #ifdef PAL22528_UNICODE
125 return QString::fromUtf8( txt );
127 return QString( txt );
132 static QString READY_PROMPT = ">>> ";
133 static QString DOTS_PROMPT = "... ";
135 class DumpCommandsFileValidator : public SUIT_FileValidator
138 DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {};
139 virtual ~DumpCommandsFileValidator() {};
140 virtual bool canSave( const QString& file, bool permissions );
143 bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
145 QFileInfo fi( file );
146 if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) {
147 SUIT_MessageBox::critical( parent(),
148 QObject::tr("WRN_WARNING"),
149 QObject::tr("WRN_FILE_NAME_BAD") );
152 return SUIT_FileValidator::canSave( file, permissions);
155 void staticCallbackStdout( void* data, char* c )
157 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
158 PyConsole_Editor* e = (PyConsole_Editor*)data;
159 e->putLog( fromUtf8(c) );
160 QApplication::postEvent( e, new PrintEvent( fromUtf8(c), false ) );
164 void staticCallbackStderr( void* data, char* c )
166 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
167 PyConsole_Editor* e = (PyConsole_Editor*)data;
168 e->putLog( fromUtf8(c) );
169 QApplication::postEvent( e, new PrintEvent( fromUtf8(c), true ) );
177 Creates python editor window.
178 \param theInterp python interper
179 \param theParent parent widget
181 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
183 : QTextEdit( theParent ),
185 myCmdInHistory( -1 ),
187 myShowBanner( true ),
189 myIsSuppressOutput( false )
191 QString fntSet( "" );
192 QFont aFont = SUIT_Tools::stringToFont( fntSet );
194 setUndoRedoEnabled( false );
196 myPrompt = READY_PROMPT;
197 setLineWrapMode( QTextEdit::WidgetWidth );
198 setWordWrapMode( QTextOption::WrapAnywhere );
199 setAcceptRichText( false );
201 theInterp->setvoutcb( staticCallbackStdout, this );
202 theInterp->setverrcb( staticCallbackStderr, this );
204 // san - This is necessary for troubleless initialization
205 onPyInterpChanged( theInterp );
211 PyConsole_Editor::~PyConsole_Editor()
217 \brief Get Python interpreter
219 PyConsole_Interp* PyConsole_Editor::getInterp() const
225 \brief Get synchronous mode flag value.
228 \return True if python console works in synchronous mode
230 bool PyConsole_Editor::isSync() const
236 \brief Set synchronous mode flag value.
238 In synhronous mode the Python commands are executed in the GUI thread
239 and the GUI is blocked until the command is finished. In the asynchronous
240 mode each Python command is executed in the separate thread that does not
241 block the main GUI loop.
243 \param on synhronous mode flag
245 void PyConsole_Editor::setIsSync( const bool on )
251 \brief Get suppress output flag value.
253 \sa setIsSuppressOutput()
254 \return \c true if python console output is suppressed.
256 bool PyConsole_Editor::isSuppressOutput() const
258 return myIsSuppressOutput;
262 \brief Set suppress output flag value.
264 In case if suppress output flag is true, the python
265 console output suppressed.
267 \param on suppress output flag
269 void PyConsole_Editor::setIsSuppressOutput( const bool on )
271 myIsSuppressOutput = on;
275 \brief Get 'show banner' flag value.
277 \sa setIsShowBanner()
278 \return \c true if python console shows banner
280 bool PyConsole_Editor::isShowBanner() const
286 \brief Set 'show banner' flag value.
288 The banner is shown in the top of the python console window.
291 \param on 'show banner' flag
293 void PyConsole_Editor::setIsShowBanner( const bool on )
295 if ( myShowBanner != on ) {
302 \brief Check if trace logging is switched on.
304 \sa startLog(), stopLog()
305 \return \c true if trace logging is switched on
307 bool PyConsole_Editor::isLogging() const
309 return !myLogFile.isEmpty();
313 \brief Get size hint for the Python console window
314 \return size hint value
316 QSize PyConsole_Editor::sizeHint() const
318 QFontMetrics fm( font() );
319 int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1;
320 QSize s(100, fm.lineSpacing()*nbLines);
325 \brief Put the string \a str to the python editor.
326 \param str string to be put in the command line of the editor
327 \param newBlock if True, then the string is printed on a new line
328 \param isError if true, the text is printed in dark red
330 void PyConsole_Editor::addText( const QString& str,
334 QTextCursor theCursor(textCursor());
337 moveCursor( QTextCursor::End );
339 theCursor.insertBlock();
341 cf.setForeground(QBrush(Qt::red));
343 cf.setForeground(QBrush(Qt::black));
344 theCursor.insertText( str, cf);
345 moveCursor( QTextCursor::End );
346 ensureCursorVisible();
350 \brief Convenient method for executing a Python command,
351 as if the user typed it manually.
352 \param command python command to be executed
354 !!! WARNING: doesn't work properly with multi-line commands. !!!
356 void PyConsole_Editor::exec( const QString& command )
358 if ( isReadOnly() ) {
359 // some interactive command is being executed in this editor...
360 // shedule the command to the queue
361 myQueue.push_back( command );
365 moveCursor( QTextCursor::End );
366 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
367 textCursor().removeSelectedText();
368 // set "ready" prompt
369 myPrompt = READY_PROMPT;
370 // clear command buffer
371 myCommandBuffer.truncate( 0 );
372 // unset history browsing mode
374 // print command line by line
375 QString cmd = command;
376 if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
377 QStringList lines = command.split( "\n" );
378 for ( int i = 0; i < lines.size(); i++ ) {
379 if ( !lines[i].trimmed().isEmpty() )
380 myHistory.push_back( lines[i] );
381 addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
382 putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
386 // set read-only mode
389 setCursor( Qt::BusyCursor );
391 // post a request to execute Python command;
392 // editor will be informed via a custom event that execution has been completed
393 PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) );
397 \brief Create request to the python dispatcher for the command execution.
399 \param command python command to be executed
401 PyInterp_Request* PyConsole_Editor::createRequest( const QString& command )
403 return new ExecCommand( myInterp, command, this, isSync() );
407 \brief Execute command in the python interpreter
408 and wait until it is finished.
410 \param command python command to be executed
412 void PyConsole_Editor::execAndWait( const QString& command )
418 // create new event loop
419 bool sync = isSync();
421 myEventLoop = new QEventLoop( this );
430 // delete event loop after command is processed
437 \brief Process "Enter" key press event.
439 Execute the command entered by the user.
441 void PyConsole_Editor::handleReturn()
443 // Position cursor at the end
444 QTextCursor curs(textCursor());
445 curs.movePosition(QTextCursor::End);
449 QTextBlock par = document()->end().previous();
450 if ( !par.isValid() ) return;
453 QString cmd = par.text().remove( 0, promptSize() );
454 // extend the command buffer with the current command
455 myCommandBuffer.append( cmd );
456 // add command to the history
457 if ( !cmd.trimmed().isEmpty() )
458 myHistory.push_back( cmd );
459 putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
464 // set read-only mode
467 setCursor( Qt::BusyCursor );
469 // post a request to execute Python command;
470 // editor will be informed via a custom event that execution has been completed
471 PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
475 \brief Process drop event.
478 \param event drop event
480 void PyConsole_Editor::dropEvent( QDropEvent* event )
482 // get the initial drop position
483 QPoint pos = event->pos();
484 QTextCursor cur = cursorForPosition( event->pos() );
485 // if the position is not in the last line move it to the end of the command line
486 if ( cur.position() < document()->end().previous().position() + promptSize() ) {
487 moveCursor( QTextCursor::End );
488 pos = cursorRect().center();
490 // create new drop event and use it instead of the original
492 event->possibleActions(),
494 event->mouseButtons(),
495 event->keyboardModifiers(),
497 QTextEdit::dropEvent( &de );
498 // accept the original event
499 event->acceptProposedAction();
503 \brief Process mouse button release event.
505 Left mouse button: copy selection to the clipboard.
506 Middle mouse button: paste clipboard's contents.
507 \param event mouse event
509 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
511 if ( event->button() == Qt::LeftButton ) {
512 QTextEdit::mouseReleaseEvent( event );
515 else if ( event->button() == Qt::MidButton ) {
516 QTextCursor cur = cursorForPosition( event->pos() );
517 // if the position is not in the last line move it to the end of the command line
518 if ( cur.position() < document()->end().previous().position() + promptSize() ) {
519 moveCursor( QTextCursor::End );
522 setTextCursor( cur );
524 const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ?
525 QClipboard::Selection : QClipboard::Clipboard );
527 insertFromMimeData( md );
530 QTextEdit::mouseReleaseEvent( event );
535 \brief Check if the string is command.
537 Return True if the string \a str is likely to be the command
538 (i.e. it is started from the '>>>' or '...').
539 \param str string to be checked
541 bool PyConsole_Editor::isCommand( const QString& str ) const
543 return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
547 \brief Handle keyboard event.
549 Implement navigation, history browsing, copy/paste and other common
551 \param event keyboard event
553 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
555 // get cursor position
556 QTextCursor cur = textCursor();
557 int curLine = cur.blockNumber();
558 int curCol = cur.columnNumber();
560 // get last edited line
561 int endLine = document()->blockCount()-1;
563 // get pressed key code
564 int aKey = event->key();
566 // check if <Ctrl> is pressed
567 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
568 // check if <Shift> is pressed
569 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
571 if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
572 // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
573 myCommandBuffer.truncate( 0 );
574 myPrompt = READY_PROMPT;
575 addText( myPrompt, true );
576 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
579 else if ( ctrlPressed && aKey == Qt::Key_C ) {
580 // process <Ctrl>+<C> key-binding : copy
584 else if ( ctrlPressed && aKey == Qt::Key_X ) {
585 // process <Ctrl>+<X> key-binding : cut
589 else if ( ctrlPressed && aKey == Qt::Key_V ) {
590 // process <Ctrl>+<V> key-binding : paste
595 // check for printed key
596 // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
598 aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
602 // any printed key: just print it
604 if ( curLine < endLine || curCol < promptSize() ) {
605 moveCursor( QTextCursor::End );
607 QTextEdit::keyPressEvent( event );
612 // <Enter> key: process the current command
618 // <Up> arrow key: process as follows:
619 // - without <Ctrl>, <Shift> modifiers: previous command in history
620 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
621 // - with <Shift> modifier key pressed: move cursor one row up with selection
622 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
624 if ( ctrlPressed && shftPressed ) {
625 int value = verticalScrollBar()->value();
626 int spacing = fontMetrics().lineSpacing();
627 verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
629 else if ( shftPressed || ctrlPressed ) {
631 moveCursor( QTextCursor::Up,
632 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
635 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
636 // set history browsing mode
637 myCmdInHistory = myHistory.count();
638 // remember current command
639 QTextBlock par = document()->end().previous();
640 myCurrentCommand = par.text().remove( 0, promptSize() );
642 if ( myCmdInHistory > 0 ) {
644 // get previous command in the history
645 QString previousCommand = myHistory.at( myCmdInHistory );
646 // print previous command
647 moveCursor( QTextCursor::End );
648 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
649 textCursor().removeSelectedText();
650 addText( myPrompt + previousCommand );
651 // move cursor to the end
652 moveCursor( QTextCursor::End );
658 // <Down> arrow key: process as follows:
659 // - without <Ctrl>, <Shift> modifiers: next command in history
660 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
661 // - with <Shift> modifier key pressed: move cursor one row down with selection
662 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
664 if ( ctrlPressed && shftPressed ) {
665 int value = verticalScrollBar()->value();
666 int maxval = verticalScrollBar()->maximum();
667 int spacing = fontMetrics().lineSpacing();
668 verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
670 else if ( shftPressed || ctrlPressed) {
671 if ( curLine < endLine )
672 moveCursor( QTextCursor::Down,
673 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
676 if ( myCmdInHistory >= 0 ) {
677 // get next command in the history
680 if ( myCmdInHistory < myHistory.count() ) {
681 // next command in history
682 nextCommand = myHistory.at( myCmdInHistory );
685 // end of history is reached
686 // last printed command
687 nextCommand = myCurrentCommand;
688 // unset history browsing mode
691 // print next or current command
692 moveCursor( QTextCursor::End );
693 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
694 textCursor().removeSelectedText();
695 addText( myPrompt + nextCommand );
696 // move cursor to the end
697 moveCursor( QTextCursor::End );
703 // <Left> arrow key: process as follows:
704 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
705 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
706 // - with <Shift> modifier key pressed: move one symbol left with selection
707 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
709 QString txt = textCursor().block().text();
710 if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
711 moveCursor( QTextCursor::Up );
712 moveCursor( QTextCursor::EndOfBlock );
715 QTextEdit::keyPressEvent( event );
720 // <Right> arrow key: process as follows:
721 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
722 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
723 // - with <Shift> modifier key pressed: move one symbol right with selection
724 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
726 QString txt = textCursor().block().text();
727 if ( !shftPressed ) {
728 if ( curCol < txt.length() ) {
729 if ( isCommand( txt ) && curCol < promptSize() ) {
730 cur.setPosition( cur.block().position() + promptSize() );
731 setTextCursor( cur );
736 if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
737 cur.setPosition( cur.position() + promptSize()+1 );
738 setTextCursor( cur );
739 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
744 QTextEdit::keyPressEvent( event );
748 // <PageUp> key: process as follows:
749 // - without <Ctrl>, <Shift> modifiers: first command in history
750 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
751 // - with <Shift> modifier key pressed: move cursor one page up with selection
752 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
754 if ( ctrlPressed && shftPressed ) {
755 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
757 else if ( shftPressed || ctrlPressed ) {
759 qreal lastY = cursorRect( cur ).top();
761 // move using movePosition to keep the cursor's x
763 qreal y = cursorRect( cur ).top();
764 distance += qAbs( y - lastY );
766 moved = cur.movePosition( QTextCursor::Up,
767 shftPressed ? QTextCursor::KeepAnchor :
768 QTextCursor::MoveAnchor );
769 } while ( moved && distance < viewport()->height() );
771 cur.movePosition( QTextCursor::Down,
772 shftPressed ? QTextCursor::KeepAnchor :
773 QTextCursor::MoveAnchor );
774 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
776 setTextCursor( cur );
779 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
780 // set history browsing mode
781 myCmdInHistory = myHistory.count();
782 // remember current command
783 QTextBlock par = document()->end().previous();
784 myCurrentCommand = par.text().remove( 0, promptSize() );
786 if ( myCmdInHistory > 0 ) {
788 // get very first command in the history
789 QString firstCommand = myHistory.at( myCmdInHistory );
790 // print first command
791 moveCursor( QTextCursor::End );
792 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
793 textCursor().removeSelectedText();
794 addText( myPrompt + firstCommand );
795 // move cursor to the end
796 moveCursor( QTextCursor::End );
801 case Qt::Key_PageDown:
802 // <PageDown> key: process as follows:
803 // - without <Ctrl>, <Shift> modifiers: last command in history
804 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
805 // - with <Shift> modifier key pressed: move cursor one page down with selection
806 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
808 if ( ctrlPressed && shftPressed ) {
809 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
811 else if ( shftPressed || ctrlPressed ) {
813 qreal lastY = cursorRect( cur ).top();
815 // move using movePosition to keep the cursor's x
817 qreal y = cursorRect( cur ).top();
818 distance += qAbs( y - lastY );
820 moved = cur.movePosition( QTextCursor::Down,
821 shftPressed ? QTextCursor::KeepAnchor :
822 QTextCursor::MoveAnchor );
823 } while ( moved && distance < viewport()->height() );
825 cur.movePosition( QTextCursor::Up,
826 shftPressed ? QTextCursor::KeepAnchor :
827 QTextCursor::MoveAnchor );
828 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
830 setTextCursor( cur );
833 if ( myCmdInHistory >= 0 ) {
834 // unset history browsing mode
836 // print current command
837 moveCursor( QTextCursor::End );
838 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
839 textCursor().removeSelectedText();
840 addText( myPrompt + myCurrentCommand );
841 // move cursor to the end
842 moveCursor( QTextCursor::End );
848 // <Home> key: process as follows:
849 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
850 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
851 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
852 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
855 moveCursor( QTextCursor::Start,
856 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
859 QString txt = textCursor().block().text();
860 if ( isCommand( txt ) ) {
862 if ( curCol > promptSize() ) {
863 cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
864 cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
868 cur.movePosition( QTextCursor::StartOfLine );
869 cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
871 setTextCursor( cur );
874 moveCursor( QTextCursor::StartOfBlock,
875 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
877 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
882 // <End> key: process as follows:
883 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
884 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
885 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
886 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
888 moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock,
889 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
892 case Qt::Key_Backspace :
893 // <Backspace> key: process as follows
894 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
895 // - with <Shift> modifier key pressed: delete previous word
896 // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
897 // works only for last (command) line
899 if ( cur.hasSelection() ) {
902 else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
904 moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
905 textCursor().removeSelectedText();
907 else if ( ctrlPressed ) {
908 cur.setPosition( document()->end().previous().position() + promptSize(),
909 QTextCursor::KeepAnchor );
910 setTextCursor( cur );
911 textCursor().removeSelectedText();
914 QTextEdit::keyPressEvent( event );
918 cur.setPosition( document()->end().previous().position() + promptSize() );
919 setTextCursor( cur );
920 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
924 case Qt::Key_Delete :
925 // <Delete> key: process as follows
926 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
927 // - with <Shift> modifier key pressed: delete next word
928 // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
929 // works only for last (command) line
931 if ( cur.hasSelection() ) {
934 else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
936 moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
937 textCursor().removeSelectedText();
939 else if ( ctrlPressed ) {
940 moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
941 textCursor().removeSelectedText();
944 QTextEdit::keyPressEvent( event );
948 cur.setPosition( document()->end().previous().position() + promptSize() );
949 setTextCursor( cur );
950 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
954 case Qt::Key_Insert :
955 // <Insert> key: process as follows
956 // - with <Ctrl> modifier key pressed: copy()
957 // - with <Shift> modifier key pressed: paste() to the command line
962 else if ( shftPressed ) {
966 QTextEdit::keyPressEvent( event );
973 \brief Handle notification event coming from Python dispatcher.
974 \param event notification event
976 void PyConsole_Editor::customEvent( QEvent* event )
978 switch( event->type() )
980 case PrintEvent::EVENT_ID:
982 PrintEvent* pe=(PrintEvent*)event;
983 addText( pe->text(), false, pe->isError());
986 case PyInterp_Event::ES_OK:
987 case PyInterp_Event::ES_ERROR:
989 // clear command buffer
990 myCommandBuffer.truncate( 0 );
991 // add caret return line if necessary
992 QTextBlock par = document()->end().previous();
993 QString txt = par.text();
994 txt.truncate( txt.length() - 1 );
995 // IPAL19397 : addText moved to handleReturn() method
996 //if ( !txt.isEmpty() )
997 // addText( "", true );
998 // set "ready" prompt
999 myPrompt = READY_PROMPT;
1000 addText( myPrompt );
1001 // unset busy cursor
1003 // stop event loop (if running)
1005 myEventLoop->exit();
1008 case PyInterp_Event::ES_INCOMPLETE:
1010 // extend command buffer (multi-line command)
1011 myCommandBuffer.append( "\n" );
1012 // add caret return line if necessary
1013 QTextBlock par = document()->end().previous();
1014 QString txt = par.text();
1015 txt.truncate( txt.length() - 1 );
1016 // IPAL19397 : addText moved to handleReturn() method
1017 //if ( !txt.isEmpty() )
1018 // addText( "", true );
1020 myPrompt = DOTS_PROMPT;
1021 addText( myPrompt/*, true*/ ); // IPAL19397
1022 // unset busy cursor
1024 // stop event loop (if running)
1026 myEventLoop->exit();
1030 QTextEdit::customEvent( event );
1033 // unset read-only state
1034 setReadOnly( false );
1035 // unset history browsing mode
1036 myCmdInHistory = -1;
1038 if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1040 // process the next sheduled command from the queue (if there is any)
1041 QString nextcmd = myQueue[0];
1042 myQueue.pop_front();
1048 \brief Handle Python interpreter change.
1050 Perform initialization actions if the interpreter is changed.
1051 \param interp python interpreter is being set
1053 void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
1055 if ( myInterp != interp
1056 // Force read-only state and wait cursor when myInterp is NULL
1061 myBanner = myInterp->getbanner().c_str();
1062 if ( isShowBanner() )
1063 addText( myBanner );
1064 // clear command buffer
1065 myCommandBuffer.truncate(0);
1066 // unset read-only state
1067 setReadOnly( false );
1068 // unset history browsing mode
1069 myCmdInHistory = -1;
1071 addText( myPrompt );
1072 // unset busy cursor
1073 viewport()->unsetCursor();
1074 // stop event loop (if running)
1076 myEventLoop->exit();
1081 // set read-only state
1082 setReadOnly( true );
1084 setCursor( Qt::WaitCursor );
1090 \brief "Copy" operation.
1092 Reimplemented from Qt.
1093 Warning! In Qt4 this method is not virtual.
1095 void PyConsole_Editor::cut()
1097 QTextCursor cur = textCursor();
1098 if ( cur.hasSelection() ) {
1099 QApplication::clipboard()->setText( cur.selectedText() );
1100 int startSelection = cur.selectionStart();
1101 if ( startSelection < document()->end().previous().position() + promptSize() )
1102 startSelection = document()->end().previous().position() + promptSize();
1103 int endSelection = cur.selectionEnd();
1104 if ( endSelection < document()->end().previous().position() + promptSize() )
1105 endSelection = document()->end().previous().position() + promptSize();
1106 cur.setPosition( startSelection );
1107 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1108 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1109 setTextCursor( cur );
1110 textCursor().removeSelectedText();
1115 \brief "Paste" operation.
1117 Reimplemented from Qt.
1118 Warning! In Qt4 this method is not virtual.
1120 void PyConsole_Editor::paste()
1122 QTextCursor cur = textCursor();
1123 if ( cur.hasSelection() ) {
1124 int startSelection = cur.selectionStart();
1125 if ( startSelection < document()->end().previous().position() + promptSize() )
1126 startSelection = document()->end().previous().position() + promptSize();
1127 int endSelection = cur.selectionEnd();
1128 if ( endSelection < document()->end().previous().position() + promptSize() )
1129 endSelection = document()->end().previous().position() + promptSize();
1130 cur.setPosition( startSelection );
1131 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1132 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1133 setTextCursor( cur );
1134 textCursor().removeSelectedText();
1136 if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1137 moveCursor( QTextCursor::End );
1142 \brief "Clear" operation.
1144 Reimplemented from Qt.
1145 Warning! In Qt4 this method is not virtual.
1147 void PyConsole_Editor::clear()
1150 if ( isShowBanner() )
1151 addText( myBanner );
1152 myPrompt = READY_PROMPT;
1153 addText( myPrompt );
1157 \brief "Dump commands" operation.
1159 void PyConsole_Editor::dump()
1161 QStringList aFilters;
1162 aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
1164 QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1165 aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
1166 false, true, new DumpCommandsFileValidator( this ) );
1167 if ( !fileName.isEmpty() ) {
1168 QFile file( fileName );
1169 if ( !file.open( QFile::WriteOnly ) )
1172 QTextStream out (&file);
1174 for ( int i=0; i<myHistory.count(); i++ ) {
1175 out << myHistory[i] << endl;
1181 \brief "Start log" operation.
1183 void PyConsole_Editor::startLog()
1185 QStringList aFilters;
1186 aFilters.append( tr( "LOG_FILES_FILTER" ) );
1189 QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1190 aFilters, tr( "TOT_SAVE_PYLOG" ),
1192 if ( !fileName.isEmpty() ) {
1193 if ( startLog( fileName ) ) {
1197 SUIT_MessageBox::critical( this,
1198 QObject::tr("ERR_ERROR"),
1199 QObject::tr("ERR_FILE_NOT_WRITABLE") );
1209 \brief Start python trace logging
1210 \param fileName the path to the log file
1213 bool PyConsole_Editor::startLog( const QString& fileName )
1216 if ( !fileName.isEmpty() ) {
1217 QFile file( fileName );
1218 if ( file.open( QFile::WriteOnly ) ) {
1220 myLogFile = fileName;
1228 \brief "Stop log" operation.
1231 void PyConsole_Editor::stopLog()
1233 myLogFile = QString();
1237 \brief Put string to the log file
1239 void PyConsole_Editor::putLog( const QString& s )
1241 if ( !myLogFile.isEmpty() ) {
1242 QFile file( myLogFile );
1243 if ( !file.open( QFile::Append ) )
1246 QTextStream out (&file);