diff --git a/arho_feature_template/core/plan_regulation_config.py b/arho_feature_template/core/plan_regulation_config.py index fe2edce..61d391c 100644 --- a/arho_feature_template/core/plan_regulation_config.py +++ b/arho_feature_template/core/plan_regulation_config.py @@ -176,7 +176,7 @@ class PlanRegulationDefinition: """Associates a PlanRegulationConfig with an optional default value and additional data.""" regulation_config: PlanRegulationConfig - default_value: str | Number | None + default_value: str | Number | list[int] | None additional_information: list[ dict[str, str | Number | None] ] # NOTE: Correct typing for additional information values? diff --git a/arho_feature_template/gui/new_plan_regulation_group_form.py b/arho_feature_template/gui/new_plan_regulation_group_form.py index 8d7c232..c6944dc 100644 --- a/arho_feature_template/gui/new_plan_regulation_group_form.py +++ b/arho_feature_template/gui/new_plan_regulation_group_form.py @@ -57,7 +57,7 @@ def add_selected_plan_regulation(self, item: QTreeWidgetItem, column: int): self.add_plan_regulation(config) def add_plan_regulation(self, config: PlanRegulationConfig): - widget = PlanRegulationWidget(config=config, parent=self.plan_regulations_scroll_area_contents) + widget = PlanRegulationWidget.from_config(config=config, parent=self.plan_regulations_scroll_area_contents) widget.delete_signal.connect(self.delete_plan_regulation) index = self.plan_regulations_layout.count() - 1 self.plan_regulations_layout.insertWidget(index, widget) diff --git a/arho_feature_template/gui/plan_regulation_group_widget.py b/arho_feature_template/gui/plan_regulation_group_widget.py index 850f210..89f51a0 100644 --- a/arho_feature_template/gui/plan_regulation_group_widget.py +++ b/arho_feature_template/gui/plan_regulation_group_widget.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from qgis.PyQt.QtWidgets import QFrame, QLineEdit, QPushButton - from arho_feature_template.core.plan_regulation_config import PlanRegulationConfig + from arho_feature_template.core.plan_regulation_config import PlanRegulationDefinition from arho_feature_template.core.plan_regulation_group_config import PlanRegulationGroupDefinition ui_path = resources.files(__package__) / "plan_regulation_group_widget.ui" @@ -41,16 +41,14 @@ def __init__(self, group_definition: PlanRegulationGroupDefinition): self.heading.setText(self.group_definition.name) self.init_buttons() for plan_regulation_definition in self.group_definition.plan_regulations: - config = plan_regulation_definition.regulation_config - widget = self.add_plan_regulation_widget(config) - widget.populate_from_definition(plan_regulation_definition) + _ = self.add_plan_regulation_widget(plan_regulation_definition) def init_buttons(self): self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) self.del_btn.clicked.connect(lambda: self.delete_signal.emit(self)) - def add_plan_regulation_widget(self, config: PlanRegulationConfig) -> PlanRegulationWidget: - widget = PlanRegulationWidget(config=config, parent=self.frame) + def add_plan_regulation_widget(self, definition: PlanRegulationDefinition) -> PlanRegulationWidget: + widget = PlanRegulationWidget.from_definition(definition=definition, parent=self.frame) widget.delete_signal.connect(self.delete_plan_regulation_widget) self.frame.layout().addWidget(widget) return widget diff --git a/arho_feature_template/gui/plan_regulation_widget.py b/arho_feature_template/gui/plan_regulation_widget.py index fcdbbb4..0cc8431 100644 --- a/arho_feature_template/gui/plan_regulation_widget.py +++ b/arho_feature_template/gui/plan_regulation_widget.py @@ -1,6 +1,7 @@ from __future__ import annotations from importlib import resources +from numbers import Number from typing import TYPE_CHECKING, cast from qgis.core import QgsApplication @@ -46,6 +47,8 @@ class PlanRegulationWidget(QWidget, FormClass): # type: ignore """A widget representation of a plan regulation.""" + language = "fin" + delete_signal = pyqtSignal(QWidget) def __init__(self, config: PlanRegulationConfig, parent=None): @@ -53,7 +56,6 @@ def __init__(self, config: PlanRegulationConfig, parent=None): self.setupUi(self) # TYPES - # self.spacer_layout: QBoxLayout self.plan_regulation_name: QLineEdit self.form_layout: QFormLayout @@ -72,93 +74,116 @@ def __init__(self, config: PlanRegulationConfig, parent=None): self.widgets: list[tuple[QLabel, QWidget]] = [] self.plan_regulation_name.setText(config.name) self.plan_regulation_name.setReadOnly(True) - self.init_value_fields() - self.init_buttons() + self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) + self.del_btn.clicked.connect(lambda: self.delete_signal.emit(self)) + self.expand_hide_btn.clicked.connect(self._on_expand_hide_btn_clicked) + self.init_additional_information_btn() + self.init_other_information_btn() + + @classmethod + def from_config(cls, config: PlanRegulationConfig, parent=None) -> PlanRegulationWidget: + instance = cls(config, parent) + instance.init_fields() + return instance + + @classmethod + def from_definition(cls, definition: PlanRegulationDefinition, parent=None) -> PlanRegulationWidget: + instance = cls(definition.regulation_config, parent) + instance.init_fields_from_definition(definition) + return instance + + def init_fields_from_definition(self, definition: PlanRegulationDefinition): + # Value input + value_type = self.config.value_type + if value_type: + self._add_value_input(value_type, self.config.unit, definition.default_value) - def populate_from_definition(self, definition: PlanRegulationDefinition): # Additional information for info in definition.additional_information: info_type: str = cast("str", info["type"]) - self.add_additional_info(info_type) - if info_type == "kayttotarkoituskohdistus": - info_value_widget = QLineEdit() - label = QLabel(get_additional_information_name(info_type)) - value = info.get("value") - if value: - info_value_widget.setText(value) - self.form_layout.addRow(label, info_value_widget) + self.add_additional_info(info_type, info.get("value")) # TODO: Other saved information from PlanRegulationDefinition - def init_value_fields(self): + def init_fields(self): value_type = self.config.value_type if value_type: self._add_value_input(value_type, self.config.unit) - def _add_value_input(self, value_type: ValueType, unit: Unit | None): + @staticmethod + def _check_number_or_none(value: str | Number | None, error_msg: str): + if not isinstance(value, Number) and value is not None: + raise ValueError(error_msg) + + def _add_value_input( + self, value_type: ValueType, unit: Unit | None, default_value: str | Number | list[int] | None = None + ): + base_error_msg = f"Invalid type for default value {type(default_value)}." if value_type in [ValueType.DECIMAL, ValueType.POSITIVE_DECIMAL]: - self.add_decimal_input(value_type, unit) + if not isinstance(default_value, Number) and default_value is not None: + raise ValueError(base_error_msg) + self.add_decimal_input(value_type, unit, default_value) elif value_type == ValueType.POSITIVE_INTEGER: - self.add_positive_integer_input(unit) + if not isinstance(default_value, int) and default_value is not None: + raise ValueError(base_error_msg) + self.add_positive_integer_input(unit, default_value) elif value_type == ValueType.POSITIVE_INTEGER_RANGE: - self.add_positive_integer_range_input(unit) + if not isinstance(default_value, list) and default_value is not None: + raise ValueError(base_error_msg) + if isinstance(default_value, list) and len(default_value) != 2: # noqa: PLR2004 + error_msg = f"Invalid number of values in default value {type(default_value)}." + raise ValueError(error_msg) + self.add_positive_integer_range_input(unit, default_value) elif value_type == ValueType.VERSIONED_TEXT: - self.add_versioned_text_input() + if not isinstance(default_value, str) and default_value is not None: + raise ValueError(base_error_msg) + self.add_versioned_text_input(default_value) else: msg = f"Invalid input value type for plan regulation: {value_type}" raise ValueError(msg) - def init_buttons(self): - # DEL - self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) - self.del_btn.clicked.connect(lambda: self.delete_signal.emit(self)) - - # ADDITIONAL INFORMATION - type_menu = QMenu("Tyyppi", self) - type_menu_items = [ - "Pääkäyttötarkoitus", - "Osa-alue", - "Poisluettava käyttötarkoitus", - "Väliaikainen määräys", - "Vaihtoehtoinen", - "Ohjeellinen sijainti", - "Yhteystarve", - ] - - for item in type_menu_items: - action = type_menu.addAction(item) - action.triggered.connect(lambda _, item=item: self.add_additional_info(item)) - - signifigance_menu = QMenu("Merkittävyys", self) - signifigance_menu_items = [ - "Kansainvälinen", - "Valtakunnallinen", - "Maakunnallinen", - "Seudullinen", - "Alueellinen", - "Paikallinen", - ] - - for item in signifigance_menu_items: - action = signifigance_menu.addAction(item) - action.triggered.connect(lambda _, item=item: self.add_additional_info(item)) - - type_main_menu = QMenu(self) - type_main_menu.addMenu(type_menu) - type_main_menu.addMenu(signifigance_menu) - self.add_additional_information_btn.setMenu(type_main_menu) + def init_additional_information_btn(self): + informations_dict: dict[str, QMenu] = {} + add_later: dict[str, list[str]] = {} + + def _add_action(informations_dict: dict[str, QMenu], parent_id: str, info_type: str): + action = informations_dict[parent_id].addAction(info_type) + action.triggered.connect(lambda _: self.add_additional_info(info_type)) + + # Iterate code layer and build menus + for feature in get_layer_by_name("Lisätiedonlaji").getFeatures(): + if feature["level"] == 1: + menu = QMenu(feature["name"][self.language], self) + informations_dict[feature["id"]] = menu + else: + parent_id = feature["parent_id"] + info_type = feature["name"][self.language] + if parent_id in informations_dict: + _add_action(informations_dict, parent_id, info_type) + else: + if parent_id not in add_later: + add_later[parent_id] = [] + add_later[parent_id].append(info_type) + for parent_id, additional_information_types in add_later.items(): + for info_type in additional_information_types: + _add_action(informations_dict, parent_id, info_type) + + # Create main menu for btn and add submenus + additional_information_type_menu = QMenu(self) + for menu in informations_dict.values(): + additional_information_type_menu.addMenu(menu) + self.add_additional_information_btn.setMenu(additional_information_type_menu) self.add_additional_information_btn.setIcon(QgsApplication.getThemeIcon("mActionPropertiesWidget.svg")) - # OTHER INFO / ADD FIELD + def init_other_information_btn(self): add_field_menu = QMenu(self) add_field_menu.addAction("Määräysnumero").triggered.connect(self.add_regulation_number) add_field_menu.addAction("Liiteasiakirja").triggered.connect(self.add_file) add_field_menu.addAction("Aihetunniste").triggered.connect(self.add_topic_tag) theme_menu = QMenu("Kaavoitusteema", self) - language = "fin" for feature in get_layer_by_name("Kaavoitusteemat").getFeatures(): - name = feature["name"][language] + name = feature["name"][self.language] action = theme_menu.addAction(name) action.triggered.connect(lambda _, name=name: self.add_theme(name)) add_field_menu.addMenu(theme_menu) @@ -166,9 +191,6 @@ def init_buttons(self): self.add_field_btn.setMenu(add_field_menu) self.add_field_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) - # EXPAND BTN - self.expand_hide_btn.clicked.connect(self._on_expand_hide_btn_clicked) - def _on_expand_hide_btn_clicked(self): if self.expanded: for label, value_widget in self.widgets: @@ -192,7 +214,7 @@ def _add_widgets_to_form(self, label: QLabel, widget: QWidget): if not self.expanded: self._on_expand_hide_btn_clicked() - def add_decimal_input(self, value_type: ValueType, unit: Unit | None): + def add_decimal_input(self, value_type: ValueType, unit: Unit | None, default_value: Number | None = None): value_widget = QgsDoubleSpinBox() label = QLabel("Arvo") if value_type == ValueType.POSITIVE_DECIMAL: @@ -204,9 +226,11 @@ def add_decimal_input(self, value_type: ValueType, unit: Unit | None): value_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if unit: value_widget.setSuffix(f" {unit.value}") + if default_value: + value_widget.setValue(default_value) self._add_widgets_to_form(label, value_widget) - def add_positive_integer_input(self, unit: Unit | None): + def add_positive_integer_input(self, unit: Unit | None, default_value: int | None = None): value_widget = QgsSpinBox() value_widget.setMinimum(0) value_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -214,9 +238,11 @@ def add_positive_integer_input(self, unit: Unit | None): value_widget.setSuffix(f" {unit.value}") label = QLabel("Arvo") label.setToolTip("Tyyppi: kokonaisluku (positiivinen)") + if default_value: + value_widget.setValue(default_value) self._add_widgets_to_form(label, value_widget) - def add_positive_integer_range_input(self, unit: Unit | None): + def add_positive_integer_range_input(self, unit: Unit | None, default_values: list[int] | None = None): min_widget = QgsSpinBox() min_widget.setMinimum(0) min_label = QLabel("Arvo minimi") @@ -229,21 +255,37 @@ def add_positive_integer_range_input(self, unit: Unit | None): if unit: min_widget.setSuffix(f" {unit.value}") max_widget.setSuffix(f" {unit.value}") + if default_values: + min_widget.setValue(default_values[0]) + max_widget.setValue(default_values[1]) self._add_widgets_to_form(min_label, min_widget) self._add_widgets_to_form(max_label, max_widget) - def add_versioned_text_input(self): + def add_versioned_text_input(self, default_value: str | None = None): text_widget = QTextEdit() label = QLabel("Arvo") label.setToolTip("Tyyppi: kieliversioitu teksti") + if default_value: + text_widget.setText(default_value) self._add_widgets_to_form(label, text_widget) - def add_additional_info(self, info_type: str): - info_type_line = QLineEdit(get_additional_information_name(info_type)) + def add_additional_info(self, info_type: str, default_value: str | Number | None = None): + # NOTE: Now info type is the name / readable version when this is triggered by user + # Might need to refactor this later.. + name = get_additional_information_name(info_type) + info_type_line = QLineEdit(name) info_type_line.setReadOnly(True) label = QLabel("Lisätiedonlaji") self._add_widgets_to_form(label, info_type_line) + if name == "Käyttötarkoituskohdistus": + info_value_widget = QLineEdit() + info_value_label = QLabel(name) + self._add_widgets_to_form(info_value_label, info_value_widget) + + if default_value: + info_value_widget.setText(str(default_value)) + def add_regulation_number(self): if not self.regulation_number_added: number_widget = QgsSpinBox()