diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 1e7b9323a0f..a7b2646e86c 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -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}) + diff --git a/python/console.py b/python/console.py index e38e5fab9a4..607a3594dec 100755 --- a/python/console.py +++ b/python/console.py @@ -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, "") - 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_() diff --git a/python/console_sci.py b/python/console_sci.py new file mode 100644 index 00000000000..7c6e20c6eed --- /dev/null +++ b/python/console_sci.py @@ -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, "") + 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) \ No newline at end of file diff --git a/python/help.py b/python/help.py new file mode 100644 index 00000000000..5acffe67847 --- /dev/null +++ b/python/help.py @@ -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() diff --git a/python/helpConsole/CMakeLists.txt b/python/helpConsole/CMakeLists.txt new file mode 100644 index 00000000000..d20276f9c9f --- /dev/null +++ b/python/helpConsole/CMakeLists.txt @@ -0,0 +1,2 @@ +FILE(GLOB HTML_FILES *.htm) +INSTALL(FILES ${HTML_FILES} DESTINATION ${QGIS_PYTHON_DIR}/helpConsole) diff --git a/python/helpConsole/help.htm b/python/helpConsole/help.htm new file mode 100644 index 00000000000..12d0bf6da57 --- /dev/null +++ b/python/helpConsole/help.htm @@ -0,0 +1,59 @@ + + + + Help Python Console + + + + + + + + +
+ + +

Python Console for QGIS

+
+

+ 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. +

+ The following is a description of the tools in the toolbar: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Tool to clear python console
Tool to import iface class
Tool to open a python script and load in console
Tool to save a python script
This! ;-)
Run commnand (like Enter key pressed)
+ + diff --git a/python/iconConsole/CMakeLists.txt b/python/iconConsole/CMakeLists.txt new file mode 100644 index 00000000000..1a71b90be3d --- /dev/null +++ b/python/iconConsole/CMakeLists.txt @@ -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) diff --git a/python/iconConsole/iconClearConsole.png b/python/iconConsole/iconClearConsole.png new file mode 100644 index 00000000000..9c0fd5ab34b Binary files /dev/null and b/python/iconConsole/iconClearConsole.png differ diff --git a/python/iconConsole/iconHelpConsole.png b/python/iconConsole/iconHelpConsole.png new file mode 100644 index 00000000000..33ad3f91dbc Binary files /dev/null and b/python/iconConsole/iconHelpConsole.png differ diff --git a/python/iconConsole/iconOpenConsole.png b/python/iconConsole/iconOpenConsole.png new file mode 100644 index 00000000000..1c3bdc30791 Binary files /dev/null and b/python/iconConsole/iconOpenConsole.png differ diff --git a/python/iconConsole/iconRunConsole.png b/python/iconConsole/iconRunConsole.png new file mode 100644 index 00000000000..73d6a48e7c1 Binary files /dev/null and b/python/iconConsole/iconRunConsole.png differ diff --git a/python/iconConsole/iconSaveConsole.png b/python/iconConsole/iconSaveConsole.png new file mode 100644 index 00000000000..78c4d3f6e4e Binary files /dev/null and b/python/iconConsole/iconSaveConsole.png differ diff --git a/python/iconConsole/iconTempConsole.png b/python/iconConsole/iconTempConsole.png new file mode 100644 index 00000000000..179072302be Binary files /dev/null and b/python/iconConsole/iconTempConsole.png differ diff --git a/python/iconConsole/imgHelpDialog.png b/python/iconConsole/imgHelpDialog.png new file mode 100644 index 00000000000..bdfe1f6d0c0 Binary files /dev/null and b/python/iconConsole/imgHelpDialog.png differ