diff --git a/jadx-cli/src/main/java/jadx/cli/LogHelper.java b/jadx-cli/src/main/java/jadx/cli/LogHelper.java index c84f4b358..95bc796b5 100644 --- a/jadx-cli/src/main/java/jadx/cli/LogHelper.java +++ b/jadx-cli/src/main/java/jadx/cli/LogHelper.java @@ -66,10 +66,14 @@ public class LogHelper { return logLevelValue; } - private static void setLevelForClass(Class cls, Level level) { + public static void setLevelForClass(Class cls, Level level) { ((Logger) LoggerFactory.getLogger(cls)).setLevel(level); } + public static void setLevelForPackage(String pkgName, Level level) { + ((Logger) LoggerFactory.getLogger(pkgName)).setLevel(level); + } + /** * Try to detect if user provide custom logback config via -Dlogback.configurationFile= */ diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index aad4a329b..d77fa92ef 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -15,6 +15,10 @@ dependencies { implementation files('libs/jfontchooser-1.0.5.jar') implementation 'hu.kazocsaba:image-viewer:1.2.3' + implementation 'com.formdev:flatlaf:1.4' + implementation 'com.formdev:flatlaf-intellij-themes:1.4' + implementation 'org.reflections:reflections:0.9.12' + implementation 'com.google.code.gson:gson:2.8.6' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.commons:commons-text:1.9' diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java index 73b7832c0..9896d94a6 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java @@ -1,7 +1,6 @@ package jadx.gui; import javax.swing.SwingUtilities; -import javax.swing.UIManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +9,7 @@ import jadx.cli.LogHelper; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.ui.MainWindow; +import jadx.gui.utils.LafManager; import jadx.gui.utils.NLS; import jadx.gui.utils.SystemInfo; import jadx.gui.utils.logs.LogCollector; @@ -20,17 +20,15 @@ public class JadxGUI { public static void main(String[] args) { try { LogCollector.register(); - final JadxSettings settings = JadxSettingsAdapter.load(); + JadxSettings settings = JadxSettingsAdapter.load(); settings.setLogLevel(LogHelper.LogLevelEnum.INFO); // overwrite loaded settings by command line arguments if (!settings.overrideProvided(args)) { return; } - if (!tryDefaultLookAndFeel()) { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - NLS.setLocale(settings.getLangLocale()); printSystemInfo(); + LafManager.init(settings); + NLS.setLocale(settings.getLangLocale()); SwingUtilities.invokeLater(new MainWindow(settings)::init); } catch (Exception e) { @@ -39,19 +37,6 @@ public class JadxGUI { } } - 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; - } - private static void printSystemInfo() { if (LOG.isDebugEnabled()) { LOG.debug("Starting jadx-gui. Version: '{}'. JVM: {} {}. OS: {} {}", diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index dffe1340a..cf6466bb0 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -33,6 +33,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; +import jadx.gui.utils.LafManager; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; @@ -60,6 +61,7 @@ public class JadxSettings extends JadxCLIArgs { private String fontStr = ""; private String smaliFontStr = ""; private String editorThemePath = ""; + private String lafTheme = LafManager.SYSTEM_THEME_NAME; private LangLocale langLocale = NLS.defaultLocale(); private boolean autoStartJobs = false; protected String excludedPackages = ""; @@ -431,6 +433,14 @@ public class JadxSettings extends JadxCLIArgs { this.editorThemePath = editorThemePath; } + public String getLafTheme() { + return lafTheme; + } + + public void setLafTheme(String lafTheme) { + this.lafTheme = lafTheme; + } + public int getMainWindowExtendedState() { return mainWindowExtendedState; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index cc508380f..da46f9bbb 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -1,15 +1,50 @@ package jadx.gui.settings; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; -import javax.swing.*; +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.ScrollPaneConstants; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; @@ -27,6 +62,7 @@ import jadx.api.JadxArgs; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; +import jadx.gui.utils.LafManager; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -75,7 +111,7 @@ public class JadxSettingsWindow extends JDialog { leftPanel.add(makeDeobfuscationGroup()); leftPanel.add(makeRenameGroup()); leftPanel.add(makeProjectGroup()); - leftPanel.add(makeEditorGroup()); + leftPanel.add(makeAppearanceGroup()); leftPanel.add(makeOtherGroup()); leftPanel.add(makeSearchResGroup()); @@ -289,7 +325,7 @@ public class JadxSettingsWindow extends JDialog { return group; } - private SettingsGroup makeEditorGroup() { + private SettingsGroup makeAppearanceGroup() { JButton fontBtn = new JButton(NLS.str("preferences.select_font")); JButton smaliFontBtn = new JButton(NLS.str("preferences.select_smali_font")); @@ -308,9 +344,17 @@ public class JadxSettingsWindow extends JDialog { mainWindow.loadSettings(); }); - SettingsGroup group = new SettingsGroup(NLS.str("preferences.editor")); - JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn); + JComboBox lafCbx = new JComboBox<>(LafManager.getThemes()); + lafCbx.setSelectedItem(settings.getLafTheme()); + lafCbx.addActionListener(e -> { + settings.setLafTheme((String) lafCbx.getSelectedItem()); + mainWindow.loadSettings(); + }); + + SettingsGroup group = new SettingsGroup(NLS.str("preferences.appearance")); + group.addRow(NLS.str("preferences.laf_theme"), lafCbx); group.addRow(NLS.str("preferences.theme"), themesCbx); + JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn); JLabel smaliFontLabel = group.addRow(getSmaliFontLabelStr(), smaliFontBtn); fontBtn.addMouseListener(new MouseAdapter() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 4d046df6a..942fcd607 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -115,6 +115,7 @@ import jadx.gui.utils.CacheObject; import jadx.gui.utils.CodeUsageInfo; import jadx.gui.utils.FontUtils; import jadx.gui.utils.JumpPosition; +import jadx.gui.utils.LafManager; import jadx.gui.utils.Link; import jadx.gui.utils.NLS; import jadx.gui.utils.SystemInfo; @@ -1255,6 +1256,8 @@ public class MainWindow extends JFrame { } public void loadSettings() { + LafManager.updateLaf(settings); + Font font = settings.getFont(); Font largerFont = font.deriveFont(font.getSize() + 2.f); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java b/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java new file mode 100644 index 000000000..03a9ac792 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/LafManager.java @@ -0,0 +1,99 @@ +package jadx.gui.utils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.LookAndFeel; +import javax.swing.UIManager; + +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.formdev.flatlaf.FlatLaf; + +import ch.qos.logback.classic.Level; + +import jadx.cli.LogHelper; +import jadx.gui.settings.JadxSettings; + +public class LafManager { + private static final Logger LOG = LoggerFactory.getLogger(LafManager.class); + + public static final String SYSTEM_THEME_NAME = "default"; + private static final Map THEMES_MAP = initThemesMap(); + + public static void init(JadxSettings settings) { + if (setupLaf(getThemeClass(settings))) { + return; + } + setupLaf(SYSTEM_THEME_NAME); + settings.setLafTheme(SYSTEM_THEME_NAME); + settings.sync(); + } + + public static void updateLaf(JadxSettings settings) { + if (setupLaf(getThemeClass(settings))) { + FlatLaf.updateUI(); + } + } + + public static String[] getThemes() { + return THEMES_MAP.keySet().toArray(new String[0]); + } + + private static String getThemeClass(JadxSettings settings) { + return THEMES_MAP.get(settings.getLafTheme()); + } + + private static boolean setupLaf(String themeClass) { + if (SYSTEM_THEME_NAME.equals(themeClass)) { + return applyLaf(UIManager.getSystemLookAndFeelClassName()); + } + if (themeClass != null && !themeClass.isEmpty()) { + return applyLaf(themeClass); + } + return false; + } + + private static Map initThemesMap() { + Map map = new LinkedHashMap<>(); + map.put(SYSTEM_THEME_NAME, SYSTEM_THEME_NAME); + for (FlatLaf flatLafTheme : collectFlatLafThemes()) { + map.put(flatLafTheme.getName(), flatLafTheme.getClass().getName()); + } + return map; + } + + private static List collectFlatLafThemes() { + LogHelper.setLevelForPackage("org.reflections", Level.WARN); + Reflections reflections = new Reflections("com.formdev.flatlaf"); + Set> lafClasses = reflections.getSubTypesOf(FlatLaf.class); + + List themes = new ArrayList<>(lafClasses.size()); + for (Class lafClass : lafClasses) { + try { + themes.add(lafClass.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + // some classes not themes, ignore them + LOG.trace("Failed make instance for class: {}", lafClass.getName(), e); + } + } + themes.sort(Comparator.comparing(LookAndFeel::getName)); + return themes; + } + + private static boolean applyLaf(String theme) { + try { + UIManager.setLookAndFeel(theme); + return true; + } catch (Exception e) { + LOG.error("Failed to set laf to {}", theme, e); + return false; + } + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 44b0f65e5..ba4f97bd2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -113,7 +113,7 @@ about_dialog.title=Über JADX preferences.title=Einstellungen preferences.deobfuscation=Deobfuscation -preferences.editor=Editor +#preferences.appearance=Appearance preferences.decompile=Dekompilierung preferences.project=Projekt preferences.other=Andere @@ -139,6 +139,7 @@ preferences.cfg=Methoden generieren CFG-Grafiken (im 'Punkt'-Format) preferences.raw_cfg=RAW CFG-Grafiken generieren preferences.font=Schrift ändern #preferences.smali_font= +#preferences.laf_theme=Theme preferences.theme=Thema ändern preferences.start_jobs=Autom. Hintergrunddekompilierung starten preferences.select_font=Ändern diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index d72dbdc2c..0ec683f28 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -113,7 +113,7 @@ about_dialog.title=About JADX preferences.title=Preferences preferences.deobfuscation=Deobfuscation -preferences.editor=Editor +preferences.appearance=Appearance preferences.decompile=Decompilation preferences.project=Project preferences.other=Other @@ -139,6 +139,7 @@ preferences.cfg=Generate methods CFG graphs (in 'dot' format) preferences.raw_cfg=Generate RAW CFG graphs preferences.font=Editor font preferences.smali_font=Smali Editor font +preferences.laf_theme=Theme preferences.theme=Editor theme preferences.start_jobs=Auto start background decompilation preferences.select_font=Change diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index a3b2e8d6a..9e9759ced 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -113,7 +113,7 @@ about_dialog.title=Sobre JADX preferences.title=Preferencias preferences.deobfuscation=Desofuscación -preferences.editor=Editor +#preferences.appearance=Appearance preferences.decompile=Descompilación #preferences.project= preferences.other=Otros @@ -139,6 +139,7 @@ preferences.cfg=Generar methods CFG graphs (in 'dot' format) preferences.raw_cfg=Generate RAW CFG graphs preferences.font=Fuente del editor #preferences.smali_font= +#preferences.laf_theme=Theme preferences.theme=Tema del editor preferences.start_jobs=Inicio autom. descompilación de fondo preferences.select_font=Seleccionar diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index d9db1b9c0..ef72dc946 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -113,7 +113,7 @@ about_dialog.title=JADX 정보 preferences.title=설정 preferences.deobfuscation=난독화 해제 -preferences.editor=에디터 +#preferences.appearance=Appearance preferences.decompile=디컴파일 preferences.project=프로젝트 preferences.other=기타 @@ -139,6 +139,7 @@ preferences.cfg=메소드 CFG 그래프 생성 ('dot' 포맷) preferences.raw_cfg=RAW CFG 그래프 생성 preferences.font=에디터 글씨체 preferences.smali_font=Smali 에디터 글씨체 +#preferences.laf_theme=Theme preferences.theme=에디터 테마 preferences.start_jobs=백그라운드에서 디컴파일 자동 시작 preferences.select_font=변경 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index fa634b29e..58cd513b0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -113,7 +113,7 @@ about_dialog.title=关于 JADX preferences.title=首选项 preferences.deobfuscation=反混淆 -preferences.editor=编辑器 +#preferences.appearance=Appearance preferences.decompile=反编译 preferences.project=项目 preferences.other=其他 @@ -139,6 +139,7 @@ preferences.cfg=生成方法的 CFG 图(以 .dot 格式保存) preferences.raw_cfg=生成原始的 CFG 图 preferences.font=编辑器字体 #preferences.smali_font= +#preferences.laf_theme=Theme preferences.theme=编辑器主题 preferences.start_jobs=自动进行后台反编译 preferences.select_font=更改