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