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>
41 enum { IdCopy, IdPaste, IdClear, IdSelectAll };
43 static QString READY_PROMPT = ">>> ";
44 static QString DOTS_PROMPT = "... ";
45 #define PROMPT_SIZE _currentPrompt.length()
47 class ExecCommand : public PyInterp_LockRequest
50 ExecCommand(PyInterp_base* theInterp, const char* theCommand, PythonConsole_PyEditor* theListener)
51 : PyInterp_LockRequest( theInterp, theListener ), myCommand(theCommand), myState( PyInterp_Event::OK )
55 virtual void execute(){
57 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() started");
58 int ret = getInterp()->run( myCommand.latin1() );
59 // if(MYDEBUG) MESSAGE("ExecCommand::execute() - myInterp = "<<getInterp()<<"; myCommand = '"<<myCommand.latin1()<<"' - "<<ret);
61 myState = PyInterp_Event::ERROR;
63 myState = PyInterp_Event::INCOMPLETE;
64 myError = getInterp()->getverr().c_str();
65 myOutput = getInterp()->getvout().c_str();
66 // if(MYDEBUG) MESSAGE("*** ExecCommand::execute() finished");
73 virtual QEvent* createEvent() const
75 return new PyInterp_Event( myState, (PyInterp_Request*)this );
91 PythonConsole_PyEditor::PythonConsole_PyEditor(PyInterp_base* theInterp, QWidget *theParent, const char* theName):
92 QTextEdit(theParent,theName),
96 QFont aFont = SUIT_Tools::stringToFont( fntSet );
98 setTextFormat(QTextEdit::PlainText);
99 setUndoRedoEnabled( false );
101 _currentPrompt = READY_PROMPT;
104 connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
106 // san - This is necessary for troubleless initialization
107 onPyInterpChanged( theInterp );
113 PythonConsole_PyEditor::~PythonConsole_PyEditor()
115 // if(MYDEBUG) MESSAGE("PythonConsole_PyEditor::~PythonConsole_PyEditor()");
119 Called to insert a string s
121 void PythonConsole_PyEditor::setText(QString s)
123 int para=paragraphs()-1;
124 int col=paragraphLength(para);
125 insertAt(s,para,col);
126 int n = paragraphs()-1;
127 setCursorPosition( n, paragraphLength(n));
131 Called when an handleReturn
133 void PythonConsole_PyEditor::handleReturn()
135 int para=paragraphs()-2;
136 _buf.append(text(para).remove(0,PROMPT_SIZE));
137 _buf.truncate( _buf.length() - 1 );
139 viewport()->setCursor( waitCursor );
141 // Post a request to execute Python command
142 // Editor will be informed via a custom event that execution has been completed
143 PyInterp_Dispatcher::Get()->Exec( new ExecCommand( myInterp, _buf.latin1(), this ) );
147 Processes drop event: paste dragged text
149 void PythonConsole_PyEditor::contentsDropEvent( QDropEvent* event )
151 event->acceptAction();
153 if ( QTextDrag::decode( event, text ) ) {
155 int endLine = paragraphs() -1;
156 col = charAt( event->pos(), &par );
158 if ( col >= 0 && par >= 0 ) {
159 if ( par != endLine || col < PROMPT_SIZE ) {
161 col = paragraphLength( endLine );
163 setCursorPosition( par, col );
164 insertAt( text, par, col );
171 Processes middle button release event - paste clipboard's contents
173 void PythonConsole_PyEditor::contentsMouseReleaseEvent( QMouseEvent* event )
175 if ( event->button() == LeftButton ) {
176 QTextEdit::contentsMouseReleaseEvent(event);
179 if ( event->button() == MidButton ) {
180 if (QApplication::clipboard()->supportsSelection()) {
182 int endLine = paragraphs() -1;
183 col = charAt( event->pos(), &par );
184 if ( col >= 0 && par >= 0 ) {
185 if ( par != endLine || col < PROMPT_SIZE )
186 setCursorPosition( endLine, paragraphLength( endLine ) );
188 setCursorPosition( par, col );
189 QApplication::clipboard()->setSelectionMode(TRUE);
191 QApplication::clipboard()->setSelectionMode(FALSE);
196 QTextEdit::contentsMouseReleaseEvent(event);
201 Processes own popup menu
203 void PythonConsole_PyEditor::mousePressEvent (QMouseEvent* event)
205 if ( event->button() == RightButton ) {
206 QPopupMenu *popup = new QPopupMenu( this );
207 QMap<int, int> idMap;
209 int para1, col1, para2, col2;
210 getSelection(¶1, &col1, ¶2, &col2);
211 bool allSelected = hasSelectedText() &&
212 para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
214 id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
215 idMap.insert(IdCopy, id);
216 id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
217 idMap.insert(IdPaste, id);
218 id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
219 idMap.insert(IdClear, id);
220 popup->insertSeparator();
221 id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
222 idMap.insert(IdSelectAll, id);
223 popup->setItemEnabled( idMap[ IdCopy ], hasSelectedText() );
224 popup->setItemEnabled( idMap[ IdPaste ],
225 !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
226 popup->setItemEnabled( idMap[ IdSelectAll ],
227 (bool)text().length() && !allSelected );
229 int r = popup->exec( event->globalPos() );
232 if ( r == idMap[ IdCopy ] ) {
235 else if ( r == idMap[ IdPaste ] ) {
238 else if ( r == idMap[ IdClear ] ) {
241 _currentPrompt = READY_PROMPT;
242 setText(_currentPrompt);
244 else if ( r == idMap[ IdSelectAll ] ) {
249 QTextEdit::mousePressEvent(event);
254 Checks, is the string a command line or not.
257 bool PythonConsole_PyEditor::isCommand( const QString& str) const
259 // prompt may be '>>> ' or for '... '
260 return ( str.find( READY_PROMPT ) == 0 || str.find( DOTS_PROMPT ) == 0 );
265 Called when a keyPress event
267 void PythonConsole_PyEditor::keyPressEvent( QKeyEvent* e )
269 // get cursor position
271 getCursorPosition(&curLine, &curCol);
273 // get last edited line
274 int endLine = paragraphs() -1;
276 // get pressed key code
279 // check if <Ctrl> is pressed
280 bool ctrlPressed = e->state() & ControlButton;
281 // check if <Shift> is pressed
282 bool shftPressed = e->state() & ShiftButton;
283 // check if <Alt> is pressed
284 bool altPressed = e->state() & AltButton;
286 // process <Ctrl>+<C> key-bindings
287 if ( aKey == Key_C && ctrlPressed ) {
290 _currentPrompt = READY_PROMPT;
291 setText(_currentPrompt);
295 // check for printed key
296 aKey = ( aKey < Key_Space || aKey > Key_ydiaeresis ) ? aKey : 0;
302 if ( curLine < endLine || curCol < PROMPT_SIZE )
303 moveCursor( QTextEdit::MoveEnd, false );
304 QTextEdit::keyPressEvent( e );
311 moveCursor( QTextEdit::MoveEnd, false );
312 QTextEdit::keyPressEvent( e );
316 // <Up> arrow key: process as follows:
317 // - without <Ctrl>, <Shift> modifiers: previous command in history
318 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
319 // - with <Shift> modifier key pressed: move cursor one row up with selection
320 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
322 if ( ctrlPressed && shftPressed ) {
323 scrollBy( 0, -QFontMetrics( font() ).lineSpacing() );
325 else if ( shftPressed ) {
327 moveCursor( QTextEdit::MoveUp, true );
329 else if ( ctrlPressed ) {
330 moveCursor( QTextEdit::MoveUp, false );
333 QString histLine = _currentPrompt;
334 if ( ! _isInHistory ) {
336 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
337 _currentCommand.truncate( _currentCommand.length() - 1 );
339 QString previousCommand = myInterp->getPrevious();
340 if ( previousCommand.compare( BEGIN_HISTORY_PY ) != 0 )
342 removeParagraph( endLine );
343 histLine.append( previousCommand );
346 moveCursor( QTextEdit::MoveEnd, false );
351 // <Down> arrow key: process as follows:
352 // - without <Ctrl>, <Shift> modifiers: next command in history
353 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
354 // - with <Shift> modifier key pressed: move cursor one row down with selection
355 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
357 if ( ctrlPressed && shftPressed ) {
358 scrollBy( 0, QFontMetrics( font() ).lineSpacing() );
360 else if ( shftPressed ) {
361 if ( curLine < endLine )
362 moveCursor( QTextEdit::MoveDown, true );
364 else if ( ctrlPressed ) {
365 moveCursor( QTextEdit::MoveDown, false );
368 QString histLine = _currentPrompt;
369 QString nextCommand = myInterp->getNext();
370 if ( nextCommand.compare( TOP_HISTORY_PY ) != 0 ) {
371 removeParagraph( endLine );
372 histLine.append( nextCommand );
377 _isInHistory = false;
378 removeParagraph( endLine );
379 histLine.append( _currentCommand );
383 moveCursor( QTextEdit::MoveEnd, false );
388 // <Left> arrow key: process as follows:
389 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
390 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
391 // - with <Shift> modifier key pressed: move one symbol left with selection
392 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
394 if ( !shftPressed && isCommand( text( curLine ) ) && curCol <= PROMPT_SIZE ) {
395 setCursorPosition( curLine-1, 0 );
396 moveCursor( QTextEdit::MoveLineEnd, false );
399 QTextEdit::keyPressEvent( e );
404 // <Right> arrow key: process as follows:
405 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
406 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
407 // - with <Shift> modifier key pressed: move one symbol right with selection
408 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
410 if ( !shftPressed ) {
411 if ( curCol < paragraphLength( curLine ) ) {
412 if ( isCommand( text( curLine ) ) && curCol < PROMPT_SIZE ) {
413 setCursorPosition( curLine, PROMPT_SIZE );
418 if ( curLine < endLine && isCommand( text( curLine+1 ) ) ) {
419 setCursorPosition( curLine+1, PROMPT_SIZE );
424 QTextEdit::keyPressEvent( e );
428 // <PageUp> key: process as follows:
429 // - without <Ctrl>, <Shift> modifiers: first command in history
430 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
431 // - with <Shift> modifier key pressed: move cursor one page up with selection
432 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
434 if ( ctrlPressed && shftPressed ) {
435 scrollBy( 0, -visibleHeight() );
437 else if ( shftPressed ) {
439 moveCursor( QTextEdit::MovePgUp, true );
441 else if ( ctrlPressed ) {
442 moveCursor( QTextEdit::MovePgUp, false );
445 QString histLine = _currentPrompt;
446 if ( ! _isInHistory ) {
448 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
449 _currentCommand.truncate( _currentCommand.length() - 1 );
451 QString firstCommand = myInterp->getPrevious();
453 while ( ( pcmd = QString( myInterp->getPrevious() ) ).compare( BEGIN_HISTORY_PY ) != 0 )
455 if ( firstCommand.compare( BEGIN_HISTORY_PY ) != 0 ) {
456 removeParagraph( endLine );
457 histLine.append( firstCommand );
458 insertParagraph( histLine, -1 );
460 moveCursor( QTextEdit::MoveEnd, false );
465 // <PageDown> key: process as follows:
466 // - without <Ctrl>, <Shift> modifiers: last command in history
467 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
468 // - with <Shift> modifier key pressed: move cursor one page down with selection
469 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
471 if ( ctrlPressed && shftPressed ) {
472 scrollBy( 0, visibleHeight() );
474 else if ( shftPressed ) {
475 if ( curLine < endLine )
476 moveCursor( QTextEdit::MovePgDown, true );
478 else if ( ctrlPressed ) {
479 moveCursor( QTextEdit::MovePgDown, false );
482 if ( _isInHistory ) {
483 QString histLine = _currentPrompt;
484 while ( QString( myInterp->getNext() ).compare( TOP_HISTORY_PY ) != 0 );
485 _isInHistory = false;
486 removeParagraph( endLine );
487 histLine.append( _currentCommand );
488 insertParagraph( histLine, -1 );
490 moveCursor( QTextEdit::MoveEnd, false );
495 // <Home> key: process as follows:
496 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
497 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
498 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
499 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
502 moveCursor( QTextEdit::MoveHome, shftPressed );
505 if ( isCommand( text( curLine ) ) ) {
506 int ps1, ps2, cs1, cs2;
507 bool hasSelection = hasSelectedText();
509 getSelection( &ps1, &cs1, &ps2, &cs2 );
511 horizontalScrollBar()->setValue( horizontalScrollBar()->minValue() );
512 if ( curCol > PROMPT_SIZE && shftPressed )
513 setSelection( curLine, PROMPT_SIZE, curLine, ( hasSelection && ps1 == ps2 && ps1 == curLine && cs2 > PROMPT_SIZE ) ? cs2 : curCol );
514 setCursorPosition( curLine, PROMPT_SIZE );
517 moveCursor( QTextEdit::MoveLineStart, shftPressed );
523 // <End> key: process as follows:
524 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
525 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
526 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
527 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
530 moveCursor( QTextEdit::MoveEnd, shftPressed );
533 moveCursor( QTextEdit::MoveLineEnd, shftPressed );
538 // <Backspace> key: process as follows
539 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
540 // - with <Ctrl> modifier key pressed: delete previous word
541 // works only for last (command) line
543 if ( curLine == endLine && ( curCol > PROMPT_SIZE || curCol >= PROMPT_SIZE && hasSelectedText() ) ) {
544 if ( ctrlPressed && !hasSelectedText() ) {
545 QString txt = text( curLine );
547 while ( ind > 0 && txt[ ind ] == ' ' ) ind--;
548 ind = txt.findRev( ' ', ind ) + 1;
549 if ( ind > PROMPT_SIZE-1 ) {
550 setSelection( curLine, ind, curLine, curCol );
551 removeSelectedText();
554 QTextEdit::keyPressEvent( e );
558 QTextEdit::keyPressEvent( e );
564 // <Delete> key: process as follows
565 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
566 // - with <Ctrl> modifier key pressed: delete next word
567 // works only for last (command) line
569 if ( curLine == endLine && curCol > PROMPT_SIZE-1 ) {
570 if ( ctrlPressed && !hasSelectedText() ) {
571 QString txt = text( curLine );
573 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
574 ind = txt.find( ' ', ind );
575 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
576 if ( ind > PROMPT_SIZE-1 ) {
577 setSelection( curLine, curCol, curLine, ind );
578 removeSelectedText();
581 QTextEdit::keyPressEvent( e );
585 QTextEdit::keyPressEvent( e );
591 // <Insert> key: process as follows
592 // - with <Ctrl> modifier key pressed: copy()
593 // - with <Shift> modifier key pressed: paste() to the command line
598 else if ( shftPressed ) {
599 if ( curLine != endLine || curCol < PROMPT_SIZE )
600 moveCursor( QTextEdit::MoveEnd, false );
604 QTextEdit::keyPressEvent( e );
611 Handles notifications coming from Python dispatcher
613 void PythonConsole_PyEditor::customEvent(QCustomEvent* e)
615 switch( e->type() ) {
616 case PyInterp_Event::OK:
617 case PyInterp_Event::ERROR:
619 PyInterp_Event* pe = dynamic_cast<PyInterp_Event*>( e );
621 ExecCommand* ec = dynamic_cast<ExecCommand*>( pe->GetRequest() );
623 // The next line has appeared dangerous in case if
624 // Python command execution has produced very large output.
625 // A more clever approach is needed...
626 setText(ec->myOutput);
627 setText(ec->myError);
631 _currentPrompt = READY_PROMPT;
632 setText(_currentPrompt);
633 viewport()->unsetCursor();
636 case PyInterp_Event::INCOMPLETE:
639 _currentPrompt = DOTS_PROMPT;
640 setText(_currentPrompt);
641 viewport()->unsetCursor();
645 QTextEdit::customEvent( e );
648 setReadOnly( false );
649 _isInHistory = false;
653 Handles Python interpreter change
655 void PythonConsole_PyEditor::onPyInterpChanged( PyInterp_base* interp )
657 if ( myInterp != interp
658 // Force read-only state and wait cursor when myInterp is NULL
662 myBanner = myInterp->getbanner().c_str();
665 setReadOnly( false );
666 _isInHistory = false;
667 setText(_currentPrompt);
668 viewport()->unsetCursor();
673 viewport()->setCursor( waitCursor );
678 QPopupMenu* PythonConsole_PyEditor::createPopupMenu( const QPoint& pos )
680 QPopupMenu* popup = QTextEdit::createPopupMenu( pos );
682 for ( int i = 0; popup && i < popup->count(); i++ )
684 if ( !popup->isItemEnabled( popup->idAt( i ) ) )
685 popup->removeItemAt( i );
688 SUIT_Tools::simplifySeparators( popup );