From 9559ca0583f216639b44634941a4ffa4c7b2405c Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Fri, 24 Jan 2025 12:36:35 +0200 Subject: [PATCH 1/3] add regulation group dock - add regulation group dock widget - add disconnect_signal util function --- arho_feature_template/core/models.py | 8 +- arho_feature_template/core/plan_manager.py | 93 +++++++++++-- ..._form.py => plan_regulation_group_form.py} | 48 +++++-- ..._form.ui => plan_regulation_group_form.ui} | 0 .../gui/docks/regulation_groups_dock.py | 125 ++++++++++++++++++ .../gui/docks/regulation_groups_dock.ui | 87 ++++++++++++ arho_feature_template/plugin.py | 42 +++++- .../project/layers/plan_layers.py | 6 +- arho_feature_template/utils/misc_utils.py | 13 +- 9 files changed, 385 insertions(+), 37 deletions(-) rename arho_feature_template/gui/dialogs/{new_plan_regulation_group_form.py => plan_regulation_group_form.py} (79%) rename arho_feature_template/gui/dialogs/{new_plan_regulation_group_form.ui => plan_regulation_group_form.ui} (100%) create mode 100644 arho_feature_template/gui/docks/regulation_groups_dock.py create mode 100644 arho_feature_template/gui/docks/regulation_groups_dock.ui diff --git a/arho_feature_template/core/models.py b/arho_feature_template/core/models.py index 6fff605..ad49087 100644 --- a/arho_feature_template/core/models.py +++ b/arho_feature_template/core/models.py @@ -294,10 +294,10 @@ class Proposition: @dataclass class RegulationGroup: - type_code_id: str | None - name: str | None - short_name: str | None - color_code: str | None + type_code_id: str | None = None + name: str | None = None + short_name: str | None = None + color_code: str | None = None group_number: int | None = None regulations: list[Regulation] = field(default_factory=list) propositions: list[Proposition] = field(default_factory=list) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index dc1cf68..8a5f904 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -13,16 +13,18 @@ FeatureTemplateLibrary, Plan, PlanFeature, + RegulationGroup, RegulationGroupCategory, RegulationGroupLibrary, ) from arho_feature_template.exceptions import UnsavedChangesError from arho_feature_template.gui.dialogs.load_plan_dialog import LoadPlanDialog -from arho_feature_template.gui.dialogs.new_plan_regulation_group_form import NewPlanRegulationGroupForm from arho_feature_template.gui.dialogs.plan_attribute_form import PlanAttributeForm from arho_feature_template.gui.dialogs.plan_feature_form import PlanFeatureForm +from arho_feature_template.gui.dialogs.plan_regulation_group_form import PlanRegulationGroupForm from arho_feature_template.gui.dialogs.serialize_plan import SerializePlan from arho_feature_template.gui.docks.new_feature_dock import NewFeatureDock +from arho_feature_template.gui.docks.regulation_groups_dock import RegulationGroupsDock from arho_feature_template.gui.tools.inspect_plan_features_tool import InspectPlanFeatures from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer, code_layers from arho_feature_template.project.layers.plan_layers import ( @@ -53,7 +55,7 @@ if TYPE_CHECKING: from qgis.core import QgsFeature - from arho_feature_template.core.models import Proposition, Regulation, RegulationGroup + from arho_feature_template.core.models import Proposition, Regulation logger = logging.getLogger(__name__) @@ -87,6 +89,15 @@ def __init__(self): self.new_feature_dock.tool_activated.connect(self.add_new_plan_feature) self.new_feature_dock.hide() + # Initialize regulation groups dock + self.regulation_groups_dock = RegulationGroupsDock() + self.regulation_groups_dock.new_regulation_group_requested.connect(self.create_new_regulation_group) + self.regulation_groups_dock.edit_regulation_group_requested.connect(self.edit_regulation_group) + self.regulation_groups_dock.delete_regulation_group_requested.connect(self.delete_regulation_group) + if get_active_plan_id(): + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) + self.regulation_groups_dock.hide() + # Initialize digitize tools self.plan_digitize_map_tool = PlanDigitizeMapTool(iface.mapCanvas(), iface.cadDockWidget()) self.plan_digitize_map_tool.digitizingCompleted.connect(self._plan_geom_digitized) @@ -135,6 +146,25 @@ def initialize_libraries(self): ] self.new_feature_dock.initialize_feature_template_libraries(self.feature_template_libraries) + def create_new_regulation_group(self): + self._open_regulation_group_form(RegulationGroup()) + + def edit_regulation_group(self, regulation_group: RegulationGroup): + self._open_regulation_group_form(regulation_group) + + def _open_regulation_group_form(self, regulation_group: RegulationGroup): + regulation_group_form = PlanRegulationGroupForm(regulation_group) + if regulation_group_form.exec_(): + if regulation_group_form.save_as_config is True: + save_regulation_group_as_config(regulation_group_form.model) + else: + save_regulation_group(regulation_group_form.model) + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) + + def delete_regulation_group(self, group: RegulationGroup): + delete_regulation_group(group) + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) + def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 if activate: self.previous_map_tool = iface.mapCanvas().mapTool() @@ -202,6 +232,7 @@ def edit_plan(self): attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) def add_new_plan_feature(self): if not handle_unsaved_changes(): @@ -261,6 +292,7 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) def edit_plan_feature(self, feature: QgsFeature, layer_name: str): layer_class = FEATURE_LAYER_NAME_TO_CLASS_MAP[layer_name] @@ -273,6 +305,7 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) def set_active_plan(self, plan_id: str | None): """Update the project layers based on the selected land use plan. @@ -297,6 +330,9 @@ def set_active_plan(self, plan_id: str | None): if previously_in_edit_mode: plan_layer.startEditing() + # Update regulation group dock + self.regulation_groups_dock.initialize_regulation_groups(regulation_group_library_from_active_plan()) + def load_land_use_plan(self): """Load an existing land use plan using a dialog selection.""" connection_names = get_existing_database_connection_names() @@ -364,14 +400,6 @@ def save_plan_jsons(self, plan_json, outline_json): "Kaava ja sen ulkoraja tallennettu onnistuneesti.", ) - def create_new_regulation_group(self): - new_regulation_group_form = NewPlanRegulationGroupForm() - if new_regulation_group_form.exec_(): - if new_regulation_group_form.save_as_config is True: - save_regulation_group_as_config(new_regulation_group_form.model) - else: - save_regulation_group(new_regulation_group_form.model) - def unload(self): # Lambda service self.lambda_service.jsons_received.disconnect(self.save_plan_jsons) @@ -396,6 +424,13 @@ def unload(self): iface.removeDockWidget(self.new_feature_dock) self.new_feature_dock.deleteLater() + # Regulation group dock + self.regulation_groups_dock.new_regulation_group_requested.disconnect() + self.regulation_groups_dock.edit_regulation_group_requested.disconnect() + self.regulation_groups_dock.delete_regulation_group_requested.disconnect() + iface.removeDockWidget(self.regulation_groups_dock) + self.regulation_groups_dock.deleteLater() + def regulation_group_library_from_active_plan() -> RegulationGroupLibrary: category_features = list(PlanRegulationGroupTypeLayer.get_features()) @@ -548,6 +583,30 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None return feature +def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None): + if regulation_group.id_ is None: + return + + feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) + layer = RegulationGroupLayer.get_from_project() + + # Handle regulations + for regulation in regulation_group.regulations: + delete_regulation(regulation) + + # Handle propositions + for proposition in regulation_group.propositions: + delete_proposition(proposition) + + _delete_feature(feature, layer, "Kaavamääräysryhmän poisto") + + # Handle assocations + associations = RegulationGroupAssociationLayer.get_associations_for_regulation_group(str(regulation_group.id_)) + association_layer = RegulationGroupAssociationLayer.get_from_project() + for association in associations: + _delete_feature(association, association_layer, "Kaavamääräysryhmän assosiaation poisto") + + def save_regulation_group_as_config(regulation_group: RegulationGroup): pass @@ -575,6 +634,13 @@ def save_regulation(regulation: Regulation) -> QgsFeature: return feature +def delete_regulation(regulation: Regulation): + feature = PlanRegulationLayer.feature_from_model(regulation) + layer = PlanRegulationLayer.get_from_project() + + _delete_feature(feature, layer, "Kaavamääräyksen poisto") + + def save_proposition(proposition: Proposition) -> QgsFeature: feature = PlanPropositionLayer.feature_from_model(proposition) layer = PlanPropositionLayer.get_from_project() @@ -587,3 +653,10 @@ def save_proposition(proposition: Proposition) -> QgsFeature: ) return feature + + +def delete_proposition(proposition: Proposition): + feature = PlanPropositionLayer.feature_from_model(proposition) + layer = PlanPropositionLayer.get_from_project() + + _delete_feature(feature, layer, "Kaavasuosituksen poisto") diff --git a/arho_feature_template/gui/dialogs/new_plan_regulation_group_form.py b/arho_feature_template/gui/dialogs/plan_regulation_group_form.py similarity index 79% rename from arho_feature_template/gui/dialogs/new_plan_regulation_group_form.py rename to arho_feature_template/gui/dialogs/plan_regulation_group_form.py index 233ef29..db0c100 100644 --- a/arho_feature_template/gui/dialogs/new_plan_regulation_group_form.py +++ b/arho_feature_template/gui/dialogs/plan_regulation_group_form.py @@ -26,14 +26,14 @@ from arho_feature_template.gui.components.code_combobox import CodeComboBox -ui_path = resources.files(__package__) / "new_plan_regulation_group_form.ui" +ui_path = resources.files(__package__) / "plan_regulation_group_form.ui" FormClass, _ = uic.loadUiType(ui_path) -class NewPlanRegulationGroupForm(QDialog, FormClass): # type: ignore +class PlanRegulationGroupForm(QDialog, FormClass): # type: ignore """Form to create a new plan regulation group.""" - def __init__(self): + def __init__(self, regulation_group: RegulationGroup): super().__init__() self.setupUi(self) @@ -56,6 +56,12 @@ def __init__(self): self.button_box: QDialogButtonBox # INIT + self.regulation_group = regulation_group + self.regulation_widgets: list[RegulationWidget] = [] + self.proposition_widgets: list[PropositionWidget] = [] + self.save_as_config = False + + # Initialize regulation library self.regulations_selection_widget = TreeWithSearchWidget() self.regulations_tree_layout.insertWidget(1, self.regulations_selection_widget) self.regulations_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_regulation) @@ -64,14 +70,25 @@ def __init__(self): self._initalize_regulation_from_config(config) self.type_of_regulation_group.populate_from_code_layer(PlanRegulationGroupTypeLayer) - self.type_of_regulation_group.removeItem(0) # Remove NULL from combobox as underground data is required - self.button_box.accepted.connect(self._on_ok_clicked) - self.add_proposition_btn.clicked.connect(self.add_proposition) + self.type_of_regulation_group.removeItem(0) # Remove NULL from selections + + self.add_proposition_btn.clicked.connect(self.add_new_proposition) self.add_proposition_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) - self.regulation_widgets: list[RegulationWidget] = [] - self.proposition_widgets: list[PropositionWidget] = [] - self.save_as_config = False + self.button_box.accepted.connect(self._on_ok_clicked) + + # Initialize from model + self.name.setText(self.regulation_group.name if self.regulation_group.name else "") + self.short_name.setText(self.regulation_group.short_name if self.regulation_group.short_name else "") + self.group_number.setValue(self.regulation_group.group_number if self.regulation_group.group_number else 0) + self.color_code.setText(self.regulation_group.color_code if self.regulation_group.color_code else "") + self.type_of_regulation_group.set_value(self.regulation_group.type_code_id) + + for regulation in self.regulation_group.regulations: + self.add_regulation(regulation) + + for proposition in self.regulation_group.propositions: + self.add_proposition(proposition) def _initalize_regulation_from_config(self, config: RegulationConfig, parent: QTreeWidgetItem | None = None): item = self.regulations_selection_widget.add_item_to_tree(config.name, config, parent) @@ -88,10 +105,10 @@ def add_selected_regulation(self, item: QTreeWidgetItem, column: int): config: RegulationConfig = item.data(column, Qt.UserRole) # Retrieve the associated config if config.category_only: return - self.add_regulation(config) - - def add_regulation(self, config: RegulationConfig): regulation = Regulation(config=config) + self.add_regulation(regulation) + + def add_regulation(self, regulation: Regulation): widget = RegulationWidget(regulation, parent=self.regulations_scroll_area_contents) widget.delete_signal.connect(self.delete_regulation) index = self.regulations_layout.count() - 1 @@ -104,8 +121,11 @@ def delete_regulation(self, regulation_widget: RegulationWidget): self.regulation_widgets.remove(regulation_widget) regulation_widget.deleteLater() - def add_proposition(self): + def add_new_proposition(self): proposition = Proposition(name="", value="") + self.add_proposition(proposition) + + def add_proposition(self, proposition: Proposition): widget = PropositionWidget(proposition, parent=self.propositions_scroll_contents) widget.delete_signal.connect(self.delete_proposition) self.propositions_layout.insertWidget(1, widget) @@ -126,7 +146,7 @@ def into_model(self) -> RegulationGroup: group_number=self.group_number.value() if self.group_number.value() > 0 else None, regulations=[widget.into_model() for widget in self.regulation_widgets], propositions=[widget.into_model() for widget in self.proposition_widgets], - id_=None, + id_=self.regulation_group.id_, ) def _on_ok_clicked(self): diff --git a/arho_feature_template/gui/dialogs/new_plan_regulation_group_form.ui b/arho_feature_template/gui/dialogs/plan_regulation_group_form.ui similarity index 100% rename from arho_feature_template/gui/dialogs/new_plan_regulation_group_form.ui rename to arho_feature_template/gui/dialogs/plan_regulation_group_form.ui diff --git a/arho_feature_template/gui/docks/regulation_groups_dock.py b/arho_feature_template/gui/docks/regulation_groups_dock.py new file mode 100644 index 0000000..5922e8c --- /dev/null +++ b/arho_feature_template/gui/docks/regulation_groups_dock.py @@ -0,0 +1,125 @@ +from importlib import resources +from typing import TYPE_CHECKING + +from qgis.core import QgsApplication +from qgis.gui import QgsDockWidget +from qgis.PyQt import uic +from qgis.PyQt.QtCore import Qt, pyqtSignal +from qgis.PyQt.QtWidgets import QListWidget, QListWidgetItem, QMessageBox, QPushButton + +from arho_feature_template.core.models import RegulationGroup, RegulationGroupLibrary +from arho_feature_template.utils.misc_utils import disconnect_signal + +if TYPE_CHECKING: + from qgis.gui import QgsFilterLineEdit + from qgis.PyQt.QtWidgets import QWidget + + +ui_path = resources.files(__package__) / "regulation_groups_dock.ui" +DockClass, _ = uic.loadUiType(ui_path) + + +class RegulationGroupsDock(QgsDockWidget, DockClass): # type: ignore + new_regulation_group_requested = pyqtSignal() + edit_regulation_group_requested = pyqtSignal(RegulationGroup) + delete_regulation_group_requested = pyqtSignal(RegulationGroup) + + def __init__(self): + super().__init__() + self.setupUi(self) + + # TYPES + self.search_box: QgsFilterLineEdit + self.regulation_group_list: QListWidget + self.dockWidgetContents: QWidget + + self.new_btn: QPushButton + self.delete_btn: QPushButton + self.edit_btn: QPushButton + + self.new_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) + self.delete_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) + self.edit_btn.setIcon(QgsApplication.getThemeIcon("mActionEditTable.svg")) + # QgsApplication.getThemeIcon("symbologyEdit.svg") + + self.connect_buttons() + + self.regulation_group_list.setSelectionMode(self.regulation_group_list.SingleSelection) + self.regulation_group_list.clicked.connect(self.on_regulation_group_item_clicked) + + self.search_box.valueChanged.connect(self.filter_regulation_groups) + + self.selected_group = None + + def connect_buttons(self): + disconnect_signal(self.new_btn.clicked) + disconnect_signal(self.edit_btn.clicked) + disconnect_signal(self.delete_btn.clicked) + + self.new_btn.clicked.connect(self.new_regulation_group_requested.emit) + self.edit_btn.clicked.connect(self.on_edit_btn_clicked) + self.delete_btn.clicked.connect(self.on_delete_btn_clicked) + + def initialize_regulation_groups(self, regulation_group_library: RegulationGroupLibrary): + self.regulation_group_list.clear() + + for category in regulation_group_library.regulation_group_categories: + for group in category.regulation_groups: + self.add_regulation_group_to_list(group) + + self.selected_group = None + + def add_regulation_group_to_list(self, group: RegulationGroup): + short_name = group.short_name if group.short_name else "" + name = group.name if group.name else "" + if short_name and name: + text = f"{short_name} - {name}" + elif short_name: + text = short_name + else: + text = name + item = QListWidgetItem(text) + item.setToolTip(name) + item.setData(Qt.UserRole, group) + self.regulation_group_list.addItem(item) + + def on_regulation_group_item_clicked(self, index: int): + item = self.regulation_group_list.itemFromIndex(index) + group: RegulationGroup = item.data(Qt.UserRole) + # Clicked new list item => activate new group + if group != self.selected_group: + self.selected_group = group + # NOTE: Workaround to fix make sure clicked item is selected. At least after a selected item has been + # filtered out, clicking on a new template will select, immediately deselect (but keep focus) + self.regulation_group_list.setCurrentItem(item) + else: + # Clicked selected list item => clear selection + self.selected_group = None + self.regulation_group_list.clearSelection() + + def on_edit_btn_clicked(self): + if self.selected_group: + self.edit_regulation_group_requested.emit(self.selected_group) + + def on_delete_btn_clicked(self): + if self.selected_group: + response = QMessageBox.question( + None, + "Kaavamääräysryhmän poisto", + "Haluatko varmasti poistaa kaavamääräysryhmän?", + QMessageBox.Yes | QMessageBox.No, + ) + if response == QMessageBox.Yes: + self.delete_regulation_group_requested.emit(self.selected_group) + + def filter_regulation_groups(self): + search_text = self.search_box.value().lower() + + for index in range(self.regulation_group_list.count()): + item = self.regulation_group_list.item(index) + regulation_group: RegulationGroup = item.data(Qt.UserRole) + item.setHidden(search_text not in item.text().lower()) + + # Clear selection if the selected template was filtered + if self.selected_group and self.selected_group is regulation_group and item.isHidden(): + self.regulation_group_list.clearSelection() diff --git a/arho_feature_template/gui/docks/regulation_groups_dock.ui b/arho_feature_template/gui/docks/regulation_groups_dock.ui new file mode 100644 index 0000000..96c04c2 --- /dev/null +++ b/arho_feature_template/gui/docks/regulation_groups_dock.ui @@ -0,0 +1,87 @@ + + + DockWidget + + + + 0 + 0 + 322 + 503 + + + + Hallitse kaavamääräysryhmiä + + + + + + + + + Luo uusi kaavamääräysryhmä + + + Luo + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Muokkaa valittua kaavamääräysryhmää + + + Muokkaa + + + + + + + Poista valittu kaavamääräysryhmä + + + Poista + + + + + + + + + true + + + + + + + + + + + + QgsFilterLineEdit + QLineEdit +
qgsfilterlineedit.h
+
+
+ + +
diff --git a/arho_feature_template/plugin.py b/arho_feature_template/plugin.py index ad5e98e..0c43bd7 100644 --- a/arho_feature_template/plugin.py +++ b/arho_feature_template/plugin.py @@ -136,7 +136,8 @@ def initGui(self) -> None: # noqa N802 iface.addDockWidget(Qt.RightDockWidgetArea, self.plan_manager.new_feature_dock) self.plan_manager.new_feature_dock.setUserVisible(False) - self.plan_manager.new_feature_dock.visibilityChanged.connect(self.dock_visibility_changed) + + self.plan_manager.new_feature_dock.visibilityChanged.connect(self.new_feature_dock_visibility_changed) self.validation_dock = ValidationDock() iface.addDockWidget(Qt.RightDockWidgetArea, self.validation_dock) @@ -145,12 +146,24 @@ def initGui(self) -> None: # noqa N802 iface.mapCanvas().mapToolSet.connect(self.plan_manager.plan_digitize_map_tool.deactivate) + # Regulation groups dock + iface.addDockWidget(Qt.RightDockWidgetArea, self.plan_manager.regulation_groups_dock) + self.plan_manager.regulation_groups_dock.setUserVisible(False) + self.plan_manager.regulation_groups_dock.visibilityChanged.connect( + self.regulation_groups_dock_visibility_changed + ) + # Try initializing the plugin immediately in case the project is already open self.plan_manager.initialize_from_project() # (Re)initialize whenever a project is opened iface.projectRead.connect(self.plan_manager.initialize_from_project) + # icons to consider: + # icon=QgsApplication.getThemeIcon("mActionStreamingDigitize.svg"), + # icon=QgsApplication.getThemeIcon("mIconGeometryCollectionLayer.svg"), + # icon=QgsApplication.getThemeIcon("mActionSharingExport.svg"), + self.new_land_use_plan_action = self.add_action( text="Luo kaava", icon=QgsApplication.getThemeIcon("mActionNewMap.svg"), @@ -198,10 +211,19 @@ def initGui(self) -> None: # noqa N802 add_to_toolbar=True, ) - self.new_plan_regulation_group = self.add_action( - text="Luo kaavamääräysryhmä", + # self.new_plan_regulation_group = self.add_action( + # text="Luo kaavamääräysryhmä", + # icon=QgsApplication.getThemeIcon("mActionAddManualTable.svg"), + # triggered_callback=self.open_plan_regulation_group_form, + # add_to_menu=True, + # add_to_toolbar=True, + # ) + + self.regulation_groups_dock_action = self.add_action( + text="Hallitse kaavamääräysryhmiä", icon=QgsApplication.getThemeIcon("mActionAddManualTable.svg"), - triggered_callback=self.open_plan_regulation_group_form, + toggled_callback=self.toggle_regulation_groups_dock, + checkable=True, add_to_menu=True, add_to_toolbar=True, ) @@ -277,17 +299,23 @@ def unload(self) -> None: # Handle logger teardown_logger(Plugin.name) - def dock_visibility_changed(self, visible: bool) -> None: # noqa: FBT001 + def new_feature_dock_visibility_changed(self, visible: bool) -> None: # noqa: FBT001 self.new_feature_dock_action.setChecked(visible) def toggle_new_feature_dock(self, show: bool) -> None: # noqa: FBT001 self.plan_manager.new_feature_dock.setUserVisible(show) + def regulation_groups_dock_visibility_changed(self, visible: bool) -> None: # noqa: FBT001 + self.regulation_groups_dock_action.setChecked(visible) + + def toggle_regulation_groups_dock(self, show: bool) -> None: # noqa: FBT001 + self.plan_manager.regulation_groups_dock.setUserVisible(show) + def validation_dock_visibility_changed(self, visible: bool) -> None: # noqa: FBT001 self.validation_dock_action.setChecked(visible) def toggle_validation_dock(self, show: bool) -> None: # noqa: FBT001 self.validation_dock.setUserVisible(show) - def open_plan_regulation_group_form(self): - self.plan_manager.create_new_regulation_group() + # def open_plan_regulation_group_form(self): + # self.plan_manager.create_new_regulation_group() diff --git a/arho_feature_template/project/layers/plan_layers.py b/arho_feature_template/project/layers/plan_layers.py index fff22f8..9437718 100644 --- a/arho_feature_template/project/layers/plan_layers.py +++ b/arho_feature_template/project/layers/plan_layers.py @@ -277,6 +277,10 @@ def get_associations_for_feature(cls, feature_id: str, layer_name: str) -> Gener raise LayerNotFoundError(layer_name) return cls.get_features_by_attribute_value(attribute, feature_id) + @classmethod + def get_associations_for_regulation_group(cls, group_id: str) -> Generator[QgsFeature]: + return cls.get_features_by_attribute_value("plan_regulation_group_id", group_id) + @classmethod def get_group_ids_for_feature(cls, feature_id: str, layer_name: str) -> Generator[str]: attribute = cls.layer_name_to_attribute_map.get(layer_name) @@ -285,7 +289,7 @@ def get_group_ids_for_feature(cls, feature_id: str, layer_name: str) -> Generato return cls.get_attribute_values_by_another_attribute_value("plan_regulation_group_id", attribute, feature_id) @classmethod - def get_dangling_associations( + def get_dangling_associations( # by_feature cls, groups: list[RegulationGroup], feature_id: str, layer_name: str ) -> list[QgsFeature]: associations = RegulationGroupAssociationLayer.get_associations_for_feature(feature_id, layer_name) diff --git a/arho_feature_template/utils/misc_utils.py b/arho_feature_template/utils/misc_utils.py index 178c268..00aeaf4 100644 --- a/arho_feature_template/utils/misc_utils.py +++ b/arho_feature_template/utils/misc_utils.py @@ -1,10 +1,11 @@ from __future__ import annotations import os +from contextlib import suppress from typing import TYPE_CHECKING, cast from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings +from qgis.PyQt.QtCore import QSettings, pyqtBoundSignal from qgis.PyQt.QtWidgets import QMessageBox from qgis.utils import iface @@ -97,3 +98,13 @@ def get_settings(): proxy_port = settings.value("proxy_port", "5443") lambda_url = settings.value("lambda_url", "https://t5w26iqnsf.execute-api.eu-central-1.amazonaws.com/v0/ryhti") return proxy_host, proxy_port, lambda_url + + +def disconnect_signal(signal: pyqtBoundSignal) -> None: + """ + Disconnects all existing connections of a given signal. + + If no connections are defined for the signal, ignores the raised error silently. + """ + with suppress(TypeError): + signal.disconnect() From 8a54839fc52a23a0527571670b6e461894bd6b2b Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Fri, 24 Jan 2025 14:15:24 +0200 Subject: [PATCH 2/3] add possibility to open regulation group as form/dialog from plan feature form dialog --- .../plan_regulation_group_widget.py | 45 ++++++++++++------- .../plan_regulation_group_widget.ui | 25 +++++++++++ .../gui/dialogs/plan_attribute_form.py | 10 +++++ .../gui/dialogs/plan_feature_form.py | 10 +++++ 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/arho_feature_template/gui/components/plan_regulation_group_widget.py b/arho_feature_template/gui/components/plan_regulation_group_widget.py index cc8b79e..9f56fa3 100644 --- a/arho_feature_template/gui/components/plan_regulation_group_widget.py +++ b/arho_feature_template/gui/components/plan_regulation_group_widget.py @@ -6,12 +6,14 @@ from qgis.core import QgsApplication from qgis.PyQt import uic from qgis.PyQt.QtCore import pyqtSignal +from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QWidget from arho_feature_template.core.models import Proposition, Regulation, RegulationGroup from arho_feature_template.gui.components.plan_proposition_widget import PropositionWidget from arho_feature_template.gui.components.plan_regulation_widget import RegulationWidget from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer +from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path if TYPE_CHECKING: from qgis.PyQt.QtWidgets import QFormLayout, QFrame, QLabel, QLineEdit, QPushButton @@ -23,9 +25,10 @@ class RegulationGroupWidget(QWidget, FormClass): # type: ignore """A widget representation of a plan regulation group.""" + open_as_form_signal = pyqtSignal(QWidget) delete_signal = pyqtSignal(QWidget) - def __init__(self, regulation_group_data: RegulationGroup, layer_name: str): + def __init__(self, regulation_group: RegulationGroup, layer_name: str): super().__init__() self.setupUi(self) @@ -34,24 +37,36 @@ def __init__(self, regulation_group_data: RegulationGroup, layer_name: str): self.name: QLineEdit self.short_name: QLineEdit self.short_name_label: QLabel + self.edit_btn: QPushButton self.del_btn: QPushButton self.regulation_group_details_layout: QFormLayout # INIT - self.regulation_group_data = regulation_group_data - self.regulation_widgets: list[RegulationWidget] = [ - self.add_regulation_widget(regulation) for regulation in self.regulation_group_data.regulations - ] - self.proposition_widgets: list[PropositionWidget] = [ - self.add_proposition_widget(proposition) for proposition in self.regulation_group_data.propositions - ] - - regulation_group_data.type_code_id = PlanRegulationGroupTypeLayer.get_id_by_feature_layer_name(layer_name) - self.name.setText(self.regulation_group_data.name if self.regulation_group_data.name else "") - self.short_name.setText(self.regulation_group_data.short_name if self.regulation_group_data.short_name else "") + self.regulation_widgets: list[RegulationWidget] = [] + self.proposition_widgets: list[PropositionWidget] = [] + + regulation_group.type_code_id = PlanRegulationGroupTypeLayer.get_id_by_feature_layer_name(layer_name) + self.from_model(regulation_group) + + self.edit_btn.setIcon(QIcon(resources_path("icons", "settings.svg"))) + self.edit_btn.clicked.connect(lambda: self.open_as_form_signal.emit(self)) self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) self.del_btn.clicked.connect(lambda: self.delete_signal.emit(self)) + def from_model(self, regulation_group: RegulationGroup): + self.regulation_group = regulation_group + + self.name.setText(regulation_group.name if regulation_group.name else "") + self.short_name.setText(regulation_group.short_name if regulation_group.short_name else "") + + # Remove existing child widgets if reinitializing + for widget in self.regulation_widgets: + self.delete_regulation_widget(widget) + for widget in self.proposition_widgets: + self.delete_proposition_widget(widget) + self.regulation_widgets = [self.add_regulation_widget(reg) for reg in regulation_group.regulations] + self.proposition_widgets = [self.add_proposition_widget(prop) for prop in regulation_group.propositions] + def add_regulation_widget(self, regulation: Regulation) -> RegulationWidget: widget = RegulationWidget(regulation=regulation, parent=self.frame) widget.delete_signal.connect(self.delete_regulation_widget) @@ -76,11 +91,11 @@ def delete_proposition_widget(self, proposition_widget: RegulationWidget): def into_model(self) -> RegulationGroup: return RegulationGroup( - type_code_id=self.regulation_group_data.type_code_id, + type_code_id=self.regulation_group.type_code_id, name=self.name.text(), short_name=None if not self.short_name.text() else self.short_name.text(), - color_code=self.regulation_group_data.color_code, + color_code=self.regulation_group.color_code, regulations=[widget.into_model() for widget in self.regulation_widgets], propositions=[widget.into_model() for widget in self.proposition_widgets], - id_=self.regulation_group_data.id_, + id_=self.regulation_group.id_, ) diff --git a/arho_feature_template/gui/components/plan_regulation_group_widget.ui b/arho_feature_template/gui/components/plan_regulation_group_widget.ui index 6a66d13..ab36df9 100644 --- a/arho_feature_template/gui/components/plan_regulation_group_widget.ui +++ b/arho_feature_template/gui/components/plan_regulation_group_widget.ui @@ -68,6 +68,28 @@ + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Muokkaa kaavamääräysryhmää + + + + + + @@ -82,6 +104,9 @@ 16777215 + + Poista kaavamääräysryhmä + diff --git a/arho_feature_template/gui/dialogs/plan_attribute_form.py b/arho_feature_template/gui/dialogs/plan_attribute_form.py index e5dc281..2cae050 100644 --- a/arho_feature_template/gui/dialogs/plan_attribute_form.py +++ b/arho_feature_template/gui/dialogs/plan_attribute_form.py @@ -19,11 +19,13 @@ from arho_feature_template.core.models import Plan, RegulationGroup, RegulationGroupLibrary from arho_feature_template.gui.components.plan_regulation_group_widget import RegulationGroupWidget from arho_feature_template.gui.components.tree_with_search_widget import TreeWithSearchWidget +from arho_feature_template.gui.dialogs.plan_regulation_group_form import PlanRegulationGroupForm from arho_feature_template.project.layers.code_layers import ( LifeCycleStatusLayer, OrganisationLayer, PlanTypeLayer, ) +from arho_feature_template.utils.misc_utils import disconnect_signal if TYPE_CHECKING: from qgis.PyQt.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget @@ -132,12 +134,20 @@ def add_selected_plan_regulation_group(self, item: QTreeWidgetItem, column: int) def add_plan_regulation_group(self, regulation_group: RegulationGroup): regulation_group_widget = RegulationGroupWidget(regulation_group, layer_name="Kaava") regulation_group_widget.delete_signal.connect(self.remove_plan_regulation_group) + regulation_group_widget.open_as_form_signal.connect(self.open_plan_regulation_group_form) self._remove_spacer() self.plan_regulation_group_scrollarea_contents.layout().addWidget(regulation_group_widget) self.regulation_group_widgets.append(regulation_group_widget) self._add_spacer() + def open_plan_regulation_group_form(self, regulation_group_widget: RegulationGroupWidget): + group_as_form = PlanRegulationGroupForm(regulation_group_widget.into_model()) + if group_as_form.exec_(): + regulation_group_widget.from_model(group_as_form.model) + def remove_plan_regulation_group(self, regulation_group_widget: RegulationGroupWidget): + disconnect_signal(regulation_group_widget.delete_signal) + disconnect_signal(regulation_group_widget.open_as_form_signal) self.plan_regulation_group_scrollarea_contents.layout().removeWidget(regulation_group_widget) self.regulation_group_widgets.remove(regulation_group_widget) regulation_group_widget.deleteLater() diff --git a/arho_feature_template/gui/dialogs/plan_feature_form.py b/arho_feature_template/gui/dialogs/plan_feature_form.py index 94bd839..bf59490 100644 --- a/arho_feature_template/gui/dialogs/plan_feature_form.py +++ b/arho_feature_template/gui/dialogs/plan_feature_form.py @@ -21,7 +21,9 @@ from arho_feature_template.core.models import PlanFeature, RegulationGroup from arho_feature_template.gui.components.plan_regulation_group_widget import RegulationGroupWidget from arho_feature_template.gui.components.tree_with_search_widget import TreeWithSearchWidget +from arho_feature_template.gui.dialogs.plan_regulation_group_form import PlanRegulationGroupForm from arho_feature_template.project.layers.code_layers import UndergroundTypeLayer +from arho_feature_template.utils.misc_utils import disconnect_signal if TYPE_CHECKING: from qgis.PyQt.QtWidgets import QVBoxLayout, QWidget @@ -105,12 +107,20 @@ def add_selected_plan_regulation_group(self, item: QTreeWidgetItem, column: int) def add_plan_regulation_group(self, definition: RegulationGroup): regulation_group_widget = RegulationGroupWidget(definition, cast(str, self.layer_name)) regulation_group_widget.delete_signal.connect(self.remove_plan_regulation_group) + regulation_group_widget.open_as_form_signal.connect(self.open_plan_regulation_group_form) self._remove_spacer() self.plan_regulation_group_scrollarea_contents.layout().addWidget(regulation_group_widget) self.regulation_group_widgets.append(regulation_group_widget) self._add_spacer() + def open_plan_regulation_group_form(self, regulation_group_widget: RegulationGroupWidget): + group_as_form = PlanRegulationGroupForm(regulation_group_widget.into_model()) + if group_as_form.exec_(): + regulation_group_widget.from_model(group_as_form.model) + def remove_plan_regulation_group(self, regulation_group_widget: RegulationGroupWidget): + disconnect_signal(regulation_group_widget.delete_signal) + disconnect_signal(regulation_group_widget.open_as_form_signal) self.plan_regulation_group_scrollarea_contents.layout().removeWidget(regulation_group_widget) self.regulation_group_widgets.remove(regulation_group_widget) regulation_group_widget.deleteLater() From 7c5d81a486838f04a2cf2814c6dfb8ea6c34655e Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Tue, 28 Jan 2025 11:21:28 +0200 Subject: [PATCH 3/3] remove manual plan proposition and regulation deleting --- arho_feature_template/core/plan_manager.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 8a5f904..fc5cf4e 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -590,21 +590,21 @@ def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | No feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() - # Handle regulations - for regulation in regulation_group.regulations: - delete_regulation(regulation) + # # Handle regulations + # for regulation in regulation_group.regulations: + # delete_regulation(regulation) - # Handle propositions - for proposition in regulation_group.propositions: - delete_proposition(proposition) + # # Handle propositions + # for proposition in regulation_group.propositions: + # delete_proposition(proposition) _delete_feature(feature, layer, "Kaavamääräysryhmän poisto") - # Handle assocations - associations = RegulationGroupAssociationLayer.get_associations_for_regulation_group(str(regulation_group.id_)) - association_layer = RegulationGroupAssociationLayer.get_from_project() - for association in associations: - _delete_feature(association, association_layer, "Kaavamääräysryhmän assosiaation poisto") + # # Handle assocations + # associations = RegulationGroupAssociationLayer.get_associations_for_regulation_group(str(regulation_group.id_)) + # association_layer = RegulationGroupAssociationLayer.get_from_project() + # for association in associations: + # _delete_feature(association, association_layer, "Kaavamääräysryhmän assosiaation poisto") def save_regulation_group_as_config(regulation_group: RegulationGroup):