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

1from PyQt6.QtCore import QObject, QEvent, QTimer 

2from PyQt6.QtCore import Qt 

3from PyQt6.QtWidgets import QApplication, QWidget 

4 

5 

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 

13 

14 screen = top.screen() or QApplication.screenAt(top.frameGeometry().center()) or QApplication.primaryScreen() 

15 if not screen: 

16 return 

17 

18 sg = screen.availableGeometry() 

19 wg = top.frameGeometry() 

20 

21 # Skip if geometry not ready 

22 if wg.width() <= 1 or wg.height() <= 1: 

23 return 

24 

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

31 

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

35 

36 if (nx, ny) != (wg.x(), wg.y()): 

37 top.move(nx, ny) 

38 

39 

40class _ClampWindowsFilter(QObject): 

41 """Clamp windows into screen space on initial show""" 

42 

43 def eventFilter(self, obj, event): 

44 # Only act on Show events 

45 if event.type() != QEvent.Type.Show: 

46 return False 

47 

48 # Get top-level window 

49 top = obj.window() if hasattr(obj, "window") else obj 

50 if not isinstance(top, QWidget): 

51 return False 

52 

53 # Check for window flags 

54 has_window_flag = bool(top.windowFlags() & (Qt.WindowType.Window | Qt.WindowType.Dialog)) 

55 

56 if not (top.isWindow() or has_window_flag): 

57 return False 

58 

59 # Only clamp once per window 

60 if top.property("_openhcs_clamped"): 

61 return False 

62 

63 # Mark as clamped immediately to prevent re-entry 

64 top.setProperty("_openhcs_clamped", True) 

65 

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 

73 

74 QTimer.singleShot(100, clamp_once) 

75 return False 

76 

77 

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