mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
This allows the editor to be opened in a proper detached process, avoiding the editor being closed when QGIS is exited. Move to a blocklist for terminal text editors instead of the fragile polling approach (which eg doesn't work if the editor is set to pycharm)
721 lines
32 KiB
Python
721 lines
32 KiB
Python
"""
|
|
/***************************************************************************
|
|
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/
|
|
"""
|
|
import os
|
|
import subprocess
|
|
|
|
from qgis.PyQt.QtCore import Qt, QTimer, QCoreApplication, QSize, QByteArray, QFileInfo, QUrl, QDir
|
|
from qgis.PyQt.QtWidgets import QToolBar, QToolButton, QWidget, QSplitter, QTreeWidget, QAction, QFileDialog, QCheckBox, QSizePolicy, QMenu, QGridLayout, QApplication, QShortcut
|
|
from qgis.PyQt.QtGui import QDesktopServices, QKeySequence
|
|
from qgis.PyQt.QtWidgets import (
|
|
QVBoxLayout,
|
|
QMessageBox
|
|
)
|
|
from qgis.utils import iface
|
|
from .console_sci import ShellScintilla
|
|
from .console_output import ShellOutputScintilla
|
|
from .console_editor import EditorTabWidget
|
|
from .console_settings import ConsoleOptionsFactory
|
|
from qgis.core import (
|
|
Qgis,
|
|
QgsApplication,
|
|
QgsSettings,
|
|
QgsFileUtils
|
|
)
|
|
from qgis.gui import (
|
|
QgsFilterLineEdit,
|
|
QgsHelp,
|
|
QgsDockWidget,
|
|
QgsGui,
|
|
QgsApplicationExitBlockerInterface,
|
|
QgsCodeEditorDockWidget
|
|
)
|
|
from functools import partial
|
|
|
|
import sys
|
|
import re
|
|
|
|
_console = None
|
|
_options_factory = ConsoleOptionsFactory()
|
|
|
|
|
|
def show_console():
|
|
""" called from QGIS to open the console """
|
|
global _console
|
|
if _console is None:
|
|
parent = iface.mainWindow() if iface else None
|
|
_console = PythonConsole(parent)
|
|
if iface:
|
|
_console.visibilityChanged.connect(iface.actionShowPythonDialog().setChecked)
|
|
|
|
_console.show() # force show even if it was restored as hidden
|
|
# set focus to the console so the user can start typing
|
|
# defer the set focus event so it works also whether the console not visible yet
|
|
QTimer.singleShot(0, _console.activate)
|
|
else:
|
|
_console.setUserVisible(not _console.isUserVisible())
|
|
# set focus to the console so the user can start typing
|
|
if _console.isUserVisible():
|
|
_console.activate()
|
|
|
|
return _console
|
|
|
|
|
|
_console_output = None
|
|
|
|
# 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
|
|
|
|
|
|
def init_options_widget():
|
|
""" called from QGIS to add the console options widget """
|
|
global _options_factory
|
|
_options_factory.setTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
|
|
iface.registerOptionsWidgetFactory(_options_factory)
|
|
|
|
|
|
class ConsoleExitBlocker(QgsApplicationExitBlockerInterface):
|
|
|
|
def __init__(self, console):
|
|
super().__init__()
|
|
self.console = console
|
|
|
|
def allowExit(self):
|
|
return self.console.allowExit()
|
|
|
|
|
|
class PythonConsole(QgsCodeEditorDockWidget):
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__("PythonConsoleWindow", True)
|
|
self.setDockObjectName("PythonConsole")
|
|
self.setTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
|
|
|
|
self.console = PythonConsoleWidget(self)
|
|
QgsGui.instance().optionsChanged.connect(self.console.updateSettings)
|
|
vl = QVBoxLayout()
|
|
vl.setContentsMargins(0, 0, 0, 0)
|
|
vl.addWidget(self.console)
|
|
self.setLayout(vl)
|
|
self.setFocusProxy(self.console)
|
|
|
|
# closeEvent is not always called for this widget -- so we also trigger a settings
|
|
# save on application exit
|
|
QgsApplication.instance().aboutToQuit.connect(self.on_app_exit)
|
|
|
|
def on_app_exit(self):
|
|
self.console.saveSettingsConsole()
|
|
self.deleteLater()
|
|
|
|
def activate(self):
|
|
self.activateWindow()
|
|
self.raise_()
|
|
self.setFocus()
|
|
|
|
def closeEvent(self, event):
|
|
self.console.saveSettingsConsole()
|
|
self.hide()
|
|
event.ignore()
|
|
|
|
|
|
class PythonConsoleWidget(QWidget):
|
|
|
|
def __init__(self, parent=None):
|
|
QWidget.__init__(self, parent)
|
|
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
|
|
|
|
self.shell = ShellScintilla(console_widget=self)
|
|
self.setFocusProxy(self.shell)
|
|
self.shell_output = ShellOutputScintilla(
|
|
console_widget=self,
|
|
shell_editor=self.shell
|
|
)
|
|
self.tabEditorWidget = EditorTabWidget(console_widget=self)
|
|
|
|
# ------------ UI -------------------------------
|
|
|
|
self.splitterEditor = QSplitter(self)
|
|
self.splitterEditor.setOrientation(Qt.Orientation.Horizontal)
|
|
self.splitterEditor.setHandleWidth(6)
|
|
self.splitterEditor.setChildrenCollapsible(True)
|
|
|
|
self.shellOutWidget = QWidget(self)
|
|
self.shellOutWidget.setLayout(QVBoxLayout())
|
|
self.shellOutWidget.layout().setContentsMargins(0, 0, 0, 0)
|
|
self.shellOutWidget.layout().addWidget(self.shell_output)
|
|
|
|
self.splitter = QSplitter(self.splitterEditor)
|
|
self.splitter.setOrientation(Qt.Orientation.Vertical)
|
|
self.splitter.setHandleWidth(3)
|
|
self.splitter.setChildrenCollapsible(False)
|
|
self.splitter.addWidget(self.shellOutWidget)
|
|
self.splitter.addWidget(self.shell)
|
|
|
|
self.splitterObj = QSplitter(self.splitterEditor)
|
|
self.splitterObj.setHandleWidth(3)
|
|
self.splitterObj.setOrientation(Qt.Orientation.Horizontal)
|
|
|
|
self.widgetEditor = QWidget(self.splitterObj)
|
|
|
|
self.listClassMethod = QTreeWidget(self.splitterObj)
|
|
self.listClassMethod.setColumnCount(2)
|
|
objInspLabel = QCoreApplication.translate("PythonConsole", "Object Inspector")
|
|
self.listClassMethod.setHeaderLabels([objInspLabel, ''])
|
|
self.listClassMethod.setColumnHidden(1, True)
|
|
self.listClassMethod.setAlternatingRowColors(True)
|
|
|
|
# Hide side editor on start up
|
|
self.splitterObj.hide()
|
|
self.listClassMethod.hide()
|
|
|
|
icon_size = iface.iconSize(dockedToolbar=True) if iface else QSize(16, 16)
|
|
|
|
sizes = self.splitter.sizes()
|
|
self.splitter.setSizes(sizes)
|
|
|
|
# ----------------Restore Settings------------------------------------
|
|
|
|
self.restoreSettingsConsole()
|
|
|
|
# ------------------Toolbar Editor-------------------------------------
|
|
|
|
# Action for Open File
|
|
openFileBt = QCoreApplication.translate("PythonConsole", "Open Script…")
|
|
self.openFileButton = QAction(self)
|
|
self.openFileButton.setCheckable(False)
|
|
self.openFileButton.setEnabled(True)
|
|
self.openFileButton.setIcon(QgsApplication.getThemeIcon("mActionScriptOpen.svg"))
|
|
self.openFileButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.openFileButton.setIconVisibleInMenu(True)
|
|
self.openFileButton.setToolTip(openFileBt)
|
|
self.openFileButton.setText(openFileBt)
|
|
|
|
openExtEditorBt = QCoreApplication.translate("PythonConsole", "Open in External Editor")
|
|
self.openInEditorButton = QAction(self)
|
|
self.openInEditorButton.setCheckable(False)
|
|
self.openInEditorButton.setEnabled(True)
|
|
self.openInEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconShowEditorConsole.svg"))
|
|
self.openInEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.openInEditorButton.setIconVisibleInMenu(True)
|
|
self.openInEditorButton.setToolTip(openExtEditorBt)
|
|
self.openInEditorButton.setText(openExtEditorBt)
|
|
# Action for Save File
|
|
saveFileBt = QCoreApplication.translate("PythonConsole", "Save")
|
|
self.saveFileButton = QAction(self)
|
|
self.saveFileButton.setCheckable(False)
|
|
self.saveFileButton.setEnabled(False)
|
|
self.saveFileButton.setIcon(QgsApplication.getThemeIcon("mActionFileSave.svg"))
|
|
self.saveFileButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.saveFileButton.setIconVisibleInMenu(True)
|
|
self.saveFileButton.setToolTip(saveFileBt)
|
|
self.saveFileButton.setText(saveFileBt)
|
|
# Action for Save File As
|
|
saveAsFileBt = QCoreApplication.translate("PythonConsole", "Save As…")
|
|
self.saveAsFileButton = QAction(self)
|
|
self.saveAsFileButton.setCheckable(False)
|
|
self.saveAsFileButton.setEnabled(True)
|
|
self.saveAsFileButton.setIcon(QgsApplication.getThemeIcon("mActionFileSaveAs.svg"))
|
|
self.saveAsFileButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.saveAsFileButton.setIconVisibleInMenu(True)
|
|
self.saveAsFileButton.setToolTip(saveAsFileBt)
|
|
self.saveAsFileButton.setText(saveAsFileBt)
|
|
# Action Cut
|
|
cutEditorBt = QCoreApplication.translate("PythonConsole", "Cut")
|
|
self.cutEditorButton = QAction(self)
|
|
self.cutEditorButton.setCheckable(False)
|
|
self.cutEditorButton.setEnabled(True)
|
|
self.cutEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditCut.svg"))
|
|
self.cutEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.cutEditorButton.setIconVisibleInMenu(True)
|
|
self.cutEditorButton.setToolTip(cutEditorBt)
|
|
self.cutEditorButton.setText(cutEditorBt)
|
|
# Action Copy
|
|
copyEditorBt = QCoreApplication.translate("PythonConsole", "Copy")
|
|
self.copyEditorButton = QAction(self)
|
|
self.copyEditorButton.setCheckable(False)
|
|
self.copyEditorButton.setEnabled(True)
|
|
self.copyEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditCopy.svg"))
|
|
self.copyEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.copyEditorButton.setIconVisibleInMenu(True)
|
|
self.copyEditorButton.setToolTip(copyEditorBt)
|
|
self.copyEditorButton.setText(copyEditorBt)
|
|
# Action Paste
|
|
pasteEditorBt = QCoreApplication.translate("PythonConsole", "Paste")
|
|
self.pasteEditorButton = QAction(self)
|
|
self.pasteEditorButton.setCheckable(False)
|
|
self.pasteEditorButton.setEnabled(True)
|
|
self.pasteEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditPaste.svg"))
|
|
self.pasteEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.pasteEditorButton.setIconVisibleInMenu(True)
|
|
self.pasteEditorButton.setToolTip(pasteEditorBt)
|
|
self.pasteEditorButton.setText(pasteEditorBt)
|
|
# Action Run Script (subprocess)
|
|
runScriptEditorBt = QCoreApplication.translate("PythonConsole", "Run Script")
|
|
self.runScriptEditorButton = QAction(self)
|
|
self.runScriptEditorButton.setCheckable(False)
|
|
self.runScriptEditorButton.setEnabled(True)
|
|
self.runScriptEditorButton.setIcon(QgsApplication.getThemeIcon("mActionStart.svg"))
|
|
self.runScriptEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.runScriptEditorButton.setIconVisibleInMenu(True)
|
|
self.runScriptEditorButton.setToolTip(runScriptEditorBt)
|
|
self.runScriptEditorButton.setText(runScriptEditorBt)
|
|
|
|
# Action Toggle comment
|
|
toggleText = QCoreApplication.translate("PythonConsole", "Toggle Comment")
|
|
self.toggleCommentEditorButton = QAction(self)
|
|
self.toggleCommentEditorButton.setCheckable(False)
|
|
self.toggleCommentEditorButton.setEnabled(True)
|
|
self.toggleCommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"))
|
|
self.toggleCommentEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.toggleCommentEditorButton.setIconVisibleInMenu(True)
|
|
self.toggleCommentEditorButton.setToolTip(toggleText + " <b>Ctrl+:</b>")
|
|
self.toggleCommentEditorButton.setText(toggleText)
|
|
|
|
# Action Format code
|
|
reformatCodeText = QCoreApplication.translate("PythonConsole", "Reformat Code")
|
|
self.reformatCodeEditorButton = QAction(self)
|
|
self.reformatCodeEditorButton.setCheckable(False)
|
|
self.reformatCodeEditorButton.setEnabled(True)
|
|
self.reformatCodeEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconFormatCode.svg"))
|
|
self.reformatCodeEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.reformatCodeEditorButton.setIconVisibleInMenu(True)
|
|
self.reformatCodeEditorButton.setToolTip(reformatCodeText + " <b>Ctrl+Alt+F</b>")
|
|
self.reformatCodeEditorButton.setShortcut("Ctrl+Alt+F")
|
|
self.reformatCodeEditorButton.setText(reformatCodeText)
|
|
|
|
# Action for Object browser
|
|
objList = QCoreApplication.translate("PythonConsole", "Object Inspector…")
|
|
self.objectListButton = QAction(self)
|
|
self.objectListButton.setCheckable(True)
|
|
self.objectListButton.setEnabled(QgsSettings().value("pythonConsole/enableObjectInsp",
|
|
False, type=bool))
|
|
self.objectListButton.setIcon(QgsApplication.getThemeIcon("console/iconClassBrowserConsole.svg"))
|
|
self.objectListButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.objectListButton.setIconVisibleInMenu(True)
|
|
self.objectListButton.setToolTip(objList)
|
|
self.objectListButton.setText(objList)
|
|
|
|
# Action for Find text
|
|
findText = QCoreApplication.translate("PythonConsole", "Find Text")
|
|
self.find_text_action = QAction(self)
|
|
self.find_text_action.setCheckable(True)
|
|
self.find_text_action.setEnabled(True)
|
|
self.find_text_action.setIcon(QgsApplication.getThemeIcon("console/iconSearchEditorConsole.svg"))
|
|
self.find_text_action.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.find_text_action.setIconVisibleInMenu(True)
|
|
self.find_text_action.setToolTip(findText)
|
|
self.find_text_action.setText(findText)
|
|
|
|
self.tabEditorWidget.search_bar_toggled.connect(
|
|
self.find_text_action.setChecked
|
|
)
|
|
self.find_text_action.toggled.connect(
|
|
self.tabEditorWidget.toggle_search_bar
|
|
)
|
|
|
|
# ----------------Toolbar Console-------------------------------------
|
|
|
|
# Action Show Editor
|
|
showEditor = QCoreApplication.translate("PythonConsole", "Show Editor")
|
|
self.showEditorButton = QAction(self)
|
|
self.showEditorButton.setEnabled(True)
|
|
self.showEditorButton.setCheckable(True)
|
|
self.showEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconShowEditorConsole.svg"))
|
|
self.showEditorButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.showEditorButton.setIconVisibleInMenu(True)
|
|
self.showEditorButton.setToolTip(showEditor)
|
|
self.showEditorButton.setText(showEditor)
|
|
# Action for Clear button
|
|
clearBt = QCoreApplication.translate("PythonConsole", "Clear Console")
|
|
self.clearButton = QAction(self)
|
|
self.clearButton.setCheckable(False)
|
|
self.clearButton.setEnabled(True)
|
|
self.clearButton.setIcon(QgsApplication.getThemeIcon("console/iconClearConsole.svg"))
|
|
self.clearButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.clearButton.setIconVisibleInMenu(True)
|
|
self.clearButton.setToolTip(clearBt)
|
|
self.clearButton.setText(clearBt)
|
|
# Action for settings
|
|
optionsBt = QCoreApplication.translate("PythonConsole", "Options…")
|
|
self.optionsButton = QAction(self)
|
|
self.optionsButton.setCheckable(False)
|
|
self.optionsButton.setEnabled(True)
|
|
self.optionsButton.setIcon(QgsApplication.getThemeIcon("console/iconSettingsConsole.svg"))
|
|
self.optionsButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.optionsButton.setIconVisibleInMenu(True)
|
|
self.optionsButton.setToolTip(optionsBt)
|
|
self.optionsButton.setText(optionsBt)
|
|
# Action for Run script
|
|
runBt = QCoreApplication.translate("PythonConsole", "Run Command")
|
|
self.runButton = QAction(self)
|
|
self.runButton.setCheckable(False)
|
|
self.runButton.setEnabled(True)
|
|
self.runButton.setIcon(QgsApplication.getThemeIcon("mActionStart.svg"))
|
|
self.runButton.setMenuRole(QAction.MenuRole.PreferencesRole)
|
|
self.runButton.setIconVisibleInMenu(True)
|
|
self.runButton.setToolTip(runBt)
|
|
self.runButton.setText(runBt)
|
|
|
|
# Help button
|
|
self.helpConsoleAction = QAction(self)
|
|
self.helpConsoleAction.setEnabled(True)
|
|
self.helpConsoleAction.setText(QCoreApplication.translate("PythonConsole", "Python Console Help"))
|
|
self.helpAPIAction = QAction(self)
|
|
self.helpAPIAction.setEnabled(True)
|
|
self.helpAPIAction.setText(QCoreApplication.translate("PythonConsole", "PyQGIS API Documentation"))
|
|
self.helpCookbookAction = QAction(self)
|
|
self.helpCookbookAction.setEnabled(True)
|
|
self.helpCookbookAction.setText(QCoreApplication.translate("PythonConsole", "PyQGIS Cookbook"))
|
|
|
|
self.helpMenu = QMenu(self)
|
|
self.helpMenu.addAction(self.helpConsoleAction)
|
|
self.helpMenu.addAction(self.helpAPIAction)
|
|
self.helpMenu.addAction(self.helpCookbookAction)
|
|
|
|
helpBt = QCoreApplication.translate("PythonConsole", "Help…")
|
|
self.helpButton = QToolButton(self)
|
|
self.helpButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
self.helpButton.setEnabled(True)
|
|
self.helpButton.setIcon(QgsApplication.getThemeIcon("console/iconHelpConsole.svg"))
|
|
self.helpButton.setToolTip(helpBt)
|
|
self.helpButton.setMenu(self.helpMenu)
|
|
|
|
self.toolBar = QToolBar()
|
|
self.toolBar.setEnabled(True)
|
|
self.toolBar.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
self.toolBar.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu)
|
|
self.toolBar.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
|
|
self.toolBar.setIconSize(icon_size)
|
|
self.toolBar.setMovable(False)
|
|
self.toolBar.setFloatable(False)
|
|
self.toolBar.addAction(self.clearButton)
|
|
self.toolBar.addAction(self.runButton)
|
|
self.toolBar.addSeparator()
|
|
self.toolBar.addAction(self.showEditorButton)
|
|
self.toolBar.addSeparator()
|
|
self.toolBar.addAction(self.optionsButton)
|
|
self.toolBar.addWidget(self.helpButton)
|
|
self.toolBar.addSeparator()
|
|
self.toolBar.addWidget(parent.dockToggleButton())
|
|
|
|
self.toolBarEditor = QToolBar()
|
|
self.toolBarEditor.setEnabled(False)
|
|
self.toolBarEditor.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
self.toolBarEditor.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu)
|
|
self.toolBarEditor.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
|
|
self.toolBarEditor.setIconSize(icon_size)
|
|
self.toolBarEditor.setMovable(False)
|
|
self.toolBarEditor.setFloatable(False)
|
|
self.toolBarEditor.addAction(self.openFileButton)
|
|
self.toolBarEditor.addAction(self.openInEditorButton)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.saveFileButton)
|
|
self.toolBarEditor.addAction(self.saveAsFileButton)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.runScriptEditorButton)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.cutEditorButton)
|
|
self.toolBarEditor.addAction(self.copyEditorButton)
|
|
self.toolBarEditor.addAction(self.pasteEditorButton)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.find_text_action)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.toggleCommentEditorButton)
|
|
self.toolBarEditor.addAction(self.reformatCodeEditorButton)
|
|
self.toolBarEditor.addSeparator()
|
|
self.toolBarEditor.addAction(self.objectListButton)
|
|
|
|
self.widgetButton = QWidget()
|
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
|
sizePolicy.setHorizontalStretch(0)
|
|
sizePolicy.setVerticalStretch(0)
|
|
sizePolicy.setHeightForWidth(self.widgetButton.sizePolicy().hasHeightForWidth())
|
|
self.widgetButton.setSizePolicy(sizePolicy)
|
|
|
|
self.widgetButtonEditor = QWidget(self.widgetEditor)
|
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
|
sizePolicy.setHorizontalStretch(0)
|
|
sizePolicy.setVerticalStretch(0)
|
|
sizePolicy.setHeightForWidth(self.widgetButtonEditor.sizePolicy().hasHeightForWidth())
|
|
self.widgetButtonEditor.setSizePolicy(sizePolicy)
|
|
|
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
sizePolicy.setHorizontalStretch(0)
|
|
sizePolicy.setVerticalStretch(0)
|
|
sizePolicy.setHeightForWidth(self.shell_output.sizePolicy().hasHeightForWidth())
|
|
self.shell_output.setSizePolicy(sizePolicy)
|
|
|
|
self.shell_output.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
self.shell.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
|
|
# ------------ Layout -------------------------------
|
|
|
|
self.mainLayout = QGridLayout(self)
|
|
self.mainLayout.setMargin(0)
|
|
self.mainLayout.setSpacing(0)
|
|
self.mainLayout.addWidget(self.widgetButton, 0, 0, 1, 1)
|
|
self.mainLayout.addWidget(self.splitterEditor, 0, 1, 1, 1)
|
|
|
|
self.shellOutWidget.layout().insertWidget(0, self.toolBar)
|
|
|
|
self.layoutEditor = QGridLayout(self.widgetEditor)
|
|
self.layoutEditor.setMargin(0)
|
|
self.layoutEditor.setSpacing(0)
|
|
self.layoutEditor.addWidget(self.toolBarEditor, 0, 1, 1, 1)
|
|
self.layoutEditor.addWidget(self.widgetButtonEditor, 1, 0, 2, 1)
|
|
self.layoutEditor.addWidget(self.tabEditorWidget, 1, 1, 1, 1)
|
|
|
|
# ------------ Add first Tab in Editor -------------------------------
|
|
|
|
# self.tabEditorWidget.newTabEditor(tabName='first', filename=None)
|
|
|
|
# ------------ Signal -------------------------------
|
|
|
|
self.objectListButton.toggled.connect(self.toggleObjectListWidget)
|
|
self.toggleCommentEditorButton.triggered.connect(self.toggleComment)
|
|
self.reformatCodeEditorButton.triggered.connect(self.reformatCode)
|
|
self.runScriptEditorButton.triggered.connect(self.runScriptEditor)
|
|
self.cutEditorButton.triggered.connect(self.cutEditor)
|
|
self.copyEditorButton.triggered.connect(self.copyEditor)
|
|
self.pasteEditorButton.triggered.connect(self.pasteEditor)
|
|
self.showEditorButton.toggled.connect(self.toggleEditor)
|
|
self.clearButton.triggered.connect(self.shell_output.clearConsole)
|
|
self.optionsButton.triggered.connect(self.openSettings)
|
|
self.runButton.triggered.connect(self.shell.entered)
|
|
self.openFileButton.triggered.connect(self.openScriptFile)
|
|
self.openInEditorButton.triggered.connect(self.openScriptFileExtEditor)
|
|
self.saveFileButton.triggered.connect(self.saveScriptFile)
|
|
self.saveAsFileButton.triggered.connect(self.saveAsScriptFile)
|
|
self.helpConsoleAction.triggered.connect(self.openHelpConsole)
|
|
self.helpAPIAction.triggered.connect(self.openHelpAPI)
|
|
self.helpCookbookAction.triggered.connect(self.openHelpCookbook)
|
|
self.listClassMethod.itemClicked.connect(self.onClickGoToLine)
|
|
|
|
if iface is not None:
|
|
self.exit_blocker = ConsoleExitBlocker(self)
|
|
iface.registerApplicationExitBlocker(self.exit_blocker)
|
|
|
|
def allowExit(self):
|
|
tab_count = self.tabEditorWidget.count()
|
|
for i in range(tab_count):
|
|
# iterate backwards through tabs, as we may be closing some as we go
|
|
tab_index = tab_count - i - 1
|
|
tab_widget = self.tabEditorWidget.widget(tab_index)
|
|
if tab_widget.isModified():
|
|
ret = QMessageBox.question(self, self.tr("Save {}").format(self.tabEditorWidget.tabText(tab_index)),
|
|
self.tr("There are unsaved changes in this script. Do you want to keep those?"),
|
|
QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Cancel | QMessageBox.StandardButton.Discard, QMessageBox.StandardButton.Cancel)
|
|
if ret == QMessageBox.StandardButton.Save:
|
|
tab_widget.save()
|
|
if tab_widget.isModified():
|
|
# save failed, treat as cancel
|
|
return False
|
|
elif ret == QMessageBox.StandardButton.Discard:
|
|
pass
|
|
else:
|
|
return False
|
|
|
|
self.tabEditorWidget.removeTab(tab_index)
|
|
|
|
return True
|
|
|
|
def _toggleFind(self):
|
|
self.tabEditorWidget.currentWidget().toggleFindWidget()
|
|
|
|
def onClickGoToLine(self, item, column):
|
|
tabEditor = self.tabEditorWidget.currentWidget()
|
|
if item.text(1) == 'syntaxError':
|
|
check = tabEditor.syntaxCheck()
|
|
if check and not tabEditor.isReadOnly():
|
|
self.tabEditorWidget.currentWidget().save()
|
|
return
|
|
linenr = int(item.text(1))
|
|
itemName = str(item.text(0))
|
|
charPos = itemName.find(' ')
|
|
if charPos != -1:
|
|
objName = itemName[0:charPos]
|
|
else:
|
|
objName = itemName
|
|
tabEditor.goToLine(str.encode(objName), linenr)
|
|
|
|
def toggleEditor(self, checked):
|
|
self.splitterObj.show() if checked else self.splitterObj.hide()
|
|
if not self.tabEditorWidget:
|
|
self.tabEditorWidget.enableToolBarEditor(checked)
|
|
self.tabEditorWidget.restoreTabsOrAddNew()
|
|
|
|
def toggleObjectListWidget(self, checked):
|
|
self.listClassMethod.show() if checked else self.listClassMethod.hide()
|
|
|
|
def pasteEditor(self):
|
|
self.tabEditorWidget.currentWidget().paste()
|
|
|
|
def cutEditor(self):
|
|
self.tabEditorWidget.currentWidget().cut()
|
|
|
|
def copyEditor(self):
|
|
self.tabEditorWidget.currentWidget().copy()
|
|
|
|
def runScriptEditor(self):
|
|
self.tabEditorWidget.currentWidget().runScriptCode()
|
|
|
|
def toggleComment(self):
|
|
self.tabEditorWidget.currentWidget().toggleComment()
|
|
|
|
def reformatCode(self):
|
|
self.tabEditorWidget.currentWidget().reformatCode()
|
|
|
|
def openScriptFileExtEditor(self):
|
|
tabWidget = self.tabEditorWidget.currentWidget()
|
|
tabWidget.open_in_external_editor()
|
|
|
|
def openScriptFile(self):
|
|
settings = QgsSettings()
|
|
lastDirPath = settings.value("pythonConsole/lastDirPath", QDir.homePath())
|
|
openFileTr = QCoreApplication.translate("PythonConsole", "Open File")
|
|
fileList, selected_filter = QFileDialog.getOpenFileNames(
|
|
self, openFileTr, lastDirPath, "Script file (*.py)")
|
|
if fileList:
|
|
for pyFile in fileList:
|
|
for i in range(self.tabEditorWidget.count()):
|
|
tabWidget = self.tabEditorWidget.widget(i)
|
|
if tabWidget.file_path() == pyFile:
|
|
self.tabEditorWidget.setCurrentWidget(tabWidget)
|
|
break
|
|
else:
|
|
tabName = QFileInfo(pyFile).fileName()
|
|
self.tabEditorWidget.newTabEditor(tabName, pyFile)
|
|
|
|
lastDirPath = QFileInfo(pyFile).path()
|
|
settings.setValue("pythonConsole/lastDirPath", pyFile)
|
|
self.updateTabListScript(pyFile, action='append')
|
|
|
|
def saveScriptFile(self):
|
|
tabWidget = self.tabEditorWidget.currentWidget()
|
|
try:
|
|
tabWidget.save()
|
|
except (IOError, OSError) as error:
|
|
msgText = QCoreApplication.translate('PythonConsole',
|
|
'The file <b>{0}</b> could not be saved. Error: {1}').format(tabWidget.file_path(),
|
|
error.strerror)
|
|
self.callWidgetMessageBarEditor(msgText, Qgis.MessageLevel.Critical)
|
|
|
|
def saveAsScriptFile(self, index=None):
|
|
tabWidget = self.tabEditorWidget.currentWidget()
|
|
if not index:
|
|
index = self.tabEditorWidget.currentIndex()
|
|
if not tabWidget.file_path():
|
|
fileName = self.tabEditorWidget.tabText(index).replace('*', '') + '.py'
|
|
folder = QgsSettings().value("pythonConsole/lastDirPath", QDir.homePath())
|
|
pathFileName = os.path.join(folder, fileName)
|
|
fileNone = True
|
|
else:
|
|
pathFileName = tabWidget.file_path()
|
|
fileNone = False
|
|
saveAsFileTr = QCoreApplication.translate("PythonConsole", "Save File As")
|
|
filename, filter = QFileDialog.getSaveFileName(self,
|
|
saveAsFileTr,
|
|
pathFileName, "Script file (*.py)")
|
|
if filename:
|
|
filename = QgsFileUtils.ensureFileNameHasExtension(filename, ['py'])
|
|
|
|
try:
|
|
tabWidget.save(filename)
|
|
except (IOError, OSError) as error:
|
|
msgText = QCoreApplication.translate('PythonConsole',
|
|
'The file <b>{0}</b> could not be saved. Error: {1}').format(tabWidget.file_path(),
|
|
error.strerror)
|
|
self.callWidgetMessageBarEditor(msgText, Qgis.MessageLevel.Critical)
|
|
if fileNone:
|
|
tabWidget.set_file_path(None)
|
|
else:
|
|
tabWidget.set_file_path(pathFileName)
|
|
return
|
|
|
|
if not fileNone:
|
|
self.updateTabListScript(pathFileName, action='remove')
|
|
|
|
def openHelpConsole(self):
|
|
QgsHelp.openHelp("plugins/python_console.html")
|
|
|
|
def openHelpAPI(self):
|
|
m = re.search(r'^([0-9]+)\.([0-9]+)\.', Qgis.QGIS_VERSION)
|
|
if m:
|
|
QDesktopServices.openUrl(QUrl('https://qgis.org/pyqgis/{}.{}/'.format(m.group(1), m.group(2))))
|
|
|
|
def openHelpCookbook(self):
|
|
m = re.search(r'^([0-9]+)\.([0-9]+)\.', Qgis.QGIS_VERSION)
|
|
if m:
|
|
QDesktopServices.openUrl(QUrl('https://docs.qgis.org/{}.{}/en/docs/pyqgis_developer_cookbook/index.html'.format(m.group(1), m.group(2))))
|
|
|
|
def openSettings(self):
|
|
iface.showOptionsDialog(iface.mainWindow(), currentPage='consoleOptions')
|
|
|
|
def updateSettings(self):
|
|
self.shell.refreshSettingsShell()
|
|
self.shell_output.refreshSettingsOutput()
|
|
self.tabEditorWidget.refreshSettingsEditor()
|
|
|
|
def callWidgetMessageBar(self, text):
|
|
self.shell_output.widgetMessageBar(text)
|
|
|
|
def callWidgetMessageBarEditor(self, text, level):
|
|
self.tabEditorWidget.showMessage(text, level)
|
|
|
|
def updateTabListScript(self, script, action=None):
|
|
if action == 'remove':
|
|
self.tabListScript.remove(script)
|
|
elif action == 'append':
|
|
if not self.tabListScript:
|
|
self.tabListScript = []
|
|
if script not in self.tabListScript:
|
|
self.tabListScript.append(script)
|
|
else:
|
|
self.tabListScript = []
|
|
QgsSettings().setValue("pythonConsole/tabScripts",
|
|
self.tabListScript)
|
|
|
|
def saveSettingsConsole(self):
|
|
settings = QgsSettings()
|
|
settings.setValue("pythonConsole/splitterConsole", self.splitter.saveState())
|
|
settings.setValue("pythonConsole/splitterObj", self.splitterObj.saveState())
|
|
settings.setValue("pythonConsole/splitterEditor", self.splitterEditor.saveState())
|
|
|
|
self.shell.writeHistoryFile()
|
|
|
|
def restoreSettingsConsole(self):
|
|
settings = QgsSettings()
|
|
storedTabScripts = settings.value("pythonConsole/tabScripts", [])
|
|
self.tabListScript = storedTabScripts
|
|
self.splitter.restoreState(settings.value("pythonConsole/splitterConsole", QByteArray()))
|
|
self.splitterEditor.restoreState(settings.value("pythonConsole/splitterEditor", QByteArray()))
|
|
self.splitterObj.restoreState(settings.value("pythonConsole/splitterObj", QByteArray()))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
a = QApplication(sys.argv)
|
|
console = PythonConsoleWidget()
|
|
console.show()
|
|
a.exec()
|