PythonQtScriptingConsole.cpp
575 lines
| 15.2 KiB
| text/x-c
|
CppLexer
ezust
|
r0 | /* | ||
* | ||||
florianlink
|
r133 | * Copyright (C) 2010 MeVis Medical Solutions AG All Rights Reserved. | ||
ezust
|
r0 | * | ||
* This library is free software; you can redistribute it and/or | ||||
* modify it under the terms of the GNU Lesser General Public | ||||
* License as published by the Free Software Foundation; either | ||||
* version 2.1 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 | ||||
* Lesser General Public License for more details. | ||||
* | ||||
* Further, this software is distributed without any warranty that it is | ||||
* free of the rightful claim of any third person regarding infringement | ||||
* or the like. Any license provided herein, whether implied or | ||||
* otherwise, applies only to this software file. Patent licenses, if | ||||
* any, provided herein do not apply to combinations of this program with | ||||
* other software, or any other product whatsoever. | ||||
* | ||||
* You should have received a copy of the GNU Lesser General Public | ||||
* License along with this library; if not, write to the Free Software | ||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||
* | ||||
florianlink
|
r133 | * Contact information: MeVis Medical Solutions AG, Universitaetsallee 29, | ||
ezust
|
r0 | * 28359 Bremen, Germany or: | ||
* | ||||
* http://www.mevis.de | ||||
* | ||||
*/ | ||||
//---------------------------------------------------------------------------------- | ||||
/*! | ||||
// \file PythonQtScriptingConsole.cpp | ||||
// \author Florian Link | ||||
// \author Last changed by $Author: florian $ | ||||
// \date 2006-10 | ||||
*/ | ||||
//---------------------------------------------------------------------------------- | ||||
#include "PythonQtScriptingConsole.h" | ||||
#include <QMenu> | ||||
#include <QMouseEvent> | ||||
#include <QKeyEvent> | ||||
#include <QApplication> | ||||
#include <QTextDocumentFragment> | ||||
#include <QTextBlock> | ||||
#include <QTextCursor> | ||||
#include <QDebug> | ||||
#include <QCompleter> | ||||
#include <QStringListModel> | ||||
#include <QScrollBar> | ||||
//----------------------------------------------------------------------------- | ||||
PythonQtScriptingConsole::PythonQtScriptingConsole(QWidget* parent, const PythonQtObjectPtr& context, Qt::WindowFlags windowFlags) | ||||
: QTextEdit(parent) { | ||||
setWindowFlags(windowFlags); | ||||
_defaultTextCharacterFormat = currentCharFormat(); | ||||
_context = context; | ||||
_historyPosition = 0; | ||||
florianlink
|
r14 | _hadError = false; | ||
ezust
|
r0 | |||
_completer = new QCompleter(this); | ||||
_completer->setWidget(this); | ||||
QObject::connect(_completer, SIGNAL(activated(const QString&)), | ||||
this, SLOT(insertCompletion(const QString&))); | ||||
clear(); | ||||
connect(PythonQt::self(), SIGNAL(pythonStdOut(const QString&)), this, SLOT(stdOut(const QString&))); | ||||
connect(PythonQt::self(), SIGNAL(pythonStdErr(const QString&)), this, SLOT(stdErr(const QString&))); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::stdOut(const QString& s) | ||||
{ | ||||
_stdOut += s; | ||||
int idx; | ||||
while ((idx = _stdOut.indexOf('\n'))!=-1) { | ||||
consoleMessage(_stdOut.left(idx)); | ||||
std::cout << _stdOut.left(idx).toLatin1().data() << std::endl; | ||||
_stdOut = _stdOut.mid(idx+1); | ||||
} | ||||
} | ||||
void PythonQtScriptingConsole::stdErr(const QString& s) | ||||
{ | ||||
florianlink
|
r14 | _hadError = true; | ||
ezust
|
r0 | _stdErr += s; | ||
int idx; | ||||
while ((idx = _stdErr.indexOf('\n'))!=-1) { | ||||
consoleMessage(_stdErr.left(idx)); | ||||
florianlink
|
r14 | std::cerr << _stdErr.left(idx).toLatin1().data() << std::endl; | ||
ezust
|
r0 | _stdErr = _stdErr.mid(idx+1); | ||
} | ||||
} | ||||
void PythonQtScriptingConsole::flushStdOut() | ||||
{ | ||||
if (!_stdOut.isEmpty()) { | ||||
stdOut("\n"); | ||||
} | ||||
if (!_stdErr.isEmpty()) { | ||||
stdErr("\n"); | ||||
} | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
PythonQtScriptingConsole::~PythonQtScriptingConsole() { | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::clear() { | ||||
QTextEdit::clear(); | ||||
appendCommandPrompt(); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::executeLine(bool storeOnly) | ||||
{ | ||||
QTextCursor textCursor = this->textCursor(); | ||||
textCursor.movePosition(QTextCursor::End); | ||||
// Select the text from the command prompt until the end of the block | ||||
// and get the selected text. | ||||
textCursor.setPosition(commandPromptPosition()); | ||||
textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); | ||||
QString code = textCursor.selectedText(); | ||||
// i don't know where this trailing space is coming from, blast it! | ||||
if (code.endsWith(" ")) { | ||||
code.truncate(code.length()-1); | ||||
} | ||||
if (!code.isEmpty()) { | ||||
// Update the history | ||||
_history << code; | ||||
_historyPosition = _history.count(); | ||||
_currentMultiLineCode += code + "\n"; | ||||
if (!storeOnly) { | ||||
executeCode(_currentMultiLineCode); | ||||
_currentMultiLineCode = ""; | ||||
} | ||||
} | ||||
// Insert a new command prompt | ||||
appendCommandPrompt(storeOnly); | ||||
} | ||||
void PythonQtScriptingConsole::executeCode(const QString& code) | ||||
{ | ||||
// put visible cursor to the end of the line | ||||
QTextCursor cursor = QTextEdit::textCursor(); | ||||
cursor.movePosition(QTextCursor::End); | ||||
setTextCursor(cursor); | ||||
int cursorPosition = this->textCursor().position(); | ||||
// evaluate the code | ||||
_stdOut = ""; | ||||
_stdErr = ""; | ||||
PythonQtObjectPtr p; | ||||
florianlink
|
r25 | PyObject* dict = NULL; | ||
if (PyModule_Check(_context)) { | ||||
dict = PyModule_GetDict(_context); | ||||
} else if (PyDict_Check(_context)) { | ||||
dict = _context; | ||||
} | ||||
if (dict) { | ||||
p.setNewRef(PyRun_String(code.toLatin1().data(), Py_single_input, dict, dict)); | ||||
} | ||||
ezust
|
r0 | if (!p) { | ||
PythonQt::self()->handleError(); | ||||
} | ||||
flushStdOut(); | ||||
bool messageInserted = (this->textCursor().position() != cursorPosition); | ||||
// If a message was inserted, then put another empty line before the command prompt | ||||
// to improve readability. | ||||
if (messageInserted) { | ||||
append(QString()); | ||||
} | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::appendCommandPrompt(bool storeOnly) { | ||||
if (storeOnly) { | ||||
_commandPrompt = "...> "; | ||||
} else { | ||||
_commandPrompt = "py> "; | ||||
} | ||||
append(_commandPrompt); | ||||
QTextCursor cursor = textCursor(); | ||||
cursor.movePosition(QTextCursor::End); | ||||
setTextCursor(cursor); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::setCurrentFont(const QColor& color, bool bold) { | ||||
QTextCharFormat charFormat(_defaultTextCharacterFormat); | ||||
QFont font(charFormat.font()); | ||||
font.setBold(bold); | ||||
charFormat.setFont(font); | ||||
QBrush brush(charFormat.foreground()); | ||||
brush.setColor(color); | ||||
charFormat.setForeground(brush); | ||||
setCurrentCharFormat(charFormat); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
int PythonQtScriptingConsole::commandPromptPosition() { | ||||
QTextCursor textCursor(this->textCursor()); | ||||
textCursor.movePosition(QTextCursor::End); | ||||
return textCursor.block().position() + _commandPrompt.length(); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::insertCompletion(const QString& completion) | ||||
{ | ||||
QTextCursor tc = textCursor(); | ||||
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); | ||||
if (tc.selectedText()==".") { | ||||
tc.insertText(QString(".") + completion); | ||||
} else { | ||||
tc = textCursor(); | ||||
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); | ||||
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); | ||||
tc.insertText(completion); | ||||
setTextCursor(tc); | ||||
} | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::handleTabCompletion() | ||||
{ | ||||
QTextCursor textCursor = this->textCursor(); | ||||
int pos = textCursor.position(); | ||||
textCursor.setPosition(commandPromptPosition()); | ||||
textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); | ||||
int startPos = textCursor.selectionStart(); | ||||
int offset = pos-startPos; | ||||
QString text = textCursor.selectedText(); | ||||
QString textToComplete; | ||||
int cur = offset; | ||||
while (cur--) { | ||||
QChar c = text.at(cur); | ||||
if (c.isLetterOrNumber() || c == '.' || c == '_') { | ||||
textToComplete.prepend(c); | ||||
} else { | ||||
break; | ||||
} | ||||
} | ||||
QString lookup; | ||||
QString compareText = textToComplete; | ||||
int dot = compareText.lastIndexOf('.'); | ||||
if (dot!=-1) { | ||||
lookup = compareText.mid(0, dot); | ||||
compareText = compareText.mid(dot+1, offset); | ||||
} | ||||
if (!lookup.isEmpty() || !compareText.isEmpty()) { | ||||
compareText = compareText.toLower(); | ||||
QStringList found; | ||||
QStringList l = PythonQt::self()->introspection(_context, lookup, PythonQt::Anything); | ||||
foreach (QString n, l) { | ||||
if (n.toLower().startsWith(compareText)) { | ||||
found << n; | ||||
} | ||||
} | ||||
if (!found.isEmpty()) { | ||||
_completer->setCompletionPrefix(compareText); | ||||
_completer->setCompletionMode(QCompleter::PopupCompletion); | ||||
_completer->setModel(new QStringListModel(found, _completer)); | ||||
_completer->setCaseSensitivity(Qt::CaseInsensitive); | ||||
QTextCursor c = this->textCursor(); | ||||
c.movePosition(QTextCursor::StartOfWord); | ||||
QRect cr = cursorRect(c); | ||||
cr.setWidth(_completer->popup()->sizeHintForColumn(0) | ||||
+ _completer->popup()->verticalScrollBar()->sizeHint().width()); | ||||
cr.translate(0,8); | ||||
_completer->complete(cr); | ||||
} else { | ||||
_completer->popup()->hide(); | ||||
} | ||||
} else { | ||||
_completer->popup()->hide(); | ||||
} | ||||
} | ||||
void PythonQtScriptingConsole::keyPressEvent(QKeyEvent* event) { | ||||
if (_completer && _completer->popup()->isVisible()) { | ||||
// The following keys are forwarded by the completer to the widget | ||||
switch (event->key()) { | ||||
case Qt::Key_Return: | ||||
if (!_completer->popup()->currentIndex().isValid()) { | ||||
insertCompletion(_completer->currentCompletion()); | ||||
_completer->popup()->hide(); | ||||
event->accept(); | ||||
} | ||||
event->ignore(); | ||||
return; | ||||
break; | ||||
case Qt::Key_Enter: | ||||
case Qt::Key_Escape: | ||||
case Qt::Key_Tab: | ||||
case Qt::Key_Backtab: | ||||
event->ignore(); | ||||
return; // let the completer do default behavior | ||||
default: | ||||
break; | ||||
} | ||||
} | ||||
bool eventHandled = false; | ||||
QTextCursor textCursor = this->textCursor(); | ||||
int key = event->key(); | ||||
switch (key) { | ||||
case Qt::Key_Left: | ||||
// Moving the cursor left is limited to the position | ||||
// of the command prompt. | ||||
if (textCursor.position() <= commandPromptPosition()) { | ||||
QApplication::beep(); | ||||
eventHandled = true; | ||||
} | ||||
break; | ||||
case Qt::Key_Up: | ||||
// Display the previous command in the history | ||||
if (_historyPosition>0) { | ||||
_historyPosition--; | ||||
changeHistory(); | ||||
} | ||||
eventHandled = true; | ||||
break; | ||||
case Qt::Key_Down: | ||||
// Display the next command in the history | ||||
if (_historyPosition+1<_history.count()) { | ||||
_historyPosition++; | ||||
changeHistory(); | ||||
} | ||||
eventHandled = true; | ||||
break; | ||||
case Qt::Key_Return: | ||||
executeLine(event->modifiers() & Qt::ShiftModifier); | ||||
eventHandled = true; | ||||
break; | ||||
case Qt::Key_Backspace: | ||||
if (textCursor.hasSelection()) { | ||||
cut(); | ||||
eventHandled = true; | ||||
} else { | ||||
// Intercept backspace key event to check if | ||||
// deleting a character is allowed. It is not | ||||
// allowed, if the user wants to delete the | ||||
// command prompt. | ||||
if (textCursor.position() <= commandPromptPosition()) { | ||||
QApplication::beep(); | ||||
eventHandled = true; | ||||
} | ||||
} | ||||
break; | ||||
case Qt::Key_Delete: | ||||
cut(); | ||||
eventHandled = true; | ||||
break; | ||||
default: | ||||
if (key >= Qt::Key_Space && key <= Qt::Key_division) { | ||||
if (textCursor.hasSelection() && !verifySelectionBeforeDeletion()) { | ||||
// The selection must not be deleted. | ||||
eventHandled = true; | ||||
} else { | ||||
// The key is an input character, check if the cursor is | ||||
// behind the last command prompt, else inserting the | ||||
// character is not allowed. | ||||
int commandPromptPosition = this->commandPromptPosition(); | ||||
if (textCursor.position() < commandPromptPosition) { | ||||
textCursor.setPosition(commandPromptPosition); | ||||
setTextCursor(textCursor); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
if (eventHandled) { | ||||
_completer->popup()->hide(); | ||||
event->accept(); | ||||
} else { | ||||
QTextEdit::keyPressEvent(event); | ||||
QString text = event->text(); | ||||
if (!text.isEmpty()) { | ||||
handleTabCompletion(); | ||||
} else { | ||||
_completer->popup()->hide(); | ||||
} | ||||
eventHandled = true; | ||||
} | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::cut() { | ||||
bool deletionAllowed = verifySelectionBeforeDeletion(); | ||||
if (deletionAllowed) { | ||||
QTextEdit::cut(); | ||||
} | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
bool PythonQtScriptingConsole::verifySelectionBeforeDeletion() { | ||||
bool deletionAllowed = true; | ||||
QTextCursor textCursor = this->textCursor(); | ||||
int commandPromptPosition = this->commandPromptPosition(); | ||||
int selectionStart = textCursor.selectionStart(); | ||||
int selectionEnd = textCursor.selectionEnd(); | ||||
if (textCursor.hasSelection()) { | ||||
// Selected text may only be deleted after the last command prompt. | ||||
// If the selection is partly after the command prompt set the selection | ||||
// to the part and deletion is allowed. If the selection occurs before the | ||||
// last command prompt, then deletion is not allowed. | ||||
if (selectionStart < commandPromptPosition || | ||||
selectionEnd < commandPromptPosition) { | ||||
// Assure selectionEnd is bigger than selection start | ||||
if (selectionStart > selectionEnd) { | ||||
int tmp = selectionEnd; | ||||
selectionEnd = selectionStart; | ||||
selectionStart = tmp; | ||||
} | ||||
if (selectionEnd < commandPromptPosition) { | ||||
// Selection is completely before command prompt, | ||||
// so deletion is not allowed. | ||||
QApplication::beep(); | ||||
deletionAllowed = false; | ||||
} else { | ||||
// The selectionEnd is after the command prompt, so set | ||||
// the selection start to the commandPromptPosition. | ||||
selectionStart = commandPromptPosition; | ||||
textCursor.setPosition(selectionStart); | ||||
textCursor.setPosition(selectionStart, QTextCursor::KeepAnchor); | ||||
setTextCursor(textCursor); | ||||
} | ||||
} | ||||
} else { // if (hasSelectedText()) | ||||
// When there is no selected text, deletion is not allowed before the | ||||
// command prompt. | ||||
if (textCursor.position() < commandPromptPosition) { | ||||
QApplication::beep(); | ||||
deletionAllowed = false; | ||||
} | ||||
} | ||||
return deletionAllowed; | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::changeHistory() { | ||||
// Select the text after the last command prompt ... | ||||
QTextCursor textCursor = this->textCursor(); | ||||
textCursor.movePosition(QTextCursor::End); | ||||
textCursor.setPosition(commandPromptPosition(), QTextCursor::KeepAnchor); | ||||
// ... and replace it with the history text. | ||||
textCursor.insertText(_history.value(_historyPosition)); | ||||
textCursor.movePosition(QTextCursor::End); | ||||
setTextCursor(textCursor); | ||||
} | ||||
//----------------------------------------------------------------------------- | ||||
void PythonQtScriptingConsole::consoleMessage(const QString & message) { | ||||
append(QString()); | ||||
insertPlainText(message); | ||||
// Reset all font modifications done by the html string | ||||
setCurrentCharFormat(_defaultTextCharacterFormat); | ||||
} | ||||