Coverage for openhcs/pyqt_gui/widgets/shared/clickable_help_components.py: 0.0%
157 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
1"""PyQt6 clickable help components - clean architecture without circular imports."""
3import logging
4from typing import Union, Callable, Optional
5from PyQt6.QtWidgets import QLabel, QPushButton, QWidget, QHBoxLayout, QGroupBox, QVBoxLayout
6from PyQt6.QtCore import Qt, pyqtSignal
7from PyQt6.QtGui import QFont, QCursor
9from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme
11logger = logging.getLogger(__name__)
14class ClickableHelpLabel(QLabel):
15 """PyQt6 clickable label that shows help information - reuses Textual TUI help logic."""
17 help_requested = pyqtSignal()
19 def __init__(self, text: str, help_target: Union[Callable, type] = None,
20 param_name: str = None, param_description: str = None,
21 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
22 """Initialize clickable help label.
24 Args:
25 text: Display text for the label
26 help_target: Function or class to show help for (for function help)
27 param_name: Parameter name (for parameter help)
28 param_description: Parameter description (for parameter help)
29 param_type: Parameter type (for parameter help)
30 color_scheme: Color scheme for styling (optional, uses default if None)
31 """
32 # Add help indicator to text
33 display_text = f"{text} (?)"
34 super().__init__(display_text, parent)
36 # Initialize color scheme
37 self.color_scheme = color_scheme or PyQt6ColorScheme()
39 self.help_target = help_target
40 self.param_name = param_name
41 self.param_description = param_description
42 self.param_type = param_type
44 # Style as clickable
45 self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
46 self.setStyleSheet(f"""
47 QLabel {{
48 color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
49 text-decoration: underline;
50 }}
51 QLabel:hover {{
52 color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
53 }}
54 """)
56 def mousePressEvent(self, event):
57 """Handle mouse press to show help - reuses Textual TUI help manager pattern."""
58 if event.button() == Qt.MouseButton.LeftButton:
59 try:
60 # Import inside method to avoid circular imports (same pattern as Textual TUI)
61 from openhcs.pyqt_gui.windows.help_windows import HelpWindowManager
63 if self.help_target:
64 # Show function/class help using unified manager
65 HelpWindowManager.show_docstring_help(self.help_target, parent=self)
66 elif self.param_name:
67 # Show parameter help using the description passed from parameter analysis
68 HelpWindowManager.show_parameter_help(
69 self.param_name, self.param_description or "No description available", self.param_type, parent=self
70 )
72 self.help_requested.emit()
74 except Exception as e:
75 logger.error(f"Failed to show help: {e}")
76 raise
78 super().mousePressEvent(event)
83class ClickableFunctionTitle(ClickableHelpLabel):
84 """PyQt6 clickable function title that shows function documentation - mirrors Textual TUI."""
86 def __init__(self, func: Callable, index: int = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
87 func_name = getattr(func, '__name__', 'Unknown Function')
88 module_name = getattr(func, '__module__', '').split('.')[-1] if func else ''
90 # Build title text
91 title = f"{index + 1}: {func_name}" if index is not None else func_name
92 if module_name:
93 title += f" ({module_name})"
95 super().__init__(
96 text=title,
97 help_target=func,
98 color_scheme=color_scheme,
99 parent=parent
100 )
102 # Make title bold
103 font = QFont()
104 font.setBold(True)
105 self.setFont(font)
108class ClickableParameterLabel(ClickableHelpLabel):
109 """PyQt6 clickable parameter label that shows parameter documentation - mirrors Textual TUI."""
111 def __init__(self, param_name: str, param_description: str = None,
112 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
113 # Format parameter name nicely
114 display_name = param_name.replace('_', ' ').title()
116 super().__init__(
117 text=display_name,
118 param_name=param_name,
119 param_description=param_description or "No description available",
120 param_type=param_type,
121 color_scheme=color_scheme,
122 parent=parent
123 )
126class HelpIndicator(QLabel):
127 """PyQt6 simple help indicator that can be added next to any widget - mirrors Textual TUI."""
129 help_requested = pyqtSignal()
131 def __init__(self, help_target: Union[Callable, type] = None,
132 param_name: str = None, param_description: str = None,
133 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
134 super().__init__("(?)", parent)
136 # Initialize color scheme
137 self.color_scheme = color_scheme or PyQt6ColorScheme()
139 self.help_target = help_target
140 self.param_name = param_name
141 self.param_description = param_description
142 self.param_type = param_type
144 # Style as clickable help indicator
145 self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
146 self.setStyleSheet(f"""
147 QLabel {{
148 color: {self.color_scheme.to_hex(self.color_scheme.border_light)};
149 font-size: 10px;
150 border: 1px solid {self.color_scheme.to_hex(self.color_scheme.border_light)};
151 border-radius: 8px;
152 padding: 2px 4px;
153 background-color: {self.color_scheme.to_hex(self.color_scheme.window_bg)};
154 }}
155 QLabel:hover {{
156 color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
157 border-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
158 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
159 }}
160 """)
162 # Set fixed size for consistent appearance
163 self.setFixedSize(20, 16)
164 self.setAlignment(Qt.AlignmentFlag.AlignCenter)
166 def mousePressEvent(self, event):
167 """Handle mouse press to show help - reuses Textual TUI help manager pattern."""
168 if event.button() == Qt.MouseButton.LeftButton:
169 try:
170 # Import inside method to avoid circular imports (same pattern as Textual TUI)
171 from openhcs.pyqt_gui.windows.help_windows import HelpWindowManager
173 if self.help_target:
174 # Show function/class help using unified manager
175 HelpWindowManager.show_docstring_help(self.help_target, parent=self)
176 elif self.param_name:
177 # Show parameter help using the description passed from parameter analysis
178 HelpWindowManager.show_parameter_help(
179 self.param_name, self.param_description or "No description available", self.param_type, parent=self
180 )
182 self.help_requested.emit()
184 except Exception as e:
185 logger.error(f"Failed to show help: {e}")
186 raise
188 super().mousePressEvent(event)
193class HelpButton(QPushButton):
194 """PyQt6 help button for adding help functionality to any widget - mirrors Textual TUI."""
196 def __init__(self, help_target: Union[Callable, type] = None,
197 param_name: str = None, param_description: str = None,
198 param_type: type = None, text: str = "Help",
199 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
200 super().__init__(text, parent)
202 # Initialize color scheme
203 self.color_scheme = color_scheme or PyQt6ColorScheme()
205 self.help_target = help_target
206 self.param_name = param_name
207 self.param_description = param_description
208 self.param_type = param_type
210 # Connect click to help display
211 self.clicked.connect(self.show_help)
213 # Style as help button
214 self.setMaximumWidth(60)
215 self.setStyleSheet(f"""
216 QPushButton {{
217 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
218 color: white;
219 border: none;
220 padding: 4px 8px;
221 border-radius: 3px;
222 }}
223 QPushButton:hover {{
224 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
225 }}
226 QPushButton:pressed {{
227 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
228 }}
229 """)
231 def show_help(self):
232 """Show help using the unified help manager - reuses Textual TUI logic."""
233 try:
234 # Import inside method to avoid circular imports (same pattern as Textual TUI)
235 from openhcs.pyqt_gui.windows.help_windows import HelpWindowManager
237 if self.help_target:
238 # Show function/class help using unified manager
239 HelpWindowManager.show_docstring_help(self.help_target, parent=self)
240 elif self.param_name:
241 # Show parameter help using the description passed from parameter analysis
242 HelpWindowManager.show_parameter_help(
243 self.param_name, self.param_description or "No description available", self.param_type, parent=self
244 )
246 except Exception as e:
247 logger.error(f"Failed to show help: {e}")
248 raise
253class LabelWithHelp(QWidget):
254 """PyQt6 widget that combines a label with a help indicator - mirrors Textual TUI pattern."""
256 def __init__(self, text: str, help_target: Union[Callable, type] = None,
257 param_name: str = None, param_description: str = None,
258 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
259 super().__init__(parent)
261 # Initialize color scheme
262 self.color_scheme = color_scheme or PyQt6ColorScheme()
264 layout = QHBoxLayout(self)
265 layout.setContentsMargins(0, 0, 0, 0)
266 layout.setSpacing(5)
268 # Main label
269 label = QLabel(text)
270 layout.addWidget(label)
272 # Help indicator
273 help_indicator = HelpIndicator(
274 help_target=help_target,
275 param_name=param_name,
276 param_description=param_description,
277 param_type=param_type,
278 color_scheme=self.color_scheme
279 )
280 layout.addWidget(help_indicator)
282 layout.addStretch()
285class FunctionTitleWithHelp(QWidget):
286 """PyQt6 function title with integrated help - mirrors Textual TUI ClickableFunctionTitle."""
288 def __init__(self, func: Callable, index: int = None,
289 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
290 super().__init__(parent)
292 # Initialize color scheme
293 self.color_scheme = color_scheme or PyQt6ColorScheme()
295 layout = QHBoxLayout(self)
296 layout.setContentsMargins(0, 0, 0, 0)
297 layout.setSpacing(10)
299 # Function title
300 func_name = getattr(func, '__name__', 'Unknown Function')
301 module_name = getattr(func, '__module__', '').split('.')[-1] if func else ''
303 title = f"{index + 1}: {func_name}" if index is not None else func_name
304 if module_name:
305 title += f" ({module_name})"
307 title_label = QLabel(title)
308 title_font = QFont()
309 title_font.setBold(True)
310 title_label.setFont(title_font)
311 layout.addWidget(title_label)
313 # Help button
314 help_btn = HelpButton(help_target=func, text="?", color_scheme=self.color_scheme)
315 help_btn.setMaximumWidth(25)
316 help_btn.setMaximumHeight(20)
317 layout.addWidget(help_btn)
319 layout.addStretch()
322class GroupBoxWithHelp(QGroupBox):
323 """PyQt6 group box with integrated help for dataclass titles - mirrors Textual TUI pattern."""
325 def __init__(self, title: str, help_target: Union[Callable, type] = None,
326 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
327 super().__init__(parent)
329 # Initialize color scheme
330 self.color_scheme = color_scheme or PyQt6ColorScheme()
331 self.help_target = help_target
333 # Create custom title widget with help
334 title_widget = QWidget()
335 title_layout = QHBoxLayout(title_widget)
336 title_layout.setContentsMargins(0, 0, 0, 0)
337 title_layout.setSpacing(5)
339 # Title label
340 title_label = QLabel(title)
341 title_font = QFont()
342 title_font.setBold(True)
343 title_label.setFont(title_font)
344 title_layout.addWidget(title_label)
346 # Help button for dataclass
347 if help_target:
348 help_btn = HelpButton(help_target=help_target, text="?", color_scheme=self.color_scheme)
349 help_btn.setMaximumWidth(25)
350 help_btn.setMaximumHeight(20)
351 title_layout.addWidget(help_btn)
353 title_layout.addStretch()
355 # Set the custom title widget
356 self.setTitle("") # Clear default title
358 # Create main layout and add title widget at top
359 main_layout = QVBoxLayout(self)
360 main_layout.addWidget(title_widget)
362 # Content area for child widgets
363 self.content_layout = QVBoxLayout()
364 main_layout.addLayout(self.content_layout)
366 def addWidget(self, widget):
367 """Add widget to the content area."""
368 self.content_layout.addWidget(widget)
370 def addLayout(self, layout):
371 """Add layout to the content area."""
372 self.content_layout.addLayout(layout)