From 46b83a2139f8d776d1dfe6a9cf5ebcb0cc343b97 Mon Sep 17 00:00:00 2001 From: Adam Doupe Date: Thu, 20 Jul 2023 15:33:16 -0400 Subject: [PATCH] Pseudocode: Allow themes to change the weight of the font (bold/normal) and the style (italic/normal). (#1048) * Pseudocode: Allow themes to change the weight of the font (bold/normal) and the style (italic/normal). * Use Tuple instead of tuple for typing in Python <3.9. --- angrmanagement/config/color_schemes.py | 4 +- angrmanagement/config/config_manager.py | 50 ++++++++++++++++- angrmanagement/ui/dialogs/preferences.py | 26 ++++++--- .../ui/widgets/qccode_highlighter.py | 56 ++++++++++--------- 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/angrmanagement/config/color_schemes.py b/angrmanagement/config/color_schemes.py index 453da81c6..06a82c199 100644 --- a/angrmanagement/config/color_schemes.py +++ b/angrmanagement/config/color_schemes.py @@ -1,4 +1,4 @@ -from PySide6.QtGui import QColor +from PySide6.QtGui import QColor, QFont COLOR_SCHEMES = { "Light": { @@ -268,10 +268,12 @@ "palette_link": QColor(0x8B, 0xE9, 0xFD), "palette_linkvisited": QColor(0xBD, 0x93, 0xF9), "pseudocode_comment_color": QColor(0x62, 0x72, 0xA4), + "pseudocode_comment_weight": QFont.Weight.Normal, "pseudocode_function_color": QColor(0x50, 0xFA, 0x7B), "pseudocode_quotation_color": QColor(0xF1, 0xFA, 0x8C), "pseudocode_keyword_color": QColor(0xFF, 0x79, 0xC6), "pseudocode_types_color": QColor(0x8B, 0xE9, 0xFD), + "pseudocode_types_style": QFont.Style.StyleItalic, "pseudocode_variable_color": QColor(0xF8, 0xF8, 0xF2), "pseudocode_label_color": QColor(0x00, 0xAA, 0xFF), "pseudocode_highlight_color": QColor(0x44, 0x47, 0x5A), diff --git a/angrmanagement/config/config_manager.py b/angrmanagement/config/config_manager.py index 14383d66d..1910937af 100644 --- a/angrmanagement/config/config_manager.py +++ b/angrmanagement/config/config_manager.py @@ -1,8 +1,9 @@ import contextlib +import enum import logging import os import re -from typing import Any, Callable, List, Optional, Type +from typing import Any, Callable, List, Optional, Tuple, Type import tomlkit import tomlkit.exceptions @@ -82,6 +83,37 @@ def font_serializer(config_option, value: QFont) -> str: return f"{value.pointSize()}px {value.family()}" +def enum_parser_serializer_generator( + the_enum: enum.Enum, default +) -> Tuple[Callable[[str, str], enum.Enum], Callable[[str, enum.Enum], str]]: + def parser(config_option: str, value: str) -> enum.Enum: + try: + return the_enum[value] + except KeyError: + _l.error( + "Failed to parse value %r as %s for option %s. Default to %s.", + value, + type(the_enum), + config_option, + default, + ) + return default + + def serializer(config_option: str, value: enum.Enum) -> str: + if not isinstance(value, the_enum): + _l.error( + "Failed to serialize value %r as %s for option %s. Default to %s.", + value, + type(the_enum), + config_option, + default, + ) + return default + return value.name + + return parser, serializer + + def bool_parser(config_option, value) -> bool: if not value: return False @@ -104,6 +136,8 @@ def bool_serializer(config_option, value: bool) -> str: QColor: (color_parser, color_serializer), QFont: (font_parser, font_serializer), bool: (bool_parser, bool_serializer), + QFont.Weight: enum_parser_serializer_generator(QFont.Weight, QFont.Weight.Normal), + QFont.Style: enum_parser_serializer_generator(QFont.Style, QFont.Style.StyleNormal), } @@ -190,12 +224,26 @@ def bool_serializer(config_option, value: bool) -> str: CE("palette_link", QColor, QColor(0x00, 0x00, 0xFF, 0xFF)), CE("palette_linkvisited", QColor, QColor(0xFF, 0x00, 0xFF, 0xFF)), CE("pseudocode_comment_color", QColor, QColor(0x00, 0x80, 0x00, 0xFF)), + CE("pseudocode_comment_weight", QFont.Weight, QFont.Weight.Bold), + CE("pseudocode_comment_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_function_color", QColor, QColor(0x00, 0x00, 0xFF, 0xFF)), + CE("pseudocode_function_weight", QFont.Weight, QFont.Weight.Bold), + CE("pseudocode_function_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_quotation_color", QColor, QColor(0x00, 0x80, 0x00, 0xFF)), + CE("pseudocode_quotation_weight", QFont.Weight, QFont.Weight.Normal), + CE("pseudocode_quotation_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_keyword_color", QColor, QColor(0x00, 0x00, 0x80, 0xFF)), + CE("pseudocode_keyword_weight", QFont.Weight, QFont.Weight.Bold), + CE("pseudocode_keyword_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_types_color", QColor, QColor(0x00, 0x00, 0x80, 0xFF)), + CE("pseudocode_types_weight", QFont.Weight, QFont.Weight.Normal), + CE("pseudocode_types_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_variable_color", QColor, QColor(0x00, 0x00, 0x00, 0xFF)), + CE("pseudocode_variable_weight", QFont.Weight, QFont.Weight.Normal), + CE("pseudocode_variable_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_label_color", QColor, QColor(0x00, 0x00, 0xFF)), + CE("pseudocode_label_weight", QFont.Weight, QFont.Weight.Normal), + CE("pseudocode_label_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_highlight_color", QColor, QColor(0xFF, 0xFF, 0x00, 0xFF)), CE("proximity_node_background_color", QColor, QColor(0xFA, 0xFA, 0xFA)), CE("proximity_node_selected_background_color", QColor, QColor(0xCC, 0xCC, 0xCC)), diff --git a/angrmanagement/ui/dialogs/preferences.py b/angrmanagement/ui/dialogs/preferences.py index 0891580d7..cfb929e3d 100644 --- a/angrmanagement/ui/dialogs/preferences.py +++ b/angrmanagement/ui/dialogs/preferences.py @@ -1,3 +1,4 @@ +import enum from datetime import datetime from bidict import bidict @@ -115,7 +116,8 @@ class ThemeAndColors(Page): def __init__(self, parent=None): super().__init__(parent=parent) - self._to_save = {} + self._colors_to_save = {} + self._conf_to_save = {} self._schemes_combo: QComboBox = None self._init_widgets() @@ -144,11 +146,12 @@ def _init_widgets(self): edit_colors_layout = QVBoxLayout() for ce in ENTRIES: - if ce.type_ is not QColor: - continue - row = QColorOption(getattr(Conf, ce.name), ce.name) - edit_colors_layout.addWidget(row) - self._to_save[ce.name] = (ce, row) + if ce.type_ is QColor: + row = QColorOption(getattr(Conf, ce.name), ce.name) + edit_colors_layout.addWidget(row) + self._colors_to_save[ce.name] = (ce, row) + elif issubclass(ce.type_, enum.Enum): + self._conf_to_save[ce.name] = ce.value frame = QFrame() frame.setLayout(edit_colors_layout) @@ -165,8 +168,11 @@ def _init_widgets(self): def _load_color_scheme(self, name): for prop, value in COLOR_SCHEMES[name].items(): - row = self._to_save[prop][1] - row.set_color(value) + if prop in self._colors_to_save: + row = self._colors_to_save[prop][1] + row.set_color(value) + if prop in self._conf_to_save: + self._conf_to_save[prop] = value def _on_load_scheme_clicked(self): self._load_color_scheme(self._schemes_combo.currentText()) @@ -175,8 +181,10 @@ def _on_load_scheme_clicked(self): def save_config(self): # pylint: disable=assigning-non-slot Conf.theme_name = self._schemes_combo.currentText() - for ce, row in self._to_save.values(): + for ce, row in self._colors_to_save.values(): setattr(Conf, ce.name, row.color.am_obj) + for name, value in self._conf_to_save.items(): + setattr(Conf, name, value) class Style(Page): diff --git a/angrmanagement/ui/widgets/qccode_highlighter.py b/angrmanagement/ui/widgets/qccode_highlighter.py index d5fca9e8a..79df00f3b 100644 --- a/angrmanagement/ui/widgets/qccode_highlighter.py +++ b/angrmanagement/ui/widgets/qccode_highlighter.py @@ -15,7 +15,7 @@ ) from angr.sim_type import SimType from pyqodeng.core.api import SyntaxHighlighter -from PySide6.QtGui import QBrush, QFont, QTextCharFormat +from PySide6.QtGui import QBrush, QColor, QFont, QTextCharFormat from angrmanagement.config import Conf @@ -25,37 +25,43 @@ FORMATS = {} -def reset_formats(): +def create_char_format(color: QColor, weight: QFont.Weight, style: QFont.Style) -> QTextCharFormat: f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_keyword_color)) - f.setFontWeight(QFont.Bold) - FORMATS["keyword"] = f + f.setForeground(QBrush(color)) + f.setFontWeight(weight) + if style == QFont.Style.StyleItalic: + f.setFontItalic(True) + return f - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_quotation_color)) - FORMATS["quotation"] = f - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_function_color)) - f.setFontWeight(QFont.Bold) - FORMATS["function"] = f +def reset_formats(): + FORMATS["keyword"] = create_char_format( + Conf.pseudocode_keyword_color, Conf.pseudocode_keyword_weight, Conf.pseudocode_keyword_style + ) - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_comment_color)) - f.setFontWeight(QFont.Bold) - FORMATS["comment"] = f + FORMATS["quotation"] = create_char_format( + Conf.pseudocode_quotation_color, Conf.pseudocode_quotation_weight, Conf.pseudocode_quotation_style + ) - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_variable_color)) - FORMATS["variable"] = f + FORMATS["function"] = create_char_format( + Conf.pseudocode_function_color, Conf.pseudocode_function_weight, Conf.pseudocode_function_style + ) - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_types_color)) - FORMATS["type"] = f + FORMATS["comment"] = create_char_format( + Conf.pseudocode_comment_color, Conf.pseudocode_comment_weight, Conf.pseudocode_comment_style + ) - f = QTextCharFormat() - f.setForeground(QBrush(Conf.pseudocode_label_color)) - FORMATS["label"] = f + FORMATS["variable"] = create_char_format( + Conf.pseudocode_variable_color, Conf.pseudocode_variable_weight, Conf.pseudocode_variable_style + ) + + FORMATS["type"] = create_char_format( + Conf.pseudocode_types_color, Conf.pseudocode_types_weight, Conf.pseudocode_types_style + ) + + FORMATS["label"] = create_char_format( + Conf.pseudocode_label_color, Conf.pseudocode_label_weight, Conf.pseudocode_label_style + ) def _format_node(obj):