Skip to content
41 changes: 39 additions & 2 deletions capa/ida/plugin/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
CAPA_SETTINGS_RULEGEN_AUTHOR = "rulegen_author"
CAPA_SETTINGS_RULEGEN_SCOPE = "rulegen_scope"
CAPA_SETTINGS_ANALYZE = "analyze"
CAPA_SETTINGS_FONT = "font"


CAPA_OFFICIAL_RULESET_URL = f"https://github.com/mandiant/capa-rules/releases/tag/v{capa.version.__version__}"
Expand Down Expand Up @@ -304,6 +305,39 @@ def load_interface(self):

# load parent view
self.load_view_parent()
self.load_font()

def load_font(self):
"""load the user-configured font or fall back to the system fixed font"""
font_str = settings.user.get(CAPA_SETTINGS_FONT, "")
font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
if font_str:
font.fromString(font_str)
self.update_fonts(font)

def slot_font(self):
"""launch the font dialog and apply the chosen font"""
font_str = settings.user.get(CAPA_SETTINGS_FONT, "")
current_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
if font_str:
current_font.fromString(font_str)
font, ok = QtWidgets.QFontDialog.getFont(current_font, self.parent, "Select Plugin Font")
if ok:
settings.user[CAPA_SETTINGS_FONT] = font.toString()
self.update_fonts(font)

def update_fonts(self, font: QtGui.QFont):
"""propagate the selected font throughout the plugin UI"""
if hasattr(self, "model_data") and self.model_data:
self.model_data.update_font(font)
if hasattr(self, "view_tree") and self.view_tree:
self.view_tree.update_font(font)
if hasattr(self, "view_rulegen_preview") and self.view_rulegen_preview:
self.view_rulegen_preview.update_font(font)
if hasattr(self, "view_rulegen_editor") and self.view_rulegen_editor:
self.view_rulegen_editor.update_font(font)
if hasattr(self, "view_rulegen_features") and self.view_rulegen_features:
self.view_rulegen_features.update_font(font)

def load_view_tabs(self):
"""load tabs"""
Expand Down Expand Up @@ -345,22 +379,26 @@ def load_view_buttons(self):
reset_button = QtWidgets.QPushButton("Reset Selections")
save_button = QtWidgets.QPushButton("Save")
settings_button = QtWidgets.QPushButton("Settings")
font_button = QtWidgets.QPushButton("Font...")

analyze_button.clicked.connect(self.slot_analyze)
reset_button.clicked.connect(self.slot_reset)
save_button.clicked.connect(self.slot_save)
settings_button.clicked.connect(self.slot_settings)
font_button.clicked.connect(self.slot_font)

layout = QtWidgets.QHBoxLayout()
layout.addWidget(analyze_button)
layout.addWidget(reset_button)
layout.addWidget(settings_button)
layout.addWidget(font_button)
layout.addStretch(3)
layout.addWidget(save_button, alignment=QtCore.Qt.AlignRight)

self.view_analyze_button = analyze_button
self.view_reset_button = reset_button
self.view_settings_button = settings_button
self.view_font_button = font_button
self.view_save_button = save_button
self.view_buttons = layout

Expand Down Expand Up @@ -416,9 +454,8 @@ def load_view_rulegen_tab(self):
left = QtWidgets.QWidget()
left.setLayout(layout2)

font = QtGui.QFont()
font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
font.setBold(True)
font.setPointSize(11)

label1 = QtWidgets.QLabel()
label1.setAlignment(QtCore.Qt.AlignLeft)
Expand Down
14 changes: 12 additions & 2 deletions capa/ida/plugin/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def __init__(self, parent=None):
super().__init__(parent)
# root node does not have parent, contains header columns
self.root_node = CapaExplorerDataItem(None, ["Rule Information", "Address", "Details"])
self.current_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)

def update_font(self, font: QtGui.QFont):
"""update the font used to render items"""
self.current_font = font
self.layoutChanged.emit()

def reset(self):
"""reset UI elements (e.g. checkboxes, IDA color highlights)
Expand Down Expand Up @@ -134,7 +140,8 @@ def data(self, model_index, role):
CapaExplorerDataModel.COLUMN_INDEX_DETAILS,
):
# set font for virtual address and details columns
font = QtGui.QFont("Courier", weight=QtGui.QFont.Medium)
font = QtGui.QFont(self.current_font)
font.setWeight(QtGui.QFont.Medium)
if column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS:
font.setBold(True)
return font
Expand All @@ -156,10 +163,13 @@ def data(self, model_index, role):
and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION
):
# set bold font for important items
font = QtGui.QFont()
font = QtGui.QFont(self.current_font)
font.setBold(True)
return font

if role == QtCore.Qt.FontRole:
return QtGui.QFont(self.current_font)

if role == QtCore.Qt.ForegroundRole and column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS:
# set color for virtual address column
return QtGui.QColor(37, 147, 215)
Expand Down
55 changes: 47 additions & 8 deletions capa/ida/plugin/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,20 @@ def __init__(self, parent=None):
""" """
super().__init__(parent)

self.setFont(QtGui.QFont("Courier", weight=QtGui.QFont.Bold))
self.current_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
font = QtGui.QFont(self.current_font)
font.setBold(True)
self.setFont(font)
self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.setAcceptRichText(False)

def update_font(self, font: QtGui.QFont):
self.current_font = font
preview_font = QtGui.QFont(self.current_font)
preview_font.setBold(True)
self.setFont(preview_font)

def reset_view(self):
""" """
self.clear()
Expand Down Expand Up @@ -347,6 +356,7 @@ def __init__(self, preview, parent=None):
self.reset_view()

self.is_editing = False
self.current_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)

@staticmethod
def get_column_feature_index():
Expand Down Expand Up @@ -498,6 +508,20 @@ def slot_item_double_clicked(self, o, column):
o.setFlags(o.flags() & ~QtCore.Qt.ItemIsEditable)
self.is_editing = True

def update_font(self, font: QtGui.QFont):
"""apply a new font to the editor and restyle existing nodes"""
self.current_font = font
self.setFont(font)
self.header().setFont(font)
for node in iterate_tree(self):
if getattr(node, "capa_type", None) == CapaExplorerRulegenEditor.get_node_type_expression():
self.style_expression_node(node)
elif getattr(node, "capa_type", None) == CapaExplorerRulegenEditor.get_node_type_feature():
self.style_feature_node(node)
elif getattr(node, "capa_type", None) == CapaExplorerRulegenEditor.get_node_type_comment():
self.style_comment_node(node)
self.slot_resize_columns_to_content()

def update_preview(self):
""" """
rule_text = self.preview.toPlainText()
Expand Down Expand Up @@ -577,17 +601,16 @@ def load_custom_context_menu_expression(self, pos):

def style_expression_node(self, o):
""" """
font = QtGui.QFont()
font = QtGui.QFont(self.current_font)
font.setBold(True)

o.setFont(CapaExplorerRulegenEditor.get_column_feature_index(), font)

def style_feature_node(self, o):
""" """
font = QtGui.QFont()
font = QtGui.QFont(self.current_font)
brush = QtGui.QBrush()

font.setFamily("Courier")
font.setWeight(QtGui.QFont.Medium)
brush.setColor(QtGui.QColor(*COLOR_GREEN_RGB))

Expand All @@ -596,9 +619,8 @@ def style_feature_node(self, o):

def style_comment_node(self, o):
""" """
font = QtGui.QFont()
font = QtGui.QFont(self.current_font)
font.setBold(True)
font.setFamily("Courier")

o.setFont(CapaExplorerRulegenEditor.get_column_feature_index(), font)

Expand Down Expand Up @@ -814,6 +836,7 @@ def __init__(self, editor, parent=None):

self.parent_items = {}
self.editor = editor
self.current_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)

self.setHeaderLabels(["Feature", "Address"])
self.setStyleSheet("QTreeView::item {padding-right: 15 px;padding-bottom: 2 px;}")
Expand Down Expand Up @@ -972,14 +995,15 @@ def show_item_and_parents(_o):

def style_parent_node(self, o):
""" """
font = QtGui.QFont()
font = QtGui.QFont(self.current_font)
font.setBold(True)

o.setFont(CapaExplorerRulegenFeatures.get_column_feature_index(), font)

def style_leaf_node(self, o):
""" """
font = QtGui.QFont("Courier", weight=QtGui.QFont.Bold)
font = QtGui.QFont(self.current_font)
font.setBold(True)
brush = QtGui.QBrush()

o.setFont(CapaExplorerRulegenFeatures.get_column_feature_index(), font)
Expand All @@ -990,6 +1014,17 @@ def style_leaf_node(self, o):

brush.setColor(QtGui.QColor(*COLOR_BLUE_RGB))
o.setForeground(CapaExplorerRulegenFeatures.get_column_address_index(), brush)
def update_font(self, font: QtGui.QFont):
"""apply a new font to the feature tree and restyle nodes"""
self.current_font = font
self.setFont(font)
self.header().setFont(font)
for node in iterate_tree(self):
if getattr(node, "capa_type", None) == CapaExplorerRulegenFeatures.get_node_type_parent():
self.style_parent_node(node)
elif getattr(node, "capa_type", None) == CapaExplorerRulegenFeatures.get_node_type_leaf():
self.style_leaf_node(node)
self.slot_resize_columns_to_content()

def set_parent_node(self, o):
""" """
Expand Down Expand Up @@ -1133,6 +1168,10 @@ def __init__(self, model, parent=None):

self.setStyleSheet("QTreeView::item {padding-right: 15 px;padding-bottom: 2 px;}")

def update_font(self, font: QtGui.QFont):
self.setFont(font)
self.header().setFont(font)

def reset_ui(self, should_sort=True):
"""reset user interface changes

Expand Down
Loading