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

1"""PyQt6 clickable help components - clean architecture without circular imports.""" 

2 

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 

8 

9from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme 

10 

11logger = logging.getLogger(__name__) 

12 

13 

14class ClickableHelpLabel(QLabel): 

15 """PyQt6 clickable label that shows help information - reuses Textual TUI help logic.""" 

16 

17 help_requested = pyqtSignal() 

18 

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. 

23 

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) 

35 

36 # Initialize color scheme 

37 self.color_scheme = color_scheme or PyQt6ColorScheme() 

38 

39 self.help_target = help_target 

40 self.param_name = param_name 

41 self.param_description = param_description 

42 self.param_type = param_type 

43 

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 """) 

55 

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 

62 

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 ) 

71 

72 self.help_requested.emit() 

73 

74 except Exception as e: 

75 logger.error(f"Failed to show help: {e}") 

76 raise 

77 

78 super().mousePressEvent(event) 

79 

80 

81class ClickableFunctionTitle(ClickableHelpLabel): 

82 """PyQt6 clickable function title that shows function documentation - mirrors Textual TUI.""" 

83 

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 '' 

87 

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})" 

92 

93 super().__init__( 

94 text=title, 

95 help_target=func, 

96 color_scheme=color_scheme, 

97 parent=parent 

98 ) 

99 

100 # Make title bold 

101 font = QFont() 

102 font.setBold(True) 

103 self.setFont(font) 

104 

105 

106class ClickableParameterLabel(ClickableHelpLabel): 

107 """PyQt6 clickable parameter label that shows parameter documentation - mirrors Textual TUI.""" 

108 

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() 

113 

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 ) 

122 

123 

124class HelpIndicator(QLabel): 

125 """PyQt6 simple help indicator that can be added next to any widget - mirrors Textual TUI.""" 

126 

127 help_requested = pyqtSignal() 

128 

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) 

133 

134 # Initialize color scheme 

135 self.color_scheme = color_scheme or PyQt6ColorScheme() 

136 

137 self.help_target = help_target 

138 self.param_name = param_name 

139 self.param_description = param_description 

140 self.param_type = param_type 

141 

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 """) 

159 

160 # Set fixed size for consistent appearance 

161 self.setFixedSize(20, 16) 

162 self.setAlignment(Qt.AlignmentFlag.AlignCenter) 

163 

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 

170 

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 ) 

179 

180 self.help_requested.emit() 

181 

182 except Exception as e: 

183 logger.error(f"Failed to show help: {e}") 

184 raise 

185 

186 super().mousePressEvent(event) 

187 

188 

189class HelpButton(QPushButton): 

190 """PyQt6 help button for adding help functionality to any widget - mirrors Textual TUI.""" 

191 

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) 

197 

198 # Initialize color scheme 

199 self.color_scheme = color_scheme or PyQt6ColorScheme() 

200 

201 self.help_target = help_target 

202 self.param_name = param_name 

203 self.param_description = param_description 

204 self.param_type = param_type 

205 

206 # Connect click to help display 

207 self.clicked.connect(self.show_help) 

208 

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 """) 

226 

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 

232 

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 ) 

241 

242 except Exception as e: 

243 logger.error(f"Failed to show help: {e}") 

244 raise 

245 

246 

247class LabelWithHelp(QWidget): 

248 """PyQt6 widget that combines a label with a help indicator - mirrors Textual TUI pattern.""" 

249 

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) 

254 

255 # Initialize color scheme 

256 self.color_scheme = color_scheme or PyQt6ColorScheme() 

257 

258 layout = QHBoxLayout(self) 

259 layout.setContentsMargins(0, 0, 0, 0) 

260 layout.setSpacing(5) 

261 

262 # Main label 

263 label = QLabel(text) 

264 layout.addWidget(label) 

265 

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) 

275 

276 layout.addStretch() 

277 

278 

279class FunctionTitleWithHelp(QWidget): 

280 """PyQt6 function title with integrated help - mirrors Textual TUI ClickableFunctionTitle.""" 

281 

282 def __init__(self, func: Callable, index: int = None, 

283 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None): 

284 super().__init__(parent) 

285 

286 # Initialize color scheme 

287 self.color_scheme = color_scheme or PyQt6ColorScheme() 

288 

289 layout = QHBoxLayout(self) 

290 layout.setContentsMargins(0, 0, 0, 0) 

291 layout.setSpacing(10) 

292 

293 # Function title 

294 func_name = getattr(func, '__name__', 'Unknown Function') 

295 module_name = getattr(func, '__module__', '').split('.')[-1] if func else '' 

296 

297 title = f"{index + 1}: {func_name}" if index is not None else func_name 

298 if module_name: 

299 title += f" ({module_name})" 

300 

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) 

306 

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) 

312 

313 layout.addStretch() 

314 

315 

316class GroupBoxWithHelp(QGroupBox): 

317 """PyQt6 group box with integrated help for dataclass titles - mirrors Textual TUI pattern.""" 

318 

319 def __init__(self, title: str, help_target: Union[Callable, type] = None, 

320 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None): 

321 super().__init__(parent) 

322 

323 # Initialize color scheme 

324 self.color_scheme = color_scheme or PyQt6ColorScheme() 

325 self.help_target = help_target 

326 

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) 

332 

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) 

339 

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) 

346 

347 title_layout.addStretch() 

348 

349 # Set the custom title widget 

350 self.setTitle("") # Clear default title 

351 

352 # Create main layout and add title widget at top 

353 main_layout = QVBoxLayout(self) 

354 main_layout.addWidget(title_widget) 

355 

356 # Content area for child widgets 

357 self.content_layout = QVBoxLayout() 

358 main_layout.addLayout(self.content_layout) 

359 

360 def addWidget(self, widget): 

361 """Add widget to the content area.""" 

362 self.content_layout.addWidget(widget) 

363 

364 def addLayout(self, layout): 

365 """Add layout to the content area.""" 

366 self.content_layout.addLayout(layout)