From d64c35df9b1eaae8f01151472f1507b4e099adff Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Wed, 3 Dec 2025 16:38:24 -0500 Subject: [PATCH 01/16] Add OS settings-aware theming --- runmanager/__main__.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 2c4b905..b01d475 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -48,6 +48,7 @@ from qtutils.qt import QtCore, QtGui, QtWidgets from qtutils.qt.QtCore import pyqtSignal as Signal +from qtutils.qt.QtCore import Qt splash.update_text('importing labscript suite modules') from labscript_utils.ls_zprocess import zmq_get, ProcessTree, ZMQServer @@ -98,7 +99,16 @@ def log_if_global(g, g_list, message): if g in g_list: logger.info(message) - +def check_if_light_or_dark(): + style_hints = QtGui.QGuiApplication.styleHints() + if style_hints.colorScheme() == Qt.ColorScheme.Dark: + return "dark" + elif style_hints.colorScheme() == Qt.ColorScheme.Light: + return "light" + else: + return "unknown" + + def composite_colors(r0, g0, b0, a0, r1, g1, b1, a1): """composite a second colour over a first with given alpha values and return the result""" @@ -351,7 +361,7 @@ class ItemView(object): leftClicked = Signal(QtCore.QModelIndex) doubleLeftClicked = Signal(QtCore.QModelIndex) - COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue + COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue TODO: Smart way to access OS highlight def __init__(self, *args): super(ItemView, self).__init__(*args) @@ -662,11 +672,19 @@ class GroupTab(object): GLOBALS_ROLE_SORT_DATA = QtCore.Qt.UserRole + 2 GLOBALS_ROLE_PREVIOUS_TEXT = QtCore.Qt.UserRole + 3 GLOBALS_ROLE_IS_BOOL = QtCore.Qt.UserRole + 4 - - COLOR_ERROR = '#F79494' # light red - COLOR_OK = '#A5F7C6' # light green - COLOR_BOOL_ON = '#63F731' # bright green - COLOR_BOOL_OFF = '#608060' # dark green + # TODO: Do we default to light? + if check_if_light_or_dark() == "light": + COLOR_ERROR = '#F79494' # light red + COLOR_OK = '#A5F7C6' # light green + COLOR_BOOL_ON = '#63F731' # bright green + COLOR_BOOL_OFF = '#608060' # dark green + elif check_if_light_or_dark() == "dark": + COLOR_ERROR = "#A30000" # light red + COLOR_OK = "#2F4C00" # dark red + COLOR_BOOL_ON = "#3086C3" # bright blue + COLOR_BOOL_OFF = "#220070" # dark blue + else: + print(f"Unable to get color palette from os, got {check_if_light_or_dark()}") GLOBALS_DUMMY_ROW_TEXT = '' @@ -3705,6 +3723,7 @@ def handler(self, request_data): qapplication = QtWidgets.QApplication(sys.argv) qapplication.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus, False) app = RunManager() + logger.info(f'OS Theme is : {check_if_light_or_dark()}') splash.update_text('Starting remote server') remote_server = RemoteServer() splash.hide() From 57e288a1d20c7602131f00a9b85bcd196514d087 Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Wed, 3 Dec 2025 16:44:16 -0500 Subject: [PATCH 02/16] Fix color comment --- runmanager/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index b01d475..38c7489 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -679,8 +679,8 @@ class GroupTab(object): COLOR_BOOL_ON = '#63F731' # bright green COLOR_BOOL_OFF = '#608060' # dark green elif check_if_light_or_dark() == "dark": - COLOR_ERROR = "#A30000" # light red - COLOR_OK = "#2F4C00" # dark red + COLOR_ERROR = "#A30000" # red + COLOR_OK = "#2F4C00" # green COLOR_BOOL_ON = "#3086C3" # bright blue COLOR_BOOL_OFF = "#220070" # dark blue else: From 7cc2e4a72a24883053e14b18db4525005b8968f7 Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Mon, 8 Dec 2025 11:05:11 -0500 Subject: [PATCH 03/16] Use QPalette Accent for OS highlight, switch back to bright/dark green for bools, increase contrast between red and green for values --- runmanager/__main__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 38c7489..143bc0f 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -361,8 +361,7 @@ class ItemView(object): leftClicked = Signal(QtCore.QModelIndex) doubleLeftClicked = Signal(QtCore.QModelIndex) - COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue TODO: Smart way to access OS highlight - + COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue TODO: Find a way to *safely* delete this (horrible bug otherwise) def __init__(self, *args): super(ItemView, self).__init__(*args) self._pressed_index = None @@ -373,7 +372,8 @@ def __init__(self, *args): p.setColor( group, QtGui.QPalette.Highlight, - QtGui.QColor(self.COLOR_HIGHLIGHT)) + p.color(QtGui.QPalette.Accent) + ) p.setColor( group, QtGui.QPalette.HighlightedText, @@ -679,10 +679,10 @@ class GroupTab(object): COLOR_BOOL_ON = '#63F731' # bright green COLOR_BOOL_OFF = '#608060' # dark green elif check_if_light_or_dark() == "dark": - COLOR_ERROR = "#A30000" # red + COLOR_ERROR = "#BC0000" # red COLOR_OK = "#2F4C00" # green - COLOR_BOOL_ON = "#3086C3" # bright blue - COLOR_BOOL_OFF = "#220070" # dark blue + COLOR_BOOL_ON = "#34CF00" # bright green + COLOR_BOOL_OFF = "#003900" # dark green else: print(f"Unable to get color palette from os, got {check_if_light_or_dark()}") From 9d1c3c3d8e568673b557d4df1fd5ae3c122e324a Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Tue, 9 Dec 2025 09:24:47 -0500 Subject: [PATCH 04/16] Switch to lighter green, keep semitransparent blue --- runmanager/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 143bc0f..66ec6e6 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -361,7 +361,7 @@ class ItemView(object): leftClicked = Signal(QtCore.QModelIndex) doubleLeftClicked = Signal(QtCore.QModelIndex) - COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue TODO: Find a way to *safely* delete this (horrible bug otherwise) + COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue def __init__(self, *args): super(ItemView, self).__init__(*args) self._pressed_index = None @@ -681,7 +681,7 @@ class GroupTab(object): elif check_if_light_or_dark() == "dark": COLOR_ERROR = "#BC0000" # red COLOR_OK = "#2F4C00" # green - COLOR_BOOL_ON = "#34CF00" # bright green + COLOR_BOOL_ON = "#29A300" # bright green COLOR_BOOL_OFF = "#003900" # dark green else: print(f"Unable to get color palette from os, got {check_if_light_or_dark()}") From 908902d320835c60a6999069606843d072a68c45 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 8 Dec 2025 16:24:54 -0500 Subject: [PATCH 05/16] Remove redundant stylesheets from child widgets. --- runmanager/main.ui | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/runmanager/main.ui b/runmanager/main.ui index 4e44a1a..a71888b 100644 --- a/runmanager/main.ui +++ b/runmanager/main.ui @@ -449,23 +449,6 @@ subprocess Select folder ... - - QToolButton{ - border: none; - background: white; - padding: 2px; -} - -QToolButton:hover { - background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #f6f7fa, stop: 1 #dadbde); - } - - QToolButton:pressed { - background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - } - ... @@ -484,10 +467,7 @@ QToolButton:hover { false - QToolButton{ - background: rgb(224,224,224); - padding: 3px; -} + QFrame::StyledPanel @@ -534,21 +514,7 @@ QToolButton:hover { Select a file ... - QToolButton{ - border: none; - background: white; - padding: 2px; -} - -QToolButton:hover { - background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #f6f7fa, stop: 1 #dadbde); - } - - QToolButton:pressed { - background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #dadbde, stop: 1 #f6f7fa); - } + ... From b631723428f8ea373daf4284400403776d357676 Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Tue, 9 Dec 2025 09:43:18 -0500 Subject: [PATCH 06/16] Switch to palette highlight since accent does not exist in PyQt5 --- runmanager/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 66ec6e6..ee94982 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -372,7 +372,7 @@ def __init__(self, *args): p.setColor( group, QtGui.QPalette.Highlight, - p.color(QtGui.QPalette.Accent) + p.color(QtGui.QPalette.Highlight) ) p.setColor( group, From 040c6773820ba3020451504800267cb2967d7441 Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Thu, 11 Dec 2025 10:44:13 -0500 Subject: [PATCH 07/16] Simplify light or dark check, early return for pyqt5 since no color scheme support --- runmanager/__main__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index ee94982..a75cc45 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -70,6 +70,7 @@ ) from labscript_utils.qtwidgets.outputbox import OutputBox import qtutils.icons +from qtutils.qt import QT_ENV GLOBAL_MONOSPACE_FONT = "Consolas" if os.name == 'nt' else "Ubuntu Mono" @@ -100,13 +101,15 @@ def log_if_global(g, g_list, message): logger.info(message) def check_if_light_or_dark(): + + # pyqt5 defaults to light and doesn't have styleHints.colorScheme() + if QT_ENV.lower() == 'pyqt5': + return "light" style_hints = QtGui.QGuiApplication.styleHints() if style_hints.colorScheme() == Qt.ColorScheme.Dark: return "dark" - elif style_hints.colorScheme() == Qt.ColorScheme.Light: - return "light" else: - return "unknown" + return "light" def composite_colors(r0, g0, b0, a0, r1, g1, b1, a1): @@ -672,7 +675,6 @@ class GroupTab(object): GLOBALS_ROLE_SORT_DATA = QtCore.Qt.UserRole + 2 GLOBALS_ROLE_PREVIOUS_TEXT = QtCore.Qt.UserRole + 3 GLOBALS_ROLE_IS_BOOL = QtCore.Qt.UserRole + 4 - # TODO: Do we default to light? if check_if_light_or_dark() == "light": COLOR_ERROR = '#F79494' # light red COLOR_OK = '#A5F7C6' # light green From eda3295e99e57cc2d0ed374aeeda4a1421128e18 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Thu, 11 Dec 2025 17:13:42 -0500 Subject: [PATCH 08/16] Basic handling of OS theme change for PySide6 throughout GUI. Does not handle custom colors. --- runmanager/__main__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index a75cc45..5c80521 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -1357,6 +1357,17 @@ def paintEvent(self, event): self._previously_painted = True self.firstPaint.emit() return result + + 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 PoppedOutOutputBoxWindow(QtWidgets.QDialog): From 436cabba69337e6b5c31037acb1e65c1ab5ceab2 Mon Sep 17 00:00:00 2001 From: Jason Pruitt Date: Fri, 12 Dec 2025 09:47:59 -0500 Subject: [PATCH 09/16] Clean up imports, calls, and old conditional branch --- runmanager/__main__.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 5c80521..60a951a 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -46,9 +46,8 @@ import matplotlib matplotlib.use('Agg') -from qtutils.qt import QtCore, QtGui, QtWidgets +from qtutils.qt import QtCore, QtGui, QtWidgets, QT_ENV from qtutils.qt.QtCore import pyqtSignal as Signal -from qtutils.qt.QtCore import Qt splash.update_text('importing labscript suite modules') from labscript_utils.ls_zprocess import zmq_get, ProcessTree, ZMQServer @@ -70,7 +69,6 @@ ) from labscript_utils.qtwidgets.outputbox import OutputBox import qtutils.icons -from qtutils.qt import QT_ENV GLOBAL_MONOSPACE_FONT = "Consolas" if os.name == 'nt' else "Ubuntu Mono" @@ -106,7 +104,7 @@ def check_if_light_or_dark(): if QT_ENV.lower() == 'pyqt5': return "light" style_hints = QtGui.QGuiApplication.styleHints() - if style_hints.colorScheme() == Qt.ColorScheme.Dark: + if style_hints.colorScheme() == QtCore.Qt.ColorScheme.Dark: return "dark" else: return "light" @@ -675,18 +673,16 @@ class GroupTab(object): GLOBALS_ROLE_SORT_DATA = QtCore.Qt.UserRole + 2 GLOBALS_ROLE_PREVIOUS_TEXT = QtCore.Qt.UserRole + 3 GLOBALS_ROLE_IS_BOOL = QtCore.Qt.UserRole + 4 - if check_if_light_or_dark() == "light": - COLOR_ERROR = '#F79494' # light red - COLOR_OK = '#A5F7C6' # light green - COLOR_BOOL_ON = '#63F731' # bright green - COLOR_BOOL_OFF = '#608060' # dark green - elif check_if_light_or_dark() == "dark": + + COLOR_ERROR = '#F79494' # light red + COLOR_OK = '#A5F7C6' # light green + COLOR_BOOL_ON = '#63F731' # bright green + COLOR_BOOL_OFF = '#608060' # dark green + if check_if_light_or_dark() == "dark": COLOR_ERROR = "#BC0000" # red COLOR_OK = "#2F4C00" # green COLOR_BOOL_ON = "#29A300" # bright green COLOR_BOOL_OFF = "#003900" # dark green - else: - print(f"Unable to get color palette from os, got {check_if_light_or_dark()}") GLOBALS_DUMMY_ROW_TEXT = '' From 6a599e1dd452847c4ab0286503b307dd5fbc5597 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Fri, 12 Dec 2025 12:22:30 -0500 Subject: [PATCH 10/16] Ensure that global edit box uses same custom highlight Reverts using palette highlight --- runmanager/__main__.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 60a951a..1ed04fd 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -368,19 +368,19 @@ def __init__(self, *args): self._pressed_index = None self._double_click = False self.setAutoScroll(False) - p = self.palette() + palette = self.palette() for group in [QtGui.QPalette.Active, QtGui.QPalette.Inactive]: - p.setColor( + palette.setColor( group, QtGui.QPalette.Highlight, - p.color(QtGui.QPalette.Highlight) + QtGui.QColor(self.COLOR_HIGHLIGHT) ) - p.setColor( + palette.setColor( group, QtGui.QPalette.HighlightedText, - p.color(QtGui.QPalette.Active, QtGui.QPalette.WindowText) + palette.color(QtGui.QPalette.WindowText) ) - self.setPalette(p) + self.setPalette(palette) def mousePressEvent(self, event): result = super(ItemView, self).mousePressEvent(event) @@ -558,6 +558,7 @@ def data(self, index, role): class Editor(QtWidgets.QTextEdit): """Popup editor with word wrapping and automatic resizing.""" + COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue def __init__(self, parent): QtWidgets.QTextEdit.__init__(self, parent) self.setWordWrapMode(QtGui.QTextOption.WordWrap) @@ -566,6 +567,19 @@ def __init__(self, parent): self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.textChanged.connect(self.update_size) self.initial_height = None + palette = self.palette() + for group in [QtGui.QPalette.Active, QtGui.QPalette.Inactive]: + palette.setColor( + group, + QtGui.QPalette.Highlight, + QtGui.QColor(self.COLOR_HIGHLIGHT) + ) + palette.setColor( + group, + QtGui.QPalette.HighlightedText, + palette.color(QtGui.QPalette.WindowText) + ) + self.setPalette(palette) def update_size(self): if self.initial_height is not None: From 607bfa78539162dd5d5b7d4645b2b3f942355404 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Fri, 12 Dec 2025 12:23:06 -0500 Subject: [PATCH 11/16] Ensure all globals cells in new global row use custom highlight color --- runmanager/__main__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 1ed04fd..b147a77 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -810,7 +810,7 @@ def populate_model(self): # This lets later code know that this row does not correspond to an # actual global: dummy_delete_item.setData(True, self.GLOBALS_ROLE_IS_DUMMY_ROW) - dummy_delete_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_delete_item.setFlags(QtCore.Qt.ItemIsEnabled) dummy_delete_item.setToolTip('Click to add global') dummy_name_item = QtGui.QStandardItem(self.GLOBALS_DUMMY_ROW_TEXT) @@ -822,17 +822,17 @@ def populate_model(self): dummy_value_item = QtGui.QStandardItem() dummy_value_item.setData(True, self.GLOBALS_ROLE_IS_DUMMY_ROW) - dummy_value_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_value_item.setFlags(QtCore.Qt.ItemIsEnabled) dummy_value_item.setToolTip('Click to add global') dummy_units_item = QtGui.QStandardItem() dummy_units_item.setData(True, self.GLOBALS_ROLE_IS_DUMMY_ROW) - dummy_units_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_units_item.setFlags(QtCore.Qt.ItemIsEnabled) dummy_units_item.setToolTip('Click to add global') dummy_expansion_item = QtGui.QStandardItem() dummy_expansion_item.setData(True, self.GLOBALS_ROLE_IS_DUMMY_ROW) - dummy_expansion_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_expansion_item.setFlags(QtCore.Qt.ItemIsEnabled) dummy_expansion_item.setToolTip('Click to add global') self.globals_model.appendRow( @@ -2801,15 +2801,15 @@ def open_globals_file(self, globals_file): dummy_active_item = QtGui.QStandardItem() dummy_active_item.setData(True, self.GROUPS_ROLE_IS_DUMMY_ROW) - dummy_active_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_active_item.setFlags(QtCore.Qt.ItemIsEnabled) - dummy_delete_item = QtGui.QStandardItem() + dummy_delete_item = QtGui.QStandardItem('') dummy_delete_item.setData(True, self.GROUPS_ROLE_IS_DUMMY_ROW) - dummy_delete_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_delete_item.setFlags(QtCore.Qt.ItemIsEnabled) dummy_open_close_item = QtGui.QStandardItem() dummy_open_close_item.setData(True, self.GROUPS_ROLE_IS_DUMMY_ROW) - dummy_open_close_item.setFlags(QtCore.Qt.NoItemFlags) + dummy_open_close_item.setFlags(QtCore.Qt.ItemIsEnabled) # Not setting anything as the above items' sort role has the effect of # ensuring this row is always sorted to the end of the list, without From b8be64ba9f509aa2e206a4139495f08ece424641 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 15 Dec 2025 13:46:53 -0500 Subject: [PATCH 12/16] Ensure AlternatingColorModel gets correct palette colors no matter when background brush is asked for (ie after theme change). --- runmanager/__main__.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index b147a77..2e4fcc4 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -488,15 +488,6 @@ def __init__(self, view): # How much darker in each channel is the alternate base color compared # to the base color? self.view = view - palette = view.palette() - self.normal_color = palette.color(QtGui.QPalette.Base) - self.alternate_color = palette.color(QtGui.QPalette.AlternateBase) - r, g, b, a = self.normal_color.getRgb() - alt_r, alt_g, alt_b, alt_a = self.alternate_color.getRgb() - self.delta_r = alt_r - r - self.delta_g = alt_g - g - self.delta_b = alt_b - b - self.delta_a = alt_a - a # A cache, store brushes so we don't have to recalculate them. Is faster. self.bg_brushes = {} @@ -510,21 +501,31 @@ def get_bgbrush(self, normal_brush, alternate, selected): except KeyError: pass # Get the colour of the cell with alternate row shading: + normal_color = self.view.palette().color( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Base + ) + alternate_color = self.view.palette().color( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.AlternateBase + ) if normal_rgb is None: # No colour has been set. Use palette colours: if alternate: - bg_color = self.alternate_color + bg_color = alternate_color else: - bg_color = self.normal_color + bg_color = normal_color else: bg_color = normal_brush.color() if alternate: # Modify alternate rows: r, g, b, a = normal_rgb - alt_r = min(max(r + self.delta_r, 0), 255) - alt_g = min(max(g + self.delta_g, 0), 255) - alt_b = min(max(b + self.delta_b, 0), 255) - alt_a = min(max(a + self.delta_a, 0), 255) + nr, ng, nb, na = normal_color.getRgb() + ar, ag, ab, aa = alternate_color.getRgb() + alt_r = min(max(r + ar - nr, 0), 255) + alt_g = min(max(g + ag - ng, 0), 255) + alt_b = min(max(b + ab - nb, 0), 255) + alt_a = min(max(a + aa - na, 0), 255) bg_color = QtGui.QColor(alt_r, alt_g, alt_b, alt_a) # If parent is a TableView, we handle selection highlighting as part of the From 67eb5f19e08600e47a862abec4122b0990c8e365 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 15 Dec 2025 13:47:25 -0500 Subject: [PATCH 13/16] Ensure globals tableview colors get re-drawn --- runmanager/__main__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 2e4fcc4..5a5f6e8 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -1375,8 +1375,14 @@ def changeEvent(self, event): 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()) + widget.setStyleSheet(widget.styleSheet()) + + for tab in app.currently_open_groups.values(): + tab.globals_model.bg_brushes = {} # reset pre-calculated brushes + + # refresh globals model colors + app.globals_changed() return super().changeEvent(event) From c6331cc299735192f84c4030863b694947940820 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 15 Dec 2025 14:00:12 -0500 Subject: [PATCH 14/16] Update/add logging messages. --- runmanager/__main__.py | 55 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index 5a5f6e8..feffe35 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -32,6 +32,7 @@ import contextlib import subprocess import threading +import logging import ast import pprint import traceback @@ -96,7 +97,7 @@ def log_if_global(g, g_list, message): g_list = [] # add global options here if g in g_list: - logger.info(message) + logger.info(message) # uses global `logger` instance defined in __main__ def check_if_light_or_dark(): @@ -703,6 +704,8 @@ class GroupTab(object): def __init__(self, tabWidget, globals_file, group_name): + self.logger = setup_logging('lyse.groupTab') + self.tabWidget = tabWidget loader = UiLoader() @@ -745,6 +748,7 @@ def __init__(self, tabWidget, globals_file, group_name): # Populate the model with globals from the h5 file: self.populate_model() + self.logger.info(f'Initial population of {self.group_name}') # Set sensible column widths: for col in range(self.globals_model.columnCount()): if col != self.GLOBALS_COL_VALUE: @@ -793,7 +797,6 @@ def set_tab_icon(self, icon_string): else: icon = QtGui.QIcon() if self.tabWidget.tabIcon(index).cacheKey() != icon.cacheKey(): - logger.info('setting tab icon') self.tabWidget.setTabIcon(index, icon) def populate_model(self): @@ -843,7 +846,7 @@ def populate_model(self): self.ui.tableView_globals.sortByColumn(self.GLOBALS_COL_NAME, QtCore.Qt.AscendingOrder) def make_global_row(self, name, value='', units='', expansion=''): - logger.debug('%s:%s - make global row: %s ' % (self.globals_file, self.group_name, name)) + self.logger.debug('%s:%s - make global row: %s ' % (self.globals_file, self.group_name, name)) # We just set some data here, other stuff is set in # self.update_parse_indication after runmanager has a chance to parse # everything and get back to us about what that data should be. @@ -1077,7 +1080,7 @@ def do_model_sort(self): self.ui.tableView_globals.sortByColumn(sort_column, sort_order) def new_global(self, global_name): - logger.info('%s:%s - new global: %s', self.globals_file, self.group_name, global_name) + self.logger.info('%s:%s - new global: %s', self.globals_file, self.group_name, global_name) item = self.get_global_item_by_name(global_name, self.GLOBALS_COL_NAME, previous_name=self.GLOBALS_DUMMY_ROW_TEXT) try: @@ -1103,7 +1106,7 @@ def new_global(self, global_name): item.setText(self.GLOBALS_DUMMY_ROW_TEXT) def rename_global(self, previous_global_name, new_global_name): - logger.info('%s:%s - rename global: %s -> %s', + self.logger.info('%s:%s - rename global: %s -> %s', self.globals_file, self.group_name, previous_global_name, new_global_name) item = self.get_global_item_by_name(new_global_name, self.GLOBALS_COL_NAME, previous_name=previous_global_name) @@ -1131,7 +1134,7 @@ def rename_global(self, previous_global_name, new_global_name): scroll_view_to_row_if_current(self.ui.tableView_globals, item) def change_global_value(self, global_name, previous_value, new_value, interactive=True): - logger.info('%s:%s - change global value: %s = %s -> %s' % + self.logger.info('%s:%s - change global value: %s = %s -> %s' % (self.globals_file, self.group_name, global_name, previous_value, new_value)) item = self.get_global_item_by_name(global_name, self.GLOBALS_COL_VALUE) if not interactive: @@ -1184,7 +1187,7 @@ def complete_change_global_value(self, global_name, previous_value, new_value, i scroll_view_to_row_if_current(self.ui.tableView_globals, item) def change_global_units(self, global_name, previous_units, new_units): - logger.info('%s:%s - change units: %s = %s -> %s' % + self.logger.info('%s:%s - change units: %s = %s -> %s' % (self.globals_file, self.group_name, global_name, previous_units, new_units)) item = self.get_global_item_by_name(global_name, self.GLOBALS_COL_UNITS) try: @@ -1201,7 +1204,7 @@ def change_global_units(self, global_name, previous_units, new_units): scroll_view_to_row_if_current(self.ui.tableView_globals, item) def change_global_expansion(self, global_name, previous_expansion, new_expansion): - logger.info('%s:%s - change expansion: %s = %s -> %s' % + self.logger.info('%s:%s - change expansion: %s = %s -> %s' % (self.globals_file, self.group_name, global_name, previous_expansion, new_expansion)) item = self.get_global_item_by_name(global_name, self.GLOBALS_COL_EXPANSION) try: @@ -1229,7 +1232,7 @@ def check_for_boolean_values(self, item): name_item = self.globals_model.itemFromIndex(name_index) units_item = self.globals_model.itemFromIndex(units_index) global_name = name_item.text() - logger.debug('%s:%s - check for boolean values: %s' % + self.logger.debug('%s:%s - check for boolean values: %s' % (self.globals_file, self.group_name, global_name)) if value == 'True': units_item.setData(True, self.GLOBALS_ROLE_IS_BOOL) @@ -1270,7 +1273,7 @@ def globals_changed(self): app.globals_changed() def delete_global(self, global_name, confirm=True): - logger.info('%s:%s - delete global: %s' % + self.logger.info('%s:%s - delete global: %s' % (self.globals_file, self.group_name, global_name)) if confirm: if not question_dialog("Delete the global '%s'?" % global_name): @@ -1304,10 +1307,10 @@ def update_parse_indication(self, active_groups, sequence_globals, evaled_global # the new expansion type. with self.globals_model_item_changed_disconnected: if expansion_item.data(self.GLOBALS_ROLE_PREVIOUS_TEXT) != expansion: - # logger.info('expansion previous text set') + # self.logger.info('expansion previous text set') expansion_item.setData(expansion, self.GLOBALS_ROLE_PREVIOUS_TEXT) if expansion_item.data(self.GLOBALS_ROLE_SORT_DATA) != expansion: - # logger.info('sort data role set') + # self.logger.info('sort data role set') expansion_item.setData(expansion, self.GLOBALS_ROLE_SORT_DATA) # The next line will now trigger item_changed, but it will not # be detected as an actual change to the expansion type, @@ -1326,11 +1329,11 @@ def update_parse_indication(self, active_groups, sequence_globals, evaled_global if value_item.background().color().name().lower() != self.COLOR_OK.lower(): value_item.setBackground(QtGui.QBrush(QtGui.QColor(self.COLOR_OK))) if not value_item.icon().isNull(): - # logger.info('clearing icon') + # self.logger.info('clearing icon') value_item.setData(None, QtCore.Qt.DecorationRole) tooltip = repr(value) if value_item.toolTip() != tooltip: - # logger.info('tooltip_changed') + # self.logger.info('tooltip_changed') value_item.setToolTip(tooltip) if self.tab_contains_errors: self.set_tab_icon(':qtutils/fugue/exclamation') @@ -1413,6 +1416,7 @@ class RunManager(object): GROUPS_DUMMY_ROW_TEXT = '' def __init__(self): + self.logger = logging.getLogger('runmanager') splash.update_text('loading graphical interface') loader = UiLoader() loader.registerCustomWidget(FingerTabWidget) @@ -1444,6 +1448,7 @@ def __init__(self): self.setup_axes_tab() self.setup_groups_tab() self.connect_signals() + self.logger.info('UI loaded') # The last location from which a labscript file was selected, defaults # to labscriptlib: @@ -1497,6 +1502,7 @@ def __init__(self): os.path.join(runmanager_dir, 'batch_compiler.py'), output_redirection_port=self.output_box.port, ) + self.logger.info('compiler subprocess started') # Is blank until a labscript file is selected: self.previous_default_output_folder = '' @@ -1549,6 +1555,7 @@ def setup_config(self): ], } self.exp_config = LabConfig(required_params = required_config_params) + self.logger.info('LabConfig loaded') def setup_axes_tab(self): self.axes_model = QtGui.QStandardItemModel() @@ -1581,7 +1588,7 @@ def setup_axes_tab(self): # setup header widths self.ui.treeView_axes.header().setStretchLastSection(False) self.ui.treeView_axes.header().setSectionResizeMode(self.AXES_COL_NAME, QtWidgets.QHeaderView.Stretch) - + def setup_groups_tab(self): self.groups_model = QtGui.QStandardItemModel() self.groups_model.setHorizontalHeaderLabels(['File/group name', 'Active', 'Delete', 'Open/Close']) @@ -1703,6 +1710,7 @@ def connect_signals(self): QtGui.QShortcut('ctrl+W', self.ui, self.close_current_tab) QtGui.QShortcut('ctrl+Tab', self.ui, lambda: self.switch_tabs(+1)) QtGui.QShortcut('ctrl+shift+Tab', self.ui, lambda: self.switch_tabs(-1)) + self.logger.info('Signals connected') def on_close_event(self): save_data = self.get_save_data() @@ -1837,7 +1845,7 @@ def on_shot_output_folder_text_changed(self, text): self.ui.lineEdit_shot_output_folder.setToolTip(text) def on_engage_clicked(self): - logger.info('Engage') + self.logger.info('Engage') try: send_to_BLACS = self.ui.checkBox_run_shots.isChecked() send_to_runviewer = self.ui.checkBox_view_shots.isChecked() @@ -1850,7 +1858,7 @@ def on_engage_clicked(self): if not output_folder: raise Exception('Error: No output folder selected') BLACS_host = self.ui.lineEdit_BLACS_hostname.text() - logger.info('Parsing globals...') + self.logger.info('Parsing globals...') active_groups = self.get_active_groups() # Get ordering of expansion globals expansion_order = {} @@ -1864,14 +1872,14 @@ def on_engage_clicked(self): sequenceglobals, shots, evaled_globals, global_hierarchy, expansions = self.parse_globals(active_groups, expansion_order=expansion_order) except Exception as e: raise Exception('Error parsing globals:\n%s\nCompilation aborted.' % str(e)) - logger.info('Making h5 files') + self.logger.info('Making h5 files') labscript_file, run_files = self.make_h5_files( labscript_file, output_folder, sequenceglobals, shots, shuffle) self.ui.pushButton_abort.setEnabled(True) self.compile_queue.put([labscript_file, run_files, send_to_BLACS, BLACS_host, send_to_runviewer]) except Exception as e: self.output_box.output('%s\n\n' % str(e), red=True) - logger.info('end engage') + self.logger.info('end engage') def on_abort_clicked(self): self.compilation_aborted.set() @@ -2516,7 +2524,7 @@ def rollover_shot_output_folder(self): self.check_output_folder_update() except Exception as e: # Don't stop the thread. - logger.exception("error checking default output folder") + self.logger.exception("error checking default output folder") @inmain_decorator() def check_output_folder_update(self): @@ -2653,6 +2661,7 @@ def preparse_globals(self): break self.update_tabs_parsing_indication(active_groups, sequence_globals, evaled_globals, self.n_shots) self.update_axes_tab(expansions, dimensions) + self.logger.info('Globals parsed') def preparse_globals_loop(self): """Runs in a thread, waiting on a threading.Event that tells us when @@ -2677,6 +2686,7 @@ def preparse_globals_loop(self): except queue.Empty: break # Do some work: + self.logger.info(f'Pre-parsing globals with {n_requests:d} requests') self.preparse_globals() # Tell any callers calling preparse_globals_required.join() that we are # done with their request: @@ -3508,7 +3518,7 @@ def make_h5_files(self, labscript_file, output_folder, sequence_globals, shots, filename_prefix, shuffle, ) - logger.debug(run_files) + self.logger.debug(run_files) return labscript_file, run_files def send_to_BLACS(self, run_file, BLACS_hostname): @@ -3533,7 +3543,7 @@ def send_to_runviewer(self, run_file): if 'hello' not in response: raise Exception(response) except Exception as e: - logger.info('runviewer not running, attempting to start...') + self.logger.info('runviewer not running, attempting to start...') # Runviewer not running, start it: if os.name == 'nt': creationflags = 0x00000008 # DETACHED_PROCESS from the win32 API @@ -3753,7 +3763,6 @@ def handler(self, request_data): qapplication = QtWidgets.QApplication(sys.argv) qapplication.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus, False) app = RunManager() - logger.info(f'OS Theme is : {check_if_light_or_dark()}') splash.update_text('Starting remote server') remote_server = RemoteServer() splash.hide() From 33a79db8b501df4b752e9200d2806ddd69f08268 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 15 Dec 2025 14:52:58 -0500 Subject: [PATCH 15/16] Switch color handling to a singleton for easy, unified color handling between components --- runmanager/__main__.py | 99 ++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 32 deletions(-) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index feffe35..edb1eba 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -99,17 +99,6 @@ def log_if_global(g, g_list, message): if g in g_list: logger.info(message) # uses global `logger` instance defined in __main__ -def check_if_light_or_dark(): - - # pyqt5 defaults to light and doesn't have styleHints.colorScheme() - if QT_ENV.lower() == 'pyqt5': - return "light" - style_hints = QtGui.QGuiApplication.styleHints() - if style_hints.colorScheme() == QtCore.Qt.ColorScheme.Dark: - return "dark" - else: - return "light" - def composite_colors(r0, g0, b0, a0, r1, g1, b1, a1): """composite a second colour over a first with given alpha values and return the @@ -363,7 +352,7 @@ class ItemView(object): leftClicked = Signal(QtCore.QModelIndex) doubleLeftClicked = Signal(QtCore.QModelIndex) - COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue + def __init__(self, *args): super(ItemView, self).__init__(*args) self._pressed_index = None @@ -374,7 +363,7 @@ def __init__(self, *args): palette.setColor( group, QtGui.QPalette.Highlight, - QtGui.QColor(self.COLOR_HIGHLIGHT) + QtGui.QColor(RunmanagerColors().COLOR_HIGHLIGHT) ) palette.setColor( group, @@ -533,7 +522,7 @@ def get_bgbrush(self, normal_brush, alternate, selected): # background colours: if selected and isinstance(self.view, QtWidgets.QTableView): # Overlay highlight colour: - r_s, g_s, b_s, a_s = QtGui.QColor(ItemView.COLOR_HIGHLIGHT).getRgb() + r_s, g_s, b_s, a_s = QtGui.QColor(RunmanagerColors().COLOR_HIGHLIGHT).getRgb() r_0, g_0, b_0, a_0 = bg_color.getRgb() rgb = composite_colors(r_0, g_0, b_0, a_0, r_s, g_s, b_s, a_s) bg_color = QtGui.QColor(*rgb) @@ -548,7 +537,7 @@ def data(self, index, role): making the alternate colours visible even when custom colors have been set - the same shading will be applied to the custom colours. Only really looks sensible when the normal and alternate colors are similar. Also applies selection - highlight colour (using ItemView.COLOR_HIGHLIGHT), similarly with alternate-row + highlight colour (using RunmanagerColors().COLOR_HIGHLIGHT), similarly with alternate-row shading, for the case of a QTableView.""" if role == QtCore.Qt.BackgroundRole: normal_brush = QtGui.QStandardItemModel.data(self, index, QtCore.Qt.BackgroundRole) @@ -560,7 +549,7 @@ def data(self, index, role): class Editor(QtWidgets.QTextEdit): """Popup editor with word wrapping and automatic resizing.""" - COLOR_HIGHLIGHT = "#40308CC6" # Semitransparent blue + def __init__(self, parent): QtWidgets.QTextEdit.__init__(self, parent) self.setWordWrapMode(QtGui.QTextOption.WordWrap) @@ -574,7 +563,7 @@ def __init__(self, parent): palette.setColor( group, QtGui.QPalette.Highlight, - QtGui.QColor(self.COLOR_HIGHLIGHT) + QtGui.QColor(RunmanagerColors().COLOR_HIGHLIGHT) ) palette.setColor( group, @@ -677,6 +666,57 @@ def setEditorData(self, editor, index): def setModelData(self, editor, model, index): model.setData(index, editor.toPlainText()) +class RunmanagerColors(object): + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + + return cls._instance + + def __init__(self): + + if not hasattr(self, '_initialized'): + self.logger = logging.getLogger('runmanager') + + # init colors + self.update_colors_from_scheme() + + # common color definitions + self.COLOR_HIGHLIGHT = "#40308CC6" # semitransparent blue + + self._initialized = True + + def check_if_light(self): + + # pyqt5 defaults to light and doesn't have styleHints.colorScheme() + if QT_ENV.lower() == 'pyqt5': + return True + + style_hints = QtGui.QGuiApplication.styleHints() + if style_hints.colorScheme() == QtCore.Qt.ColorScheme.Dark: + return False + else: + return True + + def update_colors_from_scheme(self): + + if self.check_if_light(): + self.logger.info('Setting custom light theme colors') + # use light mode colors for GroupTabs + self.COLOR_ERROR = '#F79494' # light red + self.COLOR_OK = '#A5F7C6' # light green + self.COLOR_BOOL_ON = '#63F731' # bright green + self.COLOR_BOOL_OFF = '#608060' # dark green + else: + self.logger.info('Setting custom dark theme colors') + # use dark mode colors for GroupTabs + self.COLOR_ERROR = "#BC0000" # red + self.COLOR_OK = "#2F4C00" # green + self.COLOR_BOOL_ON = "#29A300" # bright green + self.COLOR_BOOL_OFF = "#003900" # dark green + class GroupTab(object): GLOBALS_COL_DELETE = 0 @@ -689,16 +729,6 @@ class GroupTab(object): GLOBALS_ROLE_SORT_DATA = QtCore.Qt.UserRole + 2 GLOBALS_ROLE_PREVIOUS_TEXT = QtCore.Qt.UserRole + 3 GLOBALS_ROLE_IS_BOOL = QtCore.Qt.UserRole + 4 - - COLOR_ERROR = '#F79494' # light red - COLOR_OK = '#A5F7C6' # light green - COLOR_BOOL_ON = '#63F731' # bright green - COLOR_BOOL_OFF = '#608060' # dark green - if check_if_light_or_dark() == "dark": - COLOR_ERROR = "#BC0000" # red - COLOR_OK = "#2F4C00" # green - COLOR_BOOL_ON = "#29A300" # bright green - COLOR_BOOL_OFF = "#003900" # dark green GLOBALS_DUMMY_ROW_TEXT = '' @@ -707,6 +737,7 @@ def __init__(self, tabWidget, globals_file, group_name): self.logger = setup_logging('lyse.groupTab') self.tabWidget = tabWidget + self.colorConfig = RunmanagerColors() loader = UiLoader() loader.registerCustomWidget(TableView) @@ -1240,14 +1271,14 @@ def check_for_boolean_values(self, item): units_item.setData('!1', self.GLOBALS_ROLE_SORT_DATA) units_item.setEditable(False) units_item.setCheckState(QtCore.Qt.Checked) - units_item.setBackground(QtGui.QBrush(QtGui.QColor(self.COLOR_BOOL_ON))) + units_item.setBackground(QtGui.QBrush(QtGui.QColor(self.colorConfig.COLOR_BOOL_ON))) elif value == 'False': units_item.setData(True, self.GLOBALS_ROLE_IS_BOOL) units_item.setText('Bool') units_item.setData('!0', self.GLOBALS_ROLE_SORT_DATA) units_item.setEditable(False) units_item.setCheckState(QtCore.Qt.Unchecked) - units_item.setBackground(QtGui.QBrush(QtGui.QColor(self.COLOR_BOOL_OFF))) + units_item.setBackground(QtGui.QBrush(QtGui.QColor(self.colorConfig.COLOR_BOOL_OFF))) else: was_bool = units_item.data(self.GLOBALS_ROLE_IS_BOOL) units_item.setData(False, self.GLOBALS_ROLE_IS_BOOL) @@ -1321,13 +1352,13 @@ def update_parse_indication(self, active_groups, sequence_globals, evaled_global # occur in the callback. expansion_item.setText(expansion) if isinstance(value, Exception): - value_item.setBackground(QtGui.QBrush(QtGui.QColor(self.COLOR_ERROR))) + value_item.setBackground(QtGui.QBrush(QtGui.QColor(self.colorConfig.COLOR_ERROR))) value_item.setIcon(QtGui.QIcon(':qtutils/fugue/exclamation')) tooltip = '%s: %s' % (value.__class__.__name__, str(value)) self.tab_contains_errors = True else: - if value_item.background().color().name().lower() != self.COLOR_OK.lower(): - value_item.setBackground(QtGui.QBrush(QtGui.QColor(self.COLOR_OK))) + if value_item.background().color().name().lower() != self.colorConfig.COLOR_OK.lower(): + value_item.setBackground(QtGui.QBrush(QtGui.QColor(self.colorConfig.COLOR_OK))) if not value_item.icon().isNull(): # self.logger.info('clearing icon') value_item.setData(None, QtCore.Qt.DecorationRole) @@ -1358,6 +1389,7 @@ class RunmanagerMainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): QtWidgets.QMainWindow.__init__(self, *args, **kwargs) self._previously_painted = False + self.logger = logging.getLogger('runmanager') def closeEvent(self, event): if app.on_close_event(): @@ -1376,6 +1408,9 @@ def changeEvent(self, event): # theme update only for PySide6 if QT_ENV == 'PySide6' and event.type() == QtCore.QEvent.Type.ThemeChange: + self.logger.info('Theme change event') + # update group tab color themes + RunmanagerColors().update_colors_from_scheme() for widget in self.findChildren(QtWidgets.QWidget): # Complex widgets, like TreeView and TableView require triggering styleSheet and palette updates widget.setPalette(widget.palette()) From 4661784d2efc8fc387456d5975ebe5c939dfbf8f Mon Sep 17 00:00:00 2001 From: David Meyer Date: Mon, 15 Dec 2025 14:53:18 -0500 Subject: [PATCH 16/16] Ensure boolean color get updated on theme change --- runmanager/__main__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runmanager/__main__.py b/runmanager/__main__.py index edb1eba..11fcffb 100644 --- a/runmanager/__main__.py +++ b/runmanager/__main__.py @@ -1294,6 +1294,13 @@ def check_for_boolean_values(self, item): self.ui.tableView_globals.setCurrentIndex(units_item.index()) self.ui.tableView_globals.edit(units_item.index()) + def redraw_boolean_values(self): + """Called during theme changes to ensure boolean values get repainted with new colors""" + + for r in range(self.globals_model.rowCount()-1): # don't parse add-global row + item = self.globals_model.item(r, self.GLOBALS_COL_VALUE) + self.check_for_boolean_values(item) + def globals_changed(self): """Called whenever something about a global has changed. call app.globals_changed to inform the main application that it needs to @@ -1418,6 +1425,7 @@ def changeEvent(self, event): for tab in app.currently_open_groups.values(): tab.globals_model.bg_brushes = {} # reset pre-calculated brushes + tab.redraw_boolean_values() # refresh globals model colors app.globals_changed()