qplotter 1.6.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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +67 -0
- qplot/_build_info.json +1 -0
- qplot/_metadata.py +75 -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 +578 -0
- qplot/configuration/config_schema.json +357 -0
- qplot/configuration/fonts.py +3 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +592 -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 +2340 -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 +2566 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +148 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +876 -0
- qplot/datahandling/trusted_heatmap.py +246 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1919 -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 +1804 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +123 -0
- qplot/tools/general.py +139 -0
- qplot/tools/heatmap_geometry.py +391 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +650 -0
- qplot/tools/sample_statistics.py +111 -0
- qplot/tools/worker.py +2838 -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 +4933 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_display_precision.py +48 -0
- qplot/windows/_dragdrop.py +70 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +210 -0
- qplot/windows/_mixed_appearance.py +303 -0
- qplot/windows/_mixed_plots.py +461 -0
- qplot/windows/_native_averaging.py +323 -0
- qplot/windows/_native_transforms.py +551 -0
- qplot/windows/_plot1d_snap.py +612 -0
- qplot/windows/_plot1d_traces.py +2236 -0
- qplot/windows/_plot2d_colorbar.py +1127 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2291 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +2082 -0
- qplot/windows/_plot_actions.py +2703 -0
- qplot/windows/_plot_annotation.py +186 -0
- qplot/windows/_plot_appearance.py +283 -0
- qplot/windows/_plot_axis_scaling.py +1521 -0
- qplot/windows/_plot_data_viewbox.py +94 -0
- qplot/windows/_plot_drop_axes.py +224 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +112 -0
- qplot/windows/_plot_marquee.py +659 -0
- qplot/windows/_plot_refresh.py +950 -0
- qplot/windows/_plot_source_promotion.py +178 -0
- qplot/windows/_plot_state.py +237 -0
- qplot/windows/_plot_status.py +115 -0
- qplot/windows/_plot_tiling.py +35 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +724 -0
- qplot/windows/_refresh_interval.py +172 -0
- qplot/windows/_run_controls.py +789 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +532 -0
- qplot/windows/_subplots/subplot2d.py +919 -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 +414 -0
- qplot/windows/main.py +1632 -0
- qplot/windows/plot1d.py +313 -0
- qplot/windows/plot2d.py +1935 -0
- qplotter-1.6.0.dist-info/METADATA +322 -0
- qplotter-1.6.0.dist-info/RECORD +130 -0
- qplotter-1.6.0.dist-info/WHEEL +5 -0
- qplotter-1.6.0.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
from collections.abc import Callable, Sequence
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Any, Literal, Protocol, TypeAlias, cast, overload
|
|
4
|
+
|
|
5
|
+
from PyQt6 import (
|
|
6
|
+
QtCore,
|
|
7
|
+
QtGui,
|
|
8
|
+
)
|
|
9
|
+
from PyQt6 import (
|
|
10
|
+
QtWidgets as qtw,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from qplot.tools.operation_registry import (
|
|
14
|
+
OperationCall,
|
|
15
|
+
OperationValidationError,
|
|
16
|
+
operation_specs_for,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .dropbox import expandingComboBox
|
|
20
|
+
|
|
21
|
+
OperationKind: TypeAlias = Literal["plot1d", "plot2d", "sweeper"]
|
|
22
|
+
OperationFunc: TypeAlias = Callable[..., Any]
|
|
23
|
+
OperationInputType: TypeAlias = type | Sequence[str] | None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OperationsDock(Protocol):
|
|
27
|
+
event_filter: QtCore.QObject
|
|
28
|
+
|
|
29
|
+
def VBox_context(self, *args: Any) -> qtw.QVBoxLayout: ...
|
|
30
|
+
|
|
31
|
+
def HBox_context(self, *args: Any) -> qtw.QHBoxLayout: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OperationsWindow(Protocol):
|
|
35
|
+
oper_dock: OperationsDock
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def operations_widget(window: Any) -> "operations_options_base":
|
|
39
|
+
"""
|
|
40
|
+
Entry point for getting the operation options.
|
|
41
|
+
Uses the plot kind declared by the window class to select the correct
|
|
42
|
+
options widget. Subclasses inherit this declaration, so instrumented or
|
|
43
|
+
otherwise specialised plot windows get the same operations as their base
|
|
44
|
+
plot type. Window is also passed to the class.
|
|
45
|
+
|
|
46
|
+
PLEASE SEE BOTTOM OF FILE FOR WHICH OPTIONS ARE ADDED.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
window : qplot.windows._plotWin.plotWidget
|
|
51
|
+
The type of window: 1d, 2d, sweeper. Also serves as a reference inside
|
|
52
|
+
the returned class
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
out : operations_options_base
|
|
57
|
+
Returns the a QWidget containing QListWidgets, with options based on
|
|
58
|
+
the inputted window type.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
options_dict: dict[OperationKind, type[operations_options_base]] = {
|
|
62
|
+
"plot1d" : operations_options_1d,
|
|
63
|
+
"plot2d" : operations_options_2d,
|
|
64
|
+
"sweeper": operations_options_sweep
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
operation_kind = getattr(window, "operation_kind", None)
|
|
68
|
+
if operation_kind not in options_dict:
|
|
69
|
+
raise TypeError(
|
|
70
|
+
"Plot windows must declare operation_kind as "
|
|
71
|
+
"'plot1d', 'plot2d', or 'sweeper'"
|
|
72
|
+
)
|
|
73
|
+
out = options_dict[cast(OperationKind, operation_kind)](window)
|
|
74
|
+
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class operations_options_base(qtw.QWidget):
|
|
80
|
+
"""
|
|
81
|
+
Base class for all operation widgets, handles set up and all needed functions
|
|
82
|
+
Classes inhertit this to set the options.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
operation_kind: OperationKind
|
|
87
|
+
|
|
88
|
+
def __init__(self, main: OperationsWindow):
|
|
89
|
+
super().__init__()
|
|
90
|
+
self._window = main
|
|
91
|
+
|
|
92
|
+
# Make filter to propagate context menu to widgets.
|
|
93
|
+
self.filter = self._window.oper_dock.event_filter
|
|
94
|
+
self.main_layout = self._window.oper_dock.VBox_context(self.filter, self)
|
|
95
|
+
|
|
96
|
+
self.main_layout.addWidget(qtw.QLabel("Data Operations"))
|
|
97
|
+
|
|
98
|
+
# Controls order to perform and user inputs
|
|
99
|
+
self.list_order = draggableListWidget()
|
|
100
|
+
self.list_order.setDragDropMode(qtw.QAbstractItemView.DragDropMode.InternalMove)
|
|
101
|
+
self.list_order.setToolTip("Drag Items to Control Operation Order")
|
|
102
|
+
self.main_layout.addWidget(self.list_order)
|
|
103
|
+
|
|
104
|
+
# Buttons
|
|
105
|
+
but_l = self._window.oper_dock.HBox_context(self.filter)
|
|
106
|
+
self.apply_but = qtw.QPushButton("Apply/Refresh")
|
|
107
|
+
self.apply_but.setToolTip("Apply selected operations and refresh the plot")
|
|
108
|
+
self._apply_generation = 0
|
|
109
|
+
self.apply_but.clicked.connect(self._record_apply_request)
|
|
110
|
+
but_l.addWidget(self.apply_but)
|
|
111
|
+
clear_but = qtw.QPushButton("Clear")
|
|
112
|
+
clear_but.setToolTip("Clear all selected operations")
|
|
113
|
+
clear_but.clicked.connect(self.hide_all)
|
|
114
|
+
but_l.addWidget(clear_but)
|
|
115
|
+
|
|
116
|
+
self.main_layout.addLayout(but_l)
|
|
117
|
+
|
|
118
|
+
# Allows user to toggle options
|
|
119
|
+
self.list_options = qtw.QListWidget()
|
|
120
|
+
# Some platform styles draw the vertical scrollbar as an overlay. Keep
|
|
121
|
+
# the option-row controls clear of that area, rather than allowing the
|
|
122
|
+
# checkboxes to extend underneath it.
|
|
123
|
+
scrollbar_extent = max(
|
|
124
|
+
self.list_options.verticalScrollBar().sizeHint().width(),
|
|
125
|
+
self.style().pixelMetric(
|
|
126
|
+
qtw.QStyle.PixelMetric.PM_ScrollBarExtent,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
self.list_options.setViewportMargins(0, 0, scrollbar_extent, 0)
|
|
130
|
+
self.main_layout.addWidget(self.list_options)
|
|
131
|
+
|
|
132
|
+
self.add_all_options()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _record_apply_request(self) -> None:
|
|
136
|
+
"""Identify refreshes explicitly requested from the operations panel."""
|
|
137
|
+
|
|
138
|
+
self._apply_generation += 1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def add_option(
|
|
142
|
+
self,
|
|
143
|
+
name: str,
|
|
144
|
+
func: OperationFunc,
|
|
145
|
+
input_type: OperationInputType,
|
|
146
|
+
default: object = "",
|
|
147
|
+
derivative_axis: str | None = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Adds an option to the list_options with a tickbox.
|
|
151
|
+
Creates a row stored within the option, this holds the function and
|
|
152
|
+
has a widget for the required input type. This data is read by
|
|
153
|
+
self.get_data()
|
|
154
|
+
The secondary row is added or removed when the tickbox is clicked.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
name : str
|
|
159
|
+
Display name of the function.
|
|
160
|
+
func : callable
|
|
161
|
+
Function to be passed to do_operations in worker.
|
|
162
|
+
input_type : type | list | tuple
|
|
163
|
+
The required input type:
|
|
164
|
+
bool - Makes checkbox
|
|
165
|
+
str, int, float, Decimal - Makes a line edit with numeric validation
|
|
166
|
+
None - No input box made
|
|
167
|
+
or a list/tuple of the options - Makes a dropbox with options
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
row = optionToggleRowItem(name, None, bool) # Option with tick box
|
|
171
|
+
self.list_options.addItem(row)
|
|
172
|
+
self.list_options.setItemWidget(row, row.row_widget)
|
|
173
|
+
|
|
174
|
+
# create item to add to active box. This data is fetched by self.get_data()
|
|
175
|
+
row.operation_row = rowItem(name, func, input_type, default=default)
|
|
176
|
+
row.operation_row.derivative_axis = derivative_axis
|
|
177
|
+
row.input.stateChanged.connect(lambda state:
|
|
178
|
+
self.add_or_remove_operation(state, row.operation_row)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def add_or_remove_operation(self, add : int, item : "rowItem") -> None:
|
|
183
|
+
"""
|
|
184
|
+
Based on add value, adds or removes the item from the active operations
|
|
185
|
+
and clears previous input on removal.
|
|
186
|
+
|
|
187
|
+
Actually sets hidden or visible based to look as if removed.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
add : int
|
|
192
|
+
Whether to add or remove the box from the action operations
|
|
193
|
+
For some ungodly reason, QTickBox.stateChanged emits 0 or 2 instead
|
|
194
|
+
of true or false. But if 0 -> False; if 2 -> True, so its fine.
|
|
195
|
+
item : rowItem
|
|
196
|
+
Which item is being worked on.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
row = self.list_order.row(item)
|
|
200
|
+
# Add item is not already there
|
|
201
|
+
if row == -1:
|
|
202
|
+
self.list_order.addItem(item)
|
|
203
|
+
self.list_order.setItemWidget(item, item.row_widget)
|
|
204
|
+
|
|
205
|
+
# Mimic complete removal (without garbage collector doing dumb things)
|
|
206
|
+
if add:
|
|
207
|
+
item.setHidden(False)
|
|
208
|
+
else: # Remove previous input
|
|
209
|
+
item.setHidden(True)
|
|
210
|
+
item.reset()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def add_all_options(self) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Fetches data from dict, self.operation_options, defined in children
|
|
216
|
+
classes.
|
|
217
|
+
Then adds these to available options.
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
for spec in operation_specs_for(self.operation_kind):
|
|
221
|
+
self.add_option(
|
|
222
|
+
spec.name,
|
|
223
|
+
spec.func,
|
|
224
|
+
cast(OperationInputType, spec.input_type),
|
|
225
|
+
spec.default,
|
|
226
|
+
spec.derivative_axis,
|
|
227
|
+
)
|
|
228
|
+
self.list_options.adjustSize()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@QtCore.pyqtSlot()
|
|
232
|
+
def hide_all(self) -> None:
|
|
233
|
+
for i in range(self.list_options.count()):
|
|
234
|
+
item = cast(optionToggleRowItem, self.list_options.item(i))
|
|
235
|
+
item.input.setChecked(False)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_data(self) -> list[OperationCall]:
|
|
239
|
+
"""
|
|
240
|
+
Returns the function of the items in the active operation listWidget
|
|
241
|
+
(self.list_order) from top to bottom. Also adds the user input to the
|
|
242
|
+
functions ready for processing by qplot.tools.worker.loader.do_operations.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
operations : list[callable]
|
|
247
|
+
List of functions to be performed on the data during refresh.
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
operations: list[OperationCall] = []
|
|
251
|
+
for i in range(self.list_order.count()):
|
|
252
|
+
item = cast(rowItem, self.list_order.item(i))
|
|
253
|
+
if item.isHidden():
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
input_widget = item.input
|
|
257
|
+
if (
|
|
258
|
+
isinstance(input_widget, qtw.QLineEdit)
|
|
259
|
+
and input_widget.text()
|
|
260
|
+
and not input_widget.hasAcceptableInput()
|
|
261
|
+
):
|
|
262
|
+
raise OperationValidationError(
|
|
263
|
+
f'{item.label}: enter a valid value.'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
output = item.output()
|
|
268
|
+
except (TypeError, ValueError, OverflowError) as error:
|
|
269
|
+
raise OperationValidationError(
|
|
270
|
+
f'{item.label}: enter a valid value.'
|
|
271
|
+
) from error
|
|
272
|
+
|
|
273
|
+
if output == "": # Data not entered
|
|
274
|
+
if hasattr(input_widget, "placeholderText"):
|
|
275
|
+
output = cast(Any, input_widget).placeholderText()
|
|
276
|
+
if output == "": # still blank
|
|
277
|
+
raise OperationValidationError(
|
|
278
|
+
f'{item.label}: a value is required.'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if isinstance(item.input_type, type):
|
|
282
|
+
try:
|
|
283
|
+
output = item.input_type(output)
|
|
284
|
+
except (TypeError, ValueError, OverflowError) as error:
|
|
285
|
+
raise OperationValidationError(
|
|
286
|
+
f'{item.label}: enter a valid value.'
|
|
287
|
+
) from error
|
|
288
|
+
else:
|
|
289
|
+
raise OperationValidationError(
|
|
290
|
+
f'{item.label}: a value is required.'
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if output is None: # No input requried
|
|
294
|
+
func = item.func
|
|
295
|
+
else: # Add input
|
|
296
|
+
# Some weird internal python stuff causes issues with lambda in loops
|
|
297
|
+
func = func_with_input(item.func, output)
|
|
298
|
+
|
|
299
|
+
operations.append(OperationCall(
|
|
300
|
+
item.label,
|
|
301
|
+
func,
|
|
302
|
+
getattr(item, "derivative_axis", None),
|
|
303
|
+
cooperative=True,
|
|
304
|
+
))
|
|
305
|
+
return operations
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def func_with_input(func: OperationFunc, value: object) -> OperationFunc:
|
|
309
|
+
return lambda data, cancelled_callback=None: func(
|
|
310
|
+
value,
|
|
311
|
+
data,
|
|
312
|
+
cancelled_callback=cancelled_callback,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class draggableListWidget(qtw.QListWidget):
|
|
317
|
+
"""
|
|
318
|
+
QListWidgets have a know issue, when dragging the last time in the list
|
|
319
|
+
below itself, the item contents gets deleted. This class impliments a work
|
|
320
|
+
around to prevent that bug.
|
|
321
|
+
"""
|
|
322
|
+
def dragMoveEvent(self, event: QtGui.QDragMoveEvent | None) -> None:
|
|
323
|
+
if event is None:
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
target = self.row(self.itemAt(cast(Any, event).pos()))
|
|
327
|
+
current = self.currentRow()
|
|
328
|
+
# Block drop below itself when it's the last item
|
|
329
|
+
if target == current + 1 or (current == self.count() - 1 and target == -1):
|
|
330
|
+
event.ignore()
|
|
331
|
+
else:
|
|
332
|
+
super().dragMoveEvent(event)
|
|
333
|
+
|
|
334
|
+
@overload
|
|
335
|
+
def addItem(self, item: qtw.QListWidgetItem | None) -> None: ...
|
|
336
|
+
|
|
337
|
+
@overload
|
|
338
|
+
def addItem(self, item: str | None) -> None: ...
|
|
339
|
+
|
|
340
|
+
def addItem(self, item: qtw.QListWidgetItem | str | None) -> None:
|
|
341
|
+
super().addItem(item)
|
|
342
|
+
if isinstance(item, qtw.QListWidgetItem):
|
|
343
|
+
item.setToolTip(self.toolTip())
|
|
344
|
+
|
|
345
|
+
class rowItem(qtw.QListWidgetItem):
|
|
346
|
+
"""
|
|
347
|
+
A QListWidgetItem which can have different input boxes based on input_type.
|
|
348
|
+
|
|
349
|
+
Has 5 key attributes
|
|
350
|
+
label : str - Display name of item
|
|
351
|
+
func : callable - copy of function to be used in do_operations
|
|
352
|
+
input : QWidget - The input widget
|
|
353
|
+
reset : callable - resets input to default
|
|
354
|
+
output : callabel - returns current value of input
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
def __init__(
|
|
358
|
+
self,
|
|
359
|
+
label: str,
|
|
360
|
+
func: OperationFunc | None,
|
|
361
|
+
input_type: OperationInputType,
|
|
362
|
+
default: object = "",
|
|
363
|
+
):
|
|
364
|
+
super().__init__()
|
|
365
|
+
|
|
366
|
+
self.func: OperationFunc
|
|
367
|
+
self.derivative_axis: str | None = None
|
|
368
|
+
if callable(func):
|
|
369
|
+
self.func = func
|
|
370
|
+
elif func is not None:
|
|
371
|
+
raise AssertionError("Func is not callable")
|
|
372
|
+
|
|
373
|
+
self._label = qtw.QLabel(label)
|
|
374
|
+
self.input_type = input_type
|
|
375
|
+
self.input: qtw.QCheckBox | qtw.QLineEdit | expandingComboBox | None
|
|
376
|
+
self.reset: Callable[[], None]
|
|
377
|
+
self.output: Callable[[], object]
|
|
378
|
+
|
|
379
|
+
self.row_widget = qtw.QWidget()
|
|
380
|
+
row_layout = qtw.QHBoxLayout()
|
|
381
|
+
self.row_widget.setLayout(row_layout)
|
|
382
|
+
|
|
383
|
+
row_layout.addWidget(self._label)
|
|
384
|
+
row_layout.setContentsMargins(5, 5, 5, 5)
|
|
385
|
+
|
|
386
|
+
if input_type is bool: # on/off tickbox
|
|
387
|
+
checkbox = qtw.QCheckBox()
|
|
388
|
+
self.input = checkbox
|
|
389
|
+
checkbox.setToolTip(f"Enable or disable {label}")
|
|
390
|
+
self.reset = lambda: checkbox.setChecked(False)
|
|
391
|
+
self.output = lambda: bool(checkbox.isChecked())
|
|
392
|
+
|
|
393
|
+
elif input_type in [int, float, Decimal, str]: # Textbox input
|
|
394
|
+
line_edit = qtw.QLineEdit()
|
|
395
|
+
self.input = line_edit
|
|
396
|
+
self.reset = lambda: line_edit.setText("")
|
|
397
|
+
scalar_type = cast(type, input_type)
|
|
398
|
+
self.output = lambda: (scalar_type(line_edit.text())
|
|
399
|
+
if line_edit.text() else "")
|
|
400
|
+
# Restrict user input to reduce errors
|
|
401
|
+
if input_type is int:
|
|
402
|
+
line_edit.setValidator(QtGui.QIntValidator())
|
|
403
|
+
elif input_type in (float, Decimal):
|
|
404
|
+
validator = QtGui.QDoubleValidator()
|
|
405
|
+
validator.setNotation(QtGui.QDoubleValidator.Notation.ScientificNotation)
|
|
406
|
+
validator.setLocale(QtCore.QLocale("C")) # Avoids locale issues like commas
|
|
407
|
+
line_edit.setValidator(validator)
|
|
408
|
+
|
|
409
|
+
elif input_type is None: # No input needed
|
|
410
|
+
self.input = None
|
|
411
|
+
self.reset = lambda: None
|
|
412
|
+
self.output = lambda: None
|
|
413
|
+
|
|
414
|
+
elif isinstance(input_type, (list, tuple)): # Select from options
|
|
415
|
+
combo_box = expandingComboBox()
|
|
416
|
+
self.input = combo_box
|
|
417
|
+
combo_box.addItems(list(input_type))
|
|
418
|
+
self.reset = lambda: combo_box.setCurrentIndex(-1)
|
|
419
|
+
self.output = lambda: combo_box.currentText()
|
|
420
|
+
|
|
421
|
+
else:
|
|
422
|
+
raise TypeError(
|
|
423
|
+
f"Invalid input type: {input_type}, must be int, float, Decimal"
|
|
424
|
+
", str, bool, None, or an array of values.")
|
|
425
|
+
|
|
426
|
+
row_layout.addStretch() # push to edges
|
|
427
|
+
if self.input is not None:
|
|
428
|
+
row_layout.addWidget(self.input)
|
|
429
|
+
if isinstance(self.input, qtw.QCheckBox):
|
|
430
|
+
self.input.setChecked(True if default == True else False)
|
|
431
|
+
else:
|
|
432
|
+
self.input.setPlaceholderText(str(default))
|
|
433
|
+
|
|
434
|
+
# pyqt defaults height of widget to 0, what?
|
|
435
|
+
self.setSizeHint(self.row_widget.sizeHint())
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def label(self) -> str:
|
|
440
|
+
return self._label.text()
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class optionToggleRowItem(rowItem):
|
|
445
|
+
operation_row: rowItem
|
|
446
|
+
input: qtw.QCheckBox
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class operations_options_common(operations_options_base):
|
|
450
|
+
operation_kind: OperationKind
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class operations_options_1d(operations_options_common):
|
|
454
|
+
operation_kind = "plot1d"
|
|
455
|
+
|
|
456
|
+
class operations_options_2d(operations_options_common):
|
|
457
|
+
operation_kind = "plot2d"
|
|
458
|
+
|
|
459
|
+
class operations_options_sweep(operations_options_common):
|
|
460
|
+
operation_kind = "sweeper"
|