From cf272f88017308d2ee6b0e59ccad8b2e22fa9e1f Mon Sep 17 00:00:00 2001 From: Salvatore Larosa Date: Mon, 10 Sep 2012 19:16:37 +0200 Subject: [PATCH] New Python Console --- python/CMakeLists.txt | 6 + python/console.py | 517 +++++++++++------------- python/console_sci.py | 456 +++++++++++++++++++++ python/help.py | 37 ++ python/helpConsole/CMakeLists.txt | 2 + python/helpConsole/help.htm | 59 +++ python/iconConsole/CMakeLists.txt | 12 + python/iconConsole/iconClearConsole.png | Bin 0 -> 1225 bytes python/iconConsole/iconHelpConsole.png | Bin 0 -> 1816 bytes python/iconConsole/iconOpenConsole.png | Bin 0 -> 1187 bytes python/iconConsole/iconRunConsole.png | Bin 0 -> 3853 bytes python/iconConsole/iconSaveConsole.png | Bin 0 -> 1243 bytes python/iconConsole/iconTempConsole.png | Bin 0 -> 1958 bytes python/iconConsole/imgHelpDialog.png | Bin 0 -> 3643 bytes 14 files changed, 799 insertions(+), 290 deletions(-) create mode 100644 python/console_sci.py create mode 100644 python/help.py create mode 100644 python/helpConsole/CMakeLists.txt create mode 100644 python/helpConsole/help.htm create mode 100644 python/iconConsole/CMakeLists.txt create mode 100644 python/iconConsole/iconClearConsole.png create mode 100644 python/iconConsole/iconHelpConsole.png create mode 100644 python/iconConsole/iconOpenConsole.png create mode 100644 python/iconConsole/iconRunConsole.png create mode 100644 python/iconConsole/iconSaveConsole.png create mode 100644 python/iconConsole/iconTempConsole.png create mode 100644 python/iconConsole/imgHelpDialog.png 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 0000000000000000000000000000000000000000..9c0fd5ab34bbfbd9719867e56146f170ea91c64c GIT binary patch literal 1225 zcmV;)1UCDLP)sxD99b!^xB85$wH&%?K8gaoy?NY;4*=RmqC61M~ogFT?uv@bMPdy@-4T<4Xn0+KY9>T`c^Hmc+vhSsJU zn73jq?*ND!P;>^`JguR4^}=8H>vI>c+cUB0h5V_j4QH7{e))6i-liI64;OEEfP-f> zT-gJ%UT=__^+s7;UJ!4oUmKU9D(P!|TV6df=k|$Os)qAV){W94*Dfeo4y#~s3LJ1` z!kcxKGOJ*`&>Mz8y&*NNLj=N30s*Vqd2!GF1@E$S)E1R2ht)te{OVjt-jba&o3rwV zgf7-ZIB=7Qs)?wgp#{%KgWhe*Enc(a^lnG(@oH=UC+bE30BpNs5vA|b6ICPOa3oAT z>P06933z*m#RNiHh2p<1Lqo^NDyXQiKQdwGZ=1-NTLGswj{q2k40aP!LcUH_F%Sy& zqbfZlr5c+%g0Zd(b%w+r{{bCCu>Il00@n6IMCM|m3I8T zK|+B6ga~4B*a(N>-0s)V>js1n2D+O!$q6%8<~^Ff=+voQmJLyfX+*#?Ymjk`7A4pf zS7WJCi3ITWxJen8i4qE8OUuL8?ZKL2#AHnq?yKKoOU^-WPFbm{Oc@aX(Cy`z_tu#% zOLDQ$q6!|jA5HOKNt#4V?ZKX2h|k?D$36Zs$r(koU)a)K-_-X)*=pK^zyt_!pZX?1 zJ-Uq}lMBnA4f!r2gi%I@+J$rKv$FltagjN_6km5YO1L}h>FoM_`6uEX?~n`v8lV7* z{yx{&1lSBZ&ODLrS(ThIUuU#PqN+nkVGv2_PtxCZo&jI*!s)Y}wH52w5K_=3-j+-# zF75*`C#2u;8Ow@F3bz=oIp}nQ^xtwZ=;z$NdjIwVjoj$!!w*D literal 0 HcmV?d00001 diff --git a/python/iconConsole/iconHelpConsole.png b/python/iconConsole/iconHelpConsole.png new file mode 100644 index 0000000000000000000000000000000000000000..33ad3f91dbc098ba4c78b3caf28ba362660a823e GIT binary patch literal 1816 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 B5`q8# literal 0 HcmV?d00001 diff --git a/python/iconConsole/iconRunConsole.png b/python/iconConsole/iconRunConsole.png new file mode 100644 index 0000000000000000000000000000000000000000..73d6a48e7c10bdf5096f765f6b1dac6b6f1ea7a6 GIT binary patch literal 3853 zcmV+o5AyJdP)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGY`Tzg~`T;|EsUrXY00(qQO+^RX2?-S}EXXuH*#H0pKS@ME zR7l5_R$YizRTN$Oes{*1$?+$&Qpv%PFiRBd2g!V}B1|EA5e-Crks|svqnGfZhicNG zhopK40*M$3Dx>gW#t_F8u|E?cqEyf*LLI-4neX0n_F6rh`;9Z`CwXCW&%Kwk);?=( z_P&PyaXtRVmz@oEO_7J1+(C)%Ue2CAz4_UW2XX&v0~mgF{Z;qcwSXVpec)C(x59l{ z6lcrhG&+sWQTO<_=s!w)fB4Of=h|VkC0MuXU@sN-(R~5wr%0}%JLnN4)7=Tu4?$cL zA{)ow*>VL=){ej#rixrCVO25Kcu4f0MTbbpt9y!-?p%H~Z~(~L5r}T0Y7u%Nt972H zv(M>B9#ABWbfn-d?Qq%>&|Q)Q`g0InWKAUKo+P5TB!WmyO77f?u(@w1g8K%3!qN3h zld1iKvq;V%y;~$a#ub;r93n>veM9;@lAs(;q6~7?pI?SXtqagaiDv+e>h#mdTi|Q^ z4{Q~pmQBmsj|KmlL@s9cM(XVZ8Uk$70t3@UA+1X0yGg-A##NH>br z>45GjUWdX|Fu+&>US?wX0Ds>wC4c1g!JP8jMOvkEC4)9->b zr$jP!R_qT?29vK4dZU`ANONNtx3So?QpovfA z-eC#=qa`0AOfeTbvb-zQV;$WiB&t$OhGynp4ZU(S;6mr1WK~ZQzz9%)T4WJW&_wX` zF4kcvvX1$Sf;&%%F=6y%adTUkqvBF3Q5Cq zji%n>Dhgob@;^opYcmA_YGqdes7&R^B0YNncm?dGpi(d&%I+K<%K+fp6&v?^u)UOn z?nXL_h#EkpOp64uXL@?aK9IB+ROhm} zBj@0uR_U3tQ#7M*gZy96Gs$6cf!-y=+(47*yh(>_`N0d5=D~`WKE`*uo|w?Ew#|{_ zj4|Yuk(GhrEEF9KmdY`9n|!m~HT?CYhWF3;BjNcvd3H>!qgAw5@LQ>-><$tf^6YHX-z!~hVAk&2rvH;Lx5s5!$lcxGFddk zI$LshM7N#1zM;=wA49h2i*%Zkt8b9XUXy0XWCqj)T{2}2TQbaR{h9m+S|}D>7+)l} P00000NkvXXu0mjfvF3P)e}ag(71Ui8 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 literal 0 HcmV?d00001 diff --git a/python/iconConsole/imgHelpDialog.png b/python/iconConsole/imgHelpDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..bdfe1f6d0c05f6642a7b30bef7bffb94efb385db GIT binary patch literal 3643 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