1 // Copyright (C) 2007-2014 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 static QString READY_PROMPT = ">>> ";
117 static QString DOTS_PROMPT = "... ";
119 class DumpCommandsFileValidator : public SUIT_FileValidator
122 DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {};
123 virtual ~DumpCommandsFileValidator() {};
124 virtual bool canSave( const QString& file, bool permissions );
127 bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions)
129 QFileInfo fi( file );
130 if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) {
131 SUIT_MessageBox::critical( parent(),
132 QObject::tr("WRN_WARNING"),
133 QObject::tr("WRN_FILE_NAME_BAD") );
136 return SUIT_FileValidator::canSave( file, permissions);
139 void staticCallbackStdout( void* data, char* c )
141 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
142 PyConsole_Editor* e = (PyConsole_Editor*)data;
143 e->putLog( QString::fromUtf8(c) );
144 QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), false ) );
148 void staticCallbackStderr( void* data, char* c )
150 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
151 PyConsole_Editor* e = (PyConsole_Editor*)data;
152 e->putLog( QString::fromUtf8(c) );
153 QApplication::postEvent( e, new PrintEvent( QString::fromUtf8(c), true ) );
161 Creates python editor window.
162 \param theInterp python interper
163 \param theParent parent widget
165 PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp,
167 : QTextEdit( theParent ),
169 myCmdInHistory( -1 ),
171 myShowBanner( true ),
173 myIsSuppressOutput( false )
175 QString fntSet( "" );
176 QFont aFont = SUIT_Tools::stringToFont( fntSet );
178 setUndoRedoEnabled( false );
180 myPrompt = READY_PROMPT;
181 setLineWrapMode( QTextEdit::WidgetWidth );
182 setWordWrapMode( QTextOption::WrapAnywhere );
183 setAcceptRichText( false );
185 theInterp->setvoutcb( staticCallbackStdout, this );
186 theInterp->setverrcb( staticCallbackStderr, this );
188 // san - This is necessary for troubleless initialization
189 onPyInterpChanged( theInterp );
195 Does nothing for the moment.
197 PyConsole_Editor::~PyConsole_Editor()
205 \brief Get synchronous mode flag value.
208 \return True if python console works in synchronous mode
210 bool PyConsole_Editor::isSync() const
216 \brief Set synchronous mode flag value.
218 In synhronous mode the Python commands are executed in the GUI thread
219 and the GUI is blocked until the command is finished. In the asynchronous
220 mode each Python command is executed in the separate thread that does not
221 block the main GUI loop.
223 \param on synhronous mode flag
225 void PyConsole_Editor::setIsSync( const bool on )
231 \brief Get suppress output flag value.
233 \sa setIsSuppressOutput()
234 \return \c true if python console output is suppressed.
236 bool PyConsole_Editor::isSuppressOutput() const
238 return myIsSuppressOutput;
242 \brief Set suppress output flag value.
244 In case if suppress output flag is true, the python
245 console output suppressed.
247 \param on suppress output flag
249 void PyConsole_Editor::setIsSuppressOutput( const bool on )
251 myIsSuppressOutput = on;
255 \brief Get 'show banner' flag value.
257 \sa setIsShowBanner()
258 \return \c true if python console shows banner
260 bool PyConsole_Editor::isShowBanner() const
266 \brief Set 'show banner' flag value.
268 The banner is shown in the top of the python console window.
271 \param on 'show banner' flag
273 void PyConsole_Editor::setIsShowBanner( const bool on )
275 if ( myShowBanner != on ) {
282 \brief Check if trace logging is switched on.
284 \sa startLog(), stopLog()
285 \return \c true if trace logging is switched on
287 bool PyConsole_Editor::isLogging() const
289 return !myLogFile.isEmpty();
293 \brief Get size hint for the Python console window
294 \return size hint value
296 QSize PyConsole_Editor::sizeHint() const
298 QFontMetrics fm( font() );
299 int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1;
300 QSize s(100, fm.lineSpacing()*nbLines);
305 \brief Put the string \a str to the python editor.
306 \param str string to be put in the command line of the editor
307 \param newBlock if True, then the string is printed on a new line
308 \param isError if true, the text is printed in dark red
310 void PyConsole_Editor::addText( const QString& str,
314 QTextCursor theCursor(textCursor());
317 moveCursor( QTextCursor::End );
319 theCursor.insertBlock();
321 cf.setForeground(QBrush(Qt::red));
323 cf.setForeground(QBrush(Qt::black));
324 theCursor.insertText( str, cf);
325 moveCursor( QTextCursor::End );
326 ensureCursorVisible();
330 \brief Convenient method for executing a Python command,
331 as if the user typed it manually.
332 \param command python command to be executed
334 !!! WARNING: doesn't work properly with multi-line commands. !!!
336 void PyConsole_Editor::exec( const QString& command )
338 if ( isReadOnly() ) {
339 // some interactive command is being executed in this editor...
340 // shedule the command to the queue
341 myQueue.push_back( command );
345 moveCursor( QTextCursor::End );
346 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
347 textCursor().removeSelectedText();
348 // set "ready" prompt
349 myPrompt = READY_PROMPT;
350 // clear command buffer
351 myCommandBuffer.truncate( 0 );
352 // unset history browsing mode
354 // print command line by line
355 QString cmd = command;
356 if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
357 QStringList lines = command.split( "\n" );
358 for ( int i = 0; i < lines.size(); i++ ) {
359 if ( !lines[i].trimmed().isEmpty() )
360 myHistory.push_back( lines[i] );
361 addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
362 putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
366 // set read-only mode
369 setCursor( Qt::BusyCursor );
371 // post a request to execute Python command;
372 // editor will be informed via a custom event that execution has been completed
373 PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) );
377 \brief Create request to the python dispatcher for the command execution.
379 \param command python command to be executed
381 PyInterp_Request* PyConsole_Editor::createRequest( const QString& command )
383 return new ExecCommand( myInterp, command, this, isSync() );
387 \brief Execute command in the python interpreter
388 and wait until it is finished.
390 \param command python command to be executed
392 void PyConsole_Editor::execAndWait( const QString& command )
398 // create new event loop
399 myEventLoop = new QEventLoop( this );
404 // delete event loop after command is processed
410 \brief Process "Enter" key press event.
412 Execute the command entered by the user.
414 void PyConsole_Editor::handleReturn()
416 // Position cursor at the end
417 QTextCursor curs(textCursor());
418 curs.movePosition(QTextCursor::End);
422 QTextBlock par = document()->end().previous();
423 if ( !par.isValid() ) return;
426 QString cmd = par.text().remove( 0, promptSize() );
427 // extend the command buffer with the current command
428 myCommandBuffer.append( cmd );
429 // add command to the history
430 if ( !cmd.trimmed().isEmpty() )
431 myHistory.push_back( cmd );
432 putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
437 // set read-only mode
440 setCursor( Qt::BusyCursor );
442 // post a request to execute Python command;
443 // editor will be informed via a custom event that execution has been completed
444 PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) );
448 \brief Process drop event.
451 \param event drop event
453 void PyConsole_Editor::dropEvent( QDropEvent* event )
455 // get the initial drop position
456 QPoint pos = event->pos();
457 QTextCursor cur = cursorForPosition( event->pos() );
458 // if the position is not in the last line move it to the end of the command line
459 if ( cur.position() < document()->end().previous().position() + promptSize() ) {
460 moveCursor( QTextCursor::End );
461 pos = cursorRect().center();
463 // create new drop event and use it instead of the original
465 event->possibleActions(),
467 event->mouseButtons(),
468 event->keyboardModifiers(),
470 QTextEdit::dropEvent( &de );
471 // accept the original event
472 event->acceptProposedAction();
476 \brief Process mouse button release event.
478 Left mouse button: copy selection to the clipboard.
479 Middle mouse button: paste clipboard's contents.
480 \param event mouse event
482 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
484 if ( event->button() == Qt::LeftButton ) {
485 QTextEdit::mouseReleaseEvent( event );
488 else if ( event->button() == Qt::MidButton ) {
489 QTextCursor cur = cursorForPosition( event->pos() );
490 // if the position is not in the last line move it to the end of the command line
491 if ( cur.position() < document()->end().previous().position() + promptSize() ) {
492 moveCursor( QTextCursor::End );
495 setTextCursor( cur );
497 const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ?
498 QClipboard::Selection : QClipboard::Clipboard );
500 insertFromMimeData( md );
503 QTextEdit::mouseReleaseEvent( event );
508 \brief Check if the string is command.
510 Return True if the string \a str is likely to be the command
511 (i.e. it is started from the '>>>' or '...').
512 \param str string to be checked
514 bool PyConsole_Editor::isCommand( const QString& str ) const
516 return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
520 \brief Handle keyboard event.
522 Implement navigation, history browsing, copy/paste and other common
524 \param event keyboard event
526 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
528 // get cursor position
529 QTextCursor cur = textCursor();
530 int curLine = cur.blockNumber();
531 int curCol = cur.columnNumber();
533 // get last edited line
534 int endLine = document()->blockCount()-1;
536 // get pressed key code
537 int aKey = event->key();
539 // check if <Ctrl> is pressed
540 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
541 // check if <Shift> is pressed
542 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
544 if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
545 // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
546 myCommandBuffer.truncate( 0 );
547 myPrompt = READY_PROMPT;
548 addText( myPrompt, true );
549 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
552 else if ( ctrlPressed && aKey == Qt::Key_C ) {
553 // process <Ctrl>+<C> key-binding : copy
557 else if ( ctrlPressed && aKey == Qt::Key_X ) {
558 // process <Ctrl>+<X> key-binding : cut
562 else if ( ctrlPressed && aKey == Qt::Key_V ) {
563 // process <Ctrl>+<V> key-binding : paste
568 // check for printed key
569 // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
571 aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
575 // any printed key: just print it
577 if ( curLine < endLine || curCol < promptSize() ) {
578 moveCursor( QTextCursor::End );
580 QTextEdit::keyPressEvent( event );
585 // <Enter> key: process the current command
591 // <Up> arrow key: process as follows:
592 // - without <Ctrl>, <Shift> modifiers: previous command in history
593 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
594 // - with <Shift> modifier key pressed: move cursor one row up with selection
595 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
597 if ( ctrlPressed && shftPressed ) {
598 int value = verticalScrollBar()->value();
599 int spacing = fontMetrics().lineSpacing();
600 verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
602 else if ( shftPressed || ctrlPressed ) {
604 moveCursor( QTextCursor::Up,
605 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
608 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
609 // set history browsing mode
610 myCmdInHistory = myHistory.count();
611 // remember current command
612 QTextBlock par = document()->end().previous();
613 myCurrentCommand = par.text().remove( 0, promptSize() );
615 if ( myCmdInHistory > 0 ) {
617 // get previous command in the history
618 QString previousCommand = myHistory.at( myCmdInHistory );
619 // print previous command
620 moveCursor( QTextCursor::End );
621 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
622 textCursor().removeSelectedText();
623 addText( myPrompt + previousCommand );
624 // move cursor to the end
625 moveCursor( QTextCursor::End );
631 // <Down> arrow key: process as follows:
632 // - without <Ctrl>, <Shift> modifiers: next command in history
633 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
634 // - with <Shift> modifier key pressed: move cursor one row down with selection
635 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
637 if ( ctrlPressed && shftPressed ) {
638 int value = verticalScrollBar()->value();
639 int maxval = verticalScrollBar()->maximum();
640 int spacing = fontMetrics().lineSpacing();
641 verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
643 else if ( shftPressed || ctrlPressed) {
644 if ( curLine < endLine )
645 moveCursor( QTextCursor::Down,
646 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
649 if ( myCmdInHistory >= 0 ) {
650 // get next command in the history
653 if ( myCmdInHistory < myHistory.count() ) {
654 // next command in history
655 nextCommand = myHistory.at( myCmdInHistory );
658 // end of history is reached
659 // last printed command
660 nextCommand = myCurrentCommand;
661 // unset history browsing mode
664 // print next or current command
665 moveCursor( QTextCursor::End );
666 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
667 textCursor().removeSelectedText();
668 addText( myPrompt + nextCommand );
669 // move cursor to the end
670 moveCursor( QTextCursor::End );
676 // <Left> arrow key: process as follows:
677 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
678 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
679 // - with <Shift> modifier key pressed: move one symbol left with selection
680 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
682 QString txt = textCursor().block().text();
683 if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
684 moveCursor( QTextCursor::Up );
685 moveCursor( QTextCursor::EndOfBlock );
688 QTextEdit::keyPressEvent( event );
693 // <Right> arrow key: process as follows:
694 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
695 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
696 // - with <Shift> modifier key pressed: move one symbol right with selection
697 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
699 QString txt = textCursor().block().text();
700 if ( !shftPressed ) {
701 if ( curCol < txt.length() ) {
702 if ( isCommand( txt ) && curCol < promptSize() ) {
703 cur.setPosition( cur.block().position() + promptSize() );
704 setTextCursor( cur );
709 if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
710 cur.setPosition( cur.position() + promptSize()+1 );
711 setTextCursor( cur );
712 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
717 QTextEdit::keyPressEvent( event );
721 // <PageUp> key: process as follows:
722 // - without <Ctrl>, <Shift> modifiers: first command in history
723 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
724 // - with <Shift> modifier key pressed: move cursor one page up with selection
725 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
727 if ( ctrlPressed && shftPressed ) {
728 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
730 else if ( shftPressed || ctrlPressed ) {
732 qreal lastY = cursorRect( cur ).top();
734 // move using movePosition to keep the cursor's x
736 qreal y = cursorRect( cur ).top();
737 distance += qAbs( y - lastY );
739 moved = cur.movePosition( QTextCursor::Up,
740 shftPressed ? QTextCursor::KeepAnchor :
741 QTextCursor::MoveAnchor );
742 } while ( moved && distance < viewport()->height() );
744 cur.movePosition( QTextCursor::Down,
745 shftPressed ? QTextCursor::KeepAnchor :
746 QTextCursor::MoveAnchor );
747 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
749 setTextCursor( cur );
752 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
753 // set history browsing mode
754 myCmdInHistory = myHistory.count();
755 // remember current command
756 QTextBlock par = document()->end().previous();
757 myCurrentCommand = par.text().remove( 0, promptSize() );
759 if ( myCmdInHistory > 0 ) {
761 // get very first command in the history
762 QString firstCommand = myHistory.at( myCmdInHistory );
763 // print first command
764 moveCursor( QTextCursor::End );
765 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
766 textCursor().removeSelectedText();
767 addText( myPrompt + firstCommand );
768 // move cursor to the end
769 moveCursor( QTextCursor::End );
774 case Qt::Key_PageDown:
775 // <PageDown> key: process as follows:
776 // - without <Ctrl>, <Shift> modifiers: last command in history
777 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
778 // - with <Shift> modifier key pressed: move cursor one page down with selection
779 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
781 if ( ctrlPressed && shftPressed ) {
782 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
784 else if ( shftPressed || ctrlPressed ) {
786 qreal lastY = cursorRect( cur ).top();
788 // move using movePosition to keep the cursor's x
790 qreal y = cursorRect( cur ).top();
791 distance += qAbs( y - lastY );
793 moved = cur.movePosition( QTextCursor::Down,
794 shftPressed ? QTextCursor::KeepAnchor :
795 QTextCursor::MoveAnchor );
796 } while ( moved && distance < viewport()->height() );
798 cur.movePosition( QTextCursor::Up,
799 shftPressed ? QTextCursor::KeepAnchor :
800 QTextCursor::MoveAnchor );
801 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
803 setTextCursor( cur );
806 if ( myCmdInHistory >= 0 ) {
807 // unset history browsing mode
809 // print current command
810 moveCursor( QTextCursor::End );
811 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
812 textCursor().removeSelectedText();
813 addText( myPrompt + myCurrentCommand );
814 // move cursor to the end
815 moveCursor( QTextCursor::End );
821 // <Home> key: process as follows:
822 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
823 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
824 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
825 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
828 moveCursor( QTextCursor::Start,
829 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
832 QString txt = textCursor().block().text();
833 if ( isCommand( txt ) ) {
835 if ( curCol > promptSize() ) {
836 cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
837 cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
841 cur.movePosition( QTextCursor::StartOfLine );
842 cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
844 setTextCursor( cur );
847 moveCursor( QTextCursor::StartOfBlock,
848 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
850 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
855 // <End> key: process as follows:
856 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
857 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
858 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
859 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
861 moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock,
862 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
865 case Qt::Key_Backspace :
866 // <Backspace> key: process as follows
867 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
868 // - with <Shift> modifier key pressed: delete previous word
869 // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
870 // works only for last (command) line
872 if ( cur.hasSelection() ) {
875 else if ( cur.position() > document()->end().previous().position() + promptSize() ) {
877 moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
878 textCursor().removeSelectedText();
880 else if ( ctrlPressed ) {
881 cur.setPosition( document()->end().previous().position() + promptSize(),
882 QTextCursor::KeepAnchor );
883 setTextCursor( cur );
884 textCursor().removeSelectedText();
887 QTextEdit::keyPressEvent( event );
891 cur.setPosition( document()->end().previous().position() + promptSize() );
892 setTextCursor( cur );
893 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
897 case Qt::Key_Delete :
898 // <Delete> key: process as follows
899 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
900 // - with <Shift> modifier key pressed: delete next word
901 // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
902 // works only for last (command) line
904 if ( cur.hasSelection() ) {
907 else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) {
909 moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
910 textCursor().removeSelectedText();
912 else if ( ctrlPressed ) {
913 moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
914 textCursor().removeSelectedText();
917 QTextEdit::keyPressEvent( event );
921 cur.setPosition( document()->end().previous().position() + promptSize() );
922 setTextCursor( cur );
923 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
927 case Qt::Key_Insert :
928 // <Insert> key: process as follows
929 // - with <Ctrl> modifier key pressed: copy()
930 // - with <Shift> modifier key pressed: paste() to the command line
935 else if ( shftPressed ) {
939 QTextEdit::keyPressEvent( event );
946 \brief Handle notification event coming from Python dispatcher.
947 \param event notification event
949 void PyConsole_Editor::customEvent( QEvent* event )
951 switch( event->type() )
953 case PrintEvent::EVENT_ID:
955 PrintEvent* pe=(PrintEvent*)event;
956 addText( pe->text(), false, pe->isError());
959 case PyInterp_Event::ES_OK:
960 case PyInterp_Event::ES_ERROR:
962 // clear command buffer
963 myCommandBuffer.truncate( 0 );
964 // add caret return line if necessary
965 QTextBlock par = document()->end().previous();
966 QString txt = par.text();
967 txt.truncate( txt.length() - 1 );
968 // IPAL19397 : addText moved to handleReturn() method
969 //if ( !txt.isEmpty() )
970 // addText( "", true );
971 // set "ready" prompt
972 myPrompt = READY_PROMPT;
976 // stop event loop (if running)
981 case PyInterp_Event::ES_INCOMPLETE:
983 // extend command buffer (multi-line command)
984 myCommandBuffer.append( "\n" );
985 // add caret return line if necessary
986 QTextBlock par = document()->end().previous();
987 QString txt = par.text();
988 txt.truncate( txt.length() - 1 );
989 // IPAL19397 : addText moved to handleReturn() method
990 //if ( !txt.isEmpty() )
991 // addText( "", true );
993 myPrompt = DOTS_PROMPT;
994 addText( myPrompt/*, true*/ ); // IPAL19397
997 // stop event loop (if running)
1003 QTextEdit::customEvent( event );
1006 // unset read-only state
1007 setReadOnly( false );
1008 // unset history browsing mode
1009 myCmdInHistory = -1;
1011 if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1013 // process the next sheduled command from the queue (if there is any)
1014 QString nextcmd = myQueue[0];
1015 myQueue.pop_front();
1021 \brief Handle Python interpreter change.
1023 Perform initialization actions if the interpreter is changed.
1024 \param interp python interpreter is being set
1026 void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp )
1028 if ( myInterp != interp
1029 // Force read-only state and wait cursor when myInterp is NULL
1034 myBanner = myInterp->getbanner().c_str();
1035 if ( isShowBanner() )
1036 addText( myBanner );
1037 // clear command buffer
1038 myCommandBuffer.truncate(0);
1039 // unset read-only state
1040 setReadOnly( false );
1041 // unset history browsing mode
1042 myCmdInHistory = -1;
1044 addText( myPrompt );
1045 // unset busy cursor
1046 viewport()->unsetCursor();
1047 // stop event loop (if running)
1049 myEventLoop->exit();
1054 // set read-only state
1055 setReadOnly( true );
1057 setCursor( Qt::WaitCursor );
1063 \brief "Copy" operation.
1065 Reimplemented from Qt.
1066 Warning! In Qt4 this method is not virtual.
1068 void PyConsole_Editor::cut()
1070 QTextCursor cur = textCursor();
1071 if ( cur.hasSelection() ) {
1072 QApplication::clipboard()->setText( cur.selectedText() );
1073 int startSelection = cur.selectionStart();
1074 if ( startSelection < document()->end().previous().position() + promptSize() )
1075 startSelection = document()->end().previous().position() + promptSize();
1076 int endSelection = cur.selectionEnd();
1077 if ( endSelection < document()->end().previous().position() + promptSize() )
1078 endSelection = document()->end().previous().position() + promptSize();
1079 cur.setPosition( startSelection );
1080 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1081 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1082 setTextCursor( cur );
1083 textCursor().removeSelectedText();
1088 \brief "Paste" operation.
1090 Reimplemented from Qt.
1091 Warning! In Qt4 this method is not virtual.
1093 void PyConsole_Editor::paste()
1095 QTextCursor cur = textCursor();
1096 if ( cur.hasSelection() ) {
1097 int startSelection = cur.selectionStart();
1098 if ( startSelection < document()->end().previous().position() + promptSize() )
1099 startSelection = document()->end().previous().position() + promptSize();
1100 int endSelection = cur.selectionEnd();
1101 if ( endSelection < document()->end().previous().position() + promptSize() )
1102 endSelection = document()->end().previous().position() + promptSize();
1103 cur.setPosition( startSelection );
1104 cur.setPosition( endSelection, QTextCursor::KeepAnchor );
1105 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1106 setTextCursor( cur );
1107 textCursor().removeSelectedText();
1109 if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1110 moveCursor( QTextCursor::End );
1115 \brief "Clear" operation.
1117 Reimplemented from Qt.
1118 Warning! In Qt4 this method is not virtual.
1120 void PyConsole_Editor::clear()
1123 if ( isShowBanner() )
1124 addText( myBanner );
1125 myPrompt = READY_PROMPT;
1126 addText( myPrompt );
1130 \brief "Dump commands" operation.
1132 void PyConsole_Editor::dump()
1134 QStringList aFilters;
1135 aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
1137 QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1138 aFilters, tr( "TOT_DUMP_PYCOMMANDS" ),
1139 false, true, new DumpCommandsFileValidator( this ) );
1140 if ( !fileName.isEmpty() ) {
1141 QFile file( fileName );
1142 if ( !file.open( QFile::WriteOnly ) )
1145 QTextStream out (&file);
1147 for ( int i=0; i<myHistory.count(); i++ ) {
1148 out << myHistory[i] << endl;
1154 \brief "Start log" operation.
1156 void PyConsole_Editor::startLog()
1158 QStringList aFilters;
1159 aFilters.append( tr( "LOG_FILES_FILTER" ) );
1162 QString fileName = SUIT_FileDlg::getFileName( this, QString(),
1163 aFilters, tr( "TOT_SAVE_PYLOG" ),
1165 if ( !fileName.isEmpty() ) {
1166 if ( startLog( fileName ) ) {
1170 SUIT_MessageBox::critical( this,
1171 QObject::tr("ERR_ERROR"),
1172 QObject::tr("ERR_FILE_NOT_WRITABLE") );
1182 \brief Start python trace logging
1183 \param fileName the path to the log file
1186 bool PyConsole_Editor::startLog( const QString& fileName )
1189 if ( !fileName.isEmpty() ) {
1190 QFile file( fileName );
1191 if ( file.open( QFile::WriteOnly ) ) {
1193 myLogFile = fileName;
1201 \brief "Stop log" operation.
1204 void PyConsole_Editor::stopLog()
1206 myLogFile = QString();
1210 \brief Put string to the log file
1212 void PyConsole_Editor::putLog( const QString& s )
1214 if ( !myLogFile.isEmpty() ) {
1215 QFile file( myLogFile );
1216 if ( !file.open( QFile::Append ) )
1219 QTextStream out (&file);