qplotter 1.6.0b3__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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +63 -0
- qplot/_metadata.py +12 -0
- qplot/_repair.py +14 -0
- qplot/_shutdown_supervisor.py +4049 -0
- qplot/_version.py +12 -0
- qplot/_windows_shutdown_job.py +1166 -0
- qplot/configuration/__init__.py +3 -0
- qplot/configuration/config.py +573 -0
- qplot/configuration/config_schema.json +319 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +579 -0
- qplot/configuration/themes/blank.py +17 -0
- qplot/configuration/themes/dark.py +54 -0
- qplot/configuration/themes/light.py +54 -0
- qplot/datahandling/LoadFromDB.py +224 -0
- qplot/datahandling/__init__.py +47 -0
- qplot/datahandling/_trusted_live_helper.py +812 -0
- qplot/datahandling/_trusted_live_protocol.py +1745 -0
- qplot/datahandling/database.py +2330 -0
- qplot/datahandling/dimensions.py +43 -0
- qplot/datahandling/file_identity.py +461 -0
- qplot/datahandling/parameter_data.py +149 -0
- qplot/datahandling/plot_progress.py +17 -0
- qplot/datahandling/qcodes_cache.py +277 -0
- qplot/datahandling/qcodes_compat.py +14 -0
- qplot/datahandling/readSQL.py +2442 -0
- qplot/datahandling/readonly.py +2561 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +127 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +868 -0
- qplot/datahandling/trusted_heatmap.py +241 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1894 -0
- qplot/datahandling/trusted_live_supervisor.py +2161 -0
- qplot/datahandling/trusted_plot.py +283 -0
- qplot/datahandling/trusted_presentation.py +1065 -0
- qplot/datahandling/trusted_snapshot.py +1132 -0
- qplot/datahandling/trusted_work_coordinator.py +2157 -0
- qplot/datahandling/trusted_work_scheduler.py +1204 -0
- qplot/diagnostics.py +241 -0
- qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
- qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
- qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
- qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
- qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
- qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
- qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
- qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
- qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
- qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
- qplot/testdata.py +1733 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +114 -0
- qplot/tools/general.py +134 -0
- qplot/tools/heatmap_geometry.py +381 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +625 -0
- qplot/tools/sample_statistics.py +56 -0
- qplot/tools/worker.py +2787 -0
- qplot/windows/__init__.py +9 -0
- qplot/windows/_colorbar.py +422 -0
- qplot/windows/_commands.py +523 -0
- qplot/windows/_config_persistence.py +123 -0
- qplot/windows/_database_actions.py +4904 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_dragdrop.py +71 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +169 -0
- qplot/windows/_native_averaging.py +173 -0
- qplot/windows/_native_transforms.py +389 -0
- qplot/windows/_plot1d_snap.py +608 -0
- qplot/windows/_plot1d_traces.py +2183 -0
- qplot/windows/_plot2d_colorbar.py +1126 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2197 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +1993 -0
- qplot/windows/_plot_actions.py +2695 -0
- qplot/windows/_plot_appearance.py +276 -0
- qplot/windows/_plot_axis_scaling.py +1485 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +110 -0
- qplot/windows/_plot_marquee.py +656 -0
- qplot/windows/_plot_refresh.py +942 -0
- qplot/windows/_plot_state.py +239 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +635 -0
- qplot/windows/_refresh_interval.py +70 -0
- qplot/windows/_run_controls.py +828 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +531 -0
- qplot/windows/_subplots/subplot2d.py +918 -0
- qplot/windows/_svg_export.py +209 -0
- qplot/windows/_trusted_derived_qt.py +1759 -0
- qplot/windows/_widgets/__init__.py +23 -0
- qplot/windows/_widgets/_run_formatting.py +387 -0
- qplot/windows/_widgets/details_tables.py +1119 -0
- qplot/windows/_widgets/dropbox.py +290 -0
- qplot/windows/_widgets/operations.py +460 -0
- qplot/windows/_widgets/preview.py +2997 -0
- qplot/windows/_widgets/run_list_items.py +488 -0
- qplot/windows/_widgets/toolbar.py +111 -0
- qplot/windows/_widgets/treeWidgets.py +2572 -0
- qplot/windows/_window_controls.py +413 -0
- qplot/windows/main.py +1635 -0
- qplot/windows/plot1d.py +311 -0
- qplot/windows/plot2d.py +1901 -0
- qplotter-1.6.0b3.dist-info/METADATA +318 -0
- qplotter-1.6.0b3.dist-info/RECORD +119 -0
- qplotter-1.6.0b3.dist-info/WHEEL +5 -0
- qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from PyQt6 import (
|
|
5
|
+
QtCore,
|
|
6
|
+
QtGui,
|
|
7
|
+
)
|
|
8
|
+
from PyQt6 import (
|
|
9
|
+
QtWidgets as qtw,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from ._commands import create_action
|
|
13
|
+
from ._config_persistence import persist_config_values
|
|
14
|
+
from ._window_controls import (
|
|
15
|
+
CONFIRM_CLOSE_ALL_KEY,
|
|
16
|
+
CONFIRM_QUIT_KEY,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
THEME_OPTIONS = (
|
|
20
|
+
("Light", "light"),
|
|
21
|
+
("Dark", "dark"),
|
|
22
|
+
("PyQt", "pyqt"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
MOUSE_MODE_KEY = "user_preference.mouse_mode"
|
|
26
|
+
MOUSE_MODE_OPTIONS = (
|
|
27
|
+
("Rectangle zoom (1 button)", "rect"),
|
|
28
|
+
("Pan (3 button)", "pan"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
COPY_PLOT_IMAGE_RESOLUTION_KEY = "user_preference.copy_plot_image_resolution"
|
|
32
|
+
COPY_PLOT_IMAGE_RESOLUTION_SCREEN = "screen"
|
|
33
|
+
COPY_PLOT_IMAGE_RESOLUTION_300_DPI = "dpi_300"
|
|
34
|
+
COPY_PLOT_IMAGE_RESOLUTION_SVG = "svg"
|
|
35
|
+
COPY_PLOT_IMAGE_RESOLUTION_OPTIONS = (
|
|
36
|
+
("Screen resolution", COPY_PLOT_IMAGE_RESOLUTION_SCREEN),
|
|
37
|
+
("300 dpi", COPY_PLOT_IMAGE_RESOLUTION_300_DPI),
|
|
38
|
+
("Vector SVG", COPY_PLOT_IMAGE_RESOLUTION_SVG),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
COLORBAR_WIDTH_KEY = "user_preference.colorbar_width"
|
|
42
|
+
AXIS_TICK_WIDTH_KEY = "user_preference.axis_tick_width"
|
|
43
|
+
AXIS_MAJOR_TICK_COUNT_KEY = "user_preference.axis_major_tick_count"
|
|
44
|
+
|
|
45
|
+
PREFERENCE_KEYS = (
|
|
46
|
+
"user_preference.theme",
|
|
47
|
+
COLORBAR_WIDTH_KEY,
|
|
48
|
+
AXIS_TICK_WIDTH_KEY,
|
|
49
|
+
AXIS_MAJOR_TICK_COUNT_KEY,
|
|
50
|
+
"GUI.preview_size",
|
|
51
|
+
MOUSE_MODE_KEY,
|
|
52
|
+
COPY_PLOT_IMAGE_RESOLUTION_KEY,
|
|
53
|
+
"file.default_load_path",
|
|
54
|
+
"user_preference.default_refresh_rate",
|
|
55
|
+
CONFIRM_CLOSE_ALL_KEY,
|
|
56
|
+
CONFIRM_QUIT_KEY,
|
|
57
|
+
"runtime_settings.max_threads",
|
|
58
|
+
"runtime_settings.max_full_heatmap_points",
|
|
59
|
+
"runtime_settings.max_heatmap_grid_cells",
|
|
60
|
+
"runtime_settings.max_heatmap_grid_side",
|
|
61
|
+
"runtime_settings.del_grace_period",
|
|
62
|
+
"runtime_settings.cloud_sync_timeout",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PreferencesDialog(qtw.QDialog):
|
|
67
|
+
"""
|
|
68
|
+
Dialog for editing common config-backed qPlot preferences.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
preferencesApplied = QtCore.pyqtSignal()
|
|
72
|
+
|
|
73
|
+
def __init__(self, config, parent=None):
|
|
74
|
+
super().__init__(parent)
|
|
75
|
+
self.config = config
|
|
76
|
+
|
|
77
|
+
self.setWindowTitle("Preferences")
|
|
78
|
+
self.setModal(True)
|
|
79
|
+
self.setMinimumWidth(420)
|
|
80
|
+
|
|
81
|
+
self._build_ui()
|
|
82
|
+
self._edited_preference_keys: set[str] = set()
|
|
83
|
+
for key, widget in zip(PREFERENCE_KEYS, self._preference_controls(), strict=True):
|
|
84
|
+
mark_edited = lambda _value, key=key: self._edited_preference_keys.add(key)
|
|
85
|
+
if isinstance(widget, (qtw.QSpinBox, qtw.QDoubleSpinBox)):
|
|
86
|
+
signal = widget.valueChanged
|
|
87
|
+
line_edit = widget.lineEdit()
|
|
88
|
+
if line_edit is not None:
|
|
89
|
+
# Typing the same rounded display is still an explicit
|
|
90
|
+
# edit, even when the numeric valueChanged signal is silent.
|
|
91
|
+
line_edit.textEdited.connect(mark_edited)
|
|
92
|
+
elif isinstance(widget, qtw.QComboBox):
|
|
93
|
+
signal = widget.currentIndexChanged
|
|
94
|
+
elif isinstance(widget, qtw.QLineEdit):
|
|
95
|
+
signal = widget.textChanged
|
|
96
|
+
else:
|
|
97
|
+
signal = widget.toggled
|
|
98
|
+
signal.connect(mark_edited)
|
|
99
|
+
self.load_from_config()
|
|
100
|
+
|
|
101
|
+
def _build_ui(self):
|
|
102
|
+
layout = qtw.QVBoxLayout(self)
|
|
103
|
+
layout.setContentsMargins(12, 12, 12, 12)
|
|
104
|
+
layout.setSpacing(10)
|
|
105
|
+
|
|
106
|
+
tabs = qtw.QTabWidget(self)
|
|
107
|
+
tabs.addTab(self._appearance_tab(), "Appearance")
|
|
108
|
+
tabs.addTab(self._interaction_tab(), "Interaction")
|
|
109
|
+
tabs.addTab(self._files_tab(), "Files")
|
|
110
|
+
tabs.addTab(self._confirmations_tab(), "Confirmations")
|
|
111
|
+
tabs.addTab(self._runtime_tab(), "Runtime")
|
|
112
|
+
layout.addWidget(tabs)
|
|
113
|
+
|
|
114
|
+
self.buttonBox = qtw.QDialogButtonBox(
|
|
115
|
+
qtw.QDialogButtonBox.StandardButton.Ok
|
|
116
|
+
| qtw.QDialogButtonBox.StandardButton.Cancel
|
|
117
|
+
| qtw.QDialogButtonBox.StandardButton.Apply
|
|
118
|
+
| qtw.QDialogButtonBox.StandardButton.RestoreDefaults,
|
|
119
|
+
self,
|
|
120
|
+
)
|
|
121
|
+
self.buttonBox.accepted.connect(self._accept_preferences)
|
|
122
|
+
self.buttonBox.rejected.connect(self.reject)
|
|
123
|
+
apply_button = self.buttonBox.button(qtw.QDialogButtonBox.StandardButton.Apply)
|
|
124
|
+
if apply_button is None:
|
|
125
|
+
raise RuntimeError("Preferences dialog Apply button is not available.")
|
|
126
|
+
apply_button.clicked.connect(self.apply_preferences)
|
|
127
|
+
self.restoreDefaultsButton = self.buttonBox.button(
|
|
128
|
+
qtw.QDialogButtonBox.StandardButton.RestoreDefaults
|
|
129
|
+
)
|
|
130
|
+
if self.restoreDefaultsButton is None:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
"Preferences dialog Restore Defaults button is not available."
|
|
133
|
+
)
|
|
134
|
+
self.restoreDefaultsButton.setObjectName("restorePreferenceDefaultsButton")
|
|
135
|
+
self.restoreDefaultsButton.setAccessibleName("Restore preference defaults")
|
|
136
|
+
self.restoreDefaultsButton.setToolTip(
|
|
137
|
+
"Restore the preferences shown here to their defaults"
|
|
138
|
+
)
|
|
139
|
+
self.restoreDefaultsButton.clicked.connect(self.restore_defaults)
|
|
140
|
+
layout.addWidget(self.buttonBox)
|
|
141
|
+
|
|
142
|
+
def _appearance_tab(self):
|
|
143
|
+
tab = qtw.QWidget(self)
|
|
144
|
+
form = qtw.QFormLayout(tab)
|
|
145
|
+
form.setFieldGrowthPolicy(qtw.QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
|
146
|
+
form.setContentsMargins(8, 8, 8, 8)
|
|
147
|
+
form.setSpacing(8)
|
|
148
|
+
|
|
149
|
+
self.themeCombo = qtw.QComboBox(tab)
|
|
150
|
+
self.themeCombo.setObjectName("themePreferenceCombo")
|
|
151
|
+
self.themeCombo.setAccessibleName("Theme")
|
|
152
|
+
for label, value in THEME_OPTIONS:
|
|
153
|
+
self.themeCombo.addItem(label, value)
|
|
154
|
+
self._add_row(form, "&Theme:", self.themeCombo)
|
|
155
|
+
|
|
156
|
+
self.colorbarWidthSpin = qtw.QSpinBox(tab)
|
|
157
|
+
self.colorbarWidthSpin.setObjectName("colorbarWidthPreferenceSpin")
|
|
158
|
+
self.colorbarWidthSpin.setAccessibleName("Color scale width")
|
|
159
|
+
self.colorbarWidthSpin.setRange(1, 500)
|
|
160
|
+
self.colorbarWidthSpin.setSuffix(" px")
|
|
161
|
+
self.colorbarWidthSpin.setToolTip(
|
|
162
|
+
"Width of heatmap colour scale bars"
|
|
163
|
+
)
|
|
164
|
+
self._add_row(form, "Color scale &width:", self.colorbarWidthSpin)
|
|
165
|
+
|
|
166
|
+
self.axisTickWidthSpin = qtw.QDoubleSpinBox(tab)
|
|
167
|
+
self.axisTickWidthSpin.setObjectName("axisTickWidthPreferenceSpin")
|
|
168
|
+
self.axisTickWidthSpin.setAccessibleName("Axis and tick width")
|
|
169
|
+
self.axisTickWidthSpin.setRange(0.5, 10.0)
|
|
170
|
+
self.axisTickWidthSpin.setSingleStep(0.5)
|
|
171
|
+
self.axisTickWidthSpin.setDecimals(1)
|
|
172
|
+
self.axisTickWidthSpin.setSuffix(" px")
|
|
173
|
+
self.axisTickWidthSpin.setToolTip(
|
|
174
|
+
"Line width for every plot axis and major and minor tick mark, "
|
|
175
|
+
"including colour scales"
|
|
176
|
+
)
|
|
177
|
+
self._add_row(form, "Axis and tick &width:", self.axisTickWidthSpin)
|
|
178
|
+
|
|
179
|
+
self.axisMajorTickCountSpin = qtw.QSpinBox(tab)
|
|
180
|
+
self.axisMajorTickCountSpin.setObjectName("axisMajorTickCountPreferenceSpin")
|
|
181
|
+
self.axisMajorTickCountSpin.setAccessibleName("Target labels per axis")
|
|
182
|
+
self.axisMajorTickCountSpin.setRange(2, 10)
|
|
183
|
+
self.axisMajorTickCountSpin.setToolTip(
|
|
184
|
+
"Target number of labelled major ticks on each axis, including "
|
|
185
|
+
"the heatmap colour scale"
|
|
186
|
+
)
|
|
187
|
+
self._add_row(form, "&Target labels per axis:", self.axisMajorTickCountSpin)
|
|
188
|
+
|
|
189
|
+
self.previewSizeSpin = qtw.QSpinBox(tab)
|
|
190
|
+
self.previewSizeSpin.setObjectName("previewSizePreferenceSpin")
|
|
191
|
+
self.previewSizeSpin.setAccessibleName("Preview size")
|
|
192
|
+
self.previewSizeSpin.setRange(50, 1000)
|
|
193
|
+
self.previewSizeSpin.setSingleStep(50)
|
|
194
|
+
self.previewSizeSpin.setSuffix(" px")
|
|
195
|
+
self._add_row(form, "&Preview size:", self.previewSizeSpin)
|
|
196
|
+
|
|
197
|
+
self.refreshRateSpin = qtw.QDoubleSpinBox(tab)
|
|
198
|
+
self.refreshRateSpin.setObjectName("refreshRatePreferenceSpin")
|
|
199
|
+
self.refreshRateSpin.setAccessibleName("Default refresh interval")
|
|
200
|
+
self.refreshRateSpin.setRange(0.0, 10_000.0)
|
|
201
|
+
self.refreshRateSpin.setSingleStep(0.1)
|
|
202
|
+
self.refreshRateSpin.setDecimals(1)
|
|
203
|
+
self.refreshRateSpin.setSuffix(" s")
|
|
204
|
+
self._add_row(form, "&Default refresh interval:", self.refreshRateSpin)
|
|
205
|
+
|
|
206
|
+
return tab
|
|
207
|
+
|
|
208
|
+
def _interaction_tab(self):
|
|
209
|
+
tab = qtw.QWidget(self)
|
|
210
|
+
form = qtw.QFormLayout(tab)
|
|
211
|
+
form.setFieldGrowthPolicy(qtw.QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
|
212
|
+
form.setContentsMargins(8, 8, 8, 8)
|
|
213
|
+
form.setSpacing(8)
|
|
214
|
+
|
|
215
|
+
self.mouseModeCombo = qtw.QComboBox(tab)
|
|
216
|
+
self.mouseModeCombo.setObjectName("mouseModePreferenceCombo")
|
|
217
|
+
self.mouseModeCombo.setAccessibleName("Mouse mode")
|
|
218
|
+
for label, value in MOUSE_MODE_OPTIONS:
|
|
219
|
+
self.mouseModeCombo.addItem(label, value)
|
|
220
|
+
self._add_row(form, "&Mouse mode:", self.mouseModeCombo)
|
|
221
|
+
|
|
222
|
+
self.copyPlotImageResolutionCombo = qtw.QComboBox(tab)
|
|
223
|
+
self.copyPlotImageResolutionCombo.setObjectName(
|
|
224
|
+
"copyPlotImageResolutionPreferenceCombo"
|
|
225
|
+
)
|
|
226
|
+
self.copyPlotImageResolutionCombo.setAccessibleName(
|
|
227
|
+
"Copy plot image resolution"
|
|
228
|
+
)
|
|
229
|
+
for label, value in COPY_PLOT_IMAGE_RESOLUTION_OPTIONS:
|
|
230
|
+
self.copyPlotImageResolutionCombo.addItem(label, value)
|
|
231
|
+
self._add_row(
|
|
232
|
+
form,
|
|
233
|
+
"&Copy plot image:",
|
|
234
|
+
self.copyPlotImageResolutionCombo,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return tab
|
|
238
|
+
|
|
239
|
+
def _files_tab(self):
|
|
240
|
+
tab = qtw.QWidget(self)
|
|
241
|
+
form = qtw.QFormLayout(tab)
|
|
242
|
+
form.setFieldGrowthPolicy(qtw.QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
|
243
|
+
form.setContentsMargins(8, 8, 8, 8)
|
|
244
|
+
form.setSpacing(8)
|
|
245
|
+
|
|
246
|
+
path_widget = qtw.QWidget(tab)
|
|
247
|
+
path_layout = qtw.QHBoxLayout(path_widget)
|
|
248
|
+
path_layout.setContentsMargins(0, 0, 0, 0)
|
|
249
|
+
path_layout.setSpacing(6)
|
|
250
|
+
|
|
251
|
+
self.defaultLoadPathEdit = qtw.QLineEdit(tab)
|
|
252
|
+
self.defaultLoadPathEdit.setObjectName("defaultLoadPathPreferenceEdit")
|
|
253
|
+
self.defaultLoadPathEdit.setAccessibleName("Default load location")
|
|
254
|
+
self.defaultLoadPathEdit.setPlaceholderText("No default folder")
|
|
255
|
+
path_layout.addWidget(self.defaultLoadPathEdit, 1)
|
|
256
|
+
|
|
257
|
+
self.defaultLoadPathButton = qtw.QToolButton(tab)
|
|
258
|
+
self.defaultLoadPathButton.setObjectName("defaultLoadPathPreferenceButton")
|
|
259
|
+
style = self.style()
|
|
260
|
+
if style is not None:
|
|
261
|
+
self.defaultLoadPathButton.setIcon(
|
|
262
|
+
style.standardIcon(qtw.QStyle.StandardPixmap.SP_DirOpenIcon)
|
|
263
|
+
)
|
|
264
|
+
self.defaultLoadPathButton.setToolTip("Choose default load location")
|
|
265
|
+
self.defaultLoadPathButton.setAccessibleName(
|
|
266
|
+
"Choose default load location"
|
|
267
|
+
)
|
|
268
|
+
self.defaultLoadPathButton.clicked.connect(self.choose_default_load_path)
|
|
269
|
+
path_layout.addWidget(self.defaultLoadPathButton)
|
|
270
|
+
|
|
271
|
+
self._add_row(
|
|
272
|
+
form,
|
|
273
|
+
"&Default load location:",
|
|
274
|
+
path_widget,
|
|
275
|
+
self.defaultLoadPathEdit,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return tab
|
|
279
|
+
|
|
280
|
+
def _confirmations_tab(self):
|
|
281
|
+
tab = qtw.QWidget(self)
|
|
282
|
+
layout = qtw.QVBoxLayout(tab)
|
|
283
|
+
layout.setContentsMargins(8, 8, 8, 8)
|
|
284
|
+
layout.setSpacing(8)
|
|
285
|
+
|
|
286
|
+
self.confirmCloseAllCheck = qtw.QCheckBox(
|
|
287
|
+
"Confirm before closing all plot windows",
|
|
288
|
+
tab,
|
|
289
|
+
)
|
|
290
|
+
self.confirmCloseAllCheck.setObjectName("confirmCloseAllPreferenceCheck")
|
|
291
|
+
self.confirmCloseAllCheck.setAccessibleName(
|
|
292
|
+
"Confirm before closing all plot windows"
|
|
293
|
+
)
|
|
294
|
+
layout.addWidget(self.confirmCloseAllCheck)
|
|
295
|
+
|
|
296
|
+
self.confirmQuitCheck = qtw.QCheckBox("Confirm before quit", tab)
|
|
297
|
+
self.confirmQuitCheck.setObjectName("confirmQuitPreferenceCheck")
|
|
298
|
+
self.confirmQuitCheck.setAccessibleName("Confirm before quit")
|
|
299
|
+
layout.addWidget(self.confirmQuitCheck)
|
|
300
|
+
|
|
301
|
+
layout.addStretch(1)
|
|
302
|
+
return tab
|
|
303
|
+
|
|
304
|
+
def _runtime_tab(self):
|
|
305
|
+
tab = qtw.QWidget(self)
|
|
306
|
+
form = qtw.QFormLayout(tab)
|
|
307
|
+
form.setFieldGrowthPolicy(qtw.QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
|
308
|
+
form.setContentsMargins(8, 8, 8, 8)
|
|
309
|
+
form.setSpacing(8)
|
|
310
|
+
|
|
311
|
+
self.maxThreadsSpin = qtw.QSpinBox(tab)
|
|
312
|
+
self.maxThreadsSpin.setObjectName("maxThreadsPreferenceSpin")
|
|
313
|
+
self.maxThreadsSpin.setAccessibleName("Maximum worker threads")
|
|
314
|
+
self.maxThreadsSpin.setRange(1, 999)
|
|
315
|
+
self._add_row(form, "&Maximum worker threads:", self.maxThreadsSpin)
|
|
316
|
+
|
|
317
|
+
self.maxFullHeatmapPointsSpin = qtw.QSpinBox(tab)
|
|
318
|
+
self.maxFullHeatmapPointsSpin.setObjectName(
|
|
319
|
+
"maxFullHeatmapPointsPreferenceSpin"
|
|
320
|
+
)
|
|
321
|
+
self.maxFullHeatmapPointsSpin.setAccessibleName(
|
|
322
|
+
"Maximum full-resolution heatmap points"
|
|
323
|
+
)
|
|
324
|
+
self.maxFullHeatmapPointsSpin.setRange(1, 2_000_000_000)
|
|
325
|
+
self.maxFullHeatmapPointsSpin.setSingleStep(100_000)
|
|
326
|
+
self.maxFullHeatmapPointsSpin.setSuffix(" points")
|
|
327
|
+
if hasattr(self.maxFullHeatmapPointsSpin, "setGroupSeparatorShown"):
|
|
328
|
+
self.maxFullHeatmapPointsSpin.setGroupSeparatorShown(True)
|
|
329
|
+
self.maxFullHeatmapPointsSpin.setToolTip(
|
|
330
|
+
"Use full-resolution heatmap loading up to this many points; "
|
|
331
|
+
"larger heatmaps use SQL spatial aggregation."
|
|
332
|
+
)
|
|
333
|
+
self._add_row(
|
|
334
|
+
form,
|
|
335
|
+
"Full-resolution &heatmap limit:",
|
|
336
|
+
self.maxFullHeatmapPointsSpin,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
self.maxHeatmapGridCellsSpin = qtw.QSpinBox(tab)
|
|
340
|
+
self.maxHeatmapGridCellsSpin.setObjectName(
|
|
341
|
+
"maxHeatmapGridCellsPreferenceSpin"
|
|
342
|
+
)
|
|
343
|
+
self.maxHeatmapGridCellsSpin.setAccessibleName(
|
|
344
|
+
"Maximum heatmap display grid cells"
|
|
345
|
+
)
|
|
346
|
+
self.maxHeatmapGridCellsSpin.setRange(1, 2_000_000_000)
|
|
347
|
+
self.maxHeatmapGridCellsSpin.setSingleStep(10_000)
|
|
348
|
+
self.maxHeatmapGridCellsSpin.setSuffix(" cells")
|
|
349
|
+
if hasattr(self.maxHeatmapGridCellsSpin, "setGroupSeparatorShown"):
|
|
350
|
+
self.maxHeatmapGridCellsSpin.setGroupSeparatorShown(True)
|
|
351
|
+
self.maxHeatmapGridCellsSpin.setToolTip(
|
|
352
|
+
"Maximum cells in a downsampled heatmap display grid."
|
|
353
|
+
)
|
|
354
|
+
self._add_row(
|
|
355
|
+
form,
|
|
356
|
+
"Heatmap display grid &cells:",
|
|
357
|
+
self.maxHeatmapGridCellsSpin,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
self.maxHeatmapGridSideSpin = qtw.QSpinBox(tab)
|
|
361
|
+
self.maxHeatmapGridSideSpin.setObjectName(
|
|
362
|
+
"maxHeatmapGridSidePreferenceSpin"
|
|
363
|
+
)
|
|
364
|
+
self.maxHeatmapGridSideSpin.setAccessibleName(
|
|
365
|
+
"Maximum heatmap display grid bins per axis"
|
|
366
|
+
)
|
|
367
|
+
self.maxHeatmapGridSideSpin.setRange(1, 2_000_000_000)
|
|
368
|
+
self.maxHeatmapGridSideSpin.setSingleStep(100)
|
|
369
|
+
self.maxHeatmapGridSideSpin.setSuffix(" bins")
|
|
370
|
+
if hasattr(self.maxHeatmapGridSideSpin, "setGroupSeparatorShown"):
|
|
371
|
+
self.maxHeatmapGridSideSpin.setGroupSeparatorShown(True)
|
|
372
|
+
self.maxHeatmapGridSideSpin.setToolTip(
|
|
373
|
+
"Maximum bins on either axis of a downsampled heatmap display grid."
|
|
374
|
+
)
|
|
375
|
+
self._add_row(
|
|
376
|
+
form,
|
|
377
|
+
"Heatmap display grid &side:",
|
|
378
|
+
self.maxHeatmapGridSideSpin,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
self.delGracePeriodSpin = qtw.QDoubleSpinBox(tab)
|
|
382
|
+
self.delGracePeriodSpin.setObjectName("deleteGracePreferenceSpin")
|
|
383
|
+
self.delGracePeriodSpin.setAccessibleName("Dataset release grace period")
|
|
384
|
+
self.delGracePeriodSpin.setRange(0.0, 300.0)
|
|
385
|
+
self.delGracePeriodSpin.setSingleStep(1.0)
|
|
386
|
+
self.delGracePeriodSpin.setDecimals(1)
|
|
387
|
+
self.delGracePeriodSpin.setSuffix(" s")
|
|
388
|
+
self._add_row(
|
|
389
|
+
form,
|
|
390
|
+
"Dataset release &grace period:",
|
|
391
|
+
self.delGracePeriodSpin,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
self.cloudSyncTimeoutSpin = qtw.QDoubleSpinBox(tab)
|
|
395
|
+
self.cloudSyncTimeoutSpin.setObjectName("cloudSyncPreferenceSpin")
|
|
396
|
+
self.cloudSyncTimeoutSpin.setAccessibleName("Cloud sync timeout")
|
|
397
|
+
self.cloudSyncTimeoutSpin.setRange(1.0, 3600.0)
|
|
398
|
+
self.cloudSyncTimeoutSpin.setSingleStep(10.0)
|
|
399
|
+
self.cloudSyncTimeoutSpin.setDecimals(1)
|
|
400
|
+
self.cloudSyncTimeoutSpin.setSuffix(" s")
|
|
401
|
+
self._add_row(form, "&Cloud sync timeout:", self.cloudSyncTimeoutSpin)
|
|
402
|
+
|
|
403
|
+
return tab
|
|
404
|
+
|
|
405
|
+
def _add_row(self, form, label_text, widget, buddy=None):
|
|
406
|
+
label = qtw.QLabel(label_text, self)
|
|
407
|
+
label.setBuddy(buddy or widget)
|
|
408
|
+
form.addRow(label, widget)
|
|
409
|
+
|
|
410
|
+
def load_from_config(self):
|
|
411
|
+
"""
|
|
412
|
+
Loads current config values into the dialog widgets.
|
|
413
|
+
|
|
414
|
+
"""
|
|
415
|
+
self.set_preference_values({
|
|
416
|
+
key: self.config.get(key)
|
|
417
|
+
for key in PREFERENCE_KEYS
|
|
418
|
+
})
|
|
419
|
+
self._loaded_control_values = self.preference_values()
|
|
420
|
+
self._edited_preference_keys.clear()
|
|
421
|
+
|
|
422
|
+
def _preference_controls(self):
|
|
423
|
+
"""Return the controls in the same order as their preference keys."""
|
|
424
|
+
return (
|
|
425
|
+
self.themeCombo,
|
|
426
|
+
self.colorbarWidthSpin,
|
|
427
|
+
self.axisTickWidthSpin,
|
|
428
|
+
self.axisMajorTickCountSpin,
|
|
429
|
+
self.previewSizeSpin,
|
|
430
|
+
self.mouseModeCombo,
|
|
431
|
+
self.copyPlotImageResolutionCombo,
|
|
432
|
+
self.defaultLoadPathEdit,
|
|
433
|
+
self.refreshRateSpin,
|
|
434
|
+
self.confirmCloseAllCheck,
|
|
435
|
+
self.confirmQuitCheck,
|
|
436
|
+
self.maxThreadsSpin,
|
|
437
|
+
self.maxFullHeatmapPointsSpin,
|
|
438
|
+
self.maxHeatmapGridCellsSpin,
|
|
439
|
+
self.maxHeatmapGridSideSpin,
|
|
440
|
+
self.delGracePeriodSpin,
|
|
441
|
+
self.cloudSyncTimeoutSpin,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def set_preference_values(self, values):
|
|
445
|
+
"""
|
|
446
|
+
Loads preference values into the dialog widgets.
|
|
447
|
+
|
|
448
|
+
"""
|
|
449
|
+
widgets = self._preference_controls()
|
|
450
|
+
blocked_states = [widget.blockSignals(True) for widget in widgets]
|
|
451
|
+
try:
|
|
452
|
+
theme_index = self.themeCombo.findData(values["user_preference.theme"])
|
|
453
|
+
self.themeCombo.setCurrentIndex(max(theme_index, 0))
|
|
454
|
+
|
|
455
|
+
self.colorbarWidthSpin.setValue(int(values[COLORBAR_WIDTH_KEY]))
|
|
456
|
+
self.axisTickWidthSpin.setValue(float(values[AXIS_TICK_WIDTH_KEY]))
|
|
457
|
+
self.axisMajorTickCountSpin.setValue(
|
|
458
|
+
int(values[AXIS_MAJOR_TICK_COUNT_KEY])
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
self.previewSizeSpin.setValue(int(values["GUI.preview_size"]))
|
|
462
|
+
mouse_mode_index = self.mouseModeCombo.findData(values[MOUSE_MODE_KEY])
|
|
463
|
+
self.mouseModeCombo.setCurrentIndex(max(mouse_mode_index, 0))
|
|
464
|
+
copy_resolution_index = self.copyPlotImageResolutionCombo.findData(
|
|
465
|
+
values[COPY_PLOT_IMAGE_RESOLUTION_KEY]
|
|
466
|
+
)
|
|
467
|
+
self.copyPlotImageResolutionCombo.setCurrentIndex(
|
|
468
|
+
max(copy_resolution_index, 0)
|
|
469
|
+
)
|
|
470
|
+
self.defaultLoadPathEdit.setText(str(values["file.default_load_path"]))
|
|
471
|
+
self.refreshRateSpin.setValue(
|
|
472
|
+
float(values["user_preference.default_refresh_rate"])
|
|
473
|
+
)
|
|
474
|
+
self.confirmCloseAllCheck.setChecked(
|
|
475
|
+
bool(values[CONFIRM_CLOSE_ALL_KEY])
|
|
476
|
+
)
|
|
477
|
+
self.confirmQuitCheck.setChecked(
|
|
478
|
+
bool(values[CONFIRM_QUIT_KEY])
|
|
479
|
+
)
|
|
480
|
+
self.maxThreadsSpin.setValue(
|
|
481
|
+
int(values["runtime_settings.max_threads"])
|
|
482
|
+
)
|
|
483
|
+
self.maxFullHeatmapPointsSpin.setValue(
|
|
484
|
+
int(values["runtime_settings.max_full_heatmap_points"])
|
|
485
|
+
)
|
|
486
|
+
self.maxHeatmapGridCellsSpin.setValue(
|
|
487
|
+
int(values["runtime_settings.max_heatmap_grid_cells"])
|
|
488
|
+
)
|
|
489
|
+
self.maxHeatmapGridSideSpin.setValue(
|
|
490
|
+
int(values["runtime_settings.max_heatmap_grid_side"])
|
|
491
|
+
)
|
|
492
|
+
self.delGracePeriodSpin.setValue(
|
|
493
|
+
float(values["runtime_settings.del_grace_period"])
|
|
494
|
+
)
|
|
495
|
+
self.cloudSyncTimeoutSpin.setValue(
|
|
496
|
+
float(values["runtime_settings.cloud_sync_timeout"])
|
|
497
|
+
)
|
|
498
|
+
finally:
|
|
499
|
+
for widget, signals_were_blocked in zip(
|
|
500
|
+
widgets,
|
|
501
|
+
blocked_states,
|
|
502
|
+
strict=True,
|
|
503
|
+
):
|
|
504
|
+
widget.blockSignals(signals_were_blocked)
|
|
505
|
+
|
|
506
|
+
def preference_values(self):
|
|
507
|
+
"""
|
|
508
|
+
Returns the current widget values keyed by config path.
|
|
509
|
+
|
|
510
|
+
"""
|
|
511
|
+
values = {
|
|
512
|
+
"user_preference.theme": self.themeCombo.currentData(),
|
|
513
|
+
COLORBAR_WIDTH_KEY: int(self.colorbarWidthSpin.value()),
|
|
514
|
+
AXIS_TICK_WIDTH_KEY: float(self.axisTickWidthSpin.value()),
|
|
515
|
+
AXIS_MAJOR_TICK_COUNT_KEY: int(self.axisMajorTickCountSpin.value()),
|
|
516
|
+
"GUI.preview_size": int(self.previewSizeSpin.value()),
|
|
517
|
+
MOUSE_MODE_KEY: self.mouseModeCombo.currentData(),
|
|
518
|
+
COPY_PLOT_IMAGE_RESOLUTION_KEY: (
|
|
519
|
+
self.copyPlotImageResolutionCombo.currentData()
|
|
520
|
+
),
|
|
521
|
+
"file.default_load_path": self.defaultLoadPathEdit.text().strip(),
|
|
522
|
+
"user_preference.default_refresh_rate": self.refreshRateSpin.value(),
|
|
523
|
+
CONFIRM_CLOSE_ALL_KEY: self.confirmCloseAllCheck.isChecked(),
|
|
524
|
+
CONFIRM_QUIT_KEY: self.confirmQuitCheck.isChecked(),
|
|
525
|
+
"runtime_settings.max_threads": int(self.maxThreadsSpin.value()),
|
|
526
|
+
"runtime_settings.max_full_heatmap_points": int(
|
|
527
|
+
self.maxFullHeatmapPointsSpin.value()
|
|
528
|
+
),
|
|
529
|
+
"runtime_settings.max_heatmap_grid_cells": int(
|
|
530
|
+
self.maxHeatmapGridCellsSpin.value()
|
|
531
|
+
),
|
|
532
|
+
"runtime_settings.max_heatmap_grid_side": int(
|
|
533
|
+
self.maxHeatmapGridSideSpin.value()
|
|
534
|
+
),
|
|
535
|
+
"runtime_settings.del_grace_period": self.delGracePeriodSpin.value(),
|
|
536
|
+
"runtime_settings.cloud_sync_timeout": self.cloudSyncTimeoutSpin.value(),
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
key: values[key]
|
|
540
|
+
for key in PREFERENCE_KEYS
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
def default_preference_values(self):
|
|
544
|
+
"""
|
|
545
|
+
Returns schema defaults for the preferences shown in this dialog.
|
|
546
|
+
|
|
547
|
+
"""
|
|
548
|
+
return {
|
|
549
|
+
key: self._schema_default(key)
|
|
550
|
+
for key in PREFERENCE_KEYS
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
def _schema_default(self, key):
|
|
554
|
+
section, name = key.split(".")
|
|
555
|
+
schema = self.config.schema["properties"][section]["properties"][name]
|
|
556
|
+
return deepcopy(schema["default"])
|
|
557
|
+
|
|
558
|
+
def apply_preferences(self):
|
|
559
|
+
"""
|
|
560
|
+
Persists the dialog values and emits preferencesApplied on success.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
control_values = self.preference_values()
|
|
564
|
+
# Display precision and text normalization may differ from valid saved
|
|
565
|
+
# values. Only an actual edit may replace those exact saved values.
|
|
566
|
+
changed_values = {
|
|
567
|
+
key: value
|
|
568
|
+
for key, value in control_values.items()
|
|
569
|
+
if self.config.get(key) != value and (
|
|
570
|
+
key in self._edited_preference_keys
|
|
571
|
+
or value != self._loaded_control_values[key]
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
if changed_values and not persist_config_values(
|
|
575
|
+
self,
|
|
576
|
+
self.config,
|
|
577
|
+
changed_values,
|
|
578
|
+
"the preferences",
|
|
579
|
+
self.load_from_config,
|
|
580
|
+
):
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
self._loaded_control_values = control_values
|
|
584
|
+
self._edited_preference_keys.clear()
|
|
585
|
+
self.preferencesApplied.emit()
|
|
586
|
+
return True
|
|
587
|
+
|
|
588
|
+
def restore_defaults(self):
|
|
589
|
+
"""
|
|
590
|
+
Restores the preferences shown in this dialog to schema defaults.
|
|
591
|
+
|
|
592
|
+
"""
|
|
593
|
+
reply = qtw.QMessageBox.question(
|
|
594
|
+
self,
|
|
595
|
+
"Restore Preference Defaults",
|
|
596
|
+
"Restore the preferences shown in this dialog to their defaults?",
|
|
597
|
+
qtw.QMessageBox.StandardButton.Yes | qtw.QMessageBox.StandardButton.No,
|
|
598
|
+
qtw.QMessageBox.StandardButton.No,
|
|
599
|
+
)
|
|
600
|
+
if reply != qtw.QMessageBox.StandardButton.Yes:
|
|
601
|
+
return False
|
|
602
|
+
|
|
603
|
+
self.set_preference_values(self.default_preference_values())
|
|
604
|
+
# Reset is explicit consent to defaults, even when the old value had
|
|
605
|
+
# the same rounded representation in its control.
|
|
606
|
+
self._edited_preference_keys.update(PREFERENCE_KEYS)
|
|
607
|
+
return True
|
|
608
|
+
|
|
609
|
+
def choose_default_load_path(self):
|
|
610
|
+
current_path = self.defaultLoadPathEdit.text().strip()
|
|
611
|
+
if not os.path.isdir(current_path):
|
|
612
|
+
current_path = ""
|
|
613
|
+
|
|
614
|
+
folder = qtw.QFileDialog.getExistingDirectory(
|
|
615
|
+
self,
|
|
616
|
+
"Default Load Location",
|
|
617
|
+
current_path,
|
|
618
|
+
)
|
|
619
|
+
if folder:
|
|
620
|
+
self.defaultLoadPathEdit.setText(folder)
|
|
621
|
+
|
|
622
|
+
def _accept_preferences(self):
|
|
623
|
+
if self.apply_preferences():
|
|
624
|
+
self.accept()
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def create_preferences_action(window, triggered):
|
|
628
|
+
"""
|
|
629
|
+
Creates the shared Preferences action used by main and plot windows.
|
|
630
|
+
|
|
631
|
+
"""
|
|
632
|
+
action = create_action("preferences.open", window)
|
|
633
|
+
action.setMenuRole(QtGui.QAction.MenuRole.PreferencesRole)
|
|
634
|
+
action.triggered.connect(triggered)
|
|
635
|
+
return action
|