1390 lines
46 KiB
C++
1390 lines
46 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
# include <QApplication>
|
|
# include <QClipboard>
|
|
# include <QDockWidget>
|
|
# include <QGridLayout>
|
|
# include <QHBoxLayout>
|
|
# include <QKeyEvent>
|
|
# include <QMenu>
|
|
# include <QMessageBox>
|
|
# include <QPushButton>
|
|
# include <QSpacerItem>
|
|
# include <QTextCursor>
|
|
# include <QTextDocumentFragment>
|
|
# include <QTextStream>
|
|
# include <QUrl>
|
|
#endif
|
|
|
|
#include "PythonConsole.h"
|
|
#include "PythonConsolePy.h"
|
|
#include "CallTips.h"
|
|
#include "Application.h"
|
|
#include "Action.h"
|
|
#include "Command.h"
|
|
#include "DlgEditorImp.h"
|
|
#include "FileDialog.h"
|
|
#include "MainWindow.h"
|
|
|
|
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/Exception.h>
|
|
#include <CXX/Exception.hxx>
|
|
|
|
using namespace Gui;
|
|
|
|
namespace Gui
|
|
{
|
|
|
|
static size_t promptLength = 4; //< length of prompt string: ">>> " or "... ", in either case 4 characters
|
|
|
|
struct PythonConsoleP
|
|
{
|
|
enum Output {Error = 20, Message = 21};
|
|
enum CopyType {Normal, History, Command};
|
|
CopyType type;
|
|
PyObject *_stdoutPy, *_stderrPy, *_stdinPy, *_stdin;
|
|
InteractiveInterpreter* interpreter;
|
|
CallTipsList* callTipsList;
|
|
ConsoleHistory history;
|
|
QString output, error, info;
|
|
QStringList statements;
|
|
bool interactive;
|
|
QMap<QString, QColor> colormap; // Color map
|
|
PythonConsoleP()
|
|
{
|
|
type = Normal;
|
|
interpreter = 0;
|
|
colormap[QLatin1String("Text")] = Qt::black;
|
|
colormap[QLatin1String("Bookmark")] = Qt::cyan;
|
|
colormap[QLatin1String("Breakpoint")] = Qt::red;
|
|
colormap[QLatin1String("Keyword")] = Qt::blue;
|
|
colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
|
|
colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
|
|
colormap[QLatin1String("Number")] = Qt::blue;
|
|
colormap[QLatin1String("String")] = Qt::red;
|
|
colormap[QLatin1String("Character")] = Qt::red;
|
|
colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
|
|
colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
|
|
colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
|
|
colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
|
|
colormap[QLatin1String("Python error")] = Qt::red;
|
|
}
|
|
};
|
|
struct InteractiveInterpreterP
|
|
{
|
|
PyObject* interpreter;
|
|
PyObject* sysmodule;
|
|
QStringList buffer;
|
|
};
|
|
} // namespace Gui
|
|
|
|
InteractiveInterpreter::InteractiveInterpreter()
|
|
{
|
|
// import code.py and create an instance of InteractiveInterpreter
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* module = PyImport_ImportModule("code");
|
|
if (!module)
|
|
throw Base::PyException();
|
|
PyObject* func = PyObject_GetAttrString(module, "InteractiveInterpreter");
|
|
PyObject* args = Py_BuildValue("()");
|
|
d = new InteractiveInterpreterP;
|
|
d->interpreter = PyEval_CallObject(func,args);
|
|
Py_DECREF(args);
|
|
Py_DECREF(func);
|
|
Py_DECREF(module);
|
|
|
|
setPrompt();
|
|
}
|
|
|
|
InteractiveInterpreter::~InteractiveInterpreter()
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
Py_XDECREF(d->interpreter);
|
|
Py_XDECREF(d->sysmodule);
|
|
delete d;
|
|
}
|
|
|
|
/**
|
|
* Set the ps1 and ps2 members of the sys module if not yet defined.
|
|
*/
|
|
void InteractiveInterpreter::setPrompt()
|
|
{
|
|
// import code.py and create an instance of InteractiveInterpreter
|
|
Base::PyGILStateLocker lock;
|
|
d->sysmodule = PyImport_ImportModule("sys");
|
|
if (!PyObject_HasAttrString(d->sysmodule, "ps1"))
|
|
PyObject_SetAttrString(d->sysmodule, "ps1", PyString_FromString(">>> "));
|
|
if (!PyObject_HasAttrString(d->sysmodule, "ps2"))
|
|
PyObject_SetAttrString(d->sysmodule, "ps2", PyString_FromString("... "));
|
|
}
|
|
|
|
/**
|
|
* Compile a command and determine whether it is incomplete.
|
|
*
|
|
* The source string may contain line feeds and/or carriage returns. \n
|
|
* Return value / exceptions raised:
|
|
* - Return a code object if the command is complete and valid
|
|
* - Return None if the command is incomplete
|
|
* - Raise SyntaxError, ValueError or OverflowError if the command is a
|
|
* syntax error (OverflowError and ValueError can be produced by
|
|
* malformed literals).
|
|
*/
|
|
PyObject* InteractiveInterpreter::compile(const char* source) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* func = PyObject_GetAttrString(d->interpreter, "compile");
|
|
PyObject* args = Py_BuildValue("(s)", source);
|
|
PyObject* eval = PyEval_CallObject(func,args); // must decref later
|
|
|
|
Py_DECREF(args);
|
|
Py_DECREF(func);
|
|
|
|
if (eval){
|
|
return eval;
|
|
} else {
|
|
// do not throw Base::PyException as this clears the error indicator
|
|
throw Base::Exception();
|
|
}
|
|
|
|
// can never happen
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Compile a command and determine whether it is incomplete.
|
|
*
|
|
* The source string may contain line feeds and/or carriage returns. \n
|
|
* Return value:
|
|
* - Return 1 if the command is incomplete
|
|
* - Return 0 if the command is complete and valid
|
|
* - Return -1 if the command is a syntax error
|
|
* .
|
|
* (OverflowError and ValueError can be produced by malformed literals).
|
|
*/
|
|
int InteractiveInterpreter::compileCommand(const char* source) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* func = PyObject_GetAttrString(d->interpreter, "compile");
|
|
PyObject* args = Py_BuildValue("(s)", source);
|
|
PyObject* eval = PyEval_CallObject(func,args); // must decref later
|
|
|
|
Py_DECREF(args);
|
|
Py_DECREF(func);
|
|
|
|
int ret = 0;
|
|
if (eval){
|
|
if (PyObject_TypeCheck(Py_None, eval->ob_type))
|
|
ret = 1; // incomplete
|
|
else
|
|
ret = 0; // complete
|
|
Py_DECREF(eval);
|
|
} else {
|
|
ret = -1; // invalid
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Compile and run some source in the interpreter.
|
|
*
|
|
* One several things can happen:
|
|
*
|
|
* - The input is incorrect; compile() raised an exception (SyntaxError or OverflowError).
|
|
* A syntax traceback will be printed by calling Python's PyErr_Print() method to the redirected stderr.
|
|
*
|
|
* - The input is incomplete, and more input is required; compile() returned 'None'.
|
|
* Nothing happens.
|
|
*
|
|
* - The input is complete; compile() returned a code object. The code is executed by calling
|
|
* runCode() (which also handles run-time exceptions, except for SystemExit).
|
|
* .
|
|
* The return value is True if the input is incomplete, False in the other cases (unless
|
|
* an exception is raised). The return value can be used to decide whether to use sys.ps1
|
|
* or sys.ps2 to prompt the next line.
|
|
*/
|
|
bool InteractiveInterpreter::runSource(const char* source) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* code;
|
|
try {
|
|
code = compile(source);
|
|
} catch (const Base::Exception&) {
|
|
// A system, overflow or value error was raised.
|
|
// We clear the traceback info as this might be a longly
|
|
// message we don't need.
|
|
PyObject *errobj, *errdata, *errtraceback;
|
|
PyErr_Fetch(&errobj, &errdata, &errtraceback);
|
|
PyErr_Restore(errobj, errdata, 0);
|
|
// print error message
|
|
if (PyErr_Occurred()) PyErr_Print();
|
|
return false;
|
|
}
|
|
|
|
// the command is incomplete
|
|
if (PyObject_TypeCheck(Py_None, code->ob_type)) {
|
|
Py_DECREF(code);
|
|
return true;
|
|
}
|
|
|
|
// run the code and return false
|
|
runCode((PyCodeObject*)code);
|
|
return false;
|
|
}
|
|
|
|
/* Execute a code object.
|
|
*
|
|
* When an exception occurs, a traceback is displayed.
|
|
* All exceptions are caught except SystemExit, which is reraised.
|
|
*/
|
|
void InteractiveInterpreter::runCode(PyCodeObject* code) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject *module, *dict, *presult; /* "exec code in d, d" */
|
|
module = PyImport_AddModule("__main__"); /* get module, init python */
|
|
if (module == NULL)
|
|
throw Base::PyException(); /* not incref'd */
|
|
dict = PyModule_GetDict(module); /* get dict namespace */
|
|
if (dict == NULL)
|
|
throw Base::PyException(); /* not incref'd */
|
|
|
|
// It seems that the return value is always 'None' or Null
|
|
presult = PyEval_EvalCode(code, dict, dict); /* run compiled bytecode */
|
|
Py_XDECREF(code); /* decref the code object */
|
|
if (!presult) {
|
|
if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
|
|
// throw SystemExit exception
|
|
throw Base::SystemExitException();
|
|
}
|
|
if ( PyErr_Occurred() ) /* get latest python exception information */
|
|
PyErr_Print(); /* and print the error to the error output */
|
|
} else {
|
|
Py_DECREF(presult);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store the line into the internal buffer and compile the total buffer.
|
|
* In case it is a complete Python command the buffer is emptied.
|
|
*/
|
|
bool InteractiveInterpreter::push(const char* line)
|
|
{
|
|
d->buffer.append(QString::fromAscii(line));
|
|
QString source = d->buffer.join(QLatin1String("\n"));
|
|
try {
|
|
// Source is already UTF-8, so we can use toAscii()
|
|
bool more = runSource(source.toAscii());
|
|
if (!more)
|
|
d->buffer.clear();
|
|
return more;
|
|
} catch (const Base::SystemExitException&) {
|
|
d->buffer.clear();
|
|
throw;
|
|
} catch (...) {
|
|
// indication of unhandled exception
|
|
d->buffer.clear();
|
|
if (PyErr_Occurred())
|
|
PyErr_Print();
|
|
throw;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool InteractiveInterpreter::hasPendingInput( void ) const
|
|
{
|
|
return (!d->buffer.isEmpty());
|
|
}
|
|
|
|
QStringList InteractiveInterpreter::getBuffer() const
|
|
{
|
|
return d->buffer;
|
|
}
|
|
|
|
void InteractiveInterpreter::setBuffer(const QStringList& buf)
|
|
{
|
|
d->buffer = buf;
|
|
}
|
|
|
|
void InteractiveInterpreter::clearBuffer()
|
|
{
|
|
d->buffer.clear();
|
|
}
|
|
|
|
/* TRANSLATOR Gui::PythonConsole */
|
|
|
|
/**
|
|
* Constructs a PythonConsole which is a child of 'parent'.
|
|
*/
|
|
PythonConsole::PythonConsole(QWidget *parent)
|
|
: TextEdit(parent), WindowParameter( "Editor" )
|
|
{
|
|
d = new PythonConsoleP();
|
|
d->interactive = false;
|
|
|
|
// create an instance of InteractiveInterpreter
|
|
try {
|
|
d->interpreter = new InteractiveInterpreter();
|
|
} catch (const Base::Exception& e) {
|
|
setPlainText(QString::fromAscii(e.what()));
|
|
setEnabled(false);
|
|
}
|
|
|
|
// use the console highlighter
|
|
pythonSyntax = new PythonConsoleHighlighter(this);
|
|
pythonSyntax->setDocument(this->document());
|
|
|
|
// create the window for call tips
|
|
d->callTipsList = new CallTipsList(this);
|
|
d->callTipsList->setFrameStyle(QFrame::Box|QFrame::Raised);
|
|
d->callTipsList->setLineWidth(2);
|
|
installEventFilter(d->callTipsList);
|
|
viewport()->installEventFilter(d->callTipsList);
|
|
d->callTipsList->setSelectionMode( QAbstractItemView::SingleSelection );
|
|
d->callTipsList->hide();
|
|
|
|
QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal);
|
|
setFont(serifFont);
|
|
|
|
// set colors and font from settings
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
hPrefGrp->Attach( this );
|
|
hPrefGrp->NotifyAll();
|
|
|
|
// disable undo/redo stuff
|
|
setUndoRedoEnabled( false );
|
|
setAcceptDrops( true );
|
|
|
|
// try to override Python's stdout/err
|
|
Base::PyGILStateLocker lock;
|
|
d->_stdoutPy = new PythonStdout(this);
|
|
d->_stderrPy = new PythonStderr(this);
|
|
d->_stdinPy = new PythonStdin (this);
|
|
d->_stdin = PySys_GetObject("stdin");
|
|
PySys_SetObject("stdin", d->_stdinPy);
|
|
|
|
const char* version = PyString_AsString(PySys_GetObject("version"));
|
|
const char* platform = PyString_AsString(PySys_GetObject("platform"));
|
|
d->info = QString::fromAscii("Python %1 on %2\n"
|
|
"Type 'help', 'copyright', 'credits' or 'license' for more information.")
|
|
.arg(QString::fromAscii(version)).arg(QString::fromAscii(platform));
|
|
d->output = d->info;
|
|
printPrompt(PythonConsole::Complete);
|
|
}
|
|
|
|
/** Destroys the object and frees any allocated resources */
|
|
PythonConsole::~PythonConsole()
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
getWindowParameter()->Detach( this );
|
|
delete pythonSyntax;
|
|
Py_XDECREF(d->_stdoutPy);
|
|
Py_XDECREF(d->_stderrPy);
|
|
Py_XDECREF(d->_stdinPy);
|
|
delete d->interpreter;
|
|
delete d;
|
|
}
|
|
|
|
/** Set new font and colors according to the paramerts. */
|
|
void PythonConsole::OnChange( Base::Subject<const char*> &rCaller,const char* sReason )
|
|
{
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
|
|
if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
|
|
int fontSize = hPrefGrp->GetInt("FontSize", 10);
|
|
QString fontFamily = QString::fromAscii(hPrefGrp->GetASCII("Font", "Courier").c_str());
|
|
|
|
QFont font(fontFamily, fontSize);
|
|
setFont(font);
|
|
QFontMetrics metric(font);
|
|
int width = metric.width(QLatin1String("0000"));
|
|
setTabStopWidth(width);
|
|
} else {
|
|
QMap<QString, QColor>::ConstIterator it = d->colormap.find(QString::fromAscii(sReason));
|
|
if (it != d->colormap.end()) {
|
|
QColor color = it.value();
|
|
unsigned long col = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
|
|
col = hPrefGrp->GetUnsigned( sReason, col);
|
|
color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
|
|
pythonSyntax->setColor(QString::fromAscii(sReason), color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the input of the console to make the correct indentations.
|
|
* After a command is prompted completely the Python interpreter is started.
|
|
*/
|
|
void PythonConsole::keyPressEvent(QKeyEvent * e)
|
|
{
|
|
bool restartHistory = true;
|
|
QTextCursor cursor = this->textCursor();
|
|
|
|
// construct reference cursor at begin of input line ...
|
|
QTextCursor inputLineBegin = cursor;
|
|
inputLineBegin.movePosition( QTextCursor::End );
|
|
inputLineBegin.movePosition( QTextCursor::StartOfLine );
|
|
inputLineBegin.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptLength );
|
|
|
|
if (cursor < inputLineBegin)
|
|
{
|
|
/**
|
|
* The cursor is placed not on the input line (or within the prompt string)
|
|
* So we handle key input as follows:
|
|
* - don't allow changing previous lines.
|
|
* - allow full movement (no prompt restriction)
|
|
* - allow copying content (Ctrl+C)
|
|
* - "escape" to end of input line
|
|
*/
|
|
switch (e->key())
|
|
{
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Enter:
|
|
case Qt::Key_Escape:
|
|
this->moveCursor( QTextCursor::End );
|
|
break;
|
|
|
|
default:
|
|
if (e->text().isEmpty() ||
|
|
e->matches(QKeySequence::Copy) ||
|
|
e->matches(QKeySequence::SelectAll)) {
|
|
TextEdit::keyPressEvent(e);
|
|
}
|
|
else if (!e->text().isEmpty() &&
|
|
(e->modifiers() == Qt::NoModifier ||
|
|
e->modifiers() == Qt::ShiftModifier)) {
|
|
this->moveCursor(QTextCursor::End);
|
|
TextEdit::keyPressEvent(e);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* The cursor sits somewhere on the input line (after the prompt)
|
|
* Here we handle key input a bit different:
|
|
* - restrict cursor movement to input line range (excluding the prompt characters)
|
|
* - roam the history by Up/Down keys
|
|
* - show call tips on period
|
|
*/
|
|
QTextBlock inputBlock = inputLineBegin.block(); //< get the last paragraph's text
|
|
QString inputLine = inputBlock.text().mid(promptLength); //< and skip prompt characters
|
|
|
|
switch (e->key())
|
|
{
|
|
case Qt::Key_Escape:
|
|
{
|
|
// disable current input line - i.e. put it to history but don't execute it.
|
|
if (!inputLine.isEmpty())
|
|
{
|
|
d->history.append( QLatin1String("# ") + inputLine ); //< put line to history ...
|
|
inputLineBegin.insertText( QString::fromAscii("# ") ); //< but comment it on console
|
|
setTextCursor( inputLineBegin );
|
|
printPrompt(d->interpreter->hasPendingInput() //< print adequate prompt
|
|
? PythonConsole::Incomplete
|
|
: PythonConsole::Complete);
|
|
}
|
|
} break;
|
|
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Enter:
|
|
{
|
|
runSource( inputLine ); //< commit input line
|
|
d->history.append( inputLine ); //< put statement to history
|
|
} break;
|
|
|
|
case Qt::Key_Period:
|
|
{
|
|
// analyse context and show available call tips
|
|
int contextLength = cursor.position() - inputLineBegin.position();
|
|
TextEdit::keyPressEvent(e);
|
|
d->callTipsList->showTips( inputLine.left( contextLength ) );
|
|
} break;
|
|
|
|
case Qt::Key_Home:
|
|
{
|
|
QTextCursor::MoveMode mode = (e->modifiers() & Qt::ShiftModifier)? QTextCursor::KeepAnchor
|
|
/* else */ : QTextCursor::MoveAnchor;
|
|
cursor.setPosition( inputBlock.position() + promptLength, mode );
|
|
setTextCursor( cursor );
|
|
ensureCursorVisible();
|
|
} break;
|
|
|
|
case Qt::Key_Up:
|
|
{
|
|
// if possible, move back in history
|
|
if (d->history.prev( inputLine ))
|
|
{ overrideCursor( d->history.value() ); }
|
|
restartHistory = false;
|
|
} break;
|
|
|
|
case Qt::Key_Down:
|
|
{
|
|
// if possible, move forward in history
|
|
if (d->history.next())
|
|
{ overrideCursor( d->history.value() ); }
|
|
restartHistory = false;
|
|
} break;
|
|
|
|
case Qt::Key_Left:
|
|
{
|
|
if (cursor > inputLineBegin)
|
|
{ TextEdit::keyPressEvent(e); }
|
|
restartHistory = false;
|
|
} break;
|
|
|
|
case Qt::Key_Right:
|
|
{
|
|
TextEdit::keyPressEvent(e);
|
|
restartHistory = false;
|
|
} break;
|
|
|
|
case Qt::Key_Backspace:
|
|
{
|
|
if (cursor > inputLineBegin)
|
|
{ TextEdit::keyPressEvent(e); }
|
|
} break;
|
|
|
|
default:
|
|
{
|
|
TextEdit::keyPressEvent(e);
|
|
} break;
|
|
}
|
|
// This can't be done in CallTipsList::eventFilter() because we must first perform
|
|
// the event and afterwards update the list widget
|
|
if (d->callTipsList->isVisible())
|
|
{ d->callTipsList->validateCursor(); }
|
|
}
|
|
// any cursor move resets the history to its latest item.
|
|
if (restartHistory)
|
|
{ d->history.restart(); }
|
|
}
|
|
|
|
/**
|
|
* Insert an output message to the console. This message comes from
|
|
* the Python interpreter and is redirected from sys.stdout.
|
|
*/
|
|
void PythonConsole::insertPythonOutput( const QString& msg )
|
|
{
|
|
d->output += msg;
|
|
}
|
|
|
|
/**
|
|
* Insert an error message to the console. This message comes from
|
|
* the Python interpreter and is redirected from sys.stderr.
|
|
*/
|
|
void PythonConsole::insertPythonError ( const QString& err )
|
|
{
|
|
d->error += err;
|
|
}
|
|
|
|
void PythonConsole::onFlush()
|
|
{
|
|
printPrompt(PythonConsole::Flush);
|
|
}
|
|
|
|
/** Prints the ps1 prompt (>>> ) for complete and ps2 prompt (... ) for
|
|
* incomplete commands to the console window.
|
|
*/
|
|
void PythonConsole::printPrompt(PythonConsole::Prompt mode)
|
|
{
|
|
// write normal messages
|
|
if (!d->output.isEmpty()) {
|
|
appendOutput(d->output, (int)PythonConsoleP::Message);
|
|
d->output = QString::null;
|
|
}
|
|
|
|
// write error messages
|
|
if (!d->error.isEmpty()) {
|
|
appendOutput(d->error, (int)PythonConsoleP::Error);
|
|
d->error = QString::null;
|
|
}
|
|
|
|
// Append the prompt string
|
|
QTextCursor cursor = textCursor();
|
|
cursor.beginEditBlock();
|
|
cursor.movePosition(QTextCursor::End);
|
|
QTextBlock block = cursor.block();
|
|
|
|
// Python's print command appends a trailing '\n' to the system output.
|
|
// In this case, however, we should not add a new text block. We force
|
|
// the current block to be normal text (user state = 0) to be highlighted
|
|
// correctly and append the '>>> ' or '... ' to this block.
|
|
if (block.length() > 1)
|
|
cursor.insertBlock(cursor.blockFormat(), cursor.charFormat());
|
|
else
|
|
block.setUserState(0);
|
|
|
|
switch (mode)
|
|
{
|
|
case PythonConsole::Incomplete:
|
|
cursor.insertText(QString::fromAscii("... "));
|
|
break;
|
|
case PythonConsole::Complete:
|
|
cursor.insertText(QString::fromAscii(">>> "));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
cursor.endEditBlock();
|
|
|
|
// move cursor to the end
|
|
cursor.movePosition(QTextCursor::End);
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
/**
|
|
* Appends \a output to the console and set \a state as user state to
|
|
* the text block which is needed for the highlighting.
|
|
*/
|
|
void PythonConsole::appendOutput(const QString& output, int state)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
cursor.movePosition(QTextCursor::End);
|
|
int pos = cursor.position() + 1;
|
|
|
|
// delay rehighlighting
|
|
cursor.beginEditBlock();
|
|
appendPlainText(output);
|
|
|
|
QTextBlock block = this->document()->findBlock(pos);
|
|
while (block.isValid()) {
|
|
block.setUserState(state);
|
|
block = block.next();
|
|
}
|
|
cursor.endEditBlock(); // start highlightiong
|
|
}
|
|
|
|
/**
|
|
* Builds up the Python command and pass it to the interpreter.
|
|
*/
|
|
void PythonConsole::runSource(const QString& line)
|
|
{
|
|
bool incomplete = false;
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* default_stdout = PySys_GetObject("stdout");
|
|
PyObject* default_stderr = PySys_GetObject("stderr");
|
|
PySys_SetObject("stdout", d->_stdoutPy);
|
|
PySys_SetObject("stderr", d->_stderrPy);
|
|
d->interactive = true;
|
|
|
|
try {
|
|
// launch the command now
|
|
incomplete = d->interpreter->push(line.toUtf8());
|
|
setFocus(); // if focus was lost
|
|
}
|
|
catch (const Base::SystemExitException&) {
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
bool check = hPrefGrp->GetBool("CheckSystemExit",true);
|
|
if (!check) qApp->quit();
|
|
int ret = QMessageBox::question(this, tr("System exit"), tr("The application is still running.\nDo you want to exit without saving your data?"),
|
|
QMessageBox::Yes, QMessageBox::No|QMessageBox::Escape|QMessageBox::Default);
|
|
if (ret == QMessageBox::Yes) {
|
|
qApp->quit();
|
|
}
|
|
else {
|
|
PyErr_Clear();
|
|
}
|
|
}
|
|
catch (const Py::Exception&) {
|
|
QMessageBox::critical(this, tr("Python console"), tr("Unhandled PyCXX exception."));
|
|
}
|
|
catch (const Base::Exception&) {
|
|
QMessageBox::critical(this, tr("Python console"), tr("Unhandled FreeCAD exception."));
|
|
}
|
|
catch (const std::exception&) {
|
|
QMessageBox::critical(this, tr("Python console"), tr("Unhandled std C++ exception."));
|
|
}
|
|
catch (...) {
|
|
QMessageBox::critical(this, tr("Python console"), tr("Unhandled unknown C++ exception."));
|
|
}
|
|
|
|
printPrompt(incomplete ? PythonConsole::Incomplete
|
|
: PythonConsole::Complete);
|
|
PySys_SetObject("stdout", default_stdout);
|
|
PySys_SetObject("stderr", default_stderr);
|
|
d->interactive = false;
|
|
for (QStringList::Iterator it = d->statements.begin(); it != d->statements.end(); ++it)
|
|
printStatement(*it);
|
|
d->statements.clear();
|
|
}
|
|
|
|
bool PythonConsole::isComment(const QString& source) const
|
|
{
|
|
if (source.isEmpty())
|
|
return false;
|
|
int i=0;
|
|
while (i < source.length()) {
|
|
QChar ch = source.at(i++);
|
|
if (ch.isSpace())
|
|
continue;
|
|
if (ch == QLatin1Char('#'))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Prints the Python statement cmd to the console.
|
|
* @note The statement gets only printed and added to the history but not invoked.
|
|
*/
|
|
void PythonConsole::printStatement( const QString& cmd )
|
|
{
|
|
// If we are in interactive mode we have to wait until the command is finished,
|
|
// afterwards we can print the statements.
|
|
if (d->interactive) {
|
|
d->statements << cmd;
|
|
return;
|
|
}
|
|
|
|
QTextCursor cursor = textCursor();
|
|
QStringList statements = cmd.split(QLatin1String("\n"));
|
|
for (QStringList::Iterator it = statements.begin(); it != statements.end(); ++it) {
|
|
// go to the end before inserting new text
|
|
cursor.movePosition(QTextCursor::End);
|
|
cursor.insertText( *it );
|
|
d->history.append( *it );
|
|
printPrompt(PythonConsole::Complete);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows the Python window and sets the focus to set text cursor.
|
|
*/
|
|
void PythonConsole::showEvent (QShowEvent * e)
|
|
{
|
|
TextEdit::showEvent(e);
|
|
// set also the text cursor to the edit field
|
|
setFocus();
|
|
}
|
|
|
|
void PythonConsole::visibilityChanged (bool visible)
|
|
{
|
|
if (visible)
|
|
setFocus();
|
|
}
|
|
|
|
void PythonConsole::changeEvent(QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::ParentChange) {
|
|
QDockWidget* dw = qobject_cast<QDockWidget*>(this->parentWidget());
|
|
if (dw) {
|
|
connect(dw, SIGNAL(visibilityChanged(bool)),
|
|
this, SLOT(visibilityChanged(bool)));
|
|
}
|
|
}
|
|
TextEdit::changeEvent(e);
|
|
}
|
|
|
|
/**
|
|
* Drops the event \a e and writes the right Python command.
|
|
*/
|
|
void PythonConsole::dropEvent (QDropEvent * e)
|
|
{
|
|
const QMimeData* mimeData = e->mimeData();
|
|
if (mimeData->hasFormat(QLatin1String("text/x-action-items"))) {
|
|
QByteArray itemData = mimeData->data(QLatin1String("text/x-action-items"));
|
|
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
|
|
|
|
int ctActions; dataStream >> ctActions;
|
|
for (int i=0; i<ctActions; i++) {
|
|
QString action;
|
|
dataStream >> action;
|
|
printStatement(QString::fromAscii("Gui.runCommand(\"%1\")").arg(action));
|
|
}
|
|
|
|
e->setDropAction(Qt::CopyAction);
|
|
e->accept();
|
|
}
|
|
else // this will call insertFromMimeData
|
|
QPlainTextEdit::dropEvent(e);
|
|
}
|
|
|
|
/** Dragging of action objects is allowed. */
|
|
void PythonConsole::dragMoveEvent( QDragMoveEvent *e )
|
|
{
|
|
const QMimeData* mimeData = e->mimeData();
|
|
if (mimeData->hasFormat(QLatin1String("text/x-action-items")))
|
|
e->accept();
|
|
else // this will call canInsertFromMimeData
|
|
QPlainTextEdit::dragMoveEvent(e);
|
|
}
|
|
|
|
/** Dragging of action objects is allowed. */
|
|
void PythonConsole::dragEnterEvent (QDragEnterEvent * e)
|
|
{
|
|
const QMimeData* mimeData = e->mimeData();
|
|
if (mimeData->hasFormat(QLatin1String("text/x-action-items")))
|
|
e->accept();
|
|
else // this will call canInsertFromMimeData
|
|
QPlainTextEdit::dragEnterEvent(e);
|
|
}
|
|
|
|
bool PythonConsole::canInsertFromMimeData (const QMimeData * source) const
|
|
{
|
|
if (source->hasText())
|
|
return true;
|
|
if (source->hasUrls()) {
|
|
QList<QUrl> uri = source->urls();
|
|
for (QList<QUrl>::ConstIterator it = uri.begin(); it != uri.end(); ++it) {
|
|
QFileInfo info((*it).toLocalFile());
|
|
if (info.exists() && info.isFile()) {
|
|
QString ext = info.suffix().toLower();
|
|
if (ext == QLatin1String("py") || ext == QLatin1String("fcmacro"))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Allow to paste plain text or urls of text files.
|
|
*/
|
|
void PythonConsole::insertFromMimeData (const QMimeData * source)
|
|
{
|
|
if (!source)
|
|
return;
|
|
// First check on urls instead of text otherwise it may happen that a url
|
|
// is handled as text
|
|
if (source->hasUrls()) {
|
|
QList<QUrl> uri = source->urls();
|
|
for (QList<QUrl>::ConstIterator it = uri.begin(); it != uri.end(); ++it) {
|
|
// get the file name and check the extension
|
|
QFileInfo info((*it).toLocalFile());
|
|
QString ext = info.suffix().toLower();
|
|
if (info.exists() && info.isFile() &&
|
|
(ext == QLatin1String("py") || ext == QLatin1String("fcmacro"))) {
|
|
// load the file and read-in the source code
|
|
QFile file(info.absoluteFilePath());
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
QTextStream str(&file);
|
|
runSourceFromMimeData(str.readAll());
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (source->hasText()) {
|
|
runSourceFromMimeData(source->text());
|
|
return;
|
|
}
|
|
}
|
|
|
|
QMimeData * PythonConsole::createMimeDataFromSelection () const
|
|
{
|
|
QMimeData* mime = new QMimeData();
|
|
|
|
switch (d->type) {
|
|
case PythonConsoleP::Normal:
|
|
{
|
|
const QTextDocumentFragment fragment(textCursor());
|
|
mime->setText(fragment.toPlainText());
|
|
} break;
|
|
case PythonConsoleP::Command:
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
int s = cursor.selectionStart();
|
|
int e = cursor.selectionEnd();
|
|
QTextBlock b;
|
|
QStringList lines;
|
|
for (b = document()->begin(); b.isValid(); b = b.next()) {
|
|
int pos = b.position();
|
|
if ( pos >= s && pos <= e ) {
|
|
if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) {
|
|
QString line = b.text();
|
|
// and skip the prompt characters consisting of either ">>> " or "... "
|
|
line = line.mid(promptLength);
|
|
lines << line;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString text = lines.join(QLatin1String("\n"));
|
|
mime->setText(text);
|
|
} break;
|
|
case PythonConsoleP::History:
|
|
{
|
|
const QStringList& hist = d->history.values();
|
|
QString text = hist.join(QLatin1String("\n"));
|
|
mime->setText(text);
|
|
} break;
|
|
}
|
|
|
|
return mime;
|
|
}
|
|
|
|
void PythonConsole::runSourceFromMimeData(const QString& source)
|
|
{
|
|
// When inserting a big text block we must break it down into several command
|
|
// blocks instead of processing the text block as a whole or each single line.
|
|
// If we processed the complete block as a whole only the first valid Python
|
|
// command would be executed and the rest would be ignored. However, if we
|
|
// processed each line separately the interpreter might be confused that a block
|
|
// is complete but it might be not. This is for instance, if a class or method
|
|
// definition contains several empty lines which leads to error messages (almost
|
|
// indentation errors) later on.
|
|
QString text = source;
|
|
if (text.isNull())
|
|
return;
|
|
|
|
#if defined (Q_OS_LINUX)
|
|
// Need to convert CRLF to LF
|
|
text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
|
#elif defined(Q_OS_WIN32)
|
|
// Need to convert CRLF to LF
|
|
text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
|
#elif defined(Q_OS_MAC)
|
|
//need to convert CR to LF
|
|
text.replace(QLatin1Char('\r'), QLatin1Char('\n'));
|
|
#endif
|
|
|
|
// separate the lines and get the last one
|
|
QStringList lines = text.split(QLatin1Char('\n'));
|
|
QString last = lines.back();
|
|
lines.pop_back();
|
|
|
|
QTextCursor cursor = textCursor();
|
|
QStringList buffer = d->interpreter->getBuffer();
|
|
d->interpreter->clearBuffer();
|
|
|
|
int countNewlines = lines.count(), i = 0;
|
|
for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it, ++i) {
|
|
QString line = *it;
|
|
|
|
// insert the text to the current cursor position
|
|
cursor.insertText(*it);
|
|
|
|
// for the very first line get the complete block
|
|
// because it may differ from the inserted text
|
|
if (i == 0) {
|
|
// get the text from the current cursor position to the end, remove it
|
|
// and add it to the last line
|
|
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
QString select = cursor.selectedText();
|
|
cursor.removeSelectedText();
|
|
last = last + select;
|
|
line = cursor.block().text();
|
|
line = line.mid(promptLength);
|
|
}
|
|
|
|
// put statement to the history
|
|
d->history.append(line);
|
|
|
|
buffer.append(line);
|
|
int ret = d->interpreter->compileCommand(buffer.join(QLatin1String("\n")).toUtf8());
|
|
if (ret == 1) { // incomplete
|
|
printPrompt(PythonConsole::Incomplete);
|
|
}
|
|
else if (ret == 0) { // complete
|
|
// check if the following lines belong to the previous block
|
|
int k=i+1;
|
|
QString nextline;
|
|
while ((nextline.isEmpty() || isComment(nextline)) && k < countNewlines) {
|
|
nextline = lines[k];
|
|
k++;
|
|
}
|
|
|
|
int ret = d->interpreter->compileCommand(nextline.toUtf8());
|
|
|
|
// If the line is valid, i.e. complete or incomplete the previous block
|
|
// is finished
|
|
if (ret == -1) {
|
|
// the command is not finished yet
|
|
printPrompt(PythonConsole::Incomplete);
|
|
}
|
|
else {
|
|
runSource(buffer.join(QLatin1String("\n")));
|
|
buffer.clear();
|
|
}
|
|
}
|
|
else { // invalid
|
|
runSource(buffer.join(QLatin1String("\n")));
|
|
ensureCursorVisible();
|
|
return; // exit the method on error
|
|
}
|
|
}
|
|
|
|
// set the incomplete block to the interpreter and insert the last line
|
|
d->interpreter->setBuffer(buffer);
|
|
cursor.insertText(last);
|
|
ensureCursorVisible();
|
|
}
|
|
|
|
/**
|
|
* Overwrites the text of the cursor.
|
|
*/
|
|
void PythonConsole::overrideCursor(const QString& txt)
|
|
{
|
|
// Go to the last line and the fourth position, right after the prompt
|
|
QTextCursor cursor = textCursor();
|
|
QTextBlock block = cursor.block();
|
|
cursor.movePosition(QTextCursor::End);
|
|
cursor.movePosition(QTextCursor::StartOfLine);
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength);
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, block.text().length());
|
|
cursor.removeSelectedText();
|
|
cursor.insertText(txt);
|
|
// move cursor to the end
|
|
cursor.movePosition(QTextCursor::End);
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
void PythonConsole::contextMenuEvent ( QContextMenuEvent * e )
|
|
{
|
|
QMenu menu(this);
|
|
QAction *a;
|
|
// construct reference cursor at begin of input line ...
|
|
QTextCursor cursor = this->textCursor();
|
|
QTextCursor inputLineBegin = cursor;
|
|
inputLineBegin.movePosition(QTextCursor::End);
|
|
inputLineBegin.movePosition(QTextCursor::StartOfLine);
|
|
inputLineBegin.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength);
|
|
|
|
a = menu.addAction(tr("&Copy"), this, SLOT(copy()), Qt::CTRL+Qt::Key_C);
|
|
a->setEnabled(textCursor().hasSelection());
|
|
|
|
a = menu.addAction(tr("&Copy command"), this, SLOT(onCopyCommand()));
|
|
a->setEnabled(textCursor().hasSelection());
|
|
|
|
a = menu.addAction(tr("&Copy history"), this, SLOT(onCopyHistory()));
|
|
a->setEnabled(!d->history.isEmpty());
|
|
|
|
a = menu.addAction( tr("Save history as..."), this, SLOT(onSaveHistoryAs()));
|
|
a->setEnabled(!d->history.isEmpty());
|
|
|
|
menu.addSeparator();
|
|
|
|
a = menu.addAction(tr("&Paste"), this, SLOT(paste()), Qt::CTRL+Qt::Key_V);
|
|
const QMimeData *md = QApplication::clipboard()->mimeData();
|
|
a->setEnabled(cursor >= inputLineBegin && md && canInsertFromMimeData(md));
|
|
|
|
a = menu.addAction(tr("Select All"), this, SLOT(selectAll()), Qt::CTRL+Qt::Key_A);
|
|
a->setEnabled(!document()->isEmpty());
|
|
|
|
a = menu.addAction(tr("Clear console"), this, SLOT(onClearConsole()));
|
|
a->setEnabled(!document()->isEmpty());
|
|
|
|
menu.addSeparator();
|
|
menu.addAction( tr("Insert file name..."), this, SLOT(onInsertFileName()));
|
|
menu.addSeparator();
|
|
|
|
QAction* wrap = menu.addAction(tr("Word wrap"));
|
|
wrap->setCheckable(true);
|
|
wrap->setChecked(this->wordWrapMode() != QTextOption::NoWrap);
|
|
|
|
QAction* exec = menu.exec(e->globalPos());
|
|
if (exec == wrap) {
|
|
this->setWordWrapMode(wrap->isChecked()
|
|
? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
|
|
}
|
|
}
|
|
|
|
void PythonConsole::onClearConsole()
|
|
{
|
|
clear();
|
|
d->output = d->info;
|
|
printPrompt(PythonConsole::Complete);
|
|
}
|
|
|
|
void PythonConsole::onSaveHistoryAs()
|
|
{
|
|
QString cMacroPath = QString::fromUtf8(getDefaultParameter()->GetGroup( "Macro" )->
|
|
GetASCII("MacroPath",App::Application::getUserAppDataDir().c_str()).c_str());
|
|
QString fn = FileDialog::getSaveFileName(this, tr("Save History"), cMacroPath,
|
|
tr("Macro Files (*.FCMacro *.py)"));
|
|
if (!fn.isEmpty()) {
|
|
int dot = fn.indexOf(QLatin1Char('.'));
|
|
if (dot != -1) {
|
|
QFile f(fn);
|
|
if (f.open(QIODevice::WriteOnly)) {
|
|
QTextStream t (&f);
|
|
const QStringList& hist = d->history.values();
|
|
for (QStringList::ConstIterator it = hist.begin(); it != hist.end(); ++it)
|
|
t << *it << "\n";
|
|
f.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PythonConsole::onInsertFileName()
|
|
{
|
|
QString fn = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), tr("Insert file name"), QString::null, tr("All Files (*.*)") );
|
|
if ( fn.isEmpty() )
|
|
return;
|
|
insertPlainText(fn);
|
|
}
|
|
|
|
/**
|
|
* Copy the history of the console into the clipboard.
|
|
*/
|
|
void PythonConsole::onCopyHistory()
|
|
{
|
|
if (d->history.isEmpty())
|
|
return;
|
|
d->type = PythonConsoleP::History;
|
|
QMimeData *data = createMimeDataFromSelection();
|
|
QApplication::clipboard()->setMimeData(data);
|
|
d->type = PythonConsoleP::Normal;
|
|
}
|
|
|
|
/**
|
|
* Copy the selected commands into the clipboard. This is a subset of the history.
|
|
*/
|
|
void PythonConsole::onCopyCommand()
|
|
{
|
|
d->type = PythonConsoleP::Command;
|
|
copy();
|
|
d->type = PythonConsoleP::Normal;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
PythonConsoleHighlighter::PythonConsoleHighlighter(QObject* parent)
|
|
: PythonSyntaxHighlighter(parent)
|
|
{
|
|
}
|
|
|
|
PythonConsoleHighlighter::~PythonConsoleHighlighter()
|
|
{
|
|
}
|
|
|
|
void PythonConsoleHighlighter::highlightBlock(const QString& text)
|
|
{
|
|
const int ErrorOutput = (int)PythonConsoleP::Error;
|
|
const int MessageOutput = (int)PythonConsoleP::Message;
|
|
|
|
// Get user state to re-highlight the blocks in the appropriate format
|
|
int stateOfPara = currentBlockState();
|
|
|
|
switch (stateOfPara)
|
|
{
|
|
case ErrorOutput:
|
|
{
|
|
// Error output
|
|
QTextCharFormat errorFormat;
|
|
errorFormat.setForeground(color(QLatin1String("Python error")));
|
|
errorFormat.setFontItalic(true);
|
|
setFormat( 0, text.length(), errorFormat);
|
|
} break;
|
|
case MessageOutput:
|
|
{
|
|
// Normal output
|
|
QTextCharFormat outputFormat;
|
|
outputFormat.setForeground(color(QLatin1String("Python output")));
|
|
setFormat( 0, text.length(), outputFormat);
|
|
} break;
|
|
default:
|
|
{
|
|
PythonSyntaxHighlighter::highlightBlock(text);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void PythonConsoleHighlighter::colorChanged(const QString& type, const QColor& col)
|
|
{
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
ConsoleHistory::ConsoleHistory()
|
|
{
|
|
_it = _history.end();
|
|
}
|
|
|
|
ConsoleHistory::~ConsoleHistory()
|
|
{
|
|
}
|
|
|
|
void ConsoleHistory::first()
|
|
{
|
|
_it = _history.begin();
|
|
}
|
|
|
|
bool ConsoleHistory::more()
|
|
{
|
|
return (_it != _history.end());
|
|
}
|
|
|
|
/**
|
|
* next switches the history pointer to the next item.
|
|
* While searching the next item, the routine respects the search prefix set by prev().
|
|
* @return true if the pointer was switched to a later item, false otherwise.
|
|
*/
|
|
bool ConsoleHistory::next()
|
|
{
|
|
bool wentNext = false;
|
|
|
|
// if we didn't reach history's end ...
|
|
if (_it != _history.end())
|
|
{
|
|
// we go forward until we find an item matching the prefix.
|
|
for (++_it; _it != _history.end(); ++_it)
|
|
{
|
|
if (!_it->isEmpty() && _it->startsWith( _prefix ))
|
|
{ break; }
|
|
}
|
|
// we did a step - no matter of a matching prefix.
|
|
wentNext = true;
|
|
}
|
|
return wentNext;
|
|
}
|
|
|
|
/**
|
|
* prev switches the history pointer to the previous item.
|
|
* The optional parameter prefix allows to search the history selectively for commands that start
|
|
* with a certain character sequence.
|
|
* @param prefix - prefix string for searching backwards in history, empty string by default
|
|
* @return true if the pointer was switched to an earlier item, false otherwise.
|
|
*/
|
|
bool ConsoleHistory::prev( const QString &prefix )
|
|
{
|
|
bool wentPrev = false;
|
|
|
|
// store prefix if it's the first history access
|
|
if (_it == _history.end())
|
|
{ _prefix = prefix; }
|
|
|
|
// while we didn't go back or reach history's begin ...
|
|
while (!wentPrev && _it != _history.begin())
|
|
{
|
|
// go back in history and check if item matches prefix
|
|
// Skip empty items
|
|
--_it;
|
|
wentPrev = (!_it->isEmpty() && _it->startsWith( _prefix ));
|
|
}
|
|
return wentPrev;
|
|
}
|
|
|
|
bool ConsoleHistory::isEmpty() const
|
|
{
|
|
return _history.isEmpty();
|
|
}
|
|
|
|
const QString& ConsoleHistory::value() const
|
|
{
|
|
return ((_it != _history.end())? *_it
|
|
/* else */ : _prefix);
|
|
}
|
|
|
|
void ConsoleHistory::append( const QString& item )
|
|
{
|
|
_history.append( item );
|
|
// reset iterator to make the next history
|
|
// access begin with the latest item.
|
|
_it = _history.end();
|
|
}
|
|
|
|
const QStringList& ConsoleHistory::values() const
|
|
{
|
|
return this->_history;
|
|
}
|
|
|
|
/**
|
|
* restart resets the history access to the latest item.
|
|
*/
|
|
void ConsoleHistory::restart( void )
|
|
{
|
|
_it = _history.end();
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::PythonInputField */
|
|
|
|
PythonInputField::PythonInputField(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
QGridLayout* gridLayout = new QGridLayout(this);
|
|
gridLayout->setSpacing(6);
|
|
gridLayout->setMargin(9);
|
|
|
|
editField = new PythonEditor(this);
|
|
gridLayout->addWidget(editField, 0, 0, 1, 1);
|
|
setFocusProxy(editField);
|
|
|
|
QHBoxLayout* hboxLayout = new QHBoxLayout();
|
|
hboxLayout->setSpacing(6);
|
|
hboxLayout->setMargin(0);
|
|
|
|
QSpacerItem* spacerItem = new QSpacerItem(131, 31, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
hboxLayout->addItem(spacerItem);
|
|
|
|
okButton = new QPushButton(this);
|
|
hboxLayout->addWidget(okButton);
|
|
clearButton = new QPushButton(this);
|
|
hboxLayout->addWidget(clearButton);
|
|
gridLayout->addLayout(hboxLayout, 1, 0, 1, 1);
|
|
|
|
|
|
this->setWindowTitle(Gui::PythonConsole::tr("Python Input Dialog"));
|
|
okButton->setText(tr("OK"));
|
|
clearButton->setText(tr("Clear"));
|
|
|
|
QObject::connect(okButton, SIGNAL(clicked()), this, SIGNAL(textEntered()));
|
|
QObject::connect(clearButton, SIGNAL(clicked()), editField, SLOT(clear()));
|
|
}
|
|
|
|
PythonInputField::~PythonInputField()
|
|
{
|
|
}
|
|
|
|
QString PythonInputField::getText() const
|
|
{
|
|
return editField->toPlainText();
|
|
}
|
|
|
|
void PythonInputField::clear()
|
|
{
|
|
return editField->clear();
|
|
}
|
|
|
|
void PythonInputField::changeEvent(QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
this->setWindowTitle(Gui::PythonConsole::tr("Python Input Dialog"));
|
|
okButton->setText(tr("OK"));
|
|
clearButton->setText(tr("Clear"));
|
|
}
|
|
else {
|
|
QWidget::changeEvent(e);
|
|
}
|
|
}
|
|
|
|
void PythonInputField::showEvent(QShowEvent* e)
|
|
{
|
|
editField->setFocus();
|
|
}
|
|
|
|
#include "moc_PythonConsole.cpp"
|