Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lizmap/dialogs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def __init__(
self.inIgnKey.textChanged.connect(self.check_api_key_address)
self.inGoogleKey.textChanged.connect(self.check_api_key_address)

self._ignore_layer_tree_state = False

# Layer tree
self.layer_tree.headerItem().setText(0, tr('List of layers'))

Expand Down Expand Up @@ -680,6 +682,7 @@ def follow_map_theme_toggled(self):
def filter_layer_tree(self, search_text: str):
""" Filter the layer tree based on search text. """
search_text = search_text.lower().strip()
self._ignore_layer_tree_state = bool(search_text)

def filter_item(item):
""" Recursively filter tree items. Returns True if item or any child matches. """
Expand Down
73 changes: 71 additions & 2 deletions lizmap/plugin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import hashlib
import json
import logging
import os
Expand Down Expand Up @@ -596,6 +597,9 @@ def write_log_message(message, tag, level):

# Catch user interaction on layer tree and inputs
self.dlg.layer_tree.itemSelectionChanged.connect(self.from_data_to_ui_for_layer_group)
self.dlg.layer_tree.itemExpanded.connect(self._on_layer_tree_group_state_changed)
self.dlg.layer_tree.itemCollapsed.connect(self._on_layer_tree_group_state_changed)
self.dlg.layer_search_input.textChanged.connect(self._on_layer_search_changed)

self.dlg.scales_warning.set_text(tr(
"The map is in EPSG:3857 (Google Mercator), only the minimum and maximum scales will be used for the map."
Expand Down Expand Up @@ -2489,8 +2493,13 @@ def populate_layer_tree(self) -> dict:
root = self.project.layerTreeRoot()

# Recursively process layer tree nodes
self.process_node(root, None, json_layers)
self.dlg.layer_tree.expandAll()
self.dlg._ignore_layer_tree_state = True
try:
self.process_node(root, None, json_layers)
self.dlg.layer_tree.expandAll() # default: all expanded
self._restore_layer_tree_group_states() # override with saved states (if any)
finally:
self.dlg._ignore_layer_tree_state = False

# Add the self.myDic to the global layerList dictionary
self.layerList = self.myDic
Expand All @@ -2500,6 +2509,66 @@ def populate_layer_tree(self) -> dict:
# The return is used in tests
return json_layers

def _layer_tree_state_key(self) -> str:
"""Return QgsSettings key for group expand states of the current project."""
project_path = self.project.fileName()
if not project_path:
return ''
key_hash = hashlib.sha256(project_path.encode('utf-8')).hexdigest()
return f'lizmap/layer_tree_group_states/{key_hash}'

def _save_layer_tree_group_states(self):
"""Persist expanded/collapsed state of group items to QgsSettings."""
if not self._layer_tree_state_key():
return
states: dict = {}
self._collect_group_states(self.dlg.layer_tree.invisibleRootItem(), states)
QgsSettings().setValue(self._layer_tree_state_key(), json.dumps(states))

def _collect_group_states(self, parent_item: QTreeWidgetItem, states: dict):
"""Recursively collect expanded state for group items."""
for i in range(parent_item.childCount()):
item = parent_item.child(i)
if item.text(2) == 'group':
states[item.text(1)] = item.isExpanded()
self._collect_group_states(item, states)

def _restore_layer_tree_group_states(self):
"""Restore saved expanded/collapsed state to group items."""
key = self._layer_tree_state_key()
if not key:
return
stored = QgsSettings().value(key)
if stored is None:
return
try:
states = json.loads(stored)
except (json.JSONDecodeError, TypeError):
return
self._apply_group_states(self.dlg.layer_tree.invisibleRootItem(), states)

def _apply_group_states(self, parent_item: QTreeWidgetItem, states: dict):
"""Recursively apply expanded state to group items."""
for i in range(parent_item.childCount()):
item = parent_item.child(i)
if item.text(2) == 'group':
group_id = item.text(1)
if group_id in states:
item.setExpanded(states[group_id])
self._apply_group_states(item, states)

def _on_layer_tree_group_state_changed(self, item):
"""Save group states when user manually expands or collapses a group."""
if not self.dlg._ignore_layer_tree_state:
self._save_layer_tree_group_states()

def _on_layer_search_changed(self, text: str):
"""Restore group states when search filter is cleared."""
if not text.strip():
self.dlg._ignore_layer_tree_state = True
self._restore_layer_tree_group_states()
self.dlg._ignore_layer_tree_state = False

def from_data_to_ui_for_layer_group(self):
""" Restore layer/group values into each field when selecting a layer in the tree. """
# At the beginning, enable all widgets.
Expand Down