Skip to content

Commit

Permalink
Color schemes: Multiple fixes. (#1374)
Browse files Browse the repository at this point in the history
* Color schemes: Multiple fixes.

- Do not save all color settings when one of the default color schemes
  is in use. Fixes #1039.
- Only load a new color scheme when the "Save" button is clicked.
- Introduce a "base_theme_name" setting that the Custom theme uses (to
  load icons, etc.). Do not attempt to load resources from non-existent
  directorys. Fixes #805.
- Switch to medium font weight for pseudocode view to reduce eye stress.
- Fix the issue that the new theme does not immediately apply to
  CodeEdit, CodeEdit panels, or text in the function table view.
- Remove non-color settings from the default color schemes.

* Switch function name font weight to Medium.

* Type check qfunction_table.py.

* Fix the crash in QCCodeEdit.

* Lint code.
  • Loading branch information
ltfish authored Feb 4, 2025
1 parent ec6f039 commit 230d65e
Show file tree
Hide file tree
Showing 25 changed files with 190 additions and 52 deletions.
5 changes: 3 additions & 2 deletions angrmanagement/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import asyncio
import ctypes
import glob
import multiprocessing
import os
import signal
Expand Down Expand Up @@ -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)

Expand Down
12 changes: 8 additions & 4 deletions angrmanagement/config/color_schemes.py
Original file line number Diff line number Diff line change
@@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
77 changes: 67 additions & 10 deletions angrmanagement/config/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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),
}

Expand All @@ -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)),
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified angrmanagement/resources/fonts/SourceCodePro-Regular.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 3 additions & 7 deletions angrmanagement/ui/css/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"
Expand Down Expand Up @@ -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)
25 changes: 20 additions & 5 deletions angrmanagement/ui/dialogs/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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]
Expand All @@ -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():
Expand Down
4 changes: 4 additions & 0 deletions angrmanagement/ui/views/code_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 39 additions & 5 deletions angrmanagement/ui/widgets/qccode_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
#
Expand Down
Loading

0 comments on commit 230d65e

Please sign in to comment.