New Python Console

This commit is contained in:
Salvatore Larosa 2012-09-10 19:16:37 +02:00
parent 51606a6631
commit cf272f8801
14 changed files with 799 additions and 290 deletions

View File

@ -97,6 +97,9 @@ SET (QGIS_PYTHON_DIR ${PYTHON_SITE_PACKAGES_DIR}/qgis)
ADD_CUSTOM_TARGET(compile_python_files ALL)
ADD_SUBDIRECTORY(iconConsole)
ADD_SUBDIRECTORY(helpConsole)
ADD_CUSTOM_COMMAND(TARGET compile_python_files
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${QGIS_PYTHON_OUTPUT_DIRECTORY}
@ -115,3 +118,6 @@ ENDFOREACH(file)
PYTHON_INSTALL(__init__.py ${QGIS_PYTHON_DIR})
PYTHON_INSTALL(utils.py ${QGIS_PYTHON_DIR})
PYTHON_INSTALL(console.py ${QGIS_PYTHON_DIR})
PYTHON_INSTALL(console_sci.py ${QGIS_PYTHON_DIR})
PYTHON_INSTALL(help.py ${QGIS_PYTHON_DIR})

View File

@ -1,36 +1,32 @@
# -*- coding: utf-8 -*-
# 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.
# -*- coding:utf-8 -*-
"""
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, ...
- python code highlighting
/***************************************************************************
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 qgis.utils import iface
from console_sci import PythonEdit
from help import HelpDialog
import sys
import traceback
import code
_init_commands = ["from qgis.core import *", "import qgis.utils"]
import os
_console = None
@ -47,286 +43,227 @@ def show_console():
_console.activateWindow()
_console.edit.setFocus()
_old_stdout = sys.stdout
_console_output = None
def clearConsole():
global _console
if _console is None:
return
_console.edit.clearConsole()
# 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
global _console_output
_console_output = obj
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
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
sys.stdout = QgisOutputCatcher()
class PythonConsole(QDockWidget):
def __init__(self, parent=None):
QDockWidget.__init__(self, parent)
self.setObjectName("Python Console")
self.setAllowedAreas(Qt.BottomDockWidgetArea)
self.widget = QWidget()
self.l = QVBoxLayout(self.widget)
self.l.setMargin(0)
self.edit = PythonEdit()
self.l.addWidget(self.edit)
self.setWidget(self.widget)
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
# try to restore position from stored main window state
if not iface.mainWindow().restoreDockWidget(self):
iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self)
def sizeHint(self):
return QSize(500,300)
def closeEvent(self, event):
QWidget.closeEvent(self, event)
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)
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])
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])
class PythonConsole(QDockWidget):
def __init__(self, parent=None):
QDockWidget.__init__(self, parent)
self.setObjectName("Python Console")
#self.setAllowedAreas(Qt.BottomDockWidgetArea)
self.widgetButton = QWidget()
self.widgetEdit = QWidget()
self.toolBar = QToolBar()
self.toolBar.setEnabled(True)
#self.toolBar.setFont(font)
self.toolBar.setFocusPolicy(Qt.NoFocus)
self.toolBar.setContextMenuPolicy(Qt.DefaultContextMenu)
self.toolBar.setLayoutDirection(Qt.LeftToRight)
self.toolBar.setIconSize(QSize(24, 24))
self.toolBar.setOrientation(Qt.Vertical)
self.toolBar.setMovable(True)
self.toolBar.setFloatable(True)
#self.toolBar.setAllowedAreas(Qt.LeftToolBarArea)
#self.toolBar.setAllowedAreas(Qt.RightToolBarArea)
#self.toolBar.setObjectName(_fromUtf8("toolMappa"))
self.b = QVBoxLayout(self.widgetButton)
self.e = QHBoxLayout(self.widgetEdit)
self.e.setMargin(0)
self.b.setMargin(0)
## Action for Clear button
self.clearButton = QAction(parent)
self.clearButton.setCheckable(False)
self.clearButton.setEnabled(True)
self.clearButton.setIcon(QIcon("icon/iconClearConsole.png"))
self.clearButton.setMenuRole(QAction.PreferencesRole)
self.clearButton.setIconVisibleInMenu(True)
self.clearButton.setToolTip('Clear console')
## Action for paste snippets code
self.clearButton = QAction(parent)
self.clearButton.setCheckable(False)
self.clearButton.setEnabled(True)
self.clearButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconClearConsole.png"))
self.clearButton.setMenuRole(QAction.PreferencesRole)
self.clearButton.setIconVisibleInMenu(True)
self.clearButton.setToolTip('Clear console')
## Action for paste snippets code
# self.currentLayerButton = QAction(parent)
# self.currentLayerButton.setCheckable(False)
# self.currentLayerButton.setEnabled(True)
# self.currentLayerButton.setIcon(QIcon("icon/iconTempConsole.png"))
# self.currentLayerButton.setMenuRole(QAction.PreferencesRole)
# self.currentLayerButton.setIconVisibleInMenu(True)
##
self.loadIfaceButton = QAction(parent)
self.loadIfaceButton.setCheckable(False)
self.loadIfaceButton.setEnabled(True)
self.loadIfaceButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconTempConsole.png"))
self.loadIfaceButton.setMenuRole(QAction.PreferencesRole)
self.loadIfaceButton.setIconVisibleInMenu(True)
self.loadIfaceButton.setToolTip('Import iface class')
## Action for Open File
self.openFileButton = QAction(parent)
self.openFileButton.setCheckable(False)
self.openFileButton.setEnabled(True)
self.openFileButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconOpenConsole.png"))
self.openFileButton.setMenuRole(QAction.PreferencesRole)
self.openFileButton.setIconVisibleInMenu(True)
self.openFileButton.setToolTip('Open script file')
## Action for Save File
self.saveFileButton = QAction(parent)
self.saveFileButton.setCheckable(False)
self.saveFileButton.setEnabled(True)
self.saveFileButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconSaveConsole.png"))
self.saveFileButton.setMenuRole(QAction.PreferencesRole)
self.saveFileButton.setIconVisibleInMenu(True)
self.saveFileButton.setToolTip('Save to file')
## Action for Run script
self.runButton = QAction(parent)
self.runButton.setCheckable(False)
self.runButton.setEnabled(True)
self.runButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconRunConsole.png"))
self.runButton.setMenuRole(QAction.PreferencesRole)
self.runButton.setIconVisibleInMenu(True)
self.runButton.setToolTip('Run command')
## Help action
self.helpButton = QAction(parent)
self.helpButton.setCheckable(False)
self.helpButton.setEnabled(True)
self.helpButton.setIcon(QIcon(os.path.dirname(__file__) + "/iconConsole/iconHelpConsole.png"))
self.helpButton.setMenuRole(QAction.PreferencesRole)
self.helpButton.setIconVisibleInMenu(True)
self.helpButton.setToolTip('Help')
self.toolBar.addAction(self.clearButton)
#self.toolBar.addAction(self.currentLayerButton)
self.toolBar.addAction(self.loadIfaceButton)
self.toolBar.addAction(self.openFileButton)
self.toolBar.addAction(self.saveFileButton)
self.toolBar.addAction(self.helpButton)
self.toolBar.addAction(self.runButton)
self.b.addWidget(self.toolBar)
self.edit = PythonEdit()
self.setWidget(self.widgetEdit)
self.e.addWidget(self.widgetButton)
self.e.addWidget(self.edit)
self.edit.setFocus()
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
self.clearButton.activated.connect(self.edit.clearConsole)
#self.currentLayerButton.activated.connect(self.cLayer)
self.loadIfaceButton.activated.connect(self.iface)
self.runButton.activated.connect(self.edit.entered)
self.openFileButton.activated.connect(self.openScriptFile)
self.saveFileButton.activated.connect(self.saveScriptFile)
self.helpButton.activated.connect(self.openHelp)
# try to restore position from stored main window state
if not iface.mainWindow().restoreDockWidget(self):
iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self)
def cLayer(self):
self.edit.commandConsole('cLayer')
def iface(self):
self.edit.commandConsole('iface')
def openScriptFile(self):
scriptFile = QFileDialog.getOpenFileName(
self, "Open File", "", "Script file (*.py)")
if scriptFile.isEmpty() == False:
oF = open(scriptFile, 'r')
listScriptFile = []
for line in oF:
if line != "\n":
listScriptFile.append(line)
self.edit.insertTextFromFile(listScriptFile)
def saveScriptFile(self):
scriptFile = QFileDialog()
scriptFile.setDefaultSuffix(".py")
fName = scriptFile.getSaveFileName(
self, "Save file", QString(), "Script file (*.py)")
if fName.isEmpty() == False:
filename = str(fName)
if not filename.endswith(".py"):
fName += ".py"
sF = open(fName,'w')
listText = self.edit.getTextFromEditor()
is_first_line = True
for s in listText:
if s[0:3] in (">>>", "..."):
s.replace(">>> ", "")
s.replace("... ", "")
if is_first_line:
# see, no write() in this branch
is_first_line = False
else:
# we've just written a line; add a newline
sF.write('\n')
sF.write(s)
sF.close()
class PythonEdit(QTextEdit, code.InteractiveInterpreter):
def openHelp(self):
dlg = HelpDialog()
dlg.exec_()
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("Monospace")
monofont.setStyleHint(QFont.TypeWriter)
self.setFont(monofont)
self.buffer = []
self.insertInitText()
for line in _init_commands:
self.runsource(line)
self.displayPrompt(False)
self.history = QStringList()
self.historyIndex = 0
self.high = ConsoleHighlighter(self)
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()
def displayPrompt(self, more=False):
self.currentPrompt = "... " if more else ">>> "
self.currentPromptLength = len(self.currentPrompt)
self.insertTaggedLine(self.currentPrompt, ConsoleHighlighter.EDIT_LINE)
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 or e.key() == Qt.Key_Enter:
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:
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)
# use normal operation for right key
elif e.key() == Qt.Key_Right:
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)
# if home is pressed, move cursor to right of prompt
elif e.key() == Qt.Key_Home:
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else 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:
anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else 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()
if source.hasText():
pasteList = QStringList()
pasteList = source.text().split("\n")
# move the cursor to the end only if the text is multi-line or is going to be pasted not into the last line
if (len(pasteList) > 1) or (not self.isCursorInEditionZone()):
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1)
self.setTextCursor(self.cursor)
# with multi-line text also run the commands
for line in pasteList[:-1]:
self.insertPlainText(line)
self.runCommand(unicode(self.currentCommand()))
# last line: only paste the text, do not run it
self.insertPlainText(unicode(pasteList[-1]))
def entered(self):
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
self.setTextCursor(self.cursor)
self.runCommand( unicode(self.currentCommand()) )
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)
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.insertTaggedText(output, ConsoleHighlighter.OUTPUT)
self.displayPrompt(more)
def write(self, txt):
""" reimplementation from code.InteractiveInterpreter """
self.insertTaggedText(txt, ConsoleHighlighter.ERROR)
def closeEvent(self, event):
QWidget.closeEvent(self, event)
if __name__ == '__main__':
a = QApplication(sys.argv)
show_console()
a.exec_()
a = QApplication(sys.argv)
show_console()
a.exec_()

456
python/console_sci.py Normal file
View File

@ -0,0 +1,456 @@
# -*- 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)

37
python/help.py Normal file
View File

@ -0,0 +1,37 @@
from PyQt4 import QtCore, QtGui, QtWebKit
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os
class HelpDialog(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self.setModal(True)
self.setupUi()
def setupUi(self):
self.resize(500, 300)
self.webView = QtWebKit.QWebView()
self.setWindowTitle("Help Python Console")
self.verticalLayout= QtGui.QVBoxLayout()
self.verticalLayout.setSpacing(2)
self.verticalLayout.setMargin(0)
self.verticalLayout.addWidget(self.webView)
self.closeButton = QtGui.QPushButton()
self.closeButton.setText("Close")
self.closeButton.setMaximumWidth(150)
self.horizontalLayout= QtGui.QHBoxLayout()
self.horizontalLayout.setSpacing(2)
self.horizontalLayout.setMargin(0)
self.horizontalLayout.addStretch(1000)
self.horizontalLayout.addWidget(self.closeButton)
QObject.connect(self.closeButton, QtCore.SIGNAL("clicked()"), self.closeWindow)
self.verticalLayout.addLayout(self.horizontalLayout)
self.setLayout(self.verticalLayout)
filename = os.path.dirname(__file__) + "/helpConsole/help.htm"
url = QtCore.QUrl(filename)
self.webView.load(url)
def closeWindow(self):
self.close()

View File

@ -0,0 +1,2 @@
FILE(GLOB HTML_FILES *.htm)
INSTALL(FILES ${HTML_FILES} DESTINATION ${QGIS_PYTHON_DIR}/helpConsole)

View File

@ -0,0 +1,59 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Help Python Console</title>
<style>
body{
font-family: verdana,arial,helvetica,sans-serif;
/*font-family:Verdana,Geneva,sans-serif;*/
font-size : 12px;
}
</style>
</head>
<body>
<table>
<tr>
<td>
<img src="../iconConsole/imgHelpDialog.png" />
</td>
<td>
<h2>Python Console for QGIS</h2>
</td>
</tr>
</table>
<p align="justify">
To access Quantum GIS environment from this console
use qgis.utils.iface object (instance of QgisInterface class).
To import the class QgisInterface can also use the dedicated
button on the toolbar on the left.
<br><br>
The following is a description of the tools in the toolbar:
</p>
<table width="100%" bordercolor="#000" border="1">
<tr>
<td><img src="../iconConsole/iconClearConsole.png" /></td>
<td>Tool to clear python console</td>
</tr>
<tr>
<td><img src="../iconConsole/iconTempConsole.png" /></td>
<td>Tool to import iface class</td>
</tr>
<tr>
<td><img src="../iconConsole/iconOpenConsole.png" /></td>
<td>Tool to open a python script and load in console</td>
</tr>
<tr>
<td><img src="../iconConsole/iconSaveConsole.png" /></td>
<td>Tool to save a python script</td>
</tr>
<tr>
<td><img src="../iconConsole/iconHelpConsole.png" /></td>
<td>This! ;-)</td>
</tr>
<tr>
<td><img src="../iconConsole/iconRunConsole.png" /></td>
<td>Run commnand (like Enter key pressed)</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,12 @@
SET(ICON_FILES
iconClearConsole.png
iconOpenConsole.png
iconRunConsole.png
iconTempConsole.png
iconSaveConsole.png
iconHelpConsole.png
imgHelpDialog.png
)
FILE(GLOB ICON_FILES *.png)
INSTALL(FILES ${ICON_FILES} DESTINATION ${QGIS_PYTHON_DIR}/iconConsole)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB