Skip to content

Commit daa4663

Browse files
committed
add TreeWithSearchWidget, use it in new plan regulation form, plan feature form and plan attribute form
1 parent c347e3c commit daa4663

8 files changed

+139
-225
lines changed

arho_feature_template/core/plan_manager.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,7 @@ def _plan_geom_digitized(self, feature: QgsFeature):
166166
if not plan_layer:
167167
return
168168

169-
attribute_form = PlanAttributeForm(
170-
[*self.regulation_group_libraries, regulation_group_library_from_active_plan()]
171-
)
169+
attribute_form = PlanAttributeForm(self.regulation_group_libraries)
172170
if attribute_form.exec_():
173171
plan_attributes = attribute_form.get_plan_attributes()
174172
plan_attributes.geom = feature.geometry()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
from qgis.gui import QgsFilterLineEdit
6+
from qgis.PyQt.QtCore import Qt
7+
from qgis.PyQt.QtWidgets import QSizePolicy, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
8+
9+
10+
class TreeWithSearchWidget(QWidget):
11+
"""A widget combining a QTreeWidget and QgsFilterLineEdit."""
12+
13+
def __init__(self):
14+
super().__init__()
15+
self.search = QgsFilterLineEdit(self)
16+
self.search.setShowClearButton(True)
17+
self.search.setShowSearchIcon(True)
18+
self.search.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
19+
self.search.valueChanged.connect(self.filter_tree_items)
20+
21+
self.tree = QTreeWidget(self)
22+
self.tree.setHeaderHidden(True)
23+
self.tree.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
24+
25+
layout = QVBoxLayout()
26+
layout.addWidget(self.search)
27+
layout.addWidget(self.tree)
28+
self.setLayout(layout)
29+
30+
self.setContentsMargins(0, 0, 0, 0)
31+
layout.setContentsMargins(0, 0, 0, 0)
32+
33+
def add_item_to_tree(
34+
self, text: str | None, model: Any | None = None, parent: QTreeWidgetItem | None = None
35+
) -> QTreeWidgetItem:
36+
item = QTreeWidgetItem(parent)
37+
if text:
38+
item.setText(0, text)
39+
item.setToolTip(0, text) # Set text as tooltip in case the tree width is not enough to show item text
40+
if model:
41+
item.setData(0, Qt.UserRole, model)
42+
if not parent:
43+
self.tree.addTopLevelItem(item)
44+
45+
return item
46+
47+
def filter_tree_items(self):
48+
search_text = self.search.value().lower()
49+
50+
# Iterate over all group (top-level) items in the tree
51+
for i in range(self.tree.topLevelItemCount()):
52+
group_item = self.tree.topLevelItem(i)
53+
group_item.setHidden(True) # Initially hide the top-level item
54+
55+
matches_group = search_text in group_item.text(0).lower()
56+
if matches_group:
57+
# If group matches, show it and all its children
58+
self.show_all_children(group_item)
59+
group_item.setHidden(False)
60+
else:
61+
# Otherwise, recursively filter its children
62+
matches_child = self.filter_children(group_item, search_text)
63+
group_item.setHidden(not matches_child)
64+
65+
def filter_children(self, parent_item: QTreeWidgetItem, search_text: str) -> bool:
66+
"""Recursively filter children and return True if any child matches."""
67+
has_match = False
68+
for i in range(parent_item.childCount()):
69+
child = parent_item.child(i)
70+
matches = search_text in child.text(0).lower()
71+
child.setHidden(not matches)
72+
73+
if child.childCount() > 0:
74+
matches |= self.filter_children(child, search_text)
75+
76+
if matches:
77+
has_match = True
78+
79+
return has_match
80+
81+
def show_all_children(self, parent_item: QTreeWidgetItem):
82+
"""Show the parent item and all its children recursively."""
83+
parent_item.setHidden(False)
84+
for i in range(parent_item.childCount()):
85+
child = parent_item.child(i)
86+
child.setHidden(False)
87+
if child.childCount() > 0:
88+
self.show_all_children(child)

arho_feature_template/gui/dialogs/new_plan_regulation_group_form.py

+11-16
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55

66
from qgis.PyQt import uic
77
from qgis.PyQt.QtCore import Qt
8-
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QTextBrowser, QTreeWidget, QTreeWidgetItem
8+
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QTextBrowser, QTreeWidgetItem, QVBoxLayout
99

1010
from arho_feature_template.core.models import Regulation, RegulationConfig, RegulationGroup, RegulationLibrary
1111
from arho_feature_template.gui.components.plan_regulation_widget import RegulationWidget
12+
from arho_feature_template.gui.components.tree_with_search_widget import TreeWithSearchWidget
1213
from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer
1314

1415
if TYPE_CHECKING:
@@ -35,39 +36,33 @@ def __init__(self):
3536
self.color_code: QLineEdit
3637
self.type_of_regulation_group: CodeComboBox
3738

38-
self.regulations_view: QTreeWidget
39+
self.regulations_tree_layout: QVBoxLayout
3940
self.regulations_scroll_area_contents: QWidget
4041
self.regulations_layout: QBoxLayout
4142
self.regulation_info: QTextBrowser
4243

4344
self.button_box: QDialogButtonBox
4445

4546
# INIT
46-
self.initialize_regulations()
47+
self.regulations_selection_widget = TreeWithSearchWidget()
48+
self.regulations_tree_layout.insertWidget(1, self.regulations_selection_widget)
49+
self.regulations_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_regulation)
50+
self.regulations_selection_widget.tree.itemClicked.connect(self.update_selected_regulation)
51+
[self._initalize_regulation_from_config(config) for config in RegulationLibrary.get_regulations()]
52+
4753
self.type_of_regulation_group.populate_from_code_layer(PlanRegulationGroupTypeLayer)
4854
self.type_of_regulation_group.removeItem(0) # Remove NULL from combobox as underground data is required
49-
self.regulations_view.itemDoubleClicked.connect(self.add_selected_regulation)
50-
self.regulations_view.itemClicked.connect(self.update_selected_regulation)
5155
self.button_box.accepted.connect(self._on_ok_clicked)
5256
self.regulation_widgets: list[RegulationWidget] = []
5357
self.save_as_config = False
5458

5559
def _initalize_regulation_from_config(self, config: RegulationConfig, parent: QTreeWidgetItem | None = None):
56-
tree_item = QTreeWidgetItem(parent)
57-
tree_item.setText(0, config.name)
58-
tree_item.setData(0, Qt.UserRole, config)
59-
60-
if parent is None:
61-
self.regulations_view.addTopLevelItem(tree_item)
60+
item = self.regulations_selection_widget.add_item_to_tree(config.name, config, parent)
6261

6362
# Initialize plan regulations recursively
6463
if config.child_regulations:
6564
for child_config in config.child_regulations:
66-
self._initalize_regulation_from_config(child_config, tree_item)
67-
68-
def initialize_regulations(self):
69-
for config in RegulationLibrary.get_regulations():
70-
self._initalize_regulation_from_config(config)
65+
self._initalize_regulation_from_config(child_config, item)
7166

7267
def update_selected_regulation(self, item: QTreeWidgetItem, column: int):
7368
config: RegulationConfig = item.data(column, Qt.UserRole) # Retrieve the associated config

arho_feature_template/gui/dialogs/new_plan_regulation_group_form.ui

+1-46
Original file line numberDiff line numberDiff line change
@@ -111,54 +111,14 @@
111111
</attribute>
112112
<layout class="QHBoxLayout" name="horizontalLayout">
113113
<item>
114-
<layout class="QVBoxLayout" name="verticalLayout">
114+
<layout class="QVBoxLayout" name="regulations_tree_layout">
115115
<item>
116116
<widget class="QLabel" name="label_6">
117117
<property name="text">
118118
<string>Kaavamääräykset</string>
119119
</property>
120120
</widget>
121121
</item>
122-
<item>
123-
<widget class="QgsFilterLineEdit" name="mLineEdit">
124-
<property name="enabled">
125-
<bool>false</bool>
126-
</property>
127-
<property name="sizePolicy">
128-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
129-
<horstretch>0</horstretch>
130-
<verstretch>0</verstretch>
131-
</sizepolicy>
132-
</property>
133-
<property name="placeholderText">
134-
<string>Suodata kaavamääräyksiä</string>
135-
</property>
136-
<property name="showSearchIcon">
137-
<bool>true</bool>
138-
</property>
139-
<property name="qgisRelation" stdset="0">
140-
<string notr="true"/>
141-
</property>
142-
</widget>
143-
</item>
144-
<item>
145-
<widget class="QTreeWidget" name="regulations_view">
146-
<property name="sizePolicy">
147-
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
148-
<horstretch>0</horstretch>
149-
<verstretch>0</verstretch>
150-
</sizepolicy>
151-
</property>
152-
<attribute name="headerVisible">
153-
<bool>false</bool>
154-
</attribute>
155-
<column>
156-
<property name="text">
157-
<string notr="true">1</string>
158-
</property>
159-
</column>
160-
</widget>
161-
</item>
162122
<item>
163123
<widget class="QLabel" name="label_4">
164124
<property name="text">
@@ -256,11 +216,6 @@ p, li { white-space: pre-wrap; }
256216
</layout>
257217
</widget>
258218
<customwidgets>
259-
<customwidget>
260-
<class>QgsFilterLineEdit</class>
261-
<extends>QLineEdit</extends>
262-
<header>qgsfilterlineedit.h</header>
263-
</customwidget>
264219
<customwidget>
265220
<class>QgsSpinBox</class>
266221
<extends>QSpinBox</extends>

arho_feature_template/gui/dialogs/plan_attribute_form.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313
QSizePolicy,
1414
QSpacerItem,
1515
QTextEdit,
16-
QTreeWidget,
1716
QTreeWidgetItem,
1817
)
1918

2019
from arho_feature_template.core.models import Plan, RegulationGroup, RegulationGroupLibrary
2120
from arho_feature_template.gui.components.plan_regulation_group_widget import RegulationGroupWidget
21+
from arho_feature_template.gui.components.tree_with_search_widget import TreeWithSearchWidget
2222
from arho_feature_template.project.layers.code_layers import (
2323
LifeCycleStatusLayer,
2424
OrganisationLayer,
2525
PlanTypeLayer,
2626
)
2727

2828
if TYPE_CHECKING:
29-
from qgis.PyQt.QtWidgets import QComboBox, QLineEdit, QTextEdit, QTreeWidget, QWidget
29+
from qgis.PyQt.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget
3030

3131
from arho_feature_template.gui.components.code_combobox import CodeComboBox, HierarchicalCodeComboBox
3232

@@ -47,7 +47,7 @@ class PlanAttributeForm(QDialog, FormClass): # type: ignore
4747

4848
plan_regulation_group_scrollarea_contents: QWidget
4949
plan_regulation_group_libraries_combobox: QComboBox
50-
plan_regulation_groups_tree: QTreeWidget
50+
regulation_groups_tree_layout: QVBoxLayout
5151

5252
button_box: QDialogButtonBox
5353

@@ -66,9 +66,11 @@ def __init__(self, regulation_group_libraries: list[RegulationGroupLibrary], par
6666
self.lifecycle_status_combo_box.currentIndexChanged.connect(self._check_required_fields)
6767

6868
self.scroll_area_spacer = None
69+
self.regulation_groups_selection_widget = TreeWithSearchWidget()
70+
self.regulation_groups_tree_layout.insertWidget(2, self.regulation_groups_selection_widget)
6971
self.regulation_group_widgets: list[RegulationGroupWidget] = []
7072
[self.init_plan_regulation_group_library(library) for library in regulation_group_libraries]
71-
self.plan_regulation_groups_tree.itemDoubleClicked.connect(self.add_selected_plan_regulation_group)
73+
self.regulation_groups_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_plan_regulation_group)
7274

7375
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
7476

@@ -117,13 +119,11 @@ def remove_plan_regulation_group(self, regulation_group_widget: RegulationGroupW
117119
def init_plan_regulation_group_library(self, library: RegulationGroupLibrary):
118120
self.plan_regulation_group_libraries_combobox.addItem(library.name)
119121
for category in library.regulation_group_categories:
120-
category_item = QTreeWidgetItem()
121-
category_item.setText(0, category.name)
122-
self.plan_regulation_groups_tree.addTopLevelItem(category_item)
122+
category_item = self.regulation_groups_selection_widget.add_item_to_tree(category.name)
123123
for group_definition in category.regulation_groups:
124-
regulation_group_item = QTreeWidgetItem(category_item)
125-
regulation_group_item.setText(0, group_definition.name)
126-
regulation_group_item.setData(0, Qt.UserRole, group_definition)
124+
self.regulation_groups_selection_widget.add_item_to_tree(
125+
group_definition.name, group_definition, category_item
126+
)
127127

128128
# ---
129129

arho_feature_template/gui/dialogs/plan_attribute_form.ui

+2-65
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243
</property>
244244
<layout class="QHBoxLayout" name="horizontalLayout">
245245
<item>
246-
<layout class="QVBoxLayout" name="verticalLayout_5">
246+
<layout class="QVBoxLayout" name="regulation_groups_tree_layout">
247247
<item>
248248
<widget class="QLabel" name="label_6">
249249
<property name="text">
@@ -254,64 +254,6 @@
254254
<item>
255255
<widget class="QComboBox" name="plan_regulation_group_libraries_combobox"/>
256256
</item>
257-
<item>
258-
<widget class="QgsFilterLineEdit" name="mLineEdit">
259-
<property name="enabled">
260-
<bool>false</bool>
261-
</property>
262-
<property name="sizePolicy">
263-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
264-
<horstretch>0</horstretch>
265-
<verstretch>0</verstretch>
266-
</sizepolicy>
267-
</property>
268-
<property name="placeholderText">
269-
<string>Suodata kaavamääräysryhmiä</string>
270-
</property>
271-
<property name="clearButtonEnabled">
272-
<bool>false</bool>
273-
</property>
274-
<property name="showSearchIcon">
275-
<bool>true</bool>
276-
</property>
277-
<property name="qgisRelation" stdset="0">
278-
<string notr="true"/>
279-
</property>
280-
</widget>
281-
</item>
282-
<item>
283-
<widget class="QTreeWidget" name="plan_regulation_groups_tree">
284-
<property name="sizePolicy">
285-
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
286-
<horstretch>0</horstretch>
287-
<verstretch>0</verstretch>
288-
</sizepolicy>
289-
</property>
290-
<property name="frameShape">
291-
<enum>QFrame::StyledPanel</enum>
292-
</property>
293-
<property name="frameShadow">
294-
<enum>QFrame::Sunken</enum>
295-
</property>
296-
<property name="editTriggers">
297-
<set>QAbstractItemView::NoEditTriggers</set>
298-
</property>
299-
<property name="dragDropMode">
300-
<enum>QAbstractItemView::NoDragDrop</enum>
301-
</property>
302-
<property name="alternatingRowColors">
303-
<bool>false</bool>
304-
</property>
305-
<attribute name="headerVisible">
306-
<bool>false</bool>
307-
</attribute>
308-
<column>
309-
<property name="text">
310-
<string/>
311-
</property>
312-
</column>
313-
</widget>
314-
</item>
315257
</layout>
316258
</item>
317259
<item>
@@ -333,7 +275,7 @@
333275
<rect>
334276
<x>0</x>
335277
<y>0</y>
336-
<width>541</width>
278+
<width>601</width>
337279
<height>493</height>
338280
</rect>
339281
</property>
@@ -369,11 +311,6 @@
369311
<header>qgscollapsiblegroupbox.h</header>
370312
<container>1</container>
371313
</customwidget>
372-
<customwidget>
373-
<class>QgsFilterLineEdit</class>
374-
<extends>QLineEdit</extends>
375-
<header>qgsfilterlineedit.h</header>
376-
</customwidget>
377314
<customwidget>
378315
<class>CodeComboBox</class>
379316
<extends>QComboBox</extends>

0 commit comments

Comments
 (0)