Path: blob/devel/ElmerGUI/PythonQt/src/gui/PythonQtScriptingConsole.cpp
5160 views
/*1*2* Copyright (C) 2006 MeVis Research GmbH All Rights Reserved.3*4* This library is free software; you can redistribute it and/or5* modify it under the terms of the GNU Lesser General Public6* License as published by the Free Software Foundation; either7* version 2.1 of the License, or (at your option) any later version.8*9* This library is distributed in the hope that it will be useful,10* but WITHOUT ANY WARRANTY; without even the implied warranty of11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU12* Lesser General Public License for more details.13*14* Further, this software is distributed without any warranty that it is15* free of the rightful claim of any third person regarding infringement16* or the like. Any license provided herein, whether implied or17* otherwise, applies only to this software file. Patent licenses, if18* any, provided herein do not apply to combinations of this program with19* other software, or any other product whatsoever.20*21* You should have received a copy of the GNU Lesser General Public22* License along with this library; if not, write to the Free Software23* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA24*25* Contact information: MeVis Research GmbH, Universitaetsallee 29,26* 28359 Bremen, Germany or:27*28* http://www.mevis.de29*30*/3132//----------------------------------------------------------------------------------33/*!34// \file PythonQtScriptingConsole.cpp35// \author Florian Link36// \author Last changed by $Author: florian $37// \date 2006-1038*/39//----------------------------------------------------------------------------------4041#include "PythonQtScriptingConsole.h"4243#include <QApplication>44#include <QCompleter>45#include <QDebug>46#include <QKeyEvent>47#include <QMenu>48#include <QMouseEvent>49#include <QScrollBar>50#include <QStringListModel>51#include <QTextBlock>52#include <QTextCursor>53#include <QTextDocumentFragment>545556//-----------------------------------------------------------------------------5758PythonQtScriptingConsole::PythonQtScriptingConsole(59QWidget *parent, const PythonQtObjectPtr &context,60Qt::WindowFlags windowFlags)61: QTextEdit(parent) {6263setWindowFlags(windowFlags);6465_defaultTextCharacterFormat = currentCharFormat();66_context = context;67_historyPosition = 0;6869_completer = new QCompleter(this);70_completer->setWidget(this);71QObject::connect(_completer, SIGNAL(activated(const QString &)), this,72SLOT(insertCompletion(const QString &)));7374clear();7576connect(PythonQt::self(), SIGNAL(pythonStdOut(const QString &)), this,77SLOT(stdOut(const QString &)));78connect(PythonQt::self(), SIGNAL(pythonStdErr(const QString &)), this,79SLOT(stdErr(const QString &)));80}8182//-----------------------------------------------------------------------------8384void PythonQtScriptingConsole::stdOut(const QString &s) {85_stdOut += s;86int idx;87while ((idx = _stdOut.indexOf('\n')) != -1) {88consoleMessage(_stdOut.left(idx));89std::cout << _stdOut.left(idx).toLatin1().data() << std::endl;90_stdOut = _stdOut.mid(idx + 1);91}92}9394void PythonQtScriptingConsole::stdErr(const QString &s) {95_stdErr += s;96int idx;97while ((idx = _stdErr.indexOf('\n')) != -1) {98consoleMessage(_stdErr.left(idx));99std::cout << _stdErr.left(idx).toLatin1().data() << std::endl;100_stdErr = _stdErr.mid(idx + 1);101}102}103104void PythonQtScriptingConsole::flushStdOut() {105if (!_stdOut.isEmpty()) {106stdOut("\n");107}108if (!_stdErr.isEmpty()) {109stdErr("\n");110}111}112113//-----------------------------------------------------------------------------114115PythonQtScriptingConsole::~PythonQtScriptingConsole() {}116117//-----------------------------------------------------------------------------118119void PythonQtScriptingConsole::clear() {120121QTextEdit::clear();122appendCommandPrompt();123}124125//-----------------------------------------------------------------------------126127void PythonQtScriptingConsole::executeLine(bool storeOnly) {128QTextCursor textCursor = this->textCursor();129textCursor.movePosition(QTextCursor::End);130131// Select the text from the command prompt until the end of the block132// and get the selected text.133textCursor.setPosition(commandPromptPosition());134textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);135QString code = textCursor.selectedText();136137// i don't know where this trailing space is coming from, blast it!138if (code.endsWith(" ")) {139code.truncate(code.length() - 1);140}141142if (!code.isEmpty()) {143// Update the history144_history << code;145_historyPosition = _history.count();146_currentMultiLineCode += code + "\n";147148if (!storeOnly) {149executeCode(_currentMultiLineCode);150_currentMultiLineCode = "";151}152}153// Insert a new command prompt154appendCommandPrompt(storeOnly);155}156157void PythonQtScriptingConsole::executeCode(const QString &code) {158// put visible cursor to the end of the line159QTextCursor cursor = QTextEdit::textCursor();160cursor.movePosition(QTextCursor::End);161setTextCursor(cursor);162163int cursorPosition = this->textCursor().position();164165// evaluate the code166_stdOut = "";167_stdErr = "";168PythonQtObjectPtr p;169p.setNewRef(PyRun_String(code.toLatin1().data(), Py_single_input,170PyModule_GetDict(_context),171PyModule_GetDict(_context)));172if (!p) {173PythonQt::self()->handleError();174}175176flushStdOut();177178bool messageInserted = (this->textCursor().position() != cursorPosition);179180// If a message was inserted, then put another empty line before the command181// prompt to improve readability.182if (messageInserted) {183append(QString());184}185}186187//-----------------------------------------------------------------------------188189void PythonQtScriptingConsole::appendCommandPrompt(bool storeOnly) {190if (storeOnly) {191_commandPrompt = "...> ";192} else {193_commandPrompt = "py> ";194}195append(_commandPrompt);196197QTextCursor cursor = textCursor();198cursor.movePosition(QTextCursor::End);199setTextCursor(cursor);200}201202//-----------------------------------------------------------------------------203204void PythonQtScriptingConsole::setCurrentFont(const QColor &color, bool bold) {205206QTextCharFormat charFormat(_defaultTextCharacterFormat);207208QFont font(charFormat.font());209font.setBold(bold);210charFormat.setFont(font);211212QBrush brush(charFormat.foreground());213brush.setColor(color);214charFormat.setForeground(brush);215216setCurrentCharFormat(charFormat);217}218219//-----------------------------------------------------------------------------220221int PythonQtScriptingConsole::commandPromptPosition() {222223QTextCursor textCursor(this->textCursor());224textCursor.movePosition(QTextCursor::End);225226return textCursor.block().position() + _commandPrompt.length();227}228229//-----------------------------------------------------------------------------230231void PythonQtScriptingConsole::insertCompletion(const QString &completion) {232QTextCursor tc = textCursor();233tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);234if (tc.selectedText() == ".") {235tc.insertText(QString(".") + completion);236} else {237tc = textCursor();238tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);239tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);240tc.insertText(completion);241setTextCursor(tc);242}243}244245//-----------------------------------------------------------------------------246void PythonQtScriptingConsole::handleTabCompletion() {247QTextCursor textCursor = this->textCursor();248int pos = textCursor.position();249textCursor.setPosition(commandPromptPosition());250textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);251int startPos = textCursor.selectionStart();252253int offset = pos - startPos;254QString text = textCursor.selectedText();255256QString textToComplete;257int cur = offset;258while (cur--) {259QChar c = text.at(cur);260if (c.isLetterOrNumber() || c == '.' || c == '_') {261textToComplete.prepend(c);262} else {263break;264}265}266267QString lookup;268QString compareText = textToComplete;269int dot = compareText.lastIndexOf('.');270if (dot != -1) {271lookup = compareText.mid(0, dot);272compareText = compareText.mid(dot + 1, offset);273}274if (!lookup.isEmpty() || !compareText.isEmpty()) {275compareText = compareText.toLower();276QStringList found;277QStringList l =278PythonQt::self()->introspection(_context, lookup, PythonQt::Anything);279foreach (QString n, l) {280if (n.toLower().startsWith(compareText)) {281found << n;282}283}284285if (!found.isEmpty()) {286_completer->setCompletionPrefix(compareText);287_completer->setCompletionMode(QCompleter::PopupCompletion);288_completer->setModel(new QStringListModel(found, _completer));289_completer->setCaseSensitivity(Qt::CaseInsensitive);290QTextCursor c = this->textCursor();291c.movePosition(QTextCursor::StartOfWord);292QRect cr = cursorRect(c);293cr.setWidth(_completer->popup()->sizeHintForColumn(0) +294_completer->popup()->verticalScrollBar()->sizeHint().width());295cr.translate(0, 8);296_completer->complete(cr);297} else {298_completer->popup()->hide();299}300} else {301_completer->popup()->hide();302}303}304305void PythonQtScriptingConsole::keyPressEvent(QKeyEvent *event) {306307if (_completer && _completer->popup()->isVisible()) {308// The following keys are forwarded by the completer to the widget309switch (event->key()) {310case Qt::Key_Return:311if (!_completer->popup()->currentIndex().isValid()) {312insertCompletion(_completer->currentCompletion());313_completer->popup()->hide();314event->accept();315}316event->ignore();317return;318break;319case Qt::Key_Enter:320case Qt::Key_Escape:321case Qt::Key_Tab:322case Qt::Key_Backtab:323324event->ignore();325return; // let the completer do default behavior326default:327break;328}329}330bool eventHandled = false;331QTextCursor textCursor = this->textCursor();332333int key = event->key();334switch (key) {335336case Qt::Key_Left:337338// Moving the cursor left is limited to the position339// of the command prompt.340341if (textCursor.position() <= commandPromptPosition()) {342343QApplication::beep();344eventHandled = true;345}346break;347348case Qt::Key_Up:349350// Display the previous command in the history351if (_historyPosition > 0) {352_historyPosition--;353changeHistory();354}355356eventHandled = true;357break;358359case Qt::Key_Down:360361// Display the next command in the history362if (_historyPosition + 1 < _history.count()) {363_historyPosition++;364changeHistory();365}366367eventHandled = true;368break;369370case Qt::Key_Return:371372executeLine(event->modifiers() & Qt::ShiftModifier);373eventHandled = true;374break;375376case Qt::Key_Backspace:377378if (textCursor.hasSelection()) {379380cut();381eventHandled = true;382383} else {384385// Intercept backspace key event to check if386// deleting a character is allowed. It is not387// allowed, if the user wants to delete the388// command prompt.389390if (textCursor.position() <= commandPromptPosition()) {391392QApplication::beep();393eventHandled = true;394}395}396break;397398case Qt::Key_Delete:399400cut();401eventHandled = true;402break;403404default:405406if (key >= Qt::Key_Space && key <= Qt::Key_division) {407408if (textCursor.hasSelection() && !verifySelectionBeforeDeletion()) {409410// The selection must not be deleted.411eventHandled = true;412413} else {414415// The key is an input character, check if the cursor is416// behind the last command prompt, else inserting the417// character is not allowed.418419int commandPromptPosition = this->commandPromptPosition();420if (textCursor.position() < commandPromptPosition) {421422textCursor.setPosition(commandPromptPosition);423setTextCursor(textCursor);424}425}426}427}428429if (eventHandled) {430431_completer->popup()->hide();432event->accept();433434} else {435436QTextEdit::keyPressEvent(event);437QString text = event->text();438if (!text.isEmpty()) {439handleTabCompletion();440} else {441_completer->popup()->hide();442}443eventHandled = true;444}445}446447//-----------------------------------------------------------------------------448449void PythonQtScriptingConsole::cut() {450451bool deletionAllowed = verifySelectionBeforeDeletion();452if (deletionAllowed) {453QTextEdit::cut();454}455}456457//-----------------------------------------------------------------------------458459bool PythonQtScriptingConsole::verifySelectionBeforeDeletion() {460461bool deletionAllowed = true;462463QTextCursor textCursor = this->textCursor();464465int commandPromptPosition = this->commandPromptPosition();466int selectionStart = textCursor.selectionStart();467int selectionEnd = textCursor.selectionEnd();468469if (textCursor.hasSelection()) {470471// Selected text may only be deleted after the last command prompt.472// If the selection is partly after the command prompt set the selection473// to the part and deletion is allowed. If the selection occurs before the474// last command prompt, then deletion is not allowed.475476if (selectionStart < commandPromptPosition ||477selectionEnd < commandPromptPosition) {478479// Assure selectionEnd is bigger than selection start480if (selectionStart > selectionEnd) {481int tmp = selectionEnd;482selectionEnd = selectionStart;483selectionStart = tmp;484}485486if (selectionEnd < commandPromptPosition) {487488// Selection is completely before command prompt,489// so deletion is not allowed.490QApplication::beep();491deletionAllowed = false;492493} else {494495// The selectionEnd is after the command prompt, so set496// the selection start to the commandPromptPosition.497selectionStart = commandPromptPosition;498textCursor.setPosition(selectionStart);499textCursor.setPosition(selectionStart, QTextCursor::KeepAnchor);500setTextCursor(textCursor);501}502}503504} else { // if (hasSelectedText())505506// When there is no selected text, deletion is not allowed before the507// command prompt.508if (textCursor.position() < commandPromptPosition) {509510QApplication::beep();511deletionAllowed = false;512}513}514515return deletionAllowed;516}517518//-----------------------------------------------------------------------------519520void PythonQtScriptingConsole::changeHistory() {521522// Select the text after the last command prompt ...523QTextCursor textCursor = this->textCursor();524textCursor.movePosition(QTextCursor::End);525textCursor.setPosition(commandPromptPosition(), QTextCursor::KeepAnchor);526527// ... and replace it with the history text.528textCursor.insertText(_history.value(_historyPosition));529530textCursor.movePosition(QTextCursor::End);531setTextCursor(textCursor);532}533534//-----------------------------------------------------------------------------535536void PythonQtScriptingConsole::consoleMessage(const QString &message) {537538append(QString());539insertPlainText(message);540541// Reset all font modifications done by the html string542setCurrentCharFormat(_defaultTextCharacterFormat);543}544545546