gui: fix colors to match system theme, add editor theme selector (#297)

This commit is contained in:
Skylot
2018-06-19 23:12:53 +03:00
parent 10fd3652d4
commit 4e2e5aa975
11 changed files with 234 additions and 90 deletions
+17 -5
View File
@@ -21,15 +21,27 @@ public class JadxGUI {
if (!settings.processArgs(args)) {
return;
}
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(() -> {
MainWindow window = new MainWindow(settings);
window.open();
});
if (!tryDefaultLookAndFeel()) {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
SwingUtilities.invokeLater(new MainWindow(settings)::open);
} catch (Exception e) {
LOG.error("Error: {}", e.getMessage(), e);
System.exit(1);
}
}
private static boolean tryDefaultLookAndFeel() {
String defLaf = System.getProperty("swing.defaultlaf");
if (defLaf != null) {
try {
UIManager.setLookAndFeel(defLaf);
return true;
} catch (Exception e) {
LOG.error("Failed to set default laf: {}", defLaf, e);
}
}
return false;
}
}
@@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.cli.JadxCLIArgs;
import jadx.gui.ui.CodeArea;
import static jadx.gui.utils.Utils.FONT_HACK;
@@ -23,7 +24,7 @@ public class JadxSettings extends JadxCLIArgs {
private static final String USER_HOME = System.getProperty("user.home");
private static final int RECENT_FILES_COUNT = 15;
private static final int CURRENT_SETTINGS_VERSION = 1;
private static final int CURRENT_SETTINGS_VERSION = 2;
private static final Font DEFAULT_FONT = FONT_HACK != null ? FONT_HACK : new RSyntaxTextArea().getFont();
@@ -36,6 +37,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean checkForUpdates = false;
private List<String> recentFiles = new ArrayList<>();
private String fontStr = "";
private String editorThemePath = "";
private boolean autoStartJobs = false;
private int settingsVersion = 0;
@@ -210,6 +212,14 @@ public class JadxSettings extends JadxCLIArgs {
this.fontStr = font.getFontName() + addStyleName(font.getStyle()) + "-" + font.getSize();
}
public String getEditorThemePath() {
return editorThemePath;
}
public void setEditorThemePath(String editorThemePath) {
this.editorThemePath = editorThemePath;
}
private static String addStyleName(int style) {
switch (style) {
case Font.BOLD:
@@ -233,7 +243,10 @@ public class JadxSettings extends JadxCLIArgs {
setReplaceConsts(true);
setSkipResources(false);
setAutoStartJobs(false);
// fromVersion++;
fromVersion++;
}
if (fromVersion == 1) {
setEditorThemePath(CodeArea.getAllThemes()[0].getPath());
}
settingsVersion = CURRENT_SETTINGS_VERSION;
sync();
@@ -12,13 +12,17 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import say.swing.JFontChooser;
import jadx.gui.ui.CodeArea;
import jadx.gui.ui.CodeArea.EditorTheme;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
import static jadx.gui.utils.Utils.FONT_HACK;
@@ -196,12 +200,30 @@ public class JadxSettingsWindow extends JDialog {
LOG.info("Selected Font : {}", font);
settings.setFont(font);
mainWindow.updateFont(font);
mainWindow.loadSettings();
}
}
});
EditorTheme[] editorThemes = CodeArea.getAllThemes();
final JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
for (EditorTheme theme: editorThemes) {
if (theme.getPath().equals(settings.getEditorThemePath())) {
themesCbx.setSelectedItem(theme);
break;
}
}
themesCbx.addActionListener(e -> {
int i = themesCbx.getSelectedIndex();
EditorTheme editorTheme = editorThemes[i];
settings.setEditorThemePath(editorTheme.getPath());
mainWindow.setEditorTheme(editorTheme.getPath());
mainWindow.loadSettings();
});
SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor"));
other.addRow(NLS.str("preferences.font"), fontBtn);
other.addRow(NLS.str("preferences.theme"), themesCbx);
return other;
}
@@ -16,7 +16,6 @@ import java.awt.event.MouseEvent;
import org.fife.ui.rsyntaxtextarea.LinkGenerator;
import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenTypes;
import org.fife.ui.rtextarea.SearchContext;
@@ -32,14 +31,11 @@ import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.Position;
class CodeArea extends RSyntaxTextArea {
public final class CodeArea extends RSyntaxTextArea {
private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class);
private static final long serialVersionUID = 6312736869579635796L;
public static final Color CODE_BACKGROUND = new Color(0xFAFAFA);
public static final Color JUMP_TOKEN_FGD = new Color(0x491BA1);
private final CodePanel contentPanel;
private final JNode node;
@@ -48,10 +44,9 @@ class CodeArea extends RSyntaxTextArea {
this.node = panel.getNode();
setMarkOccurrences(true);
setBackground(CODE_BACKGROUND);
setAntiAliasingEnabled(true);
setEditable(false);
loadSettings();
Caret caret = getCaret();
if (caret instanceof DefaultCaret) {
((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
@@ -59,18 +54,13 @@ class CodeArea extends RSyntaxTextArea {
caret.setVisible(true);
setSyntaxEditingStyle(node.getSyntaxName());
if (node instanceof JClass) {
SyntaxScheme scheme = getSyntaxScheme();
scheme.getStyle(Token.FUNCTION).foreground = Color.BLACK;
setHyperlinksEnabled(true);
CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator((JClass) node);
setLinkGenerator(codeLinkProcessor);
addHyperlinkListener(codeLinkProcessor);
addMenuItems(this, (JClass) node);
}
registerWordHighlighter();
setText(node.getContent());
}
@@ -113,8 +103,21 @@ class CodeArea extends RSyntaxTextArea {
}
public void loadSettings() {
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
setFont(settings.getFont());
loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this);
}
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
area.setAntiAliasingEnabled(true);
mainWindow.getEditorTheme().apply(area);
JadxSettings settings = mainWindow.getSettings();
area.setFont(settings.getFont());
}
public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) {
RSyntaxTextArea area = new RSyntaxTextArea();
loadCommonSettings(mainWindow, area);
return area;
}
private boolean isJumpToken(Token token) {
@@ -129,6 +132,16 @@ class CodeArea extends RSyntaxTextArea {
if (node instanceof JClass) {
Position pos = getDefPosition((JClass) node, this, token.getOffset());
if (pos != null) {
// don't underline definition place
try {
int defLine = pos.getLine();
int lineOfOffset = getLineOfOffset(token.getOffset()) + 1;
if (defLine == lineOfOffset) {
return false;
}
} catch (BadLocationException e) {
return false;
}
return true;
}
}
@@ -136,13 +149,13 @@ class CodeArea extends RSyntaxTextArea {
return false;
}
@Override
public Color getForegroundForToken(Token t) {
if (isJumpToken(t)) {
return JUMP_TOKEN_FGD;
}
return super.getForegroundForToken(t);
}
// @Override
// public Color getForegroundForToken(Token t) {
// if (isJumpToken(t)) {
// return getHyperlinkForeground();
// }
// return super.getForegroundForToken(t);
// }
static Position getDefPosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
JavaNode node = getJavaNodeAtOffset(jCls, textArea, offset);
@@ -311,4 +324,38 @@ class CodeArea extends RSyntaxTextArea {
}
}
}
public static final class EditorTheme {
private final String name;
private final String path;
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;
}
}
public static EditorTheme[] getAllThemes() {
return new EditorTheme[]{
new EditorTheme("default", "/org/fife/ui/rsyntaxtextarea/themes/default.xml"),
new EditorTheme("eclipse", "/org/fife/ui/rsyntaxtextarea/themes/eclipse.xml"),
new EditorTheme("idea", "/org/fife/ui/rsyntaxtextarea/themes/idea.xml"),
new EditorTheme("vs", "/org/fife/ui/rsyntaxtextarea/themes/vs.xml"),
new EditorTheme("dark", "/org/fife/ui/rsyntaxtextarea/themes/dark.xml"),
new EditorTheme("monokai", "/org/fife/ui/rsyntaxtextarea/themes/monokai.xml")
};
}
}
@@ -24,7 +24,7 @@ class CodePanel extends ContentPanel {
searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea);
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
initLineNumbers();
setLayout(new BorderLayout());
add(searchBar, BorderLayout.NORTH);
@@ -34,6 +34,10 @@ class CodePanel extends ContentPanel {
Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction());
}
private void initLineNumbers() {
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
}
private class SearchAction extends AbstractAction {
private static final long serialVersionUID = 8650568214755387093L;
@@ -46,6 +50,8 @@ class CodePanel extends ContentPanel {
@Override
public void loadSettings() {
codeArea.loadSettings();
initLineNumbers();
updateUI();
}
@Override
@@ -23,6 +23,7 @@ import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rtextarea.SearchEngine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -152,7 +153,7 @@ public abstract class CommonSearchDialog extends JDialog {
resultsTable.setShowHorizontalLines(false);
resultsTable.setDragEnabled(false);
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
resultsTable.setBackground(CodeArea.CODE_BACKGROUND);
// resultsTable.setBackground(CodeArea.CODE_BACKGROUND);
resultsTable.setColumnSelectionAllowed(false);
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
resultsTable.setAutoscrolls(false);
@@ -259,6 +260,7 @@ public abstract class CommonSearchDialog extends JDialog {
if (!model.isAddDescColumn()) {
firstColMaxWidth = width;
}
Component nodeComp = null;
Component codeComp = null;
for (int col = 0; col < columnCount; col++) {
int colWidth = 50;
@@ -268,6 +270,9 @@ public abstract class CommonSearchDialog extends JDialog {
continue;
}
colWidth = Math.max(comp.getPreferredSize().width, colWidth);
if (nodeComp == null && col == 0) {
nodeComp = comp;
}
if (codeComp == null && col == 1) {
codeComp = comp;
}
@@ -281,10 +286,16 @@ public abstract class CommonSearchDialog extends JDialog {
TableColumn column = columnModel.getColumn(col);
column.setPreferredWidth(colWidth);
}
if (codeComp != null) {
setRowHeight(Math.max(20, codeComp.getPreferredSize().height + 4));
}
// setRowHeight(Math.max(nodeComp.getPreferredSize().height, codeComp.getPreferredSize().height + 4));
updateUI();
setRowHeight(Math.max(getHeight(nodeComp), getHeight(codeComp) + 4));
}
private int getHeight(@Nullable Component nodeComp) {
if (nodeComp != null) {
return Math.max(nodeComp.getHeight(), nodeComp.getPreferredSize().height);
}
return 0;
}
}
@@ -381,19 +392,15 @@ public abstract class CommonSearchDialog extends JDialog {
}
protected class ResultsTableCellRenderer implements TableCellRenderer {
private final Color selectedBackground;
private final Color selectedForeground;
private final Color foreground;
private final JLabel emptyLabel = new JLabel();
private final Color codeSelectedColor;
private final Color codeBackground;
private Map<Integer, Component> componentCache = new HashMap<>();
public ResultsTableCellRenderer() {
UIDefaults defaults = UIManager.getDefaults();
foreground = defaults.getColor("List.foreground");
selectedBackground = defaults.getColor("List.selectionBackground");
selectedForeground = defaults.getColor("List.selectionForeground");
RSyntaxTextArea area = CodeArea.getDefaultArea(mainWindow);
this.codeSelectedColor = area.getSelectionColor();
this.codeBackground = area.getBackground();
}
@Override
@@ -403,29 +410,38 @@ public abstract class CommonSearchDialog extends JDialog {
Component comp = componentCache.get(id);
if (comp == null) {
if (obj instanceof JNode) {
comp = makeCell((JNode) obj, column);
comp = makeCell(table, (JNode) obj, column);
componentCache.put(id, comp);
} else {
comp = emptyLabel;
}
}
updateSelection(comp, isSelected);
updateSelection(table, comp, isSelected);
return comp;
}
private void updateSelection(Component comp, boolean isSelected) {
if (isSelected) {
comp.setBackground(selectedBackground);
comp.setForeground(selectedForeground);
private void updateSelection(JTable table, Component comp, boolean isSelected) {
if (comp instanceof RSyntaxTextArea) {
if (isSelected) {
comp.setBackground(codeSelectedColor);
} else {
comp.setBackground(codeBackground);
}
} else {
comp.setBackground(CodeArea.CODE_BACKGROUND);
comp.setForeground(foreground);
if (isSelected) {
comp.setBackground(table.getSelectionBackground());
comp.setForeground(table.getSelectionForeground());
} else {
comp.setBackground(table.getBackground());
comp.setForeground(table.getForeground());
}
}
}
private Component makeCell(JNode node, int column) {
private Component makeCell(JTable table, JNode node, int column) {
if (column == 0) {
JLabel label = new JLabel(node.makeLongString() + " ", node.getIcon(), SwingConstants.LEFT);
label.setFont(table.getFont());
label.setOpaque(true);
label.setToolTipText(label.getText());
return label;
@@ -433,13 +449,13 @@ public abstract class CommonSearchDialog extends JDialog {
if (!node.hasDescString()) {
return emptyLabel;
}
RSyntaxTextArea textArea = new RSyntaxTextArea();
textArea.setFont(codeFont);
RSyntaxTextArea textArea = CodeArea.getDefaultArea(mainWindow);
textArea.setLayout(new GridLayout(1, 1));
textArea.setEditable(false);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
textArea.setText(" " + node.makeDescString());
textArea.setRows(1);
textArea.setColumns(textArea.getText().length());
textArea.setColumns(textArea.getText().length() + 1);
if (highlightText != null) {
SearchContext searchContext = new SearchContext(highlightText);
searchContext.setMatchCase(!highlightTextCaseInsensitive);
@@ -18,15 +18,13 @@ import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Token;
public class LineNumbers extends JPanel implements CaretListener {
private static final long serialVersionUID = -4978268673635308190L;
private static final Border OUTER = new MatteBorder(0, 0, 0, 1, Color.LIGHT_GRAY);
private static final int NUM_HEIGHT = Integer.MAX_VALUE - 1000000;
private static final Color NUM_FOREGROUND = Color.GRAY;
private static final Color NUM_BACKGROUND = CodeArea.CODE_BACKGROUND;
private static final Color CURRENT_LINE_FOREGROUND = new Color(227, 0, 0);
private CodeArea codeArea;
private boolean useSourceLines = true;
@@ -35,11 +33,19 @@ public class LineNumbers extends JPanel implements CaretListener {
private int lastLine;
private Map<String, FontMetrics> fonts;
private transient final Color numberColor;
private transient final Color currentColor;
private transient final Border border;
public LineNumbers(CodeArea component) {
this.codeArea = component;
setFont(component.getFont());
setBackground(NUM_BACKGROUND);
setForeground(NUM_FOREGROUND);
SyntaxScheme syntaxScheme = codeArea.getSyntaxScheme();
numberColor = syntaxScheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground;
currentColor = syntaxScheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground;
border = new MatteBorder(0, 0, 0, 1, syntaxScheme.getStyle(Token.COMMENT_MULTILINE).foreground);
setBackground(codeArea.getBackground());
setForeground(numberColor);
setBorderGap(5);
setPreferredWidth();
@@ -58,7 +64,7 @@ public class LineNumbers extends JPanel implements CaretListener {
public void setBorderGap(int borderGap) {
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
setBorder(new CompoundBorder(border, inner));
lastDigits = 0;
}
@@ -85,6 +91,8 @@ public class LineNumbers extends JPanel implements CaretListener {
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(codeArea.getFont());
FontMetrics fontMetrics = codeArea.getFontMetrics(codeArea.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
@@ -95,9 +103,9 @@ public class LineNumbers extends JPanel implements CaretListener {
while (rowStartOffset <= endOffset) {
try {
if (isCurrentLine(rowStartOffset)) {
g.setColor(CURRENT_LINE_FOREGROUND);
g.setColor(currentColor);
} else {
g.setColor(NUM_FOREGROUND);
g.setColor(numberColor);
}
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
@@ -2,8 +2,6 @@ package jadx.gui.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import ch.qos.logback.classic.Level;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
@@ -21,28 +19,25 @@ class LogViewer extends JDialog {
private final transient JadxSettings settings;
private transient RSyntaxTextArea textPane;
public LogViewer(JadxSettings settings) {
this.settings = settings;
initUI();
public LogViewer(MainWindow mainWindow) {
this.settings = mainWindow.getSettings();
initUI(mainWindow);
registerLogListener();
settings.loadWindowPos(this);
}
public final void initUI() {
textPane = new RSyntaxTextArea();
public final void initUI(MainWindow mainWindow) {
textPane = CodeArea.getDefaultArea(mainWindow);
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
JPanel controlPane = new JPanel();
controlPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
final JComboBox<Level> cb = new JComboBox<>(LEVEL_ITEMS);
cb.setSelectedItem(level);
cb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int i = cb.getSelectedIndex();
level = LEVEL_ITEMS[i];
registerLogListener();
}
cb.addActionListener(e -> {
int i = cb.getSelectedIndex();
level = LEVEL_ITEMS[i];
registerLogListener();
});
JLabel levelLabel = new JLabel(NLS.str("log.level"));
levelLabel.setLabelFor(cb);
@@ -52,11 +47,7 @@ class LogViewer extends JDialog {
JScrollPane scrollPane = new JScrollPane(textPane);
JButton close = new JButton(NLS.str("tabs.close"));
close.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
close();
}
});
close.addActionListener(event -> close());
close.setAlignmentX(0.5f);
Container contentPane = getContentPane();
@@ -84,11 +75,9 @@ class LogViewer extends JDialog {
@Override
public void onAppend(final String logStr) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
textPane.append(logStr);
textPane.updateUI();
}
SwingUtilities.invokeLater(() -> {
textPane.append(logStr);
textPane.updateUI();
});
}
});
@@ -23,10 +23,12 @@ import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,6 +100,7 @@ public class MainWindow extends JFrame {
private transient Link updateLink;
private transient ProgressPanel progressPane;
private transient BackgroundWorker backgroundWorker;
private transient Theme editorTheme;
public MainWindow(JadxSettings settings) {
this.wrapper = new JadxWrapper(settings);
@@ -107,9 +110,16 @@ public class MainWindow extends JFrame {
resetCache();
initUI();
initMenuAndToolbar();
applySettings();
checkForUpdate();
}
private void applySettings() {
setFont(settings.getFont());
setEditorTheme(settings.getEditorThemePath());
loadSettings();
}
public void open() {
pack();
setLocationAndPosition();
@@ -419,7 +429,7 @@ public class MainWindow extends JFrame {
Action logAction = new AbstractAction(NLS.str("menu.log"), ICON_LOG) {
@Override
public void actionPerformed(ActionEvent e) {
new LogViewer(settings).setVisible(true);
new LogViewer(MainWindow.this).setVisible(true);
}
};
logAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.log"));
@@ -613,6 +623,26 @@ public class MainWindow extends JFrame {
public void updateFont(Font font) {
setFont(font);
}
public void setEditorTheme(String editorThemePath) {
try {
editorTheme = Theme.load(getClass().getResourceAsStream(editorThemePath));
} catch (Exception e) {
LOG.error("Can't load editor theme from classpath: {}", editorThemePath);
try {
editorTheme = Theme.load(new FileInputStream(editorThemePath));
} catch (Exception e2) {
LOG.error("Can't load editor theme from file: {}", editorThemePath);
}
}
}
public Theme getEditorTheme() {
return editorTheme;
}
public void loadSettings() {
tabbedPane.loadSettings();
}
@@ -41,7 +41,7 @@ class TabbedPane extends JTabbedPane {
private transient JumpManager jumps = new JumpManager();
TabbedPane(MainWindow window) {
mainWindow = window;
this.mainWindow = window;
setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);