Coverage for openhcs/pyqt_gui/app.py: 0.0%
88 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
1"""
2OpenHCS PyQt6 Application
4Main application class that initializes the PyQt6 application and
5manages global configuration and services.
6"""
8import sys
9import logging
10import asyncio
11from typing import Optional
12from pathlib import Path
14from PyQt6.QtWidgets import QApplication, QMessageBox
15from PyQt6.QtCore import QTimer
16from PyQt6.QtGui import QIcon
18from openhcs.core.config import GlobalPipelineConfig
19from openhcs.core.orchestrator.gpu_scheduler import setup_global_gpu_registry
20from openhcs.io.base import storage_registry
21from openhcs.io.filemanager import FileManager
23from openhcs.pyqt_gui.main import OpenHCSMainWindow
25logger = logging.getLogger(__name__)
28class OpenHCSPyQtApp(QApplication):
29 """
30 OpenHCS PyQt6 Application.
32 Main application class that manages global state, configuration,
33 and the main window lifecycle.
34 """
36 def __init__(self, argv: list, global_config: Optional[GlobalPipelineConfig] = None):
37 """
38 Initialize the OpenHCS PyQt6 application.
40 Args:
41 argv: Command line arguments
42 global_config: Global configuration (uses default if None)
43 """
44 super().__init__(argv)
46 # Application metadata
47 self.setApplicationName("OpenHCS")
48 self.setApplicationVersion("1.0.0")
49 self.setOrganizationName("OpenHCS Development Team")
50 self.setOrganizationDomain("openhcs.org")
52 # Global configuration
53 self.global_config = global_config or GlobalPipelineConfig()
55 # Shared components
56 self.storage_registry = storage_registry
57 self.file_manager = FileManager(self.storage_registry)
59 # Main window
60 self.main_window: Optional[OpenHCSMainWindow] = None
62 # Setup application
63 self.setup_application()
65 logger.info("OpenHCS PyQt6 application initialized")
67 def setup_application(self):
68 """Setup application-wide configuration."""
69 # Setup GPU registry
70 setup_global_gpu_registry(global_config=self.global_config)
71 logger.info("GPU registry setup completed")
73 # CRITICAL FIX: Establish global config context for lazy dataclass resolution
74 # This was missing and caused placeholder resolution to fall back to static defaults
75 from openhcs.config_framework.global_config import set_global_config_for_editing
76 from openhcs.config_framework.lazy_factory import ensure_global_config_context
77 from openhcs.core.config import GlobalPipelineConfig
79 # Set for editing (UI placeholders)
80 set_global_config_for_editing(GlobalPipelineConfig, self.global_config)
82 # ALSO ensure context for orchestrator creation (required by orchestrator.__init__)
83 ensure_global_config_context(GlobalPipelineConfig, self.global_config)
85 logger.info("Global configuration context established for lazy dataclass resolution")
87 # Set application icon (if available)
88 icon_path = Path(__file__).parent / "resources" / "openhcs_icon.png"
89 if icon_path.exists():
90 self.setWindowIcon(QIcon(str(icon_path)))
92 # Setup exception handling
93 sys.excepthook = self.handle_exception
95 def create_main_window(self) -> OpenHCSMainWindow:
96 """
97 Create and show the main window.
99 Returns:
100 Created main window
101 """
102 if self.main_window is None:
103 self.main_window = OpenHCSMainWindow(self.global_config)
105 # Connect application-level signals
106 self.main_window.config_changed.connect(self.on_config_changed)
108 return self.main_window
110 def show_main_window(self):
111 """Show the main window."""
112 if self.main_window is None:
113 self.create_main_window()
115 self.main_window.show()
116 self.main_window.raise_()
117 self.main_window.activateWindow()
119 def on_config_changed(self, new_config: GlobalPipelineConfig):
120 """
121 Handle global configuration changes.
123 Args:
124 new_config: New global configuration
125 """
126 self.global_config = new_config
127 logger.info("Global configuration updated")
129 def handle_exception(self, exc_type, exc_value, exc_traceback):
130 """
131 Handle uncaught exceptions.
133 Args:
134 exc_type: Exception type
135 exc_value: Exception value
136 exc_traceback: Exception traceback
137 """
138 if issubclass(exc_type, KeyboardInterrupt):
139 # Handle Ctrl+C gracefully
140 sys.__excepthook__(exc_type, exc_value, exc_traceback)
141 return
143 # Log the exception
144 logger.critical(
145 "Uncaught exception",
146 exc_info=(exc_type, exc_value, exc_traceback)
147 )
149 # Show error dialog
150 error_msg = f"An unexpected error occurred:\n\n{exc_type.__name__}: {exc_value}"
152 if self.main_window:
153 QMessageBox.critical(
154 self.main_window,
155 "Unexpected Error",
156 error_msg
157 )
158 else:
159 # No main window - application is in invalid state
160 raise RuntimeError("Uncaught exception occurred but no main window available for error dialog")
162 def run(self) -> int:
163 """
164 Run the application.
166 Returns:
167 Application exit code
168 """
169 try:
170 # Show main window
171 self.show_main_window()
173 # Start event loop
174 exit_code = self.exec()
176 # Ensure clean shutdown
177 self.cleanup()
179 return exit_code
181 except Exception as e:
182 logger.error(f"Error during application run: {e}")
183 self.cleanup()
184 return 1
186 def cleanup(self):
187 """Clean up application resources."""
188 try:
189 logger.info("Starting application cleanup...")
191 # Process any remaining events
192 self.processEvents()
194 # Clean up main window
195 if hasattr(self, 'main_window') and self.main_window:
196 # Force close if not already closed
197 if not self.main_window.isHidden():
198 self.main_window.close()
199 self.main_window.deleteLater()
200 self.main_window = None
202 # Process events again to handle deleteLater
203 self.processEvents()
205 # Force garbage collection
206 import gc
207 gc.collect()
209 logger.info("Application cleanup completed")
211 except Exception as e:
212 logger.warning(f"Error during application cleanup: {e}")
215if __name__ == "__main__":
216 # Don't run directly - use launch.py instead
217 print("Use 'python -m openhcs.pyqt_gui' or 'python -m openhcs.pyqt_gui.launch' to start the GUI")
218 sys.exit(1)