diff --git a/src/jabs/ui/central_widget.py b/src/jabs/ui/central_widget.py index c2712dbf..6cb04eb1 100644 --- a/src/jabs/ui/central_widget.py +++ b/src/jabs/ui/central_widget.py @@ -753,7 +753,8 @@ def _training_thread_complete(self, elapsed_ms) -> None: jabs_app_has_focus = QApplication.activeWindow() is not None # If any JABS window has focus and a dialog exists, close it and create new one - # This is the only reliable way to switch focus on macOS + # This is the only reliable way to switch focus on macOS to ensure the dialog is + # brought to the front if jabs_app_has_focus and self._training_report_dialog is not None: # Save the old position and size before closing old_geometry = self._training_report_dialog.geometry() @@ -764,49 +765,32 @@ def _training_thread_complete(self, elapsed_ms) -> None: self._training_report_dialog = TrainingReportDialog( self._training_report_markdown, title=f"Training Report: {self._controls.current_behavior}", - parent=self, + parent=None, ) - - # Connect to cleanup when dialog is closed self._training_report_dialog.finished.connect( lambda: setattr(self, "_training_report_dialog", None) ) - - # Show with aggressive focus-stealing - self._training_report_dialog.setWindowFlags( - self._training_report_dialog.windowFlags() | Qt.WindowType.WindowStaysOnTopHint - ) self._training_report_dialog.show() - # Restore geometry AFTER show() to preserve position self._training_report_dialog.setGeometry(old_geometry) - # Force processing of events to ensure window system updates QtCore.QCoreApplication.processEvents() self._training_report_dialog.raise_() self._training_report_dialog.activateWindow() - # Try to force repaint/update self._training_report_dialog.repaint() QtCore.QCoreApplication.processEvents() - # Remove stay-on-top flag after a brief delay - QtCore.QTimer.singleShot(100, lambda: self._remove_stay_on_top_flag()) - elif self._training_report_dialog is not None: # JABS doesn't have focus - just update existing dialog quietly self._training_report_dialog.update_content( self._training_report_markdown, title=f"Training Report: {self._controls.current_behavior}", ) - # Ensure dialog is visible (in case it was minimized or hidden) - if self._training_report_dialog.isMinimized(): - self._training_report_dialog.showNormal() - elif not self._training_report_dialog.isVisible(): - self._training_report_dialog.show() + else: # No existing dialog - create new one self._training_report_dialog = TrainingReportDialog( self._training_report_markdown, title=f"Training Report: {self._controls.current_behavior}", - parent=self, + parent=None, ) # Connect to cleanup when dialog is closed self._training_report_dialog.finished.connect( @@ -816,30 +800,8 @@ def _training_thread_complete(self, elapsed_ms) -> None: # Show the dialog self._training_report_dialog.show() - # Only bring to front and activate if JABS has focus - if jabs_app_has_focus: - # Temporarily set stay-on-top to ensure it appears in front - self._training_report_dialog.setWindowFlags( - self._training_report_dialog.windowFlags() - | Qt.WindowType.WindowStaysOnTopHint - ) - self._training_report_dialog.show() # Need to call show() again after changing flags - self._training_report_dialog.raise_() - self._training_report_dialog.activateWindow() - - # Remove stay-on-top flag after a brief delay so user can manage windows normally - QtCore.QTimer.singleShot(100, lambda: self._remove_stay_on_top_flag()) - self._training_report_markdown = None # Clear after displaying - def _remove_stay_on_top_flag(self): - """Remove WindowStaysOnTopHint from training report dialog.""" - if self._training_report_dialog is not None: - self._training_report_dialog.setWindowFlags( - self._training_report_dialog.windowFlags() & ~Qt.WindowType.WindowStaysOnTopHint - ) - self._training_report_dialog.show() # Need to call show() again after changing flags - def _training_thread_error_callback(self, error: Exception) -> None: """handle an error in the training thread""" self._cleanup_training_thread() diff --git a/src/jabs/ui/training_report.py b/src/jabs/ui/training_report.py index 39800447..21d406fe 100644 --- a/src/jabs/ui/training_report.py +++ b/src/jabs/ui/training_report.py @@ -4,6 +4,7 @@ import markdown2 from PySide6 import QtCore +from PySide6.QtCore import Qt from PySide6.QtGui import QIcon from PySide6.QtWebEngineWidgets import QWebEngineView from PySide6.QtWidgets import ( @@ -32,6 +33,15 @@ def __init__(self, markdown_content: str, title: str = "Training Report", parent self.setWindowTitle(title) self.resize(1200, 700) + # Ensure the window close button is enabled on all platforms + # Set window flags to ensure all standard window controls are present and enabled + self.setWindowFlags( + self.windowFlags() + | Qt.WindowType.WindowCloseButtonHint + | Qt.WindowType.WindowMinimizeButtonHint + | Qt.WindowType.WindowMaximizeButtonHint + ) + # Store markdown content for copying to clipboard self._markdown_content = markdown_content