QGIS/python/console_sci.py
2012-09-10 19:16:37 +02:00

456 lines
17 KiB
Python

# -*- coding:utf-8 -*-
"""
/***************************************************************************
Python Conosle for QGIS
-------------------
begin : 2012-09-xx
copyright : (C) 2012 by Salvatore Larosa
email : lrssvtml (at) gmail (dot) com
***************************************************************************/
/***************************************************************************
* *
* 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 https://code.google.com/p/pydee/
"""
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qsci import *
from PyQt4.Qsci import QsciScintilla, QsciScintillaBase, QsciLexerPython
#from qgis.utils import iface
import sys
import traceback
import code
_init_commands = ["from qgis.core import *", "import qgis.utils"]
_console = None
_old_stdout = sys.stdout
_console_output = None
class PythonEdit(QsciScintilla, code.InteractiveInterpreter):
def __init__(self, parent=None):
#QsciScintilla.__init__(self, parent)
super(PythonEdit,self).__init__(parent)
code.InteractiveInterpreter.__init__(self, locals=None)
self.current_prompt_pos = None
self.new_input_line = True
self.setMarginWidth(0, 0)
self.setMarginWidth(1, 0)
self.setMarginWidth(2, 0)
self.buffer = []
self.insertInitText()
self.setCursorPosition(4,4)
self.displayPrompt(False)
for line in _init_commands:
self.runsource(line)
self.history = QStringList()
self.historyIndex = 0
# Brace matching: enable for a brace immediately before or after
# the current position
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
#self.moveToMatchingBrace()
#self.selectToMatchingBrace()
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
self.setCaretWidth(2)
# Set Python lexer
# Set style for Python comments (style number 1) to a fixed-width
# courier.
self.setLexers(True)
# Indentation
#self.setAutoIndent(True)
#self.setIndentationsUseTabs(False)
#self.setIndentationWidth(4)
#self.setTabIndents(True)
#self.setBackspaceUnindents(True)
#self.setTabWidth(4)
self.setAutoCompletionThreshold(1)
self.setAutoCompletionSource(self.AcsAPIs)
# Don't want to see the horizontal scrollbar at all
# Use raw message to Scintilla here (all messages are documented
# here: http://www.scintilla.org/ScintillaDoc.html)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
# not too small
#self.setMinimumSize(500, 300)
self.SendScintilla(QsciScintilla.SCI_SETWRAPMODE, 1)
self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
def clearConsole(self):
"""Clear the contents of the console."""
self.setText('')
self.insertInitText()
self.displayPrompt(False)
self.setFocus()
def commandConsole(self, command):
line, pos = self.getCurLine()
selCmd= self.text(line).length()
self.setSelection(line, 4, line, selCmd)
self.removeSelectedText()
if command == "iface":
"""Import QgisInterface class"""
self.append('from qgis.utils import iface')
self.move_cursor_to_end()
elif command == "cLayer":
"""Retrive current Layer from map camvas"""
self.append('cLayer = iface.mapCanvas().currentLayer()')
self.move_cursor_to_end()
self.setFocus()
def setLexers(self, lexer):
if lexer:
font = QFont()
font.setFamily('Courier New') ## Courier New
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
self.lexer = QsciLexerPython()
self.lexer.setDefaultFont(font)
#self.lexer.setDefaultFont(QFont('Mono', 10, 0, False))
#self.lexer.setDefaultColor(Qt.darkGray)
self.lexer.setColor(Qt.red, 1)
self.lexer.setColor(Qt.darkGreen, 5)
self.lexer.setColor(Qt.darkBlue, 15)
self.lexer.setFont(font, 1)
self.lexer.setFont(font, 3)
self.lexer.setFont(font, 4)
self.api = QsciAPIs(self.lexer)
self.api.load("API/PyQGIS_1.8.api")
self.api.load("API/osgeo_gdal-ogr_1.9.1-1.api")
self.api.prepare()
self.lexer.setAPIs(self.api)
self.setLexer(self.lexer)
## TODO: show completion list for file and directory
# def show_completion_list(self, completions, text):
# """Private method to display the possible completions"""
# if len(completions) == 0:
# return
# if len(completions) > 1:
# self.showUserList(1, QStringList(sorted(completions)))
# self.completion_chars = 1
# else:
# txt = completions[0]
# if text != "":
# txt = txt.replace(text, "")
# self.insert(txt)
# self.completion_chars = 0
#
# def show_file_completion(self):
# """Display a completion list for files and directories"""
# cwd = os.getcwdu()
# self.show_completion_list(self.listdir_fullpath('/'), cwd)
#
# def listdir_fullpath(self, d):
# return [os.path.join(d, f) for f in os.listdir(d)]
def insertInitText(self):
#self.setLexers(False)
txtInit = ("## To access Quantum GIS environment from this console\n"
"## use qgis.utils.iface object (instance of QgisInterface class).\n\n")
initText = self.setText(txtInit)
def getCurrentPos(self):
""" Get the position (as an int) of the cursor.
getCursorPosition() returns a (linenr, index) tuple.
"""
return self.SendScintilla(self.SCI_GETCURRENTPOS)
def getText(self):
""" Get the text as a unicode string. """
value = self.getBytes().decode('utf-8')
# print (value) printing can give an error because the console font
# may not have all unicode characters
return value
def getBytes(self):
""" Get the text as bytes (utf-8 encoded). This is how
the data is stored internally. """
len = self.SendScintilla(self.SCI_GETLENGTH)+1
bb = QByteArray(len,'0')
N = self.SendScintilla(self.SCI_GETTEXT, len, bb)
return bytes(bb)[:-1]
def getTextLength(self):
return self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
def getLine(self, linenr):
""" Get the bytes on the given line number. """
len = self.SendScintilla(QsciScintilla.SCI_LINELENGTH)+1
bb = QByteArray(len,'0')
N = self.SendScintilla(QsciScintilla.SCI_GETLINE, len, bb)
return bytes(bb)[:-1]
def getCurLine(self):
""" Get the current line (as a string) and the
position of the cursor in it. """
linenr, index = self.getCursorPosition()
#line = self.getLine(linenr) #.decode('utf-8')
return linenr, index
def get_end_pos(self):
"""Return (line, index) position of the last character"""
line = self.lines() - 1
return (line, self.text(line).length())
def is_cursor_at_end(self):
"""Return True if cursor is at the end of text"""
cline, cindex = self.getCursorPosition()
return (cline, cindex) == self.get_end_pos()
def move_cursor_to_end(self):
"""Move cursor to end of text"""
line, index = self.get_end_pos()
self.setCursorPosition(line, index)
self.ensureCursorVisible()
def on_new_line(self):
"""On new input line"""
self.move_cursor_to_end()
self.current_prompt_pos = self.getCursorPosition()
self.new_input_line = False
def is_cursor_on_last_line(self):
"""Return True if cursor is on the last line"""
cline, _ = self.getCursorPosition()
return cline == self.lines() - 1
def new_prompt(self, prompt):
"""
Print a new prompt and save its (line, index) position
"""
self.write(prompt, prompt=True)
# now we update our cursor giving end of prompt
self.current_prompt_pos = self.getCursorPosition()
self.ensureCursorVisible()
def check_selection(self):
"""
Check if selected text is r/w,
otherwise remove read-only parts of selection
"""
if self.current_prompt_pos is None:
self.move_cursor_to_end()
return
line_from, index_from, line_to, index_to = self.getSelection()
pline, pindex = self.current_prompt_pos
if line_from < pline or \
(line_from == pline and index_from < pindex):
self.setSelection(pline, pindex, line_to, index_to)
def displayPrompt(self, more=False):
self.append("... ") if more else self.append(">>> ")
self.move_cursor_to_end()
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 showPrevious(self):
if self.historyIndex < len(self.history) and not self.history.isEmpty():
line, pos = self.getCurLine()
selCmd= self.text(line).length()
self.setSelection(line, 4, line, selCmd)
self.removeSelectedText()
self.historyIndex += 1
if self.historyIndex == len(self.history):
self.insert("")
pass
else:
self.insert(self.history[self.historyIndex])
self.move_cursor_to_end()
#self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
def showNext(self):
if self.historyIndex > 0 and not self.history.isEmpty():
line, pos = self.getCurLine()
selCmd = self.text(line).length()
self.setSelection(line, 4, line, selCmd)
self.removeSelectedText()
self.historyIndex -= 1
if self.historyIndex == len(self.history):
self.insert("")
else:
self.insert(self.history[self.historyIndex])
self.move_cursor_to_end()
#self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
def keyPressEvent(self, e):
linenr, index = self.getCurLine()
if not self.is_cursor_on_last_line() or index < 4:
if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier:
if e.key() == Qt.Key_C or e.key() == Qt.Key_A:
QsciScintilla.keyPressEvent(self, e)
else:
# all other keystrokes get sent to the input line
self.move_cursor_to_end()
#pass
else:
if (e.key() == Qt.Key_Return or e.key() == Qt.Key_Enter) and not self.isListActive():
self.entered()
elif e.key() == Qt.Key_Backspace:
curPos, pos = self.getCursorPosition()
line = self.lines() -1
if curPos < line -1 or pos < 5:
return
#else:
#self.move_cursor_to_end()
QsciScintilla.keyPressEvent(self, e)
elif e.key() == Qt.Key_Delete:
if self.hasSelectedText():
self.check_selection()
self.removeSelectedText()
elif self.is_cursor_on_last_line():
self.SendScintilla(QsciScintilla.SCI_CLEAR)
e.accept()
elif e.key() == Qt.Key_Down and not self.isListActive():
self.showPrevious()
elif e.key() == Qt.Key_Up and not self.isListActive():
self.showNext()
elif e.key() == Qt.Key_Left:
e.accept()
if e.modifiers() & Qt.ShiftModifier:
if e.modifiers() & Qt.ControlModifier:
self.SendScintilla(QsciScintilla.SCI_WORDLEFTEXTEND)
else:
self.SendScintilla(QsciScintilla.SCI_CHARLEFTEXTEND)
else:
if e.modifiers() & Qt.ControlModifier:
self.SendScintilla(QsciScintilla.SCI_WORDLEFT)
else:
self.SendScintilla(QsciScintilla.SCI_CHARLEFT)
elif e.key() == Qt.Key_Right:
e.accept()
if e.modifiers() & Qt.ShiftModifier:
if e.modifiers() & Qt.ControlModifier:
self.SendScintilla(QsciScintilla.SCI_WORDRIGHTEXTEND)
else:
self.SendScintilla(QsciScintilla.SCI_CHARRIGHTEXTEND)
else:
if e.modifiers() & Qt.ControlModifier:
self.SendScintilla(QsciScintilla.SCI_WORDRIGHT)
else:
self.SendScintilla(QsciScintilla.SCI_CHARRIGHT)
## TODO: press event for auto-completion file directory
#elif e.key() == Qt.Key_Tab:
#self.show_file_completion()
#else:
#self.on_new_line()
else:
QsciScintilla.keyPressEvent(self, e)
def paste(self):
"""Reimplement QScintilla method"""
# Moving cursor to the end of the last line
self.move_cursor_to_end()
#QsciScintilla.paste(self)
QMessageBox.warning(self, "Python Console",
"Currently the action paste in console is not supported!")
return
## Drag and drop
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
stringDrag = e.mimeData().text()
pasteList = QStringList()
pasteList = stringDrag.split("\n")
for line in pasteList[:-1]:
self.append(line)
self.move_cursor_to_end()
#self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
self.runCommand(unicode(self.currentCommand()))
self.append(unicode(pasteList[-1]))
self.move_cursor_to_end()
def getTextFromEditor(self):
text = self.text()
textList = QStringList()
textList = text.split("\n")
return textList
def insertTextFromFile(self, listOpenFile):
for line in listOpenFile[:-1]:
self.append(line)
self.move_cursor_to_end()
self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
self.runCommand(unicode(self.currentCommand()))
self.append(unicode(listOpenFile[-1]))
self.move_cursor_to_end()
self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
def entered(self):
self.move_cursor_to_end()
self.runCommand( unicode(self.currentCommand()) )
self.setFocus()
#self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
def currentCommand(self):
linenr, index = self.getCurLine()
#for i in range(0, linenr):
txtLength = self.text(linenr).length()
string = self.text()
cmdLine = string.right(txtLength - 4)
cmd = str(cmdLine)
return cmd
def runCommand(self, cmd):
self.updateHistory(cmd)
self.SendScintilla(QsciScintilla.SCI_NEWLINE)
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.append(output)
self.move_cursor_to_end()
self.displayPrompt(more)
def write(self, txt):
self.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(txt), 1)
self.append(txt)
self.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(txt), 1)