mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-31 00:06:02 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			628 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			628 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding:utf-8 -*-
 | |
| """
 | |
| /***************************************************************************
 | |
| Python Console for QGIS
 | |
|                              -------------------
 | |
| begin                : 2012-09-10
 | |
| 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 qgis.PyQt.QtCore import Qt, QByteArray, QCoreApplication, QFile, QSize
 | |
| from qgis.PyQt.QtWidgets import QDialog, QMenu, QShortcut, QApplication
 | |
| from qgis.PyQt.QtGui import QKeySequence, QFontMetrics, QStandardItemModel, QStandardItem, QClipboard
 | |
| from qgis.PyQt.Qsci import QsciScintilla
 | |
| from qgis.gui import (
 | |
|     QgsCodeEditorPython,
 | |
|     QgsCodeEditorColorScheme
 | |
| )
 | |
| 
 | |
| import sys
 | |
| import os
 | |
| import code
 | |
| import codecs
 | |
| import re
 | |
| import traceback
 | |
| 
 | |
| from qgis.core import QgsApplication, QgsSettings, Qgis
 | |
| from qgis.gui import QgsCodeEditor
 | |
| 
 | |
| from .ui_console_history_dlg import Ui_HistoryDialogPythonConsole
 | |
| 
 | |
| _init_commands = ["import sys", "import os", "import re", "import math", "from qgis.core import *",
 | |
|                   "from qgis.gui import *", "from qgis.analysis import *", "from qgis._3d import *",
 | |
|                   "import processing", "import qgis.utils",
 | |
|                   "from qgis.utils import iface", "from qgis.PyQt.QtCore import *", "from qgis.PyQt.QtGui import *",
 | |
|                   "from qgis.PyQt.QtWidgets import *",
 | |
|                   "from qgis.PyQt.QtNetwork import *", "from qgis.PyQt.QtXml import *"]
 | |
| _historyFile = os.path.join(QgsApplication.qgisSettingsDirPath(), "console_history.txt")
 | |
| 
 | |
| 
 | |
| class ShellScintilla(QgsCodeEditorPython, code.InteractiveInterpreter):
 | |
| 
 | |
|     def __init__(self, parent=None):
 | |
|         super(QgsCodeEditorPython, self).__init__(parent)
 | |
|         code.InteractiveInterpreter.__init__(self, locals=None)
 | |
| 
 | |
|         self.parent = parent
 | |
| 
 | |
|         self.opening = ['(', '{', '[', "'", '"']
 | |
|         self.closing = [')', '}', ']', "'", '"']
 | |
| 
 | |
|         self.settings = QgsSettings()
 | |
| 
 | |
|         self.new_input_line = True
 | |
| 
 | |
|         self.buffer = []
 | |
|         self.continuationLine = False
 | |
| 
 | |
|         self.displayPrompt(self.continuationLine)
 | |
| 
 | |
|         for line in _init_commands:
 | |
|             try:
 | |
|                 self.runsource(line)
 | |
|             except ModuleNotFoundError:
 | |
|                 pass
 | |
| 
 | |
|         self.history = []
 | |
|         self.softHistory = ['']
 | |
|         self.softHistoryIndex = 0
 | |
|         # Read history command file
 | |
|         self.readHistoryFile()
 | |
| 
 | |
|         self.historyDlg = HistoryDialog(self)
 | |
| 
 | |
|         self.refreshSettingsShell()
 | |
| 
 | |
|         # 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.setWrapMode(QsciScintilla.WrapCharacter)
 | |
|         self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
 | |
| 
 | |
|         # Disable command key
 | |
|         ctrl, shift = self.SCMOD_CTRL << 16, self.SCMOD_SHIFT << 16
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L') + ctrl)
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('T') + ctrl)
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('D') + ctrl)
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('Z') + ctrl)
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('Y') + ctrl)
 | |
|         self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L') + ctrl + shift)
 | |
| 
 | |
|         # New QShortcut = ctrl+space/ctrl+alt+space for Autocomplete
 | |
|         self.newShortcutCSS = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Space), self)
 | |
|         self.newShortcutCAS = QShortcut(QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_Space), self)
 | |
|         self.newShortcutCSS.setContext(Qt.WidgetShortcut)
 | |
|         self.newShortcutCAS.setContext(Qt.WidgetShortcut)
 | |
|         self.newShortcutCAS.activated.connect(self.autoComplete)
 | |
|         self.newShortcutCSS.activated.connect(self.showHistory)
 | |
| 
 | |
|     def initializeLexer(self):
 | |
|         super().initializeLexer()
 | |
|         self.setCaretLineVisible(False)
 | |
|         self.setLineNumbersVisible(False)  # NO linenumbers for the input line
 | |
|         self.setFoldingVisible(False)
 | |
|         # Margin 1 is used for the '>>>' prompt (console input)
 | |
|         self.setMarginLineNumbers(1, True)
 | |
|         self.setMarginWidth(1, "00000")
 | |
|         self.setMarginType(1, 5)  # TextMarginRightJustified=5
 | |
|         self.setMarginsBackgroundColor(self.color(QgsCodeEditorColorScheme.ColorRole.Background))
 | |
|         self.setEdgeMode(QsciScintilla.EdgeNone)
 | |
| 
 | |
|     def _setMinimumHeight(self):
 | |
|         font = self.lexer().defaultFont(0)
 | |
|         fm = QFontMetrics(font)
 | |
| 
 | |
|         self.setMinimumHeight(fm.height() + 10)
 | |
| 
 | |
|     def refreshSettingsShell(self):
 | |
|         # Set Python lexer
 | |
|         self.initializeLexer()
 | |
| 
 | |
|         # Sets minimum height for input area based of font metric
 | |
|         self._setMinimumHeight()
 | |
| 
 | |
|     def showHistory(self):
 | |
|         if not self.historyDlg.isVisible():
 | |
|             self.historyDlg.show()
 | |
|         self.historyDlg._reloadHistory()
 | |
|         self.historyDlg.activateWindow()
 | |
| 
 | |
|     def commandConsole(self, commands):
 | |
|         if not self.is_cursor_on_last_line():
 | |
|             self.move_cursor_to_end()
 | |
|         for cmd in commands:
 | |
|             self.setText(cmd)
 | |
|             self.entered()
 | |
|         self.move_cursor_to_end()
 | |
|         self.setFocus()
 | |
| 
 | |
|     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')
 | |
|         self.SendScintilla(self.SCI_GETTEXT, len, bb)
 | |
|         return bytes(bb)[:-1]
 | |
| 
 | |
|     def getTextLength(self):
 | |
|         return self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
 | |
| 
 | |
|     def get_end_pos(self):
 | |
|         """Return (line, index) position of the last character"""
 | |
|         line = self.lines() - 1
 | |
|         return line, len(self.text(line))
 | |
| 
 | |
|     def is_cursor_at_start(self):
 | |
|         """Return True if cursor is at the end of text"""
 | |
|         cline, cindex = self.getCursorPosition()
 | |
|         return cline == 0 and cindex == 0
 | |
| 
 | |
|     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_start(self):
 | |
|         """Move cursor to start of text"""
 | |
|         self.setCursorPosition(0, 0)
 | |
|         self.ensureCursorVisible()
 | |
|         self.ensureLineVisible(0)
 | |
|         self.displayPrompt(self.continuationLine)
 | |
| 
 | |
|     def move_cursor_to_end(self):
 | |
|         """Move cursor to end of text"""
 | |
|         line, index = self.get_end_pos()
 | |
|         self.setCursorPosition(line, index)
 | |
|         self.ensureCursorVisible()
 | |
|         self.ensureLineVisible(line)
 | |
|         self.displayPrompt(self.continuationLine)
 | |
| 
 | |
|     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
 | |
|         line, index = self.getCursorPosition()
 | |
|         self.ensureCursorVisible()
 | |
|         self.ensureLineVisible(line)
 | |
| 
 | |
|     def displayPrompt(self, more=False):
 | |
|         self.SendScintilla(QsciScintilla.SCI_MARGINSETTEXT, 0, str.encode("..." if more else ">>>"))
 | |
| 
 | |
|     def syncSoftHistory(self):
 | |
|         self.softHistory = self.history[:]
 | |
|         self.softHistory.append('')
 | |
|         self.softHistoryIndex = len(self.softHistory) - 1
 | |
| 
 | |
|     def updateSoftHistory(self):
 | |
|         self.softHistory[self.softHistoryIndex] = self.text()
 | |
| 
 | |
|     def updateHistory(self, command, skipSoftHistory=False):
 | |
|         if isinstance(command, list):
 | |
|             for line in command:
 | |
|                 self.history.append(line)
 | |
|         elif not command == "":
 | |
|             if len(self.history) <= 0 or \
 | |
|                     command != self.history[-1]:
 | |
|                 self.history.append(command)
 | |
|         if not skipSoftHistory:
 | |
|             self.syncSoftHistory()
 | |
| 
 | |
|     def writeHistoryFile(self, fromCloseConsole=False):
 | |
|         ok = False
 | |
|         try:
 | |
|             wH = codecs.open(_historyFile, 'w', encoding='utf-8')
 | |
|             for s in self.history:
 | |
|                 wH.write(s + '\n')
 | |
|             ok = True
 | |
|         except:
 | |
|             raise
 | |
|         wH.close()
 | |
|         if ok and not fromCloseConsole:
 | |
|             msgText = QCoreApplication.translate('PythonConsole',
 | |
|                                                  'History saved successfully.')
 | |
|             self.parent.callWidgetMessageBar(msgText)
 | |
| 
 | |
|     def readHistoryFile(self):
 | |
|         fileExist = QFile.exists(_historyFile)
 | |
|         if fileExist:
 | |
|             with codecs.open(_historyFile, 'r', encoding='utf-8') as rH:
 | |
|                 for line in rH:
 | |
|                     if line != "\n":
 | |
|                         l = line.rstrip('\n')
 | |
|                         self.updateHistory(l, True)
 | |
|             self.syncSoftHistory()
 | |
|         else:
 | |
|             return
 | |
| 
 | |
|     def clearHistory(self, clearSession=False):
 | |
|         if clearSession:
 | |
|             self.history = []
 | |
|             self.syncSoftHistory()
 | |
|             msgText = QCoreApplication.translate('PythonConsole',
 | |
|                                                  'Session and file history cleared successfully.')
 | |
|             self.parent.callWidgetMessageBar(msgText)
 | |
|             return
 | |
|         ok = False
 | |
|         try:
 | |
|             cH = codecs.open(_historyFile, 'w', encoding='utf-8')
 | |
|             ok = True
 | |
|         except:
 | |
|             raise
 | |
|         cH.close()
 | |
|         if ok:
 | |
|             msgText = QCoreApplication.translate('PythonConsole',
 | |
|                                                  'History cleared successfully.')
 | |
|             self.parent.callWidgetMessageBar(msgText)
 | |
| 
 | |
|     def clearHistorySession(self):
 | |
|         self.clearHistory(True)
 | |
| 
 | |
|     def showPrevious(self):
 | |
|         if self.softHistoryIndex < len(self.softHistory) - 1 and self.softHistory:
 | |
|             self.softHistoryIndex += 1
 | |
|             self.setText(self.softHistory[self.softHistoryIndex])
 | |
|             self.move_cursor_to_end()
 | |
|             # self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
 | |
| 
 | |
|     def showNext(self):
 | |
|         if self.softHistoryIndex > 0 and self.softHistory:
 | |
|             self.softHistoryIndex -= 1
 | |
|             self.setText(self.softHistory[self.softHistoryIndex])
 | |
|             self.move_cursor_to_end()
 | |
|             # self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
 | |
| 
 | |
|     def keyPressEvent(self, e):
 | |
|         # update the live history
 | |
|         self.updateSoftHistory()
 | |
| 
 | |
|         startLine, startPos, endLine, endPos = self.getSelection()
 | |
| 
 | |
|         # handle invalid cursor position and multiline selections
 | |
|         if startLine < endLine:
 | |
|             # allow copying and selecting
 | |
|             if e.modifiers() & (Qt.ControlModifier | Qt.MetaModifier):
 | |
|                 if e.key() == Qt.Key_C:
 | |
|                     # only catch and return from Ctrl-C here if there's a selection
 | |
|                     if self.hasSelectedText():
 | |
|                         QsciScintilla.keyPressEvent(self, e)
 | |
|                         return
 | |
|                 elif e.key() == Qt.Key_A:
 | |
|                     QsciScintilla.keyPressEvent(self, e)
 | |
|                     return
 | |
|                 else:
 | |
|                     return
 | |
|             # allow selection
 | |
|             if e.modifiers() & Qt.ShiftModifier:
 | |
|                 if e.key() in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Home, Qt.Key_End):
 | |
|                     QsciScintilla.keyPressEvent(self, e)
 | |
|                 return
 | |
|             # all other keystrokes get sent to the input line
 | |
|             self.move_cursor_to_end()
 | |
| 
 | |
|         if e.modifiers() & (
 | |
|                 Qt.ControlModifier | Qt.MetaModifier) and e.key() == Qt.Key_C and not self.hasSelectedText():
 | |
|             # keyboard interrupt
 | |
|             sys.stdout.fire_keyboard_interrupt = True
 | |
|             return
 | |
| 
 | |
|         line, index = self.getCursorPosition()
 | |
|         cmd = self.text(line)
 | |
|         hasSelectedText = self.hasSelectedText()
 | |
| 
 | |
|         if e.key() in (Qt.Key_Return, Qt.Key_Enter) and not self.isListActive():
 | |
|             self.entered()
 | |
| 
 | |
|         elif e.key() in (Qt.Key_Left, Qt.Key_Home):
 | |
|             QsciScintilla.keyPressEvent(self, e)
 | |
| 
 | |
|         elif e.key() in (Qt.Key_Backspace, Qt.Key_Delete):
 | |
|             QsciScintilla.keyPressEvent(self, e)
 | |
|             self.recolor()
 | |
| 
 | |
|         elif (e.modifiers() & (Qt.ControlModifier | Qt.MetaModifier) and e.key() == Qt.Key_V) or \
 | |
|                 (e.modifiers() & Qt.ShiftModifier and e.key() == Qt.Key_Insert):
 | |
|             self.paste()
 | |
|             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()
 | |
| 
 | |
|         # TODO: press event for auto-completion file directory
 | |
|         else:
 | |
|             t = e.text()
 | |
|             self.autoCloseBracket = self.settings.value("pythonConsole/autoCloseBracket", False, type=bool)
 | |
|             self.autoImport = self.settings.value("pythonConsole/autoInsertionImport", True, type=bool)
 | |
|             # Close bracket automatically
 | |
|             if t in self.opening and self.autoCloseBracket:
 | |
|                 i = self.opening.index(t)
 | |
|                 if self.hasSelectedText() and startPos != 0:
 | |
|                     selText = self.selectedText()
 | |
|                     self.removeSelectedText()
 | |
|                     self.insert(self.opening[i] + selText + self.closing[i])
 | |
|                     self.setCursorPosition(endLine, endPos + 2)
 | |
|                     return
 | |
|                 elif t == '(' and (re.match(r'^[ \t]*def \w+$', cmd)
 | |
|                                    or re.match(r'^[ \t]*class \w+$', cmd)):
 | |
|                     self.insert('):')
 | |
|                 else:
 | |
|                     self.insert(self.closing[i])
 | |
|             # FIXES #8392 (automatically removes the redundant char
 | |
|             # when autoclosing brackets option is enabled)
 | |
|             elif t in [')', ']', '}'] and self.autoCloseBracket:
 | |
|                 try:
 | |
|                     if cmd[index - 1] in self.opening and t == cmd[index]:
 | |
|                         self.setCursorPosition(line, index + 1)
 | |
|                         self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
 | |
|                 except IndexError:
 | |
|                     pass
 | |
|             elif t == ' ' and self.autoImport:
 | |
|                 ptrn = r'^[ \t]*from [\w.]+$'
 | |
|                 if re.match(ptrn, cmd):
 | |
|                     self.insert(' import')
 | |
|                     self.setCursorPosition(line, index + 7)
 | |
|             QsciScintilla.keyPressEvent(self, e)
 | |
| 
 | |
|         self.displayPrompt(self.continuationLine)
 | |
| 
 | |
|     def contextMenuEvent(self, e):
 | |
|         menu = QMenu(self)
 | |
|         subMenu = QMenu(menu)
 | |
|         titleHistoryMenu = QCoreApplication.translate("PythonConsole", "Command History")
 | |
|         subMenu.setTitle(titleHistoryMenu)
 | |
|         subMenu.addAction(
 | |
|             QCoreApplication.translate("PythonConsole", "Show"),
 | |
|             self.showHistory, 'Ctrl+Shift+SPACE')
 | |
|         subMenu.addAction(
 | |
|             QCoreApplication.translate("PythonConsole", "Clear File"),
 | |
|             self.clearHistory)
 | |
|         subMenu.addAction(
 | |
|             QCoreApplication.translate("PythonConsole", "Clear Session"),
 | |
|             self.clearHistorySession)
 | |
|         menu.addMenu(subMenu)
 | |
|         menu.addSeparator()
 | |
|         copyAction = menu.addAction(
 | |
|             QgsApplication.getThemeIcon("mActionEditCopy.svg"),
 | |
|             QCoreApplication.translate("PythonConsole", "Copy"),
 | |
|             self.copy, QKeySequence.Copy)
 | |
|         pasteAction = menu.addAction(
 | |
|             QgsApplication.getThemeIcon("mActionEditPaste.svg"),
 | |
|             QCoreApplication.translate("PythonConsole", "Paste"),
 | |
|             self.paste, QKeySequence.Paste)
 | |
|         pyQGISHelpAction = menu.addAction(QgsApplication.getThemeIcon("console/iconHelpConsole.svg"),
 | |
|                                           QCoreApplication.translate("PythonConsole", "Search Selected in PyQGIS docs"),
 | |
|                                           self.searchSelectedTextInPyQGISDocs)
 | |
|         copyAction.setEnabled(False)
 | |
|         pasteAction.setEnabled(False)
 | |
|         pyQGISHelpAction.setEnabled(False)
 | |
|         if self.hasSelectedText():
 | |
|             copyAction.setEnabled(True)
 | |
|             pyQGISHelpAction.setEnabled(True)
 | |
|         if QApplication.clipboard().text():
 | |
|             pasteAction.setEnabled(True)
 | |
|         menu.exec_(self.mapToGlobal(e.pos()))
 | |
| 
 | |
|     def mousePressEvent(self, e):
 | |
|         """
 | |
|         Re-implemented to handle the mouse press event.
 | |
|         e: the mouse press event (QMouseEvent)
 | |
|         """
 | |
|         self.setFocus()
 | |
|         if e.button() == Qt.MidButton:
 | |
|             stringSel = QApplication.clipboard().text(QClipboard.Selection)
 | |
|             if not self.is_cursor_on_last_line():
 | |
|                 self.move_cursor_to_end()
 | |
|             self.insertFromDropPaste(stringSel)
 | |
|             e.accept()
 | |
|         else:
 | |
|             QsciScintilla.mousePressEvent(self, e)
 | |
| 
 | |
|     def paste(self):
 | |
|         """
 | |
|         Method to display data from the clipboard.
 | |
| 
 | |
|         XXX: It should reimplement the virtual QScintilla.paste method,
 | |
|         but it seems not used by QScintilla code.
 | |
|         """
 | |
|         stringPaste = QApplication.clipboard().text()
 | |
|         if self.is_cursor_on_last_line():
 | |
|             if self.hasSelectedText():
 | |
|                 self.removeSelectedText()
 | |
|         else:
 | |
|             self.move_cursor_to_end()
 | |
|         self.insertFromDropPaste(stringPaste)
 | |
| 
 | |
|     # Drag and drop
 | |
|     def dropEvent(self, e):
 | |
|         if e.mimeData().hasText():
 | |
|             stringDrag = e.mimeData().text()
 | |
|             self.insertFromDropPaste(stringDrag)
 | |
|             self.setFocus()
 | |
|             e.setDropAction(Qt.CopyAction)
 | |
|             e.accept()
 | |
|         else:
 | |
|             QsciScintilla.dropEvent(self, e)
 | |
| 
 | |
|     def insertFromDropPaste(self, textDP):
 | |
|         pasteList = textDP.splitlines()
 | |
|         if pasteList:
 | |
|             for line in pasteList[:-1]:
 | |
|                 cleanLine = line.replace(">>> ", "").replace("... ", "")
 | |
|                 self.insert(cleanLine)
 | |
|                 self.move_cursor_to_end()
 | |
|                 self.runCommand(self.text())
 | |
|             if pasteList[-1] != "":
 | |
|                 line = pasteList[-1]
 | |
|                 cleanLine = line.replace(">>> ", "").replace("... ", "")
 | |
|                 curpos = self.getCursorPosition()
 | |
|                 self.insert(cleanLine)
 | |
|                 self.setCursorPosition(curpos[0], curpos[1] + len(cleanLine))
 | |
| 
 | |
|     def insertTextFromFile(self, listOpenFile):
 | |
|         for line in listOpenFile[:-1]:
 | |
|             self.append(line)
 | |
|             self.move_cursor_to_end()
 | |
|             self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
 | |
|             self.runCommand(self.text())
 | |
|         self.append(listOpenFile[-1])
 | |
|         self.move_cursor_to_end()
 | |
|         self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
 | |
| 
 | |
|     def entered(self):
 | |
|         self.move_cursor_to_end()
 | |
|         self.runCommand(self.text())
 | |
|         self.setFocus()
 | |
|         self.move_cursor_to_end()
 | |
| 
 | |
|     def runCommand(self, cmd):
 | |
|         self.writeCMD(cmd)
 | |
|         import webbrowser
 | |
|         self.updateHistory(cmd)
 | |
|         version = 'master' if 'master' in Qgis.QGIS_VERSION.lower() else re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0]
 | |
|         if cmd in ('_pyqgis', '_api', '_cookbook'):
 | |
|             if cmd == '_pyqgis':
 | |
|                 webbrowser.open("https://qgis.org/pyqgis/{}".format(version))
 | |
|             elif cmd == '_api':
 | |
|                 webbrowser.open("https://qgis.org/api/{}".format('' if version == 'master' else version))
 | |
|             elif cmd == '_cookbook':
 | |
|                 webbrowser.open("https://docs.qgis.org/{}/en/docs/pyqgis_developer_cookbook/".format(
 | |
|                     'testing' if version == 'master' else version))
 | |
|         else:
 | |
|             self.buffer.append(cmd)
 | |
|             src = "\n".join(self.buffer)
 | |
|             more = self.runsource(src)
 | |
|             self.continuationLine = True
 | |
|             if not more:
 | |
|                 self.continuationLine = False
 | |
|                 self.buffer = []
 | |
| 
 | |
|         # prevents to commands with more lines to break the console
 | |
|         # in the case they have a eol different from '\n'
 | |
|         self.setText('')
 | |
|         self.move_cursor_to_end()
 | |
|         self.displayPrompt(self.continuationLine)
 | |
| 
 | |
|     def write(self, txt):
 | |
|         if sys.stderr:
 | |
|             sys.stderr.write(txt)
 | |
| 
 | |
|     def writeCMD(self, txt):
 | |
|         if sys.stdout:
 | |
|             sys.stdout.fire_keyboard_interrupt = False
 | |
|         if len(txt) > 0:
 | |
|             prompt = "... " if self.continuationLine else ">>> "
 | |
|             sys.stdout.write(prompt + txt + '\n')
 | |
| 
 | |
|     def runsource(self, source, filename='<input>', symbol='single'):
 | |
|         if sys.stdout:
 | |
|             sys.stdout.fire_keyboard_interrupt = False
 | |
|         hook = sys.excepthook
 | |
|         try:
 | |
|             def excepthook(etype, value, tb):
 | |
|                 self.write("".join(traceback.format_exception(etype, value, tb)))
 | |
| 
 | |
|             sys.excepthook = excepthook
 | |
| 
 | |
|             return super(ShellScintilla, self).runsource(source, filename, symbol)
 | |
|         finally:
 | |
|             sys.excepthook = hook
 | |
| 
 | |
| 
 | |
| class HistoryDialog(QDialog, Ui_HistoryDialogPythonConsole):
 | |
| 
 | |
|     def __init__(self, parent):
 | |
|         QDialog.__init__(self, parent)
 | |
|         self.setupUi(self)
 | |
|         self.parent = parent
 | |
|         self.setWindowTitle(QCoreApplication.translate("PythonConsole",
 | |
|                                                        "Python Console - Command History"))
 | |
|         self.listView.setToolTip(QCoreApplication.translate("PythonConsole",
 | |
|                                                             "Double-click on item to execute"))
 | |
| 
 | |
|         self.listView.setFont(QgsCodeEditorPython.getMonospaceFont())
 | |
| 
 | |
|         self.model = QStandardItemModel(self.listView)
 | |
| 
 | |
|         self._reloadHistory()
 | |
| 
 | |
|         self.deleteScut = QShortcut(QKeySequence(Qt.Key_Delete), self)
 | |
|         self.deleteScut.activated.connect(self._deleteItem)
 | |
|         self.listView.doubleClicked.connect(self._runHistory)
 | |
|         self.reloadHistory.clicked.connect(self._reloadHistory)
 | |
|         self.saveHistory.clicked.connect(self._saveHistory)
 | |
|         self.runHistoryButton.clicked.connect(self._executeSelectedHistory)
 | |
| 
 | |
|     def _executeSelectedHistory(self):
 | |
|         items = self.listView.selectionModel().selectedIndexes()
 | |
|         items.sort()
 | |
|         for item in items:
 | |
|             self.parent.runCommand(item.data(Qt.DisplayRole))
 | |
| 
 | |
|     def _runHistory(self, item):
 | |
|         cmd = item.data(Qt.DisplayRole)
 | |
|         self.parent.runCommand(cmd)
 | |
| 
 | |
|     def _saveHistory(self):
 | |
|         self.parent.writeHistoryFile(True)
 | |
| 
 | |
|     def _reloadHistory(self):
 | |
|         self.model.clear()
 | |
|         item = None
 | |
|         for i in self.parent.history:
 | |
|             item = QStandardItem(i)
 | |
|             if sys.platform.startswith('win'):
 | |
|                 item.setSizeHint(QSize(18, 18))
 | |
|             self.model.appendRow(item)
 | |
| 
 | |
|         self.listView.setModel(self.model)
 | |
|         self.listView.scrollToBottom()
 | |
|         if item:
 | |
|             self.listView.setCurrentIndex(self.model.indexFromItem(item))
 | |
| 
 | |
|     def _deleteItem(self):
 | |
|         itemsSelected = self.listView.selectionModel().selectedIndexes()
 | |
|         if itemsSelected:
 | |
|             item = itemsSelected[0].row()
 | |
|             # Remove item from the command history (just for the current session)
 | |
|             self.parent.history.pop(item)
 | |
|             self.parent.softHistory.pop(item)
 | |
|             if item < self.parent.softHistoryIndex:
 | |
|                 self.parent.softHistoryIndex -= 1
 | |
|             self.parent.setText(self.parent.softHistory[self.parent.softHistoryIndex])
 | |
|             self.parent.move_cursor_to_end()
 | |
|             # Remove row from the command history dialog
 | |
|             self.model.removeRow(item)
 |