Files
jadx/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java
T
Yaroslav fbebcb9845 feat(gui): add dynamic code editor theme from UI theme (PR #2471)
fix(gui): created dynamic theme for code editor (#1763)
2025-05-02 17:10:27 +01:00

812 lines
31 KiB
Java

package jadx.gui.settings.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
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.JSplitPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.plugins.events.JadxEvents;
import jadx.api.plugins.events.types.ReloadSettingsWindow;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.core.utils.GsonUtils;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.JadxUpdateChannel;
import jadx.gui.settings.LineNumbersMode;
import jadx.gui.settings.XposedCodegenLanguage;
import jadx.gui.settings.ui.cache.CacheSettingsGroup;
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.tab.dnd.TabDndGhostType;
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;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.DocumentUpdateListener;
public class JadxSettingsWindow extends JDialog {
private static final long serialVersionUID = -1804570470377354148L;
private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsWindow.class);
private final transient MainWindow mainWindow;
private final transient JadxSettings settings;
private final transient String startSettings;
private final transient String startSettingsHash;
private final transient LangLocale prevLang;
private final transient Consumer<ReloadSettingsWindow> reloadListener;
private transient boolean needReload = false;
private transient SettingsTree tree;
private List<ISettingsGroup> groups;
public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) {
this.mainWindow = mainWindow;
this.settings = settings;
this.startSettings = JadxSettingsAdapter.makeString(settings);
this.startSettingsHash = calcSettingsHash();
this.prevLang = settings.getLangLocale();
initUI();
setTitle(NLS.str("preferences.title"));
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
pack();
UiUtils.setWindowIcons(this);
setLocationRelativeTo(null);
if (!mainWindow.getSettings().loadWindowPos(this)) {
setSize(700, 800);
}
reloadListener = ev -> UiUtils.uiRun(this::reloadUI);
mainWindow.events().global().addListener(JadxEvents.RELOAD_SETTINGS_WINDOW, reloadListener);
}
private void reloadUI() {
int[] selection = tree.getSelectionRows();
closeGroups(false);
getContentPane().removeAll();
initUI();
// wait for other events to process
UiUtils.uiRun(() -> {
tree.setSelectionRows(selection);
SwingUtilities.updateComponentTreeUI(this);
});
}
private void initUI() {
JPanel wrapGroupPanel = new JPanel(new BorderLayout(10, 10));
groups = new ArrayList<>();
groups.add(makeDecompilationGroup());
groups.add(makeDeobfuscationGroup());
groups.add(makeRenameGroup());
groups.add(new CacheSettingsGroup(this));
groups.add(makeAppearanceGroup());
groups.add(new ShortcutsSettingsGroup(this, settings));
groups.add(makeSearchResGroup());
groups.add(makeProjectGroup());
groups.add(new PluginSettings(mainWindow, settings).build());
groups.add(makeOtherGroup());
tree = new SettingsTree();
tree.init(wrapGroupPanel, groups);
tree.setFocusable(true);
JScrollPane leftPane = new JScrollPane(tree);
leftPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 3, 3));
JScrollPane rightPane = new JScrollPane(wrapGroupPanel);
rightPane.getVerticalScrollBar().setUnitIncrement(16);
rightPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
rightPane.setBorder(BorderFactory.createEmptyBorder(10, 3, 3, 10));
JSplitPane splitPane = new JSplitPane();
splitPane.setResizeWeight(0.2);
splitPane.setLeftComponent(leftPane);
splitPane.setRightComponent(rightPane);
Container contentPane = getContentPane();
contentPane.add(splitPane, BorderLayout.CENTER);
contentPane.add(buildButtonsPane(), BorderLayout.PAGE_END);
KeyStroke strokeEsc = KeyStroke.getKeyStroke("ESCAPE");
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(strokeEsc, "ESCAPE");
getRootPane().getActionMap().put("ESCAPE", new ActionHandler(this::cancel));
}
private JPanel buildButtonsPane() {
JButton saveBtn = new JButton(NLS.str("preferences.save"));
saveBtn.addActionListener(event -> save());
JButton cancelButton = new JButton(NLS.str("preferences.cancel"));
cancelButton.addActionListener(event -> cancel());
JButton resetBtn = new JButton(NLS.str("preferences.reset"));
resetBtn.addActionListener(event -> reset());
JButton copyBtn = new JButton(NLS.str("preferences.copy"));
copyBtn.addActionListener(event -> copySettings());
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
buttonPane.add(resetBtn);
buttonPane.add(copyBtn);
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(saveBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
getRootPane().setDefaultButton(saveBtn);
return buttonPane;
}
private static void enableComponents(Container container, boolean enable) {
for (Component component : container.getComponents()) {
if (component instanceof Container) {
enableComponents((Container) component, enable);
}
component.setEnabled(enable);
}
}
private SettingsGroup makeDeobfuscationGroup() {
JCheckBox deobfOn = new JCheckBox();
deobfOn.setSelected(settings.isDeobfuscationOn());
deobfOn.addItemListener(e -> {
settings.setDeobfuscationOn(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
SpinnerNumberModel minLenModel = new SpinnerNumberModel(settings.getDeobfuscationMinLength(), 0, Integer.MAX_VALUE, 1);
JSpinner minLenSpinner = new JSpinner(minLenModel);
minLenSpinner.addChangeListener(e -> {
settings.setDeobfuscationMinLength((Integer) minLenSpinner.getValue());
needReload();
});
SpinnerNumberModel maxLenModel = new SpinnerNumberModel(settings.getDeobfuscationMaxLength(), 0, Integer.MAX_VALUE, 1);
JSpinner maxLenSpinner = new JSpinner(maxLenModel);
maxLenSpinner.addChangeListener(e -> {
settings.setDeobfuscationMaxLength((Integer) maxLenSpinner.getValue());
needReload();
});
JComboBox<ResourceNameSource> resNamesSource = new JComboBox<>(ResourceNameSource.values());
resNamesSource.setSelectedItem(settings.getResourceNameSource());
resNamesSource.addActionListener(e -> {
settings.setResourceNameSource((ResourceNameSource) resNamesSource.getSelectedItem());
needReload();
});
JComboBox<GeneratedRenamesMappingFileMode> generatedRenamesMappingFileModeCB =
new JComboBox<>(GeneratedRenamesMappingFileMode.values());
generatedRenamesMappingFileModeCB.setSelectedItem(settings.getGeneratedRenamesMappingFileMode());
generatedRenamesMappingFileModeCB.addActionListener(e -> {
GeneratedRenamesMappingFileMode newValue =
(GeneratedRenamesMappingFileMode) generatedRenamesMappingFileModeCB.getSelectedItem();
if (newValue != settings.getGeneratedRenamesMappingFileMode()) {
settings.setGeneratedRenamesMappingFileMode(newValue);
needReload();
}
});
JButton editWhitelistedEntities = new JButton(NLS.str("preferences.excludedPackages.button"));
editWhitelistedEntities.addActionListener(event -> {
String prevWhitelistedEntities = settings.getDeobfuscationWhitelistStr();
String result = JOptionPane.showInputDialog(this,
NLS.str("preferences.deobfuscation_whitelist.editDialog"),
prevWhitelistedEntities);
if (result != null) {
settings.setDeobfuscationWhitelistStr(result);
if (!prevWhitelistedEntities.equals(result)) {
needReload();
}
}
});
SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation"));
deobfGroup.addRow(NLS.str("preferences.deobfuscation_on"), deobfOn);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_name_source"), resNamesSource);
deobfGroup.addRow(NLS.str("preferences.generated_renames_mapping_file_mode"), generatedRenamesMappingFileModeCB);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_whitelist"),
NLS.str("preferences.deobfuscation_whitelist.tooltip"), editWhitelistedEntities);
deobfGroup.end();
Collection<JComponent> connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner);
deobfOn.addItemListener(e -> enableComponentList(connectedComponents, e.getStateChange() == ItemEvent.SELECTED));
enableComponentList(connectedComponents, settings.isDeobfuscationOn());
return deobfGroup;
}
private SettingsGroup makeRenameGroup() {
JCheckBox renameCaseSensitive = new JCheckBox();
renameCaseSensitive.setSelected(settings.isRenameCaseSensitive());
renameCaseSensitive.addItemListener(e -> {
settings.updateRenameFlag(JadxArgs.RenameEnum.CASE, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renameValid = new JCheckBox();
renameValid.setSelected(settings.isRenameValid());
renameValid.addItemListener(e -> {
settings.updateRenameFlag(JadxArgs.RenameEnum.VALID, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renamePrintable = new JCheckBox();
renamePrintable.setSelected(settings.isRenamePrintable());
renamePrintable.addItemListener(e -> {
settings.updateRenameFlag(JadxArgs.RenameEnum.PRINTABLE, e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JComboBox<UseSourceNameAsClassNameAlias> useSourceNameAsClassNameAlias = new JComboBox<>(UseSourceNameAsClassNameAlias.values());
useSourceNameAsClassNameAlias.setSelectedItem(settings.getUseSourceNameAsClassNameAlias());
useSourceNameAsClassNameAlias.addActionListener(e -> {
settings.setUseSourceNameAsClassNameAlias((UseSourceNameAsClassNameAlias) useSourceNameAsClassNameAlias.getSelectedItem());
needReload();
});
JSpinner repeatLimit = new JSpinner(new SpinnerNumberModel(settings.getSourceNameRepeatLimit(), 1, Integer.MAX_VALUE, 1));
repeatLimit.addChangeListener(e -> {
settings.setSourceNameRepeatLimit((Integer) repeatLimit.getValue());
needReload();
});
SettingsGroup group = new SettingsGroup(NLS.str("preferences.rename"));
group.addRow(NLS.str("preferences.rename_case"), renameCaseSensitive);
group.addRow(NLS.str("preferences.rename_valid"), renameValid);
group.addRow(NLS.str("preferences.rename_printable"), renamePrintable);
group.addRow(NLS.str("preferences.rename_use_source_name_as_class_name_alias"), useSourceNameAsClassNameAlias);
group.addRow(NLS.str("preferences.rename_source_name_repeat_limit"), repeatLimit);
return group;
}
private void enableComponentList(Collection<JComponent> connectedComponents, boolean enabled) {
connectedComponents.forEach(comp -> comp.setEnabled(enabled));
}
private SettingsGroup makeProjectGroup() {
JComboBox<JadxSettings.SAVEOPTION> dropdown = new JComboBox<>(JadxSettings.SAVEOPTION.values());
dropdown.setSelectedItem(settings.getSaveOption());
dropdown.addActionListener(e -> {
settings.setSaveOption((JadxSettings.SAVEOPTION) dropdown.getSelectedItem());
needReload();
});
SettingsGroup group = new SettingsGroup(NLS.str("preferences.project"));
group.addRow(NLS.str("preferences.saveOption"), dropdown);
return group;
}
private SettingsGroup makeAppearanceGroup() {
JComboBox<LangLocale> languageCbx = new JComboBox<>(NLS.getLangLocales());
for (LangLocale locale : NLS.getLangLocales()) {
if (locale.equals(settings.getLangLocale())) {
languageCbx.setSelectedItem(locale);
break;
}
}
languageCbx.addActionListener(e -> settings.setLangLocale((LangLocale) languageCbx.getSelectedItem()));
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;
}
}
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());
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.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);
fontBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Font font = new JadxFontDialog(JadxSettingsWindow.this, NLS.str("preferences.font"))
.select(settings.getFont(), false);
if (font != null) {
LOG.debug("Selected Font: {}", font);
settings.setFont(font);
mainWindow.loadSettings();
fontLabel.setText(getFontLabelStr());
}
}
});
smaliFontBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Font font = new JadxFontDialog(JadxSettingsWindow.this, NLS.str("preferences.smali_font"))
.select(settings.getSmaliFont(), true);
if (font != null) {
LOG.debug("Selected Font: {} for smali", font);
settings.setSmaliFont(font);
mainWindow.loadSettings();
smaliFontLabel.setText(getSmaliFontLabelStr());
}
}
});
JComboBox<TabDndGhostType> tabDndGhostTypeCbx = new JComboBox<>(TabDndGhostType.values());
tabDndGhostTypeCbx.setSelectedItem(settings.getTabDndGhostType());
tabDndGhostTypeCbx.addActionListener(e -> {
settings.setTabDndGhostType((TabDndGhostType) tabDndGhostTypeCbx.getSelectedItem());
mainWindow.loadSettings();
});
group.addRow(NLS.str("preferences.tab_dnd_appearance"), tabDndGhostTypeCbx);
return group;
}
private String getFontLabelStr() {
Font font = settings.getFont();
String fontStyleName = FontUtils.convertFontStyleToString(font.getStyle());
return NLS.str("preferences.font") + ": " + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize();
}
private String getSmaliFontLabelStr() {
Font font = settings.getSmaliFont();
String fontStyleName = FontUtils.convertFontStyleToString(font.getStyle());
return NLS.str("preferences.smali_font") + ": " + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize();
}
private SettingsGroup makeDecompilationGroup() {
JCheckBox useDx = new JCheckBox();
useDx.setSelected(settings.isUseDx());
useDx.addItemListener(e -> {
settings.setUseDx(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JComboBox<DecompilationMode> decompilationModeComboBox = new JComboBox<>(DecompilationMode.values());
decompilationModeComboBox.setSelectedItem(settings.getDecompilationMode());
decompilationModeComboBox.addActionListener(e -> {
settings.setDecompilationMode((DecompilationMode) decompilationModeComboBox.getSelectedItem());
needReload();
});
JCheckBox showInconsistentCode = new JCheckBox();
showInconsistentCode.setSelected(settings.isShowInconsistentCode());
showInconsistentCode.addItemListener(e -> {
settings.setShowInconsistentCode(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox resourceDecode = new JCheckBox();
resourceDecode.setSelected(settings.isSkipResources());
resourceDecode.addItemListener(e -> {
settings.setSkipResources(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
// fix for #1331
int threadsCountValue = settings.getThreadsCount();
int threadsCountMax = Math.max(2, Math.max(threadsCountValue, Runtime.getRuntime().availableProcessors() * 2));
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(threadsCountValue, 1, threadsCountMax, 1);
JSpinner threadsCount = new JSpinner(spinnerModel);
threadsCount.addChangeListener(e -> {
settings.setThreadsCount((Integer) threadsCount.getValue());
needReload();
});
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
editExcludedPackages.addActionListener(event -> {
String oldExcludedPackages = settings.getExcludedPackages();
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
settings.getExcludedPackages());
if (result != null) {
settings.setExcludedPackages(result);
if (!oldExcludedPackages.equals(result)) {
needReload();
}
}
});
JCheckBox autoStartJobs = new JCheckBox();
autoStartJobs.setSelected(settings.isAutoStartJobs());
autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED));
JCheckBox escapeUnicode = new JCheckBox();
escapeUnicode.setSelected(settings.isEscapeUnicode());
escapeUnicode.addItemListener(e -> {
settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox replaceConsts = new JCheckBox();
replaceConsts.setSelected(settings.isReplaceConsts());
replaceConsts.addItemListener(e -> {
settings.setReplaceConsts(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox respectBytecodeAccessModifiers = new JCheckBox();
respectBytecodeAccessModifiers.setSelected(settings.isRespectBytecodeAccessModifiers());
respectBytecodeAccessModifiers.addItemListener(e -> {
settings.setRespectBytecodeAccessModifiers(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox useImports = new JCheckBox();
useImports.setSelected(settings.isUseImports());
useImports.addItemListener(e -> {
settings.setUseImports(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox useDebugInfo = new JCheckBox();
useDebugInfo.setSelected(settings.isDebugInfo());
useDebugInfo.addItemListener(e -> {
settings.setDebugInfo(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox inlineAnonymous = new JCheckBox();
inlineAnonymous.setSelected(settings.isInlineAnonymousClasses());
inlineAnonymous.addItemListener(e -> {
settings.setInlineAnonymousClasses(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox inlineMethods = new JCheckBox();
inlineMethods.setSelected(settings.isInlineMethods());
inlineMethods.addItemListener(e -> {
settings.setInlineMethods(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox inlineKotlinLambdas = new JCheckBox();
inlineKotlinLambdas.setSelected(settings.isAllowInlineKotlinLambda());
inlineKotlinLambdas.addItemListener(e -> {
settings.setAllowInlineKotlinLambda(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox moveInnerClasses = new JCheckBox();
moveInnerClasses.setSelected(settings.isMoveInnerClasses());
moveInnerClasses.addItemListener(e -> {
settings.setMoveInnerClasses(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox extractFinally = new JCheckBox();
extractFinally.setSelected(settings.isExtractFinally());
extractFinally.addItemListener(e -> {
settings.setExtractFinally(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox restoreSwitchOverString = new JCheckBox();
restoreSwitchOverString.setSelected(settings.isRestoreSwitchOverString());
restoreSwitchOverString.addItemListener(e -> {
settings.setRestoreSwitchOverString(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox fsCaseSensitive = new JCheckBox();
fsCaseSensitive.setSelected(settings.isFsCaseSensitive());
fsCaseSensitive.addItemListener(e -> {
settings.setFsCaseSensitive(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JComboBox<UseKotlinMethodsForVarNames> kotlinRenameVars = new JComboBox<>(UseKotlinMethodsForVarNames.values());
kotlinRenameVars.setSelectedItem(settings.getUseKotlinMethodsForVarNames());
kotlinRenameVars.addActionListener(e -> {
settings.setUseKotlinMethodsForVarNames((UseKotlinMethodsForVarNames) kotlinRenameVars.getSelectedItem());
needReload();
});
JComboBox<CommentsLevel> commentsLevel = new JComboBox<>(CommentsLevel.values());
commentsLevel.setSelectedItem(settings.getCommentsLevel());
commentsLevel.addActionListener(e -> {
settings.setCommentsLevel((CommentsLevel) commentsLevel.getSelectedItem());
needReload();
});
JComboBox<IntegerFormat> integerFormat = new JComboBox<>(IntegerFormat.values());
integerFormat.setSelectedItem(settings.getIntegerFormat());
integerFormat.addActionListener(e -> {
settings.setIntegerFormat((IntegerFormat) integerFormat.getSelectedItem());
needReload();
});
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount);
other.addRow(NLS.str("preferences.excludedPackages"),
NLS.str("preferences.excludedPackages.tooltip"), editExcludedPackages);
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
other.addRow(NLS.str("preferences.decompilationMode"), decompilationModeComboBox);
other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode);
other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode);
other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts);
other.addRow(NLS.str("preferences.respectBytecodeAccessModifiers"), respectBytecodeAccessModifiers);
other.addRow(NLS.str("preferences.useImports"), useImports);
other.addRow(NLS.str("preferences.useDebugInfo"), useDebugInfo);
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
other.addRow(NLS.str("preferences.inlineKotlinLambdas"), inlineKotlinLambdas);
other.addRow(NLS.str("preferences.moveInnerClasses"), moveInnerClasses);
other.addRow(NLS.str("preferences.extractFinally"), extractFinally);
other.addRow(NLS.str("preferences.restoreSwitchOverString"), restoreSwitchOverString);
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
other.addRow(NLS.str("preferences.useDx"), useDx);
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
other.addRow(NLS.str("preferences.useKotlinMethodsForVarNames"), kotlinRenameVars);
other.addRow(NLS.str("preferences.commentsLevel"), commentsLevel);
other.addRow(NLS.str("preferences.integerFormat"), integerFormat);
return other;
}
private SettingsGroup makeOtherGroup() {
JComboBox<LineNumbersMode> lineNumbersMode = new JComboBox<>(LineNumbersMode.values());
lineNumbersMode.setSelectedItem(settings.getLineNumbersMode());
lineNumbersMode.addActionListener(e -> {
settings.setLineNumbersMode((LineNumbersMode) lineNumbersMode.getSelectedItem());
mainWindow.loadSettings();
});
JCheckBox jumpOnDoubleClick = new JCheckBox();
jumpOnDoubleClick.setSelected(settings.isJumpOnDoubleClick());
jumpOnDoubleClick.addItemListener(e -> settings.setJumpOnDoubleClick(e.getStateChange() == ItemEvent.SELECTED));
JCheckBox useAltFileDialog = new JCheckBox();
useAltFileDialog.setSelected(settings.isUseAlternativeFileDialog());
useAltFileDialog.addItemListener(e -> settings.setUseAlternativeFileDialog(e.getStateChange() == ItemEvent.SELECTED));
JCheckBox update = new JCheckBox();
update.setSelected(settings.isCheckForUpdates());
update.addItemListener(e -> settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED));
JCheckBox cfg = new JCheckBox();
cfg.setSelected(settings.isCfgOutput());
cfg.addItemListener(e -> {
settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox rawCfg = new JCheckBox();
rawCfg.setSelected(settings.isRawCfgOutput());
rawCfg.addItemListener(e -> {
settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JComboBox<XposedCodegenLanguage> xposedCodegenLanguage =
new JComboBox<>(XposedCodegenLanguage.getEntries().toArray(new XposedCodegenLanguage[0]));
xposedCodegenLanguage.setSelectedItem(settings.getXposedCodegenLanguage());
xposedCodegenLanguage.addActionListener(e -> {
settings.setXposedCodegenLanguage((XposedCodegenLanguage) xposedCodegenLanguage.getSelectedItem());
mainWindow.loadSettings();
});
JComboBox<JadxUpdateChannel> updateChannel =
new JComboBox<>(JadxUpdateChannel.getEntries().toArray(new JadxUpdateChannel[0]));
updateChannel.setSelectedItem(settings.getJadxUpdateChannel());
updateChannel.addActionListener(e -> {
settings.setJadxUpdateChannel((JadxUpdateChannel) updateChannel.getSelectedItem());
mainWindow.loadSettings();
});
SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
group.addRow(NLS.str("preferences.lineNumbersMode"), lineNumbersMode);
group.addRow(NLS.str("preferences.jumpOnDoubleClick"), jumpOnDoubleClick);
group.addRow(NLS.str("preferences.useAlternativeFileDialog"), useAltFileDialog);
group.addRow(NLS.str("preferences.cfg"), cfg);
group.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
group.addRow(NLS.str("preferences.xposed_codegen_language"), xposedCodegenLanguage);
group.addRow(NLS.str("preferences.check_for_updates"), update);
group.addRow(NLS.str("preferences.update_channel"), updateChannel);
return group;
}
private SettingsGroup makeSearchResGroup() {
JSpinner resultsPerPage = new JSpinner(
new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1));
resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue()));
JSpinner sizeLimit = new JSpinner(
new SpinnerNumberModel(settings.getSrhResourceSkipSize(), 0, Integer.MAX_VALUE, 1));
sizeLimit.addChangeListener(ev -> settings.setSrhResourceSkipSize((Integer) sizeLimit.getValue()));
JTextField fileExtField = new JTextField();
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> {
String ext = fileExtField.getText();
settings.setSrhResourceFileExt(ext);
}));
fileExtField.setText(settings.getSrhResourceFileExt());
SettingsGroup searchGroup = new SettingsGroup(NLS.str("preferences.search_group_title"));
searchGroup.addRow(NLS.str("preferences.search_results_per_page"), resultsPerPage);
searchGroup.addRow(NLS.str("preferences.res_skip_file"), sizeLimit);
searchGroup.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
return searchGroup;
}
private void closeGroups(boolean save) {
for (ISettingsGroup group : groups) {
group.close(save);
}
}
private void save() {
closeGroups(true);
settings.sync();
enableComponents(this, false);
SwingUtilities.invokeLater(() -> {
if (shouldReload()) {
mainWindow.getShortcutsController().loadSettings();
mainWindow.reopen();
}
if (!settings.getLangLocale().equals(prevLang)) {
JOptionPane.showMessageDialog(
this,
NLS.str("msg.language_changed", settings.getLangLocale()),
NLS.str("msg.language_changed_title", settings.getLangLocale()),
JOptionPane.INFORMATION_MESSAGE);
}
dispose();
});
}
private void cancel() {
closeGroups(false);
JadxSettingsAdapter.fill(settings, startSettings);
mainWindow.loadSettings();
dispose();
}
private void reset() {
int res = JOptionPane.showConfirmDialog(
JadxSettingsWindow.this,
NLS.str("preferences.reset_message"),
NLS.str("preferences.reset_title"),
JOptionPane.YES_NO_OPTION);
if (res == JOptionPane.YES_OPTION) {
String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault());
JadxSettingsAdapter.fill(settings, defaults);
mainWindow.loadSettings();
needReload();
getContentPane().removeAll();
initUI();
pack();
repaint();
}
}
private void copySettings() {
JsonObject settingsJson = JadxSettingsAdapter.makeJsonObject(this.settings);
// remove irrelevant preferences
settingsJson.remove("windowPos");
settingsJson.remove("mainWindowExtendedState");
settingsJson.remove("lastSaveProjectPath");
settingsJson.remove("lastOpenFilePath");
settingsJson.remove("lastSaveFilePath");
settingsJson.remove("recentProjects");
String settingsText = GsonUtils.buildGson().toJson(settingsJson);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(settingsText);
clipboard.setContents(selection, selection);
JOptionPane.showMessageDialog(
JadxSettingsWindow.this,
NLS.str("preferences.copy_message"));
}
public void needReload() {
needReload = true;
}
private boolean shouldReload() {
return needReload || !startSettingsHash.equals(calcSettingsHash());
}
@SuppressWarnings("resource")
private String calcSettingsHash() {
JadxDecompiler decompiler = mainWindow.getWrapper().getCurrentDecompiler().orElse(null);
return settings.toJadxArgs().makeCodeArgsHash(decompiler);
}
public MainWindow getMainWindow() {
return mainWindow;
}
@Override
public void dispose() {
mainWindow.events().global().removeListener(JadxEvents.RELOAD_SETTINGS_WINDOW, reloadListener);
settings.saveWindowPos(this);
super.dispose();
}
}