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, ...
|
|
|
|
- syntax highlighting
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
from PyQt4.QtCore import *
|
|
|
|
from PyQt4.QtGui import *
|
|
|
|
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"]
|
|
|
|
|
2010-01-10 00:10:23 +00:00
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
_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:
|
2010-01-10 11:14:46 +00:00
|
|
|
_console = PythonConsole()
|
2010-01-10 00:10:23 +00:00
|
|
|
_console.show()
|
|
|
|
_console.raise_()
|
|
|
|
_console.setWindowState( _console.windowState() & ~Qt.WindowMinimized )
|
|
|
|
_console.activateWindow()
|
2010-01-10 11:14:46 +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
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
class PythonConsole(QWidget):
|
2010-01-10 00:10:23 +00:00
|
|
|
def __init__(self, parent=None):
|
|
|
|
QWidget.__init__(self, parent)
|
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
self.edit = PythonEdit()
|
|
|
|
self.l = QVBoxLayout()
|
|
|
|
self.l.addWidget(self.edit)
|
|
|
|
self.setLayout(self.l)
|
|
|
|
self.setWindowTitle("Python console")
|
2010-01-10 00:10:23 +00:00
|
|
|
|
2010-01-10 11:14:46 +00:00
|
|
|
s = QSettings()
|
|
|
|
self.restoreGeometry(s.value("/python/console/geometry").toByteArray())
|
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):
|
2010-01-10 11:14:46 +00:00
|
|
|
s = QSettings()
|
|
|
|
s.setValue("/python/console/geometry", QVariant(self.saveGeometry()))
|
2010-01-10 00:10:23 +00:00
|
|
|
QWidget.closeEvent(self, event)
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
#monofont = QFont("Bitstream Vera Sans Mono", 10)
|
|
|
|
#self.setFont(monofont)
|
|
|
|
|
|
|
|
self.buffer = []
|
|
|
|
|
|
|
|
self.insertPlainText("To access Quantum GIS environment from this console\n"
|
|
|
|
"use qgis.utils.iface object (instance of QgisInterface class).\n"
|
|
|
|
"\n")
|
|
|
|
|
|
|
|
for line in _init_commands:
|
|
|
|
self.runsource(line)
|
|
|
|
|
|
|
|
self.displayPrompt(False)
|
|
|
|
|
|
|
|
self.history = QStringList()
|
|
|
|
self.historyIndex = 0
|
|
|
|
|
|
|
|
#from pythonhigh import PythonHighlighter
|
|
|
|
#self.high = PythonHighlighter(self)
|
|
|
|
|
|
|
|
def displayPrompt(self, more=False):
|
|
|
|
self.currentPrompt = "... " if more else ">>> "
|
|
|
|
self.currentPromptLength = len(self.currentPrompt)
|
|
|
|
self.insertPlainText(self.currentPrompt)
|
|
|
|
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():
|
|
|
|
if e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier:
|
|
|
|
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:
|
|
|
|
if e.modifiers() == Qt.ShiftModifier:
|
|
|
|
anchor = QTextCursor.KeepAnchor
|
|
|
|
else:
|
|
|
|
anchor = QTextCursor.MoveAnchor
|
|
|
|
if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
|
|
|
|
self.cursor.movePosition(QTextCursor.WordLeft, anchor)
|
|
|
|
else:
|
|
|
|
self.cursor.movePosition(QTextCursor.Left, anchor)
|
|
|
|
# use normal operation for right key
|
|
|
|
elif e.key() == Qt.Key_Right:
|
|
|
|
if e.modifiers() == Qt.ShiftModifier:
|
|
|
|
anchor = QTextCursor.KeepAnchor
|
|
|
|
else:
|
|
|
|
anchor = QTextCursor.MoveAnchor
|
|
|
|
if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
|
|
|
|
self.cursor.movePosition(QTextCursor.WordRight, anchor)
|
|
|
|
else:
|
|
|
|
self.cursor.movePosition(QTextCursor.Right, anchor)
|
|
|
|
# if home is pressed, move cursor to right of prompt
|
|
|
|
elif e.key() == Qt.Key_Home:
|
|
|
|
if e.modifiers() == Qt.ShiftModifier:
|
|
|
|
anchor = QTextCursor.KeepAnchor
|
|
|
|
else:
|
|
|
|
anchor = QTextCursor.MoveAnchor
|
|
|
|
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:
|
|
|
|
if e.modifiers() == Qt.ShiftModifier:
|
|
|
|
anchor = QTextCursor.KeepAnchor
|
|
|
|
else:
|
|
|
|
anchor = QTextCursor.MoveAnchor
|
|
|
|
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")
|
|
|
|
for line in pasteList:
|
|
|
|
self.insertPlainText(line)
|
|
|
|
self.runCommand(unicode(line))
|
|
|
|
|
|
|
|
def entered(self):
|
|
|
|
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
|
|
|
|
self.runCommand( unicode(self.currentCommand()) )
|
|
|
|
|
|
|
|
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:
|
|
|
|
self.insertPlainText(output)
|
|
|
|
self.displayPrompt(more)
|
|
|
|
|
|
|
|
def write(self, txt):
|
|
|
|
""" reimplementation from code.InteractiveInterpreter """
|
|
|
|
self.insertPlainText(txt)
|
|
|
|
|
|
|
|
|
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_()
|