Coverage for openhcs/pyqt_gui/utils/window_utils.py: 0.0%
51 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
1from PyQt6.QtCore import QObject, QEvent, QTimer
2from PyQt6.QtCore import Qt
3from PyQt6.QtWidgets import QApplication, QWidget
6def ensure_window_on_screen(w: QWidget) -> None:
7 """Clamp a window to screen bounds (call once at open)."""
8 if not w:
9 return
10 top = w.window() if hasattr(w, "window") else w
11 if not isinstance(top, QWidget):
12 return
14 screen = top.screen() or QApplication.screenAt(top.frameGeometry().center()) or QApplication.primaryScreen()
15 if not screen:
16 return
18 sg = screen.availableGeometry()
19 wg = top.frameGeometry()
21 # Skip if geometry not ready
22 if wg.width() <= 1 or wg.height() <= 1:
23 return
25 # Shrink if larger than work area
26 max_w = max(100, sg.width() - 20)
27 max_h = max(100, sg.height() - 20)
28 if wg.width() > max_w or wg.height() > max_h:
29 top.resize(min(wg.width(), max_w), min(wg.height(), max_h))
30 wg = top.frameGeometry()
32 # Clamp position to screen
33 nx = max(sg.left(), min(wg.x(), sg.right() - wg.width()))
34 ny = max(sg.top(), min(wg.y(), sg.bottom() - wg.height()))
36 if (nx, ny) != (wg.x(), wg.y()):
37 top.move(nx, ny)
40class _ClampWindowsFilter(QObject):
41 """Clamp windows into screen space on initial show"""
43 def eventFilter(self, obj, event):
44 # Only act on Show events
45 if event.type() != QEvent.Type.Show:
46 return False
48 # Get top-level window
49 top = obj.window() if hasattr(obj, "window") else obj
50 if not isinstance(top, QWidget):
51 return False
53 # Check for window flags
54 has_window_flag = bool(top.windowFlags() & (Qt.WindowType.Window | Qt.WindowType.Dialog))
56 if not (top.isWindow() or has_window_flag):
57 return False
59 # Only clamp once per window
60 if top.property("_openhcs_clamped"):
61 return False
63 # Mark as clamped immediately to prevent re-entry
64 top.setProperty("_openhcs_clamped", True)
66 # Clamp after geometry is finalized
67 def clamp_once():
68 try:
69 if top and not top.property("_openhcs_deleted"):
70 ensure_window_on_screen(top)
71 except RuntimeError:
72 pass
74 QTimer.singleShot(100, clamp_once)
75 return False
78def install_global_window_bounds_filter(app: QApplication):
79 """Install filter once on QApplication (call early in setup_application)."""
80 window_filter = _ClampWindowsFilter(app)
81 app.installEventFilter(window_filter)
82 setattr(app, "_openhcs_window_bounds_filter", window_filter)
83 return window_filter