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.
Files changed (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,488 @@
1
+ from PyQt6 import QtCore, QtGui
2
+ from PyQt6 import QtWidgets as qtw
3
+
4
+ from ._run_formatting import one_dimensional_duplicate_point_count, run_tooltip_text
5
+ from .preview import (
6
+ DraggablePreviewImageLabel,
7
+ PreviewImageLabel,
8
+ preview_placeholder_dimensions,
9
+ unsupported_preview_label,
10
+ )
11
+
12
+ MEASUREMENT_PREVIEW_SIZE = 22
13
+ MEASUREMENT_PREVIEW_SPACING = 3
14
+ MEASUREMENT_PREVIEW_GENERATING_COLORS = (
15
+ "#e9f4ff",
16
+ "#edf7f1",
17
+ "#fff4e3",
18
+ "#f4efff",
19
+ "#eef7f8",
20
+ "#f8eff4",
21
+ )
22
+ MEASUREMENT_PREVIEW_GENERATING_BORDERS = (
23
+ "#bdd8f2",
24
+ "#c5dfcc",
25
+ "#ead3aa",
26
+ "#d4c8ee",
27
+ "#bfdadd",
28
+ "#e0c3d1",
29
+ )
30
+
31
+
32
+ class RunPreviewCell(qtw.QWidget):
33
+ plotRequested = QtCore.pyqtSignal(str, str)
34
+ exportRequested = QtCore.pyqtSignal(str, str)
35
+
36
+ def __init__(self, guid, count, parent=None, icon_size=MEASUREMENT_PREVIEW_SIZE):
37
+ super().__init__(parent)
38
+ self.guid = guid
39
+ self.placeholder_count = max(0, int(count or 0))
40
+ self.icon_size = int(icon_size)
41
+ self._generating = False
42
+ self._has_rendered_previews = False
43
+ self._placeholder_dimensions = []
44
+ self._placeholder_parameters = []
45
+ self._previews = []
46
+ self._pending_display = None
47
+
48
+ self.content_layout = qtw.QHBoxLayout()
49
+ self.content_layout.setContentsMargins(2, 0, 2, 0)
50
+ self.content_layout.setSpacing(MEASUREMENT_PREVIEW_SPACING)
51
+ self.content_layout.setAlignment(
52
+ QtCore.Qt.AlignmentFlag.AlignLeft
53
+ | QtCore.Qt.AlignmentFlag.AlignVCenter
54
+ )
55
+ self.setLayout(self.content_layout)
56
+ self.setFixedHeight(self.icon_size + 6)
57
+ self.show_placeholders()
58
+
59
+
60
+ def show_placeholders(self, count=None, generating=None):
61
+ if self._defer_display_during_drag(("placeholders", count, generating)):
62
+ return
63
+ selected_parameter = self._clear_layout()
64
+ self._has_rendered_previews = False
65
+ self._previews = []
66
+ if generating is not None:
67
+ self._generating = bool(generating)
68
+ placeholder_count = self.placeholder_count if count is None else max(0, int(count))
69
+ for index in range(placeholder_count):
70
+ self.content_layout.addWidget(
71
+ self._placeholder_label(index, generating=self._generating)
72
+ )
73
+ self.content_layout.addStretch()
74
+ self._restore_selection(selected_parameter)
75
+
76
+
77
+ def show_previews(self, previews):
78
+ previews = list(previews or [])
79
+ if self._defer_display_during_drag(("previews", previews)):
80
+ return
81
+ self._generating = False
82
+ selected_parameter = self._clear_layout()
83
+ self._previews = list(previews or [])
84
+
85
+ preview_count = 0
86
+ parameters = self._placeholder_parameters
87
+ if parameters:
88
+ previews_by_parameter: dict[str, list[dict]] = {}
89
+ for preview in self._previews:
90
+ if not self._preview_is_displayable(preview):
91
+ continue
92
+ parameter = preview.get("parameter", "")
93
+ previews_by_parameter.setdefault(parameter, []).append(preview)
94
+
95
+ slot_count = max(self.placeholder_count, len(parameters))
96
+ for index in range(slot_count):
97
+ preview = None
98
+ if index < len(parameters):
99
+ matches = previews_by_parameter.get(parameters[index]) or []
100
+ if matches:
101
+ preview = matches.pop(0)
102
+ if preview is None:
103
+ self.content_layout.addWidget(self._placeholder_label(index))
104
+ else:
105
+ self._add_preview_label(preview)
106
+ preview_count += 1
107
+ else:
108
+ for preview in self._previews:
109
+ if not self._preview_is_displayable(preview):
110
+ continue
111
+ self._add_preview_label(preview)
112
+ preview_count += 1
113
+ for index in range(preview_count, self.placeholder_count):
114
+ self.content_layout.addWidget(self._placeholder_label(index))
115
+ self.content_layout.addStretch()
116
+ self._has_rendered_previews = preview_count > 0
117
+ self._restore_selection(selected_parameter)
118
+
119
+
120
+ @staticmethod
121
+ def _preview_is_displayable(preview):
122
+ return bool(preview.get("unsupported") or preview.get("image") is not None)
123
+
124
+
125
+ def _add_preview_label(self, preview):
126
+ if preview.get("unsupported"):
127
+ label = unsupported_preview_label(
128
+ preview,
129
+ self.icon_size,
130
+ "measurementPreviewUnsupported",
131
+ )
132
+ self.content_layout.addWidget(label)
133
+ return
134
+
135
+ label = DraggablePreviewImageLabel(
136
+ self.guid,
137
+ preview.get("parameter", ""),
138
+ preview.get("axes") or [],
139
+ )
140
+ label.setObjectName("measurementPreviewImage")
141
+ label.setFixedSize(self.icon_size, self.icon_size)
142
+ label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
143
+ label.setToolTip(preview.get("title", ""))
144
+ label.set_preview_accessibility(preview.get("title"))
145
+ label.setPixmap(
146
+ QtGui.QPixmap.fromImage(preview["image"]).scaled(
147
+ self.icon_size,
148
+ self.icon_size,
149
+ QtCore.Qt.AspectRatioMode.KeepAspectRatio,
150
+ QtCore.Qt.TransformationMode.SmoothTransformation,
151
+ )
152
+ )
153
+ label.plotRequested.connect(self._emit_plot_requested)
154
+ label.exportRequested.connect(self._emit_export_requested)
155
+ label.dragFinished.connect(self._apply_pending_display)
156
+ self.content_layout.addWidget(label)
157
+
158
+
159
+ def set_generating(self, generating):
160
+ generating = bool(generating)
161
+ if generating:
162
+ if self._generating:
163
+ return
164
+ if self._has_rendered_previews:
165
+ self._generating = True
166
+ return
167
+ self.show_placeholders(generating=True)
168
+ return
169
+
170
+ self._generating = False
171
+ for label in self.findChildren(qtw.QLabel, "measurementPreviewPlaceholder"):
172
+ label.setStyleSheet("")
173
+ label.setToolTip("")
174
+
175
+
176
+ def update_placeholder_metadata(self, metadata):
177
+ dimensions = preview_placeholder_dimensions(metadata)
178
+ parameters = list(metadata.get("measure_parameters") or [])
179
+ if (dimensions != self._placeholder_dimensions
180
+ or parameters != self._placeholder_parameters):
181
+ self._placeholder_dimensions = dimensions
182
+ self._placeholder_parameters = parameters
183
+ if self._has_rendered_previews:
184
+ self.show_previews(self._previews)
185
+ else:
186
+ self.show_placeholders()
187
+
188
+
189
+ def _placeholder_label(self, index=0, generating=False):
190
+ parameters = self._placeholder_parameters
191
+ parameter = parameters[index] if index < len(parameters) else ""
192
+ label = DraggablePreviewImageLabel(self.guid, parameter, axes_pending=True)
193
+ label.dragFinished.connect(self._apply_pending_display)
194
+ label.plotRequested.connect(self._emit_plot_requested)
195
+ label.exportRequested.connect(self._emit_export_requested)
196
+ dimensions = self._placeholder_dimensions
197
+ dimension = dimensions[index] if index < len(dimensions) else None
198
+ label.setText(f"{dimension}D" if dimension else "")
199
+ label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
200
+ font = label.font()
201
+ font.setPointSize(8)
202
+ label.setFont(font)
203
+ label.setObjectName("measurementPreviewPlaceholder")
204
+ label.setFixedSize(self.icon_size, self.icon_size)
205
+ label.setFrameShape(qtw.QFrame.Shape.Box)
206
+ label.setFrameShadow(qtw.QFrame.Shadow.Plain)
207
+ label.setLineWidth(1)
208
+ if generating:
209
+ color, border = self._generating_placeholder_colors(index)
210
+ label.setStyleSheet(
211
+ "QLabel#measurementPreviewPlaceholder {"
212
+ f"background-color: {color};"
213
+ f"border: 1px solid {border};"
214
+ "}"
215
+ )
216
+ label.setToolTip("Generating preview")
217
+ return label
218
+
219
+
220
+ def _defer_display_during_drag(self, display):
221
+ # QDrag runs a nested event loop. Preview publication in that loop
222
+ # must not replace/delete its source until the user has dropped it.
223
+ if any(label.drag_active for label in self.findChildren(DraggablePreviewImageLabel)):
224
+ self._pending_display = display
225
+ return True
226
+ return False
227
+
228
+
229
+ def _apply_pending_display(self):
230
+ display = self._pending_display
231
+ self._pending_display = None
232
+ if display is None:
233
+ return
234
+ if display[0] == "previews":
235
+ self.show_previews(display[1])
236
+ else:
237
+ self.show_placeholders(count=display[1], generating=display[2])
238
+
239
+
240
+ def _generating_placeholder_colors(self, index):
241
+ seed = sum(ord(char) for char in str(self.guid or ""))
242
+ palette_index = (seed + int(index or 0)) % len(MEASUREMENT_PREVIEW_GENERATING_COLORS)
243
+ return (
244
+ MEASUREMENT_PREVIEW_GENERATING_COLORS[palette_index],
245
+ MEASUREMENT_PREVIEW_GENERATING_BORDERS[palette_index],
246
+ )
247
+
248
+
249
+ def _emit_plot_requested(self, parameter):
250
+ self.plotRequested.emit(self.guid, parameter)
251
+
252
+
253
+ def _emit_export_requested(self, parameter):
254
+ self.exportRequested.emit(self.guid, parameter)
255
+
256
+
257
+ def _clear_layout(self):
258
+ selected_parameter = next(
259
+ (label.parameter for label in self.findChildren(PreviewImageLabel)
260
+ if label._selected),
261
+ None,
262
+ )
263
+ while self.content_layout.count():
264
+ item = self.content_layout.takeAt(0)
265
+ if item is None:
266
+ continue
267
+ widget = item.widget()
268
+ if widget is not None:
269
+ widget.setParent(None)
270
+ widget.deleteLater()
271
+ return selected_parameter
272
+
273
+
274
+ def _restore_selection(self, parameter):
275
+ if parameter:
276
+ for label in self.findChildren(PreviewImageLabel):
277
+ if label.parameter == parameter:
278
+ label.set_selected(True)
279
+ break
280
+
281
+
282
+ class EqualsAlignedDelegate(qtw.QStyledItemDelegate):
283
+ """
284
+ Paints setpoint counts with equals signs aligned.
285
+ """
286
+
287
+ right_text_alignment = (
288
+ QtCore.Qt.AlignmentFlag.AlignLeft
289
+ | QtCore.Qt.AlignmentFlag.AlignVCenter
290
+ )
291
+
292
+ def __init__(self, *args, **kwargs):
293
+ super().__init__(*args, **kwargs)
294
+ self._right_width_cache = {}
295
+
296
+
297
+ def invalidate_width_cache(self):
298
+ self._right_width_cache.clear()
299
+
300
+ def paint(self, painter, option, index):
301
+ text = index.data(QtCore.Qt.ItemDataRole.DisplayRole)
302
+ if text is None or text == "":
303
+ super().paint(painter, option, index)
304
+ return
305
+
306
+ opt = qtw.QStyleOptionViewItem(option)
307
+ self.initStyleOption(opt, index)
308
+ text = str(text)
309
+ sections = self._display_sections(text)
310
+ if sections is None:
311
+ super().paint(painter, option, index)
312
+ return
313
+ left, right = sections
314
+
315
+ opt.text = ""
316
+
317
+ widget = opt.widget
318
+ style = widget.style() if widget else qtw.QApplication.style()
319
+ if style is None:
320
+ super().paint(painter, option, index)
321
+ return
322
+ style.drawControl(qtw.QStyle.ControlElement.CE_ItemViewItem, opt, painter, widget)
323
+
324
+ text_rect = style.subElementRect(
325
+ qtw.QStyle.SubElement.SE_ItemViewItemText,
326
+ opt,
327
+ widget
328
+ ).adjusted(2, 0, -2, 0)
329
+ metrics = opt.fontMetrics
330
+ equals_text = " = "
331
+ equals_width = metrics.horizontalAdvance(equals_text)
332
+ max_right_width = self._max_right_width(index, metrics)
333
+ if right is None and max_right_width <= 0:
334
+ painter.save()
335
+ painter.setFont(opt.font)
336
+ painter.setPen(self._text_color(opt))
337
+ painter.drawText(
338
+ text_rect,
339
+ QtCore.Qt.AlignmentFlag.AlignRight
340
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
341
+ metrics.elidedText(left, QtCore.Qt.TextElideMode.ElideLeft, text_rect.width())
342
+ )
343
+ painter.restore()
344
+ return
345
+
346
+ equals_left = max(
347
+ text_rect.left(),
348
+ text_rect.right() - max_right_width - equals_width
349
+ )
350
+
351
+ left_rect = QtCore.QRect(
352
+ text_rect.left(),
353
+ text_rect.top(),
354
+ max(0, equals_left - text_rect.left()),
355
+ text_rect.height()
356
+ )
357
+ equals_rect = QtCore.QRect(
358
+ equals_left,
359
+ text_rect.top(),
360
+ equals_width,
361
+ text_rect.height()
362
+ )
363
+ right_rect = QtCore.QRect(
364
+ equals_left + equals_width,
365
+ text_rect.top(),
366
+ max(0, text_rect.right() - equals_left - equals_width + 1),
367
+ text_rect.height()
368
+ )
369
+
370
+ painter.save()
371
+ painter.setFont(opt.font)
372
+ painter.setPen(self._text_color(opt))
373
+ painter.drawText(
374
+ left_rect,
375
+ QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter,
376
+ metrics.elidedText(left, QtCore.Qt.TextElideMode.ElideLeft, left_rect.width())
377
+ )
378
+ if right is not None:
379
+ painter.drawText(equals_rect, QtCore.Qt.AlignmentFlag.AlignCenter, equals_text)
380
+ self._draw_right_text(painter, right_rect, right, metrics)
381
+ painter.restore()
382
+
383
+
384
+ def _display_sections(self, text):
385
+ if " = " in text:
386
+ return str(text).split(" = ", 1)
387
+ if self._is_count_text(text):
388
+ return str(text), None
389
+ return None
390
+
391
+
392
+ def _is_count_text(self, text):
393
+ try:
394
+ int(str(text).replace(",", ""))
395
+ except (TypeError, ValueError):
396
+ return False
397
+ return True
398
+
399
+
400
+ def _draw_right_text(self, painter, right_rect, right, metrics):
401
+ painter.drawText(
402
+ right_rect,
403
+ self.right_text_alignment,
404
+ metrics.elidedText(right, QtCore.Qt.TextElideMode.ElideRight, right_rect.width())
405
+ )
406
+
407
+
408
+ def _max_right_width(self, index, metrics):
409
+ view = self.parent()
410
+ if not isinstance(view, qtw.QTreeWidget):
411
+ return 0
412
+
413
+ column = index.column()
414
+ cached = self._right_width_cache.get(column)
415
+ if cached is not None:
416
+ return cached
417
+
418
+ max_width = 0
419
+ for row in range(view.topLevelItemCount()):
420
+ item = view.topLevelItem(row)
421
+ if item is None:
422
+ continue
423
+
424
+ sections = self._display_sections(item.text(column))
425
+ if sections is None:
426
+ continue
427
+
428
+ _, right = sections
429
+ if right is None:
430
+ right = one_dimensional_duplicate_point_count(
431
+ getattr(item, "run_metadata", {})
432
+ )
433
+ if right is None:
434
+ continue
435
+
436
+ max_width = max(max_width, metrics.horizontalAdvance(right))
437
+ self._right_width_cache[column] = max_width
438
+ return max_width
439
+
440
+
441
+ def _text_color(self, option):
442
+ return option.palette.color(QtGui.QPalette.ColorRole.Text)
443
+
444
+
445
+ class SortableTreeWidgetItem(qtw.QTreeWidgetItem):
446
+ """
447
+ QTreeWidgetItem with an overridden comparator that sorts numerical values
448
+ as numbers instead of sorting them alphabetically.
449
+ """
450
+ def __init__(self, strings):
451
+ super().__init__(strings)
452
+ self.run_metadata = {}
453
+ self._guid = ""
454
+
455
+ def __lt__(self, other: qtw.QTreeWidgetItem) -> bool:
456
+ tree = self.treeWidget()
457
+ if tree is None:
458
+ return super().__lt__(other)
459
+
460
+ col = tree.sortColumn()
461
+ value1 = self.data(col, QtCore.Qt.ItemDataRole.UserRole)
462
+ value2 = other.data(col, QtCore.Qt.ItemDataRole.UserRole)
463
+ if value1 is not None and value2 is not None:
464
+ try:
465
+ return float(value1) < float(value2)
466
+ except (TypeError, ValueError):
467
+ pass
468
+
469
+ text1 = self.text(col)
470
+ text2 = other.text(col)
471
+ try:
472
+ return float(text1) < float(text2)
473
+ except ValueError:
474
+ return text1 < text2
475
+
476
+ @property
477
+ def guid(self):
478
+ return self._guid
479
+
480
+
481
+ def set_guid(self, guid):
482
+ self._guid = guid
483
+
484
+
485
+ def update_tooltip(self):
486
+ tooltip = run_tooltip_text(self.run_metadata)
487
+ for col in range(self.columnCount()):
488
+ self.setToolTip(col, tooltip)
@@ -0,0 +1,111 @@
1
+ from typing import Any
2
+
3
+ from PyQt6 import QtCore
4
+ from PyQt6 import QtWidgets as qtw
5
+
6
+ _NO_ALIGNMENT = QtCore.Qt.AlignmentFlag(0)
7
+
8
+
9
+ class QDock_context(qtw.QDockWidget):
10
+ """
11
+ A custom QDockWidget designed to emulate a side QToolbar.
12
+
13
+ """
14
+ def __init__(self, *args, **kargs):
15
+ super().__init__(*args, **kargs)
16
+
17
+ self.setTitleBarWidget(qtw.QWidget(self))
18
+
19
+ self.event_filter = contextMenuFilter(self, self.parent())
20
+ self.installEventFilter(self.event_filter)
21
+
22
+ core_widget = qtw.QFrame()
23
+ self.setWidget(core_widget)
24
+
25
+ self.content_layout = self.VBox_context(self.event_filter, core_widget)
26
+
27
+
28
+ ### Overwrite add functions to add to main layout
29
+ def addLayout(self, *args: Any, **kargs: Any) -> "QDock_context.HBox_context":
30
+ layout = self.HBox_context(self.event_filter, *args, **kargs)
31
+ self.content_layout.addLayout(layout)
32
+ return layout
33
+
34
+ def addWidget(self, *args: Any, **kargs: Any) -> None:
35
+ self.content_layout.addWidget(*args, **kargs)
36
+
37
+ ### SUB CLASS LAYOUT TO CARRY CONTEXT MENU THROUGH ###
38
+ class VBox_context(qtw.QVBoxLayout):
39
+ def __init__(self, event_filter, *args, **kargs):
40
+ super().__init__(*args, **kargs)
41
+ self.event_filter = event_filter
42
+
43
+ def addWidget(
44
+ self,
45
+ widget: qtw.QWidget | None,
46
+ stretch: int = 0,
47
+ alignment: QtCore.Qt.AlignmentFlag = _NO_ALIGNMENT,
48
+ ) -> None:
49
+ if widget is not None:
50
+ widget.installEventFilter(self.event_filter)
51
+ for child in self.findChildren(qtw.QWidget):
52
+ child.installEventFilter(self.event_filter)
53
+
54
+ super().addWidget(widget, stretch, alignment)
55
+
56
+ class HBox_context(qtw.QHBoxLayout):
57
+ def __init__(self, event_filter, *args, **kargs):
58
+ super().__init__(*args, **kargs)
59
+ self.event_filter = event_filter
60
+
61
+ def addWidget(
62
+ self,
63
+ widget: qtw.QWidget | None,
64
+ stretch: int = 0,
65
+ alignment: QtCore.Qt.AlignmentFlag = _NO_ALIGNMENT,
66
+ ) -> None:
67
+ if widget is not None:
68
+ widget.installEventFilter(self.event_filter)
69
+ for child in self.findChildren(qtw.QWidget):
70
+ child.installEventFilter(self.event_filter)
71
+
72
+ super().addWidget(widget, stretch, alignment)
73
+
74
+
75
+ class contextMenuFilter(QtCore.QObject):
76
+ """Filter to show toggle menu from any widget inside the dock."""
77
+ def __init__(self, parent, main_window):
78
+ super().__init__(parent)
79
+ self.main_window = main_window
80
+
81
+ def eventFilter(self, obj, event):
82
+ """
83
+ Event handler for right-clicking on a QDock_context
84
+ Produces a context menu for toggling display of toolbars and dockwidgets
85
+
86
+ Parameters
87
+ ----------
88
+ obj :
89
+ Unused by required slot.
90
+ event : PyQt6.QtCore.QEvent.Type.ContextMenu
91
+
92
+ Returns
93
+ -------
94
+ bool
95
+ Whether to show context menu.
96
+
97
+ """
98
+ if event.type() == QtCore.QEvent.Type.ContextMenu:
99
+ menu = qtw.QMenu(self.main_window)
100
+
101
+ # Find toolbar and dock widgets to add to context menu
102
+ for toolbar in self.main_window.findChildren(qtw.QToolBar):
103
+ menu.addAction(toolbar.toggleViewAction())
104
+
105
+ for dock in self.main_window.findChildren(qtw.QDockWidget):
106
+ menu.addAction(dock.toggleViewAction())
107
+
108
+ # Display context menu
109
+ menu.exec(event.globalPos())
110
+ return True
111
+ return False