feat: Implement lazy-loading for SearchFilterWidget to improve performance and resource management

This commit is contained in:
William Valentin
2025-08-08 17:26:45 -07:00
parent c54095df0b
commit 117e489072
+42 -17
View File
@@ -24,7 +24,6 @@ from pathology_management_window import PathologyManagementWindow
from pathology_manager import PathologyManager
from preferences import get_config_dir, get_pref, save_preferences, set_pref
from search_filter import DataFilter
from search_filter_ui import SearchFilterWidget
from settings_window import SettingsWindow
from theme_manager import ThemeManager
from ui_manager import UIManager
@@ -346,6 +345,8 @@ class MedTrackerApp:
# --- Main Frame ---
main_frame: ttk.Frame = ttk.Frame(self.root, padding="10", style="Card.TFrame")
main_frame.grid(row=0, column=0, sticky="nsew")
# Store for lazy child creation (search panel)
self.main_frame = main_frame
# Configure root window grid
self.root.grid_rowconfigure(0, weight=1)
@@ -400,19 +401,14 @@ class MedTrackerApp:
self.tree: ttk.Treeview = table_ui["tree"]
self.tree.bind("<Double-1>", self.handle_double_click)
# --- Create Search/Filter Widget ---
self.search_filter_widget = SearchFilterWidget(
main_frame,
self.data_filter,
self._on_filter_update,
self.medicine_manager,
self.pathology_manager,
logger,
)
# Restore prior visibility state from preferences
# --- Search/Filter Widget (lazy-loaded) ---
self.search_filter_widget = None # Created on demand
# Restore prior visibility preference, but only create when needed
self.search_filter_visible = bool(get_pref("search_panel_visible", False))
if self.search_filter_visible:
self.search_filter_widget.show()
self._ensure_search_widget()
# mypy: widget ensured above
self.search_filter_widget.show() # type: ignore[union-attr]
# --- Create Status Bar ---
self.status_bar = self.ui_manager.create_status_bar(main_frame)
@@ -426,9 +422,8 @@ class MedTrackerApp:
# Force one-time restoration in refresh and reflect in the UI if visible
try:
self.refresh_data_display(apply_filters=True)
if self.search_filter_visible and hasattr(
self.search_filter_widget, "sync_ui_from_filter"
):
if self.search_filter_visible and self.search_filter_widget is not None:
# Keep UI in sync only if panel is actually instantiated
self.search_filter_widget.sync_ui_from_filter()
except Exception:
self.refresh_data_display()
@@ -1169,18 +1164,44 @@ Use Ctrl+S to save entries and Ctrl+Q to quit."""
def _toggle_search_filter(self) -> None:
"""Toggle the search and filter panel."""
if self.search_filter_visible:
self.search_filter_widget.hide()
if self.search_filter_widget is not None:
self.search_filter_widget.hide()
self.search_filter_visible = False
self.ui_manager.update_status("Search panel hidden", "info")
set_pref("search_panel_visible", False)
save_preferences()
else:
self.search_filter_widget.show()
self._ensure_search_widget()
# mypy: widget ensured above
self.search_filter_widget.show() # type: ignore[union-attr]
self.search_filter_visible = True
self.ui_manager.update_status("Search panel shown", "info")
set_pref("search_panel_visible", True)
save_preferences()
def _ensure_search_widget(self) -> None:
"""Create the search widget on demand to support lazy-loading."""
if getattr(self, "search_filter_widget", None) is not None:
return
try:
# Local import to defer module load cost until first use
from search_filter_ui import SearchFilterWidget # type: ignore
self.search_filter_widget = SearchFilterWidget(
self.main_frame,
self.data_filter,
self._on_filter_update,
self.medicine_manager,
self.pathology_manager,
logger,
)
# If filters were restored earlier, reflect state in UI now
with contextlib.suppress(Exception):
self.search_filter_widget.sync_ui_from_filter()
except Exception as exc:
logger.error(f"Failed to initialize search panel: {exc}")
self.ui_manager.update_status("Search panel unavailable", "error")
def _on_filter_update(self) -> None:
"""Handle filter updates from the search widget."""
# Debounce rapid filter changes to avoid repeated heavy refresh.
@@ -1608,6 +1629,10 @@ Use Ctrl+S to save entries and Ctrl+Q to quit."""
if current_scroll_top > 0:
self.tree.yview_moveto(current_scroll_top)
# Ensure alternating stripes are normalized after any update
with contextlib.suppress(Exception):
self.ui_manager.normalize_tree_stripes(self.tree)
except Exception as e:
logger.error(f"Error updating tree efficiently: {e}")