Coverage for paintmaskanimator/tool_window.py: 85%

51 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""Small floating windows for occasional tools (colour chart, onion settings). 

2 

3These used to be dock panels, but they are opened only now and then and need 

4more room than a panel column gives them. A :class:`ToolWindow` floats above 

5the main window (``Qt.Tool``: on macOS it hides while the app is in the 

6background, like a palette window), is not modal, and remembers where the user 

7left it. 

8""" 

9from PySide6.QtCore import QByteArray, Qt, Signal 

10from PySide6.QtWidgets import QDialog, QVBoxLayout 

11 

12from . import config 

13 

14_GEOMETRY_KEY = "tool_window_geometry" 

15 

16 

17class ToolWindow(QDialog): 

18 """Non-modal floating window hosting one content widget.""" 

19 

20 closed = Signal() 

21 

22 def __init__(self, parent, title, key, content, default_size=None): 

23 super().__init__(parent, Qt.WindowType.Tool) 

24 self.setWindowTitle(title) 

25 self.setModal(False) 

26 self._key = key 

27 self.content = content 

28 layout = QVBoxLayout(self) 

29 layout.setContentsMargins(0, 0, 0, 0) 

30 layout.addWidget(content) 

31 if not self._restore_geometry(): 31 ↛ exitline 31 didn't return from function '__init__' because the condition on line 31 was always true

32 if default_size is not None: 

33 self.resize(*default_size) 

34 self._place_beside_parent() 

35 

36 def _restore_geometry(self): 

37 saved = (config.get_value(_GEOMETRY_KEY, {}) or {}).get(self._key) 

38 if not saved: 38 ↛ 40line 38 didn't jump to line 40 because the condition on line 38 was always true

39 return False 

40 try: 

41 return self.restoreGeometry(QByteArray.fromBase64(saved.encode("ascii"))) 

42 except (AttributeError, TypeError, ValueError): 

43 return False 

44 

45 def _save_geometry(self): 

46 stored = dict(config.get_value(_GEOMETRY_KEY, {}) or {}) 

47 stored[self._key] = bytes(self.saveGeometry().toBase64().data()).decode("ascii") 

48 config.set_value(_GEOMETRY_KEY, stored) 

49 

50 def _place_beside_parent(self): 

51 parent = self.parentWidget() 

52 if parent is None: 52 ↛ 53line 52 didn't jump to line 53 because the condition on line 52 was never true

53 return 

54 frame = parent.frameGeometry() 

55 # Upper right of the main window, clear of the colour column's top. 

56 x = frame.right() - self.width() - 280 

57 y = frame.top() + 90 

58 self.move(max(frame.left(), x), y) 

59 

60 def show_and_raise(self): 

61 self.show() 

62 self.raise_() 

63 self.activateWindow() 

64 

65 def reject(self): 

66 # Esc would only hide a QDialog; close it so ``closed`` always fires. 

67 self.close() 

68 

69 def hideEvent(self, event): 

70 self._save_geometry() 

71 super().hideEvent(event) 

72 

73 def closeEvent(self, event): 

74 # Not QDialog.closeEvent: it calls reject(), which here calls close(). 

75 event.accept() 

76 self.closed.emit()