Coverage for openhcs/textual_tui/windows/config_window.py: 0.0%
60 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""Configuration window for OpenHCS Textual TUI."""
3from typing import Type, Any, Callable, Optional
4from textual.app import ComposeResult
5from textual.widgets import Button
6from textual.containers import Container, Horizontal
8from openhcs.textual_tui.windows.base_window import BaseOpenHCSWindow
9from openhcs.textual_tui.widgets.config_form import ConfigFormWidget
12class ConfigWindow(BaseOpenHCSWindow):
13 """Configuration window using textual-window system."""
15 DEFAULT_CSS = """
16 ConfigWindow {
17 width: 60; height: 20;
18 min-width: 60; min-height: 20;
19 }
20 ConfigWindow #dialog_container {
21 width: 80;
22 height: 30;
23 }
24 """
26 def __init__(self, config_class: Type, current_config: Any,
27 on_save_callback: Optional[Callable] = None,
28 is_global_config_editing: bool = False, **kwargs):
29 """
30 Initialize config window.
32 Args:
33 config_class: Configuration class type
34 current_config: Current configuration instance
35 on_save_callback: Function to call when config is saved
36 """
37 super().__init__(
38 window_id="config_dialog",
39 title="Configuration",
40 mode="temporary",
41 **kwargs
42 )
44 self.config_class = config_class
45 self.current_config = current_config
46 self.on_save_callback = on_save_callback
48 # Create the form widget using unified parameter analysis
49 self.config_form = ConfigFormWidget.from_dataclass(config_class, current_config, is_global_config_editing=is_global_config_editing)
51 def calculate_content_height(self) -> int:
52 """Calculate dialog height based on number of fields."""
53 # Base height for title, buttons, padding
54 base_height = 8
56 # Height per field (label + input + spacing)
57 field_height = 2
59 # Count total fields (including nested)
60 total_fields = len(self.field_specs)
62 # Add extra height for nested dataclasses
63 for spec in self.field_specs:
64 if hasattr(spec.actual_type, '__dataclass_fields__'):
65 # Nested dataclass adds extra height for collapsible
66 total_fields += len(spec.actual_type.__dataclass_fields__) + 1
68 calculated = base_height + (total_fields * field_height)
70 # Clamp between reasonable bounds
71 return min(max(calculated, 15), 40)
75 def compose(self) -> ComposeResult:
76 """Compose the config window content."""
77 with Container(classes="dialog-content"):
78 yield self.config_form
80 # Buttons
81 with Horizontal(classes="dialog-buttons"):
82 yield Button("Reset to Defaults", id="reset_to_defaults", compact=True)
83 yield Button("Save", id="save", compact=True)
84 yield Button("Cancel", id="cancel", compact=True)
86 def on_mount(self) -> None:
87 """Called when the window is mounted - prevent automatic scrolling on focus."""
88 # Override the default focus behavior to prevent automatic scrolling
89 # when the first widget in the form gets focus
90 self.call_after_refresh(self._set_initial_focus_without_scroll)
92 def _set_initial_focus_without_scroll(self) -> None:
93 """Set focus to the first input without causing scroll."""
94 try:
95 # Find the first focusable widget in the config form
96 first_input = self.config_form.query("Input, Checkbox, RadioSet").first()
97 if first_input:
98 # Focus without scrolling to prevent the window from jumping
99 first_input.focus(scroll_visible=False)
100 except Exception:
101 # If no focusable widgets found, that's fine - no focus needed
102 pass
106 def on_button_pressed(self, event: Button.Pressed) -> None:
107 """Handle button presses."""
108 if event.button.id == "save":
109 self._handle_save()
110 elif event.button.id == "cancel":
111 self.close_window()
112 elif event.button.id == "reset_to_defaults":
113 self._handle_reset_to_defaults()
115 def _handle_save(self):
116 """Handle save button - reuse existing logic from ConfigDialogScreen."""
117 # Get form values (same method as original)
118 form_values = self.config_form.get_config_values()
120 # Create new config instance (same as original)
121 new_config = self.config_class(**form_values)
123 # Call the callback if provided
124 if self.on_save_callback:
125 self.on_save_callback(new_config)
127 self.close_window()
129 def _handle_reset_to_defaults(self):
130 """Reset all parameters using individual field reset logic for consistency."""
131 # Use the same logic as individual reset buttons to ensure consistency
132 # This delegates to the form manager's lazy-aware reset logic
133 if hasattr(self.config_form.form_manager, 'reset_all_parameters'):
134 # Use the form manager's lazy-aware reset_all_parameters method
135 self.config_form.form_manager.reset_all_parameters()
136 else:
137 # Fallback: reset each parameter individually
138 from openhcs.textual_tui.widgets.shared.signature_analyzer import SignatureAnalyzer
139 param_info = SignatureAnalyzer.analyze(self.config_class)
140 for param_name in param_info.keys():
141 if hasattr(self.config_form.form_manager, 'reset_parameter'):
142 self.config_form.form_manager.reset_parameter(param_name)