/* * * Copyright (C) 2006 MeVis Research GmbH All Rights Reserved. * * 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 * * Contact information: MeVis Research GmbH, Universitaetsallee 29, * 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 #include #include #include #include #include #include #include #include #include #include //----------------------------------------------------------------------------- PythonQtScriptingConsole::PythonQtScriptingConsole(QWidget* parent, const PythonQtObjectPtr& context, Qt::WindowFlags windowFlags) : QTextEdit(parent) { setWindowFlags(windowFlags); _defaultTextCharacterFormat = currentCharFormat(); _context = context; _historyPosition = 0; _hadError = false; _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) { _hadError = true; _stdErr += s; int idx; while ((idx = _stdErr.indexOf('\n'))!=-1) { consoleMessage(_stdErr.left(idx)); std::cerr << _stdErr.left(idx).toLatin1().data() << std::endl; _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; p.setNewRef(PyRun_String(code.toLatin1().data(), Py_single_input, PyModule_GetDict(_context), PyModule_GetDict(_context))); 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); }