Coverage for openhcs/pyqt_gui/windows/help_windows.py: 0.0%
160 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +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
11# REUSE the actual working Textual TUI help components
12from openhcs.introspection.signature_analyzer import DocstringExtractor
13from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme
14from openhcs.pyqt_gui.shared.style_generator import StyleSheetGenerator
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 and style generator
26 self.color_scheme = color_scheme or PyQt6ColorScheme()
27 self.style_generator = StyleSheetGenerator(self.color_scheme)
29 self.setWindowTitle(title)
30 self.setModal(False) # Allow interaction with main window
32 # Setup UI
33 self.setup_ui()
35 # Apply centralized styling
36 self.setStyleSheet(self.style_generator.generate_dialog_style())
38 def setup_ui(self):
39 """Setup the base help window UI."""
40 layout = QVBoxLayout(self)
42 # Content area (to be filled by subclasses)
43 self.content_area = QScrollArea()
44 self.content_area.setWidgetResizable(True)
45 self.content_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
46 self.content_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
47 layout.addWidget(self.content_area)
49 # Close button
50 button_layout = QHBoxLayout()
51 button_layout.addStretch()
53 close_btn = QPushButton("Close")
54 close_btn.clicked.connect(self.close)
55 button_layout.addWidget(close_btn)
57 layout.addLayout(button_layout)
60class DocstringHelpWindow(BaseHelpWindow):
61 """Help window for functions and classes - reuses Textual TUI DocstringExtractor."""
63 def __init__(self, target: Union[Callable, type], title: Optional[str] = None,
64 color_scheme: Optional[PyQt6ColorScheme] = None, parent=None):
65 self.target = target
67 # REUSE Textual TUI docstring extraction logic
68 self.docstring_info = DocstringExtractor.extract(target)
70 # Generate title from target if not provided
71 if title is None:
72 if hasattr(target, '__name__'):
73 title = f"Help: {target.__name__}"
74 else:
75 title = "Help"
77 super().__init__(title, color_scheme, parent)
78 self.populate_content()
80 def populate_content(self):
81 """Populate the help content with minimal styling."""
82 content_widget = QWidget()
83 layout = QVBoxLayout(content_widget)
84 layout.setContentsMargins(10, 10, 10, 10)
85 layout.setSpacing(5)
87 # Function/class summary
88 if self.docstring_info.summary:
89 summary_label = QLabel(self.docstring_info.summary)
90 summary_label.setWordWrap(True)
91 summary_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
92 summary_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)}; font-size: 12px;")
93 layout.addWidget(summary_label)
95 # Full description
96 if self.docstring_info.description:
97 desc_label = QLabel(self.docstring_info.description)
98 desc_label.setWordWrap(True)
99 desc_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
100 desc_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)}; font-size: 12px;")
101 layout.addWidget(desc_label)
103 # Parameters section
104 if self.docstring_info.parameters:
105 params_label = QLabel("Parameters:")
106 params_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
107 params_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_accent)}; font-size: 14px; font-weight: bold; margin-top: 8px;")
108 layout.addWidget(params_label)
110 for param_name, param_desc in self.docstring_info.parameters.items():
111 # Parameter name
112 name_label = QLabel(f"• {param_name}")
113 name_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
114 name_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)}; font-size: 12px; margin-left: 5px; margin-top: 3px;")
115 layout.addWidget(name_label)
117 # Parameter description
118 if param_desc:
119 desc_label = QLabel(param_desc)
120 desc_label.setWordWrap(True)
121 desc_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
122 desc_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)}; font-size: 12px; margin-left: 20px;")
123 layout.addWidget(desc_label)
125 # Returns section
126 if self.docstring_info.returns:
127 returns_label = QLabel("Returns:")
128 returns_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
129 returns_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_accent)}; font-size: 14px; font-weight: bold; margin-top: 8px;")
130 layout.addWidget(returns_label)
132 returns_desc = QLabel(self.docstring_info.returns)
133 returns_desc.setWordWrap(True)
134 returns_desc.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
135 returns_desc.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)}; font-size: 12px; margin-left: 5px;")
136 layout.addWidget(returns_desc)
138 # Examples section
139 if self.docstring_info.examples:
140 examples_label = QLabel("Examples:")
141 examples_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
142 examples_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_accent)}; font-size: 14px; font-weight: bold; margin-top: 8px;")
143 layout.addWidget(examples_label)
145 examples_text = QTextEdit()
146 examples_text.setPlainText(self.docstring_info.examples)
147 examples_text.setReadOnly(True)
148 examples_text.setMaximumHeight(150)
149 examples_text.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
150 examples_text.setStyleSheet(f"""
151 QTextEdit {{
152 background-color: transparent;
153 color: {self.color_scheme.to_hex(self.color_scheme.text_primary)};
154 border: none;
155 font-family: monospace;
156 font-size: 11px;
157 }}
158 QTextEdit:hover {{
159 background-color: transparent;
160 }}
161 """)
162 layout.addWidget(examples_text)
164 layout.addStretch()
165 self.content_area.setWidget(content_widget)
167 # Auto-size to content
168 self.adjustSize()
169 # Set reasonable min/max sizes
170 self.setMinimumSize(400, 200)
171 self.setMaximumSize(800, 600)
174class HelpWindowManager:
175 """PyQt6 help window manager - unified window for all help content."""
177 # Class-level window reference for singleton behavior
178 _help_window = None
180 @classmethod
181 def show_docstring_help(cls, target: Union[Callable, type], title: Optional[str] = None, parent=None):
182 """Show help for a function or class - reuses Textual TUI extraction logic."""
183 try:
184 # Check if existing window is still valid
185 if cls._help_window and hasattr(cls._help_window, 'isVisible'):
186 try:
187 if not cls._help_window.isHidden():
188 cls._help_window.target = target
189 cls._help_window.docstring_info = DocstringExtractor.extract(target)
190 cls._help_window.setWindowTitle(title or f"Help: {getattr(target, '__name__', 'Unknown')}")
191 cls._help_window.populate_content()
192 cls._help_window.raise_()
193 cls._help_window.activateWindow()
194 return
195 except RuntimeError:
196 # Window was deleted, clear reference
197 cls._help_window = None
199 # Create new window
200 cls._help_window = DocstringHelpWindow(target, title=title, parent=parent)
201 cls._help_window.show()
203 except Exception as e:
204 logger.error(f"Failed to show docstring help: {e}")
205 QMessageBox.warning(parent, "Help Error", f"Failed to show help: {e}")
207 @classmethod
208 def show_parameter_help(cls, param_name: str, param_description: str, param_type: type = None, parent=None):
209 """Show help for a parameter - creates a fake docstring object and uses DocstringHelpWindow."""
210 try:
211 # Create a fake docstring info object for the parameter
212 from dataclasses import dataclass
214 @dataclass
215 class FakeDocstringInfo:
216 summary: str = ""
217 description: str = ""
218 parameters: dict = None
219 returns: str = ""
220 examples: str = ""
222 # Build parameter display
223 type_str = f" ({getattr(param_type, '__name__', str(param_type))})" if param_type else ""
224 fake_info = FakeDocstringInfo(
225 summary=f"• {param_name}{type_str}",
226 description=param_description or "No description available",
227 parameters={},
228 returns="",
229 examples=""
230 )
232 # Check if existing window is still valid
233 if cls._help_window and hasattr(cls._help_window, 'isVisible'):
234 try:
235 if not cls._help_window.isHidden():
236 cls._help_window.docstring_info = fake_info
237 cls._help_window.setWindowTitle(f"Parameter: {param_name}")
238 cls._help_window.populate_content()
239 cls._help_window.raise_()
240 cls._help_window.activateWindow()
241 return
242 except RuntimeError:
243 # Window was deleted, clear reference
244 cls._help_window = None
246 # Create new window with fake target
247 class FakeTarget:
248 __name__ = param_name
250 cls._help_window = DocstringHelpWindow(FakeTarget, title=f"Parameter: {param_name}", parent=parent)
251 cls._help_window.docstring_info = fake_info
252 cls._help_window.populate_content()
253 cls._help_window.show()
255 except Exception as e:
256 logger.error(f"Failed to show parameter help: {e}")
257 QMessageBox.warning(parent, "Help Error", f"Failed to show help: {e}")
260class HelpableWidget:
261 """Mixin class to add help functionality to PyQt6 widgets - mirrors Textual TUI."""
263 def show_function_help(self, target: Union[Callable, type]) -> None:
264 """Show help window for a function or class."""
265 HelpWindowManager.show_docstring_help(target, parent=self)
267 def show_parameter_help(self, param_name: str, param_description: str, param_type: type = None) -> None:
268 """Show help window for a parameter."""
269 HelpWindowManager.show_parameter_help(param_name, param_description, param_type, parent=self)