Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 5 additions & 43 deletions src/jabs/ui/central_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
)
Comment on lines 790 to 794
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why parent is changed to None

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self is the CentralWidget, so probably not really an appropriate parent. It should probably be either the Main Window or None, and Windows won't let you minimize it into the task bar like a normal window if it's a child of some other widget (even the Main Window). When parent is None, on Windows it will have its own entry in the task bar, which I felt made it easier to keep track of.

I could be convinced to make it a child of the Main Window in a future release though

# Connect to cleanup when dialog is closed
self._training_report_dialog.finished.connect(
Expand All @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions src/jabs/ui/training_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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

Expand Down