2010-01-10 00:10:23 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
|
|
|
# Some portions of code were taken from managerR plugin.
|
|
|
|
|
|
|
|
"""
|
|
|
|
Implementation of interactive Python console widget for QGIS.
|
|
|
|
|
|
|
|
Has +- the same behaviour as command-line interactive console:
|
|
|
|
- runs commands one by one
|
|
|
|
- supports expressions that span through more lines
|
|
|
|
- has command history, accessible using up/down keys
|
|
|
|
- supports pasting of commands
|
|
|
|
|
|
|
|
TODO:
|
|
|
|
- configuration - init commands, font, ...
|
2010-02-10 19:03:27 +00:00
|
|
|
- python code highlighting
|
2010-01-10 11:14:46 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
from PyQt4.QtCore import *
|
|
|
|
from PyQt4.QtGui import *
|
2011-01-05 23:26:18 +00:00
|
|
|
from qgis.utils import iface
|
2010-01-10 00:10:23 +00:00
|
|
|
import sys
|
2010-01-10 11:14:46 +00:00
|
|
|
import traceback
|
|
|
|
import code
|
2010-01-10 00:10:23 +00:00
|
|
|
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
_init_commands = ["from qgis.core import *", "import qgis.utils"]
|
|
|
|
|
|
|
|
_console = None
|
2010-01-10 00:10:23 +00:00
|
|
|
|
|
|
|
def show_console():
|
|
|
|
""" called from QGIS to open the console """
|
|
|
|
global _console
|
|
|
|
if _console is None:
|
2011-01-05 23:26:18 +00:00
|
|
|
_console = PythonConsole(iface.mainWindow())
|
|
|
|
_console.show() # force show even if it was restored as hidden
|
|
|
|
else:
|
|
|
|
_console.setVisible(not _console.isVisible())
|
|
|
|
# set focus to the edit box so the user can start typing
|
|
|
|
if _console.isVisible():
|
|
|
|
_console.activateWindow()
|
|
|
|
_console.edit.setFocus()
|
2011-01-05 04:43:34 +00:00
|
|
|
|
2011-01-05 23:26:18 +00:00
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
_old_stdout = sys.stdout
|
2010-01-10 11:14:46 +00:00
|
|
|
_console_output = None
|
|
|
|
|
2011-01-05 23:26:18 +00:00
|
|
|
|
2011-01-05 23:32:29 +00:00
|
|
|
def clearConsole():
|
|
|
|
global _console
|
|
|
|
if _console is None:
|
|
|
|
return
|
|
|
|
_console.edit.clearConsole()
|
|
|
|
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
# hook for python console so all output will be redirected
|
|
|
|
# and then shown in console
|
|
|
|
def console_displayhook(obj):
|
|
|
|
global _console_output
|
|
|
|
_console_output = obj
|
2010-01-10 00:10:23 +00:00
|
|
|
|
|
|
|
class QgisOutputCatcher:
|
|
|
|
def __init__(self):
|
|
|
|
self.data = ''
|
|
|
|
def write(self, stuff):
|
|
|
|
self.data += stuff
|
|
|
|
def get_and_clean_data(self):
|
|
|
|
tmp = self.data
|
|
|
|
self.data = ''
|
|
|
|
return tmp
|
|
|
|
def flush(self):
|
|
|
|
pass
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
sys.stdout = QgisOutputCatcher()
|
2010-01-10 00:10:23 +00:00
|
|
|
|
2011-01-05 23:26:18 +00:00
|
|
|
class PythonConsole(QDockWidget):
|
2010-01-10 00:10:23 +00:00
|
|
|
def __init__(self, parent=None):
|
2011-01-05 23:26:18 +00:00
|
|
|
QDockWidget.__init__(self, parent)
|
|
|
|
self.setObjectName("Python Console")
|
|
|
|
self.setAllowedAreas(Qt.BottomDockWidgetArea)
|
|
|
|
self.widget = QWidget()
|
|
|
|
self.l = QVBoxLayout(self.widget)
|
2010-01-10 11:14:46 +00:00
|
|
|
self.edit = PythonEdit()
|
|
|
|
self.l.addWidget(self.edit)
|
2011-01-05 23:26:18 +00:00
|
|
|
self.setWidget(self.widget)
|
2011-01-05 04:43:23 +00:00
|
|
|
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
|
2011-01-05 23:26:18 +00:00
|
|
|
# try to restore position from stored main window state
|
|
|
|
if not iface.mainWindow().restoreDockWidget(self):
|
|
|
|
iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self)
|
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
def sizeHint(self):
|
|
|
|
return QSize(500,300)
|
2010-01-10 00:10:23 +00:00
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
QWidget.closeEvent(self, event)
|
|
|
|
|
|
|
|
|
2010-02-10 19:03:27 +00:00
|
|
|
class ConsoleHighlighter(QSyntaxHighlighter):
|
|
|
|
EDIT_LINE, ERROR, OUTPUT, INIT = range(4)
|
|
|
|
def __init__(self, doc):
|
|
|
|
QSyntaxHighlighter.__init__(self,doc)
|
|
|
|
formats = { self.OUTPUT : Qt.black,
|
|
|
|
self.ERROR : Qt.red,
|
|
|
|
self.EDIT_LINE : Qt.darkGreen,
|
|
|
|
self.INIT : Qt.gray }
|
|
|
|
self.f = {}
|
|
|
|
for tag, color in formats.iteritems():
|
|
|
|
self.f[tag] = QTextCharFormat()
|
|
|
|
self.f[tag].setForeground(color)
|
|
|
|
|
|
|
|
def highlightBlock(self, txt):
|
|
|
|
size = txt.length()
|
|
|
|
state = self.currentBlockState()
|
|
|
|
if state == self.OUTPUT or state == self.ERROR or state == self.INIT:
|
|
|
|
self.setFormat(0,size, self.f[state])
|
|
|
|
# highlight prompt only
|
|
|
|
if state == self.EDIT_LINE:
|
|
|
|
self.setFormat(0,3, self.f[self.EDIT_LINE])
|
|
|
|
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
class PythonEdit(QTextEdit, code.InteractiveInterpreter):
|
|
|
|
|
|
|
|
def __init__(self,parent=None):
|
|
|
|
QTextEdit.__init__(self, parent)
|
|
|
|
code.InteractiveInterpreter.__init__(self, locals=None)
|
|
|
|
|
|
|
|
self.setTextInteractionFlags(Qt.TextEditorInteraction)
|
|
|
|
self.setAcceptDrops(False)
|
|
|
|
self.setMinimumSize(30, 30)
|
|
|
|
self.setUndoRedoEnabled(False)
|
|
|
|
self.setAcceptRichText(False)
|
2010-05-04 12:48:59 +00:00
|
|
|
monofont = QFont("Monospace")
|
|
|
|
monofont.setStyleHint(QFont.TypeWriter)
|
2010-01-11 00:13:57 +00:00
|
|
|
self.setFont(monofont)
|
2010-01-10 11:14:46 +00:00
|
|
|
|
|
|
|
self.buffer = []
|
2011-01-05 23:32:29 +00:00
|
|
|
|
|
|
|
self.insertInitText()
|
2010-01-10 11:14:46 +00:00
|
|
|
|
|
|
|
for line in _init_commands:
|
|
|
|
self.runsource(line)
|
|
|
|
|
|
|
|
self.displayPrompt(False)
|
|
|
|
|
|
|
|
self.history = QStringList()
|
|
|
|
self.historyIndex = 0
|
|
|
|
|
2010-02-10 19:03:27 +00:00
|
|
|
self.high = ConsoleHighlighter(self)
|
2011-01-05 23:32:29 +00:00
|
|
|
|
|
|
|
def insertInitText(self):
|
|
|
|
self.insertTaggedText(QCoreApplication.translate("PythonConsole", "To access Quantum GIS environment from this console\n"
|
|
|
|
"use qgis.utils.iface object (instance of QgisInterface class).\n\n"),
|
|
|
|
ConsoleHighlighter.INIT)
|
|
|
|
|
|
|
|
|
|
|
|
def clearConsole(self):
|
|
|
|
self.clear()
|
|
|
|
self.insertInitText()
|
2010-01-10 11:14:46 +00:00
|
|
|
|
|
|
|
def displayPrompt(self, more=False):
|
|
|
|
self.currentPrompt = "... " if more else ">>> "
|
|
|
|
self.currentPromptLength = len(self.currentPrompt)
|
2010-02-10 19:03:27 +00:00
|
|
|
self.insertTaggedLine(self.currentPrompt, ConsoleHighlighter.EDIT_LINE)
|
2010-01-10 11:14:46 +00:00
|
|
|
self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
|
|
|
|
|
|
|
|
def isCursorInEditionZone(self):
|
|
|
|
cursor = self.textCursor()
|
|
|
|
pos = cursor.position()
|
|
|
|
block = self.document().lastBlock()
|
|
|
|
last = block.position() + self.currentPromptLength
|
|
|
|
return pos >= last
|
|
|
|
|
|
|
|
def currentCommand(self):
|
|
|
|
block = self.cursor.block()
|
|
|
|
text = block.text()
|
|
|
|
return text.right(text.length()-self.currentPromptLength)
|
|
|
|
|
|
|
|
def showPrevious(self):
|
|
|
|
if self.historyIndex < len(self.history) and not self.history.isEmpty():
|
|
|
|
self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
|
|
|
|
self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
|
|
|
|
self.cursor.removeSelectedText()
|
|
|
|
self.cursor.insertText(self.currentPrompt)
|
|
|
|
self.historyIndex += 1
|
|
|
|
if self.historyIndex == len(self.history):
|
|
|
|
self.insertPlainText("")
|
|
|
|
else:
|
|
|
|
self.insertPlainText(self.history[self.historyIndex])
|
|
|
|
|
|
|
|
def showNext(self):
|
|
|
|
if self.historyIndex > 0 and not self.history.isEmpty():
|
|
|
|
self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
|
|
|
|
self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
|
|
|
|
self.cursor.removeSelectedText()
|
|
|
|
self.cursor.insertText(self.currentPrompt)
|
|
|
|
self.historyIndex -= 1
|
|
|
|
if self.historyIndex == len(self.history):
|
|
|
|
self.insertPlainText("")
|
|
|
|
else:
|
|
|
|
self.insertPlainText(self.history[self.historyIndex])
|
|
|
|
|
|
|
|
def updateHistory(self, command):
|
|
|
|
if isinstance(command, QStringList):
|
|
|
|
for line in command:
|
|
|
|
self.history.append(line)
|
|
|
|
elif not command == "":
|
|
|
|
if len(self.history) <= 0 or \
|
|
|
|
not command == self.history[-1]:
|
|
|
|
self.history.append(command)
|
|
|
|
self.historyIndex = len(self.history)
|
|
|
|
|
|
|
|
def keyPressEvent(self, e):
|
|
|
|
self.cursor = self.textCursor()
|
|
|
|
# if the cursor isn't in the edition zone, don't do anything except Ctrl+C
|
|
|
|
if not self.isCursorInEditionZone():
|
2010-02-01 19:10:15 +00:00
|
|
|
if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier:
|
2010-01-10 11:14:46 +00:00
|
|
|
if e.key() == Qt.Key_C or e.key() == Qt.Key_A:
|
|
|
|
QTextEdit.keyPressEvent(self, e)
|
|
|
|
else:
|
|
|
|
# all other keystrokes get sent to the input line
|
|
|
|
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
|
|
|
|
else:
|
|
|
|
# if Return is pressed, then perform the commands
|
|
|
|
if e.key() == Qt.Key_Return:
|
|
|
|
self.entered()
|
|
|
|
# if Up or Down is pressed
|
|
|
|
elif e.key() == Qt.Key_Down:
|
|
|
|
self.showPrevious()
|
|
|
|
elif e.key() == Qt.Key_Up:
|
|
|
|
self.showNext()
|
|
|
|
# if backspace is pressed, delete until we get to the prompt
|
|
|
|
elif e.key() == Qt.Key_Backspace:
|
|
|
|
if not self.cursor.hasSelection() and self.cursor.columnNumber() == self.currentPromptLength:
|
|
|
|
return
|
|
|
|
QTextEdit.keyPressEvent(self, e)
|
|
|
|
# if the left key is pressed, move left until we get to the prompt
|
|
|
|
elif e.key() == Qt.Key_Left and self.cursor.position() > self.document().lastBlock().position() + self.currentPromptLength:
|
2010-02-01 19:10:15 +00:00
|
|
|
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor
|
|
|
|
move = QTextCursor.WordLeft if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Left
|
|
|
|
self.cursor.movePosition(move, anchor)
|
2010-01-10 11:14:46 +00:00
|
|
|
# use normal operation for right key
|
|
|
|
elif e.key() == Qt.Key_Right:
|
2010-02-01 19:10:15 +00:00
|
|
|
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor
|
|
|
|
move = QTextCursor.WordRight if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Right
|
|
|
|
self.cursor.movePosition(move, anchor)
|
2010-01-10 11:14:46 +00:00
|
|
|
# if home is pressed, move cursor to right of prompt
|
|
|
|
elif e.key() == Qt.Key_Home:
|
2010-02-01 19:10:15 +00:00
|
|
|
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor
|
2010-01-10 11:14:46 +00:00
|
|
|
self.cursor.movePosition(QTextCursor.StartOfBlock, anchor, 1)
|
|
|
|
self.cursor.movePosition(QTextCursor.Right, anchor, self.currentPromptLength)
|
|
|
|
# use normal operation for end key
|
|
|
|
elif e.key() == Qt.Key_End:
|
2010-02-01 19:10:15 +00:00
|
|
|
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor
|
2010-01-10 11:14:46 +00:00
|
|
|
self.cursor.movePosition(QTextCursor.EndOfBlock, anchor, 1)
|
|
|
|
# use normal operation for all remaining keys
|
|
|
|
else:
|
|
|
|
QTextEdit.keyPressEvent(self, e)
|
|
|
|
self.setTextCursor(self.cursor)
|
|
|
|
self.ensureCursorVisible()
|
|
|
|
|
|
|
|
def insertFromMimeData(self, source):
|
|
|
|
self.cursor = self.textCursor()
|
|
|
|
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1)
|
|
|
|
self.setTextCursor(self.cursor)
|
|
|
|
if source.hasText():
|
|
|
|
pasteList = QStringList()
|
|
|
|
pasteList = source.text().split("\n")
|
2011-03-23 20:29:32 +00:00
|
|
|
# with multi-line text also run the commands
|
|
|
|
for line in pasteList[:-1]:
|
|
|
|
self.insertPlainText(line)
|
2011-04-05 13:50:24 +00:00
|
|
|
self.runCommand(unicode(self.currentCommand()))
|
2011-03-23 20:29:32 +00:00
|
|
|
# last line: only paste the text, do not run it
|
|
|
|
self.insertPlainText(unicode(pasteList[-1]))
|
2010-01-10 11:14:46 +00:00
|
|
|
|
|
|
|
def entered(self):
|
|
|
|
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
|
2010-02-01 19:10:15 +00:00
|
|
|
self.setTextCursor(self.cursor)
|
2010-01-10 11:14:46 +00:00
|
|
|
self.runCommand( unicode(self.currentCommand()) )
|
|
|
|
|
2010-02-10 19:03:27 +00:00
|
|
|
def insertTaggedText(self, txt, tag):
|
|
|
|
|
|
|
|
if len(txt) > 0 and txt[-1] == '\n': # remove trailing newline to avoid one more empty line
|
|
|
|
txt = txt[0:-1]
|
|
|
|
|
|
|
|
c = self.textCursor()
|
|
|
|
for line in txt.split('\n'):
|
|
|
|
b = c.block()
|
|
|
|
b.setUserState(tag)
|
|
|
|
c.insertText(line)
|
|
|
|
c.insertBlock()
|
|
|
|
|
|
|
|
def insertTaggedLine(self, txt, tag):
|
|
|
|
c = self.textCursor()
|
|
|
|
b = c.block()
|
|
|
|
b.setUserState(tag)
|
|
|
|
c.insertText(txt)
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
def runCommand(self, cmd):
|
|
|
|
|
|
|
|
self.updateHistory(cmd)
|
|
|
|
|
|
|
|
self.insertPlainText("\n")
|
|
|
|
|
|
|
|
self.buffer.append(cmd)
|
|
|
|
src = "\n".join(self.buffer)
|
|
|
|
more = self.runsource(src, "<input>")
|
|
|
|
if not more:
|
|
|
|
self.buffer = []
|
|
|
|
|
|
|
|
output = sys.stdout.get_and_clean_data()
|
|
|
|
if output:
|
2010-02-10 19:03:27 +00:00
|
|
|
self.insertTaggedText(output, ConsoleHighlighter.OUTPUT)
|
2010-01-10 11:14:46 +00:00
|
|
|
self.displayPrompt(more)
|
|
|
|
|
|
|
|
def write(self, txt):
|
|
|
|
""" reimplementation from code.InteractiveInterpreter """
|
2010-02-10 19:03:27 +00:00
|
|
|
self.insertTaggedText(txt, ConsoleHighlighter.ERROR)
|
2010-01-10 11:14:46 +00:00
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
a = QApplication(sys.argv)
|
2010-01-10 11:14:46 +00:00
|
|
|
show_console()
|
2010-01-10 00:10:23 +00:00
|
|
|
a.exec_()
|