triplot 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,256 @@
1
+ """Edit > Settings: the house style, for every figure and for this one.
2
+
3
+ Two columns, because two things are wanted at once: defaults that persist
4
+ between sessions, and a way to override them for one particular save
5
+ file.
6
+
7
+ * **Default** is the user's own house style. It lives on this computer
8
+ (`core/style.py`, `preferences.json`), is written when the page is closed
9
+ with OK, and is what every figure falls back on.
10
+ * **This figure** belongs to the open document and is saved in its session
11
+ file. Empty ("default") means "whatever the default says"; a value here
12
+ wins for this figure alone. The change is one undo step.
13
+
14
+ A size set in an object's OWN settings (double-click a label) wins over both
15
+ columns, because that was a decision about that one object.
16
+
17
+ Below them, **Handling**: how the program responds to the hand - the pick
18
+ distance. It has a Default and nothing else, because it is about the person
19
+ at the trackpad and not about any figure.
20
+
21
+ Live, like every dialog here: the plot follows each change as it is made.
22
+ Closing the page any way keeps the changes; Revert puts back what was there
23
+ when it opened - both columns.
24
+ """
25
+
26
+ from PySide6.QtCore import Qt
27
+ from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QFrame,
28
+ QGridLayout, QLabel, QScrollArea, QVBoxLayout,
29
+ QWidget)
30
+
31
+ from ..core import style
32
+ from .dialogs import (FontChoice, NumberBox, StyleChoice, StyleNumber,
33
+ StyleText, enter_stays, readable)
34
+
35
+
36
+ class SettingsDialog(QDialog):
37
+ """The house style, as a two-column table."""
38
+
39
+ def __init__(self, window):
40
+ QDialog.__init__(self, window)
41
+ self.setWindowTitle("Settings")
42
+ # Not modal, like the object dialogs: a size is judged by looking
43
+ # at the plot, and a modal page hides it.
44
+ self.setModal(False)
45
+ self.setWindowFlag(Qt.Tool, True)
46
+ self.main = window
47
+ self.doc = window.doc
48
+ self._saved_defaults = style.preferences()
49
+ self._saved_figure = dict((s.key, getattr(self.doc.style, s.key))
50
+ for s in style.FIGURE_SETTINGS)
51
+ self.defaults = {}
52
+ self.figure = {}
53
+
54
+ layout = QVBoxLayout(self)
55
+ # The rows SCROLL: the list only gets longer, and a page taller than
56
+ # the screen puts its OK button out of reach.
57
+ self._scroll = QScrollArea(self)
58
+ self._scroll.setWidgetResizable(True)
59
+ self._scroll.setFrameShape(QFrame.NoFrame)
60
+ self._scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
61
+ body = QWidget(self._scroll)
62
+ self._scroll.setWidget(body)
63
+ grid = QGridLayout(body)
64
+ grid.setContentsMargins(0, 0, 8, 0)
65
+ grid.setHorizontalSpacing(12)
66
+ layout.addWidget(self._scroll, 1)
67
+ for column, text in ((1, "Default"), (2, "This figure")):
68
+ head = QLabel("<b>{}</b>".format(text))
69
+ grid.addWidget(head, 0, column)
70
+ handling = [s for s in style.SETTINGS if not s.figure]
71
+ rows = list(style.FIGURE_SETTINGS) + ([None] if handling else []) + handling
72
+ for row, setting in enumerate(rows, start=1):
73
+ if setting is None:
74
+ head = QLabel("<b>Handling</b>")
75
+ head.setContentsMargins(0, 10, 0, 0)
76
+ grid.addWidget(head, row, 0)
77
+ continue
78
+ name = QLabel(setting.title)
79
+ if setting.note:
80
+ name.setToolTip(setting.note)
81
+ grid.addWidget(name, row, 0)
82
+ key = setting.key
83
+ if setting.kind in ("format", "font"):
84
+ # The Default column's None is "the built-in value"; the
85
+ # figure's is "whatever the default says".
86
+ kind = StyleText if setting.kind == "format" else FontChoice
87
+ default = kind(style.user_preference(key),
88
+ (lambda k=key: style.builtin(k)),
89
+ parent=self, reset_text="Built-in")
90
+ default.changed.connect(
91
+ lambda k=key: self._default_changed(k))
92
+ own = kind(getattr(self.doc.style, key),
93
+ (lambda k=key: style.preference(k)), parent=self)
94
+ elif setting.kind == "choice":
95
+ titles = setting.titles
96
+ default = StyleChoice(setting.choices, style.preference(key),
97
+ lambda: "", parent=self,
98
+ titles=titles, allow_default=False)
99
+ default.changed.connect(
100
+ lambda k=key: self._default_changed(k))
101
+ own = StyleChoice(
102
+ setting.choices, getattr(self.doc.style, key),
103
+ (lambda k=key, t=titles: t.get(
104
+ style.preference(k), style.preference(k))),
105
+ parent=self, titles=titles)
106
+ else:
107
+ default = NumberBox(self)
108
+ default.setDecimals(setting.decimals)
109
+ default.setRange(float(setting.low), float(setting.high))
110
+ default.setSingleStep(float(setting.step))
111
+ default.setSuffix(setting.suffix)
112
+ default.setValue(float(style.preference(key)))
113
+ default.valueChanged.connect(
114
+ lambda _v=0.0, k=key: self._default_changed(k))
115
+ own = None
116
+ if setting.figure:
117
+ own = StyleNumber(setting, getattr(self.doc.style, key),
118
+ (lambda k=key: style.preference(k)),
119
+ parent=self)
120
+ self.defaults[key] = default
121
+ grid.addWidget(default, row, 1)
122
+ if setting.note and not setting.figure:
123
+ default.setToolTip(setting.note)
124
+ if own is not None:
125
+ own.changed.connect(lambda k=key: self._figure_changed(k))
126
+ self.figure[key] = own
127
+ grid.addWidget(own, row, 2)
128
+
129
+ note = QLabel(
130
+ "Default: this computer, every figure. This figure: saved in its "
131
+ "session. An object's own setting wins over both.")
132
+ note.setWordWrap(True)
133
+ note.setStyleSheet("color: #9a9a9a;")
134
+ layout.addWidget(note)
135
+
136
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok
137
+ | QDialogButtonBox.Cancel
138
+ | QDialogButtonBox.RestoreDefaults)
139
+ buttons.button(QDialogButtonBox.Cancel).setText("Revert")
140
+ buttons.button(QDialogButtonBox.Cancel).setToolTip(
141
+ "Undo this page's changes and close. Closing otherwise keeps "
142
+ "them.")
143
+ buttons.accepted.connect(self.accept)
144
+ buttons.rejected.connect(self.revert)
145
+ buttons.button(QDialogButtonBox.RestoreDefaults).setText(
146
+ "Built-in defaults")
147
+ buttons.button(QDialogButtonBox.RestoreDefaults).setToolTip(
148
+ "Default column back to the built-in values.")
149
+ buttons.button(QDialogButtonBox.RestoreDefaults).clicked.connect(
150
+ lambda _c=False: self.restore_builtin())
151
+ layout.addWidget(buttons)
152
+ # As tall as the rows need, up to most of the screen; beyond that
153
+ # the rows scroll and the buttons stay put.
154
+ wanted = body.sizeHint().height() + note.sizeHint().height() \
155
+ + buttons.sizeHint().height() + 60
156
+ screen = window.screen()
157
+ limit = (int(screen.availableGeometry().height() * 0.75)
158
+ if screen is not None else wanted)
159
+ self.resize(max(560, body.sizeHint().width() + 40),
160
+ min(wanted, limit))
161
+
162
+ # ------------------------------------------------------------ editing
163
+ def default_value(self, key):
164
+ widget = self.defaults[key]
165
+ if isinstance(widget, (StyleChoice, StyleText, FontChoice)):
166
+ return widget.value()
167
+ return float(widget.value())
168
+
169
+ def set_default(self, key, value):
170
+ """Change one default as if it had been typed. For the button, and
171
+ for tests."""
172
+ widget = self.defaults[key]
173
+ if isinstance(widget, (StyleText, FontChoice)):
174
+ widget.blockSignals(True)
175
+ widget.set_value(None if value == style.builtin(key) else value)
176
+ widget.blockSignals(False)
177
+ self._default_changed(key)
178
+ return
179
+ inner = widget.combo if isinstance(widget, StyleChoice) else widget
180
+ inner.blockSignals(True)
181
+ if isinstance(widget, StyleChoice):
182
+ widget.set_value(value)
183
+ else:
184
+ widget.setValue(float(value))
185
+ inner.blockSignals(False)
186
+ self._default_changed(key)
187
+
188
+ def _default_changed(self, key):
189
+ style.set_preference(key, self.default_value(key))
190
+ # The "This figure" column shows the default wherever it follows it.
191
+ if key in self.figure:
192
+ self.figure[key].refresh()
193
+ self._live()
194
+
195
+ def _figure_changed(self, key):
196
+ setattr(self.doc.style, key, self.figure[key].value())
197
+ self._live()
198
+
199
+ def restore_builtin(self):
200
+ for setting in style.SETTINGS:
201
+ self.set_default(setting.key, setting.default)
202
+
203
+ def _live(self):
204
+ self.main._live_change()
205
+
206
+ # ----------------------------------------------------------- finishing
207
+ def accept(self):
208
+ """Keep both columns: the defaults on disk, the figure on the stack.
209
+
210
+ Written defensively: this runs in a Qt slot, where an exception is an
211
+ abort rather than a traceback, and a read-only settings folder is not
212
+ a reason to lose the figure.
213
+ """
214
+ try:
215
+ style.save_preferences()
216
+ except OSError as exc:
217
+ self.main.note.setText("Could not save the defaults: {}".format(
218
+ exc))
219
+ changes = []
220
+ for key, old in self._saved_figure.items():
221
+ new = getattr(self.doc.style, key)
222
+ if new != old:
223
+ setattr(self.doc.style, key, old)
224
+ changes.append((self.doc.style, key, new))
225
+ if changes and self.main.doc is self.doc:
226
+ self.main.undo.set_props(changes, "figure style")
227
+ else:
228
+ self._live()
229
+ QDialog.accept(self)
230
+
231
+ def reject(self):
232
+ """Its X, Esc or Ctrl+W: keep the changes, like OK (see
233
+ `_LiveDialog.reject`). Only Revert puts them back."""
234
+ self.accept()
235
+
236
+ def keyPressEvent(self, ev):
237
+ """Enter takes the value and keeps the page open (`enter_stays`)."""
238
+ if enter_stays(self, ev):
239
+ return
240
+ QDialog.keyPressEvent(self, ev)
241
+
242
+ def showEvent(self, ev):
243
+ readable(self)
244
+ QDialog.showEvent(self, ev)
245
+
246
+ def revert(self):
247
+ style.restore_preferences(self._saved_defaults)
248
+ for key, old in self._saved_figure.items():
249
+ setattr(self.doc.style, key, old)
250
+ self._live()
251
+ QDialog.reject(self)
252
+
253
+ # Qt calls the handlers here by itself; an error in one is logged and
254
+ # survived rather than the end of the program (`core/log.py`).
255
+ from ..core import log as _log
256
+ _log.guard_classes(globals(), __name__)