1 // Copyright (C) 2007-2012 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.
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 - paste multiline text: process each line as separate command
92 (including mouse middle-button click pasting)
93 - the same for drag-n-drop of multiline text
96 #include "PyConsole_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!!
97 #include "PyConsole_Editor.h"
98 #include <PyInterp_Dispatcher.h>
100 #include <SUIT_Tools.h>
101 #include <SUIT_FileDlg.h>
102 #include <SUIT_MessageBox.h>
103 #include <SUIT_FileValidator.h>
105 #include <QApplication>
106 #include <QClipboard>
107 #include <QDropEvent>
110 #include <QMouseEvent>
111 #include <QScrollBar>
112 #include <QTextBlock>
113 #include <QTextCursor>
114 #include <QTextDocument>
115 #include <QTextStream>
117 static QString READY_PROMPT = ">>> ";
118 static QString DOTS_PROMPT = "... ";
119 #define PROMPT_SIZE myPrompt.length()
121 #define PRINT_EVENT 65432
124 class DumpCommandsFileValidator : public SUIT_FileValidator
127 DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {};
128 virtual ~DumpCommandsFileValidator() {};
129 virtual bool canSave( const QString& file, bool permissions );
132 bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
134 QFileInfo fi( file );
135 if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) {
136 SUIT_MessageBox::critical( parent(),
137 QObject::tr("WRN_WARNING"),
138 QObject::tr("WRN_FILE_NAME_BAD") );
141 return SUIT_FileValidator::canSave( file, permissions);
146 \brief Python command execution request.
150 class ExecCommand : public PyInterp_LockRequest
156 Creates new python command execution request.
157 \param theInterp python interpreter
158 \param theCommand python command
159 \param theListener widget to get the notification messages
160 \param sync if True the request is processed synchronously
162 ExecCommand( PyInterp_Interp* theInterp,
163 const QString& theCommand,
164 PyConsole_Editor* theListener,
166 : PyInterp_LockRequest( theInterp, theListener, sync ),
167 myCommand( theCommand ), myState( PyInterp_Event::ES_OK )
172 \brief Execute the python command in the interpreter and
173 get its execution status.
175 virtual void execute()
177 if ( myCommand != "" )
179 int ret = getInterp()->run( myCommand.toUtf8().data() );
181 myState = PyInterp_Event::ES_ERROR;
183 myState = PyInterp_Event::ES_INCOMPLETE;
188 \brief Create and return a notification event.
189 \return new notification event
191 virtual QEvent* createEvent() const
194 QCoreApplication::sendPostedEvents( listener(), PRINT_EVENT );
195 return new PyInterp_Event( myState, (PyInterp_Request*)this );
199 QString myCommand; //!< Python command
200 int myState; //!< Python command execution status
205 \brief Python command output backend event.
209 class PrintEvent : public QEvent
214 \param c message text (python trace)
216 PrintEvent( const char* c ) : QEvent( (QEvent::Type)PRINT_EVENT ), myText( c ) {}
219 \return message text (python trace)
221 QString text() const { return myText; }
224 QString myText; //!< Event message (python trace)
227 void staticCallback( void* data, char* c )
229 if(!((PyConsole_Editor*)data)->isSuppressOutput())
230 QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c ) );
236 Creates python editor window.
237 \param theInterp python interper
238 \param theParent parent widget
240 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
242 : QTextEdit( theParent ),
244 myCmdInHistory( -1 ),
246 myShowBanner( true ),
248 myIsSuppressOutput( false )
250 QString fntSet( "" );
251 QFont aFont = SUIT_Tools::stringToFont( fntSet );
253 setUndoRedoEnabled( false );
255 myPrompt = READY_PROMPT;
256 setLineWrapMode( QTextEdit::WidgetWidth );
257 setWordWrapMode( QTextOption::WrapAnywhere );
258 setAcceptRichText( false );
260 theInterp->setvoutcb( staticCallback, this );
261 theInterp->setverrcb( staticCallback, this );
263 // san - This is necessary for troubleless initialization
264 onPyInterpChanged( theInterp );
270 Does nothing for the moment.
272 PyConsole_Editor::~PyConsole_Editor()
277 \brief Get synchronous mode flag value.
280 \return True if python console works in synchronous mode
282 bool PyConsole_Editor::isSync() const
288 \brief Set synchronous mode flag value.
290 In synhronous mode the Python commands are executed in the GUI thread
291 and the GUI is blocked until the command is finished. In the asynchronous
292 mode each Python command is executed in the separate thread that does not
293 block the main GUI loop.
295 \param on synhronous mode flag
297 void PyConsole_Editor::setIsSync( const bool on )
303 \brief Get suppress output flag value.
305 \sa setIsSuppressOutput()
306 \return \c true if python console output is suppressed.
308 bool PyConsole_Editor::isSuppressOutput() const
310 return myIsSuppressOutput;
314 \brief Set suppress output flag value.
316 In case if suppress output flag is true, the python
317 console output suppressed.
319 \param on suppress output flag
321 void PyConsole_Editor::setIsSuppressOutput( const bool on )
323 myIsSuppressOutput = on;
327 \brief Get 'show banner' flag value.
329 \sa setIsShowBanner()
330 \return \c true if python console shows banner
332 bool PyConsole_Editor::isShowBanner() const
338 \brief Set 'show banner' flag value.
340 The banner is shown in the top of the python console window.
343 \param on 'show banner' flag
345 void PyConsole_Editor::setIsShowBanner( const bool on )
347 if ( myShowBanner != on ) {
354 \brief Get size hint for the Python console window
355 \return size hint value
357 QSize PyConsole_Editor::sizeHint() const
359 QFontMetrics fm( font() );
360 int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1;
361 QSize s(100, fm.lineSpacing()*nbLines);
366 \brief Put the string \a str to the python editor.
367 \param str string to be put in the command line of the editor
368 \param newBlock if True, then the string is printed on a new line
370 void PyConsole_Editor::addText( const QString& str,
371 const bool newBlock )
373 moveCursor( QTextCursor::End );
375 textCursor().insertBlock();
376 textCursor().insertText( str );
377 moveCursor( QTextCursor::End );
378 ensureCursorVisible();
382 \brief Convenient method for executing a Python command,
383 as if the user typed it manually.
384 \param command python command to be executed
386 void PyConsole_Editor::exec( const QString& command )
388 if ( isReadOnly() ) {
389 // some interactive command is being executed in this editor...
390 // shedule the command to the queue
391 myQueue.push_back( command );
395 moveCursor( QTextCursor::End );
396 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
397 textCursor().removeSelectedText();
398 // set "ready" prompt
399 myPrompt = READY_PROMPT;
400 // clear command buffer
401 myCommandBuffer.truncate( 0 );
402 // unset history browsing mode
404 // print command line by line
405 QString cmd = command;
406 if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
407 QStringList lines = command.split( "\n" );
408 for ( int i = 0; i < lines.size(); i++ ) {
409 if ( !lines[i].trimmed().isEmpty() )
410 myHistory.push_back( lines[i] );
411 addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
415 // set read-only mode
418 setCursor( Qt::BusyCursor );
420 // post a request to execute Python command;
421 // editor will be informed via a custom event that execution has been completed
422 PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) );
426 \brief Create request to the python dispatcher for the command execution.
428 \param command python command to be executed
430 PyInterp_Request* PyConsole_Editor::createRequest( const QString& command )
432 return new ExecCommand( myInterp, command, this, isSync() );
436 \brief Execute command in the python interpreter
437 and wait until it is finished.
439 \param command python command to be executed
441 void PyConsole_Editor::execAndWait( const QString& command )
447 // create new event loop
448 myEventLoop = new QEventLoop( this );
453 // delete event loop after command is processed
459 \brief Process "Enter" key press event.
461 Execute the command entered by the user.
463 void PyConsole_Editor::handleReturn()
466 QTextBlock par = document()->end().previous();
467 if ( !par.isValid() ) return;
470 QString cmd = par.text().remove( 0, PROMPT_SIZE );
471 // extend the command buffer with the current command
472 myCommandBuffer.append( cmd );
473 // add command to the history
474 if ( !cmd.trimmed().isEmpty() )
475 myHistory.push_back( cmd );
480 // set read-only mode
483 setCursor( Qt::BusyCursor );
485 // post a request to execute Python command;
486 // editor will be informed via a custom event that execution has been completed
487 PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
491 \brief Process drop event.
494 \param event drop event
496 void PyConsole_Editor::dropEvent( QDropEvent* event )
498 // get the initial drop position
499 QPoint pos = event->pos();
500 QTextCursor cur = cursorForPosition( event->pos() );
501 // if the position is not in the last line move it to the end of the command line
502 if ( cur.position() < document()->end().previous().position() + PROMPT_SIZE ) {
503 moveCursor( QTextCursor::End );
504 pos = cursorRect().center();
506 // create new drop event and use it instead of the original
508 event->possibleActions(),
510 event->mouseButtons(),
511 event->keyboardModifiers(),
513 QTextEdit::dropEvent( &de );
514 // accept the original event
515 event->acceptProposedAction();
519 \brief Process mouse button release event.
521 Left mouse button: copy selection to the clipboard.
522 Middle mouse button: paste clipboard's contents.
523 \param event mouse event
525 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
527 if ( event->button() == Qt::LeftButton ) {
528 QTextEdit::mouseReleaseEvent( event );
531 else if ( event->button() == Qt::MidButton ) {
533 if ( QApplication::clipboard()->supportsSelection() )
534 text = QApplication::clipboard()->text( QClipboard::Selection );
535 if ( text.isEmpty() )
536 text = QApplication::clipboard()->text( QClipboard::Clipboard );
537 QTextCursor cur = cursorForPosition( event->pos() );
538 // if the position is not in the last line move it to the end of the command line
539 if ( cur.position() < document()->end().previous().position() + PROMPT_SIZE ) {
540 moveCursor( QTextCursor::End );
543 setTextCursor( cur );
545 textCursor().clearSelection();
546 textCursor().insertText( text );
549 QTextEdit::mouseReleaseEvent( event );
554 \brief Check if the string is command.
556 Return True if the string \a str is likely to be the command
557 (i.e. it is started from the '>>>' or '...').
558 \param str string to be checked
560 bool PyConsole_Editor::isCommand( const QString& str ) const
562 return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
566 \brief Handle keyboard event.
568 Implement navigation, history browsing, copy/paste and other common
570 \param event keyboard event
572 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
574 // get cursor position
575 QTextCursor cur = textCursor();
576 int curLine = cur.blockNumber();
577 int curCol = cur.columnNumber();
579 // get last edited line
580 int endLine = document()->blockCount()-1;
582 // get pressed key code
583 int aKey = event->key();
585 // check if <Ctrl> is pressed
586 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
587 // check if <Shift> is pressed
588 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
590 if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
591 // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
592 myCommandBuffer.truncate( 0 );
593 myPrompt = READY_PROMPT;
594 addText( myPrompt, true );
595 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
598 else if ( ctrlPressed && aKey == Qt::Key_C ) {
599 // process <Ctrl>+<C> key-binding : copy
603 else if ( ctrlPressed && aKey == Qt::Key_X ) {
604 // process <Ctrl>+<X> key-binding : cut
608 else if ( ctrlPressed && aKey == Qt::Key_V ) {
609 // process <Ctrl>+<V> key-binding : paste
614 // check for printed key
615 aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
619 // any printed key: just print it
621 if ( curLine < endLine || curCol < PROMPT_SIZE ) {
622 moveCursor( QTextCursor::End );
624 QTextEdit::keyPressEvent( event );
629 // <Enter> key: process the current command
635 // <Up> arrow key: process as follows:
636 // - without <Ctrl>, <Shift> modifiers: previous command in history
637 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
638 // - with <Shift> modifier key pressed: move cursor one row up with selection
639 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
641 if ( ctrlPressed && shftPressed ) {
642 int value = verticalScrollBar()->value();
643 int spacing = fontMetrics().lineSpacing();
644 verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
646 else if ( shftPressed || ctrlPressed ) {
648 moveCursor( QTextCursor::Up,
649 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
652 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
653 // set history browsing mode
654 myCmdInHistory = myHistory.count();
655 // remember current command
656 QTextBlock par = document()->end().previous();
657 myCurrentCommand = par.text().remove( 0, PROMPT_SIZE );
659 if ( myCmdInHistory > 0 ) {
661 // get previous command in the history
662 QString previousCommand = myHistory.at( myCmdInHistory );
663 // print previous command
664 moveCursor( QTextCursor::End );
665 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
666 textCursor().removeSelectedText();
667 addText( myPrompt + previousCommand );
668 // move cursor to the end
669 moveCursor( QTextCursor::End );
675 // <Down> arrow key: process as follows:
676 // - without <Ctrl>, <Shift> modifiers: next command in history
677 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
678 // - with <Shift> modifier key pressed: move cursor one row down with selection
679 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
681 if ( ctrlPressed && shftPressed ) {
682 int value = verticalScrollBar()->value();
683 int maxval = verticalScrollBar()->maximum();
684 int spacing = fontMetrics().lineSpacing();
685 verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
687 else if ( shftPressed || ctrlPressed) {
688 if ( curLine < endLine )
689 moveCursor( QTextCursor::Down,
690 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
693 if ( myCmdInHistory >= 0 ) {
694 // get next command in the history
697 if ( myCmdInHistory < myHistory.count() ) {
698 // next command in history
699 nextCommand = myHistory.at( myCmdInHistory );
702 // end of history is reached
703 // last printed command
704 nextCommand = myCurrentCommand;
705 // unset history browsing mode
708 // print next or current command
709 moveCursor( QTextCursor::End );
710 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
711 textCursor().removeSelectedText();
712 addText( myPrompt + nextCommand );
713 // move cursor to the end
714 moveCursor( QTextCursor::End );
720 // <Left> arrow key: process as follows:
721 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
722 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
723 // - with <Shift> modifier key pressed: move one symbol left with selection
724 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
726 QString txt = textCursor().block().text();
727 if ( !shftPressed && isCommand( txt ) && curCol <= PROMPT_SIZE ) {
728 moveCursor( QTextCursor::Up );
729 moveCursor( QTextCursor::EndOfBlock );
732 QTextEdit::keyPressEvent( event );
737 // <Right> arrow key: process as follows:
738 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
739 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
740 // - with <Shift> modifier key pressed: move one symbol right with selection
741 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
743 QString txt = textCursor().block().text();
744 if ( !shftPressed ) {
745 if ( curCol < txt.length() ) {
746 if ( isCommand( txt ) && curCol < PROMPT_SIZE ) {
747 cur.setPosition( cur.block().position() + PROMPT_SIZE );
748 setTextCursor( cur );
753 if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
754 cur.setPosition( cur.position() + PROMPT_SIZE+1 );
755 setTextCursor( cur );
756 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
761 QTextEdit::keyPressEvent( event );
765 // <PageUp> key: process as follows:
766 // - without <Ctrl>, <Shift> modifiers: first command in history
767 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
768 // - with <Shift> modifier key pressed: move cursor one page up with selection
769 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
771 if ( ctrlPressed && shftPressed ) {
772 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
774 else if ( shftPressed || ctrlPressed ) {
776 qreal lastY = cursorRect( cur ).top();
778 // move using movePosition to keep the cursor's x
780 qreal y = cursorRect( cur ).top();
781 distance += qAbs( y - lastY );
783 moved = cur.movePosition( QTextCursor::Up,
784 shftPressed ? QTextCursor::KeepAnchor :
785 QTextCursor::MoveAnchor );
786 } while ( moved && distance < viewport()->height() );
788 cur.movePosition( QTextCursor::Down,
789 shftPressed ? QTextCursor::KeepAnchor :
790 QTextCursor::MoveAnchor );
791 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
793 setTextCursor( cur );
796 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
797 // set history browsing mode
798 myCmdInHistory = myHistory.count();
799 // remember current command
800 QTextBlock par = document()->end().previous();
801 myCurrentCommand = par.text().remove( 0, PROMPT_SIZE );
803 if ( myCmdInHistory > 0 ) {
805 // get very first command in the history
806 QString firstCommand = myHistory.at( myCmdInHistory );
807 // print first command
808 moveCursor( QTextCursor::End );
809 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
810 textCursor().removeSelectedText();
811 addText( myPrompt + firstCommand );
812 // move cursor to the end
813 moveCursor( QTextCursor::End );
818 case Qt::Key_PageDown:
819 // <PageDown> key: process as follows:
820 // - without <Ctrl>, <Shift> modifiers: last command in history
821 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
822 // - with <Shift> modifier key pressed: move cursor one page down with selection
823 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
825 if ( ctrlPressed && shftPressed ) {
826 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
828 else if ( shftPressed || ctrlPressed ) {
830 qreal lastY = cursorRect( cur ).top();
832 // move using movePosition to keep the cursor's x
834 qreal y = cursorRect( cur ).top();
835 distance += qAbs( y - lastY );
837 moved = cur.movePosition( QTextCursor::Down,
838 shftPressed ? QTextCursor::KeepAnchor :
839 QTextCursor::MoveAnchor );
840 } while ( moved && distance < viewport()->height() );
842 cur.movePosition( QTextCursor::Up,
843 shftPressed ? QTextCursor::KeepAnchor :
844 QTextCursor::MoveAnchor );
845 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
847 setTextCursor( cur );
850 if ( myCmdInHistory >= 0 ) {
851 // unset history browsing mode
853 // print current command
854 moveCursor( QTextCursor::End );
855 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
856 textCursor().removeSelectedText();
857 addText( myPrompt + myCurrentCommand );
858 // move cursor to the end
859 moveCursor( QTextCursor::End );
865 // <Home> key: process as follows:
866 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
867 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
868 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
869 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
872 moveCursor( QTextCursor::Start,
873 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
876 QString txt = textCursor().block().text();
877 if ( isCommand( txt ) ) {
879 if ( curCol > PROMPT_SIZE ) {
880 cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
881 cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, PROMPT_SIZE );
885 cur.movePosition( QTextCursor::StartOfLine );
886 cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PROMPT_SIZE );
888 setTextCursor( cur );
891 moveCursor( QTextCursor::StartOfBlock,
892 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
894 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
899 // <End> key: process as follows:
900 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
901 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
902 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
903 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
905 moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock,
906 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
909 case Qt::Key_Backspace :
910 // <Backspace> key: process as follows
911 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
912 // - with <Shift> modifier key pressed: delete previous word
913 // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
914 // works only for last (command) line
916 if ( cur.hasSelection() ) {
919 else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE ) {
921 moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
922 textCursor().removeSelectedText();
924 else if ( ctrlPressed ) {
925 cur.setPosition( document()->end().previous().position() + PROMPT_SIZE,
926 QTextCursor::KeepAnchor );
927 setTextCursor( cur );
928 textCursor().removeSelectedText();
931 QTextEdit::keyPressEvent( event );
935 cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
936 setTextCursor( cur );
937 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
941 case Qt::Key_Delete :
942 // <Delete> key: process as follows
943 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
944 // - with <Shift> modifier key pressed: delete next word
945 // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
946 // works only for last (command) line
948 if ( cur.hasSelection() ) {
951 else if ( cur.position() > document()->end().previous().position() + PROMPT_SIZE-1 ) {
953 moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
954 textCursor().removeSelectedText();
956 else if ( ctrlPressed ) {
957 moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
958 textCursor().removeSelectedText();
961 QTextEdit::keyPressEvent( event );
965 cur.setPosition( document()->end().previous().position() + PROMPT_SIZE );
966 setTextCursor( cur );
967 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
971 case Qt::Key_Insert :
972 // <Insert> key: process as follows
973 // - with <Ctrl> modifier key pressed: copy()
974 // - with <Shift> modifier key pressed: paste() to the command line
979 else if ( shftPressed ) {
983 QTextEdit::keyPressEvent( event );
990 \brief Handle notification event coming from Python dispatcher.
991 \param event notification event
993 void PyConsole_Editor::customEvent( QEvent* event )
995 switch( event->type() )
999 PrintEvent* pe=(PrintEvent*)event;
1000 addText( pe->text() );
1003 case PyInterp_Event::ES_OK:
1004 case PyInterp_Event::ES_ERROR:
1006 // clear command buffer
1007 myCommandBuffer.truncate( 0 );
1008 // add caret return line if necessary
1009 QTextBlock par = document()->end().previous();
1010 QString txt = par.text();
1011 txt.truncate( txt.length() - 1 );
1012 // IPAL19397 : addText moved to handleReturn() method
1013 //if ( !txt.isEmpty() )
1014 // addText( "", true );
1015 // set "ready" prompt
1016 myPrompt = READY_PROMPT;
1017 addText( myPrompt );
1018 // unset busy cursor
1020 // stop event loop (if running)
1022 myEventLoop->exit();
1025 case PyInterp_Event::ES_INCOMPLETE:
1027 // extend command buffer (multi-line command)
1028 myCommandBuffer.append( "\n" );
1029 // add caret return line if necessary
1030 QTextBlock par = document()->end().previous();
1031 QString txt = par.text();
1032 txt.truncate( txt.length() - 1 );
1033 // IPAL19397 : addText moved to handleReturn() method
1034 //if ( !txt.isEmpty() )
1035 // addText( "", true );
1037 myPrompt = DOTS_PROMPT;
1038 addText( myPrompt/*, true*/ ); // IPAL19397
1039 // unset busy cursor
1041 // stop event loop (if running)
1043 myEventLoop->exit();
1047 QTextEdit::customEvent( event );
1050 // unset read-only state
1051 setReadOnly( false );
1052 // unset history browsing mode
1053 myCmdInHistory = -1;
1055 if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1057 // process the next sheduled command from the queue (if there is any)
1058 QString nextcmd = myQueue[0];
1059 myQueue.pop_front();
1065 \brief Handle Python interpreter change.
1067 Perform initialization actions if the interpreter is changed.
1068 \param interp python interpreter is being set
1070 void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
1072 if ( myInterp != interp
1073 // Force read-only state and wait cursor when myInterp is NULL
1078 myBanner = myInterp->getbanner().c_str();
1079 if ( isShowBanner() )
1080 addText( myBanner );
1081 // clear command buffer
1082 myCommandBuffer.truncate(0);
1083 // unset read-only state
1084 setReadOnly( false );
1085 // unset history browsing mode
1086 myCmdInHistory = -1;
1088 addText( myPrompt );
1089 // unset busy cursor
1090 viewport()->unsetCursor();
1091 // stop event loop (if running)
1093 myEventLoop->exit();
1098 // set read-only state
1099 setReadOnly( true );
1101 setCursor( Qt::WaitCursor );
1107 \brief "Copy" operation.
1109 Reimplemented from Qt.
1110 Warning! In Qt4 this method is not virtual.
1112 void PyConsole_Editor::cut()
1114 QTextCursor cur = textCursor();
1115 if ( cur.hasSelection() ) {
1116 QApplication::clipboard()->setText( cur.selectedText() );
1117 int startSelection = cur.selectionStart();
1118 if ( startSelection < document()->end().previous().position() + PROMPT_SIZE )
1119 startSelection = document()->end().previous().position() + PROMPT_SIZE;
1120 int endSelection = cur.selectionEnd();
1121 if ( endSelection < document()->end().previous().position() + PROMPT_SIZE )
1122 endSelection = document()->end().previous().position() + PROMPT_SIZE;
1123 cur.setPosition( startSelection );
1124 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1125 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1126 setTextCursor( cur );
1127 textCursor().removeSelectedText();
1132 \brief "Paste" operation.
1134 Reimplemented from Qt.
1135 Warning! In Qt4 this method is not virtual.
1137 void PyConsole_Editor::paste()
1139 QTextCursor cur = textCursor();
1140 if ( cur.hasSelection() ) {
1141 int startSelection = cur.selectionStart();
1142 if ( startSelection < document()->end().previous().position() + PROMPT_SIZE )
1143 startSelection = document()->end().previous().position() + PROMPT_SIZE;
1144 int endSelection = cur.selectionEnd();
1145 if ( endSelection < document()->end().previous().position() + PROMPT_SIZE )
1146 endSelection = document()->end().previous().position() + PROMPT_SIZE;
1147 cur.setPosition( startSelection );
1148 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1149 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1150 setTextCursor( cur );
1151 textCursor().removeSelectedText();
1153 if ( textCursor().position() < document()->end().previous().position() + PROMPT_SIZE )
1154 moveCursor( QTextCursor::End );
1159 \brief "Clear" operation.
1161 Reimplemented from Qt.
1162 Warning! In Qt4 this method is not virtual.
1164 void PyConsole_Editor::clear()
1167 if ( isShowBanner() )
1168 addText( myBanner );
1169 myPrompt = READY_PROMPT;
1170 addText( myPrompt );
1174 \brief "Dump commands" operation.
1176 void PyConsole_Editor::dump()
1178 QStringList aFilters;
1179 aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
1181 QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1182 aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
1183 false, true, new DumpCommandsFileValidator( this ) );
1184 if ( fileName != "" ) {
1185 QFile file( fileName );
1186 if ( !file.open( QFile::WriteOnly ) )
1189 QTextStream out (&file);
1191 for( int i=0; i<myHistory.count(); i++ ) {
1192 out<<myHistory[i]<<endl;