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

1""" 

2OpenHCS PyQt6 Application 

3 

4Main application class that initializes the PyQt6 application and 

5manages global configuration and services. 

6""" 

7 

8import sys 

9import logging 

10import asyncio 

11from typing import Optional 

12from pathlib import Path 

13 

14from PyQt6.QtWidgets import QApplication, QMessageBox 

15from PyQt6.QtCore import QTimer 

16from PyQt6.QtGui import QIcon 

17 

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 

22 

23from openhcs.pyqt_gui.main import OpenHCSMainWindow 

24 

25logger = logging.getLogger(__name__) 

26 

27 

28class OpenHCSPyQtApp(QApplication): 

29 """ 

30 OpenHCS PyQt6 Application. 

31  

32 Main application class that manages global state, configuration, 

33 and the main window lifecycle. 

34 """ 

35 

36 def __init__(self, argv: list, global_config: Optional[GlobalPipelineConfig] = None): 

37 """ 

38 Initialize the OpenHCS PyQt6 application. 

39  

40 Args: 

41 argv: Command line arguments 

42 global_config: Global configuration (uses default if None) 

43 """ 

44 super().__init__(argv) 

45 

46 # Application metadata 

47 self.setApplicationName("OpenHCS") 

48 self.setApplicationVersion("1.0.0") 

49 self.setOrganizationName("OpenHCS Development Team") 

50 self.setOrganizationDomain("openhcs.org") 

51 

52 # Global configuration 

53 self.global_config = global_config or get_default_global_config() 

54 

55 # Shared components 

56 self.storage_registry = storage_registry 

57 self.file_manager = FileManager(self.storage_registry) 

58 

59 # Main window 

60 self.main_window: Optional[OpenHCSMainWindow] = None 

61 

62 # Setup application 

63 self.setup_application() 

64 

65 logger.info("OpenHCS PyQt6 application initialized") 

66 

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") 

72 

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))) 

77 

78 # Setup exception handling 

79 sys.excepthook = self.handle_exception 

80 

81 def create_main_window(self) -> OpenHCSMainWindow: 

82 """ 

83 Create and show the main window. 

84  

85 Returns: 

86 Created main window 

87 """ 

88 if self.main_window is None: 

89 self.main_window = OpenHCSMainWindow(self.global_config) 

90 

91 # Connect application-level signals 

92 self.main_window.config_changed.connect(self.on_config_changed) 

93 

94 return self.main_window 

95 

96 def show_main_window(self): 

97 """Show the main window.""" 

98 if self.main_window is None: 

99 self.create_main_window() 

100 

101 self.main_window.show() 

102 self.main_window.raise_() 

103 self.main_window.activateWindow() 

104 

105 def on_config_changed(self, new_config: GlobalPipelineConfig): 

106 """ 

107 Handle global configuration changes. 

108  

109 Args: 

110 new_config: New global configuration 

111 """ 

112 self.global_config = new_config 

113 logger.info("Global configuration updated") 

114 

115 def handle_exception(self, exc_type, exc_value, exc_traceback): 

116 """ 

117 Handle uncaught exceptions. 

118  

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 

128 

129 # Log the exception 

130 logger.critical( 

131 "Uncaught exception", 

132 exc_info=(exc_type, exc_value, exc_traceback) 

133 ) 

134 

135 # Show error dialog 

136 error_msg = f"An unexpected error occurred:\n\n{exc_type.__name__}: {exc_value}" 

137 

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") 

147 

148 def run(self) -> int: 

149 """ 

150 Run the application. 

151 

152 Returns: 

153 Application exit code 

154 """ 

155 try: 

156 # Show main window 

157 self.show_main_window() 

158 

159 # Start event loop 

160 exit_code = self.exec() 

161 

162 # Ensure clean shutdown 

163 self.cleanup() 

164 

165 return exit_code 

166 

167 except Exception as e: 

168 logger.error(f"Error during application run: {e}") 

169 self.cleanup() 

170 return 1 

171 

172 def cleanup(self): 

173 """Clean up application resources.""" 

174 try: 

175 logger.info("Starting application cleanup...") 

176 

177 # Process any remaining events 

178 self.processEvents() 

179 

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 

187 

188 # Process events again to handle deleteLater 

189 self.processEvents() 

190 

191 # Force garbage collection 

192 import gc 

193 gc.collect() 

194 

195 logger.info("Application cleanup completed") 

196 

197 except Exception as e: 

198 logger.warning(f"Error during application cleanup: {e}") 

199 

200 

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)