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
« 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."""
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
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
16logger = logging.getLogger(__name__)
19class BaseHelpWindow(QDialog):
20 """Base class for all PyQt6 help windows - reuses Textual TUI help logic."""
22 def __init__(self, title: str = "Help", color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
23 super().__init__(parent)
25 # Initialize color scheme
26 self.color_scheme = color_scheme or PyQt6ColorScheme()
28 self.setWindowTitle(title)
29 self.setModal(False) # Allow interaction with main window
30 self.resize(600, 400)
32 # Setup UI
33 self.setup_ui()
35 def setup_ui(self):
36 """Setup the base help window UI."""
37 layout = QVBoxLayout(self)
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)
46 # Close button
47 button_layout = QHBoxLayout()
48 button_layout.addStretch()
50 close_btn = QPushButton("Close")
51 close_btn.clicked.connect(self.close)
52 button_layout.addWidget(close_btn)
54 layout.addLayout(button_layout)
57class DocstringHelpWindow(BaseHelpWindow):
58 """Help window for functions and classes - reuses Textual TUI DocstringExtractor."""
60 def __init__(self, target: Union[Callable, type], title: Optional[str] = None,
61 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
62 self.target = target
64 # REUSE Textual TUI docstring extraction logic
65 self.docstring_info = DocstringExtractor.extract(target)
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"
74 super().__init__(title, color_scheme, parent)
75 self.populate_content()
77 def populate_content(self):
78 """Populate the help content using Textual TUI docstring info."""
79 content_widget = QWidget()
80 layout = QVBoxLayout(content_widget)
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)
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)
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)
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)
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)
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)
127 layout.addWidget(param_widget)
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)
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)
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)
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)
157 layout.addStretch()
158 self.content_area.setWidget(content_widget)
161class ParameterHelpWindow(BaseHelpWindow):
162 """Help window for individual parameters - reuses Textual TUI parameter logic."""
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
170 title = f"Parameter Help: {param_name}"
171 super().__init__(title, color_scheme, parent)
172 self.populate_content()
174 def populate_content(self):
175 """Populate parameter help content."""
176 content_widget = QWidget()
177 layout = QVBoxLayout(content_widget)
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})"
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)
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)
203 layout.addStretch()
204 self.content_area.setWidget(content_widget)
207class HelpWindowManager:
208 """PyQt6 help window manager - reuses Textual TUI help logic."""
210 # Class-level storage for singleton windows
211 _docstring_window = None
212 _parameter_window = None
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
232 # Create new window
233 cls._docstring_window = DocstringHelpWindow(target, title=title, parent=parent)
234 cls._docstring_window.show()
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}")
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
259 # Create new window
260 cls._parameter_window = ParameterHelpWindow(param_name, param_description, param_type, parent=parent)
261 cls._parameter_window.show()
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}")
268class HelpableWidget:
269 """Mixin class to add help functionality to PyQt6 widgets - mirrors Textual TUI."""
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)
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)