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
28 #include <PythonConsole_PyEditor.h> // this include must be first (see PyInterp_base.h)!
30 #include <PyInterp_Dispatcher.h>
32 #include <SUIT_Tools.h>
35 #include <qclipboard.h>
36 #include <qdragobject.h>
37 #include <qapplication.h>
38 #include <qpopupmenu.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,
60 PythonConsole_PyEditor* theListener, bool sync = false)
61 : PyInterp_LockRequest( theInterp, theListener, sync ),
62 myCommand( theCommand ), myState( PyInterp_Event::OK )
66 virtual void execute(){
68 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() started");
69 int ret = getInterp()->run( myCommand.latin1() );
70 // if(MYDEBUG) MESSAGE("ExecCommand::execute() - myInterp = "<<getInterp()<<"; myCommand = '"<<myCommand.latin1()<<"' - "<<ret);
72 myState = PyInterp_Event::ERROR;
74 myState = PyInterp_Event::INCOMPLETE;
75 myError = getInterp()->getverr().c_str();
76 myOutput = getInterp()->getvout().c_str();
77 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() finished");
84 virtual QEvent* createEvent() const
86 return new PyInterp_Event( myState, (PyInterp_Request*)this );
102 PythonConsole_PyEditor::PythonConsole_PyEditor(PyInterp_base* theInterp, QWidget *theParent, const char* theName):
103 QTextEdit(theParent,theName),
106 QString fntSet( "" );
107 QFont aFont = SUIT_Tools::stringToFont( fntSet );
109 //setTextFormat(QTextEdit::PlainText);
110 setUndoRedoEnabled( false );
112 _currentPrompt = READY_PROMPT;
115 connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
117 // san - This is necessary for troubleless initialization
118 onPyInterpChanged( theInterp );
124 PythonConsole_PyEditor::~PythonConsole_PyEditor()
126 // if(MYDEBUG) MESSAGE("PythonConsole_PyEditor::~PythonConsole_PyEditor()");
130 Called to insert a string s
132 void PythonConsole_PyEditor::setText(QString s)
134 int para=paragraphs()-1;
135 int col=paragraphLength(para);
136 insertAt(s,para,col);
137 int n = paragraphs()-1;
138 setCursorPosition( n, paragraphLength(n));
142 Convenient method for executing a Python command,
143 as if the user typed it manually
145 void PythonConsole_PyEditor::exec( const QString& command )
147 // Some interactive command is being executed in this editor -> do nothing
150 int para=paragraphs()-1;
151 removeParagraph( para );
152 _currentPrompt = READY_PROMPT;
154 _isInHistory = false;
155 setText( "\n" + _currentPrompt);
156 setText( command + "\n" );
161 Called when an handleReturn
163 void PythonConsole_PyEditor::handleReturn()
165 int para=paragraphs()-2;
166 _buf.append(text(para).remove(0,PROMPT_SIZE));
167 _buf.truncate( _buf.length() - 1 );
169 viewport()->setCursor( waitCursor );
171 // Post a request to execute Python command
172 // Editor will be informed via a custom event that execution has been completed
173 PyInterp_Dispatcher::Get()->Exec( new ExecCommand( myInterp, _buf.latin1(), this ) );
177 Processes drop event: paste dragged text
179 void PythonConsole_PyEditor::contentsDropEvent( QDropEvent* event )
181 event->acceptAction();
183 if ( QTextDrag::decode( event, text ) ) {
185 int endLine = paragraphs() -1;
186 col = charAt( event->pos(), &par );
188 if ( col >= 0 && par >= 0 ) {
189 if ( par != endLine || col < PROMPT_SIZE ) {
191 col = paragraphLength( endLine );
193 setCursorPosition( par, col );
194 insertAt( text, par, col );
201 Processes middle button release event - paste clipboard's contents
203 void PythonConsole_PyEditor::contentsMouseReleaseEvent( QMouseEvent* event )
205 if ( event->button() == LeftButton ) {
206 QTextEdit::contentsMouseReleaseEvent(event);
209 if ( event->button() == MidButton ) {
210 if (QApplication::clipboard()->supportsSelection()) {
212 int endLine = paragraphs() -1;
213 col = charAt( event->pos(), &par );
214 if ( col >= 0 && par >= 0 ) {
215 if ( par != endLine || col < PROMPT_SIZE )
216 setCursorPosition( endLine, paragraphLength( endLine ) );
218 setCursorPosition( par, col );
219 QApplication::clipboard()->setSelectionMode(TRUE);
221 QApplication::clipboard()->setSelectionMode(FALSE);
226 QTextEdit::contentsMouseReleaseEvent(event);
231 Processes own popup menu
233 void PythonConsole_PyEditor::mousePressEvent (QMouseEvent* event)
235 if ( event->button() == RightButton ) {
236 QPopupMenu *popup = new QPopupMenu( this );
237 QMap<int, int> idMap;
239 int para1, col1, para2, col2;
240 getSelection(¶1, &col1, ¶2, &col2);
241 bool allSelected = hasSelectedText() &&
242 para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
244 id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
245 idMap.insert(IdCopy, id);
246 id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
247 idMap.insert(IdPaste, id);
248 id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
249 idMap.insert(IdClear, id);
250 popup->insertSeparator();
251 id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
252 idMap.insert(IdSelectAll, id);
253 popup->setItemEnabled( idMap[ IdCopy ], hasSelectedText() );
254 popup->setItemEnabled( idMap[ IdPaste ],
255 !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
256 popup->setItemEnabled( idMap[ IdSelectAll ],
257 (bool)text().length() && !allSelected );
259 int r = popup->exec( event->globalPos() );
262 if ( r == idMap[ IdCopy ] ) {
265 else if ( r == idMap[ IdPaste ] ) {
268 else if ( r == idMap[ IdClear ] ) {
271 _currentPrompt = READY_PROMPT;
272 setText(_currentPrompt);
274 else if ( r == idMap[ IdSelectAll ] ) {
279 QTextEdit::mousePressEvent(event);
284 Checks, is the string a command line or not.
287 bool PythonConsole_PyEditor::isCommand( const QString& str) const
289 // prompt may be '>>> ' or for '... '
290 return ( str.find( READY_PROMPT ) == 0 || str.find( DOTS_PROMPT ) == 0 );
295 Called when a keyPress event
297 void PythonConsole_PyEditor::keyPressEvent( QKeyEvent* e )
299 // get cursor position
301 getCursorPosition(&curLine, &curCol);
303 // get last edited line
304 int endLine = paragraphs() -1;
306 // get pressed key code
309 // check if <Ctrl> is pressed
310 bool ctrlPressed = e->state() & ControlButton;
311 // check if <Shift> is pressed
312 bool shftPressed = e->state() & ShiftButton;
314 // process <Ctrl>+<C> key-bindings
315 if ( aKey == Key_C && ctrlPressed ) {
318 _currentPrompt = READY_PROMPT;
319 setText(_currentPrompt);
323 // check for printed key
324 aKey = ( aKey < Key_Space || aKey > Key_ydiaeresis ) ? aKey : 0;
330 if ( curLine < endLine || curCol < PROMPT_SIZE )
331 moveCursor( QTextEdit::MoveEnd, false );
332 QTextEdit::keyPressEvent( e );
339 moveCursor( QTextEdit::MoveEnd, false );
340 QTextEdit::keyPressEvent( e );
344 // <Up> arrow key: process as follows:
345 // - without <Ctrl>, <Shift> modifiers: previous command in history
346 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
347 // - with <Shift> modifier key pressed: move cursor one row up with selection
348 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
350 if ( ctrlPressed && shftPressed ) {
351 scrollBy( 0, -QFontMetrics( font() ).lineSpacing() );
353 else if ( shftPressed ) {
355 moveCursor( QTextEdit::MoveUp, true );
357 else if ( ctrlPressed ) {
358 moveCursor( QTextEdit::MoveUp, false );
361 QString histLine = _currentPrompt;
362 if ( ! _isInHistory ) {
364 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
365 _currentCommand.truncate( _currentCommand.length() - 1 );
367 QString previousCommand = myInterp->getPrevious();
368 if ( previousCommand.compare( BEGIN_HISTORY_PY ) != 0 )
370 removeParagraph( endLine );
371 histLine.append( previousCommand );
374 moveCursor( QTextEdit::MoveEnd, false );
379 // <Down> arrow key: process as follows:
380 // - without <Ctrl>, <Shift> modifiers: next command in history
381 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
382 // - with <Shift> modifier key pressed: move cursor one row down with selection
383 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
385 if ( ctrlPressed && shftPressed ) {
386 scrollBy( 0, QFontMetrics( font() ).lineSpacing() );
388 else if ( shftPressed ) {
389 if ( curLine < endLine )
390 moveCursor( QTextEdit::MoveDown, true );
392 else if ( ctrlPressed ) {
393 moveCursor( QTextEdit::MoveDown, false );
396 QString histLine = _currentPrompt;
397 QString nextCommand = myInterp->getNext();
398 if ( nextCommand.compare( TOP_HISTORY_PY ) != 0 ) {
399 removeParagraph( endLine );
400 histLine.append( nextCommand );
405 _isInHistory = false;
406 removeParagraph( endLine );
407 histLine.append( _currentCommand );
411 moveCursor( QTextEdit::MoveEnd, false );
416 // <Left> arrow key: process as follows:
417 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
418 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
419 // - with <Shift> modifier key pressed: move one symbol left with selection
420 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
422 if ( !shftPressed && isCommand( text( curLine ) ) && curCol <= PROMPT_SIZE ) {
423 setCursorPosition( curLine-1, 0 );
424 moveCursor( QTextEdit::MoveLineEnd, false );
427 QTextEdit::keyPressEvent( e );
432 // <Right> arrow key: process as follows:
433 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
434 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
435 // - with <Shift> modifier key pressed: move one symbol right with selection
436 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
438 if ( !shftPressed ) {
439 if ( curCol < paragraphLength( curLine ) ) {
440 if ( isCommand( text( curLine ) ) && curCol < PROMPT_SIZE ) {
441 setCursorPosition( curLine, PROMPT_SIZE );
446 if ( curLine < endLine && isCommand( text( curLine+1 ) ) ) {
447 setCursorPosition( curLine+1, PROMPT_SIZE );
452 QTextEdit::keyPressEvent( e );
456 // <PageUp> key: process as follows:
457 // - without <Ctrl>, <Shift> modifiers: first command in history
458 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
459 // - with <Shift> modifier key pressed: move cursor one page up with selection
460 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
462 if ( ctrlPressed && shftPressed ) {
463 scrollBy( 0, -visibleHeight() );
465 else if ( shftPressed ) {
467 moveCursor( QTextEdit::MovePgUp, true );
469 else if ( ctrlPressed ) {
470 moveCursor( QTextEdit::MovePgUp, false );
473 QString histLine = _currentPrompt;
474 if ( ! _isInHistory ) {
476 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
477 _currentCommand.truncate( _currentCommand.length() - 1 );
479 QString firstCommand = myInterp->getPrevious();
481 while ( ( pcmd = QString( myInterp->getPrevious() ) ).compare( BEGIN_HISTORY_PY ) != 0 )
483 if ( firstCommand.compare( BEGIN_HISTORY_PY ) != 0 ) {
484 removeParagraph( endLine );
485 histLine.append( firstCommand );
486 insertParagraph( histLine, -1 );
488 moveCursor( QTextEdit::MoveEnd, false );
493 // <PageDown> key: process as follows:
494 // - without <Ctrl>, <Shift> modifiers: last command in history
495 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
496 // - with <Shift> modifier key pressed: move cursor one page down with selection
497 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
499 if ( ctrlPressed && shftPressed ) {
500 scrollBy( 0, visibleHeight() );
502 else if ( shftPressed ) {
503 if ( curLine < endLine )
504 moveCursor( QTextEdit::MovePgDown, true );
506 else if ( ctrlPressed ) {
507 moveCursor( QTextEdit::MovePgDown, false );
510 if ( _isInHistory ) {
511 QString histLine = _currentPrompt;
512 while ( QString( myInterp->getNext() ).compare( TOP_HISTORY_PY ) != 0 );
513 _isInHistory = false;
514 removeParagraph( endLine );
515 histLine.append( _currentCommand );
516 insertParagraph( histLine, -1 );
518 moveCursor( QTextEdit::MoveEnd, false );
523 // <Home> key: process as follows:
524 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
525 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
526 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
527 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
530 moveCursor( QTextEdit::MoveHome, shftPressed );
533 if ( isCommand( text( curLine ) ) ) {
534 int ps1, ps2, cs1, cs2;
535 bool hasSelection = hasSelectedText();
537 getSelection( &ps1, &cs1, &ps2, &cs2 );
539 horizontalScrollBar()->setValue( horizontalScrollBar()->minValue() );
540 if ( curCol > PROMPT_SIZE && shftPressed )
541 setSelection( curLine, PROMPT_SIZE, curLine, ( hasSelection && ps1 == ps2 && ps1 == curLine && cs2 > PROMPT_SIZE ) ? cs2 : curCol );
542 setCursorPosition( curLine, PROMPT_SIZE );
545 moveCursor( QTextEdit::MoveLineStart, shftPressed );
551 // <End> key: process as follows:
552 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
553 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
554 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
555 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
558 moveCursor( QTextEdit::MoveEnd, shftPressed );
561 moveCursor( QTextEdit::MoveLineEnd, shftPressed );
566 // <Backspace> key: process as follows
567 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
568 // - with <Ctrl> modifier key pressed: delete previous word
569 // works only for last (command) line
571 if ( curLine == endLine && ( curCol > PROMPT_SIZE || curCol >= PROMPT_SIZE && hasSelectedText() ) ) {
572 if ( ctrlPressed && !hasSelectedText() ) {
573 QString txt = text( curLine );
575 while ( ind > 0 && txt[ ind ] == ' ' ) ind--;
576 ind = txt.findRev( ' ', ind ) + 1;
577 if ( ind > PROMPT_SIZE-1 ) {
578 setSelection( curLine, ind, curLine, curCol );
579 removeSelectedText();
582 QTextEdit::keyPressEvent( e );
586 QTextEdit::keyPressEvent( e );
592 // <Delete> key: process as follows
593 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
594 // - with <Ctrl> modifier key pressed: delete next word
595 // works only for last (command) line
597 if ( curLine == endLine && curCol > PROMPT_SIZE-1 ) {
598 if ( ctrlPressed && !hasSelectedText() ) {
599 QString txt = text( curLine );
601 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
602 ind = txt.find( ' ', ind );
603 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
604 if ( ind > PROMPT_SIZE-1 ) {
605 setSelection( curLine, curCol, curLine, ind );
606 removeSelectedText();
609 QTextEdit::keyPressEvent( e );
613 QTextEdit::keyPressEvent( e );
619 // <Insert> key: process as follows
620 // - with <Ctrl> modifier key pressed: copy()
621 // - with <Shift> modifier key pressed: paste() to the command line
626 else if ( shftPressed ) {
627 if ( curLine != endLine || curCol < PROMPT_SIZE )
628 moveCursor( QTextEdit::MoveEnd, false );
632 QTextEdit::keyPressEvent( e );
639 Handles notifications coming from Python dispatcher
641 void PythonConsole_PyEditor::customEvent(QCustomEvent* e)
643 switch( e->type() ) {
644 case PyInterp_Event::OK:
645 case PyInterp_Event::ERROR:
647 PyInterp_Event* pe = dynamic_cast<PyInterp_Event*>( e );
649 ExecCommand* ec = dynamic_cast<ExecCommand*>( pe->GetRequest() );
651 // The next line has appeared dangerous in case if
652 // Python command execution has produced very large output.
653 // A more clever approach is needed...
654 setText(ec->myOutput);
655 setText(ec->myError);
659 _currentPrompt = READY_PROMPT;
660 setText(_currentPrompt);
661 viewport()->unsetCursor();
664 case PyInterp_Event::INCOMPLETE:
667 _currentPrompt = DOTS_PROMPT;
668 setText(_currentPrompt);
669 viewport()->unsetCursor();
673 QTextEdit::customEvent( e );
676 setReadOnly( false );
677 _isInHistory = false;
681 Handles Python interpreter change
683 void PythonConsole_PyEditor::onPyInterpChanged( PyInterp_base* interp )
685 if ( myInterp != interp
686 // Force read-only state and wait cursor when myInterp is NULL
690 myBanner = myInterp->getbanner().c_str();
693 setReadOnly( false );
694 _isInHistory = false;
695 setText(_currentPrompt);
696 viewport()->unsetCursor();
701 viewport()->setCursor( waitCursor );
709 QPopupMenu* PythonConsole_PyEditor::createPopupMenu( const QPoint& pos )
711 QPopupMenu* popup = QTextEdit::createPopupMenu( pos );
714 for ( int i = 0; popup && i < popup->count(); i++ )
716 if ( !popup->isItemEnabled( popup->idAt( i ) ) )
717 ids.append( popup->idAt( i ) );
720 for ( QValueList<int>::const_iterator it = ids.begin(); it != ids.end(); ++it )
721 popup->removeItem( *it );
723 SUIT_Tools::simplifySeparators( popup );
725 if ( !popup->count() )