From 1d1c0abe6b1e5490e6dd6e42013fd3d63ff1340a Mon Sep 17 00:00:00 2001 From: David Meyer Date: Fri, 21 Nov 2025 10:47:06 -0500 Subject: [PATCH 1/5] Switch `exec_` to `exec` --- runviewer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runviewer/__main__.py b/runviewer/__main__.py index bbccb2a..8aa1bb2 100644 --- a/runviewer/__main__.py +++ b/runviewer/__main__.py @@ -1671,7 +1671,7 @@ def handler(self, h5_filepath): splash.hide() def execute_program(): - qapplication.exec_() + qapplication.exec() # Let the interpreter run every 500ms so it sees Ctrl-C interrupts: timer = QTimer() From 3dfcead3864e83eca161c5274f40f5cd85cb0035 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Tue, 2 Dec 2025 14:44:40 -0500 Subject: [PATCH 2/5] Bump qtutils version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0e13256..25b529b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "labscript_utils>=3.1.0b1", "labscript-c-extensions", "pyqtgraph>=0.11.1", - "qtutils>=2.0.0", + "qtutils>=4.0.0", "zprocess", "numpy>=1.15", "scipy", From f38a8f7ebcfd3fbe98467fb126bf7fd494facde5 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Wed, 3 Dec 2025 12:11:02 -0500 Subject: [PATCH 3/5] Update stylesheet to respect OS themeing better --- runviewer/main.ui | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/runviewer/main.ui b/runviewer/main.ui index c2db1d2..60d7f0a 100644 --- a/runviewer/main.ui +++ b/runviewer/main.ui @@ -29,28 +29,28 @@ QPushButton:hover { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #f6f7fa, stop: 1 #dadbde); - border: 1px solid #8f8f91; + stop: 0 palette(light), stop: 1 palette(window)); + border: 1px solid palette(dark); border-radius: 3px; } QPushButton:pressed { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - border: 1px solid #8f8f91; + stop: 0 palette(window), stop: 1 palette(light)); + border: 1px solid palette(dark); border-radius: 3px; } QPushButton:checked { - background-color: #dadbde; - border: 1px solid #8f8f91; + background-color: palette(window); + border: 1px solid palette(dark); border-radius: 3px; } QPushButton:hover:checked { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - border: 1px solid #8f8f91; + stop: 0 palette(window), stop: 1 palette(light)); + border: 1px solid palette(dark); border-radius: 3px; } @@ -61,28 +61,28 @@ QToolButton { QToolButton:hover { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #f6f7fa, stop: 1 #dadbde); - border: 1px solid #8f8f91; + stop: 0 palette(light), stop: 1 palette(window)); + border: 1px solid palette(dark); border-radius: 3px; } QToolButton:pressed { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - border: 1px solid #8f8f91; + stop: 0 palette(window), stop: 1 palette(light)); + border: 1px solid palette(dark); border-radius: 3px; } QToolButton:checked { - background-color: #dadbde; - border: 1px solid #8f8f91; + background-color: palette(window); + border: 1px solid palette(dark); border-radius: 3px; } QToolButton:hover:checked { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - border: 1px solid #8f8f91; + stop: 0 palette(window), stop: 1 palette(light)); + border: 1px solid palette(dark); border-radius: 3px; } From bc3cba89b3c340e6f903779f0f2590a6ea4fbc57 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Thu, 11 Dec 2025 16:42:21 -0500 Subject: [PATCH 4/5] Remove Qt star imports --- runviewer/__main__.py | 207 +++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 104 deletions(-) diff --git a/runviewer/__main__.py b/runviewer/__main__.py index 8aa1bb2..58ccf42 100644 --- a/runviewer/__main__.py +++ b/runviewer/__main__.py @@ -46,9 +46,8 @@ import h5py # No splash update for Qt - the splash screen already imported it -from qtutils.qt.QtCore import * -from qtutils.qt.QtGui import * -from qtutils.qt.QtWidgets import * +from qtutils.qt import QtCore, QtGui, QtWidgets +from qtutils import UiLoader, inmain_later, inmain_decorator splash.update_text('importing pyqtgraph') import pyqtgraph as pg @@ -61,7 +60,6 @@ pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') -from qtutils import * import qtutils.icons splash.update_text('importing labscript suite modules') from labscript_utils.connections import ConnectionTable @@ -105,7 +103,7 @@ def format_time(input_sec): def int_to_enum(enum_list, value): """stupid hack to work around the fact that PySide screws with the type of a variable when it goes into a model. Enums are converted to ints, which then - can't be interpreted by QColor correctly (for example) + can't be interpreted by QtGui.QColor correctly (for example) unfortunately Qt doesn't provide a python list structure of enums, so you have to build the list yourself. """ @@ -144,12 +142,12 @@ def __init__(self, input_times, target_positions, stop_time): self.scaled_stop_time = self.get_scaled_time(self.org_stop_time) -class ColourDelegate(QItemDelegate): +class ColourDelegate(QtWidgets.QItemDelegate): def __init__(self, view, *args, **kwargs): - QItemDelegate.__init__(self, *args, **kwargs) + QtWidgets.QItemDelegate.__init__(self, *args, **kwargs) self._view = view - self._colours = [Qt.black, Qt.red, Qt.green, Qt.blue, Qt.cyan, Qt.magenta, Qt.yellow, Qt.gray, Qt.darkRed, Qt.darkGreen, Qt.darkBlue, Qt.darkCyan, Qt.darkMagenta, Qt.darkYellow, Qt.darkGray, Qt.lightGray] + self._colours = [QtCore.Qt.black, QtCore.Qt.red, QtCore.Qt.green, QtCore.Qt.blue, QtCore.Qt.cyan, QtCore.Qt.magenta, QtCore.Qt.yellow, QtCore.Qt.gray, QtCore.Qt.darkRed, QtCore.Qt.darkGreen, QtCore.Qt.darkBlue, QtCore.Qt.darkCyan, QtCore.Qt.darkMagenta, QtCore.Qt.darkYellow, QtCore.Qt.darkGray, QtCore.Qt.lightGray] self._current_colour_index = 0 @@ -161,21 +159,21 @@ def get_next_colour(self): return colour def createEditor(self, parent, option, index): - editor = QComboBox(parent) - #colours = QColor.colorNames() + editor = QtWidgets.QComboBox(parent) + #colours = QtGui.QColor.colorNames() for colour in self._colours: - pixmap = QPixmap(20, 20) + pixmap = QtGui.QPixmap(20, 20) pixmap.fill(colour) - editor.addItem(QIcon(pixmap), '', colour) + editor.addItem(QtGui.QIcon(pixmap), '', colour) editor.activated.connect(lambda index, editor=editor: self._view.commitData(editor)) - editor.activated.connect(lambda index, editor=editor: self._view.closeEditor(editor, QAbstractItemDelegate.NoHint)) - QTimer.singleShot(10, editor.showPopup) + editor.activated.connect(lambda index, editor=editor: self._view.closeEditor(editor, QtWidgets.QAbstractItemDelegate.NoHint)) + QtCore.QTimer.singleShot(10, editor.showPopup) return editor def setEditorData(self, editor, index): - value = index.model().data(index, Qt.UserRole) + value = index.model().data(index, QtCore.Qt.UserRole) for i in range(editor.count()): if editor.itemData(i) == value(): editor.setCurrentIndex(i) @@ -188,8 +186,8 @@ def setModelData(self, editor, model, index): # Note, all data being written to the model must be read out of the editor PRIOR to calling model.setData() # This is because a call to model.setData() triggers setEditorData(), which messes up subsequent # calls to the editor to determine the currently selected item/data - model.setData(index, icon, Qt.DecorationRole) - model.setData(index, lambda clist=self._colours, colour=colour: int_to_enum(clist, colour), Qt.UserRole) + model.setData(index, icon, QtCore.Qt.DecorationRole) + model.setData(index, lambda clist=self._colours, colour=colour: int_to_enum(clist, colour), QtCore.Qt.UserRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) @@ -201,7 +199,7 @@ def __init__(self, exp_config): self.ui = UiLoader().load(os.path.join(runviewer_dir, 'main.ui')) # setup shot treeview model - self.shot_model = QStandardItemModel() + self.shot_model = QtGui.QStandardItemModel() self.shot_model.setHorizontalHeaderLabels(['colour', 'shutters', 'path']) self.ui.shot_treeview.setModel(self.shot_model) self.ui.shot_treeview.resizeColumnToContents(1) @@ -210,7 +208,7 @@ def __init__(self, exp_config): self.ui.shot_treeview.setItemDelegateForColumn(0, self.shot_colour_delegate) # setup channel treeview model - self.channel_model = QStandardItemModel() + self.channel_model = QtGui.QStandardItemModel() self.channel_model.setHorizontalHeaderLabels(['channel']) self.ui.channel_treeview.setModel(self.channel_model) self.channel_model.itemChanged.connect(self.update_plots) @@ -264,29 +262,29 @@ def __init__(self, exp_config): self.ui.plot_layout.addWidget(time_axis_plot) # add some icons - self.ui.add_shot.setIcon(QIcon(':/qtutils/fugue/plus')) - self.ui.remove_shots.setIcon(QIcon(':/qtutils/fugue/minus')) - self.ui.enable_selected_shots.setIcon(QIcon(':/qtutils/fugue/ui-check-box')) - self.ui.disable_selected_shots.setIcon(QIcon(':/qtutils/fugue/ui-check-box-uncheck')) - self.ui.group_channel.setIcon(QIcon(':/qtutils/fugue/layers-group')) - self.ui.delete_group.setIcon(QIcon(':/qtutils/fugue/layers-ungroup')) - self.ui.channel_move_to_top.setIcon(QIcon(':/qtutils/fugue/arrow-stop-090')) - self.ui.channel_move_up.setIcon(QIcon(':/qtutils/fugue/arrow-090')) - self.ui.channel_move_down.setIcon(QIcon(':/qtutils/fugue/arrow-270')) - self.ui.channel_move_to_bottom.setIcon(QIcon(':/qtutils/fugue/arrow-stop-270')) - self.ui.reset_x_axis.setIcon(QIcon(':/qtutils/fugue/layer-resize-replicate')) - self.ui.reset_y_axis.setIcon(QIcon(':/qtutils/fugue/layer-resize-replicate-vertical')) - self.ui.toggle_tooltip.setIcon(QIcon(':/qtutils/fugue/ui-tooltip-balloon')) - self.ui.linear_time.setIcon(QIcon(':/qtutils/fugue/clock-history')) - self.ui.equal_space_time.setIcon(QIcon(':/qtutils/fugue/border-vertical-all')) + self.ui.add_shot.setIcon(QtGui.QIcon(':/qtutils/fugue/plus')) + self.ui.remove_shots.setIcon(QtGui.QIcon(':/qtutils/fugue/minus')) + self.ui.enable_selected_shots.setIcon(QtGui.QIcon(':/qtutils/fugue/ui-check-box')) + self.ui.disable_selected_shots.setIcon(QtGui.QIcon(':/qtutils/fugue/ui-check-box-uncheck')) + self.ui.group_channel.setIcon(QtGui.QIcon(':/qtutils/fugue/layers-group')) + self.ui.delete_group.setIcon(QtGui.QIcon(':/qtutils/fugue/layers-ungroup')) + self.ui.channel_move_to_top.setIcon(QtGui.QIcon(':/qtutils/fugue/arrow-stop-090')) + self.ui.channel_move_up.setIcon(QtGui.QIcon(':/qtutils/fugue/arrow-090')) + self.ui.channel_move_down.setIcon(QtGui.QIcon(':/qtutils/fugue/arrow-270')) + self.ui.channel_move_to_bottom.setIcon(QtGui.QIcon(':/qtutils/fugue/arrow-stop-270')) + self.ui.reset_x_axis.setIcon(QtGui.QIcon(':/qtutils/fugue/layer-resize-replicate')) + self.ui.reset_y_axis.setIcon(QtGui.QIcon(':/qtutils/fugue/layer-resize-replicate-vertical')) + self.ui.toggle_tooltip.setIcon(QtGui.QIcon(':/qtutils/fugue/ui-tooltip-balloon')) + self.ui.linear_time.setIcon(QtGui.QIcon(':/qtutils/fugue/clock-history')) + self.ui.equal_space_time.setIcon(QtGui.QIcon(':/qtutils/fugue/border-vertical-all')) self.ui.linear_time.setEnabled(False) self.ui.equal_space_time.setEnabled(False) - self.ui.actionOpen_Shot.setIcon(QIcon(':/qtutils/fugue/plus')) - self.ui.actionQuit.setIcon(QIcon(':/qtutils/fugue/cross-button')) - self.ui.actionLoad_channel_config.setIcon(QIcon(':/qtutils/fugue/folder-open')) - self.ui.actionSave_channel_config.setIcon(QIcon(':/qtutils/fugue/disk')) + self.ui.actionOpen_Shot.setIcon(QtGui.QIcon(':/qtutils/fugue/plus')) + self.ui.actionQuit.setIcon(QtGui.QIcon(':/qtutils/fugue/cross-button')) + self.ui.actionLoad_channel_config.setIcon(QtGui.QIcon(':/qtutils/fugue/folder-open')) + self.ui.actionSave_channel_config.setIcon(QtGui.QIcon(':/qtutils/fugue/disk')) # disable buttons that are not yet implemented to help avoid confusion! self.ui.group_channel.setEnabled(False) @@ -314,8 +312,8 @@ def __init__(self, exp_config): self.ui.actionSave_channel_config.triggered.connect(self.on_save_channel_config) # Keyboard shortcuts: - QShortcut('Del', self.ui.shot_treeview, lambda: self.on_remove_shots(confirm=True)) - QShortcut('Shift+Del', self.ui.shot_treeview, lambda: self.on_remove_shots(confirm=False)) + QtGui.QShortcut('Del', self.ui.shot_treeview, lambda: self.on_remove_shots(confirm=True)) + QtGui.QShortcut('Shift+Del', self.ui.shot_treeview, lambda: self.on_remove_shots(confirm=False)) splash.update_text('done') self.ui.show() @@ -394,12 +392,12 @@ def _update_markers(self, index): if (r, g, b) == (-1, -1, -1): # Default colour, black: r, g, b = (0, 0, 0) - color = QColor(r, g, b) + color = QtGui.QColor(r, g, b) label = m['label'].decode() if isinstance( m['label'], bytes) else str(m['label']) if i == 0: - line = self._markers_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=Qt.DashLine), label=label, labelOpts= {"color": color, "fill": QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=False ) + line = self._markers_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=QtCore.Qt.DashLine), label=label, labelOpts= {"color": color, "fill": QtGui.QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=False ) else: - line = self._markers_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=Qt.DashLine), label=label, labelOpts= {"color": color, "fill": QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=True ) + line = self._markers_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=QtCore.Qt.DashLine), label=label, labelOpts= {"color": color, "fill": QtGui.QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=True ) line.setBounds([last_time+1e-9 if last_time !=0 else last_time ,None]) line.sigPositionChanged.connect(self._marker_moving) line.sigPositionChangeFinished.connect(self._marker_moved) @@ -407,7 +405,7 @@ def _update_markers(self, index): self.movable_marker_items[line] = self._markers_plot[0] self.marker_times_unscaled[line] = unscaled_t - line = self._time_axis_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=Qt.DashLine), label=format_time(delta_t), labelOpts= {"color": color, "fill": QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=False ) + line = self._time_axis_plot[0].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=QtCore.Qt.DashLine), label=format_time(delta_t), labelOpts= {"color": color, "fill": QtGui.QColor(255, 255, 255, 255), "rotateAxis":(1, 0), "anchors": [(0.5, 0),(0.5, 0)]}, movable=False ) self.all_marker_items[line] = self._time_axis_plot[0] last_time = t self.update_plots() @@ -417,7 +415,7 @@ def mouseMovedEvent(self, position, ui, name): v = ui.scene().views()[0] viewP = v.mapFromScene(position) glob_pos = ui.mapToGlobal(viewP) # convert to Screen x - glob_zero = ui.mapToGlobal(QPoint(0, 0)) + glob_zero = ui.mapToGlobal(QtCore.QPoint(0, 0)) self._global_start_x = glob_zero.x() self._global_start_y = glob_zero.y() self._global_width = ui.width() @@ -432,7 +430,7 @@ def mouseMovedEvent(self, position, ui, name): else: unscaled_t = scaled_t if unscaled_t is not None: - pos = QPoint(glob_pos.x(), glob_pos.y()) + pos = QtCore.QPoint(glob_pos.x(), glob_pos.y()) plot_data = ui.plotItem.listDataItems()[0].getData() if plot_data[0] is not None and scaled_t is not None: nearest_index = numpy.abs(plot_data[0] - scaled_t).argmin() - 1 @@ -440,7 +438,7 @@ def mouseMovedEvent(self, position, ui, name): else: y_val = '-' text = "Plot: {} \nTime: {:.9f}s\nValue: {}".format(name, unscaled_t, y_val) - QToolTip.showText(pos, text) + QtWidgets.QToolTip.showText(pos, text) def _reset_linear_time(self): self.scale_time = False @@ -590,7 +588,7 @@ def _process_shots(self): inmain_later(self.load_shot, filepath) def on_load_channel_config(self): - config_file = QFileDialog.getOpenFileName(self.ui, "Select file to load", self.default_config_path, "Config files (*.ini)") + config_file = QtWidgets.QFileDialog.getOpenFileName(self.ui, "Select file to load", self.default_config_path, "Config files (*.ini)") if isinstance(config_file, tuple): config_file, _ = config_file if config_file: @@ -601,21 +599,21 @@ def on_load_channel_config(self): check_items = self.channel_model.findItems(channel) if len(check_items) == 0: items = [] - check_item = QStandardItem(channel) + check_item = QtGui.QStandardItem(channel) check_item.setEditable(False) check_item.setCheckable(True) items.append(check_item) - check_item.setCheckState(Qt.Checked if checked else Qt.Unchecked) + check_item.setCheckState(QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) check_item.setEnabled(False) self.channel_model.insertRow(row, items) else: check_item = check_items[0] - check_item.setCheckState(Qt.Checked if checked else Qt.Unchecked) + check_item.setCheckState(QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) self.channel_model.takeRow(check_item.row()) self.channel_model.insertRow(row, check_item) def on_save_channel_config(self): - save_file = QFileDialog.getSaveFileName(self.ui, 'Select file to save current channel configuration', self.default_config_path, "config files (*.ini)") + save_file = QtWidgets.QFileDialog.getSaveFileName(self.ui, 'Select file to save current channel configuration', self.default_config_path, "config files (*.ini)") if type(save_file) is tuple: save_file, _ = save_file @@ -624,7 +622,7 @@ def on_save_channel_config(self): channels = [] for row in range(self.channel_model.rowCount()): item = self.channel_model.item(row) - channels.append((item.text(), item.checkState() == Qt.Checked)) + channels.append((item.text(), item.checkState() == QtCore.Qt.Checked)) save_appconfig(save_file, {'runviewer_state': {'channels': channels}}) @@ -644,7 +642,7 @@ def on_toggle_shutter(self, checked, current_shot): line.hide() def on_add_shot(self): - selected_files = QFileDialog.getOpenFileNames(self.ui, "Select file to load", self.last_opened_shots_folder, "HDF5 files (*.h5 *.hdf5)") + selected_files = QtWidgets.QFileDialog.getOpenFileNames(self.ui, "Select file to load", self.last_opened_shots_folder, "HDF5 files (*.h5 *.hdf5)") popup_warning = False if isinstance(selected_files, tuple): selected_files, _ = selected_files @@ -668,11 +666,11 @@ def on_add_shot(self): popup_warning = True raise if popup_warning: - message = QMessageBox() + message = QtWidgets.QMessageBox() message.setText("Warning: Some shots were not loaded because they were not valid hdf5 files") - message.setIcon(QMessageBox.Warning) + message.setIcon(QtWidgets.QMessageBox.Warning) message.setWindowTitle("Runviewer") - message.setStandardButtons(QMessageBox.Ok) + message.setStandardButtons(QtWidgets.QMessageBox.Ok) message.exec_() def on_remove_shots(self, confirm=True): @@ -684,9 +682,9 @@ def on_remove_shots(self, confirm=True): selected_row_list.sort(reverse=True) if confirm: - reply = QMessageBox.question(self.ui, 'Runviewer', 'Remove {} shots?'.format(len(selected_row_list)), - QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: + reply = QtWidgets.QMessageBox.question(self.ui, 'Runviewer', 'Remove {} shots?'.format(len(selected_row_list)), + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: return for row in selected_row_list: @@ -695,8 +693,8 @@ def on_remove_shots(self, confirm=True): shutter_item = self.shot_model.item(row, SHOT_MODEL__SHUTTER_INDEX) shot = item.data() # unselect shot - item.setCheckState(Qt.Unchecked) - shutter_item.setCheckState(Qt.Unchecked) + item.setCheckState(QtCore.Qt.Unchecked) + shutter_item.setCheckState(QtCore.Qt.Unchecked) # remove shot from markers list shot_combobox_index = self.ui.markers_comboBox.findText(os.path.basename(shot.path)) self.ui.markers_comboBox.removeItem(shot_combobox_index) @@ -714,18 +712,18 @@ def on_shot_selection_changed(self, item): check_shutter = self.shot_model.item(row, SHOT_MODEL__SHUTTER_INDEX) if checked: - colour = colour_item.data(Qt.UserRole) + colour = colour_item.data(QtCore.Qt.UserRole) if colour is not None: colour = colour() else: colour = self.shot_colour_delegate.get_next_colour() colour_item.setEditable(True) - pixmap = QPixmap(20, 20) + pixmap = QtGui.QPixmap(20, 20) pixmap.fill(colour) - icon = QIcon(pixmap) - colour_item.setData(lambda clist=self.shot_colour_delegate._colours, colour=colour: int_to_enum(clist, colour), Qt.UserRole) - colour_item.setData(icon, Qt.DecorationRole) + icon = QtGui.QIcon(pixmap) + colour_item.setData(lambda clist=self.shot_colour_delegate._colours, colour=colour: int_to_enum(clist, colour), QtCore.Qt.UserRole) + colour_item.setData(icon, QtCore.Qt.DecorationRole) shot_combobox_index = self.ui.markers_comboBox.findText(os.path.basename(item.data().path)) self.ui.markers_comboBox.model().item(shot_combobox_index).setEnabled(True) if self.ui.markers_comboBox.currentIndex() == 0: @@ -746,7 +744,7 @@ def on_shot_selection_changed(self, item): check_shutter.setEnabled(False) # model.setData(index, editor.itemIcon(editor.currentIndex()), - # model.setData(index, editor.itemData(editor.currentIndex()), Qt.UserRole) + # model.setData(index, editor.itemData(editor.currentIndex()), QtCore.Qt.UserRole) self.update_channels_treeview() elif self.shot_model.indexFromItem(item).column() == SHOT_MODEL__COLOUR_INDEX: @@ -759,8 +757,8 @@ def on_shot_selection_changed(self, item): for channel in self.plot_items.keys(): for shot in self.plot_items[channel]: if shot == current_shot: - colour = item.data(Qt.UserRole) - self.plot_items[channel][shot].setPen(pg.mkPen(QColor(colour()), width=2)) + colour = item.data(QtCore.Qt.UserRole) + self.plot_items[channel][shot].setPen(pg.mkPen(QtGui.QColor(colour()), width=2)) elif self.shot_model.indexFromItem(item).column() == SHOT_MODEL__SHUTTER_INDEX: current_shot = self.shot_model.item(self.shot_model.indexFromItem(item).row(), SHOT_MODEL__CHECKBOX_INDEX).data() self.on_toggle_shutter(item.checkState(), current_shot) @@ -772,27 +770,27 @@ def load_shot(self, filepath): # add shot to shot list # Create Items items = [] - colour_item = QStandardItem('') + colour_item = QtGui.QStandardItem('') colour_item.setEditable(False) colour_item.setToolTip('Double-click to change colour') items.append(colour_item) - check_shutter = QStandardItem() + check_shutter = QtGui.QStandardItem() check_shutter.setCheckable(True) - check_shutter.setCheckState(Qt.Unchecked) # options are Qt.Checked OR Qt.Unchecked + check_shutter.setCheckState(QtCore.Qt.Unchecked) # options are QtCore.Qt.Checked OR QtCore.Qt.Unchecked check_shutter.setEnabled(False) check_shutter.setToolTip("Toggle shutter markers") items.append(check_shutter) - check_item = QStandardItem(shot.path) + check_item = QtGui.QStandardItem(shot.path) check_item.setEditable(False) check_item.setCheckable(True) - check_item.setCheckState(Qt.Unchecked) # options are Qt.Checked OR Qt.Unchecked + check_item.setCheckState(QtCore.Qt.Unchecked) # options are QtCore.Qt.Checked OR QtCore.Qt.Unchecked check_item.setData(shot) check_item.setToolTip(filepath) items.append(check_item) # script name - # path_item = QStandardItem(shot.path) + # path_item = QtGui.QStandardItem(shot.path) # path_item.setEditable(False) # items.append(path_item) self.shot_model.appendRow(items) @@ -810,9 +808,9 @@ def get_selected_shots_and_colours(self): item = self.shot_model.item(i, SHOT_MODEL__CHECKBOX_INDEX) colour_item = self.shot_model.item(i, SHOT_MODEL__COLOUR_INDEX) shutter_item = self.shot_model.item(i, SHOT_MODEL__SHUTTER_INDEX) - if item.checkState() == Qt.Checked: + if item.checkState() == QtCore.Qt.Checked: shot = item.data() - colour_item_data = colour_item.data(Qt.UserRole) + colour_item_data = colour_item.data(QtCore.Qt.UserRole) ticked_shots[shot] = (colour_item_data(), shutter_item.checkState()) return ticked_shots @@ -848,12 +846,12 @@ def update_channels_treeview(self): channels_to_add = channels_set.difference(treeview_channels) for channel in sorted(channels_to_add): items = [] - check_item = QStandardItem(channel) + check_item = QtGui.QStandardItem(channel) check_item.setEditable(False) check_item.setCheckable(True) - check_item.setCheckState(Qt.Unchecked) + check_item.setCheckState(QtCore.Qt.Unchecked) items.append(check_item) - # channel_name_item = QStandardItem(channel) + # channel_name_item = QtGui.QStandardItem(channel) # channel_name_item.setEditable(False) # items.append(channel_name_item) self.channel_model.appendRow(items) @@ -912,7 +910,7 @@ def update_plots(self): for i in range(self.channel_model.rowCount()): check_item = self.channel_model.item(i, CHANNEL_MODEL__CHECKBOX_INDEX) channel = str(check_item.text()) - if check_item.checkState() == Qt.Checked and check_item.isEnabled(): + if check_item.checkState() == QtCore.Qt.Checked and check_item.isEnabled(): # we want to show this plot # does a plot already exist? If yes, show it if channel in self.plot_widgets: @@ -937,9 +935,9 @@ def update_plots(self): # do we need to add any plot items for shots that were not previously selected? for shot, (colour, shutters_checked) in ticked_shots.items(): if shot not in self.plot_items[channel]: - # plot_item = self.plot_widgets[channel].plot(shot.traces[channel][0], shot.traces[channel][1], pen=pg.mkPen(QColor(colour), width=2)) + # plot_item = self.plot_widgets[channel].plot(shot.traces[channel][0], shot.traces[channel][1], pen=pg.mkPen(QtGui.QColor(colour), width=2)) # Add empty plot as it the custom resampling we do will happen quicker if we don't attempt to first plot all of the data - plot_item = self.plot_widgets[channel].plot([0, 0], [0], pen=pg.mkPen(QColor(colour), width=2), stepMode='center') + plot_item = self.plot_widgets[channel].plot([0, 0], [0], pen=pg.mkPen(QtGui.QColor(colour), width=2), stepMode='center') self.plot_items[channel][shot] = plot_item # Add Shutter Markers of newly ticked Shots @@ -947,10 +945,10 @@ def update_plots(self): for t, m in self.all_markers.items(): color = m['color'] - color = QColor(color[0], color[1], color[2]) + color = QtGui.QColor(color[0], color[1], color[2]) if self.scale_time and self.scalehandler is not None: t = self.scalehandler.get_scaled_time(t) - line = self.plot_widgets[channel].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=Qt.DashLine)) + line = self.plot_widgets[channel].addLine(x=t, pen=pg.mkPen(color=color, width=1.5, style=QtCore.Qt.DashLine)) self.all_marker_items[line] = self.plot_widgets[channel] # If no, create one @@ -982,9 +980,9 @@ def create_plot(self, channel, ticked_shots): units = '' for shot, (colour, shutters_checked) in ticked_shots.items(): if channel in shot.traces: - # plot_item = self.plot_widgets[channel].plot(shot.traces[channel][0], shot.traces[channel][1], pen=pg.mkPen(QColor(colour), width=2)) + # plot_item = self.plot_widgets[channel].plot(shot.traces[channel][0], shot.traces[channel][1], pen=pg.mkPen(QtGui.QColor(colour), width=2)) # Add empty plot as it the custom resampling we do will happen quicker if we don't attempt to first plot all of the data - plot_item = self.plot_widgets[channel].plot([0, 0], [0], pen=pg.mkPen(QColor(colour), width=2), stepMode='center') + plot_item = self.plot_widgets[channel].plot([0, 0], [0], pen=pg.mkPen(QtGui.QColor(colour), width=2), stepMode='center') self.plot_items[channel][shot] = plot_item if len(shot.traces[channel]) == 3: @@ -1003,18 +1001,18 @@ def add_shutter_markers(self, shot, channel, shutters_checked): if shot not in self.shutter_lines[channel] and channel in shot.shutter_times: self.shutter_lines[channel][shot] = [[], []] - open_color = QColor(0, 255, 0) - close_color = QColor(255, 0, 0) + open_color = QtGui.QColor(0, 255, 0) + close_color = QtGui.QColor(255, 0, 0) for t, val in shot.shutter_times[channel].items(): scaled_t = t if val: # val != 0, shutter open - line = self.plot_widgets[channel].addLine(x=scaled_t, pen=pg.mkPen(color=open_color, width=4., style=Qt.DotLine)) + line = self.plot_widgets[channel].addLine(x=scaled_t, pen=pg.mkPen(color=open_color, width=4., style=QtCore.Qt.DotLine)) self.shutter_lines[channel][shot][1].append(line) if not shutters_checked: line.hide() else: # else shutter close - line = self.plot_widgets[channel].addLine(x=scaled_t, pen=pg.mkPen(color=close_color, width=4., style=Qt.DotLine)) + line = self.plot_widgets[channel].addLine(x=scaled_t, pen=pg.mkPen(color=close_color, width=4., style=QtCore.Qt.DotLine)) self.shutter_lines[channel][shot][0].append(line) if not shutters_checked: line.hide() @@ -1269,7 +1267,7 @@ def __pool_resample(self, channel, shot, colour, xmin, xmax, dx): xnew, ynew = self.resample(shot.scaled_times(channel), shot.traces[channel][1], xmin, xmax, shot.stop_time, dx) else: xnew, ynew = self.resample(shot.traces[channel][0], shot.traces[channel][1], xmin, xmax, shot.stop_time, dx) - return inmain_later(self.plot_items[channel][shot].setData, xnew, ynew, pen=pg.mkPen(QColor(colour), width=2), stepMode='center') + return inmain_later(self.plot_items[channel][shot].setData, xnew, ynew, pen=pg.mkPen(QtGui.QColor(colour), width=2), stepMode='center') except Exception: #self._resample = True pass @@ -1278,11 +1276,11 @@ def __pool_resample(self, channel, shot, colour, xmin, xmax, dx): def channel_checked_and_enabled(self, channel): # logger.info('is channel %s enabled' % channel) index = self.channel_model.index(0, CHANNEL_MODEL__CHANNEL_INDEX) - indexes = self.channel_model.match(index, Qt.DisplayRole, channel, 1, Qt.MatchExactly) + indexes = self.channel_model.match(index, QtCore.Qt.DisplayRole, channel, 1, QtCore.Qt.MatchExactly) # logger.info('number of matches %d' % len(indexes)) if len(indexes) == 1: check_item = self.channel_model.itemFromIndex(indexes[0]) - if check_item.checkState() == Qt.Checked and check_item.isEnabled(): + if check_item.checkState() == QtCore.Qt.Checked and check_item.isEnabled(): return True return False @@ -1311,10 +1309,10 @@ def on_y_axes_reset(self): plot_widget.enableAutoRange(axis=pg.ViewBox.YAxis) def _enable_selected_shots(self): - self.update_ticks_of_selected_shots(Qt.Checked) + self.update_ticks_of_selected_shots(QtCore.Qt.Checked) def _disable_selected_shots(self): - self.update_ticks_of_selected_shots(Qt.Unchecked) + self.update_ticks_of_selected_shots(QtCore.Qt.Unchecked) def update_ticks_of_selected_shots(self, state): # Get the selection model from the treeview @@ -1342,7 +1340,7 @@ def _move_up(self): # Add the selected row into a position one above self.channel_model.insertRow(row - 1, items) # Since it is now a newly inserted row, select it again - selection_model.select(self.channel_model.indexFromItem(items[0]), QItemSelectionModel.SelectCurrent) + selection_model.select(self.channel_model.indexFromItem(items[0]), QtCore.QItemSelectionModel.SelectCurrent) # reupdate the list of selected indices to reflect this change selected_row_list[i] -= 1 self.update_plot_positions() @@ -1363,7 +1361,7 @@ def _move_down(self): # Add the selected row into a position one above self.channel_model.insertRow(row + 1, items) # Since it is now a newly inserted row, select it again - selection_model.select(self.channel_model.indexFromItem(items[0]), QItemSelectionModel.SelectCurrent) + selection_model.select(self.channel_model.indexFromItem(items[0]), QtCore.QItemSelectionModel.SelectCurrent) # reupdate the list of selected indices to reflect this change selected_row_list[i] += 1 self.update_plot_positions() @@ -1384,7 +1382,7 @@ def _move_top(self): # Add the selected row into a position one above self.channel_model.insertRow(row - 1, items) # Since it is now a newly inserted row, select it again - selection_model.select(self.channel_model.indexFromItem(items[0]), QItemSelectionModel.SelectCurrent) + selection_model.select(self.channel_model.indexFromItem(items[0]), QtCore.QItemSelectionModel.SelectCurrent) # reupdate the list of selected indices to reflect this change selected_row_list[i] -= 1 row -= 1 @@ -1405,7 +1403,7 @@ def _move_bottom(self): # Add the selected row into a position one above self.channel_model.insertRow(row + 1, items) # Since it is now a newly inserted row, select it again - selection_model.select(self.channel_model.indexFromItem(items[0]), QItemSelectionModel.SelectCurrent) + selection_model.select(self.channel_model.indexFromItem(items[0]), QtCore.QItemSelectionModel.SelectCurrent) # reupdate the list of selected indices to reflect this change selected_row_list[i] += 1 row += 1 @@ -1425,7 +1423,7 @@ def update_plot_positions(self): channel = str(check_item.text()) if channel in self.plot_widgets: self.ui.plot_layout.addWidget(self.plot_widgets[channel]) - if check_item.checkState() == Qt.Checked and check_item.isEnabled(): + if check_item.checkState() == QtCore.Qt.Checked and check_item.isEnabled(): self.plot_widgets[channel].show() else: self.plot_widgets[channel].hide() @@ -1655,9 +1653,10 @@ def handler(self, h5_filepath): if __name__ == "__main__": - qapplication = QApplication.instance() + qapplication = QtWidgets.QApplication.instance() if qapplication is None: qapplication = QApplication(sys.argv) + qapplication = QtWidgets.QApplication(sys.argv) shots_to_process_queue = Queue() @@ -1674,7 +1673,7 @@ def execute_program(): qapplication.exec() # Let the interpreter run every 500ms so it sees Ctrl-C interrupts: - timer = QTimer() + timer = QtCore.QTimer() timer.start(500) timer.timeout.connect(lambda: None) # Upon seeing a ctrl-c interrupt, quit the event loop From 678d13895cff3ae20db84b99a6069b5add5897ac Mon Sep 17 00:00:00 2001 From: David Meyer Date: Thu, 11 Dec 2025 16:43:12 -0500 Subject: [PATCH 5/5] Add custom `QMainWindow` to handle OS theme update events. --- runviewer/__main__.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/runviewer/__main__.py b/runviewer/__main__.py index 58ccf42..2dffa9e 100644 --- a/runviewer/__main__.py +++ b/runviewer/__main__.py @@ -46,7 +46,7 @@ import h5py # No splash update for Qt - the splash screen already imported it -from qtutils.qt import QtCore, QtGui, QtWidgets +from qtutils.qt import QtCore, QtGui, QtWidgets, QT_ENV from qtutils import UiLoader, inmain_later, inmain_decorator splash.update_text('importing pyqtgraph') @@ -192,11 +192,24 @@ def setModelData(self, editor, model, index): def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) +class RunviewerMainWindow(QtWidgets.QMainWindow): + + def changeEvent(self, event): + + # theme update only for PySide6 + if QT_ENV == 'PySide6' and event.type() == QtCore.QEvent.Type.ThemeChange: + for widget in self.findChildren(QtWidgets.QWidget): + # Complex widgets, like TreeView and TableView require triggering styleSheet and palette updates + widget.setStyleSheet(widget.styleSheet()) + widget.setPalette(widget.palette()) + + return super().changeEvent(event) + class RunViewer(object): def __init__(self, exp_config): splash.update_text('loading graphical interface') - self.ui = UiLoader().load(os.path.join(runviewer_dir, 'main.ui')) + self.ui = UiLoader().load(os.path.join(runviewer_dir, 'main.ui'), RunviewerMainWindow()) # setup shot treeview model self.shot_model = QtGui.QStandardItemModel()