PythonConsole.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
00003  *                                                                         *
00004  *   This file is part of the FreeCAD CAx development system.              *
00005  *                                                                         *
00006  *   This library is free software; you can redistribute it and/or         *
00007  *   modify it under the terms of the GNU Library General Public           *
00008  *   License as published by the Free Software Foundation; either          *
00009  *   version 2 of the License, or (at your option) any later version.      *
00010  *                                                                         *
00011  *   This library  is distributed in the hope that it will be useful,      *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00014  *   GNU Library General Public License for more details.                  *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU Library General Public     *
00017  *   License along with this library; see the file COPYING.LIB. If not,    *
00018  *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
00019  *   Suite 330, Boston, MA  02111-1307, USA                                *
00020  *                                                                         *
00021  ***************************************************************************/
00022 
00023 
00024 #include "PreCompiled.h"
00025 #ifndef _PreComp_
00026 # include <QClipboard>
00027 # include <QDockWidget>
00028 # include <QGridLayout>
00029 # include <QHBoxLayout>
00030 # include <QKeyEvent>
00031 # include <QMenu>
00032 # include <QMessageBox>
00033 # include <QPushButton>
00034 # include <QSpacerItem>
00035 # include <QTextCursor>
00036 # include <QTextDocumentFragment>
00037 # include <QTextStream>
00038 # include <QUrl>
00039 #endif
00040 
00041 #include "PythonConsole.h"
00042 #include "PythonConsolePy.h"
00043 #include "CallTips.h"
00044 #include "Application.h"
00045 #include "Action.h"
00046 #include "Command.h"
00047 #include "DlgEditorImp.h"
00048 #include "FileDialog.h"
00049 #include "MainWindow.h"
00050 
00051 
00052 #include <Base/Interpreter.h>
00053 #include <Base/Exception.h>
00054 #include <CXX/Exception.hxx>
00055 
00056 using namespace Gui;
00057 
00058 namespace Gui {
00059 struct PythonConsoleP
00060 {
00061     enum Output {Error = 20, Message = 21};
00062     enum CopyType {Normal, History, Command};
00063     CopyType type;
00064     PyObject *_stdoutPy, *_stderrPy, *_stdinPy, *_stdin;
00065     InteractiveInterpreter* interpreter;
00066     CallTipsList* callTipsList;
00067     ConsoleHistory history;
00068     QString output, error;
00069     QStringList statements;
00070     bool interactive;
00071     QMap<QString, QColor> colormap; // Color map
00072     PythonConsoleP()
00073     {
00074         type = Normal;
00075         interpreter = 0;
00076         colormap[QLatin1String("Text")] = Qt::black;
00077         colormap[QLatin1String("Bookmark")] = Qt::cyan;
00078         colormap[QLatin1String("Breakpoint")] = Qt::red;
00079         colormap[QLatin1String("Keyword")] = Qt::blue;
00080         colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
00081         colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
00082         colormap[QLatin1String("Number")] = Qt::blue;
00083         colormap[QLatin1String("String")] = Qt::red;
00084         colormap[QLatin1String("Character")] = Qt::red;
00085         colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
00086         colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
00087         colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
00088         colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
00089         colormap[QLatin1String("Python error")] = Qt::red;
00090     }
00091 };
00092 struct InteractiveInterpreterP
00093 {
00094     PyObject* interpreter;
00095     PyObject* sysmodule;
00096     QStringList buffer;
00097 };
00098 } // namespace Gui
00099 
00100 InteractiveInterpreter::InteractiveInterpreter()
00101 {
00102     // import code.py and create an instance of InteractiveInterpreter
00103     Base::PyGILStateLocker lock;
00104     PyObject* module = PyImport_ImportModule("code");
00105     if (!module)
00106     throw Base::PyException();
00107     PyObject* func = PyObject_GetAttrString(module, "InteractiveInterpreter");
00108     PyObject* args = Py_BuildValue("()");
00109     d = new InteractiveInterpreterP;
00110     d->interpreter = PyEval_CallObject(func,args);
00111     Py_DECREF(args);
00112     Py_DECREF(func);
00113     Py_DECREF(module);
00114 
00115     setPrompt();
00116 }
00117 
00118 InteractiveInterpreter::~InteractiveInterpreter()
00119 {
00120     Base::PyGILStateLocker lock;
00121     Py_XDECREF(d->interpreter);
00122     Py_XDECREF(d->sysmodule);
00123     delete d;
00124 }
00125 
00129 void InteractiveInterpreter::setPrompt()
00130 {
00131     // import code.py and create an instance of InteractiveInterpreter
00132     Base::PyGILStateLocker lock;
00133     d->sysmodule = PyImport_ImportModule("sys");
00134     if (!PyObject_HasAttrString(d->sysmodule, "ps1"))
00135         PyObject_SetAttrString(d->sysmodule, "ps1", PyString_FromString(">>> "));
00136     if (!PyObject_HasAttrString(d->sysmodule, "ps2"))
00137         PyObject_SetAttrString(d->sysmodule, "ps2", PyString_FromString("... "));
00138 }
00139 
00151 PyObject* InteractiveInterpreter::compile(const char* source) const
00152 {
00153     Base::PyGILStateLocker lock;
00154     PyObject* func = PyObject_GetAttrString(d->interpreter, "compile");
00155     PyObject* args = Py_BuildValue("(s)", source);
00156     PyObject* eval = PyEval_CallObject(func,args);  // must decref later
00157 
00158     Py_DECREF(args);
00159     Py_DECREF(func);
00160 
00161     if (eval){
00162         return eval;
00163     } else {
00164         // do not throw Base::PyException as this clears the error indicator
00165         throw Base::Exception();
00166     }
00167 
00168     // can never happen
00169     return 0;
00170 }
00171 
00183 int InteractiveInterpreter::compileCommand(const char* source) const
00184 {
00185     Base::PyGILStateLocker lock;
00186     PyObject* func = PyObject_GetAttrString(d->interpreter, "compile");
00187     PyObject* args = Py_BuildValue("(s)", source);
00188     PyObject* eval = PyEval_CallObject(func,args);  // must decref later
00189 
00190     Py_DECREF(args);
00191     Py_DECREF(func);
00192 
00193     int ret = 0;
00194     if (eval){
00195         if (PyObject_TypeCheck(Py_None, eval->ob_type))
00196             ret = 1; // incomplete
00197         else
00198             ret = 0; // complete
00199         Py_DECREF(eval);
00200     } else {
00201         ret = -1;    // invalid
00202     }
00203 
00204     return ret;
00205 }
00206 
00225 bool InteractiveInterpreter::runSource(const char* source) const
00226 {
00227     Base::PyGILStateLocker lock;
00228     PyObject* code;
00229     try {
00230         code = compile(source);
00231     } catch (const Base::Exception&) {
00232         // A system, overflow or value error was raised.
00233         // We clear the traceback info as this might be a longly
00234         // message we don't need.
00235         PyObject *errobj, *errdata, *errtraceback;
00236         PyErr_Fetch(&errobj, &errdata, &errtraceback);
00237         PyErr_Restore(errobj, errdata, 0);
00238         // print error message
00239         if (PyErr_Occurred()) PyErr_Print();
00240             return false;
00241     }
00242 
00243     // the command is incomplete
00244     if (PyObject_TypeCheck(Py_None, code->ob_type)) {
00245         Py_DECREF(code);
00246         return true;
00247     }
00248 
00249     // run the code and return false
00250     runCode((PyCodeObject*)code);
00251     return false;
00252 }
00253 
00254 /* Execute a code object.
00255  *
00256  * When an exception occurs,  a traceback is displayed.
00257  * All exceptions are caught except SystemExit, which is reraised.
00258  */
00259 void InteractiveInterpreter::runCode(PyCodeObject* code) const
00260 {
00261     Base::PyGILStateLocker lock;
00262     PyObject *module, *dict, *presult;           /* "exec code in d, d" */
00263     module = PyImport_AddModule("__main__");     /* get module, init python */
00264     if (module == NULL) 
00265         throw Base::PyException();                 /* not incref'd */
00266     dict = PyModule_GetDict(module);             /* get dict namespace */
00267     if (dict == NULL) 
00268         throw Base::PyException();                 /* not incref'd */
00269 
00270     // It seems that the return value is always 'None' or Null
00271     presult = PyEval_EvalCode(code, dict, dict); /* run compiled bytecode */
00272     Py_XDECREF(code);                            /* decref the code object */
00273     if (!presult) {
00274         if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
00275             // throw SystemExit exception
00276             throw Base::SystemExitException();
00277         }
00278         if ( PyErr_Occurred() )                    /* get latest python exception information */
00279             PyErr_Print();                           /* and print the error to the error output */
00280     } else {
00281         Py_DECREF(presult);
00282     }
00283 }
00284 
00289 bool InteractiveInterpreter::push(const char* line)
00290 {
00291     d->buffer.append(QString::fromAscii(line));
00292     QString source = d->buffer.join(QLatin1String("\n"));
00293     try {
00294         // Source is already UTF-8, so we can use toAscii()
00295         bool more = runSource(source.toAscii());
00296         if (!more)
00297             d->buffer.clear();
00298         return more;
00299     } catch (const Base::SystemExitException&) {
00300         d->buffer.clear();
00301         throw;
00302     } catch (...) {
00303         // indication of unhandled exception
00304         d->buffer.clear();
00305         if (PyErr_Occurred())
00306             PyErr_Print();
00307         throw;
00308     }
00309 
00310     return false;
00311 }
00312 
00313 QStringList InteractiveInterpreter::getBuffer() const
00314 {
00315     return d->buffer;
00316 }
00317 
00318 void InteractiveInterpreter::setBuffer(const QStringList& buf)
00319 {
00320     d->buffer = buf;
00321 }
00322 
00323 void InteractiveInterpreter::clearBuffer()
00324 {
00325     d->buffer.clear();
00326 }
00327 
00328 /* TRANSLATOR Gui::PythonConsole */
00329 
00333 PythonConsole::PythonConsole(QWidget *parent)
00334   : TextEdit(parent), WindowParameter( "Editor" )
00335 {
00336     d = new PythonConsoleP();
00337     d->interactive = false;
00338 
00339     // create an instance of InteractiveInterpreter
00340     try { 
00341         d->interpreter = new InteractiveInterpreter();
00342     } catch (const Base::Exception& e) {
00343         setPlainText(QString::fromAscii(e.what()));
00344         setEnabled(false);
00345     }
00346 
00347     // use the console highlighter
00348     pythonSyntax = new PythonConsoleHighlighter(this);
00349     pythonSyntax->setDocument(this->document());
00350 
00351     // create the window for call tips
00352     d->callTipsList = new CallTipsList(this);
00353     d->callTipsList->setFrameStyle(QFrame::Box|QFrame::Raised);
00354     d->callTipsList->setLineWidth(2);
00355     installEventFilter(d->callTipsList);
00356     viewport()->installEventFilter(d->callTipsList);
00357     d->callTipsList->setSelectionMode( QAbstractItemView::SingleSelection );
00358     d->callTipsList->hide();
00359 
00360     QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal);
00361     setFont(serifFont);
00362     
00363     // set colors and font from settings
00364     ParameterGrp::handle hPrefGrp = getWindowParameter();
00365     hPrefGrp->Attach( this );
00366     hPrefGrp->NotifyAll();
00367 
00368     // disable undo/redo stuff
00369     setUndoRedoEnabled( false );
00370     setAcceptDrops( true );
00371 
00372     // try to override Python's stdout/err
00373     Base::PyGILStateLocker lock;
00374     d->_stdoutPy = new PythonStdout(this);
00375     d->_stderrPy = new PythonStderr(this);
00376     d->_stdinPy  = new PythonStdin (this);
00377     d->_stdin  = PySys_GetObject("stdin");
00378     PySys_SetObject("stdin", d->_stdinPy);
00379 
00380     const char* version  = PyString_AsString(PySys_GetObject("version"));
00381     const char* platform = PyString_AsString(PySys_GetObject("platform"));
00382     d->output = QString::fromAscii("Python %1 on %2\n"
00383     "Type 'help', 'copyright', 'credits' or 'license' for more information.")
00384     .arg(QString::fromAscii(version)).arg(QString::fromAscii(platform));
00385     printPrompt(false);
00386 }
00387 
00389 PythonConsole::~PythonConsole()
00390 {
00391     Base::PyGILStateLocker lock;
00392     getWindowParameter()->Detach( this );
00393     delete pythonSyntax;
00394     Py_XDECREF(d->_stdoutPy);
00395     Py_XDECREF(d->_stderrPy);
00396     Py_XDECREF(d->_stdinPy);
00397     delete d->interpreter;
00398     delete d;
00399 }
00400 
00402 void PythonConsole::OnChange( Base::Subject<const char*> &rCaller,const char* sReason )
00403 {
00404     ParameterGrp::handle hPrefGrp = getWindowParameter();
00405 
00406     if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
00407         int fontSize = hPrefGrp->GetInt("FontSize", 10);
00408         QString fontFamily = QString::fromAscii(hPrefGrp->GetASCII("Font", "Courier").c_str());
00409         
00410         QFont font(fontFamily, fontSize);
00411         setFont(font);
00412         QFontMetrics metric(font);
00413         int width = metric.width(QLatin1String("0000"));
00414         setTabStopWidth(width);
00415     } else {
00416         QMap<QString, QColor>::ConstIterator it = d->colormap.find(QString::fromAscii(sReason));
00417         if (it != d->colormap.end()) {
00418             QColor color = it.value();
00419             unsigned long col = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
00420             col = hPrefGrp->GetUnsigned( sReason, col);
00421             color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
00422             pythonSyntax->setColor(QString::fromAscii(sReason), color);
00423         }
00424     }
00425 }
00426 
00431 void PythonConsole::keyPressEvent(QKeyEvent * e)
00432 {
00433     if (e->modifiers() & Qt::ControlModifier) {
00434         switch( e->key() ) 
00435         {
00436         case Qt::Key_Up:
00437             {
00438                 // no modification, just history facility
00439                 if (!d->history.isEmpty()) {
00440                     if (d->history.prev()) {
00441                         QString cmd = d->history.value();
00442                         overrideCursor(cmd);
00443                     }   return;
00444                 }
00445             }   break;
00446         case Qt::Key_Down:
00447             {
00448                 // no modification, just history facility
00449                 if (!d->history.isEmpty()) {
00450                     if (d->history.next()) {
00451                         QString cmd = d->history.value();
00452                         overrideCursor(cmd);
00453                     }   return;
00454                 }
00455             }   break;
00456         default:
00457             break;
00458         }
00459     }
00460 
00461     switch (e->key())
00462     {
00463     // running Python interpreter?
00464     case Qt::Key_Return:
00465     case Qt::Key_Enter:
00466         {
00467             // make sure to be at the end
00468             QTextCursor cursor = textCursor();
00469             cursor.movePosition(QTextCursor::End);
00470  
00471             // get the last paragraph's text
00472             QTextBlock block = cursor.block();
00473             QString line = block.text();
00474 
00475             // and skip the first 4 characters consisting of either ">>> " or "... "
00476             line = line.mid(4);
00477 
00478             // put statement to the history
00479             d->history.append(line);
00480 
00481             // evaluate and run the command
00482             runSource(line);
00483         }   break;
00484     case Qt::Key_Period:
00485         {
00486             QTextCursor cursor = textCursor();
00487             QTextBlock block = cursor.block();
00488             QString text = block.text();
00489             int length = cursor.position() - block.position();
00490             TextEdit::keyPressEvent(e);
00491             d->callTipsList->showTips(text.left(length));
00492         }   break;
00493     case Qt::Key_Home:
00494         {
00495             if (e->modifiers() & Qt::ControlModifier) {
00496                 TextEdit::keyPressEvent(e);
00497             } else {
00498                 QTextCursor::MoveMode mode = e->modifiers() & Qt::ShiftModifier
00499                      ? QTextCursor::KeepAnchor
00500                      : QTextCursor::MoveAnchor;
00501                 QTextCursor cursor = textCursor();
00502                 QTextBlock block = cursor.block();
00503                 QString text = block.text();
00504                 int cursorPos = block.position();
00505                 if (text.startsWith(QLatin1String(">>> ")) ||
00506                     text.startsWith(QLatin1String("... ")))
00507                     cursorPos += 4;
00508                 cursor.setPosition(cursorPos, mode);
00509                 setTextCursor(cursor);
00510                 ensureCursorVisible();
00511             }
00512         }   break;
00513     default: 
00514         {
00515             TextEdit::keyPressEvent(e);
00516 
00517             // This can't be done in CallTipsList::eventFilter() because we must first perform
00518             // the event and afterwards update the list widget
00519             if (d->callTipsList->isVisible()) {
00520                 d->callTipsList->validateCursor();
00521             }
00522         }   break;
00523     }  
00524 }
00525 
00530 void PythonConsole::insertPythonOutput( const QString& msg )
00531 {
00532     d->output += msg;
00533 }
00534 
00539 void PythonConsole::insertPythonError ( const QString& err )
00540 {
00541     d->error += err;
00542 }
00543 
00547 void PythonConsole::printPrompt(bool incomplete)
00548 {
00549     // write normal messages
00550     if (!d->output.isEmpty()) {
00551         appendOutput(d->output, (int)PythonConsoleP::Message);
00552         d->output = QString::null;
00553     }
00554 
00555     // write error messages
00556     if (!d->error.isEmpty()) {
00557         appendOutput(d->error, (int)PythonConsoleP::Error);
00558         d->error = QString::null;
00559     }
00560 
00561     // Append the prompt string 
00562     QTextCursor cursor = textCursor();
00563     cursor.beginEditBlock();
00564     cursor.movePosition(QTextCursor::End);
00565     QTextBlock block = cursor.block();
00566 
00567     // Python's print command appends a trailing '\n' to the system output.
00568     // In this case, however, we should not add a new text block. We force
00569     // the current block to be normal text (user state = 0) to be highlighted 
00570     // correctly and append the '>>> ' or '... ' to this block.
00571     if (block.length() > 1)
00572         cursor.insertBlock(cursor.blockFormat(), cursor.charFormat());
00573     else
00574         block.setUserState(0);
00575 
00576     incomplete ? cursor.insertText(QString::fromAscii("... "))
00577                : cursor.insertText(QString::fromAscii(">>> "));
00578     cursor.endEditBlock();
00579 
00580     // move cursor to the end
00581     cursor.movePosition(QTextCursor::End);
00582     setTextCursor(cursor);
00583 }
00584 
00589 void PythonConsole::appendOutput(const QString& output, int state)
00590 {
00591     QTextCursor cursor = textCursor();
00592     cursor.movePosition(QTextCursor::End);
00593     int pos = cursor.position() + 1;
00594     
00595     // delay rehighlighting
00596     cursor.beginEditBlock();
00597     appendPlainText(output);
00598 
00599     QTextBlock block = this->document()->findBlock(pos);
00600     while (block.isValid()) {
00601         block.setUserState(state);
00602         block = block.next();
00603     }
00604     cursor.endEditBlock(); // start highlightiong
00605 }
00606 
00610 void PythonConsole::runSource(const QString& line)
00611 {
00612     bool incomplete = false;
00613     Base::PyGILStateLocker lock;
00614     PyObject* default_stdout = PySys_GetObject("stdout");
00615     PyObject* default_stderr = PySys_GetObject("stderr");
00616     PySys_SetObject("stdout", d->_stdoutPy);
00617     PySys_SetObject("stderr", d->_stderrPy);
00618     d->interactive = true;
00619     
00620     try {
00621         // launch the command now
00622         incomplete = d->interpreter->push(line.toUtf8());
00623         setFocus(); // if focus was lost
00624     }
00625     catch (const Base::SystemExitException&) {
00626         ParameterGrp::handle hPrefGrp = getWindowParameter();
00627         bool check = hPrefGrp->GetBool("CheckSystemExit",true);
00628         if (!check)  Base::Interpreter().systemExit();
00629         int ret = QMessageBox::question(this, tr("System exit"), tr("The application is still running.\nDo you want to exit without saving your data?"),
00630         QMessageBox::Yes, QMessageBox::No|QMessageBox::Escape|QMessageBox::Default);
00631         if (ret == QMessageBox::Yes) {
00632             Base::Interpreter().systemExit();
00633         } else {
00634             PyErr_Clear();
00635         }
00636     }
00637     catch (const Py::Exception&) {
00638         QMessageBox::critical(this, tr("Python console"), tr("Unhandled PyCXX exception."));
00639     }
00640     catch (const Base::Exception&) {
00641         QMessageBox::critical(this, tr("Python console"), tr("Unhandled FreeCAD exception."));
00642     }
00643     catch (const std::exception&) {
00644         QMessageBox::critical(this, tr("Python console"), tr("Unhandled std C++ exception."));
00645     }
00646     catch (...) {
00647         QMessageBox::critical(this, tr("Python console"), tr("Unhandled unknown C++ exception."));
00648     }
00649 
00650     PySys_SetObject("stdout", default_stdout);
00651     PySys_SetObject("stderr", default_stderr);
00652     printPrompt(incomplete);
00653     d->interactive = false;
00654     for (QStringList::Iterator it = d->statements.begin(); it != d->statements.end(); ++it)
00655         printStatement(*it);
00656     d->statements.clear();
00657 }
00658 
00659 bool PythonConsole::isComment(const QString& source) const
00660 {
00661     if (source.isEmpty())
00662         return false;
00663     int i=0;
00664     while (i < source.length()) {
00665         QChar ch = source.at(i++);
00666         if (ch.isSpace())
00667             continue;
00668         if (ch == QLatin1Char('#'))
00669             return true;
00670     }
00671 
00672     return false;
00673 }
00674 
00679 void PythonConsole::printStatement( const QString& cmd )
00680 {
00681     // If we are in interactive mode we have to wait until the command is finished,
00682     // afterwards we can print the statements.
00683     if (d->interactive) {
00684         d->statements << cmd;
00685         return;
00686     }
00687 
00688     QTextCursor cursor = textCursor();
00689     QStringList statements = cmd.split(QLatin1String("\n"));
00690     for (QStringList::Iterator it = statements.begin(); it != statements.end(); ++it) {
00691         // go to the end before inserting new text 
00692         cursor.movePosition(QTextCursor::End);
00693         cursor.insertText( *it );
00694         d->history.append( *it );
00695         printPrompt(false);
00696     }
00697 }
00698 
00702 void PythonConsole::showEvent (QShowEvent * e)
00703 {
00704     TextEdit::showEvent(e);
00705     // set also the text cursor to the edit field
00706     setFocus();
00707 }
00708 
00709 void PythonConsole::visibilityChanged (bool visible)
00710 {
00711     if (visible)
00712         setFocus();
00713 }
00714 
00715 void PythonConsole::changeEvent(QEvent *e)
00716 {
00717     if (e->type() == QEvent::ParentChange) {
00718         QDockWidget* dw = qobject_cast<QDockWidget*>(this->parentWidget());
00719         if (dw) {
00720             connect(dw, SIGNAL(visibilityChanged(bool)),
00721                     this, SLOT(visibilityChanged(bool)));
00722         }
00723     }
00724     TextEdit::changeEvent(e);
00725 }
00726 
00730 void PythonConsole::dropEvent (QDropEvent * e)
00731 {
00732     const QMimeData* mimeData = e->mimeData();
00733     if (mimeData->hasFormat(QLatin1String("text/x-action-items"))) {
00734         QByteArray itemData = mimeData->data(QLatin1String("text/x-action-items"));
00735         QDataStream dataStream(&itemData, QIODevice::ReadOnly);
00736 
00737         int ctActions; dataStream >> ctActions;
00738         for (int i=0; i<ctActions; i++) {
00739             QString action;
00740             dataStream >> action;
00741             printStatement(QString::fromAscii("Gui.runCommand(\"%1\")").arg(action));
00742         }
00743 
00744         e->setDropAction(Qt::CopyAction);
00745         e->accept();
00746     }
00747     else // this will call insertFromMimeData
00748         QPlainTextEdit::dropEvent(e);
00749 }
00750 
00752 void PythonConsole::dragMoveEvent( QDragMoveEvent *e )
00753 {
00754     const QMimeData* mimeData = e->mimeData();
00755     if (mimeData->hasFormat(QLatin1String("text/x-action-items")))
00756         e->accept();
00757     else // this will call canInsertFromMimeData
00758         QPlainTextEdit::dragMoveEvent(e);
00759 }
00760 
00762 void PythonConsole::dragEnterEvent (QDragEnterEvent * e)
00763 {
00764     const QMimeData* mimeData = e->mimeData();
00765     if (mimeData->hasFormat(QLatin1String("text/x-action-items")))
00766         e->accept();
00767     else // this will call canInsertFromMimeData
00768         QPlainTextEdit::dragEnterEvent(e);
00769 }
00770 
00771 bool PythonConsole::canInsertFromMimeData (const QMimeData * source) const
00772 {
00773     if (source->hasText())
00774         return true;
00775     if (source->hasUrls()) {
00776         QList<QUrl> uri = source->urls();
00777         for (QList<QUrl>::ConstIterator it = uri.begin(); it != uri.end(); ++it) {
00778             QFileInfo info((*it).toLocalFile());
00779             if (info.exists() && info.isFile()) {
00780                 QString ext = info.suffix().toLower();
00781                 if (ext == QLatin1String("py") || ext == QLatin1String("fcmacro"))
00782                     return true;
00783             }
00784         }
00785     }
00786 
00787     return false;
00788 }
00789 
00793 void PythonConsole::insertFromMimeData (const QMimeData * source)
00794 {
00795     if (!source)
00796         return;
00797     // First check on urls instead of text otherwise it may happen that a url
00798     // is handled as text
00799     if (source->hasUrls()) {
00800         QList<QUrl> uri = source->urls();
00801         for (QList<QUrl>::ConstIterator it = uri.begin(); it != uri.end(); ++it) {
00802             // get the file name and check the extension
00803             QFileInfo info((*it).toLocalFile());
00804             QString ext = info.suffix().toLower();
00805             if (info.exists() && info.isFile() && 
00806                 (ext == QLatin1String("py") || ext == QLatin1String("fcmacro"))) {
00807                 // load the file and read-in the source code
00808                 QFile file(info.absoluteFilePath());
00809                 if (file.open(QIODevice::ReadOnly)) {
00810                     QTextStream str(&file);
00811                     runSourceFromMimeData(str.readAll());
00812                 }
00813                 file.close();
00814             }
00815         }
00816 
00817         return;
00818     }
00819     if (source->hasText()) {
00820         runSourceFromMimeData(source->text());
00821         return;
00822     }
00823 }
00824 
00825 QMimeData * PythonConsole::createMimeDataFromSelection () const
00826 {
00827     QMimeData* mime = new QMimeData();
00828     
00829     switch (d->type) {
00830         case PythonConsoleP::Normal:
00831             {
00832                 const QTextDocumentFragment fragment(textCursor());
00833                 mime->setText(fragment.toPlainText());
00834             }   break;
00835         case PythonConsoleP::Command:
00836             {
00837                 QTextCursor cursor = textCursor();
00838                 int s = cursor.selectionStart();
00839                 int e = cursor.selectionEnd();
00840                 QTextBlock b;
00841                 QStringList lines;
00842                 for (b = document()->begin(); b.isValid(); b = b.next()) {
00843                     int pos = b.position();
00844                     if ( pos >= s && pos <= e ) {
00845                         if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) {
00846                             QString line = b.text();
00847                             // and skip the first 4 characters consisting of either ">>> " or "... "
00848                             line = line.mid(4);
00849                             lines << line;
00850                         }
00851                     }
00852                 }
00853 
00854                 QString text = lines.join(QLatin1String("\n"));
00855                 mime->setText(text);
00856             }   break;
00857         case PythonConsoleP::History:
00858             {
00859                 const QStringList& hist = d->history.values();
00860                 QString text = hist.join(QLatin1String("\n"));
00861                 mime->setText(text);
00862             }   break;
00863     }
00864 
00865     return mime;
00866 }
00867 
00868 void PythonConsole::runSourceFromMimeData(const QString& source)
00869 {
00870     // When inserting a big text block we must break it down into several command
00871     // blocks instead of processing the text block as a whole or each single line.
00872     // If we processed the complete block as a whole only the first valid Python
00873     // command would be executed and the rest would be ignored. However, if we 
00874     // processed each line separately the interpreter might be confused that a block 
00875     // is complete but it might be not. This is for instance, if a class or method 
00876     // definition contains several empty lines which leads to error messages (almost
00877     // indentation errors) later on.
00878     QString text = source;
00879     if (text.isNull())
00880         return;
00881 
00882 #if defined (Q_OS_LINUX)
00883     // Need to convert CRLF to LF
00884     text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
00885 #elif defined(Q_OS_WIN32)
00886     // Need to convert CRLF to LF
00887     text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
00888 #elif defined(Q_OS_MAC)
00889     //need to convert CR to LF
00890     text.replace(QLatin1Char('\r'), QLatin1Char('\n'));
00891 #endif
00892 
00893     // separate the lines and get the last one
00894     QStringList lines = text.split(QLatin1Char('\n'));
00895     QString last = lines.back();
00896     lines.pop_back();
00897 
00898     QTextCursor cursor = textCursor();
00899     QStringList buffer = d->interpreter->getBuffer();
00900     d->interpreter->clearBuffer();
00901 
00902     int countNewlines = lines.count(), i = 0;
00903     for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it, ++i) {
00904         QString line = *it;
00905 
00906         // insert the text to the current cursor position
00907         cursor.insertText(*it);
00908 
00909         // for the very first line get the complete block
00910         // because it may differ from the inserted text
00911         if (i == 0) {
00912             // get the text from the current cursor position to the end, remove it
00913             // and add it to the last line
00914             cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
00915             QString select = cursor.selectedText();
00916             cursor.removeSelectedText();
00917             last = last + select;
00918             line = cursor.block().text();
00919             line = line.mid(4);
00920         }
00921 
00922         // put statement to the history
00923         d->history.append(line);
00924 
00925         buffer.append(line);
00926         int ret = d->interpreter->compileCommand(buffer.join(QLatin1String("\n")).toUtf8());
00927         if (ret == 1) { // incomplete
00928             printPrompt(true);
00929         }
00930         else if (ret == 0) { // complete
00931             // check if the following lines belong to the previous block
00932             int k=i+1;
00933             QString nextline;
00934             while ((nextline.isEmpty() || isComment(nextline)) && k < countNewlines) {
00935                 nextline = lines[k];
00936                 k++;
00937             }
00938             
00939             int ret = d->interpreter->compileCommand(nextline.toUtf8());
00940 
00941             // If the line is valid, i.e. complete or incomplete the previous block
00942             // is finished
00943             if (ret == -1) {
00944                 // the command is not finished yet
00945                 printPrompt(true);
00946             }
00947             else {
00948                 runSource(buffer.join(QLatin1String("\n")));
00949                 buffer.clear();
00950             }
00951         }
00952         else { // invalid
00953             runSource(buffer.join(QLatin1String("\n")));
00954             ensureCursorVisible();
00955             return; // exit the method on error
00956         }
00957     }
00958 
00959     // set the incomplete block to the interpreter and insert the last line
00960     d->interpreter->setBuffer(buffer);
00961     cursor.insertText(last);
00962     ensureCursorVisible();
00963 }
00964 
00968 void PythonConsole::overrideCursor(const QString& txt)
00969 {
00970     // Go to the last line and the fourth position, right after the prompt
00971     QTextCursor cursor = textCursor();
00972     QTextBlock block = cursor.block();
00973     cursor.movePosition(QTextCursor::End);
00974     cursor.movePosition(QTextCursor::StartOfLine);
00975     cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 4);
00976     cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, block.text().length());
00977     cursor.removeSelectedText();
00978     cursor.insertText(txt);
00979     // move cursor to the end
00980     cursor.movePosition(QTextCursor::End);
00981     setTextCursor(cursor);
00982 }
00983 
00984 void PythonConsole::contextMenuEvent ( QContextMenuEvent * e )
00985 {
00986     QMenu menu(this);
00987     QAction *a;
00988 
00989     a = menu.addAction(tr("&Copy"), this, SLOT(copy()), Qt::CTRL+Qt::Key_C);
00990     a->setEnabled(textCursor().hasSelection());
00991 
00992     a = menu.addAction(tr("&Copy command"), this, SLOT(onCopyCommand()));
00993     a->setEnabled(textCursor().hasSelection());
00994 
00995     a = menu.addAction(tr("&Copy history"), this, SLOT(onCopyHistory()));
00996     a->setEnabled(!d->history.isEmpty());
00997 
00998     a = menu.addAction( tr("Save history as..."), this, SLOT(onSaveHistoryAs()));
00999     a->setEnabled(!d->history.isEmpty());
01000 
01001     menu.addSeparator();
01002     a = menu.addAction(tr("&Paste"), this, SLOT(paste()), Qt::CTRL+Qt::Key_V);
01003     const QMimeData *md = QApplication::clipboard()->mimeData();
01004     a->setEnabled(md && canInsertFromMimeData(md));
01005 
01006     a = menu.addAction(tr("Select All"), this, SLOT(selectAll()), Qt::CTRL+Qt::Key_A);
01007     a->setEnabled(!document()->isEmpty());
01008 
01009     menu.addSeparator();
01010     menu.addAction( tr("Insert file name..."), this, SLOT(onInsertFileName()));
01011     menu.addSeparator();
01012 
01013     QAction* wrap = menu.addAction(tr("Word wrap"));
01014     wrap->setCheckable(true);
01015     wrap->setChecked(this->wordWrapMode() != QTextOption::NoWrap);
01016 
01017     QAction* exec = menu.exec(e->globalPos());
01018     if (exec == wrap) {
01019         this->setWordWrapMode(wrap->isChecked()
01020             ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
01021     }
01022 }
01023 
01024 void PythonConsole::onSaveHistoryAs()
01025 {
01026     QString cMacroPath = QString::fromUtf8(getDefaultParameter()->GetGroup( "Macro" )->
01027         GetASCII("MacroPath",App::Application::getUserAppDataDir().c_str()).c_str());
01028     QString fn = FileDialog::getSaveFileName(this, tr("Save History"), cMacroPath,
01029         tr("Macro Files (*.FCMacro *.py)"));
01030     if (!fn.isEmpty()) {
01031         int dot = fn.indexOf(QLatin1Char('.'));
01032         if (dot != -1) {
01033             QFile f(fn);
01034             if (f.open(QIODevice::WriteOnly)) {
01035                 QTextStream t (&f);
01036                 const QStringList& hist = d->history.values();
01037                 for (QStringList::ConstIterator it = hist.begin(); it != hist.end(); ++it)
01038                     t << *it << "\n";
01039                 f.close();
01040             }
01041         }
01042     }
01043 }
01044 
01045 void PythonConsole::onInsertFileName()
01046 {
01047     QString fn = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), tr("Insert file name"), QString::null, tr("All Files (*.*)") );
01048     if ( fn.isEmpty() )
01049         return;
01050     insertPlainText(fn);
01051 }
01052 
01056 void PythonConsole::onCopyHistory()
01057 {
01058     if (d->history.isEmpty())
01059         return;
01060     d->type = PythonConsoleP::History;
01061     QMimeData *data = createMimeDataFromSelection();
01062     QApplication::clipboard()->setMimeData(data);
01063     d->type = PythonConsoleP::Normal;
01064 }
01065 
01069 void PythonConsole::onCopyCommand()
01070 {
01071     d->type = PythonConsoleP::Command;
01072     copy();
01073     d->type = PythonConsoleP::Normal;
01074 }
01075 
01076 // ---------------------------------------------------------------------
01077 
01078 PythonConsoleHighlighter::PythonConsoleHighlighter(QObject* parent)
01079   : PythonSyntaxHighlighter(parent)
01080 {
01081 }
01082 
01083 PythonConsoleHighlighter::~PythonConsoleHighlighter()
01084 {
01085 }
01086 
01087 void PythonConsoleHighlighter::highlightBlock(const QString& text)
01088 {
01089     const int ErrorOutput   = (int)PythonConsoleP::Error;
01090     const int MessageOutput = (int)PythonConsoleP::Message;
01091 
01092     // Get user state to re-highlight the blocks in the appropriate format
01093     int stateOfPara = currentBlockState();
01094 
01095     switch (stateOfPara)
01096     {
01097     case ErrorOutput:
01098         {
01099             // Error output
01100             QTextCharFormat errorFormat;
01101             errorFormat.setForeground(color(QLatin1String("Python error")));
01102             errorFormat.setFontItalic(true);
01103             setFormat( 0, text.length(), errorFormat);
01104         }   break;
01105     case MessageOutput:
01106         {
01107             // Normal output
01108             QTextCharFormat outputFormat;
01109             outputFormat.setForeground(color(QLatin1String("Python output")));
01110             setFormat( 0, text.length(), outputFormat);
01111         }   break;
01112     default:
01113         {
01114             PythonSyntaxHighlighter::highlightBlock(text);
01115         }   break;
01116     }
01117 }
01118 
01119 void PythonConsoleHighlighter::colorChanged(const QString& type, const QColor& col)
01120 {
01121 }
01122 
01123 // ---------------------------------------------------------------------
01124 
01125 ConsoleHistory::ConsoleHistory()
01126 {
01127     it = _history.end();
01128 }
01129 
01130 ConsoleHistory::~ConsoleHistory()
01131 {
01132 }
01133 
01134 void ConsoleHistory::first()
01135 {
01136     it = _history.begin();
01137 }
01138 
01139 bool ConsoleHistory::more()
01140 {
01141     return (it != _history.end());
01142 }
01143 
01144 bool ConsoleHistory::next() 
01145 {
01146     if (it != _history.end()) {
01147         for (++it; it != _history.end(); ++it) {
01148             if (!it->isEmpty())
01149                 break;
01150         }
01151         return true;
01152     }
01153 
01154     return false;
01155 }
01156 
01157 bool ConsoleHistory::prev() 
01158 {
01159     if (it != _history.begin()) {
01160         for (--it; it != _history.begin(); --it) {
01161             if (!it->isEmpty())
01162                 break;
01163         }
01164         return true;
01165     }
01166 
01167     return false;
01168 }
01169 
01170 bool ConsoleHistory::isEmpty() const
01171 {
01172     return _history.isEmpty();
01173 }
01174 
01175 QString ConsoleHistory::value() const
01176 {
01177     if ( it != _history.end() )
01178         return *it;
01179     else
01180         return QString::null;
01181 }
01182 
01183 void ConsoleHistory::append( const QString& item )
01184 {
01185     _history.append( item );
01186     it = _history.end();
01187 }
01188 
01189 const QStringList& ConsoleHistory::values() const
01190 {
01191     return this->_history;
01192 }
01193 
01194 // -----------------------------------------------------
01195 
01196 /* TRANSLATOR Gui::PythonInputField */
01197 
01198 PythonInputField::PythonInputField(QWidget* parent)
01199   : QWidget(parent)
01200 {
01201     QGridLayout* gridLayout = new QGridLayout(this);
01202     gridLayout->setSpacing(6);
01203     gridLayout->setMargin(9);
01204 
01205     editField = new PythonEditor(this);
01206     gridLayout->addWidget(editField, 0, 0, 1, 1);
01207 
01208     QHBoxLayout* hboxLayout = new QHBoxLayout();
01209     hboxLayout->setSpacing(6);
01210     hboxLayout->setMargin(0);
01211 
01212     QSpacerItem* spacerItem = new QSpacerItem(131, 31, QSizePolicy::Expanding, QSizePolicy::Minimum);
01213     hboxLayout->addItem(spacerItem);
01214 
01215     okButton = new QPushButton(this);
01216     hboxLayout->addWidget(okButton);
01217     clearButton = new QPushButton(this);
01218     hboxLayout->addWidget(clearButton);
01219     gridLayout->addLayout(hboxLayout, 1, 0, 1, 1);
01220 
01221 
01222     this->setWindowTitle(Gui::PythonConsole::tr("Python Input Dialog"));
01223     okButton->setText(tr("OK"));
01224     clearButton->setText(tr("Clear"));
01225 
01226     QObject::connect(okButton, SIGNAL(clicked()), this, SIGNAL(textEntered()));
01227     QObject::connect(clearButton, SIGNAL(clicked()), editField, SLOT(clear()));
01228 }
01229 
01230 PythonInputField::~PythonInputField()
01231 {
01232 }
01233 
01234 QString PythonInputField::getText() const
01235 {
01236     return editField->toPlainText();
01237 }
01238 
01239 void PythonInputField::clear()
01240 {
01241     return editField->clear();
01242 }
01243 
01244 void PythonInputField::changeEvent(QEvent *e)
01245 {
01246     if (e->type() == QEvent::LanguageChange) {
01247         this->setWindowTitle(Gui::PythonConsole::tr("Python Input Dialog"));
01248         okButton->setText(tr("OK"));
01249         clearButton->setText(tr("Clear"));
01250     }
01251     else {
01252         QWidget::changeEvent(e);
01253     }
01254 }
01255 
01256 void PythonInputField::showEvent(QShowEvent* e)
01257 {
01258     editField->setFocus();
01259 }
01260 
01261 #include "moc_PythonConsole.cpp"

Generated on Wed Nov 23 19:00:32 2011 for FreeCAD by  doxygen 1.6.1