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 : QAD_PyEditor.cxx
25 // Author : Nicolas REJNERI
29 #include "QAD_PyEditor.h" // this include must be first (see PyInterp_base.h)!
30 #include "QAD_Application.h"
31 #include "QAD_Desktop.h"
32 #include "QAD_Config.h"
33 #include "QAD_Tools.h"
34 #include "QAD_MessageBox.h"
36 #include <qapplication.h>
38 #include <qclipboard.h>
40 #include <qdragobject.h>
42 // NRI : Temporary added
44 #include <SALOMEconfig.h>
45 #include CORBA_SERVER_HEADER(SALOMEDS)
46 #include CORBA_SERVER_HEADER(SALOMEDS_Attributes)
49 #include "utilities.h"
54 static int MYDEBUG = 0;
56 static int MYDEBUG = 0;
60 enum { IdCopy, IdPaste, IdClear, IdSelectAll };
63 static QString READY_PROMPT = ">>> ";
64 static QString DOTS_PROMPT = "... ";
65 #define PROMPT_SIZE _currentPrompt.length()
67 class TInitEditorThread : public QThread
70 TInitEditorThread(QAD_PyInterp*& theInterp,
71 QMutex* theStudyMutex, QMutex* theMutex,
72 QAD_PyEditor* theListener):
75 myStudyMutex(theStudyMutex),
76 myListener(theListener)
78 // san - commented as inefficient: sometimes event is processed significant period of time after this moment
79 //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
82 virtual ~TInitEditorThread(){}
86 ThreadLock anEditorLock(myMutex,"TInitEditorThread::anEditorLock");
87 ThreadLock aStudyLock(myStudyMutex,"TInitEditorThread::aStudyLock");
88 ThreadLock aPyLock = GetPyThreadLock("TInitEditorThread::aPyLock");
89 if(MYDEBUG) MESSAGE("TInitEditorThread::run() - myInterp = "<<myInterp<<"; myMutex = "<<myMutex);
90 myListener->myBanner = myInterp->getbanner().c_str();
91 QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::INITIALIZE));
92 QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::PYTHON_OK));
93 QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::UNSET_CURSOR));
99 QAD_PyInterp*& myInterp;
100 QAD_PyEditor* myListener;
104 class TExecCommandThread : public QThread
107 TExecCommandThread(QAD_PyInterp*& theInterp,
108 QMutex* theStudyMutex, QMutex* theMutex,
109 QAD_PyEditor* theListener):
112 myStudyMutex(theStudyMutex),
113 myListener(theListener),
116 //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
119 virtual ~TExecCommandThread() {}
121 void exec(const char* theCommand){
122 myCommand = theCommand;
128 //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
129 int anId = QAD_PyEditor::PYTHON_OK;
131 ThreadLock anEditorLock(myMutex,"TExecCommandThread::anEditorLock");
132 //ThreadLock aStudyLock(myStudyMutex,"TExecCommandThread::aStudyLock");
133 ThreadLock aPyLock = GetPyThreadLock("TExecCommandThread::aPyLock");
134 int ret = myInterp->run( myCommand.latin1() );
135 if(MYDEBUG) MESSAGE("TExecCommand::run() - myInterp = "<<myInterp<<"; myCommand = '"<<myCommand.latin1()<<"' - "<<ret);
137 anId = QAD_PyEditor::PYTHON_ERROR;
139 anId = QAD_PyEditor::PYTHON_INCOMPLETE;
140 myListener->myError = myInterp->getverr().c_str();
141 myListener->myOutput = myInterp->getvout().c_str();
143 myListener->myError = "";
144 myListener->myOutput = "";
146 QThread::postEvent(myListener, new QCustomEvent(anId));
147 QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::UNSET_CURSOR));
152 QMutex* myStudyMutex;
153 QAD_PyInterp*& myInterp;
154 QAD_PyEditor* myListener;
162 QAD_PyEditor::QAD_PyEditor(QAD_PyInterp*& theInterp, QMutex* theMutex,
163 QWidget *theParent, const char* theName):
164 QTextEdit(theParent,theName),
165 myStudyMutex(theMutex),
166 myInitEditorMutex(new QMutex),
167 myExecCommandMutex(new QMutex),
169 myInitEditorThread(0),
170 myExecCommandThread(0)
172 QString fntSet = QAD_CONFIG->getSetting("Viewer:ConsoleFont");
173 QFont myFont = QAD_Tools::stringToFont( fntSet );
174 // QFont myFont("Courier",11);
176 setTextFormat(QTextEdit::PlainText);
178 // san - This is necessary for troubleless initialization
180 viewport()->setCursor( waitCursor );
182 myInitEditorThread = new TInitEditorThread(myInterp,myStudyMutex,myInitEditorMutex,this);
183 myExecCommandThread = new TExecCommandThread(myInterp,myStudyMutex,myExecCommandMutex,this);
185 _currentPrompt = READY_PROMPT;
186 setPalette( QAD_Application::getPalette(true) );
189 connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
193 void QAD_PyEditor::Init()
195 myInitEditorThread->start();
202 QAD_PyEditor::~QAD_PyEditor()
204 if(MYDEBUG) MESSAGE("QAD_PyEditor::~QAD_PyEditor()");
207 ThreadLock aLock(myInitEditorMutex,"myInitEditorMutex");
208 delete myInitEditorThread;
210 delete myInitEditorMutex;
214 ThreadLock aLock(myExecCommandMutex,"myExecCommandMutex");
215 delete myExecCommandThread;
217 delete myExecCommandMutex;
222 Called to insert a string s
224 void QAD_PyEditor::setText(QString s)
226 int para=paragraphs()-1;
227 int col=paragraphLength(para);
228 insertAt(s,para,col);
229 int n = paragraphs()-1;
230 setCursorPosition( n, paragraphLength(n));
234 Called when an handleReturn
236 void QAD_PyEditor::handleReturn()
238 int para=paragraphs()-2;
240 // NRI : Temporary added
241 SALOMEDS::Study_var aStudy = QAD_Application::getDesktop()->getActiveStudy()->getStudyDocument();
243 if ( aStudy->GetProperties()->IsLocked() ) {
244 QApplication::restoreOverrideCursor();
245 QAD_MessageBox::warn1 ( (QWidget*)QAD_Application::getDesktop(),
246 QObject::tr("WARNING"),
247 QObject::tr("WRN_STUDY_LOCKED"),
248 QObject::tr("BUT_OK") );
250 _currentPrompt = READY_PROMPT;
251 setText(_currentPrompt);
257 _buf.append(text(para).remove(0,PROMPT_SIZE));
258 _buf.truncate( _buf.length() - 1 );
260 viewport()->setCursor( waitCursor );
261 myExecCommandThread->exec(_buf.latin1());
265 Processes drop event: paste dragged text
267 void QAD_PyEditor::contentsDropEvent( QDropEvent* event )
269 event->acceptAction();
271 if ( QTextDrag::decode( event, text ) ) {
273 int endLine = paragraphs() -1;
274 col = charAt( event->pos(), &par );
276 if ( col >= 0 && par >= 0 ) {
277 if ( par != endLine || col < PROMPT_SIZE ) {
279 col = paragraphLength( endLine );
281 setCursorPosition( par, col );
282 insertAt( text, par, col );
289 Processes middle button release event - paste clipboard's contents
291 void QAD_PyEditor::contentsMouseReleaseEvent( QMouseEvent* event )
293 if ( event->button() == LeftButton ) {
294 QTextEdit::contentsMouseReleaseEvent(event);
297 if ( event->button() == MidButton ) {
298 if (QApplication::clipboard()->supportsSelection()) {
300 int endLine = paragraphs() -1;
301 col = charAt( event->pos(), &par );
302 if ( col >= 0 && par >= 0 ) {
303 if ( par != endLine || col < PROMPT_SIZE )
304 setCursorPosition( endLine, paragraphLength( endLine ) );
306 setCursorPosition( par, col );
307 QApplication::clipboard()->setSelectionMode(TRUE);
309 QApplication::clipboard()->setSelectionMode(FALSE);
314 QTextEdit::contentsMouseReleaseEvent(event);
319 Processes own popup menu
321 void QAD_PyEditor::mousePressEvent (QMouseEvent* event)
323 if ( event->button() == RightButton ) {
324 QPopupMenu *popup = new QPopupMenu( this );
325 QMap<int, int> idMap;
327 int para1, col1, para2, col2;
328 getSelection(¶1, &col1, ¶2, &col2);
329 bool allSelected = hasSelectedText() &&
330 para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
332 id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
333 idMap.insert(IdCopy, id);
334 id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
335 idMap.insert(IdPaste, id);
336 id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
337 idMap.insert(IdClear, id);
338 popup->insertSeparator();
339 id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
340 idMap.insert(IdSelectAll, id);
341 popup->setItemEnabled( idMap[ IdCopy ], hasSelectedText() );
342 popup->setItemEnabled( idMap[ IdPaste ],
343 !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
344 popup->setItemEnabled( idMap[ IdSelectAll ],
345 (bool)text().length() && !allSelected );
347 int r = popup->exec( event->globalPos() );
350 if ( r == idMap[ IdCopy ] ) {
353 else if ( r == idMap[ IdPaste ] ) {
356 else if ( r == idMap[ IdClear ] ) {
359 _currentPrompt = READY_PROMPT;
360 setText(_currentPrompt);
362 else if ( r == idMap[ IdSelectAll ] ) {
367 QTextEdit::mousePressEvent(event);
372 Checks, is the string a command line or not.
375 bool QAD_PyEditor::isCommand( const QString& str) const
377 // prompt may be '>>> ' or for '... '
378 return ( str.find( READY_PROMPT ) == 0 || str.find( DOTS_PROMPT ) == 0 );
383 Called when a keyPress event
385 void QAD_PyEditor::keyPressEvent( QKeyEvent* e )
387 // get cursor position
389 getCursorPosition(&curLine, &curCol);
391 // get last edited line
392 int endLine = paragraphs() -1;
394 // get pressed key code
397 // check if <Ctrl> is pressed
398 bool ctrlPressed = e->state() & ControlButton;
399 // check if <Shift> is pressed
400 bool shftPressed = e->state() & ShiftButton;
401 // check if <Alt> is pressed
402 bool altPressed = e->state() & AltButton;
404 // process <Ctrl>+<C> key-bindings
405 if ( aKey == Key_C && ctrlPressed ) {
408 _currentPrompt = READY_PROMPT;
409 setText(_currentPrompt);
413 // check for printed key
414 aKey = ( aKey < Key_Space || aKey > Key_ydiaeresis ) ? aKey : 0;
420 if ( curLine < endLine || curCol < PROMPT_SIZE )
421 moveCursor( QTextEdit::MoveEnd, false );
422 QTextEdit::keyPressEvent( e );
429 moveCursor( QTextEdit::MoveEnd, false );
430 QTextEdit::keyPressEvent( e );
434 // <Up> arrow key: process as follows:
435 // - without <Ctrl>, <Shift> modifiers: previous command in history
436 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
437 // - with <Shift> modifier key pressed: move cursor one row up with selection
438 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
440 if ( ctrlPressed && shftPressed ) {
441 scrollBy( 0, -QFontMetrics( font() ).lineSpacing() );
443 else if ( shftPressed ) {
445 moveCursor( QTextEdit::MoveUp, true );
447 else if ( ctrlPressed ) {
448 moveCursor( QTextEdit::MoveUp, false );
451 QString histLine = _currentPrompt;
452 if ( ! _isInHistory ) {
454 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
455 _currentCommand.truncate( _currentCommand.length() - 1 );
457 QString previousCommand = myInterp->getPrevious();
458 if ( previousCommand.compare( BEGIN_HISTORY_PY ) != 0 ) {
459 removeParagraph( endLine );
460 histLine.append( previousCommand );
461 insertParagraph( histLine, -1 );
463 moveCursor( QTextEdit::MoveEnd, false );
468 // <Down> arrow key: process as follows:
469 // - without <Ctrl>, <Shift> modifiers: next command in history
470 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
471 // - with <Shift> modifier key pressed: move cursor one row down with selection
472 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
474 if ( ctrlPressed && shftPressed ) {
475 scrollBy( 0, QFontMetrics( font() ).lineSpacing() );
477 else if ( shftPressed ) {
478 if ( curLine < endLine )
479 moveCursor( QTextEdit::MoveDown, true );
481 else if ( ctrlPressed ) {
482 moveCursor( QTextEdit::MoveDown, false );
485 QString histLine = _currentPrompt;
486 QString nextCommand = myInterp->getNext();
487 if ( nextCommand.compare( TOP_HISTORY_PY ) != 0 ) {
488 removeParagraph( endLine );
489 histLine.append( nextCommand );
490 insertParagraph( histLine, -1 );
494 _isInHistory = false;
495 removeParagraph( endLine );
496 histLine.append( _currentCommand );
497 insertParagraph( histLine, -1 );
500 moveCursor( QTextEdit::MoveEnd, false );
505 // <Left> arrow key: process as follows:
506 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
507 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
508 // - with <Shift> modifier key pressed: move one symbol left with selection
509 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
511 if ( !shftPressed && isCommand( text( curLine ) ) && curCol <= PROMPT_SIZE ) {
512 setCursorPosition( curLine-1, 0 );
513 moveCursor( QTextEdit::MoveLineEnd, false );
516 QTextEdit::keyPressEvent( e );
521 // <Right> arrow key: process as follows:
522 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
523 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
524 // - with <Shift> modifier key pressed: move one symbol right with selection
525 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
527 if ( !shftPressed ) {
528 if ( curCol < paragraphLength( curLine ) ) {
529 if ( isCommand( text( curLine ) ) && curCol < PROMPT_SIZE ) {
530 setCursorPosition( curLine, PROMPT_SIZE );
535 if ( curLine < endLine && isCommand( text( curLine+1 ) ) ) {
536 setCursorPosition( curLine+1, PROMPT_SIZE );
541 QTextEdit::keyPressEvent( e );
545 // <PageUp> key: process as follows:
546 // - without <Ctrl>, <Shift> modifiers: first command in history
547 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
548 // - with <Shift> modifier key pressed: move cursor one page up with selection
549 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
551 if ( ctrlPressed && shftPressed ) {
552 scrollBy( 0, -visibleHeight() );
554 else if ( shftPressed ) {
556 moveCursor( QTextEdit::MovePgUp, true );
558 else if ( ctrlPressed ) {
559 moveCursor( QTextEdit::MovePgUp, false );
562 QString histLine = _currentPrompt;
563 if ( ! _isInHistory ) {
565 _currentCommand = text( endLine ).remove( 0, PROMPT_SIZE );
566 _currentCommand.truncate( _currentCommand.length() - 1 );
568 QString firstCommand = myInterp->getPrevious();
570 while ( ( pcmd = QString( myInterp->getPrevious() ) ).compare( BEGIN_HISTORY_PY ) != 0 )
572 if ( firstCommand.compare( BEGIN_HISTORY_PY ) != 0 ) {
573 removeParagraph( endLine );
574 histLine.append( firstCommand );
575 insertParagraph( histLine, -1 );
577 moveCursor( QTextEdit::MoveEnd, false );
582 // <PageDown> key: process as follows:
583 // - without <Ctrl>, <Shift> modifiers: last command in history
584 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
585 // - with <Shift> modifier key pressed: move cursor one page down with selection
586 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
588 if ( ctrlPressed && shftPressed ) {
589 scrollBy( 0, visibleHeight() );
591 else if ( shftPressed ) {
592 if ( curLine < endLine )
593 moveCursor( QTextEdit::MovePgDown, true );
595 else if ( ctrlPressed ) {
596 moveCursor( QTextEdit::MovePgDown, false );
599 if ( _isInHistory ) {
600 QString histLine = _currentPrompt;
601 while ( QString( myInterp->getNext() ).compare( TOP_HISTORY_PY ) != 0 );
602 _isInHistory = false;
603 removeParagraph( endLine );
604 histLine.append( _currentCommand );
605 insertParagraph( histLine, -1 );
607 moveCursor( QTextEdit::MoveEnd, false );
612 // <Home> key: process as follows:
613 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
614 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
615 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
616 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
619 moveCursor( QTextEdit::MoveHome, shftPressed );
622 if ( isCommand( text( curLine ) ) ) {
623 int ps1, ps2, cs1, cs2;
624 bool hasSelection = hasSelectedText();
626 getSelection( &ps1, &cs1, &ps2, &cs2 );
628 horizontalScrollBar()->setValue( horizontalScrollBar()->minValue() );
629 if ( curCol > PROMPT_SIZE && shftPressed )
630 setSelection( curLine, PROMPT_SIZE, curLine, ( hasSelection && ps1 == ps2 && ps1 == curLine && cs2 > PROMPT_SIZE ) ? cs2 : curCol );
631 setCursorPosition( curLine, PROMPT_SIZE );
634 moveCursor( QTextEdit::MoveLineStart, shftPressed );
640 // <End> key: process as follows:
641 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
642 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
643 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
644 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
647 moveCursor( QTextEdit::MoveEnd, shftPressed );
650 moveCursor( QTextEdit::MoveLineEnd, shftPressed );
655 // <Backspace> key: process as follows
656 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
657 // - with <Ctrl> modifier key pressed: delete previous word
658 // works only for last (command) line
660 if ( curLine == endLine && ( curCol > PROMPT_SIZE || curCol >= PROMPT_SIZE && hasSelectedText() ) ) {
661 if ( ctrlPressed && !hasSelectedText() ) {
662 QString txt = text( curLine );
664 while ( ind > 0 && txt[ ind ] == ' ' ) ind--;
665 ind = txt.findRev( ' ', ind ) + 1;
666 if ( ind > PROMPT_SIZE-1 ) {
667 setSelection( curLine, ind, curLine, curCol );
668 removeSelectedText();
671 QTextEdit::keyPressEvent( e );
675 QTextEdit::keyPressEvent( e );
681 // <Delete> key: process as follows
682 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
683 // - with <Ctrl> modifier key pressed: delete next word
684 // works only for last (command) line
686 if ( curLine == endLine && curCol > PROMPT_SIZE-1 ) {
687 if ( ctrlPressed && !hasSelectedText() ) {
688 QString txt = text( curLine );
690 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
691 ind = txt.find( ' ', ind );
692 while ( ind < txt.length()-1 && txt[ ind ] == ' ' ) ind++;
693 if ( ind > PROMPT_SIZE-1 ) {
694 setSelection( curLine, curCol, curLine, ind );
695 removeSelectedText();
698 QTextEdit::keyPressEvent( e );
702 QTextEdit::keyPressEvent( e );
708 // <Insert> key: process as follows
709 // - with <Ctrl> modifier key pressed: copy()
710 // - with <Shift> modifier key pressed: paste() to the command line
715 else if ( shftPressed ) {
716 if ( curLine != endLine || curCol < PROMPT_SIZE )
717 moveCursor( QTextEdit::MoveEnd, false );
721 QTextEdit::keyPressEvent( e );
725 // NRI : DEBUG PAS TERRIBLE //
726 if (( e->key() == Key_F3) ||
727 ( e->key() == Key_F4) ||
728 ( e->key() == Key_Return) ||
729 ( e->key() == Key_Escape))
730 QAD_Application::getDesktop()->onKeyPress( e );
734 void QAD_PyEditor::customEvent(QCustomEvent* e)
736 switch( e->type() ) {
743 _currentPrompt = READY_PROMPT;
744 setText(_currentPrompt);
747 case PYTHON_INCOMPLETE:
750 _currentPrompt = DOTS_PROMPT;
751 setText(_currentPrompt);
756 setText(myInterp->getbanner().c_str());
758 QApplication::restoreOverrideCursor();
761 case SET_WAIT_CURSOR:
763 viewport()->setCursor( waitCursor );
768 viewport()->unsetCursor();
772 QTextEdit::customEvent( e );
775 setReadOnly( false );
776 _isInHistory = false;