From 9130c8cf27c1d19fa7976faff9d4cace53d84312 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Wed, 29 Jan 2025 10:21:17 +0200 Subject: [PATCH 1/2] redesign general regulations UI, add propositions for plan --- .../general_regulation_group_widget.py | 117 +++++++++++++ .../general_regulation_group_widget.ui | 163 ++++++++++++++++++ .../plan_regulation_group_widget.py | 8 +- .../gui/dialogs/plan_attribute_form.py | 100 +++++------ .../gui/dialogs/plan_attribute_form.ui | 93 +++++----- 5 files changed, 374 insertions(+), 107 deletions(-) create mode 100644 arho_feature_template/gui/components/general_regulation_group_widget.py create mode 100644 arho_feature_template/gui/components/general_regulation_group_widget.ui diff --git a/arho_feature_template/gui/components/general_regulation_group_widget.py b/arho_feature_template/gui/components/general_regulation_group_widget.py new file mode 100644 index 0000000..03c38a0 --- /dev/null +++ b/arho_feature_template/gui/components/general_regulation_group_widget.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +from importlib import resources +from typing import TYPE_CHECKING + +from qgis.core import QgsApplication +from qgis.PyQt import uic +from qgis.PyQt.QtCore import pyqtSignal +from qgis.PyQt.QtWidgets import QMenu, QWidget + +from arho_feature_template.core.models import Proposition, Regulation, RegulationGroup, RegulationLibrary +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 + +if TYPE_CHECKING: + from qgis.PyQt.QtWidgets import QFormLayout, QFrame, QLineEdit, QPushButton + +ui_path = resources.files(__package__) / "general_regulation_group_widget.ui" +FormClass, _ = uic.loadUiType(ui_path) + + +class GeneralRegulationGroupWidget(QWidget, FormClass): # type: ignore + """A widget representation of a general regulation group.""" + + # open_as_form_signal = pyqtSignal(QWidget) + delete_signal = pyqtSignal(QWidget) + + def __init__(self, regulation_group: RegulationGroup, layer_name: str): + super().__init__() + self.setupUi(self) + + # TYPES + self.frame: QFrame + self.name: QLineEdit + # self.edit_btn: QPushButton + self.add_field_btn: QPushButton + self.del_btn: QPushButton + self.regulation_group_details_layout: QFormLayout + + # INIT + 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.verbal_regulation_config = RegulationLibrary.get_regulation_by_code("sanallinenMaarays") + + # self.edit_btn.setIcon(QIcon(resources_path("icons", "settings.svg"))) + # self.edit_btn.clicked.connect(lambda: self.open_as_form_signal.emit(self)) + add_field_menu = QMenu() + add_field_menu.addAction("Lisää kaavamääräys").triggered.connect(self.add_new_regulation) + add_field_menu.addAction("Lisää kaavasuositus").triggered.connect(self.add_new_proposition) + self.add_field_btn.setMenu(add_field_menu) + self.add_field_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) + + 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 "") + + # 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) + for regulation in regulation_group.regulations: + self.add_regulation_widget(regulation) + for proposition in regulation_group.propositions: + self.add_proposition_widget(proposition) + + def add_new_regulation(self): + regulation = Regulation(self.verbal_regulation_config) + self.add_regulation_widget(regulation) + + def add_regulation_widget(self, regulation: Regulation) -> RegulationWidget: + widget = RegulationWidget(regulation=regulation, parent=self.frame) + widget.delete_signal.connect(self.delete_regulation_widget) + self.frame.layout().addWidget(widget) + self.regulation_widgets.append(widget) + return widget + + def delete_regulation_widget(self, regulation_widget: RegulationWidget): + self.frame.layout().removeWidget(regulation_widget) + self.regulation_widgets.remove(regulation_widget) + regulation_widget.deleteLater() + + def add_new_proposition(self): + proposition = Proposition(value="") + self.add_proposition_widget(proposition) + + def add_proposition_widget(self, proposition: Proposition) -> PropositionWidget: + widget = PropositionWidget(proposition=proposition, parent=self.frame) + widget.delete_signal.connect(self.delete_proposition_widget) + self.frame.layout().addWidget(widget) + self.proposition_widgets.append(widget) + return widget + + def delete_proposition_widget(self, proposition_widget: RegulationWidget): + self.frame.layout().removeWidget(proposition_widget) + self.proposition_widgets.remove(proposition_widget) + proposition_widget.deleteLater() + + def into_model(self) -> RegulationGroup: + return RegulationGroup( + type_code_id=self.regulation_group.type_code_id, + name=self.name.text(), + short_name=None, + color_code=None, + regulations=[widget.into_model() for widget in self.regulation_widgets], + propositions=[widget.into_model() for widget in self.proposition_widgets], + id_=self.regulation_group.id_, + ) diff --git a/arho_feature_template/gui/components/general_regulation_group_widget.ui b/arho_feature_template/gui/components/general_regulation_group_widget.ui new file mode 100644 index 0000000..31b7bf7 --- /dev/null +++ b/arho_feature_template/gui/components/general_regulation_group_widget.ui @@ -0,0 +1,163 @@ + + + general_regulation_group_form + + + + 0 + 0 + 420 + 98 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + 2 + + + + + + + + + 13 + 75 + false + true + + + + Yleismääräysryhmä + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Poista kaavamääräysryhmä + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 50 + false + + + + Nimi + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + 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 9f56fa3..880f8b9 100644 --- a/arho_feature_template/gui/components/plan_regulation_group_widget.py +++ b/arho_feature_template/gui/components/plan_regulation_group_widget.py @@ -64,13 +64,16 @@ def from_model(self, regulation_group: RegulationGroup): 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] + for regulation in regulation_group.regulations: + self.add_regulation_widget(regulation) + for proposition in regulation_group.propositions: + self.add_proposition_widget(proposition) def add_regulation_widget(self, regulation: Regulation) -> RegulationWidget: widget = RegulationWidget(regulation=regulation, parent=self.frame) widget.delete_signal.connect(self.delete_regulation_widget) self.frame.layout().addWidget(widget) + self.regulation_widgets.append(widget) return widget def delete_regulation_widget(self, regulation_widget: RegulationWidget): @@ -82,6 +85,7 @@ def add_proposition_widget(self, proposition: Proposition) -> PropositionWidget: widget = PropositionWidget(proposition=proposition, parent=self.frame) widget.delete_signal.connect(self.delete_proposition_widget) self.frame.layout().addWidget(widget) + self.proposition_widgets.append(widget) return widget def delete_proposition_widget(self, proposition_widget: RegulationWidget): diff --git a/arho_feature_template/gui/dialogs/plan_attribute_form.py b/arho_feature_template/gui/dialogs/plan_attribute_form.py index e746ccd..9a5ad62 100644 --- a/arho_feature_template/gui/dialogs/plan_attribute_form.py +++ b/arho_feature_template/gui/dialogs/plan_attribute_form.py @@ -5,23 +5,18 @@ from qgis.core import QgsApplication from qgis.PyQt import uic -from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import ( - QComboBox, QDialog, QDialogButtonBox, QLineEdit, - QSizePolicy, - QSpacerItem, QTextEdit, - QTreeWidgetItem, ) from arho_feature_template.core.models import Document, Plan, RegulationGroup, RegulationGroupLibrary + +# from arho_feature_template.gui.components.plan_regulation_group_widget import RegulationGroupWidget +from arho_feature_template.gui.components.general_regulation_group_widget import GeneralRegulationGroupWidget from arho_feature_template.gui.components.plan_document_widget import DocumentWidget -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, @@ -30,7 +25,7 @@ from arho_feature_template.utils.misc_utils import disconnect_signal if TYPE_CHECKING: - from qgis.PyQt.QtWidgets import QComboBox, QLineEdit, QPushButton, QTextEdit, QVBoxLayout, QWidget + from qgis.PyQt.QtWidgets import QLineEdit, QPushButton, QTextEdit, QVBoxLayout from arho_feature_template.gui.components.code_combobox import CodeComboBox, HierarchicalCodeComboBox @@ -49,17 +44,18 @@ class PlanAttributeForm(QDialog, FormClass): # type: ignore producers_plan_identifier_line_edit: QLineEdit matter_management_identifier_line_edit: QLineEdit - plan_regulation_group_scrollarea_contents: QWidget - plan_regulation_group_libraries_combobox: QComboBox - regulation_groups_tree_layout: QVBoxLayout + regulations_layout: QVBoxLayout + add_general_regulation_group_btn: QPushButton + # plan_regulation_group_scrollarea_contents: QWidget + # plan_regulation_group_libraries_combobox: QComboBox + # regulation_groups_tree_layout: QVBoxLayout - documents_scroll_contents: QWidget documents_layout: QVBoxLayout add_document_btn: QPushButton button_box: QDialogButtonBox - def __init__(self, plan: Plan, regulation_group_libraries: list[RegulationGroupLibrary], parent=None): + def __init__(self, plan: Plan, _regulation_group_libraries: list[RegulationGroupLibrary], parent=None): super().__init__(parent) self.setupUi(self) @@ -92,13 +88,12 @@ def __init__(self, plan: Plan, regulation_group_libraries: list[RegulationGroupL self.lifecycle_status_combo_box.currentIndexChanged.connect(self._check_required_fields) self.scroll_area_spacer = None - self.regulation_groups_selection_widget = TreeWithSearchWidget() - self.regulation_groups_tree_layout.insertWidget(2, self.regulation_groups_selection_widget) - self.regulation_group_widgets: list[RegulationGroupWidget] = [] - for library in regulation_group_libraries: - self.init_plan_regulation_group_library(library) - - self.regulation_groups_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_plan_regulation_group) + self.regulation_group_widgets: list[GeneralRegulationGroupWidget] = [] + # self.regulation_groups_selection_widget = TreeWithSearchWidget() + # self.regulation_groups_tree_layout.insertWidget(2, self.regulation_groups_selection_widget) + # for library in regulation_group_libraries: + # self.init_plan_regulation_group_library(library) + # self.regulation_groups_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_plan_regulation_group) for regulation_group in plan.general_regulations: self.add_plan_regulation_group(regulation_group) @@ -107,6 +102,9 @@ def __init__(self, plan: Plan, regulation_group_libraries: list[RegulationGroupL for document in plan.documents: self.add_document(document) + self.add_general_regulation_group_btn.clicked.connect(self.add_new_regulation_group) + self.add_general_regulation_group_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) + self.add_document_btn.clicked.connect(self.add_new_document) self.add_document_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) @@ -130,57 +128,49 @@ def _check_required_fields(self) -> None: # --- COPIED FROM PLAN FEATURE FORM --- - def _add_spacer(self): - self.scroll_area_spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding) - self.plan_regulation_group_scrollarea_contents.layout().addItem(self.scroll_area_spacer) - - def _remove_spacer(self): - if self.scroll_area_spacer is not None: - self.plan_regulation_group_scrollarea_contents.layout().removeItem(self.scroll_area_spacer) - self.scroll_area_spacer = None + # def add_selected_plan_regulation_group(self, item: QTreeWidgetItem, column: int): + # if not item.parent(): + # return + # regulation_group: RegulationGroup = item.data(column, Qt.UserRole) + # self.add_plan_regulation_group(regulation_group) - def add_selected_plan_regulation_group(self, item: QTreeWidgetItem, column: int): - if not item.parent(): - return - regulation_group: RegulationGroup = item.data(column, Qt.UserRole) - self.add_plan_regulation_group(regulation_group) + def add_new_regulation_group(self): + self.add_plan_regulation_group(RegulationGroup()) def add_plan_regulation_group(self, regulation_group: RegulationGroup): - regulation_group_widget = RegulationGroupWidget(regulation_group, layer_name="Kaava") + regulation_group_widget = GeneralRegulationGroupWidget(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) + # regulation_group_widget.open_as_form_signal.connect(self.open_plan_regulation_group_form) + self.regulations_layout.insertWidget(1, 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 open_plan_regulation_group_form(self, regulation_group_widget: GeneralRegulationGroupWidget): + # 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): + def remove_plan_regulation_group(self, regulation_group_widget: GeneralRegulationGroupWidget): 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) + # disconnect_signal(regulation_group_widget.open_as_form_signal) + self.regulations_layout.removeWidget(regulation_group_widget) self.regulation_group_widgets.remove(regulation_group_widget) regulation_group_widget.deleteLater() - def init_plan_regulation_group_library(self, library: RegulationGroupLibrary): - self.plan_regulation_group_libraries_combobox.addItem(library.name) - for category in library.regulation_group_categories: - category_item = self.regulation_groups_selection_widget.add_item_to_tree(category.name) - for group_definition in category.regulation_groups: - self.regulation_groups_selection_widget.add_item_to_tree( - group_definition.name, group_definition, category_item - ) + # def init_plan_regulation_group_library(self, library: RegulationGroupLibrary): + # self.plan_regulation_group_libraries_combobox.addItem(library.name) + # for category in library.regulation_group_categories: + # category_item = self.regulation_groups_selection_widget.add_item_to_tree(category.name) + # for group_definition in category.regulation_groups: + # self.regulation_groups_selection_widget.add_item_to_tree( + # group_definition.name, group_definition, category_item + # ) def add_new_document(self): self.add_document(Document()) self._check_required_fields() def add_document(self, document: Document): - widget = DocumentWidget(document, parent=self.documents_scroll_contents) + widget = DocumentWidget(document) widget.delete_signal.connect(self.delete_document) widget.document_edited.connect(self._check_required_fields) self.documents_layout.insertWidget(1, widget) diff --git a/arho_feature_template/gui/dialogs/plan_attribute_form.ui b/arho_feature_template/gui/dialogs/plan_attribute_form.ui index c31b0eb..af09f1e 100644 --- a/arho_feature_template/gui/dialogs/plan_attribute_form.ui +++ b/arho_feature_template/gui/dialogs/plan_attribute_form.ui @@ -233,59 +233,52 @@ - Yleismääräykset + Yleismääräysryhmät - + - - - Yleismääräykset + + + true - - - - - - - Kaavamääräysryhmäkirjastot - - - - - - - - - - - - - - Valitut yleismääräysryhmät - - - - - - - true - - - - - 0 - 0 - 601 - 493 - - - - - - - - - + + + + 0 + 0 + 831 + 562 + + + + + + + + 0 + 0 + + + + Lisää yleismääräysryhmä + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + From 6819d93c2c4f1b9638585612fbadc441ea229860 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Wed, 29 Jan 2025 12:24:16 +0200 Subject: [PATCH 2/2] implement deleting propositions and regulations when deleting or editing regulation group --- arho_feature_template/core/plan_manager.py | 34 ++++++++++++++----- .../project/layers/plan_layers.py | 22 ++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 11596ed..ccac6e4 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -573,20 +573,36 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() + editing = regulation_group.id_ is not None _save_feature( feature=feature, layer=layer, id_=regulation_group.id_, - edit_text="Kaavamääräysryhmän lisäys" if regulation_group.id_ is None else "Kaavamääräysryhmän muokkaus", + edit_text="Kaavamääräysryhmän muokkaus" if editing else "Kaavamääräysryhmän lisäys", ) - # Handle regulations + if editing: + # Check for regulations to be deleted + regulation_layer = PlanRegulationLayer.get_from_project() + for reg_feature in PlanRegulationLayer.get_regulations_to_delete( + regulation_group.regulations, regulation_group + ): + _delete_feature(reg_feature, regulation_layer, "Kaavamääräyksen poisto") + + # Check for propositions to be deleted + proposition_layer = PlanPropositionLayer.get_from_project() + for prop_feature in PlanPropositionLayer.get_propositions_to_delete( + regulation_group.propositions, regulation_group + ): + _delete_feature(prop_feature, proposition_layer, "Kaavasuosituksen poisto") + + # Save regulations if regulation_group.regulations: for regulation in regulation_group.regulations: regulation.regulation_group_id_ = feature["id"] # Updating regulation group ID save_regulation(regulation) - # Handle propositions + # Save propositions if regulation_group.propositions: for proposition in regulation_group.propositions: proposition.regulation_group_id_ = feature["id"] # Updating regulation group ID @@ -602,13 +618,13 @@ 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) + # Delete regulations + for regulation in regulation_group.regulations: + delete_regulation(regulation) - # # Handle propositions - # for proposition in regulation_group.propositions: - # delete_proposition(proposition) + # Delete propositions + for proposition in regulation_group.propositions: + delete_proposition(proposition) _delete_feature(feature, layer, "Kaavamääräysryhmän poisto") diff --git a/arho_feature_template/project/layers/plan_layers.py b/arho_feature_template/project/layers/plan_layers.py index 96a3a42..f8a08db 100644 --- a/arho_feature_template/project/layers/plan_layers.py +++ b/arho_feature_template/project/layers/plan_layers.py @@ -361,6 +361,17 @@ def model_from_feature(cls, feature: QgsFeature) -> Regulation: def regulations_with_group_id(cls, group_id: str) -> Generator[QgsFeature]: return cls.get_features_by_attribute_value("plan_regulation_group_id", group_id) + @classmethod + def get_regulations_to_delete( + cls, regulations: list[Regulation], regulation_group: RegulationGroup + ) -> list[QgsFeature]: + updated_regulation_ids = [regulation.id_ for regulation in regulations] + return [ + reg + for reg in cls.get_features_by_attribute_value("plan_regulation_group_id", str(regulation_group.id_)) + if reg["id"] not in updated_regulation_ids + ] + class PlanPropositionLayer(AbstractPlanLayer): name = "Kaavasuositus" @@ -403,6 +414,17 @@ def model_from_feature(cls, feature: QgsFeature) -> Proposition: def propositions_with_group_id(cls, group_id: str) -> Generator[QgsFeature]: return cls.get_features_by_attribute_value("plan_regulation_group_id", group_id) + @classmethod + def get_propositions_to_delete( + cls, propositions: list[Proposition], regulation_group: RegulationGroup + ) -> list[QgsFeature]: + updated_proposition_ids = [proposition.id_ for proposition in propositions] + return [ + prop + for prop in cls.get_features_by_attribute_value("plan_regulation_group_id", str(regulation_group.id_)) + if prop["id"] not in updated_proposition_ids + ] + class DocumentLayer(AbstractPlanLayer): name = "Asiakirjat"