From c650d6f6935fa09ed5792413de3f2960fecd9f14 Mon Sep 17 00:00:00 2001 From: Robier Al Kaadi <robal695@student.liu.se> Date: Wed, 17 Jul 2024 14:08:30 +0200 Subject: [PATCH] Solve Preference dialogue for scheduler GUI --- b_asic/gui_utils/color_button.py | 25 +- b_asic/scheduler_gui/_preferences.py | 66 ++- b_asic/scheduler_gui/main_window.py | 608 +++++++++++++++++++++++- b_asic/scheduler_gui/operation_item.py | 78 ++- b_asic/scheduler_gui/scheduler_event.py | 13 +- b_asic/scheduler_gui/scheduler_item.py | 21 + b_asic/scheduler_gui/signal_item.py | 24 +- b_asic/scheduler_gui/ui_main_window.py | 26 +- 8 files changed, 812 insertions(+), 49 deletions(-) diff --git a/b_asic/gui_utils/color_button.py b/b_asic/gui_utils/color_button.py index 725ddda1..63a0ed49 100644 --- a/b_asic/gui_utils/color_button.py +++ b/b_asic/gui_utils/color_button.py @@ -1,9 +1,10 @@ """ Qt button for use in preference dialogs, selecting color. """ + from qtpy.QtCore import Qt, Signal from qtpy.QtGui import QColor -from qtpy.QtWidgets import QColorDialog, QPushButton +from qtpy.QtWidgets import QPushButton class ColorButton(QPushButton): @@ -25,7 +26,7 @@ class ColorButton(QPushButton): self._color = None self._default = color - self.pressed.connect(self.pick_color) + # self.pressed.connect(self.pick_color) # Set the initial/default state. self.set_color(self._default) @@ -37,23 +38,27 @@ class ColorButton(QPushButton): self._color_changed.emit(color) if self._color: - self.setStyleSheet("background-color: %s;" % self._color) + self.setStyleSheet(f"background-color: {self._color.name()};") else: self.setStyleSheet("") + def set_text_color(self, color: QColor): + """Set text color.""" + self.setStyleSheet(f"color: {color.name()};") + @property def color(self): """Current color.""" return self._color - def pick_color(self): - """Show color-picker dialog to select color.""" - dlg = QColorDialog(self) - if self._color: - dlg.setCurrentColor(self._color) + # def pick_color(self): + # """Show color-picker dialog to select color.""" + # dlg = QColorDialog(self) + # if self._color: + # dlg.setCurrentColor(self._color) - if dlg.exec_(): - self.set_color(dlg.currentColor()) + # if dlg.exec_(): + # self.set_color(dlg.currentColor()) def mousePressEvent(self, e): if e.button() == Qt.RightButton: diff --git a/b_asic/scheduler_gui/_preferences.py b/b_asic/scheduler_gui/_preferences.py index b0cbcf70..f2da14ab 100644 --- a/b_asic/scheduler_gui/_preferences.py +++ b/b_asic/scheduler_gui/_preferences.py @@ -1,4 +1,4 @@ -from qtpy.QtGui import QColor +from qtpy.QtGui import QColor, QFont from b_asic._preferences import EXECUTION_TIME_COLOR, LATENCY_COLOR, SIGNAL_COLOR @@ -18,3 +18,67 @@ OPERATION_HEIGHT = 0.75 OPERATION_GAP = 1 - OPERATION_HEIGHT # TODO: For now, should really fix the bug SCHEDULE_INDENT = 0.2 +DEFAULT_FONT = QFont("Times", 12.0) +DEFAULT_FONT_COLOR = QColor(*SIGNAL_COLOR) + + +class ColorDataType: + def __init__( + self, + DEFAULT: QColor, + current_color: QColor = SIGNAL_INACTIVE, + changed: bool = False, + name: str = '', + ): + self.current_color = current_color + self.DEFAULT = DEFAULT + self.changed = changed + self.name = name + + +Latency_Color = ColorDataType( + current_color=OPERATION_LATENCY_INACTIVE, + DEFAULT=OPERATION_LATENCY_INACTIVE, + name='Latency Color', +) +Execution_Time_Color = ColorDataType( + current_color=OPERATION_EXECUTION_TIME_ACTIVE, + DEFAULT=OPERATION_EXECUTION_TIME_ACTIVE, + name='Execution Time Color', +) +Signal_Warning_Color = ColorDataType( + current_color=SIGNAL_WARNING, DEFAULT=SIGNAL_WARNING, name='Warning Color' +) +Signal_Color = ColorDataType( + current_color=SIGNAL_INACTIVE, DEFAULT=SIGNAL_INACTIVE, name='Signal Color' +) +Active_Color = ColorDataType( + current_color=SIGNAL_ACTIVE, DEFAULT=SIGNAL_ACTIVE, name='Active Color' +) + + +class FontDataType: + def __init__( + self, + current_font: QFont, + DEFAULT: QFont = DEFAULT_FONT, + DEFAULT_COLOR: QColor = DEFAULT_FONT_COLOR, + color: QColor = DEFAULT_FONT_COLOR, + size: int = 12, + italic: bool = False, + bold: bool = False, + changed: bool = False, + ): + self.current_font = current_font + self.DEFAULT = DEFAULT + self.DEFAULT_COLOR = DEFAULT_COLOR + self.size = size + self.color = color + self.italic = italic + self.bold = bold + self.changed = changed + + +Font = FontDataType( + current_font=DEFAULT_FONT, DEFAULT=DEFAULT_FONT, DEFAULT_COLOR=DEFAULT_FONT_COLOR +) diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py index 7a0c4114..8ba117c3 100644 --- a/b_asic/scheduler_gui/main_window.py +++ b/b_asic/scheduler_gui/main_window.py @@ -18,6 +18,7 @@ from typing import TYPE_CHECKING, Deque, List, Optional, cast, overload # Qt/qtpy import qtpy +import qtpy.QtCore # QGraphics and QPainter imports from qtpy.QtCore import ( @@ -30,19 +31,28 @@ from qtpy.QtCore import ( Qt, Slot, ) -from qtpy.QtGui import QCloseEvent +from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator from qtpy.QtWidgets import ( QAbstractButton, QAction, QApplication, QCheckBox, + QColorDialog, + QDialog, + QDialogButtonBox, QFileDialog, + QFontDialog, QGraphicsItemGroup, QGraphicsScene, + QGroupBox, + QHBoxLayout, QInputDialog, + QLabel, + QLineEdit, QMainWindow, QMessageBox, QTableWidgetItem, + QVBoxLayout, ) # B-ASIC @@ -50,9 +60,19 @@ import b_asic.scheduler_gui.logger as logger from b_asic._version import __version__ from b_asic.graph_component import GraphComponent, GraphID from b_asic.gui_utils.about_window import AboutWindow +from b_asic.gui_utils.color_button import ColorButton from b_asic.gui_utils.icons import get_icon from b_asic.gui_utils.mpl_window import MPLWindow from b_asic.schedule import Schedule +from b_asic.scheduler_gui._preferences import ( + Active_Color, + ColorDataType, + Execution_Time_Color, + Font, + Latency_Color, + Signal_Color, + Signal_Warning_Color, +) from b_asic.scheduler_gui.axes_item import AxesItem from b_asic.scheduler_gui.operation_item import OperationItem from b_asic.scheduler_gui.scheduler_item import SchedulerItem @@ -106,6 +126,8 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): _splitter_pos: int _splitter_min: int _zoom: float + _color_per_type: dict[str, QColor] = dict() + converted_colorPerType: dict[str, str] = dict() def __init__(self): """Initialize Scheduler-GUI.""" @@ -125,6 +147,8 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._execution_time_for_variables = None self._execution_time_plot_dialogs = defaultdict(lambda: None) self._ports_accesses_for_storage = None + self._color_changed_perType = False + self.changed_operation_colors: dict[str, QColor] = dict() # Recent files self._max_recent_files = 4 @@ -426,6 +450,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self.action_view_variables.setEnabled(False) self.action_view_port_accesses.setEnabled(False) self.menu_view_execution_times.setEnabled(False) + self.menu_Pref.setEnabled(False) @Slot() def save(self) -> None: @@ -678,6 +703,8 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._graph._signals.execution_time_plot.connect(self._execution_time_plot) self.info_table_fill_schedule(self._schedule) self._update_operation_types() + self.menu_Pref.setEnabled(True) + self.load_preferences() self.action_view_variables.setEnabled(True) self.action_view_port_accesses.setEnabled(True) self.update_statusbar(self.tr("Schedule loaded successfully")) @@ -713,6 +740,31 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): settings.setValue("scheduler/splitter/state", self.splitter.saveState()) settings.setValue("scheduler/splitter/pos", self.splitter.sizes()[1]) + settings.beginGroup("scheduler/preferences") + settings.setValue("font", Font.current_font.toString()) + settings.setValue("fontSize", Font.size) + settings.setValue("fontColor", Font.color) + settings.setValue("fontBold", Font.current_font.bold()) + settings.setValue("fontItalic", Font.current_font.italic()) + settings.setValue("fontChanged", Font.changed) + + settings.setValue(Signal_Color.name, Signal_Color.current_color.name()) + settings.setValue(Active_Color.name, Active_Color.current_color.name()) + settings.setValue( + Signal_Warning_Color.name, Signal_Warning_Color.current_color.name() + ) + settings.setValue( + Execution_Time_Color.name, Execution_Time_Color.current_color.name() + ) + + settings.setValue(f"{Signal_Color.name}_changed", Signal_Color.changed) + settings.setValue(f"{Active_Color.name}_changed", Active_Color.changed) + settings.setValue( + f"{Signal_Warning_Color.name}_changed", Signal_Warning_Color.changed + ) + self.Save_colortype() + settings.sync() + if settings.isWritable(): log.debug(f"Settings written to '{settings.fileName()}'.") else: @@ -738,6 +790,78 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): settings.value("scheduler/hide_exit_dialog", False, bool) ) + settings.beginGroup("scheduler/preferences") + Font.current_font = QFont( + settings.value("font", defaultValue=Font.DEFAULT.toString(), type=str) + ) + Font.size = settings.value( + "fontSize", defaultValue=Font.DEFAULT.pointSizeF(), type=int + ) + Font.color = QColor( + settings.value("fontColor", defaultValue=Font.DEFAULT_COLOR, type=str) + ) + Font.bold = settings.value( + "fontBold", defaultValue=Font.DEFAULT.bold(), type=bool + ) + Font.italic = settings.value( + "fontItalic", defaultValue=Font.DEFAULT.italic(), type=bool + ) + Font.changed = settings.value("fontChanged", Font.changed, bool) + + Signal_Color.current_color = QColor( + settings.value( + "Signal Color", defaultValue=Signal_Color.DEFAULT.name(), type=str + ) + ) + Active_Color.current_color = QColor( + settings.value( + "Active Color", defaultValue=Active_Color.DEFAULT.name(), type=str + ) + ) + Signal_Warning_Color.current_color = QColor( + settings.value( + "Warning Color", + defaultValue=Signal_Warning_Color.DEFAULT.name(), + type=str, + ) + ) + Latency_Color.current_color = QColor( + settings.value( + "Latency Color", defaultValue=Latency_Color.DEFAULT.name(), type=str + ) + ) + Execution_Time_Color.current_color = QColor( + settings.value( + "Execution Time Color", + defaultValue=Execution_Time_Color.DEFAULT.name(), + type=str, + ) + ) + Signal_Color.changed = settings.value( + f"{Signal_Color.name}_changed", False, bool + ) + Active_Color.changed = settings.value( + f"{Active_Color.name}_changed", False, bool + ) + Signal_Warning_Color.changed = settings.value( + f"{Signal_Warning_Color.name}_changed", + False, + bool, + ) + Latency_Color.changed = settings.value( + f"{Latency_Color.name}_changed", False, bool + ) + Execution_Time_Color.changed = settings.value( + f"{Execution_Time_Color.name}_changed", + False, + bool, + ) + self._color_changed_perType = settings.value( + "_color_changed_perType", False, bool + ) + + settings.endGroup() + settings.sync() log.debug(f"Settings read from '{settings.fileName()}'.") def info_table_fill_schedule(self, schedule: Schedule) -> None: @@ -859,6 +983,488 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): ) self.menu_view_execution_times.addAction(type_action) + def Preferences_Dialog_clicked(self): + """Open the Preferences dialog to customize fonts, colors, and settings""" + dialog = QDialog() + dialog.setWindowTitle("Preferences") + layout = QVBoxLayout() + layout.setSpacing(15) + + # Add label for the dialog + label = QLabel("Personalize Your Fonts and Colors") + layout.addWidget(label) + + groupbox = QGroupBox() + Hlayout = QHBoxLayout() + label = QLabel("Color Settings:") + layout.addWidget(label) + Hlayout.setSpacing(20) + + Hlayout.addWidget(self.creat_color_button(Execution_Time_Color)) + Hlayout.addWidget(self.creat_color_button(Latency_Color)) + Hlayout.addWidget( + self.creat_color_button( + ColorDataType( + current_color=Latency_Color.DEFAULT, + DEFAULT=QColor('skyblue'), + name="Latency Color per Type", + ) + ) + ) + + groupbox.setLayout(Hlayout) + layout.addWidget(groupbox) + + label = QLabel("Signal Colors:") + layout.addWidget(label) + groupbox = QGroupBox() + Hlayout = QHBoxLayout() + Hlayout.setSpacing(20) + + Signal_button = self.creat_color_button(Signal_Color) + Signal_button.setStyleSheet( + f"color: {QColor(255,255,255,0).name()}; background-color: {Signal_Color.DEFAULT.name()}" + ) + + Hlayout.addWidget(Signal_button) + Hlayout.addWidget(self.creat_color_button(Signal_Warning_Color)) + Hlayout.addWidget(self.creat_color_button(Active_Color)) + + groupbox.setLayout(Hlayout) + layout.addWidget(groupbox) + + Reset_Color_button = ColorButton(QColor('silver')) + Reset_Color_button.setText('Reset All Color settings') + Reset_Color_button.pressed.connect(self.reset_color_clicked) + layout.addWidget(Reset_Color_button) + + label = QLabel("Font Settings:") + layout.addWidget(label) + + groupbox = QGroupBox() + Hlayout = QHBoxLayout() + Hlayout.setSpacing(10) + + Font_button = ColorButton(QColor('moccasin')) + Font_button.setText('Font Settings') + Hlayout.addWidget(Font_button) + + Font_color_button = ColorButton(QColor('moccasin')) + Font_color_button.setText('Font Color') + Font_color_button.pressed.connect(self.font_color_clicked) + Hlayout.addWidget(Font_color_button) + + groupbox2 = QGroupBox() + Hlayout2 = QHBoxLayout() + + icon = QIcon.fromTheme("format-text-italic") + Italicbutton = ( + ColorButton(QColor('silver')) + if Font.italic + else ColorButton(QColor('snow')) + ) + Italicbutton.setIcon(icon) + Italicbutton.pressed.connect(lambda: self.Italic_font_clicked(Italicbutton)) + Hlayout2.addWidget(Italicbutton) + + icon = QIcon.fromTheme("format-text-bold") + Boldbutton = ( + ColorButton(QColor('silver')) if Font.bold else ColorButton(QColor('snow')) + ) + Boldbutton.setIcon(icon) + Boldbutton.pressed.connect(lambda: self.Bold_font_clicked(Boldbutton)) + Hlayout2.addWidget(Boldbutton) + + groupbox2.setLayout(Hlayout2) + Hlayout.addWidget(groupbox2) + + groupbox2 = QGroupBox() + Hlayout2 = QHBoxLayout() + Font_Size_input = QLineEdit() + Font_button.pressed.connect( + lambda: self.font_clicked(Font_Size_input, Italicbutton, Boldbutton) + ) + + icon = QIcon.fromTheme("list-add") + Incr_button = ColorButton(QColor('smoke')) + Incr_button.setIcon(icon) + Incr_button.pressed.connect(lambda: self.Incr_font_clicked(Font_Size_input)) + Incr_button.setShortcut(QCoreApplication.translate("MainWindow", "Ctrl++")) + Hlayout2.addWidget(Incr_button) + + Font_Size_input.setPlaceholderText('Font Size') + Font_Size_input.setText(f'Font Size: {Font.size}') + Font_Size_input.setValidator(QIntValidator(0, 99)) + Font_Size_input.setAlignment(Qt.AlignCenter) + Font_Size_input.textChanged.connect( + lambda: self.set_fontSize_clicked(Font_Size_input.text()) + ) + Font_Size_input.textChanged.connect( + lambda: self.set_fontSize_clicked(Font_Size_input.text()) + ) + Hlayout2.addWidget(Font_Size_input) + + icon = QIcon.fromTheme("list-remove") + Decr_button = ColorButton(QColor('smoke')) + Decr_button.setIcon(icon) + Decr_button.pressed.connect(lambda: self.Decr_font_clicked(Font_Size_input)) + Decr_button.setShortcut(QCoreApplication.translate("MainWindow", "Ctrl+-")) + + Hlayout2.addWidget(Decr_button) + + groupbox2.setLayout(Hlayout2) + Hlayout.addWidget(groupbox2) + + groupbox.setLayout(Hlayout) + layout.addWidget(groupbox) + + Reset_Font_button = ColorButton(QColor('silver')) + Reset_Font_button.setText('Reset All Font Settings') + Reset_Font_button.pressed.connect( + lambda: self.reset_font_clicked(Font_Size_input, Italicbutton, Boldbutton) + ) + layout.addWidget(Reset_Font_button) + + label = QLabel("") + layout.addWidget(label) + + Reset_button = ColorButton(QColor('salmon')) + Reset_button.setText('Reset All Settings') + Reset_button.pressed.connect( + lambda: self.reset_all_clicked(Font_Size_input, Italicbutton, Boldbutton) + ) + layout.addWidget(Reset_button) + + dialog.setLayout(layout) + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Close) + buttonBox.ButtonLayout(QDialogButtonBox.MacLayout) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.close) + layout.addWidget(buttonBox) + + dialog.exec_() + + def creat_color_button(self, color: ColorDataType) -> ColorButton: + """Create a colored button to be used to modify a certain color""" + button = ColorButton(color.DEFAULT) + button.setText(color.name) + if color.name == "Latency Color": + button.pressed.connect( + lambda: self.set_latency_color_by_type_name(all=True) + ) + elif color.name == "Latency Color per Type": + button.pressed.connect( + lambda: self.set_latency_color_by_type_name(all=False) + ) + else: + button.pressed.connect(lambda: self.Color_button_clicked(color)) + return button + + def set_latency_color_by_type_name(self, all: bool): + """Set latency color based on operation type names""" + if Latency_Color.changed: + current_color = Latency_Color.current_color + else: + current_color = Latency_Color.DEFAULT + + # Prompt user to select operation type if not setting color for all types + if not all: + used_types = self._schedule.get_used_type_names() + type, ok = QInputDialog.getItem( + self, "Select Operation Type", "Type", used_types, editable=False + ) + else: + type = "all operations" + ok = False + + # Open a color dialog to get the selected color + if all or ok: + color = QColorDialog.getColor( + current_color, self, f"Select the color of {type}" + ) + + # If a valid color is selected, update color settings and graph + if color.isValid(): + if all: + Latency_Color.changed = True + self._color_changed_perType = False + self.changed_operation_colors.clear() + Latency_Color.current_color = color + # Save color settings for each operation type + else: + self._color_changed_perType = True + self.changed_operation_colors[type] = color + self.color_pref_update() + self.update_statusbar("Preferences Updated") + + def color_pref_update(self): + """Update preferences of Latency color per type""" + for type in self._schedule.get_used_type_names(): + if Latency_Color.changed and not self._color_changed_perType: + self._color_per_type[type] = Latency_Color.current_color + elif not (Latency_Color.changed and self._color_changed_perType): + self._color_per_type[type] = Latency_Color.DEFAULT + elif not Latency_Color.changed and self._color_changed_perType: + if type in self.changed_operation_colors.keys(): + self._color_per_type[type] = self.changed_operation_colors[type] + else: + self._color_per_type[type] = Latency_Color.DEFAULT + else: + if type in self.changed_operation_colors.keys(): + self._color_per_type[type] = self.changed_operation_colors[type] + else: + self._color_per_type[type] = Latency_Color.current_color + self.Save_colortype() + + def Save_colortype(self): + """Save preferences of Latency color per type in settings""" + settings = QSettings() + for key, color in self._color_per_type.items(): + self._graph._color_change(color, key) + self.converted_colorPerType[key] = color.name() + settings.setValue( + f"scheduler/preferences/{Latency_Color.name}", + Latency_Color.current_color, + ) + settings.setValue( + f"scheduler/preferences/{Latency_Color.name}_changed", Latency_Color.changed + ) + settings.setValue( + f"scheduler/preferences/{Latency_Color.name}/perType", + self.converted_colorPerType, + ) + settings.setValue( + f"scheduler/preferences/{Latency_Color.name}/perType_changed", + self._color_changed_perType, + ) + + def Color_button_clicked(self, color_type: ColorDataType): + """Open a color dialog to select a color based on the specified color type""" + settings = QSettings() + if color_type.changed: + current_color = color_type.current_color + else: + current_color = color_type.DEFAULT + + color = QColorDialog.getColor(current_color, self, f"Select {color_type.name}") + # If a valid color is selected, update the current color and settings + if color.isValid(): + color_type.current_color = color + # colorbutton.set_color(color) + color_type.changed = ( + False if color_type.current_color == color_type.DEFAULT else True + ) + settings.setValue(f"scheduler/preferences/{color_type.name}", color.name()) + settings.sync() + + self._graph._signals.reopen.emit() + self.update_statusbar("Preferences Updated") + + def font_clicked( + self, Sizeline: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton + ): + """Open a font dialog to select a font and update the current font""" + if Font.changed: + current_font = Font.current_font + else: + current_font = Font.DEFAULT + + (ok, font) = QFontDialog.getFont(current_font, self) + if ok: + Font.current_font = font + Font.size = int(font.pointSizeF()) + Font.bold = font.bold() + Font.italic = font.italic() + self.Update_font() + self.Match_Dialog_Font(Sizeline, italicbutton, boldbutton) + self.update_statusbar("Preferences Updated") + + def Update_font(self): + """Update font preferences based on current Font settings""" + settings = QSettings() + Font.changed = ( + False + if ( + Font.current_font == Font.DEFAULT + and Font.size == int(Font.DEFAULT.pointSizeF()) + and Font.italic == Font.DEFAULT.italic() + and Font.bold == Font.DEFAULT.bold() + ) + else True + ) + settings.setValue("scheduler/preferences/font", Font.current_font.toString()) + settings.setValue("scheduler/preferences/fontSize", Font.size) + settings.setValue("scheduler/preferences/fontBold", Font.bold) + settings.setValue("scheduler/preferences/fontItalic", Font.italic) + settings.sync() + self.load_preferences() + + def load_preferences(self): + "Load the last saved preferences from settings" + settings = QSettings() + Latency_Color.current_color = QColor( + settings.value( + f"scheduler/preferences/{Latency_Color.name}", + defaultValue=Latency_Color.DEFAULT, + type=str, + ) + ) + Latency_Color.changed = settings.value( + f"scheduler/preferences/{Latency_Color.name}_changed", False, bool + ) + self.converted_colorPerType = settings.value( + f"scheduler/preferences/{Latency_Color.name}/perType", + self.converted_colorPerType, + ) + self._color_changed_perType = settings.value( + f"scheduler/preferences/{Latency_Color.name}/perType_changed", False, bool + ) + settings.sync() + + for key, color_str in self.converted_colorPerType.items(): + color = QColor(color_str) + self._color_per_type[key] = color + Match = ( + (color == Latency_Color.current_color) + if Latency_Color.changed + else (color == Latency_Color.DEFAULT) + ) + if self._color_changed_perType and not Match: + self.changed_operation_colors[key] = color + self.color_pref_update() + + if Font.changed: + Font.current_font.setPointSizeF(Font.size) + Font.current_font.setItalic(Font.italic) + Font.current_font.setBold(Font.bold) + self._graph._font_change(Font.current_font) + self._graph._font_color_change(Font.color) + else: + self._graph._font_change(Font.DEFAULT) + self._graph._font_color_change(Font.DEFAULT_COLOR) + + self.update_statusbar("Saved Preferences Loaded") + + def font_color_clicked(self): + """Select a font color and update preferences""" + settings = QSettings() + color = QColorDialog.getColor(Font.color, self, "Select Font Color") + if color.isValid(): + Font.color = color + settings.setValue("scheduler/preferences/fontColor", Font.color.name()) + settings.sync() + self._graph._font_color_change(Font.color) + + def set_fontSize_clicked(self, size): + """Set the font size to the specified size and update the font""" + Font.size = int(size) if (not size == "") else 6 + Font.current_font.setPointSizeF(Font.size) + self.Update_font() + + def Italic_font_clicked(self, button: ColorButton): + """Toggle the font style to italic if not already italic, otherwise remove italic""" + Font.italic = not Font.italic + Font.current_font.setItalic(Font.italic) + ( + button.set_color(QColor('silver')) + if Font.italic + else button.set_color(QColor('snow')) + ) + self.Update_font() + + def Bold_font_clicked(self, button: ColorButton): + """Toggle the font style to bold if not already bold, otherwise unbold""" + Font.bold = not Font.bold + Font.current_font.setBold(Font.bold) + Font.current_font.setWeight(50) + ( + button.set_color(QColor('silver')) + if Font.bold + else button.set_color(QColor('snow')) + ) + self.Update_font() + + def Incr_font_clicked(self, line: QLineEdit): + """Increase the font size by 1""" + ( + line.setText(str(Font.size + 1)) + if Font.size <= 71 + else line.setText(str(Font.size)) + ) + + def Decr_font_clicked(self, line: QLineEdit): + """Decrease the font size by 1""" + ( + line.setText(str(Font.size - 1)) + if Font.size >= 7 + else line.setText(str(Font.size)) + ) + + def reset_color_clicked(self): + """Reset the color settings""" + settings = QSettings() + Latency_Color.changed = False + Active_Color.changed = False + Signal_Warning_Color.changed = False + Signal_Color.changed = False + Execution_Time_Color.changed = False + self._color_changed_perType = False + self.color_pref_update() + + settings.beginGroup("scheduler/preferences") + settings.setValue(Latency_Color.name, Latency_Color.DEFAULT.name()) + settings.setValue(Signal_Color.name, Signal_Color.DEFAULT.name()) + settings.setValue(Active_Color.name, Active_Color.DEFAULT.name()) + settings.setValue( + Signal_Warning_Color.name, Signal_Warning_Color.DEFAULT.name() + ) + settings.setValue( + Execution_Time_Color.name, Execution_Time_Color.DEFAULT.name() + ) + settings.endGroup() + + self._graph._color_change(Latency_Color.DEFAULT, "all operations") + self._graph._signals.reopen.emit() + self.load_preferences() + + def reset_font_clicked( + self, Sizeline: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton + ): + """Reset the font settings""" + Font.current_font = QFont("Times", 12) + Font.changed = False + Font.color = Font.DEFAULT_COLOR + Font.size = int(Font.DEFAULT.pointSizeF()) + Font.bold = Font.DEFAULT.bold() + Font.italic = Font.DEFAULT.italic() + self.Update_font() + self.load_preferences() + self.Match_Dialog_Font(Sizeline, italicbutton, boldbutton) + + def reset_all_clicked( + self, Sizeline: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton + ): + """Reset both the color and the font settings""" + self.reset_color_clicked() + self.reset_font_clicked(Sizeline, italicbutton, boldbutton) + + def Match_Dialog_Font( + self, Sizeline: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton + ): + """Update the widgets on the pref dialog to match the current font""" + Sizeline.setText(str(Font.size)) + + ( + italicbutton.set_color(QColor('silver')) + if Font.italic + else italicbutton.set_color(QColor('snow')) + ) + ( + boldbutton.set_color(QColor('silver')) + if Font.bold + else boldbutton.set_color(QColor('snow')) + ) + @Slot(str) def _show_execution_times_for_type(self, type_name): self._execution_time_plot(type_name) diff --git a/b_asic/scheduler_gui/operation_item.py b/b_asic/scheduler_gui/operation_item.py index f488689f..6b9fc774 100644 --- a/b_asic/scheduler_gui/operation_item.py +++ b/b_asic/scheduler_gui/operation_item.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Dict, List, Union, cast # QGraphics and QPainter imports from qtpy.QtCore import QPointF, Qt -from qtpy.QtGui import QBrush, QColor, QCursor, QPainterPath, QPen +from qtpy.QtGui import QBrush, QColor, QCursor, QFont, QPainterPath, QPen from qtpy.QtWidgets import ( QAction, QGraphicsEllipseItem, @@ -26,13 +26,12 @@ from b_asic.graph_component import GraphID from b_asic.gui_utils.icons import get_icon from b_asic.operation import Operation from b_asic.scheduler_gui._preferences import ( - OPERATION_EXECUTION_TIME_INACTIVE, OPERATION_HEIGHT, - OPERATION_LATENCY_ACTIVE, - OPERATION_LATENCY_INACTIVE, - SIGNAL_ACTIVE, - SIGNAL_INACTIVE, - SIGNAL_WARNING, + Active_Color, + Execution_Time_Color, + Latency_Color, + Signal_Color, + Signal_Warning_Color, ) if TYPE_CHECKING: @@ -64,6 +63,7 @@ class OperationItem(QGraphicsItemGroup): _label_item: QGraphicsSimpleTextItem _port_items: List[QGraphicsEllipseItem] _port_number_items: List[QGraphicsSimpleTextItem] + _inactive_color: QColor = Latency_Color.DEFAULT def __init__( self, @@ -97,16 +97,30 @@ class OperationItem(QGraphicsItemGroup): QCursor(Qt.CursorShape.OpenHandCursor) ) # default cursor when hovering over object - self._port_filling_brush = QBrush(SIGNAL_INACTIVE) - self._port_outline_pen = QPen(SIGNAL_INACTIVE) + if Signal_Color.changed: + self._port_filling_brush = QBrush(Signal_Color.current_color) + self._port_outline_pen = QPen(Signal_Color.current_color) + else: + self._port_filling_brush = QBrush(Signal_Color.DEFAULT) + self._port_outline_pen = QPen(Signal_Color.DEFAULT) self._port_outline_pen.setWidthF(0) - self._port_filling_brush_active = QBrush(SIGNAL_ACTIVE) - self._port_outline_pen_active = QPen(SIGNAL_ACTIVE) + if Active_Color.changed: + self._port_filling_brush_active = QBrush(Active_Color.current_color) + self._port_outline_pen_active = QPen(Active_Color.current_color) + else: + self._port_filling_brush_active = QBrush(Active_Color.DEFAULT) + self._port_outline_pen_active = QPen(Active_Color.DEFAULT) self._port_outline_pen_active.setWidthF(0) - self._port_filling_brush_warning = QBrush(SIGNAL_WARNING) - self._port_outline_pen_warning = QPen(SIGNAL_WARNING) + if Signal_Warning_Color.changed: + self._port_filling_brush_warning = QBrush( + Signal_Warning_Color.current_color + ) + self._port_outline_pen_warning = QPen(Signal_Warning_Color.current_color) + else: + self._port_filling_brush_warning = QBrush(Signal_Warning_Color.DEFAULT) + self._port_outline_pen_warning = QPen(Signal_Warning_Color.DEFAULT) self._port_outline_pen_warning.setWidthF(0) self._make_component() @@ -184,20 +198,47 @@ class OperationItem(QGraphicsItemGroup): def set_active(self) -> None: """Set the item as active, i.e., draw it in special colors.""" - self._set_background(OPERATION_LATENCY_ACTIVE) + if Active_Color.changed: + self._set_background(Active_Color.current_color) + else: + self._set_background(Active_Color.DEFAULT) self.setCursor(QCursor(Qt.CursorShape.ClosedHandCursor)) def set_inactive(self) -> None: """Set the item as inactive, i.e., draw it in standard colors.""" - self._set_background(OPERATION_LATENCY_INACTIVE) + if Latency_Color.changed: + self._set_background(self._inactive_color) + else: + self._set_background(Latency_Color.DEFAULT) self.setCursor(QCursor(Qt.CursorShape.OpenHandCursor)) + def Set_font(self, font: QFont) -> None: + """Set the items font settings according to a give QFont.""" + self._label_item.prepareGeometryChange() + self._label_item.setFont(font) + center = self._latency_item.boundingRect().center() + center -= self._label_item.boundingRect().center() / self._scale + self._label_item.setPos(self._latency_item.pos() + center) + + def Set_fontColor(self, color: QColor) -> None: + """Set the items font color settings according to a give QColor""" + self._label_item.prepareGeometryChange() + self._label_item.setBrush(color) + def set_show_port_numbers(self, port_number: bool = True): for item in self._port_number_items: item.setVisible(port_number) def set_port_active(self, key: str): item = self._ports[key]["item"] + if Active_Color.changed: + self._port_filling_brush_active = QBrush(Active_Color.current_color) + self._port_outline_pen_active = QPen(Active_Color.current_color) + else: + self._port_filling_brush_active = QBrush(Active_Color.DEFAULT) + self._port_outline_pen_active = QPen(Active_Color.DEFAULT) + + self._port_outline_pen_active.setWidthF(0) item.setBrush(self._port_filling_brush_active) item.setPen(self._port_outline_pen_active) @@ -226,7 +267,10 @@ class OperationItem(QGraphicsItemGroup): port_size = 7 / self._scale # the diameter of a port - execution_time_color = QColor(OPERATION_EXECUTION_TIME_INACTIVE) + if Execution_Time_Color.changed: + execution_time_color = QColor(Execution_Time_Color.current_color) + else: + execution_time_color = QColor(Execution_Time_Color.DEFAULT) execution_time_color.setAlpha(200) # 0-255 execution_time_pen = QPen() # used by execution time outline execution_time_pen.setColor(execution_time_color) @@ -254,7 +298,7 @@ class OperationItem(QGraphicsItemGroup): self._execution_time_item.setPen(execution_time_pen) # component item - self._set_background(OPERATION_LATENCY_INACTIVE) # used by component filling + self._set_background(Latency_Color.DEFAULT) # used by component filling def create_ports(io_coordinates, prefix): for i, (x, y) in enumerate(io_coordinates): diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py index d11df346..89d4a384 100644 --- a/b_asic/scheduler_gui/scheduler_event.py +++ b/b_asic/scheduler_gui/scheduler_event.py @@ -38,6 +38,7 @@ class SchedulerEvent: # PyQt5 redraw_all = Signal() reopen = Signal() execution_time_plot = Signal(str) + TextSignal = Signal(str) _axes: Optional[AxesItem] _current_pos: QPointF @@ -69,12 +70,10 @@ class SchedulerEvent: # PyQt5 # Filters # ########### @overload - def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: - ... + def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ... @overload - def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: - ... + def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ... def installSceneEventFilters(self, filterItems) -> None: """ @@ -88,12 +87,10 @@ class SchedulerEvent: # PyQt5 item.installSceneEventFilter(self) @overload - def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: - ... + def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ... @overload - def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: - ... + def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ... def removeSceneEventFilters(self, filterItems) -> None: """ diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py index 63cae243..dcd4b993 100644 --- a/b_asic/scheduler_gui/scheduler_item.py +++ b/b_asic/scheduler_gui/scheduler_item.py @@ -12,6 +12,7 @@ from typing import Dict, List, Optional, Set, cast # QGraphics and QPainter imports from qtpy.QtCore import Signal +from qtpy.QtGui import QColor, QFont from qtpy.QtWidgets import QGraphicsItem, QGraphicsItemGroup # B-ASIC @@ -141,6 +142,26 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 for signal in self._get_all_signals(): signal.update_path() + def _color_change(self, color: QColor, name: str) -> None: + """Change inactive color of operation item *.""" + for op in self.components: + if name == "all operations": + op._set_background(color) + op._inactive_color = color + elif name == op.operation.type_name(): + op._set_background(color) + op._inactive_color = color + + def _font_change(self, font: QFont) -> None: + """Update font in the schedule.""" + for op in self.components: + op.Set_font(font) + + def _font_color_change(self, color: QColor) -> None: + """Update font color in the schedule.""" + for op in self.components: + op.Set_fontColor(color) + def _redraw_lines(self, item: OperationItem) -> None: """Update lines connected to *item*.""" for signal in self._signal_dict[item]: diff --git a/b_asic/scheduler_gui/signal_item.py b/b_asic/scheduler_gui/signal_item.py index ccb065df..1cdc5186 100644 --- a/b_asic/scheduler_gui/signal_item.py +++ b/b_asic/scheduler_gui/signal_item.py @@ -5,7 +5,6 @@ Contains the scheduler_gui SignalItem class for drawing and maintaining a signal in the schedule. """ - from typing import TYPE_CHECKING, cast from qtpy.QtCore import QPointF @@ -15,12 +14,12 @@ from qtpy.QtWidgets import QGraphicsPathItem # B-ASIC from b_asic.scheduler_gui._preferences import ( SCHEDULE_INDENT, - SIGNAL_ACTIVE, - SIGNAL_INACTIVE, - SIGNAL_WARNING, SIGNAL_WIDTH, SIGNAL_WIDTH_ACTIVE, SIGNAL_WIDTH_WARNING, + Active_Color, + Signal_Color, + Signal_Warning_Color, ) from b_asic.scheduler_gui.operation_item import OperationItem from b_asic.signal import Signal @@ -101,13 +100,24 @@ class SignalItem(QGraphicsPathItem): def _refresh_pens(self) -> None: """Create pens.""" - pen = QPen(SIGNAL_ACTIVE) + if Active_Color.changed: + pen = QPen(Active_Color.current_color) + else: + pen = QPen(Active_Color.DEFAULT) pen.setWidthF(SIGNAL_WIDTH_ACTIVE) self._active_pen = pen - pen = QPen(SIGNAL_INACTIVE) + + if Signal_Color.changed: + pen = QPen(Signal_Color.current_color) + else: + pen = QPen(Signal_Color.DEFAULT) pen.setWidthF(SIGNAL_WIDTH) self._inactive_pen = pen - pen = QPen(SIGNAL_WARNING) + + if Signal_Warning_Color.changed: + pen = QPen(Signal_Warning_Color.current_color) + else: + pen = QPen(Signal_Warning_Color.DEFAULT) pen.setWidthF(SIGNAL_WIDTH_WARNING) self._warning_pen = pen diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py index 4a48600a..b35469c8 100644 --- a/b_asic/scheduler_gui/ui_main_window.py +++ b/b_asic/scheduler_gui/ui_main_window.py @@ -131,6 +131,12 @@ class Ui_MainWindow: self.menuFile.setObjectName("menuFile") self.menu_Recent_Schedule = QtWidgets.QMenu(self.menuFile) self.menu_Recent_Schedule.setObjectName("menu_Recent_Schedule") + self.menu_Pref = QtWidgets.QAction(MainWindow) + self.menu_Pref.setEnabled(False) + self.menu_Pref.setObjectName("menu_Pref") + icon = QtGui.QIcon.fromTheme('preferences-desktop-personal') + self.menu_Pref.setIcon(icon) + self.menu_Pref.triggered.connect(self.Preferences_Dialog_clicked) self.menuView = QtWidgets.QMenu(self.menubar) self.menuView.setObjectName("menuView") self.menu_view_execution_times = QtWidgets.QMenu(self.menuView) @@ -252,13 +258,15 @@ class Ui_MainWindow: self.actionToggle_full_screen.setCheckable(True) self.actionToggle_full_screen.setObjectName("actionToggle_full_screen") self.menuFile.addAction(self.menu_open) + self.menuFile.addAction(self.menu_Recent_Schedule.menuAction()) + self.menuFile.addAction(self.menu_load_from_file) + self.menuFile.addSeparator() self.menuFile.addAction(self.menu_save) self.menuFile.addAction(self.menu_save_as) - self.menuFile.addAction(self.menu_load_from_file) - self.menuFile.addAction(self.menu_close_schedule) self.menuFile.addSeparator() - self.menuFile.addAction(self.menu_Recent_Schedule.menuAction()) + self.menuFile.addAction(self.menu_Pref) self.menuFile.addSeparator() + self.menuFile.addAction(self.menu_close_schedule) self.menuFile.addAction(self.menu_quit) self.menuView.addAction(self.menu_node_info) self.menuView.addAction(self.actionToolbar) @@ -322,6 +330,11 @@ class Ui_MainWindow: self.info_table.setSortingEnabled(__sortingEnabled) self.menuFile.setTitle(_translate("MainWindow", "&File")) self.menu_Recent_Schedule.setTitle(_translate("MainWindow", "Open &recent")) + self.menu_Pref.setText(_translate("MainWindow", "Preferences")) + self.menu_Pref.setToolTip( + _translate("MainWindow", "Customize your Font and Color Preferences") + ) + self.menu_Pref.setShortcut(_translate("MainWindow", "Ctrl+M")) self.menuView.setTitle(_translate("MainWindow", "&View")) self.menu_view_execution_times.setTitle( _translate("MainWindow", "View execution times of type") @@ -344,16 +357,18 @@ class Ui_MainWindow: self.menu_node_info.setToolTip( _translate("MainWindow", "Show/hide node information") ) - self.menu_node_info.setShortcut(_translate("MainWindow", "Ctrl+I")) + self.menu_node_info.setShortcut(_translate("MainWindow", "Ctrl+N")) self.menu_quit.setText(_translate("MainWindow", "&Quit")) self.menu_quit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.menu_save_as.setText(_translate("MainWindow", "Save &as...")) self.menu_save_as.setToolTip( _translate("MainWindow", "Save schedule with new file name") ) + self.menu_save_as.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) self.menu_exit_dialog.setText(_translate("MainWindow", "&Hide exit dialog")) self.menu_exit_dialog.setToolTip(_translate("MainWindow", "Hide exit dialog")) self.menu_close_schedule.setText(_translate("MainWindow", "&Close schedule")) + self.menu_close_schedule.setShortcut(_translate("MainWindow", "Ctrl+W")) self.actionAbout.setText(_translate("MainWindow", "&About")) self.actionAbout.setToolTip(_translate("MainWindow", "Open about window")) self.actionDocumentation.setText(_translate("MainWindow", "&Documentation")) @@ -364,6 +379,7 @@ class Ui_MainWindow: self.actionReorder.setToolTip( _translate("MainWindow", "Reorder schedule based on start time") ) + self.actionReorder.setShortcut(_translate("MainWindow", "Ctrl+R")) self.actionPlot_schedule.setText(_translate("MainWindow", "&Plot schedule")) self.actionPlot_schedule.setToolTip(_translate("MainWindow", "Plot schedule")) self.action_view_variables.setText( @@ -381,7 +397,7 @@ class Ui_MainWindow: self.actionUndo.setText(_translate("MainWindow", "Undo")) self.actionUndo.setShortcut(_translate("MainWindow", "Ctrl+Z")) self.actionRedo.setText(_translate("MainWindow", "Redo")) - self.actionRedo.setShortcut(_translate("MainWindow", "Ctrl+R")) + self.actionRedo.setShortcut(_translate("MainWindow", "Ctrl+Y")) self.actionIncrease_time_resolution.setText( _translate("MainWindow", "Increase time resolution...") ) -- GitLab