Salome HOME
72e22033ea4058816a10fa161458b08d7e9933a5
[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 using namespace std;
50
51
52 #ifdef _DEBUG_
53 static int MYDEBUG = 0;
54 #else
55 static int MYDEBUG = 0;
56 #endif
57
58
59 #define SIZEPR 4
60 enum { IdCopy, IdPaste, IdClear, IdSelectAll };
61
62
63 static QString PROMPT = ">>> ";
64
65
66 class TInitEditorThread : public QThread
67 {
68 public:
69   TInitEditorThread(QAD_PyInterp*& theInterp, 
70                     QMutex* theStudyMutex, QMutex* theMutex,
71                     QAD_PyEditor* theListener):
72     myInterp(theInterp), 
73     myMutex(theMutex),
74     myStudyMutex(theStudyMutex),
75     myListener(theListener)
76   {
77     // san - commented as inefficient: sometimes event is processed significant period of time after this moment
78     //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
79   }
80
81   virtual ~TInitEditorThread(){}
82
83 protected:
84   virtual void run(){
85     ThreadLock anEditorLock(myMutex,"TInitEditorThread::anEditorLock");
86     ThreadLock aStudyLock(myStudyMutex,"TInitEditorThread::aStudyLock");
87     ThreadLock aPyLock = GetPyThreadLock("TInitEditorThread::aPyLock");
88     if(MYDEBUG) MESSAGE("TInitEditorThread::run() - myInterp = "<<myInterp<<"; myMutex = "<<myMutex);
89     myListener->myBanner = myInterp->getbanner().c_str();
90     QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::INITIALIZE));
91     QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::PYTHON_OK));
92     QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::UNSET_CURSOR));
93   }
94
95 private:
96   QMutex* myMutex;
97   QMutex* myStudyMutex;
98   QAD_PyInterp*& myInterp;
99   QAD_PyEditor* myListener;
100 };
101
102
103 class TExecCommandThread : public QThread
104 {
105 public:
106   TExecCommandThread(QAD_PyInterp*& theInterp, 
107                      QMutex* theStudyMutex, QMutex* theMutex,
108                      QAD_PyEditor* theListener): 
109     myInterp(theInterp), 
110     myMutex(theMutex),
111     myStudyMutex(theStudyMutex),
112     myListener(theListener), 
113     myCommand("")
114   {
115     //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
116   }
117
118   virtual ~TExecCommandThread() {}
119
120   void exec(const char* theCommand){
121     myCommand = theCommand;
122     start();
123   }
124
125 protected:
126   virtual void run(){
127     //QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::SET_WAIT_CURSOR));
128     int anId = QAD_PyEditor::PYTHON_OK;
129     if(myCommand != ""){
130       ThreadLock anEditorLock(myMutex,"TExecCommandThread::anEditorLock");
131       //ThreadLock aStudyLock(myStudyMutex,"TExecCommandThread::aStudyLock");
132       ThreadLock aPyLock = GetPyThreadLock("TExecCommandThread::aPyLock");
133       int ret = myInterp->run( myCommand.latin1() );
134       if(MYDEBUG) MESSAGE("TExecCommand::run() - myInterp = "<<myInterp<<"; myCommand = '"<<myCommand.latin1()<<"' - "<<ret);
135       if(ret < 0)
136         anId = QAD_PyEditor::PYTHON_ERROR;
137       if(ret > 0)
138         anId = QAD_PyEditor::PYTHON_INCOMPLETE;
139       myListener->myError = myInterp->getverr().c_str();
140       myListener->myOutput = myInterp->getvout().c_str();
141     }else{
142       myListener->myError = "";
143       myListener->myOutput = "";
144     }
145     QThread::postEvent(myListener, new QCustomEvent(anId));
146     QThread::postEvent(myListener, new QCustomEvent(QAD_PyEditor::UNSET_CURSOR));
147   }
148
149 private:
150   QMutex* myMutex;
151   QMutex* myStudyMutex;
152   QAD_PyInterp*& myInterp;
153   QAD_PyEditor* myListener;
154   QString myCommand;
155 };
156
157
158 /*!
159     Constructor
160 */
161 QAD_PyEditor::QAD_PyEditor(QAD_PyInterp*& theInterp, QMutex* theMutex,
162                            QWidget *theParent, const char* theName): 
163   QTextEdit(theParent,theName),
164   myStudyMutex(theMutex),
165   myInitEditorMutex(new QMutex),
166   myExecCommandMutex(new QMutex),
167   myInterp(theInterp),
168   myInitEditorThread(0),
169   myExecCommandThread(0)
170 {
171   QString fntSet = QAD_CONFIG->getSetting("Viewer:ConsoleFont");
172   QFont myFont = QAD_Tools::stringToFont( fntSet );
173 //  QFont myFont("Courier",11);
174   setFont(myFont);
175   setTextFormat(QTextEdit::PlainText);
176
177   // san - This is necessary for troubleless initialization
178   setReadOnly( true );
179   viewport()->setCursor( waitCursor );
180
181   myInitEditorThread = new TInitEditorThread(myInterp,myStudyMutex,myInitEditorMutex,this);
182   myExecCommandThread = new TExecCommandThread(myInterp,myStudyMutex,myExecCommandMutex,this);
183
184   _currentPrompt = PROMPT;
185   setPalette( QAD_Application::getPalette(true) );
186   setWordWrap(NoWrap);
187
188   connect(this,SIGNAL(returnPressed()),this,SLOT(handleReturn()) );
189 }
190
191
192 void QAD_PyEditor::Init()
193 {
194   myInitEditorThread->start();
195 }
196
197
198 /*!
199     Destructor
200 */
201 QAD_PyEditor::~QAD_PyEditor()
202 {
203   if(MYDEBUG) MESSAGE("QAD_PyEditor::~QAD_PyEditor()");
204   {
205     {
206       ThreadLock aLock(myInitEditorMutex,"myInitEditorMutex");
207       delete myInitEditorThread;
208     }
209     delete myInitEditorMutex;
210   }
211   {
212     {
213       ThreadLock aLock(myExecCommandMutex,"myExecCommandMutex");
214       delete myExecCommandThread;
215     }
216     delete myExecCommandMutex;
217   }
218 }
219
220 /*!
221     Called to insert a string s 
222 */
223 void QAD_PyEditor::setText(QString s)
224 {
225   int para=paragraphs()-1;
226   int col=paragraphLength(para);
227   insertAt(s,para,col);
228   int n = paragraphs()-1;  
229   setCursorPosition( n, paragraphLength(n)); 
230 }
231
232 /*!
233     Called when an handleReturn
234 */
235 void QAD_PyEditor::handleReturn()
236 {
237   int ret;
238   int para=paragraphs()-2;
239
240   // NRI : Temporary added
241   SALOMEDS::Study_var aStudy = QAD_Application::getDesktop()->getActiveStudy()->getStudyDocument();
242   
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") );
249
250     _currentPrompt = ">>> ";
251     setText(_currentPrompt);
252     
253     return;
254   }  
255   // NRI
256
257   _buf.append(text(para).remove(0,SIZEPR));
258   _buf.truncate( _buf.length() - 1 );
259   setReadOnly( true );
260   viewport()->setCursor( waitCursor );
261   myExecCommandThread->exec(_buf.latin1());
262 }
263
264 /*
265    Processes own popup menu
266 */
267 void QAD_PyEditor::mousePressEvent (QMouseEvent * event)
268 {
269   if ( event->button() == RightButton ) {
270     QPopupMenu *popup = new QPopupMenu( this );
271     QMap<int, int> idMap;
272
273     int para1, col1, para2, col2;
274     getSelection(&para1, &col1, &para2, &col2);
275     bool allSelected = hasSelectedText() &&
276       para1 == 0 && para2 == paragraphs()-1 && col1 == 0 && para2 == paragraphLength(para2);
277     int id;
278     id = popup->insertItem( tr( "EDIT_COPY_CMD" ) );
279     idMap.insert(IdCopy, id);
280     id = popup->insertItem( tr( "EDIT_PASTE_CMD" ) );
281     idMap.insert(IdPaste, id);
282     id = popup->insertItem( tr( "EDIT_CLEAR_CMD" ) );
283     idMap.insert(IdClear, id);
284     popup->insertSeparator();
285     id = popup->insertItem( tr( "EDIT_SELECTALL_CMD" ) );
286     idMap.insert(IdSelectAll, id);
287     popup->setItemEnabled( idMap[ IdCopy ],  hasSelectedText() );
288     popup->setItemEnabled( idMap[ IdPaste ],
289                           !isReadOnly() && (bool)QApplication::clipboard()->text().length() );
290     popup->setItemEnabled( idMap[ IdSelectAll ],
291                           (bool)text().length() && !allSelected );
292     
293     int r = popup->exec( event->globalPos() );
294     delete popup;
295     
296     if ( r == idMap[ IdCopy ] ) {
297       copy();
298     }
299     else if ( r == idMap[ IdPaste ] ) {
300       paste();
301     }
302     else if ( r == idMap[ IdClear ] ) {
303       clear();
304       setText(myBanner);
305       setText(_currentPrompt);
306     }
307     else if ( r == idMap[ IdSelectAll ] ) {
308       selectAll();
309     }
310     return;
311   }
312   else {
313     QTextEdit::mousePressEvent(event);
314   }
315 }
316
317 /*!
318     Called when a Mouse release event
319 */
320 void QAD_PyEditor::mouseReleaseEvent ( QMouseEvent * e )
321 {
322   //  MESSAGE("mouseReleaseEvent");
323   int curPara, curCol; // for cursor position
324   int endPara, endCol; // for last edited line
325   getCursorPosition(&curPara, &curCol);
326   endPara = paragraphs() -1;
327   if (e->button() != MidButton)
328     QTextEdit::mouseReleaseEvent(e);
329   else if ((curPara == endPara) && (curCol >= SIZEPR))
330     QTextEdit::mouseReleaseEvent(e);
331 }
332
333 /*!
334     Called when a drop event (Drag & Drop)
335 */
336   void QAD_PyEditor::dropEvent (QDropEvent *e)
337 {
338   MESSAGE("dropEvent : not handled");
339 }
340
341 /*!
342    Checks, is the string a command line or not.
343 */
344
345 bool QAD_PyEditor::isCommand( const QString& str) const
346 {
347   if (str.find(_currentPrompt)==0)
348     return true;
349   return false;
350 }
351
352
353 /*!
354     Called when a keyPress event
355 */
356 void QAD_PyEditor::keyPressEvent( QKeyEvent *e )
357 {
358   int curLine, curCol; // for cursor position
359   int endLine, endCol; // for last edited line
360   getCursorPosition(&curLine, &curCol);
361   endLine = paragraphs() -1;
362   //MESSAGE("current position " << curLine << ", " << curCol);
363   //MESSAGE("last line " << endLine);
364   //MESSAGE(e->key());
365   int aKey=e->key();
366   int keyRange=0;
367   if ((aKey >= Key_Space) && (aKey <= Key_ydiaeresis))
368     keyRange = 0;
369   else
370     keyRange = aKey;
371
372   bool ctrlPressed = ( (e->state() & ControlButton) == ControlButton );
373   bool shftPressed = ( (e->state() & ShiftButton) ==  ShiftButton );
374
375   switch (keyRange)
376     {
377     case 0 :
378       {
379         if (curLine <endLine || curCol < SIZEPR )
380           moveCursor(QTextEdit::MoveEnd, false);
381         QTextEdit::keyPressEvent( e );
382         break;
383       }
384     case Key_Return:
385     case Key_Enter:
386       {
387         if (curLine <endLine)
388           moveCursor(QTextEdit::MoveEnd, false);
389         else
390           moveCursor(QTextEdit::MoveLineEnd, false);
391         QTextEdit::keyPressEvent( e );
392         break;
393       }
394     case Key_Up:
395       {
396         // if Cntr+Key_Up event then move cursor up
397         if (ctrlPressed) {
398             moveCursor(QTextEdit::MoveUp, false);
399         }
400         // if Shift+Key_Up event then move cursor up and select the text
401         else if ( shftPressed && curLine > 0 ){
402             moveCursor(QTextEdit::MoveUp, true);
403         }
404         // scroll the commands stack up
405         else { 
406            QString histLine = _currentPrompt;
407           if (! _isInHistory)
408             {
409               _isInHistory = true;
410               _currentCommand = text(endLine).remove(0,SIZEPR);
411               _currentCommand.truncate( _currentCommand.length() - 1 );
412               SCRUTE(_currentCommand);
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 }