Coverage for openhcs/pyqt_gui/widgets/shared/clickable_help_components.py: 0.0%
157 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"""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 and self.param_description:
67 # Show parameter help using unified manager
68 HelpWindowManager.show_parameter_help(
69 self.param_name, self.param_description, 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)
81class ClickableFunctionTitle(ClickableHelpLabel):
82 """PyQt6 clickable function title that shows function documentation - mirrors Textual TUI."""
84 def __init__(self, func: Callable, index: int = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
85 func_name = getattr(func, '__name__', 'Unknown Function')
86 module_name = getattr(func, '__module__', '').split('.')[-1] if func else ''
88 # Build title text
89 title = f"{index + 1}: {func_name}" if index is not None else func_name
90 if module_name:
91 title += f" ({module_name})"
93 super().__init__(
94 text=title,
95 help_target=func,
96 color_scheme=color_scheme,
97 parent=parent
98 )
100 # Make title bold
101 font = QFont()
102 font.setBold(True)
103 self.setFont(font)
106class ClickableParameterLabel(ClickableHelpLabel):
107 """PyQt6 clickable parameter label that shows parameter documentation - mirrors Textual TUI."""
109 def __init__(self, param_name: str, param_description: str = None,
110 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
111 # Format parameter name nicely
112 display_name = param_name.replace('_', ' ').title()
114 super().__init__(
115 text=display_name,
116 param_name=param_name,
117 param_description=param_description or "No description available",
118 param_type=param_type,
119 color_scheme=color_scheme,
120 parent=parent
121 )
124class HelpIndicator(QLabel):
125 """PyQt6 simple help indicator that can be added next to any widget - mirrors Textual TUI."""
127 help_requested = pyqtSignal()
129 def __init__(self, help_target: Union[Callable, type] = None,
130 param_name: str = None, param_description: str = None,
131 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
132 super().__init__("(?)", parent)
134 # Initialize color scheme
135 self.color_scheme = color_scheme or PyQt6ColorScheme()
137 self.help_target = help_target
138 self.param_name = param_name
139 self.param_description = param_description
140 self.param_type = param_type
142 # Style as clickable help indicator
143 self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
144 self.setStyleSheet(f"""
145 QLabel {{
146 color: {self.color_scheme.to_hex(self.color_scheme.border_light)};
147 font-size: 10px;
148 border: 1px solid {self.color_scheme.to_hex(self.color_scheme.border_light)};
149 border-radius: 8px;
150 padding: 2px 4px;
151 background-color: {self.color_scheme.to_hex(self.color_scheme.window_bg)};
152 }}
153 QLabel:hover {{
154 color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
155 border-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
156 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
157 }}
158 """)
160 # Set fixed size for consistent appearance
161 self.setFixedSize(20, 16)
162 self.setAlignment(Qt.AlignmentFlag.AlignCenter)
164 def mousePressEvent(self, event):
165 """Handle mouse press to show help - reuses Textual TUI help manager pattern."""
166 if event.button() == Qt.MouseButton.LeftButton:
167 try:
168 # Import inside method to avoid circular imports (same pattern as Textual TUI)
169 from openhcs.pyqt_gui.windows.help_windows import HelpWindowManager
171 if self.help_target:
172 # Show function/class help using unified manager
173 HelpWindowManager.show_docstring_help(self.help_target, parent=self)
174 elif self.param_name and self.param_description:
175 # Show parameter help using unified manager
176 HelpWindowManager.show_parameter_help(
177 self.param_name, self.param_description, self.param_type, parent=self
178 )
180 self.help_requested.emit()
182 except Exception as e:
183 logger.error(f"Failed to show help: {e}")
184 raise
186 super().mousePressEvent(event)
189class HelpButton(QPushButton):
190 """PyQt6 help button for adding help functionality to any widget - mirrors Textual TUI."""
192 def __init__(self, help_target: Union[Callable, type] = None,
193 param_name: str = None, param_description: str = None,
194 param_type: type = None, text: str = "Help",
195 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
196 super().__init__(text, parent)
198 # Initialize color scheme
199 self.color_scheme = color_scheme or PyQt6ColorScheme()
201 self.help_target = help_target
202 self.param_name = param_name
203 self.param_description = param_description
204 self.param_type = param_type
206 # Connect click to help display
207 self.clicked.connect(self.show_help)
209 # Style as help button
210 self.setMaximumWidth(60)
211 self.setStyleSheet(f"""
212 QPushButton {{
213 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
214 color: white;
215 border: none;
216 padding: 4px 8px;
217 border-radius: 3px;
218 }}
219 QPushButton:hover {{
220 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
221 }}
222 QPushButton:pressed {{
223 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)};
224 }}
225 """)
227 def show_help(self):
228 """Show help using the unified help manager - reuses Textual TUI logic."""
229 try:
230 # Import inside method to avoid circular imports (same pattern as Textual TUI)
231 from openhcs.pyqt_gui.windows.help_windows import HelpWindowManager
233 if self.help_target:
234 # Show function/class help using unified manager
235 HelpWindowManager.show_docstring_help(self.help_target, parent=self)
236 elif self.param_name and self.param_description:
237 # Show parameter help using unified manager
238 HelpWindowManager.show_parameter_help(
239 self.param_name, self.param_description, self.param_type, parent=self
240 )
242 except Exception as e:
243 logger.error(f"Failed to show help: {e}")
244 raise
247class LabelWithHelp(QWidget):
248 """PyQt6 widget that combines a label with a help indicator - mirrors Textual TUI pattern."""
250 def __init__(self, text: str, help_target: Union[Callable, type] = None,
251 param_name: str = None, param_description: str = None,
252 param_type: type = None, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
253 super().__init__(parent)
255 # Initialize color scheme
256 self.color_scheme = color_scheme or PyQt6ColorScheme()
258 layout = QHBoxLayout(self)
259 layout.setContentsMargins(0, 0, 0, 0)
260 layout.setSpacing(5)
262 # Main label
263 label = QLabel(text)
264 layout.addWidget(label)
266 # Help indicator
267 help_indicator = HelpIndicator(
268 help_target=help_target,
269 param_name=param_name,
270 param_description=param_description,
271 param_type=param_type,
272 color_scheme=self.color_scheme
273 )
274 layout.addWidget(help_indicator)
276 layout.addStretch()
279class FunctionTitleWithHelp(QWidget):
280 """PyQt6 function title with integrated help - mirrors Textual TUI ClickableFunctionTitle."""
282 def __init__(self, func: Callable, index: int = None,
283 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
284 super().__init__(parent)
286 # Initialize color scheme
287 self.color_scheme = color_scheme or PyQt6ColorScheme()
289 layout = QHBoxLayout(self)
290 layout.setContentsMargins(0, 0, 0, 0)
291 layout.setSpacing(10)
293 # Function title
294 func_name = getattr(func, '__name__', 'Unknown Function')
295 module_name = getattr(func, '__module__', '').split('.')[-1] if func else ''
297 title = f"{index + 1}: {func_name}" if index is not None else func_name
298 if module_name:
299 title += f" ({module_name})"
301 title_label = QLabel(title)
302 title_font = QFont()
303 title_font.setBold(True)
304 title_label.setFont(title_font)
305 layout.addWidget(title_label)
307 # Help button
308 help_btn = HelpButton(help_target=func, text="?", color_scheme=self.color_scheme)
309 help_btn.setMaximumWidth(25)
310 help_btn.setMaximumHeight(20)
311 layout.addWidget(help_btn)
313 layout.addStretch()
316class GroupBoxWithHelp(QGroupBox):
317 """PyQt6 group box with integrated help for dataclass titles - mirrors Textual TUI pattern."""
319 def __init__(self, title: str, help_target: Union[Callable, type] = None,
320 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
321 super().__init__(parent)
323 # Initialize color scheme
324 self.color_scheme = color_scheme or PyQt6ColorScheme()
325 self.help_target = help_target
327 # Create custom title widget with help
328 title_widget = QWidget()
329 title_layout = QHBoxLayout(title_widget)
330 title_layout.setContentsMargins(0, 0, 0, 0)
331 title_layout.setSpacing(5)
333 # Title label
334 title_label = QLabel(title)
335 title_font = QFont()
336 title_font.setBold(True)
337 title_label.setFont(title_font)
338 title_layout.addWidget(title_label)
340 # Help button for dataclass
341 if help_target:
342 help_btn = HelpButton(help_target=help_target, text="?", color_scheme=self.color_scheme)
343 help_btn.setMaximumWidth(25)
344 help_btn.setMaximumHeight(20)
345 title_layout.addWidget(help_btn)
347 title_layout.addStretch()
349 # Set the custom title widget
350 self.setTitle("") # Clear default title
352 # Create main layout and add title widget at top
353 main_layout = QVBoxLayout(self)
354 main_layout.addWidget(title_widget)
356 # Content area for child widgets
357 self.content_layout = QVBoxLayout()
358 main_layout.addLayout(self.content_layout)
360 def addWidget(self, widget):
361 """Add widget to the content area."""
362 self.content_layout.addWidget(widget)
364 def addLayout(self, layout):
365 """Add layout to the content area."""
366 self.content_layout.addLayout(layout)