1 // SALOME SALOMEGUI : implementation of desktop and GUI kernel
3 // Copyright (C) 2003 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.opencascade.org/SALOME/ or email : webmaster.salome@opencascade.org
24 // File : PythonConsole_PyEditor.cxx
25 // Author : Nicolas REJNERI
29 #include <PythonConsole_PyEditor.h> // this include must be first (see PyInterp_base.h)!
30 #include <PyInterp_Dispatcher.h>
31 #include <SUIT_Tools.h>
33 #include <qapplication.h>
35 #include <qclipboard.h>
36 #include <qdragobject.h>
38 //#include "utilities.h"
43 //static int MYDEBUG = 1;
45 //static int MYDEBUG = 0;
49 enum { IdCopy, IdPaste, IdClear, IdSelectAll };
52 static QString READY_PROMPT = ">>> ";
53 static QString DOTS_PROMPT = "... ";
54 #define PROMPT_SIZE _currentPrompt.length()
56 class ExecCommand : public PyInterp_LockRequest
59 ExecCommand(PyInterp_base* theInterp, const char* theCommand, PythonConsole_PyEditor* theListener)
60 : PyInterp_LockRequest( theInterp, theListener ), myCommand(theCommand), myState( PyInterp_Event::OK )
64 virtual void execute(){
66 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() started");
67 int ret = getInterp()->run( myCommand.latin1() );
68 // if(MYDEBUG) MESSAGE("ExecCommand::execute() - myInterp = "<<getInterp()<<"; myCommand = '"<<myCommand.latin1()<<"' - "<<ret);
70 myState = PyInterp_Event::ERROR;
72 myState = PyInterp_Event::INCOMPLETE;
73 myError = getInterp()->getverr().c_str();
74 myOutput = getInterp()->getvout().c_str();
75 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() finished");
82 virtual QEvent* createEvent() const
84 return new PyInterp_Event( myState, (PyInterp_Request*)this );
100 PythonConsole_PyEditor::PythonConsole_PyEditor(PyInterp_base* theInterp, QWidget *theParent, const char* theName):
101 QTextEdit(theParent,theName),
104 QString fntSet( "" );
105 QFont aFont = SUIT_Tools::stringToFont( fntSet );
107 setTextFormat(QTextEdit::PlainText);
109 _currentPrompt = READY_PROMPT;
112 connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
114 // san - This is necessary for troubleless initialization
115 onPyInterpChanged( theInterp );
121 PythonConsole_PyEditor::~PythonConsole_PyEditor()
123 // if(MYDEBUG) MESSAGE("PythonConsole_PyEditor::~PythonConsole_PyEditor()");
127 Called to insert a string s
129 void PythonConsole_PyEditor::setText(QString s)
131 int para=paragraphs()-1;
132 int col=paragraphLength(para);
133 insertAt(s,para,col);
134 int n = paragraphs()-1;
135 setCursorPosition( n, paragraphLength(n));
139 Called when an handleReturn
141 void PythonConsole_PyEditor::handleReturn()
143 int para=paragraphs()-2;
144 _buf.append(text(para).remove(0,PROMPT_SIZE));
145 _buf.truncate( _buf.length() - 1 );
147 viewport()->setCursor( waitCursor );
149 // Post a request to execute Python command
150 // Editor will be informed via a custom event that execution has been completed
151 PyInterp_Dispatcher::Get()->Exec( new ExecCommand( myInterp, _buf.latin1(), this ) );
155 Processes drop event: paste dragged text
157 void PythonConsole_PyEditor::contentsDropEvent( QDropEvent* event )
159 event->acceptAction();
161 if ( QTextDrag::decode( event, text ) ) {
163 int endLine = paragraphs() -1;
164 col = charAt( event->pos(), &par );
166 if ( col >= 0 && par >= 0 ) {
167 if ( par != endLine || col < PROMPT_SIZE ) {
169 col = paragraphLength( endLine );
171 setCursorPosition( par, col );
172 insertAt( text, par, col );
179 Processes middle button release event - paste clipboard's contents
181 void PythonConsole_PyEditor::contentsMouseReleaseEvent( QMouseEvent* event )
183 if ( event->button() == LeftButton ) {
184 QTextEdit::contentsMouseReleaseEvent(event);
187 if ( event->button() == MidButton ) {
188 if (QApplication::clipboard()->supportsSelection()) {
190 int endLine = paragraphs() -1;
191 col = charAt( event->pos(), &par );
192 if ( col >= 0 && par >= 0 ) {
193 if ( par != endLine || col < PROMPT_SIZE )
194 setCursorPosition( endLine, paragraphLength( endLine ) );
196 setCursorPosition( par, col );
197 QApplication::clipboard()->setSelectionMode(TRUE);
199 QApplication::clipboard()->setSelectionMode(FALSE);
204 QTextEdit::contentsMouseReleaseEvent(event);
209 Processes own popup menu
211 void PythonConsole_PyEditor::mousePressEvent (QMouseEvent* event)
213 if ( event->button() == RightButton ) {
214 QPopupMenu *popup = new QPopupMenu( this );
215 QMap<int, int> idMap;
217 int para1, col1, para2, col2;
218 getSelection(¶1, &col1, ¶2, &col2);
219 bool allSelected = hasSelectedText() &&
220 para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
222 id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
223 idMap.insert(IdCopy, id);
224 id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
225 idMap.insert(IdPaste, id);
226 id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
227 idMap.insert(IdClear, id);
228 popup->insertSeparator();
229 id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
230 idMap.insert(IdSelectAll, id);
231 popup->setItemEnabled( idMap[ IdCopy ], hasSelectedText() );
232 popup->setItemEnabled( idMap[ IdPaste ],
233 !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
234 popup->setItemEnabled( idMap[ IdSelectAll ],
235 (bool)text().length() && !allSelected );
237 int r = popup->exec( event->globalPos() );
240 if ( r == idMap[ IdCopy ] ) {
243 else if ( r == idMap[ IdPaste ] ) {
246 else if ( r == idMap[ IdClear ] ) {
249 _currentPrompt = READY_PROMPT;
250 setText(_currentPrompt);
252 else if ( r == idMap[ IdSelectAll ] ) {
257 QTextEdit::mousePressEvent(event);
262 Checks, is the string a command line or not.
265 bool PythonConsole_PyEditor::isCommand( const QString& str) const
267 // prompt may be '>>> ' or for '... '
268 return ( str.find( READY_PROMPT ) == 0 || str.find( DOTS_PROMPT ) == 0 );
273 Called when a keyPress event
275 void PythonConsole_PyEditor::keyPressEvent( QKeyEvent* e )
277 // get cursor position
279 getCursorPosition(&curLine, &curCol);
281 // get last edited line
282 int endLine = paragraphs() -1;
284 // get pressed key code
287 // check if <Ctrl> is pressed
288 bool ctrlPressed = e->state() & ControlButton;
289 // check if <Shift> is pressed
290 bool shftPressed = e->state() & ShiftButton;
291 // check if <Alt> is pressed
292 bool altPressed = e->state() & AltButton;
294 // process <Ctrl>+<C> key-bindings
295 if ( aKey == Key_C && ctrlPressed ) {
298 _currentPrompt = READY_PROMPT;
299 setText(_currentPrompt);
303 // check for printed key
304 aKey = ( aKey < Key_Space || aKey > Key_ydiaeresis ) ? aKey : 0;
310 if ( curLine < endLine || curCol < PROMPT_SIZE )
311 moveCursor( QTextEdit::MoveEnd, false );
312 QTextEdit::keyPressEvent( e );
319 moveCursor( QTextEdit::MoveEnd, false );
320 QTextEdit::keyPressEvent( e );
324 // <Up> arrow key: process as follows:
325 // - without <Ctrl>, <Shift> modifiers: previous command in history
326 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
327 // - with <Shift> modifier key pressed: move cursor one row up with selection
328 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
330 if ( ctrlPressed && shftPressed ) {
331 scrollBy( 0, -QFontMetrics( font() ).lineSpacing() );
333 else if ( shftPressed ) {
335 moveCursor( QTextEdit::MoveUp, true );
337 else if ( ctrlPressed ) {
338 moveCursor( QTextEdit::MoveUp, false );
341 QString histLine = _currentPrompt;
342 if ( ! _isInHistory ) {
344 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
345 _currentCommand.truncate( _currentCommand.length() - 1 );
347 QString previousCommand = myInterp->getPrevious();
348 if ( previousCommand.compare( BEGIN_HISTORY_PY ) != 0 )
350 removeParagraph( endLine );
351 histLine.append( previousCommand );
354 moveCursor( QTextEdit::MoveEnd, false );
359 // <Down> arrow key: process as follows:
360 // - without <Ctrl>, <Shift> modifiers: next command in history
361 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
362 // - with <Shift> modifier key pressed: move cursor one row down with selection
363 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
365 if ( ctrlPressed && shftPressed ) {
366 scrollBy( 0, QFontMetrics( font() ).lineSpacing() );
368 else if ( shftPressed ) {
369 if ( curLine < endLine )
370 moveCursor( QTextEdit::MoveDown, true );
372 else if ( ctrlPressed ) {
373 moveCursor( QTextEdit::MoveDown, false );
376 QString histLine = _currentPrompt;
377 QString nextCommand = myInterp->getNext();
378 if ( nextCommand.compare( TOP_HISTORY_PY ) != 0 ) {
379 removeParagraph( endLine );
380 histLine.append( nextCommand );
385 _isInHistory = false;
386 removeParagraph( endLine );
387 histLine.append( _currentCommand );
391 moveCursor( QTextEdit::MoveEnd, false );
396 // <Left> arrow key: process as follows:
397 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
398 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
399 // - with <Shift> modifier key pressed: move one symbol left with selection
400 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
402 if ( !shftPressed && isCommand( text( curLine ) ) && curCol <= PROMPT_SIZE ) {
403 setCursorPosition( curLine-1, 0 );
404 moveCursor( QTextEdit::MoveLineEnd, false );
407 QTextEdit::keyPressEvent( e );
412 // <Right> arrow key: process as follows:
413 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
414 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
415 // - with <Shift> modifier key pressed: move one symbol right with selection
416 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
418 if ( !shftPressed ) {
419 if ( curCol < paragraphLength( curLine ) ) {
420 if ( isCommand( text( curLine ) ) && curCol < PROMPT_SIZE ) {
421 setCursorPosition( curLine, PROMPT_SIZE );
426 if ( curLine < endLine && isCommand( text( curLine+1 ) ) ) {
427 setCursorPosition( curLine+1, PROMPT_SIZE );
432 QTextEdit::keyPressEvent( e );
436 // <PageUp> key: process as follows:
437 // - without <Ctrl>, <Shift> modifiers: first command in history
438 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
439 // - with <Shift> modifier key pressed: move cursor one page up with selection
440 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
442 if ( ctrlPressed && shftPressed ) {
443 scrollBy( 0, -visibleHeight() );
445 else if ( shftPressed ) {
447 moveCursor( QTextEdit::MovePgUp, true );
449 else if ( ctrlPressed ) {
450 moveCursor( QTextEdit::MovePgUp, false );
453 QString histLine = _currentPrompt;
454 if ( ! _isInHistory ) {
456 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
457 _currentCommand.truncate( _currentCommand.length() - 1 );
459 QString firstCommand = myInterp->getPrevious();
461 while ( ( pcmd = QString( myInterp->getPrevious() ) ).compare( BEGIN_HISTORY_PY ) != 0 )
463 if ( firstCommand.compare( BEGIN_HISTORY_PY ) != 0 ) {
464 removeParagraph( endLine );
465 histLine.append( firstCommand );
466 insertParagraph( histLine, -1 );
468 moveCursor( QTextEdit::MoveEnd, false );
473 // <PageDown> key: process as follows:
474 // - without <Ctrl>, <Shift> modifiers: last command in history
475 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
476 // - with <Shift> modifier key pressed: move cursor one page down with selection
477 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
479 if ( ctrlPressed && shftPressed ) {
480 scrollBy( 0, visibleHeight() );
482 else if ( shftPressed ) {
483 if ( curLine < endLine )
484 moveCursor( QTextEdit::MovePgDown, true );
486 else if ( ctrlPressed ) {
487 moveCursor( QTextEdit::MovePgDown, false );
490 if ( _isInHistory ) {
491 QString histLine = _currentPrompt;
492 while ( QString( myInterp->getNext() ).compare( TOP_HISTORY_PY ) != 0 );
493 _isInHistory = false;
494 removeParagraph( endLine );
495 histLine.append( _currentCommand );
496 insertParagraph( histLine, -1 );
498 moveCursor( QTextEdit::MoveEnd, false );
503 // <Home> key: process as follows:
504 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
505 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
506 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
507 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
510 moveCursor( QTextEdit::MoveHome, shftPressed );
513 if ( isCommand( text( curLine ) ) ) {
514 int ps1, ps2, cs1, cs2;
515 bool hasSelection = hasSelectedText();
517 getSelection( &ps1, &cs1, &ps2, &cs2 );
519 horizontalScrollBar()->setValue( horizontalScrollBar()->minValue() );
520 if ( curCol > PROMPT_SIZE && shftPressed )
521 setSelection( curLine, PROMPT_SIZE, curLine, ( hasSelection && ps1 == ps2 && ps1 == curLine && cs2 > PROMPT_SIZE ) ? cs2 : curCol );
522 setCursorPosition( curLine, PROMPT_SIZE );
525 moveCursor( QTextEdit::MoveLineStart, shftPressed );
531 // <End> key: process as follows:
532 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
533 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
534 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
535 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
538 moveCursor( QTextEdit::MoveEnd, shftPressed );
541 moveCursor( QTextEdit::MoveLineEnd, shftPressed );
546 // <Backspace> key: process as follows
547 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
548 // - with <Ctrl> modifier key pressed: delete previous word
549 // works only for last (command) line
551 if ( curLine == endLine && ( curCol > PROMPT_SIZE || curCol >= PROMPT_SIZE && hasSelectedText() ) ) {
552 if ( ctrlPressed && !hasSelectedText() ) {
553 QString txt = text( curLine );
555 while ( ind > 0 && txt[ ind ] == ' ' ) ind--;
556 ind = txt.findRev( ' ', ind ) + 1;
557 if ( ind > PROMPT_SIZE-1 ) {
558 setSelection( curLine, ind, curLine, curCol );
559 removeSelectedText();
562 QTextEdit::keyPressEvent( e );
566 QTextEdit::keyPressEvent( e );
572 // <Delete> key: process as follows
573 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
574 // - with <Ctrl> modifier key pressed: delete next word
575 // works only for last (command) line
577 if ( curLine == endLine && curCol > PROMPT_SIZE-1 ) {
578 if ( ctrlPressed && !hasSelectedText() ) {
579 QString txt = text( curLine );
581 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
582 ind = txt.find( ' ', ind );
583 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
584 if ( ind > PROMPT_SIZE-1 ) {
585 setSelection( curLine, curCol, curLine, ind );
586 removeSelectedText();
589 QTextEdit::keyPressEvent( e );
593 QTextEdit::keyPressEvent( e );
599 // <Insert> key: process as follows
600 // - with <Ctrl> modifier key pressed: copy()
601 // - with <Shift> modifier key pressed: paste() to the command line
606 else if ( shftPressed ) {
607 if ( curLine != endLine || curCol < PROMPT_SIZE )
608 moveCursor( QTextEdit::MoveEnd, false );
612 QTextEdit::keyPressEvent( e );
619 Handles notifications coming from Python dispatcher
621 void PythonConsole_PyEditor::customEvent(QCustomEvent* e)
623 switch( e->type() ) {
624 case PyInterp_Event::OK:
625 case PyInterp_Event::ERROR:
627 PyInterp_Event* pe = dynamic_cast<PyInterp_Event*>( e );
629 ExecCommand* ec = dynamic_cast<ExecCommand*>( pe->GetRequest() );
631 // The next line has appeared dangerous in case if
632 // Python command execution has produced very large output.
633 // A more clever approach is needed...
634 setText(ec->myOutput);
635 setText(ec->myError);
639 _currentPrompt = READY_PROMPT;
640 setText(_currentPrompt);
641 viewport()->unsetCursor();
644 case PyInterp_Event::INCOMPLETE:
647 _currentPrompt = DOTS_PROMPT;
648 setText(_currentPrompt);
649 viewport()->unsetCursor();
653 QTextEdit::customEvent( e );
656 setReadOnly( false );
657 _isInHistory = false;
661 Handles Python interpreter change
663 void PythonConsole_PyEditor::onPyInterpChanged( PyInterp_base* interp )
665 if ( myInterp != interp
666 // Force read-only state and wait cursor when myInterp is NULL
670 myBanner = myInterp->getbanner().c_str();
673 setReadOnly( false );
674 _isInHistory = false;
675 setText(_currentPrompt);
676 viewport()->unsetCursor();
681 viewport()->setCursor( waitCursor );