Coverage for openhcs/pyqt_gui/app.py: 0.0%
82 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"""
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, get_default_global_config
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 get_default_global_config()
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 # Set application icon (if available)
74 icon_path = Path(__file__).parent / "resources" / "openhcs_icon.png"
75 if icon_path.exists():
76 self.setWindowIcon(QIcon(str(icon_path)))
78 # Setup exception handling
79 sys.excepthook = self.handle_exception
81 def create_main_window(self) -> OpenHCSMainWindow:
82 """
83 Create and show the main window.
85 Returns:
86 Created main window
87 """
88 if self.main_window is None:
89 self.main_window = OpenHCSMainWindow(self.global_config)
91 # Connect application-level signals
92 self.main_window.config_changed.connect(self.on_config_changed)
94 return self.main_window
96 def show_main_window(self):
97 """Show the main window."""
98 if self.main_window is None:
99 self.create_main_window()
101 self.main_window.show()
102 self.main_window.raise_()
103 self.main_window.activateWindow()
105 def on_config_changed(self, new_config: GlobalPipelineConfig):
106 """
107 Handle global configuration changes.
109 Args:
110 new_config: New global configuration
111 """
112 self.global_config = new_config
113 logger.info("Global configuration updated")
115 def handle_exception(self, exc_type, exc_value, exc_traceback):
116 """
117 Handle uncaught exceptions.
119 Args:
120 exc_type: Exception type
121 exc_value: Exception value
122 exc_traceback: Exception traceback
123 """
124 if issubclass(exc_type, KeyboardInterrupt):
125 # Handle Ctrl+C gracefully
126 sys.__excepthook__(exc_type, exc_value, exc_traceback)
127 return
129 # Log the exception
130 logger.critical(
131 "Uncaught exception",
132 exc_info=(exc_type, exc_value, exc_traceback)
133 )
135 # Show error dialog
136 error_msg = f"An unexpected error occurred:\n\n{exc_type.__name__}: {exc_value}"
138 if self.main_window:
139 QMessageBox.critical(
140 self.main_window,
141 "Unexpected Error",
142 error_msg
143 )
144 else:
145 # No main window - application is in invalid state
146 raise RuntimeError("Uncaught exception occurred but no main window available for error dialog")
148 def run(self) -> int:
149 """
150 Run the application.
152 Returns:
153 Application exit code
154 """
155 try:
156 # Show main window
157 self.show_main_window()
159 # Start event loop
160 exit_code = self.exec()
162 # Ensure clean shutdown
163 self.cleanup()
165 return exit_code
167 except Exception as e:
168 logger.error(f"Error during application run: {e}")
169 self.cleanup()
170 return 1
172 def cleanup(self):
173 """Clean up application resources."""
174 try:
175 logger.info("Starting application cleanup...")
177 # Process any remaining events
178 self.processEvents()
180 # Clean up main window
181 if hasattr(self, 'main_window') and self.main_window:
182 # Force close if not already closed
183 if not self.main_window.isHidden():
184 self.main_window.close()
185 self.main_window.deleteLater()
186 self.main_window = None
188 # Process events again to handle deleteLater
189 self.processEvents()
191 # Force garbage collection
192 import gc
193 gc.collect()
195 logger.info("Application cleanup completed")
197 except Exception as e:
198 logger.warning(f"Error during application cleanup: {e}")
201if __name__ == "__main__":
202 # Don't run directly - use launch.py instead
203 print("Use 'python -m openhcs.pyqt_gui' or 'python -m openhcs.pyqt_gui.launch' to start the GUI")
204 sys.exit(1)