Salome HOME
f5258e1a05a9b6bf35d6b397129e6f8aaf0fd31b
[modules/kernel.git] / src / SALOMEGUI / QAD_PyEditor.cxx
1 //  SALOME SALOMEGUI : implementation of desktop and GUI kernel
2 //
3 //  Copyright (C) 2003  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 //  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS 
5 // 
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. 
10 // 
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. 
15 // 
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 
19 // 
20 //  See http://www.opencascade.org/SALOME/ or email : webmaster.salome@opencascade.org 
21 //
22 //
23 //
24 //  File   : QAD_PyEditor.cxx
25 //  Author : Nicolas REJNERI
26 //  Module : SALOME
27 //  $Header$
28
29 #include "QAD_PyEditor.h"
30 #include "QAD_PyInterp.h"
31 #include "QAD_Application.h"
32 #include "QAD_Desktop.h"
33 #include "QAD_Config.h"
34 #include "QAD_Tools.h"
35 #include "QAD_MessageBox.h"
36
37 #include <qapplication.h>
38 #include <qmap.h>
39 #include <qclipboard.h>
40 #include <qthread.h>
41
42 // NRI : Temporary added
43 // IDL Headers
44 #include <SALOMEconfig.h>
45 #include CORBA_SERVER_HEADER(SALOMEDS)
46 #include CORBA_SERVER_HEADER(SALOMEDS_Attributes)
47 //NRI
48
49 #include "utilities.h"
50 using namespace std;
51
52
53 #ifdef _DEBUG_
54 static int MYDEBUG = 0;
55 #else
56 static int MYDEBUG = 0;
57 #endif
58
59
60 #define SIZEPR 4
61 enum { IdCopy, IdPaste, IdClear, IdSelectAll };
62
63
64 static QString PROMPT = ">>> ";
65
66
67 class TInitEditorThread : public QThread
68 {
69 public:
70   TInitEditorThread(QAD_PyInterp*& theInterp, 
71                     QMutex* theStudyMutex, QMutex* theMutex,
72                     QAD_PyEditor* theListener):
73     myInterp(theInterp), 
74     myMutex(theMutex),
75     myStudyMutex(theStudyMutex),
76     myListener(theListener)
77   {
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));
80   }
81
82   virtual ~TInitEditorThread(){}
83
84 protected:
85   virtual void run(){
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));
94   }
95
96 private:
97   QMutex* myMutex;
98   QMutex* myStudyMutex;
99   QAD_PyInterp*& myInterp;
100   QAD_PyEditor* myListener;
101 };
102
103
104 class TExecCommandThread : public QThread
105 {
106 public:
107   TExecCommandThread(QAD_PyInterp*& theInterp, 
108                      QMutex* theStudyMutex, QMutex* theMutex,
109                      QAD_PyEditor* theListener): 
110     myInterp(theInterp), 
111     myMutex(theMutex),
112     myStudyMutex(theStudyMutex),
113     myListener(theListener), 
114     myCommand("")
115   {
116     //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
117   }
118
119   virtual ~TExecCommandThread() {}
120
121   void exec(const char* theCommand){
122     myCommand = theCommand;
123     start();
124   }
125
126 protected:
127   virtual void run(){
128     //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
129     int anId = QAD_PyEditor::PYTHON_OK;
130     if(myCommand != ""){
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);
136       if(ret < 0)
137         anId = QAD_PyEditor::PYTHON_ERROR;
138       if(ret > 0)
139         anId = QAD_PyEditor::PYTHON_INCOMPLETE;
140       myListener->myError = myInterp->getverr().c_str();
141       myListener->myOutput = myInterp->getvout().c_str();
142     }else{
143       myListener->myError = "";
144       myListener->myOutput = "";
145     }
146     QThread::postEvent(myListener, new QCustomEvent(anId));
147     QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::UNSET_CURSOR));
148   }
149
150 private:
151   QMutex* myMutex;
152   QMutex* myStudyMutex;
153   QAD_PyInterp*& myInterp;
154   QAD_PyEditor* myListener;
155   QString myCommand;
156 };
157
158
159 /*!
160     Constructor
161 */
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),
168   myInterp(theInterp),
169   myInitEditorThread(0),
170   myExecCommandThread(0)
171 {
172   QString fntSet = QAD_CONFIG->getSetting("Viewer:ConsoleFont");
173   QFont myFont = QAD_Tools::stringToFont( fntSet );
174 //  QFont myFont("Courier",11);
175   setFont(myFont);
176   setTextFormat(QTextEdit::PlainText);
177
178   // san - This is necessary for troubleless initialization
179   setReadOnly( true );
180   viewport()->setCursor( waitCursor );
181
182   myInitEditorThread = new TInitEditorThread(myInterp,myStudyMutex,myInitEditorMutex,this);
183   myExecCommandThread = new TExecCommandThread(myInterp,myStudyMutex,myExecCommandMutex,this);
184
185   _currentPrompt = PROMPT;
186   setPalette( QAD_Application::getPalette(true) );
187   setWordWrap(NoWrap);
188
189   connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
190 }
191
192
193 void QAD_PyEditor::Init()
194 {
195   myInitEditorThread->start();
196 }
197
198
199 /*!
200     Destructor
201 */
202 QAD_PyEditor::~QAD_PyEditor()
203 {
204   if(MYDEBUG) MESSAGE("QAD_PyEditor::~QAD_PyEditor()");
205   {
206     {
207       ThreadLock aLock(myInitEditorMutex,"myInitEditorMutex");
208       delete myInitEditorThread;
209     }
210     delete myInitEditorMutex;
211   }
212   {
213     {
214       ThreadLock aLock(myExecCommandMutex,"myExecCommandMutex");
215       delete myExecCommandThread;
216     }
217     delete myExecCommandMutex;
218   }
219 }
220
221 /*!
222     Called to insert a string s 
223 */
224 void QAD_PyEditor::setText(QString s)
225 {
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)); 
231 }
232
233 /*!
234     Called when an handleReturn
235 */
236 void QAD_PyEditor::handleReturn()
237 {
238   int ret;
239   int para=paragraphs()-2;
240
241   // NRI : Temporary added
242   SALOMEDS::Study_var aStudy = QAD_Application::getDesktop()->getActiveStudy()->getStudyDocument();
243   
244   if ( aStudy->GetProperties()->IsLocked() ) {
245     QApplication::restoreOverrideCursor();
246     QAD_MessageBox::warn1 ( (QWidget*)QAD_Application::getDesktop(),
247                             QObject::tr("WARNING"), 
248                             QObject::tr("WRN_STUDY_LOCKED"),
249                             QObject::tr("BUT_OK") );
250
251     _currentPrompt = ">>> ";
252     setText(_currentPrompt);
253     
254     return;
255   }  
256   // NRI
257
258   _buf.append(text(para).remove(0,SIZEPR));
259   _buf.truncate( _buf.length() - 1 );
260   setReadOnly( true );
261   viewport()->setCursor( waitCursor );
262   myExecCommandThread->exec(_buf.latin1());
263 }
264
265 /*
266    Processes own popup menu
267 */
268 void QAD_PyEditor::mousePressEvent (QMouseEvent * event)
269 {
270   if ( event->button() == RightButton ) {
271     QPopupMenu *popup = new QPopupMenu( this );
272     QMap<int, int> idMap;
273
274     int para1, col1, para2, col2;
275     getSelection(&para1, &col1, &para2, &col2);
276     bool allSelected = hasSelectedText() &&
277       para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
278     int id;
279     id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
280     idMap.insert(IdCopy, id);
281     id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
282     idMap.insert(IdPaste, id);
283     id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
284     idMap.insert(IdClear, id);
285     popup->insertSeparator();
286     id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
287     idMap.insert(IdSelectAll, id);
288     popup->setItemEnabled( idMap[ IdCopy ],  hasSelectedText() );
289     popup->setItemEnabled( idMap[ IdPaste ],
290                           !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
291     popup->setItemEnabled( idMap[ IdSelectAll ],
292                           (bool)text().length() && !allSelected );
293     
294     int r = popup->exec( event->globalPos() );
295     delete popup;
296     
297     if ( r == idMap[ IdCopy ] ) {
298       copy();
299     }
300     else if ( r == idMap[ IdPaste ] ) {
301       paste();
302     }
303     else if ( r == idMap[ IdClear ] ) {
304       clear();
305       setText(myBanner);
306       setText(_currentPrompt);
307     }
308     else if ( r == idMap[ IdSelectAll ] ) {
309       selectAll();
310     }
311     return;
312   }
313   else {
314     QTextEdit::mousePressEvent(event);
315   }
316 }
317
318 /*!
319     Called when a Mouse release event
320 */
321 void QAD_PyEditor::mouseReleaseEvent ( QMouseEvent * e )
322 {
323   //  MESSAGE("mouseReleaseEvent");
324   int curPara, curCol; // for cursor position
325   int endPara, endCol; // for last edited line
326   getCursorPosition(&curPara, &curCol);
327   endPara = paragraphs() -1;
328   if (e->button() != MidButton)
329     QTextEdit::mouseReleaseEvent(e);
330   else if ((curPara == endPara) && (curCol >= SIZEPR))
331     QTextEdit::mouseReleaseEvent(e);
332 }
333
334 /*!
335     Called when a drop event (Drag & Drop)
336 */
337   void QAD_PyEditor::dropEvent (QDropEvent *e)
338 {
339   MESSAGE("dropEvent : not handled");
340 }
341
342 /*!
343    Checks, is the string a command line or not.
344 */
345
346 bool QAD_PyEditor::isCommand( const QString& str) const
347 {
348   if (str.find(_currentPrompt)==0)
349     return true;
350   return false;
351 }
352
353
354 /*!
355     Called when a keyPress event
356 */
357 void QAD_PyEditor::keyPressEvent( QKeyEvent *e )
358 {
359   int curLine, curCol; // for cursor position
360   int endLine, endCol; // for last edited line
361   getCursorPosition(&curLine, &curCol);
362   endLine = paragraphs() -1;
363   //MESSAGE("current position " << curLine << ", " << curCol);
364   //MESSAGE("last line " << endLine);
365   //MESSAGE(e->key());
366   int aKey=e->key();
367   int keyRange=0;
368   if ((aKey >= Key_Space) && (aKey <= Key_ydiaeresis))
369     keyRange = 0;
370   else
371     keyRange = aKey;
372
373   bool ctrlPressed = ( (e->state() & ControlButton) == ControlButton );
374   bool shftPressed = ( (e->state() & ShiftButton) ==  ShiftButton );
375
376   switch (keyRange)
377     {
378     case 0 :
379       {
380         if (curLine <endLine || curCol < SIZEPR )
381           moveCursor(QTextEdit::MoveEnd, false);
382         QTextEdit::keyPressEvent( e );
383         break;
384       }
385     case Key_Return:
386     case Key_Enter:
387       {
388         if (curLine <endLine)
389           moveCursor(QTextEdit::MoveEnd, false);
390         else
391           moveCursor(QTextEdit::MoveLineEnd, false);
392         QTextEdit::keyPressEvent( e );
393         break;
394       }
395     case Key_Up:
396       {
397         // if Cntr+Key_Up event then move cursor up
398         if (ctrlPressed) {
399             moveCursor(QTextEdit::MoveUp, false);
400         }
401         // if Shift+Key_Up event then move cursor up and select the text
402         else if ( shftPressed && curLine > 0 ){
403             moveCursor(QTextEdit::MoveUp, true);
404         }
405         // scroll the commands stack up
406         else { 
407            QString histLine = _currentPrompt;
408           if (! _isInHistory)
409             {
410               _isInHistory = true;
411               _currentCommand = text(endLine).remove(0,SIZEPR);
412               _currentCommand.truncate( _currentCommand.length() - 1 );
413             }
414           QString previousCommand = myInterp->getPrevious();
415           if (previousCommand.compare(BEGIN_HISTORY_PY) != 0)
416             {
417               removeParagraph(endLine);
418               histLine.append(previousCommand);
419               insertParagraph(histLine, -1);
420             }
421           moveCursor(QTextEdit::MoveEnd, false);
422         }
423         break;
424       }
425     case Key_Down:
426       {
427         // if Cntr+Key_Down event then move cursor down
428         if (ctrlPressed) {
429           moveCursor(QTextEdit::MoveDown, false);
430         }
431         // if Shift+Key_Down event then move cursor down and select the text
432         else if ( shftPressed && curLine < endLine ) {
433           moveCursor(QTextEdit::MoveDown, true);
434         }
435         // scroll the commands stack down
436         else {
437         QString histLine = _currentPrompt;
438           QString nextCommand = myInterp->getNext();
439           if (nextCommand.compare(TOP_HISTORY_PY) != 0)
440             {
441               removeParagraph(endLine);
442               histLine.append(nextCommand);
443               insertParagraph(histLine, -1);
444             }
445           else
446             if (_isInHistory)
447               {
448                 _isInHistory = false;
449                 removeParagraph(endLine);
450                 histLine.append(_currentCommand);
451                 insertParagraph(histLine, -1);
452               }
453           moveCursor(QTextEdit::MoveEnd, false);
454         }
455         break;
456       }
457     case Key_Left:
458       {
459         if (!shftPressed && isCommand(text(curLine)) && curCol <= SIZEPR )
460           {
461             setCursorPosition((curLine -1), SIZEPR);
462             moveCursor(QTextEdit::MoveLineEnd, false);
463           }
464         else QTextEdit::keyPressEvent( e );
465         break;
466       }
467     case Key_Right:
468       {
469         if (!shftPressed && isCommand(text(curLine)) 
470             && curCol < SIZEPR) setCursorPosition(curLine, SIZEPR);
471         QTextEdit::keyPressEvent( e );
472         break;
473       }
474     case Key_Home: 
475       {
476         horizontalScrollBar()->setValue( horizontalScrollBar()->minValue() );
477         if (isCommand(text(curLine))) {
478           setCursorPosition(curLine, SIZEPR);
479           if ( curCol > SIZEPR && shftPressed )
480             setSelection( curLine, SIZEPR, curLine, curCol );
481           else
482             selectAll( false );
483         }
484         else moveCursor(QTextEdit::MoveLineStart, shftPressed);
485         break;
486       }
487     case Key_End:
488       {
489         moveCursor(QTextEdit::MoveLineEnd, shftPressed);
490         break;
491       }  
492     case Key_Backspace :
493       {
494         if ((curLine == endLine) && (curCol > SIZEPR))
495           QTextEdit::keyPressEvent( e );
496         break;
497       }
498     case Key_Delete :
499       {
500         if ((curLine == endLine) && (curCol > SIZEPR-1))
501           QTextEdit::keyPressEvent( e );
502         break;
503       }
504     case Key_Insert :
505       {
506         if ( ctrlPressed )
507           copy();
508         else if ( shftPressed ) {
509           moveCursor(QTextEdit::MoveEnd, false);
510           paste();
511         }
512         else
513           QTextEdit::keyPressEvent( e );
514         break;
515       }
516     }
517   if ( e->key() == Key_C && ( e->state() & ControlButton ) )
518     {
519       _buf.truncate(0);
520       setText("\n");
521       _currentPrompt = ">>> ";
522       setText(_currentPrompt);
523     }
524
525   // NRI : DEBUG PAS TERRIBLE //
526   if (( e->key() == Key_F3) || 
527       ( e->key() == Key_F4) ||
528       ( e->key() == Key_Return) ||
529       ( e->key() == Key_Escape))
530     QAD_Application::getDesktop()->onKeyPress( e );
531   // NRI //
532 }
533
534 void QAD_PyEditor::customEvent(QCustomEvent *e)
535 {
536   switch( e->type() ) {
537   case PYTHON_OK:
538   case PYTHON_ERROR:
539     {
540       _buf.truncate(0);
541       setText(myOutput);
542       setText(myError);
543       _currentPrompt = ">>> ";
544       setText(_currentPrompt);
545       break;
546     }
547   case PYTHON_INCOMPLETE:
548     {
549       _buf.append("\n");
550       _currentPrompt = "... ";
551       setText(_currentPrompt);
552       break;
553     }
554   case INITIALIZE:
555     {
556       setText(myInterp->getbanner().c_str());
557       _buf.truncate(0);
558       QApplication::restoreOverrideCursor();
559       break;
560     }  
561   case SET_WAIT_CURSOR:
562     {
563       viewport()->setCursor( waitCursor );
564       break;
565     }  
566   case UNSET_CURSOR:
567     {
568       viewport()->unsetCursor();
569       break;
570     }  
571   default:
572     QTextEdit::customEvent( e );
573   }
574
575   setReadOnly( false );
576   _isInHistory = false;
577 }