feat(gui): allow to set code area theme with custom code (#2471)

This commit is contained in:
Skylot
2025-05-03 20:24:49 +01:00
parent fbebcb9845
commit f56eb271a1
26 changed files with 411 additions and 212 deletions
@@ -41,7 +41,6 @@ import jadx.gui.cache.usage.UsageCacheMode;
import jadx.gui.settings.data.ShortcutsWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.ui.tab.dnd.TabDndGhostType;
import jadx.gui.utils.FontUtils;
import jadx.gui.utils.LafManager;
@@ -73,7 +72,11 @@ public class JadxSettings extends JadxCLIArgs {
private List<Path> recentProjects = new ArrayList<>();
private String fontStr = "";
private String smaliFontStr = "";
private String editorThemePath = EditorTheme.getDefaultTheme().getPath();
private String editorTheme = "";
// Deprecated. Keep for backward compatibility
private String editorThemePath = "/org/fife/ui/rsyntaxtextarea/themes/default.xml";
private String lafTheme = LafManager.INITIAL_THEME_NAME;
private LangLocale langLocale = NLS.defaultLocale();
private boolean autoStartJobs = false;
@@ -102,7 +105,6 @@ public class JadxSettings extends JadxCLIArgs {
private boolean showHeapUsageBar = false;
private boolean alwaysSelectOpened = false;
private boolean useAlternativeFileDialog = false;
private boolean useDynamicEditorTheme = false;
private Map<String, WindowLocation> windowPos = new HashMap<>();
private int mainWindowExtendedState = JFrame.NORMAL;
@@ -325,14 +327,6 @@ public class JadxSettings extends JadxCLIArgs {
this.useAlternativeFileDialog = useAlternativeFileDialog;
}
public boolean isUseDynamicEditorTheme() {
return useDynamicEditorTheme;
}
public void setUseDynamicEditorTheme(boolean useDynamicEditorTheme) {
this.useDynamicEditorTheme = useDynamicEditorTheme;
}
public String getExcludedPackages() {
return excludedPackages;
}
@@ -587,12 +581,12 @@ public class JadxSettings extends JadxCLIArgs {
this.logLevel = level;
}
public String getEditorThemePath() {
return editorThemePath;
public String getEditorTheme() {
return editorTheme;
}
public void setEditorThemePath(String editorThemePath) {
this.editorThemePath = editorThemePath;
public void setEditorTheme(String editorTheme) {
this.editorTheme = editorTheme;
}
public String getLafTheme() {
@@ -67,7 +67,8 @@ import jadx.gui.settings.ui.font.JadxFontDialog;
import jadx.gui.settings.ui.plugins.PluginSettings;
import jadx.gui.settings.ui.shortcut.ShortcutsSettingsGroup;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.ui.codearea.theme.EditorThemeManager;
import jadx.gui.ui.codearea.theme.ThemeIdAndName;
import jadx.gui.ui.tab.dnd.TabDndGhostType;
import jadx.gui.utils.FontUtils;
import jadx.gui.utils.LafManager;
@@ -351,28 +352,15 @@ public class JadxSettingsWindow extends JDialog {
JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
JButton smaliFontBtn = new JButton(NLS.str("preferences.select_smali_font"));
EditorTheme[] editorThemes = EditorTheme.getAllThemes();
JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
themesCbx.setEnabled(!settings.isUseDynamicEditorTheme());
for (EditorTheme theme : editorThemes) {
if (theme.getPath().equals(settings.getEditorThemePath())) {
themesCbx.setSelectedItem(theme);
break;
EditorThemeManager editorThemeManager = mainWindow.getEditorThemeManager();
JComboBox<ThemeIdAndName> themesCbx = new JComboBox<>(editorThemeManager.getThemeIdNameArray());
themesCbx.setSelectedItem(editorThemeManager.getCurrentThemeIdName());
themesCbx.addActionListener(evt -> {
ThemeIdAndName selected = (ThemeIdAndName) themesCbx.getSelectedItem();
if (selected != null) {
settings.setEditorTheme(selected.getId());
mainWindow.loadSettings();
}
}
themesCbx.addActionListener(e -> {
int i = themesCbx.getSelectedIndex();
EditorTheme editorTheme = editorThemes[i];
settings.setEditorThemePath(editorTheme.getPath());
mainWindow.loadSettings();
});
JCheckBox useDynamicEditorTheme = new JCheckBox();
useDynamicEditorTheme.setSelected(settings.isUseDynamicEditorTheme());
useDynamicEditorTheme.addItemListener(e -> {
settings.setUseDynamicEditorTheme(e.getStateChange() == ItemEvent.SELECTED);
mainWindow.loadSettings();
themesCbx.setEnabled(!settings.isUseDynamicEditorTheme());
});
JComboBox<String> lafCbx = new JComboBox<>(LafManager.getThemes());
@@ -385,7 +373,6 @@ public class JadxSettingsWindow extends JDialog {
SettingsGroup group = new SettingsGroup(NLS.str("preferences.appearance"));
group.addRow(NLS.str("preferences.language"), languageCbx);
group.addRow(NLS.str("preferences.laf_theme"), lafCbx);
group.addRow(NLS.str("preferences.dynamic_editor_theme"), useDynamicEditorTheme);
group.addRow(NLS.str("preferences.theme"), themesCbx);
JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn);
JLabel smaliFontLabel = group.addRow(getSmaliFontLabelStr(), smaliFontBtn);
@@ -21,11 +21,8 @@ import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -67,8 +64,6 @@ import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -129,8 +124,8 @@ import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.ui.codearea.EditorViewState;
import jadx.gui.ui.codearea.theme.EditorThemeManager;
import jadx.gui.ui.dialog.ADBDialog;
import jadx.gui.ui.dialog.AboutDialog;
import jadx.gui.ui.dialog.ExceptionDialog;
@@ -192,6 +187,7 @@ public class MainWindow extends JFrame {
private final TabsController tabsController;
private final NavigationController navController;
private final EditorSyncManager editorSyncManager;
private final EditorThemeManager editorThemeManager;
private transient @NotNull JadxProject project;
@@ -223,7 +219,6 @@ public class MainWindow extends JFrame {
private transient Link updateLink;
private transient ProgressPanel progressPane;
private transient Theme editorTheme;
private transient IssuesPanel issuesPanel;
private transient @Nullable LogPanel logPanel;
@@ -252,11 +247,12 @@ public class MainWindow extends JFrame {
this.shortcutsController = new ShortcutsController(settings);
this.tabsController = new TabsController(this);
this.navController = new NavigationController(this);
this.editorThemeManager = new EditorThemeManager(settings);
JadxEventQueue.register();
resetCache();
FontUtils.registerBundledFonts();
setEditorTheme(settings.getEditorThemePath());
editorThemeManager.setTheme(settings.getEditorTheme());
initUI();
this.editorSyncManager = new EditorSyncManager(this, tabbedPane);
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
@@ -1443,40 +1439,6 @@ public class MainWindow extends JFrame {
setLocationRelativeTo(null);
}
private void setEditorTheme(String editorThemePath) {
try {
URL themeUrl = getClass().getResource(editorThemePath);
if (themeUrl != null) {
try (InputStream is = themeUrl.openStream()) {
editorTheme = Theme.load(is);
return;
}
}
Path themePath = Paths.get(editorThemePath);
if (Files.isRegularFile(themePath)) {
try (InputStream is = Files.newInputStream(themePath)) {
editorTheme = Theme.load(is);
return;
}
}
} catch (Exception e) {
LOG.error("Failed to load editor theme: {}", editorThemePath, e);
}
LOG.warn("Falling back to default editor theme: {}", editorThemePath);
editorThemePath = EditorTheme.getDefaultTheme().getPath();
try (InputStream is = getClass().getResourceAsStream(editorThemePath)) {
editorTheme = Theme.load(is);
return;
} catch (Exception e) {
LOG.error("Failed to load default editor theme: {}", editorThemePath, e);
editorTheme = new Theme(new RSyntaxTextArea());
}
}
public Theme getEditorTheme() {
return editorTheme;
}
private void openSettings() {
settingsOpen = true;
@@ -1501,12 +1463,12 @@ public class MainWindow extends JFrame {
private void updateUiSettings() {
LafManager.updateLaf(settings);
editorThemeManager.setTheme(settings.getEditorTheme());
Font font = settings.getFont();
Font largerFont = font.deriveFont(font.getSize() + 2.f);
setFont(largerFont);
setEditorTheme(settings.getEditorThemePath());
tree.setFont(largerFont);
tree.setRowHeight(-1);
@@ -1535,6 +1497,7 @@ public class MainWindow extends JFrame {
heapUsageBar.reset();
closeAll();
editorThemeManager.unload();
dispose();
System.exit(0);
}
@@ -1754,6 +1717,10 @@ public class MainWindow extends JFrame {
return cacheManager;
}
public EditorThemeManager getEditorThemeManager() {
return editorThemeManager;
}
public JadxGuiEventsImpl events() {
return events;
}
@@ -374,18 +374,12 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
JadxSettings settings = mainWindow.getSettings();
if (settings.isUseDynamicEditorTheme()) {
DynamicCoreAreaTheme.applyCustomTheme(area);
} else {
mainWindow.getEditorTheme().apply(area);
}
mainWindow.getEditorThemeManager().apply(area);
area.setFont(settings.getFont());
Gutter gutter = RSyntaxUtilities.getGutter(area);
if (gutter != null) {
gutter.setLineNumberFont(settings.getFont());
}
}
public void loadSettings() {
@@ -520,14 +514,6 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}
}
@Override
public void updateUI() {
super.updateUI();
if (contentPanel != null && contentPanel.getMainWindow() != null) {
loadSettings();
}
}
@Override
public Dimension getPreferredSize() {
try {
@@ -45,6 +45,7 @@ public final class CodeArea extends AbstractCodeArea {
private static final long serialVersionUID = 6312736869579635796L;
private @Nullable ICodeInfo cachedCodeInfo;
private @Nullable MouseHoverHighlighter mouseHoverHighlighter;
private final ShortcutsController shortcutsController;
CodeArea(ContentPanel contentPanel, JNode node) {
@@ -76,7 +77,16 @@ public final class CodeArea extends AbstractCodeArea {
});
if (isJavaCode) {
addMouseMotionListener(new MouseHoverHighlighter(this, codeLinkGenerator));
mouseHoverHighlighter = new MouseHoverHighlighter(this, codeLinkGenerator);
addMouseMotionListener(mouseHoverHighlighter);
}
}
@Override
public void loadSettings() {
super.loadSettings();
if (mouseHoverHighlighter != null) {
mouseHoverHighlighter.loadSettings();
}
}
@@ -1,49 +0,0 @@
package jadx.gui.ui.codearea;
public final class EditorTheme {
private static final String RSTA_THEME_PATH = "/org/fife/ui/rsyntaxtextarea/themes/";
private static final EditorTheme[] ALL_THEMES =
new EditorTheme[] {
new EditorTheme("default"),
new EditorTheme("eclipse"),
new EditorTheme("idea"),
new EditorTheme("vs"),
new EditorTheme("dark"),
new EditorTheme("monokai"),
new EditorTheme("druid")
};
public static EditorTheme[] getAllThemes() {
return ALL_THEMES;
}
public static EditorTheme getDefaultTheme() {
return ALL_THEMES[0];
}
private final String name;
private final String path;
public EditorTheme(String name) {
this(name, RSTA_THEME_PATH + name + ".xml");
}
public EditorTheme(String name, String path) {
this.name = name;
this.path = path;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
@Override
public String toString() {
return name;
}
}
@@ -9,7 +9,6 @@ import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import org.apache.commons.lang3.StringUtils;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,6 +18,7 @@ import jadx.api.ResourcesLoader;
import jadx.core.utils.exceptions.JadxException;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.UiUtils;
@@ -85,12 +85,12 @@ public class HexArea extends AbstractCodeArea {
}
private void applyTheme() {
Font font = getContentPanel().getMainWindow().getSettings().getSmaliFont();
MainWindow mainWindow = getContentPanel().getMainWindow();
mainWindow.getEditorThemeManager().apply(this);
Font font = mainWindow.getSettings().getSmaliFont();
setFont(font);
Theme theme = contentPanel.getMainWindow().getEditorTheme();
if (hexPreviewPanel != null) {
hexPreviewPanel.applyTheme(theme, font);
hexPreviewPanel.applyTheme(this, font);
}
}
@@ -10,7 +10,6 @@ import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,11 +78,11 @@ public class HexPreviewPanel extends JTextArea {
setBorder(new MatteBorder(0, 2, 0, 0, borderColor));
}
public void applyTheme(Theme theme, Font font) {
setBackground(theme.bgColor);
setHighlightColor(theme.selectionBG);
setBorderColor(theme.gutterBorderColor);
setDisabledTextColor(theme.scheme.getStyle(SyntaxScheme.IDENTIFIER).foreground);
public void applyTheme(HexArea hexArea, Font font) {
setBackground(hexArea.getBackground());
setHighlightColor(hexArea.getSelectionColor());
setBorderColor(hexArea.getMatchedBracketBorderColor());
setDisabledTextColor(hexArea.getSyntaxScheme().getStyle(SyntaxScheme.IDENTIFIER).foreground);
setFont(font);
}
@@ -3,7 +3,7 @@ package jadx.gui.ui.codearea;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.text.Highlighter;
import javax.swing.text.Caret;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rtextarea.SmartHighlightPainter;
@@ -19,7 +19,7 @@ class MouseHoverHighlighter extends MouseMotionAdapter {
private final CodeArea codeArea;
private final CodeLinkGenerator codeLinkGenerator;
private final Highlighter.HighlightPainter highlighter;
private final SmartHighlightPainter highlighter;
private Object tag;
private int highlightedTokenOffset = -1;
@@ -27,7 +27,12 @@ class MouseHoverHighlighter extends MouseMotionAdapter {
public MouseHoverHighlighter(CodeArea codeArea, CodeLinkGenerator codeLinkGenerator) {
this.codeArea = codeArea;
this.codeLinkGenerator = codeLinkGenerator;
this.highlighter = new SmartHighlightPainter(codeArea.getMarkOccurrencesColor());
this.highlighter = new SmartHighlightPainter();
loadSettings();
}
public void loadSettings() {
highlighter.setPaint(codeArea.getMarkOccurrencesColor());
}
@Override
@@ -41,6 +46,11 @@ class MouseHoverHighlighter extends MouseMotionAdapter {
if (e.getModifiersEx() != 0) {
return false;
}
Caret caret = codeArea.getCaret();
if (caret.getDot() != caret.getMark()) {
// selection in action, highlight will interfere with selection
return false;
}
try {
Token token = codeArea.viewToToken(e.getPoint());
if (token == null) {
@@ -26,7 +26,6 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.Style;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.GutterIconInfo;
import org.fife.ui.rtextarea.IconRowHeader;
@@ -122,7 +121,7 @@ public final class SmaliArea extends AbstractCodeArea {
if (model != null) {
model.unload();
}
model = shouldUseSmaliPrinterV2() ? new DebugModel() : new NormalModel();
model = shouldUseSmaliPrinterV2() ? new DebugModel() : new NormalModel(this);
}
public void scrollToDebugPos(int pos) {
@@ -175,11 +174,8 @@ public final class SmaliArea extends AbstractCodeArea {
private class NormalModel extends SmaliModel {
public NormalModel() {
if (!getContentPanel().getMainWindow().getSettings().isUseDynamicEditorTheme()) {
Theme theme = getContentPanel().getMainWindow().getEditorTheme();
setSyntaxScheme(theme.scheme);
}
public NormalModel(SmaliArea smaliArea) {
getContentPanel().getMainWindow().getEditorThemeManager().apply(smaliArea);
setSyntaxEditingStyle(SYNTAX_STYLE_SMALI);
}
@@ -200,7 +196,7 @@ public final class SmaliArea extends AbstractCodeArea {
private Object runningHighlightTag = null; // running line
private final SmaliV2Style smaliV2Style = new SmaliV2Style(SmaliArea.this);
private final Map<Integer, BreakpointLine> bpMap = new HashMap<>();
private final PropertyChangeListener listener = evt -> {
private final PropertyChangeListener schemeListener = evt -> {
if (smaliV2Style.refreshTheme()) {
setSyntaxScheme(smaliV2Style);
}
@@ -209,7 +205,7 @@ public final class SmaliArea extends AbstractCodeArea {
public DebugModel() {
loadV2Style();
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_6502);
addPropertyChangeListener(SYNTAX_SCHEME_PROPERTY, listener);
addPropertyChangeListener(SYNTAX_SCHEME_PROPERTY, schemeListener);
regBreakpointEvents();
}
@@ -229,7 +225,7 @@ public final class SmaliArea extends AbstractCodeArea {
@Override
public void unload() {
removePropertyChangeListener(listener);
removePropertyChangeListener(schemeListener);
removeLineHighlight(runningHighlightTag);
UiUtils.removeKeyBinding(SmaliArea.this, bpShortcut, "set a break point");
BreakpointManager.removeListener((JClass) node);
@@ -316,11 +312,9 @@ public final class SmaliArea extends AbstractCodeArea {
}
private class SmaliV2Style extends SyntaxScheme {
Theme curTheme;
public SmaliV2Style(SmaliArea smaliArea) {
super(true);
curTheme = smaliArea.getContentPanel().getMainWindow().getEditorTheme();
smaliArea.getContentPanel().getMainWindow().getEditorThemeManager().apply(smaliArea);
updateTheme();
}
@@ -329,13 +323,8 @@ public final class SmaliArea extends AbstractCodeArea {
}
public boolean refreshTheme() {
if (getContentPanel().getMainWindow().getSettings().isUseDynamicEditorTheme()) {
return false;
}
Theme theme = getContentPanel().getMainWindow().getEditorTheme();
boolean refresh = theme != curTheme;
boolean refresh = getSyntaxScheme() != this;
if (refresh) {
curTheme = theme;
updateTheme();
}
return refresh;
@@ -1,4 +1,4 @@
package jadx.gui.ui.codearea;
package jadx.gui.ui.codearea.theme;
import java.awt.Color;
@@ -9,20 +9,36 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rtextarea.Gutter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DynamicCoreAreaTheme {
private static final Logger LOG = LoggerFactory.getLogger(DynamicCoreAreaTheme.class);
import jadx.gui.utils.NLS;
public static void applyCustomTheme(RSyntaxTextArea textArea) {
// Get the current theme's background color from UIManager
/**
* Mix current UI theme colors and apply to code area theme.
*/
public class DynamicCodeAreaTheme implements IEditorTheme {
@Override
public String getId() {
return "DynamicCodeAreaTheme";
}
@Override
public String getName() {
return NLS.str("preferences.dynamic_editor_theme");
}
public void apply(RSyntaxTextArea textArea) {
// Get the current colors from UIManager
Color themeBackground = UIManager.getColor("Panel.background");
boolean isDarkTheme = isDarkTheme(themeBackground);
Color themeForeground = UIManager.getColor("Panel.foreground");
Color separatorForeground = UIManager.getColor("Separator.foreground");
Color editorSelectionBackground = UIManager.getColor("EditorPane.selectionBackground");
Color caretForeground = UIManager.getColor("EditorPane.caretForeground");
SyntaxScheme scheme = textArea.getSyntaxScheme();
boolean isDarkTheme = isDarkTheme(themeBackground);
// Background colors based on the theme
Color editorBackground = isDarkTheme ? themeBackground : Color.WHITE; // Use white for light theme
Color lineHighlight = isDarkTheme
@@ -36,15 +52,12 @@ public class DynamicCoreAreaTheme {
: new Color(51, 153, 255, 50); // Lighter blue for light theme
Color markAllHighlightColor = isDarkTheme ? Color.decode("#32593D") : Color.decode("#ffc800");
Color markOccurrencesColor = isDarkTheme ? Color.decode("#52503A") : Color.decode("#FCE8F4");
Color matchedBracketBackground = isDarkTheme ? adjustBrightness(Color.decode("#3B514D"), 1.2f) : Color.decode("#93D9D9");
Color gutterBorderColor = UIManager.getColor("Separator.foreground");
Color hyperlinkColor = UIManager.getColor("EditorPane.selectionBackground");
Color markOccurrencesColor = adjustBrightness(editorSelectionBackground, isDarkTheme ? 0.6f : 1.4f);
// Set the syntax colors for the theme
if (isDarkTheme) {
Color dataTypeColor = Color.decode("#4EC9B0");
Color booleanColor = Color.decode("#569CD6");
scheme.getStyle(Token.COMMENT_EOL).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_MULTILINE).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_DOCUMENTATION).foreground = Color.decode("#57A64A");
@@ -56,21 +69,19 @@ public class DynamicCoreAreaTheme {
scheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_NUMBER_FLOAT).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_NUMBER_HEXADECIMAL).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = booleanColor;
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = Color.decode("#569CD6");
scheme.getStyle(Token.LITERAL_CHAR).foreground = Color.decode("#CE9178");
scheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground = Color.decode("#CE9178");
scheme.getStyle(Token.DATA_TYPE).foreground = dataTypeColor;
scheme.getStyle(Token.OPERATOR).foreground = Color.WHITE;
scheme.getStyle(Token.SEPARATOR).foreground = Color.WHITE;
scheme.getStyle(Token.IDENTIFIER).foreground = Color.decode("#9CDCFE");
scheme.getStyle(Token.IDENTIFIER).foreground = themeForeground;
// XML-specific colors for dark theme
scheme.getStyle(Token.MARKUP_TAG_DELIMITER).foreground = Color.decode("#808080"); // Gray for < > /
scheme.getStyle(Token.MARKUP_TAG_NAME).foreground = Color.decode("#569CD6"); // Blue for tag names
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE).foreground = Color.decode("#9CDCFE"); // Light blue for attributes
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE_VALUE).foreground = Color.decode("#CE9178"); // Orange for values
} else {
Color booleanColor = Color.decode("#0451A5");
Color keyColor = Color.decode("#4A7A4F"); // Soft green for keys
Color dataTypeColor = Color.decode("#267F99");
scheme.getStyle(Token.COMMENT_EOL).foreground = Color.decode("#008000");
scheme.getStyle(Token.COMMENT_MULTILINE).foreground = Color.decode("#008000");
@@ -84,39 +95,39 @@ public class DynamicCoreAreaTheme {
scheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_NUMBER_FLOAT).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_NUMBER_HEXADECIMAL).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = booleanColor;
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = Color.decode("#0451A5");
scheme.getStyle(Token.LITERAL_CHAR).foreground = Color.decode("#067d17");
scheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground = Color.decode("#067d17"); // Soft blue for values
scheme.getStyle(Token.DATA_TYPE).foreground = dataTypeColor;
scheme.getStyle(Token.OPERATOR).foreground = Color.decode("#333333");
scheme.getStyle(Token.SEPARATOR).foreground = Color.decode("#333333");
scheme.getStyle(Token.IDENTIFIER).foreground = Color.decode("#001080");
scheme.getStyle(Token.IDENTIFIER).foreground = themeForeground;
// XML-specific colors for light theme
scheme.getStyle(Token.MARKUP_TAG_DELIMITER).foreground = Color.decode("#800000"); // Dark red for < > /
scheme.getStyle(Token.MARKUP_TAG_NAME).foreground = keyColor; // Soft green for tag names (keys)
scheme.getStyle(Token.MARKUP_TAG_NAME).foreground = Color.decode("#4A7A4F"); // Soft green for tag names (keys)
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE).foreground = Color.decode("#FF0000"); // Red for attributes
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE_VALUE).foreground = Color.decode("#0000FF"); // Blue for values
}
// Apply background colors
textArea.setBackground(editorBackground);
textArea.setCaretColor(UIManager.getColor("EditorPane.caretForeground"));
textArea.setCaretColor(caretForeground);
textArea.setSelectionColor(selectionColor);
textArea.setCurrentLineHighlightColor(lineHighlight);
textArea.setMarkAllHighlightColor(markAllHighlightColor);
textArea.setMarkOccurrencesColor(markOccurrencesColor);
textArea.setHyperlinkForeground(hyperlinkColor);
textArea.setHyperlinkForeground(editorSelectionBackground);
textArea.setMatchedBracketBGColor(matchedBracketBackground);
textArea.setMatchedBracketBorderColor(lineNumberForeground);
textArea.setPaintMatchedBracketPair(true);
textArea.setAnimateBracketMatching(false);
textArea.setFadeCurrentLineHighlight(true);
// Reset gutter colors directly to ensure the change applies
Gutter gutter = RSyntaxUtilities.getGutter(textArea);
if (gutter != null) {
gutter.setBackground(editorBackground);
gutter.setBorderColor(gutterBorderColor);
gutter.setBorderColor(separatorForeground);
gutter.setLineNumberColor(lineNumberForeground);
}
}
@@ -0,0 +1,99 @@
package jadx.gui.ui.codearea.theme;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.StringUtils;
import jadx.gui.settings.JadxSettings;
public class EditorThemeManager {
private static final Logger LOG = LoggerFactory.getLogger(EditorThemeManager.class);
private final List<IEditorTheme> themes = new ArrayList<>();
private final Map<String, IEditorTheme> themesMap = new HashMap<>();
private IEditorTheme currentTheme = new FallbackEditorTheme();
public EditorThemeManager(JadxSettings settings) {
registerThemes();
if (StringUtils.isEmpty(settings.getEditorTheme())) {
// set default theme
IEditorTheme defaultTheme = themes.get(0);
settings.setEditorTheme(defaultTheme.getId());
}
}
private void registerThemes() {
registerTheme(new DynamicCodeAreaTheme());
registerTheme(new RSTABundledTheme("default"));
registerTheme(new RSTABundledTheme("eclipse"));
registerTheme(new RSTABundledTheme("idea"));
registerTheme(new RSTABundledTheme("vs"));
registerTheme(new RSTABundledTheme("dark"));
registerTheme(new RSTABundledTheme("monokai"));
registerTheme(new RSTABundledTheme("druid"));
}
public void registerTheme(IEditorTheme editorTheme) {
IEditorTheme prev = themesMap.put(editorTheme.getId(), editorTheme);
if (prev != null) {
themes.remove(prev);
}
themes.add(editorTheme);
}
public synchronized void setTheme(String id) {
if (currentTheme.getId().equals(id)) {
// already set
return;
}
// resolve new
IEditorTheme newTheme = themesMap.get(id);
if (newTheme == null) {
LOG.warn("Failed to resolve editor theme: {}", id);
return;
}
// unload current
unload();
// load new
try {
newTheme.load();
} catch (Throwable t) {
LOG.warn("Failed to load editor theme: {}", id, t);
}
currentTheme = newTheme;
}
public void apply(RSyntaxTextArea textArea) {
this.currentTheme.apply(textArea);
}
public ThemeIdAndName[] getThemeIdNameArray() {
return themes.stream()
.map(EditorThemeManager::toThemeIdAndName)
.toArray(ThemeIdAndName[]::new);
}
public ThemeIdAndName getCurrentThemeIdName() {
return toThemeIdAndName(currentTheme);
}
private static ThemeIdAndName toThemeIdAndName(IEditorTheme t) {
return new ThemeIdAndName(t.getId(), t.getName());
}
public void unload() {
try {
currentTheme.unload();
} catch (Throwable t) {
LOG.warn("Failed to unload editor theme: {}", currentTheme.getId(), t);
}
}
}
@@ -0,0 +1,28 @@
package jadx.gui.ui.codearea.theme;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Theme;
public class FallbackEditorTheme implements IEditorTheme {
private Theme baseTheme;
@Override
public String getId() {
return "fallback";
}
@Override
public String getName() {
return "Fallback";
}
@Override
public void load() {
baseTheme = new Theme(new RSyntaxTextArea());
}
@Override
public void apply(RSyntaxTextArea textArea) {
baseTheme.apply(textArea);
}
}
@@ -0,0 +1,20 @@
package jadx.gui.ui.codearea.theme;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
public interface IEditorTheme {
String getId();
String getName();
default void load() {
// optional method
}
void apply(RSyntaxTextArea textArea);
default void unload() {
// optional method
}
}
@@ -0,0 +1,55 @@
package jadx.gui.ui.codearea.theme;
import java.io.InputStream;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RSTABundledTheme implements IEditorTheme {
private static final Logger LOG = LoggerFactory.getLogger(RSTABundledTheme.class);
private static final String RSTA_THEME_PATH = "/org/fife/ui/rsyntaxtextarea/themes/";
private final String name;
private Theme loadedTheme;
public RSTABundledTheme(String name) {
this.name = name;
}
@Override
public String getId() {
return "RSTA:" + name;
}
@Override
public String getName() {
return name;
}
@Override
public void load() {
String path = RSTA_THEME_PATH + name + ".xml";
try {
try (InputStream is = RSTABundledTheme.class.getResourceAsStream(path)) {
loadedTheme = Theme.load(is);
}
} catch (Throwable t) {
LOG.error("Failed to load editor theme: {}", path, t);
loadedTheme = new Theme(new RSyntaxTextArea());
}
}
@Override
public void apply(RSyntaxTextArea textArea) {
loadedTheme.apply(textArea);
}
@Override
public void unload() {
loadedTheme = null;
}
}
@@ -0,0 +1,56 @@
package jadx.gui.ui.codearea.theme;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RSTAThemeXML implements IEditorTheme {
private static final Logger LOG = LoggerFactory.getLogger(RSTAThemeXML.class);
private final Path themePath;
private final String name;
private Theme loadedTheme;
public RSTAThemeXML(Path themeXmlPath, String name) {
this.themePath = themeXmlPath;
this.name = name;
}
@Override
public String getId() {
return "file:" + themePath;
}
@Override
public String getName() {
return name;
}
@Override
public void load() {
try {
try (InputStream is = Files.newInputStream(themePath)) {
loadedTheme = Theme.load(is);
}
} catch (Exception e) {
LOG.warn("Failed to load editor theme: {}", themePath, e);
loadedTheme = new Theme(new RSyntaxTextArea());
}
}
@Override
public void apply(RSyntaxTextArea textArea) {
loadedTheme.apply(textArea);
}
@Override
public void unload() {
loadedTheme = null;
}
}
@@ -0,0 +1,37 @@
package jadx.gui.ui.codearea.theme;
public class ThemeIdAndName {
private final String id;
private final String name;
public ThemeIdAndName(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
@Override
public final boolean equals(Object other) {
if (!(other instanceof ThemeIdAndName)) {
return false;
}
return id.equals(((ThemeIdAndName) other).id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return name;
}
}
@@ -242,7 +242,7 @@ preferences.integerFormat=Ganzzahlformat
preferences.font=Schriftart ändern
preferences.smali_font=Monospaced-Schriftart (Smali/Hex)
preferences.laf_theme=Thema
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Thema ändern
preferences.start_jobs=Autom. Hintergrunddekompilierung starten
preferences.select_font=Ändern
@@ -242,7 +242,7 @@ preferences.integerFormat=Integer format
preferences.font=Editor font
preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=Theme
preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Editor theme
preferences.start_jobs=Auto start background decompilation
preferences.select_font=Change
@@ -242,7 +242,7 @@ preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Fuente del editor
#preferences.smali_font=Monospaced font (Smali/Hex)
#preferences.laf_theme=Theme
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Tema del editor
preferences.start_jobs=Inicio autom. descompilación de fondo
preferences.select_font=Seleccionar
@@ -242,7 +242,7 @@ preferences.integerFormat=Format bilangan bulat
preferences.font=Font editor
preferences.smali_font=Font monospasi (Smali/Hex)
preferences.laf_theme=Tema
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Tema editor
preferences.start_jobs=Deskompilasi latar belakang otomatis
preferences.select_font=Ubah
@@ -242,7 +242,7 @@ preferences.raw_cfg=RAW CFG 그래프 생성
preferences.font=에디터 글씨체
#preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=테마
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=에디터 테마
preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경
@@ -242,7 +242,7 @@ preferences.raw_cfg=Gera gráficos CFG no formato RAW
preferences.font=Fonte do editor
#preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=Tema
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Tema do editor
preferences.start_jobs=Inicializar descompilação automaticamente em segundo-plano
preferences.select_font=Alterar
@@ -242,7 +242,7 @@ preferences.integerFormat=Формат чисел
preferences.font=Шрифт редактора Java
preferences.smali_font=Шрифт smali/HEX редактора
preferences.laf_theme=Тема приложения
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=Тема редактора
preferences.start_jobs=Автоматическая декомпиляция
preferences.select_font=Изменить
@@ -242,7 +242,7 @@ preferences.integerFormat=数值格式化
preferences.font=编辑器字体
preferences.smali_font=等宽字体 (Smali/Hex)
preferences.laf_theme=主题
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=编辑器主题
preferences.start_jobs=自动进行后台反编译
preferences.select_font=修改
@@ -242,7 +242,7 @@ preferences.integerFormat=整數模式
preferences.font=編輯器字型
preferences.smali_font=等寬字型 (Smali/Hex)
preferences.laf_theme=主題
#preferences.dynamic_editor_theme=Use dynamic editor theme
#preferences.dynamic_editor_theme=Use UI theme colors
preferences.theme=編輯器主題
preferences.start_jobs=自動開始背景反編譯
preferences.select_font=變更