diff --git a/angrmanagement/__main__.py b/angrmanagement/__main__.py index efa643ea3f..b95070b62c 100644 --- a/angrmanagement/__main__.py +++ b/angrmanagement/__main__.py @@ -3,6 +3,7 @@ import asyncio import ctypes +import glob import multiprocessing import os import signal @@ -135,8 +136,8 @@ def drawContents(self, painter) -> None: refresh_theme() # Load fonts, initialize font-related configuration - QFontDatabase.addApplicationFont(os.path.join(FONT_LOCATION, "SourceCodePro-Regular.ttf")) - QFontDatabase.addApplicationFont(os.path.join(FONT_LOCATION, "DejaVuSansMono.ttf")) + for font_file in glob.glob(os.path.join(FONT_LOCATION, "*.ttf")): + QFontDatabase.addApplicationFont(font_file) Conf.init_font_config() Conf.connect("ui_default_font", app.setFont, True) diff --git a/angrmanagement/config/color_schemes.py b/angrmanagement/config/color_schemes.py index b45b3e377d..d9fb9239d5 100644 --- a/angrmanagement/config/color_schemes.py +++ b/angrmanagement/config/color_schemes.py @@ -1,9 +1,11 @@ from __future__ import annotations -from PySide6.QtGui import QColor, QFont +from PySide6.QtGui import QColor + +BASE_SCHEME = "Light" COLOR_SCHEMES = { - "Light": { + BASE_SCHEME: { "disasm_view_minimap_background_color": QColor(0xFF, 0xFF, 0xFF, 0xFF), "disasm_view_minimap_outline_color": QColor(0xB8, 0xB8, 0xB8, 0xFF), "disasm_view_operand_color": QColor(0x00, 0x00, 0x80), @@ -84,6 +86,7 @@ "pseudocode_comment_color": QColor(0x00, 0x80, 0x00, 0xFF), "pseudocode_function_color": QColor(0x00, 0x00, 0xFF, 0xFF), "pseudocode_library_function_color": QColor(0xFF, 0x00, 0xFF), + "pseudocode_global_variable_color": QColor(0x00, 0x00, 0xFF), "pseudocode_quotation_color": QColor(0x00, 0x80, 0x00, 0xFF), "pseudocode_keyword_color": QColor(0x00, 0x00, 0x80, 0xFF), "pseudocode_types_color": QColor(0x10, 0x78, 0x96), @@ -183,6 +186,7 @@ "pseudocode_comment_color": QColor(0x00, 0x80, 0x00, 0xFF), "pseudocode_function_color": QColor(0x00, 0xAA, 0xFF), "pseudocode_library_function_color": QColor(0xAA, 0x00, 0xFF), + "pseudocode_global_variable_color": QColor(0x00, 0xAA, 0xFF), "pseudocode_quotation_color": QColor(0x00, 0x80, 0x00, 0xFF), "pseudocode_keyword_color": QColor(0xF1, 0xA7, 0xFA), "pseudocode_types_color": QColor(0x00, 0xFF, 0xFF, 0xFF), @@ -274,13 +278,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_library_function_color": QColor(0x8B, 0xE9, 0xFD), + "pseudocode_global_variable_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), @@ -373,6 +376,7 @@ "pseudocode_comment_color": QColor(205, 214, 244), # text "pseudocode_function_color": QColor(166, 227, 161), # green "pseudocode_library_function_color": QColor(243, 139, 168), # red + "pseudocode_global_variable_color": QColor(166, 227, 161), # green "pseudocode_quotation_color": QColor(166, 227, 161), # green "pseudocode_keyword_color": QColor(137, 220, 235), # sky "pseudocode_types_color": QColor(249, 226, 175), # yellow diff --git a/angrmanagement/config/config_manager.py b/angrmanagement/config/config_manager.py index 634de8f131..b861ed30fc 100644 --- a/angrmanagement/config/config_manager.py +++ b/angrmanagement/config/config_manager.py @@ -14,6 +14,7 @@ from angrmanagement.utils.env import app_root +from .color_schemes import COLOR_SCHEMES from .config_entry import ConfigurationEntry as CE if TYPE_CHECKING: @@ -141,7 +142,7 @@ 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.Weight: enum_parser_serializer_generator(QFont.Weight, QFont.Weight.Medium), QFont.Style: enum_parser_serializer_generator(QFont.Style, QFont.Style.StyleNormal), } @@ -153,6 +154,7 @@ def bool_serializer(config_option, value: bool) -> str: CE("disasm_font", QFont, QFont("DejaVu Sans Mono", 10)), CE("symexec_font", QFont, QFont("DejaVu Sans Mono", 10)), CE("code_font", QFont, QFont("Source Code Pro", 10)), + CE("base_theme_name", str, "Light"), CE("theme_name", str, "Light"), CE("disasm_view_minimap_viewport_color", QColor, QColor(0xFF, 0x00, 0x00)), CE("disasm_view_minimap_background_color", QColor, QColor(0xFF, 0xFF, 0xFF, 0xFF)), @@ -229,26 +231,29 @@ 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_weight", QFont.Weight, QFont.Weight.Medium), CE("pseudocode_comment_style", QFont.Style, QFont.Style.StyleNormal), CE("pseudocode_function_color", QColor, QColor(0x00, 0x00, 0xFF, 0xFF)), - CE("pseudocode_library_function_color", QColor, QColor(0xFF, 0x00, 0xFF)), - CE("pseudocode_function_weight", QFont.Weight, QFont.Weight.Bold), + CE("pseudocode_function_weight", QFont.Weight, QFont.Weight.Medium), CE("pseudocode_function_style", QFont.Style, QFont.Style.StyleNormal), + CE("pseudocode_library_function_color", QColor, QColor(0xFF, 0x00, 0xFF)), CE("pseudocode_quotation_color", QColor, QColor(0x00, 0x80, 0x00, 0xFF)), - CE("pseudocode_quotation_weight", QFont.Weight, QFont.Weight.Normal), + CE("pseudocode_quotation_weight", QFont.Weight, QFont.Weight.Medium), 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_weight", QFont.Weight, QFont.Weight.Medium), 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_weight", QFont.Weight, QFont.Weight.Medium), 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_weight", QFont.Weight, QFont.Weight.Medium), CE("pseudocode_variable_style", QFont.Style, QFont.Style.StyleNormal), + CE("pseudocode_global_variable_color", QColor, QColor(0x00, 0x00, 0xFF)), + CE("pseudocode_global_variable_weight", QFont.Weight, QFont.Weight.Medium), + CE("pseudocode_global_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_weight", QFont.Weight, QFont.Weight.Medium), 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)), @@ -546,6 +551,14 @@ def parse(cls, f, ignore_unknown_entries: bool = False): except tomlkit.exceptions.ParseError: _l.error("Failed to parse configuration file: '%s'. Continuing with default options...", exc_info=True) + # load color scheme configuration + if ( + "theme_name" in entry_map + and "base_theme_name" in entry_map + and entry_map["theme_name"].value == entry_map["base_theme_name"].value + ): + cls.load_default_theme_entries(entry_map["theme_name"].value, entry_map) + return cls(entry_map) @staticmethod @@ -592,7 +605,17 @@ def parse_file(cls, path: str, ignore_unknown_entries: bool = False): def save(self, f) -> None: out = {} - for k, v in self._entries.items(): + + entries_to_save = self._entries.copy() + if ( + "theme_name" not in entries_to_save + or "base_theme_name" in entries_to_save + or entries_to_save["theme_name"].value == entries_to_save["base_theme_name"].value + ): + # we do not save theme-related entries if the theme is one of the default themes + self.remove_default_theme_entries(entries_to_save) + + for k, v in entries_to_save.items(): v = v.value while type(v) in data_serializers: v = data_serializers[type(v)][1](k, v) @@ -654,6 +677,40 @@ def attempt_importing_initial_config(self) -> bool: return loaded + @staticmethod + def load_default_theme_entries(theme_name: str, entries: dict[str, Any]) -> None: + """ + Load theme-related entries from the configuration dictionary if the theme is one of the default themes. + + :param theme_name: The name of the theme. + :param entries: The configuration dictionary. + :return: None + """ + if not COLOR_SCHEMES or theme_name not in COLOR_SCHEMES: + return + + theme = COLOR_SCHEMES[theme_name] + for k, v in theme.items(): + entries[k] = CE(k, type(v), v) + + @staticmethod + def remove_default_theme_entries(entries: dict[str, Any]) -> None: + """ + Remove theme-related entries from the configuration dictionary if the theme is one of the default themes. + + :param entries: The configuration dictionary. + :return: None + """ + if not COLOR_SCHEMES: + return + all_theme_keys = set() + for theme in COLOR_SCHEMES.values(): + all_theme_keys.update(theme.keys()) + + for entry_name in list(entries): + if entry_name in all_theme_keys: + del entries[entry_name] + @property def has_operation_mango(self) -> bool: try: diff --git a/angrmanagement/resources/fonts/SourceCodePro-Black.ttf b/angrmanagement/resources/fonts/SourceCodePro-Black.ttf new file mode 100644 index 0000000000..09078e0d9f Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-Black.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-BlackItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-BlackItalic.ttf new file mode 100644 index 0000000000..749bbf6d4d Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-BlackItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-Bold.ttf b/angrmanagement/resources/fonts/SourceCodePro-Bold.ttf new file mode 100644 index 0000000000..4653b76c80 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-Bold.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-BoldItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-BoldItalic.ttf new file mode 100644 index 0000000000..59e9319ac9 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-BoldItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-ExtraBold.ttf b/angrmanagement/resources/fonts/SourceCodePro-ExtraBold.ttf new file mode 100644 index 0000000000..b3ba550766 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-ExtraBold.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-ExtraBoldItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-ExtraBoldItalic.ttf new file mode 100644 index 0000000000..6b2d4a4abf Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-ExtraBoldItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-ExtraLight.ttf b/angrmanagement/resources/fonts/SourceCodePro-ExtraLight.ttf new file mode 100644 index 0000000000..9f04a49716 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-ExtraLight.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-ExtraLightItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-ExtraLightItalic.ttf new file mode 100644 index 0000000000..03f4a74169 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-ExtraLightItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-Italic.ttf b/angrmanagement/resources/fonts/SourceCodePro-Italic.ttf new file mode 100644 index 0000000000..626044853e Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-Italic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-Light.ttf b/angrmanagement/resources/fonts/SourceCodePro-Light.ttf new file mode 100644 index 0000000000..ccc3a4249a Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-Light.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-LightItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-LightItalic.ttf new file mode 100644 index 0000000000..9ab74ba393 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-LightItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-Medium.ttf b/angrmanagement/resources/fonts/SourceCodePro-Medium.ttf new file mode 100644 index 0000000000..af3f57c515 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-Medium.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-MediumItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-MediumItalic.ttf new file mode 100644 index 0000000000..ceab90b7f7 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-MediumItalic.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-Regular.ttf b/angrmanagement/resources/fonts/SourceCodePro-Regular.ttf index c58300335a..b1fa336cd5 100644 Binary files a/angrmanagement/resources/fonts/SourceCodePro-Regular.ttf and b/angrmanagement/resources/fonts/SourceCodePro-Regular.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-SemiBold.ttf b/angrmanagement/resources/fonts/SourceCodePro-SemiBold.ttf new file mode 100644 index 0000000000..834b4d659d Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-SemiBold.ttf differ diff --git a/angrmanagement/resources/fonts/SourceCodePro-SemiBoldItalic.ttf b/angrmanagement/resources/fonts/SourceCodePro-SemiBoldItalic.ttf new file mode 100644 index 0000000000..9c126e39a6 Binary files /dev/null and b/angrmanagement/resources/fonts/SourceCodePro-SemiBoldItalic.ttf differ diff --git a/angrmanagement/ui/css/__init__.py b/angrmanagement/ui/css/__init__.py index 5459b2856e..68c2337b72 100644 --- a/angrmanagement/ui/css/__init__.py +++ b/angrmanagement/ui/css/__init__.py @@ -38,7 +38,7 @@ def rebuild() -> None: log.warning("Failed to load base theme at %s", base_css_path) css = "" - theme_path = os.path.join(THEME_LOCATION, Conf.theme_name) + theme_path = os.path.join(THEME_LOCATION, Conf.base_theme_name) css_path = os.path.join(theme_path, "theme.css") if os.path.exists(css_path): try: @@ -59,7 +59,7 @@ def refresh_theme() -> None: app = QApplication.instance() # determine the default application style according to the OS - if sys.platform == "win32" and Conf.theme_name == "Light": + if sys.platform == "win32" and Conf.base_theme_name == "Light": app_style = "windowsvista" else: app_style = "Fusion" @@ -99,8 +99,4 @@ def refresh_theme() -> None: if GlobalInfo.main_window is not None: for codeview in GlobalInfo.main_window.workspace.view_manager.views_by_category["pseudocode"]: - codeview.codegen.am_event(already_regenerated=True) - - if codeview._textedit is not None: - for panel in codeview._textedit.panels: - panel.setPalette(palette) + codeview.set_codeedit_palette(palette) diff --git a/angrmanagement/ui/dialogs/preferences.py b/angrmanagement/ui/dialogs/preferences.py index 275c143787..4b25912bca 100644 --- a/angrmanagement/ui/dialogs/preferences.py +++ b/angrmanagement/ui/dialogs/preferences.py @@ -114,19 +114,23 @@ def save_config(self) -> None: pass +CUSTOM_SCHEME_NAME = "Custom" + + class ThemeAndColors(Page): """ Theme and Colors preferences page. """ NAME = "Theme and Colors" + _schemes_combo: QComboBox + _base_scheme: QLabel def __init__(self, parent=None) -> None: super().__init__(parent=parent) self._colors_to_save = {} self._conf_to_save = {} - self._schemes_combo: QComboBox = None self._init_widgets() @@ -140,7 +144,7 @@ def _init_widgets(self) -> None: self._schemes_combo = QComboBox(self) current_theme_idx = 0 - for idx, name in enumerate(["Current"] + sorted(COLOR_SCHEMES)): + for idx, name in enumerate(sorted(COLOR_SCHEMES) + [CUSTOM_SCHEME_NAME]): if name == Conf.theme_name: current_theme_idx = idx self._schemes_combo.addItem(name) @@ -149,6 +153,14 @@ def _init_widgets(self) -> None: scheme_loader_layout.addWidget(self._schemes_combo) page_layout.addLayout(scheme_loader_layout) + base_scheme_layout = QHBoxLayout() + base_scheme_label = QLabel("Base Theme:") + base_scheme_label.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)) + base_scheme_layout.addWidget(base_scheme_label) + self._base_scheme = QLabel(Conf.base_theme_name) + base_scheme_layout.addWidget(self._base_scheme) + page_layout.addLayout(base_scheme_layout) + edit_colors_layout = QVBoxLayout() for ce in ENTRIES: if ce.type_ is QColor: @@ -172,6 +184,8 @@ def _init_widgets(self) -> None: self.setLayout(page_layout) def _load_color_scheme(self, name: str) -> None: + if name not in COLOR_SCHEMES: + return for prop, value in COLOR_SCHEMES[name].items(): if prop in self._colors_to_save: row = self._colors_to_save[prop][1] @@ -180,13 +194,14 @@ def _load_color_scheme(self, name: str) -> None: self._conf_to_save[prop] = value def _on_scheme_selected(self, text: str) -> None: - self._load_color_scheme(text) - self.save_config() - refresh_theme() + if text != CUSTOM_SCHEME_NAME: + self._load_color_scheme(text) + self._base_scheme.setText(text) def save_config(self) -> None: # pylint: disable=assigning-non-slot Conf.theme_name = self._schemes_combo.currentText() + Conf.base_theme_name = self._base_scheme.text() 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(): diff --git a/angrmanagement/ui/views/code_view.py b/angrmanagement/ui/views/code_view.py index 6c539450ef..a9ffd3774a 100644 --- a/angrmanagement/ui/views/code_view.py +++ b/angrmanagement/ui/views/code_view.py @@ -108,6 +108,10 @@ def reload(self) -> None: self._options.reload(force=True) self.vars_must_struct = set() + def set_codeedit_palette(self, palette): + if self._textedit is not None: + self._textedit.setPalette(palette) + def decompile( self, clear_prototype: bool = True, diff --git a/angrmanagement/ui/widgets/qccode_edit.py b/angrmanagement/ui/widgets/qccode_edit.py index 99b9736c05..dc9c8ce9a4 100644 --- a/angrmanagement/ui/widgets/qccode_edit.py +++ b/angrmanagement/ui/widgets/qccode_edit.py @@ -59,12 +59,14 @@ class QCCodeEdit(api.CodeEdit): """ def __init__(self, code_view) -> None: - super().__init__(create_default_actions=True) + self.line_number_panel: panels.LineNumberPanel | None = None + self.folding_panel: panels.FoldingPanel | None = None + self.code_highlighter: QCCodeHighlighter | None = None + self.color_scheme: api.ColorScheme | None = None + super().__init__(create_default_actions=True) self._code_view: CodeView = code_view - - self.panels.append(panels.LineNumberPanel()) - self.panels.append(panels.FoldingPanel()) + self.create_panels() self.modes.append(modes.SymbolMatcherMode()) @@ -93,6 +95,24 @@ def __init__(self, code_view) -> None: self.remove_action(self.action_swap_line_up) self.remove_action(self.action_swap_line_down) + def create_panels(self): + self.line_number_panel = panels.LineNumberPanel() + self.folding_panel = panels.FoldingPanel() + self.panels.append(self.line_number_panel) + self.panels.append(self.folding_panel) + self.line_number_panel.setVisible(True) + self.folding_panel.setVisible(True) + + def remove_panels(self): + if self.line_number_panel is not None: + self.line_number_panel.setVisible(False) + self.panels.remove(self.line_number_panel.name) + self.line_number_panel = None + if self.folding_panel is not None: + self.folding_panel.setVisible(False) + self.panels.remove(self.folding_panel.name) + self.folding_panel = None + def node_under_cursor(self): doc: QTextDocument = self.document() if not isinstance(doc, QCodeDocument): @@ -287,9 +307,23 @@ def paintEvent(self, e) -> None: def setDocument(self, document) -> None: super().setDocument(document) - self.modes.append(QCCodeHighlighter(self.document(), color_scheme=ColorSchemeIDA())) + self.color_scheme = ColorSchemeIDA() + self.code_highlighter = QCCodeHighlighter(self.document(), color_scheme=self.color_scheme) + self.modes.append(self.code_highlighter) self.syntax_highlighter.fold_detector = api.CharBasedFoldDetector() + def setPalette(self, palette, **kwargs) -> None: + super().setPalette(palette, **kwargs) + # re-create panels to apply the new palette + self.remove_panels() + self.create_panels() + # re-generate color scheme because FORMATS has changed + if self.color_scheme is not None: + self.color_scheme = ColorSchemeIDA() + if self.code_highlighter is not None: + self.code_highlighter.refresh_editor(self.color_scheme) + self.rehighlight() + # # Actions # diff --git a/angrmanagement/ui/widgets/qccode_highlighter.py b/angrmanagement/ui/widgets/qccode_highlighter.py index 0f51fec0dd..3ebbe7a601 100644 --- a/angrmanagement/ui/widgets/qccode_highlighter.py +++ b/angrmanagement/ui/widgets/qccode_highlighter.py @@ -16,6 +16,7 @@ CVariable, ) from angr.sim_type import SimType +from angr.sim_variable import SimMemoryVariable from pyqodeng.core.api import SyntaxHighlighter from PySide6.QtGui import QBrush, QColor, QFont, QTextCharFormat @@ -43,6 +44,7 @@ def reset_formats() -> None: fg = QTextCharFormat() fg.setForeground(Conf.palette_text) + fg.setFontWeight(QFont.Weight.Medium) FORMATS["normal"] = fg FORMATS["keyword"] = create_char_format( @@ -77,6 +79,12 @@ def reset_formats() -> None: Conf.pseudocode_label_color, Conf.pseudocode_label_weight, Conf.pseudocode_label_style ) + FORMATS["global_variable"] = create_char_format( + Conf.pseudocode_global_variable_color, + Conf.pseudocode_global_variable_weight, + Conf.pseudocode_global_variable_style, + ) + def _format_node(obj): """ @@ -95,23 +103,24 @@ def _format_node(obj): elif isinstance(obj, CLabel): return FORMATS["label"] elif isinstance(obj, CVariable): + if type(obj.variable) is SimMemoryVariable: + return FORMATS["global_variable"] return FORMATS["variable"] elif isinstance(obj, CArrayTypeLength): # This is the part that goes after a fixed-size array (the # "[20]" in "char foo[20];"), and it's highly unlikely # that anyone will want to change the color here. But if # you do, follow the format of the rest. - return None + return FORMATS["normal"] elif isinstance(obj, CStructFieldNameDef): # This is the part that is a field name in a struct def, # and it's highly unlikely that anyone will want to change # the color here. But if you do, follow the format of the # rest. - return None + return FORMATS["normal"] elif isinstance(obj, CClosingObject | CStatement | CConstant | CExpression): - return None - else: - return None + return FORMATS["normal"] + return FORMATS["normal"] reset_formats() diff --git a/angrmanagement/ui/widgets/qfunction_table.py b/angrmanagement/ui/widgets/qfunction_table.py index 9f5f154bdc..2bca076fff 100644 --- a/angrmanagement/ui/widgets/qfunction_table.py +++ b/angrmanagement/ui/widgets/qfunction_table.py @@ -29,6 +29,7 @@ if TYPE_CHECKING: import PySide6 + import PySide6.QtGui from angr.knowledge_plugins.functions import Function, FunctionManager from angrmanagement.ui.views.functions_view import FunctionsView @@ -77,7 +78,7 @@ def func_list(self, v) -> None: self._func_list = None self._raw_func_list = v self._data_cache.clear() - self.emit(SIGNAL("layoutChanged()")) + self.emit(SIGNAL("layoutChanged()")) # type: ignore def filter(self, keyword) -> None: if not keyword or self._raw_func_list is None: @@ -92,7 +93,10 @@ def filter(self, keyword) -> None: ] self._data_cache.clear() - self.emit(SIGNAL("layoutChanged()")) + self.emit(SIGNAL("layoutChanged()")) # type: ignore + + def clear_data_cache(self): + self._data_cache = {} def rowCount(self, *args, **kwargs): # pylint:disable=unused-argument if self.func_list is None: @@ -102,8 +106,8 @@ def rowCount(self, *args, **kwargs): # pylint:disable=unused-argument def columnCount(self, *args, **kwargs): # pylint:disable=unused-argument return len(self.Headers) + self.workspace.plugins.count_func_columns() - def headerData(self, section, orientation, role): # pylint:disable=unused-argument - if role != Qt.DisplayRole: + def headerData(self, section, orientation, role=None): # pylint:disable=unused-argument + if role != Qt.ItemDataRole.DisplayRole: return None if section < len(self.Headers): @@ -115,7 +119,7 @@ def headerData(self, section, orientation, role): # pylint:disable=unused-argum # Not enough columns return None - def data(self, index, role): + def data(self, index, role=None): if not index.isValid(): return None @@ -136,10 +140,10 @@ def data(self, index, role): def _data_uncached(self, row, col, role): func = self.func_list[row] - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return self._get_column_text(func, col) - elif role == Qt.ForegroundRole: + elif role == Qt.ItemDataRole.ForegroundRole: if func.is_syscall: color = self._config.function_table_syscall_color elif func.is_plt: @@ -157,19 +161,19 @@ def _data_uncached(self, row, col, role): return QBrush(color) - elif role == Qt.BackgroundColorRole: + elif role == Qt.ItemDataRole.BackgroundRole: color = self.workspace.plugins.color_func(func) if color is None and func.from_signature: # default colors color = self._config.function_table_signature_bg_color return color - elif role == Qt.FontRole: + elif role == Qt.ItemDataRole.FontRole: return Conf.tabular_view_font return None - def sort(self, column, order) -> None: + def sort(self, column, order=None) -> None: self.layoutAboutToBeChanged.emit() self.func_list = sorted( self.func_list, @@ -274,7 +278,9 @@ class QFunctionTableHeaderView(QHeaderView): The header for QFunctionTableView. """ - def contextMenuEvent(self, event: PySide6.QtGui.QContextMenuEvent) -> None: # pylint:disable=unused-argument + def contextMenuEvent( # type: ignore[reportIncompatibleMethodOverride] # pylint:disable=unused-argument + self, event: PySide6.QtGui.QContextMenuEvent + ) -> None: menu = QMenu("Column Menu", self) for idx in range(self.model().columnCount()): column_text = self.model().headerData(idx, Qt.Orientation.Horizontal, Qt.ItemDataRole.DisplayRole) @@ -340,6 +346,8 @@ def __init__(self, parent, workspace: Workspace, instance: Instance, selection_c self.doubleClicked.connect(self._on_function_selected) def refresh(self, added_funcs: set[int] | None = None, removed_funcs: set[int] | None = None) -> None: + if self._functions is None: + return if added_funcs: new_funcs = [] for addr in added_funcs: @@ -354,6 +362,12 @@ def refresh(self, added_funcs: set[int] | None = None, removed_funcs: set[int] | self._model.func_list = [f_ for f_ in self._model.func_list if f_.addr not in removed_funcs] self.viewport().update() + def changeEvent(self, event): # type: ignore + if event.type() == QEvent.Type.PaletteChange: + self._model.clear_data_cache() + self.viewport().update() + super().changeEvent(event) + @property def function_manager(self): return self._functions @@ -376,6 +390,8 @@ def jump_to_result(self, index: int = 0) -> None: self._selected_func.am_event(func=self._selected_func.am_obj) def load_functions(self) -> None: + if self._functions is None: + return if not self.show_alignment_functions: self._model.func_list = [v for v in self._functions.values() if not v.alignment] else: @@ -387,7 +403,7 @@ def _on_function_selected(self, model_index) -> None: self._selected_func.am_obj = self._model.func_list[row] self._selected_func.am_event(func=self._selected_func.am_obj) - def keyPressEvent(self, key_event): + def keyPressEvent(self, key_event): # type: ignore text = key_event.text() if not text or text not in string.printable or text in string.whitespace: # modifier keys @@ -399,7 +415,9 @@ def keyPressEvent(self, key_event): def contextMenuEvent(self, event: PySide6.QtGui.QContextMenuEvent) -> None: # pylint:disable=unused-argument rows = self.selectionModel().selectedRows() - funcs = [self.instance.kb.functions[r.data()] for r in rows] + funcs = [] + if self.instance.kb is not None and self.instance.kb.functions is not None: + funcs = [self.instance.kb.functions[r.data()] for r in rows] self._context_menu.set(funcs).qmenu().popup(QCursor.pos()) @@ -415,7 +433,7 @@ def __init__(self, parent) -> None: self.installEventFilter(self) - def eventFilter(self, obj, event) -> bool: # pylint:disable=unused-argument + def eventFilter(self, watched, event) -> bool: # pylint:disable=unused-argument if event.type() == QEvent.Type.KeyPress and event.key() == Qt.Key.Key_Escape: if self.text(): self.setText("")