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
« 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).
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
12from . import config
14_GEOMETRY_KEY = "tool_window_geometry"
17class ToolWindow(QDialog):
18 """Non-modal floating window hosting one content widget."""
20 closed = Signal()
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()
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
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)
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)
60 def show_and_raise(self):
61 self.show()
62 self.raise_()
63 self.activateWindow()
65 def reject(self):
66 # Esc would only hide a QDialog; close it so ``closed`` always fires.
67 self.close()
69 def hideEvent(self, event):
70 self._save_geometry()
71 super().hideEvent(event)
73 def closeEvent(self, event):
74 # Not QDialog.closeEvent: it calls reject(), which here calls close().
75 event.accept()
76 self.closed.emit()