Coverage for openhcs/pyqt_gui/windows/help_windows.py: 0.0%

179 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1"""PyQt6 help system - reuses Textual TUI help logic and components.""" 

2 

3import logging 

4from typing import Union, Callable, Optional 

5from PyQt6.QtWidgets import ( 

6 QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, 

7 QTextEdit, QScrollArea, QWidget, QMessageBox 

8) 

9from PyQt6.QtCore import Qt, pyqtSignal 

10from PyQt6.QtGui import QFont 

11 

12# REUSE the actual working Textual TUI help components 

13from openhcs.textual_tui.widgets.shared.signature_analyzer import DocstringExtractor, SignatureAnalyzer 

14from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme 

15 

16logger = logging.getLogger(__name__) 

17 

18 

19class BaseHelpWindow(QDialog): 

20 """Base class for all PyQt6 help windows - reuses Textual TUI help logic.""" 

21 

22 def __init__(self, title: str = "Help", color_scheme: Optional[PyQt6ColorScheme] = None, parent=None): 

23 super().__init__(parent) 

24 

25 # Initialize color scheme 

26 self.color_scheme = color_scheme or PyQt6ColorScheme() 

27 

28 self.setWindowTitle(title) 

29 self.setModal(False) # Allow interaction with main window 

30 self.resize(600, 400) 

31 

32 # Setup UI 

33 self.setup_ui() 

34 

35 def setup_ui(self): 

36 """Setup the base help window UI.""" 

37 layout = QVBoxLayout(self) 

38 

39 # Content area (to be filled by subclasses) 

40 self.content_area = QScrollArea() 

41 self.content_area.setWidgetResizable(True) 

42 self.content_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 

43 self.content_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 

44 layout.addWidget(self.content_area) 

45 

46 # Close button 

47 button_layout = QHBoxLayout() 

48 button_layout.addStretch() 

49 

50 close_btn = QPushButton("Close") 

51 close_btn.clicked.connect(self.close) 

52 button_layout.addWidget(close_btn) 

53 

54 layout.addLayout(button_layout) 

55 

56 

57class DocstringHelpWindow(BaseHelpWindow): 

58 """Help window for functions and classes - reuses Textual TUI DocstringExtractor.""" 

59 

60 def __init__(self, target: Union[Callable, type], title: Optional[str] = None, 

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

62 self.target = target 

63 

64 # REUSE Textual TUI docstring extraction logic 

65 self.docstring_info = DocstringExtractor.extract(target) 

66 

67 # Generate title from target if not provided 

68 if title is None: 

69 if hasattr(target, '__name__'): 

70 title = f"Help: {target.__name__}" 

71 else: 

72 title = "Help" 

73 

74 super().__init__(title, color_scheme, parent) 

75 self.populate_content() 

76 

77 def populate_content(self): 

78 """Populate the help content using Textual TUI docstring info.""" 

79 content_widget = QWidget() 

80 layout = QVBoxLayout(content_widget) 

81 

82 # Function/class summary 

83 if self.docstring_info.summary: 

84 summary_label = QLabel(self.docstring_info.summary) 

85 summary_label.setWordWrap(True) 

86 summary_font = QFont() 

87 summary_font.setBold(True) 

88 summary_font.setPointSize(12) 

89 summary_label.setFont(summary_font) 

90 layout.addWidget(summary_label) 

91 

92 # Full description 

93 if self.docstring_info.description: 

94 desc_text = QTextEdit() 

95 desc_text.setPlainText(self.docstring_info.description) 

96 desc_text.setReadOnly(True) 

97 desc_text.setMaximumHeight(200) 

98 layout.addWidget(desc_text) 

99 

100 # Parameters section 

101 if self.docstring_info.parameters: 

102 params_label = QLabel("Parameters:") 

103 params_font = QFont() 

104 params_font.setBold(True) 

105 params_label.setFont(params_font) 

106 layout.addWidget(params_label) 

107 

108 for param_name, param_desc in self.docstring_info.parameters.items(): 

109 param_widget = QWidget() 

110 param_layout = QVBoxLayout(param_widget) 

111 param_layout.setContentsMargins(20, 5, 5, 5) 

112 

113 # Parameter name 

114 name_label = QLabel(f"{param_name}") 

115 name_font = QFont() 

116 name_font.setBold(True) 

117 name_label.setFont(name_font) 

118 param_layout.addWidget(name_label) 

119 

120 # Parameter description 

121 if param_desc: 

122 desc_label = QLabel(param_desc) 

123 desc_label.setWordWrap(True) 

124 desc_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_disabled)}; margin-left: 10px;") 

125 param_layout.addWidget(desc_label) 

126 

127 layout.addWidget(param_widget) 

128 

129 # Returns section 

130 if self.docstring_info.returns: 

131 returns_label = QLabel("Returns:") 

132 returns_font = QFont() 

133 returns_font.setBold(True) 

134 returns_label.setFont(returns_font) 

135 layout.addWidget(returns_label) 

136 

137 returns_desc = QLabel(self.docstring_info.returns) 

138 returns_desc.setWordWrap(True) 

139 returns_desc.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_disabled)}; margin-left: 20px;") 

140 layout.addWidget(returns_desc) 

141 

142 # Examples section 

143 if self.docstring_info.examples: 

144 examples_label = QLabel("Examples:") 

145 examples_font = QFont() 

146 examples_font.setBold(True) 

147 examples_label.setFont(examples_font) 

148 layout.addWidget(examples_label) 

149 

150 examples_text = QTextEdit() 

151 examples_text.setPlainText(self.docstring_info.examples) 

152 examples_text.setReadOnly(True) 

153 examples_text.setMaximumHeight(150) 

154 examples_text.setStyleSheet(f"background-color: {self.color_scheme.to_hex(self.color_scheme.window_bg)}; font-family: monospace;") 

155 layout.addWidget(examples_text) 

156 

157 layout.addStretch() 

158 self.content_area.setWidget(content_widget) 

159 

160 

161class ParameterHelpWindow(BaseHelpWindow): 

162 """Help window for individual parameters - reuses Textual TUI parameter logic.""" 

163 

164 def __init__(self, param_name: str, param_description: str, param_type: type = None, 

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

166 self.param_name = param_name 

167 self.param_description = param_description 

168 self.param_type = param_type 

169 

170 title = f"Parameter Help: {param_name}" 

171 super().__init__(title, color_scheme, parent) 

172 self.populate_content() 

173 

174 def populate_content(self): 

175 """Populate parameter help content.""" 

176 content_widget = QWidget() 

177 layout = QVBoxLayout(content_widget) 

178 

179 # Parameter name and type 

180 header_text = self.param_name 

181 if self.param_type: 

182 type_name = getattr(self.param_type, '__name__', str(self.param_type)) 

183 header_text += f" ({type_name})" 

184 

185 header_label = QLabel(header_text) 

186 header_font = QFont() 

187 header_font.setBold(True) 

188 header_font.setPointSize(14) 

189 header_label.setFont(header_font) 

190 layout.addWidget(header_label) 

191 

192 # Parameter description 

193 if self.param_description: 

194 desc_text = QTextEdit() 

195 desc_text.setPlainText(self.param_description) 

196 desc_text.setReadOnly(True) 

197 layout.addWidget(desc_text) 

198 else: 

199 no_desc_label = QLabel("No description available") 

200 no_desc_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_disabled)}; font-style: italic;") 

201 layout.addWidget(no_desc_label) 

202 

203 layout.addStretch() 

204 self.content_area.setWidget(content_widget) 

205 

206 

207class HelpWindowManager: 

208 """PyQt6 help window manager - reuses Textual TUI help logic.""" 

209 

210 # Class-level storage for singleton windows 

211 _docstring_window = None 

212 _parameter_window = None 

213 

214 @classmethod 

215 def show_docstring_help(cls, target: Union[Callable, type], title: Optional[str] = None, parent=None): 

216 """Show help for a function or class - reuses Textual TUI extraction logic.""" 

217 try: 

218 # Check if existing window is still valid 

219 if cls._docstring_window and hasattr(cls._docstring_window, 'isVisible'): 

220 try: 

221 if not cls._docstring_window.isHidden(): 

222 cls._docstring_window.target = target 

223 cls._docstring_window.docstring_info = DocstringExtractor.extract(target) 

224 cls._docstring_window.populate_content() 

225 cls._docstring_window.raise_() 

226 cls._docstring_window.activateWindow() 

227 return 

228 except RuntimeError: 

229 # Window was deleted, clear reference 

230 cls._docstring_window = None 

231 

232 # Create new window 

233 cls._docstring_window = DocstringHelpWindow(target, title=title, parent=parent) 

234 cls._docstring_window.show() 

235 

236 except Exception as e: 

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

238 QMessageBox.warning(parent, "Help Error", f"Failed to show help: {e}") 

239 

240 @classmethod 

241 def show_parameter_help(cls, param_name: str, param_description: str, param_type: type = None, parent=None): 

242 """Show help for a parameter - reuses Textual TUI parameter logic.""" 

243 try: 

244 # Check if existing window is still valid 

245 if cls._parameter_window and hasattr(cls._parameter_window, 'isVisible'): 

246 try: 

247 if not cls._parameter_window.isHidden(): 

248 cls._parameter_window.param_name = param_name 

249 cls._parameter_window.param_description = param_description 

250 cls._parameter_window.param_type = param_type 

251 cls._parameter_window.populate_content() 

252 cls._parameter_window.raise_() 

253 cls._parameter_window.activateWindow() 

254 return 

255 except RuntimeError: 

256 # Window was deleted, clear reference 

257 cls._parameter_window = None 

258 

259 # Create new window 

260 cls._parameter_window = ParameterHelpWindow(param_name, param_description, param_type, parent=parent) 

261 cls._parameter_window.show() 

262 

263 except Exception as e: 

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

265 QMessageBox.warning(parent, "Help Error", f"Failed to show help: {e}") 

266 

267 

268class HelpableWidget: 

269 """Mixin class to add help functionality to PyQt6 widgets - mirrors Textual TUI.""" 

270 

271 def show_function_help(self, target: Union[Callable, type]) -> None: 

272 """Show help window for a function or class.""" 

273 HelpWindowManager.show_docstring_help(target, parent=self) 

274 

275 def show_parameter_help(self, param_name: str, param_description: str, param_type: type = None) -> None: 

276 """Show help window for a parameter.""" 

277 HelpWindowManager.show_parameter_help(param_name, param_description, param_type, parent=self)