From c6c65b391c0168e2f9bf4b1f0d3ce4ba15889b83 Mon Sep 17 00:00:00 2001 From: Rikki Guy Date: Mon, 17 Jun 2024 13:19:27 +0100 Subject: [PATCH] feat(tool): Add Style Tool Add a mini tool to apply the stylesheets to any loaded OFDS layer. --- gui_style.py | 73 +++++++++++++++++++++++ gui_style.ui | 128 +++++++++++++++++++++++++++++++++++++++++ plugin.py | 58 ++++++++++++++----- scripts/compile-gui.sh | 1 + tool/style.py | 86 +++++++++++++++++++++++++++ 5 files changed, 332 insertions(+), 14 deletions(-) create mode 100644 gui_style.py create mode 100644 gui_style.ui create mode 100644 tool/style.py diff --git a/gui_style.py b/gui_style.py new file mode 100644 index 0000000..e6fad9b --- /dev/null +++ b/gui_style.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'gui_style.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_StyleOFDSDialog(object): + def setupUi(self, StyleOFDSDialog): + StyleOFDSDialog.setObjectName("StyleOFDSDialog") + StyleOFDSDialog.resize(457, 161) + self.gridLayout_2 = QtWidgets.QGridLayout(StyleOFDSDialog) + self.gridLayout_2.setObjectName("gridLayout_2") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.spansComboBox = QtWidgets.QComboBox(StyleOFDSDialog) + self.spansComboBox.setObjectName("spansComboBox") + self.gridLayout.addWidget(self.spansComboBox, 3, 1, 1, 1) + self.line = QtWidgets.QFrame(StyleOFDSDialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 6, 2, 1, 1) + self.nodesLabel = QtWidgets.QLabel(StyleOFDSDialog) + self.nodesLabel.setMaximumSize(QtCore.QSize(100, 16777215)) + self.nodesLabel.setObjectName("nodesLabel") + self.gridLayout.addWidget(self.nodesLabel, 2, 0, 1, 1) + self.nodesComboBox = QtWidgets.QComboBox(StyleOFDSDialog) + self.nodesComboBox.setObjectName("nodesComboBox") + self.gridLayout.addWidget(self.nodesComboBox, 2, 1, 1, 1) + self.closeButton = QtWidgets.QPushButton(StyleOFDSDialog) + self.closeButton.setObjectName("closeButton") + self.gridLayout.addWidget(self.closeButton, 7, 2, 1, 1) + self.spansApplyButton = QtWidgets.QPushButton(StyleOFDSDialog) + self.spansApplyButton.setMaximumSize(QtCore.QSize(100, 16777215)) + self.spansApplyButton.setObjectName("spansApplyButton") + self.gridLayout.addWidget(self.spansApplyButton, 3, 2, 1, 1) + self.infoLabel = QtWidgets.QLabel(StyleOFDSDialog) + self.infoLabel.setObjectName("infoLabel") + self.gridLayout.addWidget(self.infoLabel, 0, 0, 1, 3) + self.nodesApplyButton = QtWidgets.QPushButton(StyleOFDSDialog) + self.nodesApplyButton.setMaximumSize(QtCore.QSize(100, 16777215)) + self.nodesApplyButton.setObjectName("nodesApplyButton") + self.gridLayout.addWidget(self.nodesApplyButton, 2, 2, 1, 1) + self.spansLabel = QtWidgets.QLabel(StyleOFDSDialog) + self.spansLabel.setMaximumSize(QtCore.QSize(100, 16777215)) + self.spansLabel.setObjectName("spansLabel") + self.gridLayout.addWidget(self.spansLabel, 3, 0, 1, 1) + self.refreshButton = QtWidgets.QPushButton(StyleOFDSDialog) + self.refreshButton.setObjectName("refreshButton") + self.gridLayout.addWidget(self.refreshButton, 7, 1, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 1) + + self.retranslateUi(StyleOFDSDialog) + self.closeButton.pressed.connect(StyleOFDSDialog.close) # type: ignore + QtCore.QMetaObject.connectSlotsByName(StyleOFDSDialog) + + def retranslateUi(self, StyleOFDSDialog): + _translate = QtCore.QCoreApplication.translate + StyleOFDSDialog.setWindowTitle(_translate("StyleOFDSDialog", "Style OFDS")) + self.nodesLabel.setText(_translate("StyleOFDSDialog", "Nodes Layer")) + self.closeButton.setText(_translate("StyleOFDSDialog", "Close")) + self.spansApplyButton.setText(_translate("StyleOFDSDialog", "Apply")) + self.infoLabel.setText(_translate("StyleOFDSDialog", "Apply styles to OFDS layers")) + self.nodesApplyButton.setText(_translate("StyleOFDSDialog", "Apply")) + self.spansLabel.setText(_translate("StyleOFDSDialog", "Spans Layer")) + self.refreshButton.setText(_translate("StyleOFDSDialog", "Refresh Layers")) diff --git a/gui_style.ui b/gui_style.ui new file mode 100644 index 0000000..b5bc594 --- /dev/null +++ b/gui_style.ui @@ -0,0 +1,128 @@ + + + StyleOFDSDialog + + + + 0 + 0 + 457 + 161 + + + + Style OFDS + + + + + + + + + + + Qt::Horizontal + + + + + + + + 100 + 16777215 + + + + Nodes Layer + + + + + + + + + + Close + + + + + + + + 100 + 16777215 + + + + Apply + + + + + + + Apply styles to OFDS layers + + + + + + + + 100 + 16777215 + + + + Apply + + + + + + + + 100 + 16777215 + + + + Spans Layer + + + + + + + Refresh Layers + + + + + + + + + + + closeButton + pressed() + StyleOFDSDialog + close() + + + 399 + 136 + + + 228 + 80 + + + + + diff --git a/plugin.py b/plugin.py index 70ce97f..1d4d883 100644 --- a/plugin.py +++ b/plugin.py @@ -4,31 +4,52 @@ from PyQt5.QtCore import PYQT_VERSION_STR, QT_VERSION_STR from PyQt5.QtWidgets import QAction from qgis.core import QgsProject -from qgis.utils import iface # type: ignore +from qgis.gui import QgisInterface +from qgis.utils import iface + +iface: QgisInterface from .tool.tool import OFDSDedupToolDialog +from .tool.style import OFDSStyleToolDialog logger = logging.getLogger(__name__) class OFDSDedupPlugin: def __init__(self): - self.tool_dialog = OFDSDedupToolDialog() + self.consolidate_tool_dialog = OFDSDedupToolDialog() + self.style_tool_dialog = OFDSStyleToolDialog() def initGui(self): - self.action = QAction("Consolidate OFDS", iface.mainWindow()) - self.action.setObjectName("consolidateOfdsAction") - self.action.triggered.connect(self.run) - iface.addToolBarIcon(self.action) - iface.addPluginToMenu("&Consolidate OFDS", self.action) + self.action_consolidate = QAction("Consolidate OFDS", iface.mainWindow()) + self.action_consolidate.setObjectName("consolidateOfdsAction") + self.action_consolidate.triggered.connect(self.run_consolidate) + iface.addToolBarIcon(self.action_consolidate) + iface.addPluginToMenu("&OFDS", self.action_consolidate) + + self.action_style = QAction("Style OFDS", iface.mainWindow()) + self.action_style.setObjectName("styleOfdsAction") + self.action_style.triggered.connect(self.run_style) + iface.addToolBarIcon(self.action_style) + iface.addPluginToMenu("&OFDS", self.action_style) def unload(self): - iface.removeToolBarIcon(self.action) - iface.removePluginToMenu("&Consolidate OFDS", self.action) - del self.action + iface.removePluginMenu("&OFDS", self.action_consolidate) + iface.removeToolBarIcon(self.action_consolidate) + del self.action_consolidate + + iface.removePluginMenu("&OFDS", self.action_style) + iface.removeToolBarIcon(self.action_style) + del self.action_style + + self.consolidate_tool_dialog.close() + del self.consolidate_tool_dialog + + self.style_tool_dialog.close() + del self.style_tool_dialog - def run(self): - logger.debug("Running OFDS Consolidation plugin") + def run_consolidate(self): + logger.debug("Opening OFDS Consolidate Tool Dialog") logger.debug( f"Qt: v{QT_VERSION_STR} PyQt: v{PYQT_VERSION_STR} Python: {sys.version}" ) @@ -37,5 +58,14 @@ def run(self): if not project: raise Exception - self.tool_dialog.reset(project=project) - self.tool_dialog.show() + self.consolidate_tool_dialog.reset(project=project) + self.consolidate_tool_dialog.show() + + def run_style(self): + logger.debug("Opening OFDS Style Tool Dialog") + project = QgsProject.instance() + if not project: + raise Exception + + self.style_tool_dialog.refresh_layers() + self.style_tool_dialog.show() diff --git a/scripts/compile-gui.sh b/scripts/compile-gui.sh index e8b9ec1..d65eaf1 100755 --- a/scripts/compile-gui.sh +++ b/scripts/compile-gui.sh @@ -2,3 +2,4 @@ set -euo pipefail pyuic5 gui.ui -o gui.py +pyuic5 gui_style.ui -o gui_style.py diff --git a/tool/style.py b/tool/style.py new file mode 100644 index 0000000..54912fe --- /dev/null +++ b/tool/style.py @@ -0,0 +1,86 @@ +import logging +from typing import List, cast + +from PyQt5.QtWidgets import QDialog +from qgis.core import ( + QgsProject, + QgsMapLayer, + QgsWkbTypes, + QgsVectorLayer, + QgsMapLayerType, +) + +from ..resources import STYLESHEET_NODES, STYLESHEET_SPANS +from ..gui_style import Ui_StyleOFDSDialog + +logger = logging.getLogger(__name__) + + +class OFDSStyleToolDialog(QDialog): + """ + The top-level GUI element, the Dialog box that holds all our other sub-elements. + + Note that there is a singleton instance of this class, it isn't created/destroyed + each time we open the tool, but it remains constantly "open" in the backgroud even + when closed or hidden, and all state is still the same next time the tool is opened + from the toolbar. This means it's important to properly tidy up the GUI and any + created/opened resources when we're done. + """ + + ui: Ui_StyleOFDSDialog + + def __init__(self): + super().__init__() + + self.ui = Ui_StyleOFDSDialog() + self.ui.setupUi(self) + + # Connect UI signals to slots on this class + self.ui.nodesApplyButton.pressed.connect(self.apply_nodes_style) + self.ui.spansApplyButton.pressed.connect(self.apply_spans_style) + self.ui.refreshButton.pressed.connect(self.refresh_layers) + + def refresh_layers(self): + """Refresh and populate the lists of layers from the current Project""" + + project = QgsProject.instance() + + # Find all the layers the user has loaded + all_layers: List[QgsMapLayer] = project.mapLayers(validOnly=True).values() + + # Filter the layers that the tool can accept + selectable_layers: List[QgsVectorLayer] = list() + + for layer in all_layers: + # only vector layers can be valid OFDS layers + if layer.type() == QgsMapLayerType.VectorLayer: + selectable_layers.append(cast(QgsVectorLayer, layer)) + + ncb = self.ui.nodesComboBox + scb = self.ui.spansComboBox + + ncb.clear() + for layer in selectable_layers: + if layer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry: + ncb.addItem(layer.name(), layer) + + scb.clear() + for layer in selectable_layers: + if layer.geometryType() == QgsWkbTypes.GeometryType.LineGeometry: + scb.addItem(layer.name(), layer) + + def apply_nodes_style(self): + """Apply the Nodes style to the currently selected Nodes layer""" + nodes_layer = self.ui.nodesComboBox.currentData() + if isinstance(nodes_layer, QgsVectorLayer): + if nodes_layer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry: + nodes_layer.loadNamedStyle(STYLESHEET_NODES.as_posix(), False) + nodes_layer.triggerRepaint() + + def apply_spans_style(self): + """Apply the Spans style to the currently selected Spans layer""" + spans_layer = self.ui.spansComboBox.currentData() + if isinstance(spans_layer, QgsVectorLayer): + if spans_layer.geometryType() == QgsWkbTypes.GeometryType.LineGeometry: + spans_layer.loadNamedStyle(STYLESHEET_SPANS.as_posix(), False) + spans_layer.triggerRepaint()