From 2bccb5485483c0682190cd87a2cef9076b179756 Mon Sep 17 00:00:00 2001 From: Florian Agbuya Date: Tue, 8 Oct 2024 12:23:23 +0800 Subject: [PATCH] gui/experiments: add custom color themes for experiment windows Signed-off-by: Florian Agbuya --- artiq/dashboard/experiments.py | 86 +++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 4be556d79..3c35b3eb7 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -28,6 +28,7 @@ class _ArgumentEditor(EntryTreeWidget): def __init__(self, manager, dock, expurl): self.manager = manager self.expurl = expurl + self.dock = dock EntryTreeWidget.__init__(self) @@ -78,6 +79,25 @@ async def _recompute_argument(self, name): argument["desc"] = procdesc argument["state"] = state self.update_argument(name, argument) + self.reapply_theme() + + def reapply_theme(self): + palette, color = self.dock.get_current_theme() + self.apply_theme(palette, color) + + def apply_theme(self, palette, color): + self.setPalette(palette) + for child in self.findChildren(QtWidgets.QWidget): + child.setPalette(palette) + child.setAutoFillBackground(True) + for i in range(self.topLevelItemCount()): + self._set_item_color(self.topLevelItem(i), color) + + def _set_item_color(self, item, color): + for col in range(item.columnCount()): + item.setBackground(col, QtGui.QBrush() if color is None else QtGui.QColor(color)) + for child_index in range(item.childCount()): + self._set_item_color(item.child(child_index), color) # Hooks that allow user-supplied argument editors to react to imminent user # actions. Here, we always keep the manager-stored submission arguments @@ -303,14 +323,66 @@ async def _recompute_arguments_task(self, overrides=dict()): self.argeditor = editor_class(self.manager, self, self.expurl) self.layout.addWidget(self.argeditor, 0, 0, 1, 5) self.argeditor.restore_state(argeditor_state) + self.argeditor.reapply_theme() def contextMenuEvent(self, event): menu = QtWidgets.QMenu(self) + select_theme = menu.addAction("Select color theme") + reset_theme = menu.addAction("Reset to default theme") + menu.addSeparator() reset_sched = menu.addAction("Reset scheduler settings") action = menu.exec(self.mapToGlobal(event.pos())) - if action == reset_sched: + if action == select_theme: + self.select_theme() + elif action == reset_theme: + self.reset_theme() + elif action == reset_sched: asyncio.ensure_future(self._recompute_sched_options_task()) + def select_theme(self): + color = QtWidgets.QColorDialog.getColor() + if color.isValid(): + self.set_theme(color.name()) + self.manager.set_theme(self.expurl, color.name()) + + def set_theme(self, color): + palette = self.modify_theme_palette(color) + self.setPalette(palette) + self.argeditor.apply_theme(palette, color) + + def modify_theme_palette(self, color): + if color is None: + return QtWidgets.QApplication.palette() + palette = self.palette() + qcolor = QtGui.QColor(color) + palette.setColor(QtGui.QPalette.ColorRole.Window, qcolor) + palette.setColor(QtGui.QPalette.ColorRole.Highlight, qcolor) + palette.setColor(QtGui.QPalette.ColorRole.Base, qcolor) + palette.setColor(QtGui.QPalette.ColorRole.Button, qcolor) + luminance = (0.299 * qcolor.red() + 0.587 * qcolor.green() + 0.114 * qcolor.blue()) / 255 + text_color = QtGui.QColor(0, 0, 0) if luminance > 0.5 else QtGui.QColor(255, 255, 255) + palette.setColor(QtGui.QPalette.ColorRole.WindowText, text_color) + palette.setColor(QtGui.QPalette.ColorRole.Text, text_color) + palette.setColor(QtGui.QPalette.ColorRole.ButtonText, text_color) + palette.setColor(QtGui.QPalette.ColorRole.PlaceholderText, text_color) + return palette + + def get_current_theme(self): + color = self.manager.get_theme(self.expurl) + palette = self.modify_theme_palette(color) + return palette, color + + def apply_theme(self): + color = self.manager.get_theme(self.expurl) + if color: + self.set_theme(color) + else: + self.set_theme(None) + + def reset_theme(self): + self.set_theme(None) + self.manager.set_theme(self.expurl, None) + async def _recompute_sched_options_task(self): try: expdesc, _ = await self.manager.compute_expdesc(self.expurl) @@ -457,6 +529,7 @@ def __init__(self, main_window, dataset_sub, self.submission_options = dict() self.submission_arguments = dict() self.argument_ui_names = dict() + self.themes = dict() self.datasets = dict() dataset_sub.add_setmodel_callback(self.set_dataset_model) @@ -483,6 +556,12 @@ def set_explist_model(self, model): def set_schedule_model(self, model): self.schedule = model.backing_store + def get_theme(self, expurl): + return self.themes.get(expurl) + + def set_theme(self, expurl, color): + self.themes[expurl] = color + def resolve_expurl(self, expurl): if expurl[:5] == "repo:": expinfo = self.explist[expurl[5:]] @@ -592,6 +671,7 @@ def open_experiment(self, expurl): self.open_experiments[expurl] = dock dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) self.main_window.centralWidget().addSubWindow(dock) + dock.apply_theme() dock.show() dock.sigClosed.connect(partial(self.on_dock_closed, expurl)) if expurl in self.dock_states: @@ -708,7 +788,8 @@ def save_state(self): "arguments": self.submission_arguments, "docks": self.dock_states, "argument_uis": self.argument_ui_names, - "open_docks": set(self.open_experiments.keys()) + "open_docks": set(self.open_experiments.keys()), + "themes": self.themes } def restore_state(self, state): @@ -719,6 +800,7 @@ def restore_state(self, state): self.submission_options = state["options"] self.submission_arguments = state["arguments"] self.argument_ui_names = state.get("argument_uis", {}) + self.themes = state.get("themes", {}) for expurl in state["open_docks"]: self.open_experiment(expurl)