Coverage for openhcs/pyqt_gui/widgets/status_bar.py: 0.0%

142 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1""" 

2Status Bar Widget for PyQt6 

3 

4Status display with system information and current operation status. 

5Uses hybrid approach: extracted business logic + clean PyQt6 UI. 

6""" 

7 

8import logging 

9from datetime import datetime 

10from typing import Optional 

11 

12from PyQt6.QtWidgets import ( 

13 QWidget, QHBoxLayout, QLabel, QProgressBar, QFrame 

14) 

15from PyQt6.QtCore import Qt, QTimer, pyqtSignal 

16from PyQt6.QtGui import QFont 

17 

18from openhcs.pyqt_gui.shared.style_generator import StyleSheetGenerator 

19from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme 

20 

21logger = logging.getLogger(__name__) 

22 

23 

24class StatusBarWidget(QWidget): 

25 """ 

26 PyQt6 Status Bar Widget. 

27  

28 Displays current status, progress, and system information. 

29 Preserves all business logic from Textual version with clean PyQt6 UI. 

30 """ 

31 

32 # Signals 

33 status_updated = pyqtSignal(str) # status message 

34 

35 def __init__(self, color_scheme: Optional[PyQt6ColorScheme] = None, parent=None): 

36 """ 

37 Initialize the status bar widget. 

38 

39 Args: 

40 color_scheme: Color scheme for styling (optional, uses default if None) 

41 parent: Parent widget 

42 """ 

43 super().__init__(parent) 

44 

45 # Initialize color scheme and style generator 

46 self.color_scheme = color_scheme or PyQt6ColorScheme() 

47 self.style_generator = StyleSheetGenerator(self.color_scheme) 

48 

49 # Business logic state 

50 self.current_status = "Ready" 

51 self.current_operation = "" 

52 self.progress_value = 0 

53 self.progress_visible = False 

54 

55 # UI components 

56 self.status_label: Optional[QLabel] = None 

57 self.operation_label: Optional[QLabel] = None 

58 self.progress_bar: Optional[QProgressBar] = None 

59 self.time_label: Optional[QLabel] = None 

60 

61 # Timer for time updates 

62 self.time_timer = QTimer() 

63 

64 # Setup UI 

65 self.setup_ui() 

66 self.setup_connections() 

67 self.start_time_updates() 

68 

69 logger.debug("Status bar widget initialized") 

70 

71 def setup_ui(self): 

72 """Setup the user interface.""" 

73 layout = QHBoxLayout(self) 

74 layout.setContentsMargins(5, 2, 5, 2) 

75 layout.setSpacing(10) 

76 

77 # Status section 

78 status_frame = self.create_status_section() 

79 layout.addWidget(status_frame) 

80 

81 # Progress section 

82 progress_frame = self.create_progress_section() 

83 layout.addWidget(progress_frame) 

84 

85 # Spacer 

86 layout.addStretch() 

87 

88 # Time section 

89 time_frame = self.create_time_section() 

90 layout.addWidget(time_frame) 

91 

92 # Set styling 

93 self.setStyleSheet(f""" 

94 StatusBarWidget {{ 

95 background-color: {self.color_scheme.to_hex(self.color_scheme.panel_bg)}; 

96 border-top: 1px solid {self.color_scheme.to_hex(self.color_scheme.border_color)}; 

97 color: white; 

98 }} 

99 """) 

100 

101 # Set fixed height 

102 self.setFixedHeight(30) 

103 

104 def create_status_section(self) -> QWidget: 

105 """ 

106 Create the status message section. 

107  

108 Returns: 

109 Widget containing status information 

110 """ 

111 frame = QFrame() 

112 layout = QHBoxLayout(frame) 

113 layout.setContentsMargins(0, 0, 0, 0) 

114 

115 # Status icon 

116 status_icon = QLabel("●") 

117 status_icon.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.status_success)}; font-weight: bold;") 

118 layout.addWidget(status_icon) 

119 

120 # Status label 

121 self.status_label = QLabel(self.current_status) 

122 self.status_label.setFont(QFont("Arial", 9)) 

123 self.status_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_primary)};") 

124 layout.addWidget(self.status_label) 

125 

126 return frame 

127 

128 def create_progress_section(self) -> QWidget: 

129 """ 

130 Create the progress section. 

131  

132 Returns: 

133 Widget containing progress information 

134 """ 

135 frame = QFrame() 

136 layout = QHBoxLayout(frame) 

137 layout.setContentsMargins(0, 0, 0, 0) 

138 

139 # Operation label 

140 self.operation_label = QLabel("") 

141 self.operation_label.setFont(QFont("Arial", 8)) 

142 self.operation_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_secondary)};") 

143 layout.addWidget(self.operation_label) 

144 

145 # Progress bar 

146 self.progress_bar = QProgressBar() 

147 self.progress_bar.setMaximumWidth(200) 

148 self.progress_bar.setMaximumHeight(16) 

149 self.progress_bar.setVisible(False) 

150 self.progress_bar.setStyleSheet(f""" 

151 QProgressBar {{ 

152 border: 1px solid {self.color_scheme.to_hex(self.color_scheme.border_color)}; 

153 border-radius: 2px; 

154 background-color: {self.color_scheme.to_hex(self.color_scheme.window_bg)}; 

155 color: white; 

156 text-align: center; 

157 font-size: 8px; 

158 }} 

159 QProgressBar::chunk {{ 

160 background-color: {self.color_scheme.to_hex(self.color_scheme.selection_bg)}; 

161 border-radius: 1px; 

162 }} 

163 """) 

164 layout.addWidget(self.progress_bar) 

165 

166 return frame 

167 

168 def create_time_section(self) -> QWidget: 

169 """ 

170 Create the time display section. 

171  

172 Returns: 

173 Widget containing time information 

174 """ 

175 frame = QFrame() 

176 layout = QHBoxLayout(frame) 

177 layout.setContentsMargins(0, 0, 0, 0) 

178 

179 # Time label 

180 self.time_label = QLabel("") 

181 self.time_label.setFont(QFont("Arial", 8)) 

182 self.time_label.setStyleSheet(f"color: {self.color_scheme.to_hex(self.color_scheme.text_disabled)};") 

183 self.time_label.setMinimumWidth(120) 

184 self.time_label.setAlignment(Qt.AlignmentFlag.AlignRight) 

185 layout.addWidget(self.time_label) 

186 

187 return frame 

188 

189 def setup_connections(self): 

190 """Setup signal/slot connections.""" 

191 self.status_updated.connect(self.update_status_display) 

192 self.time_timer.timeout.connect(self.update_time_display) 

193 

194 def start_time_updates(self): 

195 """Start the time update timer.""" 

196 self.time_timer.start(1000) # Update every second 

197 self.update_time_display() # Initial update 

198 

199 def stop_time_updates(self): 

200 """Stop the time update timer.""" 

201 self.time_timer.stop() 

202 

203 def update_time_display(self): 

204 """Update the time display.""" 

205 current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 

206 self.time_label.setText(current_time) 

207 

208 def set_status(self, message: str, status_type: str = "info"): 

209 """ 

210 Set status message with type. 

211  

212 Args: 

213 message: Status message 

214 status_type: Type of status (info, warning, error, success) 

215 """ 

216 self.current_status = message 

217 self.status_updated.emit(message) 

218 

219 # Update status icon color based on type 

220 if hasattr(self, 'status_label') and self.status_label: 

221 parent_frame = self.status_label.parent() 

222 if parent_frame: 

223 status_icon = parent_frame.findChild(QLabel) 

224 if status_icon and status_icon.text() == "●": 

225 color_map = { 

226 "info": self.color_scheme.to_hex(self.color_scheme.status_info), 

227 "warning": self.color_scheme.to_hex(self.color_scheme.status_warning), 

228 "error": self.color_scheme.to_hex(self.color_scheme.status_error), 

229 "success": self.color_scheme.to_hex(self.color_scheme.status_success) 

230 } 

231 color = color_map.get(status_type, self.color_scheme.to_hex(self.color_scheme.status_success)) 

232 status_icon.setStyleSheet(f"color: {color}; font-weight: bold;") 

233 

234 logger.debug(f"Status updated: {message} ({status_type})") 

235 

236 def set_operation(self, operation: str): 

237 """ 

238 Set current operation description. 

239  

240 Args: 

241 operation: Operation description 

242 """ 

243 self.current_operation = operation 

244 if self.operation_label: 

245 self.operation_label.setText(operation) 

246 

247 logger.debug(f"Operation updated: {operation}") 

248 

249 def show_progress(self, value: int = 0, maximum: int = 100): 

250 """ 

251 Show progress bar with value. 

252  

253 Args: 

254 value: Current progress value 

255 maximum: Maximum progress value 

256 """ 

257 self.progress_visible = True 

258 self.progress_value = value 

259 

260 if self.progress_bar: 

261 self.progress_bar.setMaximum(maximum) 

262 self.progress_bar.setValue(value) 

263 self.progress_bar.setVisible(True) 

264 

265 logger.debug(f"Progress shown: {value}/{maximum}") 

266 

267 def update_progress(self, value: int): 

268 """ 

269 Update progress value. 

270  

271 Args: 

272 value: New progress value 

273 """ 

274 self.progress_value = value 

275 

276 if self.progress_bar and self.progress_visible: 

277 self.progress_bar.setValue(value) 

278 

279 def hide_progress(self): 

280 """Hide the progress bar.""" 

281 self.progress_visible = False 

282 

283 if self.progress_bar: 

284 self.progress_bar.setVisible(False) 

285 

286 if self.operation_label: 

287 self.operation_label.setText("") 

288 

289 logger.debug("Progress hidden") 

290 

291 def update_status_display(self, message: str): 

292 """ 

293 Update the status display. 

294  

295 Args: 

296 message: Status message to display 

297 """ 

298 if self.status_label: 

299 self.status_label.setText(message) 

300 

301 def set_info_status(self, message: str): 

302 """Set info status message.""" 

303 self.set_status(message, "info") 

304 

305 def set_warning_status(self, message: str): 

306 """Set warning status message.""" 

307 self.set_status(message, "warning") 

308 

309 def set_error_status(self, message: str): 

310 """Set error status message.""" 

311 self.set_status(message, "error") 

312 

313 def set_success_status(self, message: str): 

314 """Set success status message.""" 

315 self.set_status(message, "success") 

316 

317 def clear_status(self): 

318 """Clear status message.""" 

319 self.set_status("Ready", "info") 

320 

321 def closeEvent(self, event): 

322 """Handle widget close event.""" 

323 self.stop_time_updates() 

324 event.accept()