fix: use wrap layout for options in search dialog, add size limit option

This commit is contained in:
Skylot
2025-05-29 22:44:21 +01:00
parent 59b560b553
commit 6b54cde89c
21 changed files with 289 additions and 130 deletions
@@ -423,6 +423,15 @@ public class Utils {
return collection.iterator().next();
}
public static <T> boolean isSetContainsAny(Set<T> inputSet, Set<T> searchKeys) {
for (T t : inputSet) {
if (searchKeys.contains(t)) {
return true;
}
}
return false;
}
@Nullable
public static <T> T first(List<T> list) {
if (list.isEmpty()) {
@@ -15,9 +15,11 @@ import jadx.gui.utils.NLS;
public class SearchSettings {
private final String searchString;
private final boolean useRegex;
private final boolean ignoreCase;
private final String searchPkgStr;
private boolean useRegex;
private boolean ignoreCase;
private String searchPkgStr;
private String resFilterStr;
private int resSizeLimit; // in MB
private JClass activeCls;
private JResource activeResource;
@@ -25,15 +27,11 @@ public class SearchSettings {
private ISearchMethod searchMethod;
private JavaPackage searchPackage;
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex, String searchPkgStr) {
public SearchSettings(String searchString) {
this.searchString = searchString;
this.useRegex = useRegex;
this.ignoreCase = ignoreCase;
this.searchPkgStr = searchPkgStr;
}
@Nullable
public String prepare(MainWindow mainWindow) {
public @Nullable String prepare(MainWindow mainWindow) {
if (useRegex) {
try {
int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0;
@@ -62,10 +60,18 @@ public class SearchSettings {
return this.useRegex;
}
public void setUseRegex(boolean useRegex) {
this.useRegex = useRegex;
}
public boolean isIgnoreCase() {
return this.ignoreCase;
}
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
public JavaPackage getSearchPackage() {
return this.searchPackage;
}
@@ -74,6 +80,10 @@ public class SearchSettings {
return cls.getJavaPackage().isDescendantOf(searchPackage);
}
public void setSearchPkgStr(String searchPkgStr) {
this.searchPkgStr = searchPkgStr;
}
public String getSearchString() {
return this.searchString;
}
@@ -101,4 +111,20 @@ public class SearchSettings {
public ISearchMethod getSearchMethod() {
return searchMethod;
}
public String getResFilterStr() {
return resFilterStr;
}
public void setResFilterStr(String resFilterStr) {
this.resFilterStr = resFilterStr;
}
public int getResSizeLimit() {
return resSizeLimit;
}
public void setResSizeLimit(int resSizeLimit) {
this.resSizeLimit = resSizeLimit;
}
}
@@ -48,8 +48,8 @@ public class ResourceSearchProvider implements ISearchProvider {
public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings, SearchDialog searchDialog) {
this.searchSettings = searchSettings;
this.sizeLimit = mw.getSettings().getSrhResourceSkipSize() * 1048576;
this.extSet = buildAllowedFilesExtensions(mw.getSettings().getSrhResourceFileExt());
this.extSet = buildAllowedFilesExtensions(searchSettings.getResFilterStr());
this.sizeLimit = searchSettings.getResSizeLimit() * 1024 * 1024;
this.searchDialog = searchDialog;
JResource activeResource = searchSettings.getActiveResource();
if (activeResource != null) {
@@ -168,8 +168,13 @@ public class ResourceSearchProvider implements ISearchProvider {
}
private Set<String> buildAllowedFilesExtensions(String srhResourceFileExt) {
String str = srhResourceFileExt.trim();
if (str.isEmpty() || str.equals("*")) {
anyExt = true;
return Collections.emptySet();
}
Set<String> set = new HashSet<>();
for (String extStr : srhResourceFileExt.split("[|.]")) {
for (String extStr : str.split("[|.]")) {
String ext = extStr.trim();
if (!ext.isEmpty()) {
anyExt = ext.equals("*");
@@ -249,6 +249,22 @@ public class JadxProject {
changed();
}
public void setSearchResourcesFilter(String searchResourcesFilter) {
data.setSearchResourcesFilter(searchResourcesFilter);
}
public String getSearchResourcesFilter() {
return data.getSearchResourcesFilter();
}
public void setSearchResourcesSizeLimit(int searchResourcesSizeLimit) {
data.setSearchResourcesSizeLimit(searchResourcesSizeLimit);
}
public int getSearchResourcesSizeLimit() {
return data.getSearchResourcesSizeLimit();
}
private void changed() {
JadxSettings settings = mainWindow.getSettings();
if (settings != null && settings.getSaveOption() == JadxSettings.SAVEOPTION.ALWAYS) {
@@ -112,7 +112,6 @@ public class JadxSettings extends JadxCLIArgs {
private int mainWindowExtendedState = JFrame.NORMAL;
private boolean codeAreaLineWrap = false;
private int srhResourceSkipSize = 1000;
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
private int searchResultsPerPage = 50;
private boolean useAutoSearch = true;
private boolean keepCommonDialogOpen = false;
@@ -645,14 +644,6 @@ public class JadxSettings extends JadxCLIArgs {
srhResourceSkipSize = size;
}
public String getSrhResourceFileExt() {
return srhResourceFileExt;
}
public void setSrhResourceFileExt(String all) {
srhResourceFileExt = all.trim();
}
public int getSearchResultsPerPage() {
return searchResultsPerPage;
}
@@ -22,6 +22,9 @@ public class ProjectData {
private @Nullable String cacheDir; // don't use relative path adapter
private boolean enableLiveReload = false;
private List<String> searchHistory = new ArrayList<>();
private String searchResourcesFilter = "*";
private int searchResourcesSizeLimit = 0; // in MB
protected Map<String, String> pluginOptions = new HashMap<>();
public List<Path> getFiles() {
@@ -101,6 +104,22 @@ public class ProjectData {
this.searchHistory = searchHistory;
}
public String getSearchResourcesFilter() {
return searchResourcesFilter;
}
public void setSearchResourcesFilter(String searchResourcesFilter) {
this.searchResourcesFilter = searchResourcesFilter;
}
public int getSearchResourcesSizeLimit() {
return searchResourcesSizeLimit;
}
public void setSearchResourcesSizeLimit(int searchResourcesSizeLimit) {
this.searchResourcesSizeLimit = searchResourcesSizeLimit;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
@@ -32,7 +32,6 @@ 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;
@@ -76,7 +75,6 @@ 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;
@@ -138,7 +136,6 @@ public class JadxSettingsWindow extends JDialog {
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());
@@ -638,6 +635,10 @@ public class JadxSettingsWindow extends JDialog {
jumpOnDoubleClick.setSelected(settings.isJumpOnDoubleClick());
jumpOnDoubleClick.addItemListener(e -> settings.setJumpOnDoubleClick(e.getStateChange() == ItemEvent.SELECTED));
JSpinner resultsPerPage = new JSpinner(
new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1));
resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue()));
JCheckBox useAltFileDialog = new JCheckBox();
useAltFileDialog.setSelected(settings.isUseAlternativeFileDialog());
useAltFileDialog.addItemListener(e -> settings.setUseAlternativeFileDialog(e.getStateChange() == ItemEvent.SELECTED));
@@ -683,39 +684,17 @@ public class JadxSettingsWindow extends JDialog {
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.disable_tooltip_on_hover"), disableTooltipOnHover);
group.addRow(NLS.str("preferences.search_results_per_page"), resultsPerPage);
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);
group.addRow(NLS.str("preferences.disable_tooltip_on_hover"), disableTooltipOnHover);
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);
@@ -3,7 +3,7 @@ package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -26,9 +26,12 @@ import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.WindowConstants;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeListener;
import org.jetbrains.annotations.Nullable;
@@ -46,8 +49,10 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
import jadx.core.dex.nodes.PackageNode;
import jadx.core.utils.ListUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.gui.jobs.ITaskInfo;
import jadx.gui.jobs.ITaskProgress;
import jadx.gui.search.SearchSettings;
@@ -63,14 +68,16 @@ import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.Icons;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.SimpleListener;
import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.cache.ValueCache;
import jadx.gui.utils.layout.WrapLayout;
import jadx.gui.utils.rx.RxUtils;
import jadx.gui.utils.ui.DocumentUpdateListener;
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.ACTIVE_TAB;
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.CLASS;
@@ -92,7 +99,7 @@ public class SearchDialog extends CommonSearchDialog {
}
public static void searchInActiveTab(MainWindow window, SearchPreset preset) {
SearchDialog searchDialog = new SearchDialog(window, preset, EnumSet.of(SearchOptions.ACTIVE_TAB));
SearchDialog searchDialog = new SearchDialog(window, preset, EnumSet.of(ACTIVE_TAB));
show(searchDialog, window);
}
@@ -138,9 +145,12 @@ public class SearchDialog extends CommonSearchDialog {
private final transient SearchPreset searchPreset;
private final transient Set<SearchOptions> options;
private final transient SimpleListener<Set<SearchOptions>> optionsListener = new SimpleListener<>();
private transient JTextField searchField;
private transient JTextField packageField;
private transient JTextField resExtField;
private transient JSpinner resSizeLimit;
private transient @Nullable SearchTask searchTask;
private transient JButton loadAllButton;
@@ -159,7 +169,7 @@ public class SearchDialog extends CommonSearchDialog {
private final List<JNode> pendingResults = new ArrayList<>();
/**
* Use single thread to do all background work, so additional synchronisation not needed
* Use single thread to do all background work, so additional synchronization not needed
*/
private final Executor searchBackgroundExecutor = Executors.newSingleThreadExecutor();
@@ -209,12 +219,12 @@ public class SearchDialog extends CommonSearchDialog {
break;
case CLASS:
searchOptions.add(SearchOptions.CLASS);
searchOptions.add(CLASS);
break;
case COMMENT:
searchOptions.add(SearchOptions.COMMENT);
searchOptions.remove(SearchOptions.ACTIVE_TAB);
searchOptions.add(COMMENT);
searchOptions.remove(ACTIVE_TAB);
break;
}
return searchOptions;
@@ -263,36 +273,29 @@ public class SearchDialog extends CommonSearchDialog {
}
});
JPanel searchLinePanel = new JPanel();
searchLinePanel.setLayout(new BoxLayout(searchLinePanel, BoxLayout.LINE_AXIS));
searchLinePanel.add(searchField);
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(searchBtn);
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(makeOptionsToggleButton(NLS.str("search_dialog.ignorecase"), Icons.ICON_MATCH, Icons.ICON_MATCH_SELECTED,
JPanel searchButtons = new JPanel();
searchButtons.setLayout(new BoxLayout(searchButtons, BoxLayout.LINE_AXIS));
searchButtons.add(searchBtn);
searchButtons.add(Box.createRigidArea(new Dimension(5, 0)));
searchButtons.add(makeOptionsToggleButton(NLS.str("search_dialog.ignorecase"), Icons.ICON_MATCH, Icons.ICON_MATCH_SELECTED,
SearchOptions.IGNORE_CASE));
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(makeOptionsToggleButton(NLS.str("search_dialog.regex"), Icons.ICON_REGEX, Icons.ICON_REGEX_SELECTED,
searchButtons.add(Box.createRigidArea(new Dimension(5, 0)));
searchButtons.add(makeOptionsToggleButton(NLS.str("search_dialog.regex"), Icons.ICON_REGEX, Icons.ICON_REGEX_SELECTED,
SearchOptions.USE_REGEX));
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(makeOptionsToggleButton(NLS.str("search_dialog.active_tab"), Icons.ICON_ACTIVE_TAB,
searchButtons.add(Box.createRigidArea(new Dimension(5, 0)));
searchButtons.add(makeOptionsToggleButton(NLS.str("search_dialog.active_tab"), Icons.ICON_ACTIVE_TAB,
Icons.ICON_ACTIVE_TAB_SELECTED, SearchOptions.ACTIVE_TAB));
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(autoSearchCB);
searchLinePanel.setAlignmentX(LEFT_ALIGNMENT);
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
findLabel.setAlignmentX(LEFT_ALIGNMENT);
searchButtons.add(Box.createRigidArea(new Dimension(5, 0)));
searchButtons.add(autoSearchCB);
JPanel searchFieldPanel = new JPanel();
searchFieldPanel.setLayout(new BoxLayout(searchFieldPanel, BoxLayout.PAGE_AXIS));
searchFieldPanel.setAlignmentX(LEFT_ALIGNMENT);
searchFieldPanel.add(findLabel);
searchFieldPanel.add(Box.createRigidArea(new Dimension(0, 5)));
searchFieldPanel.add(searchLinePanel);
searchFieldPanel.setLayout(new BorderLayout(5, 5));
searchFieldPanel.add(new JLabel(NLS.str("search_dialog.open_by_name")), BorderLayout.LINE_START);
searchFieldPanel.add(searchField, BorderLayout.CENTER);
searchFieldPanel.add(searchButtons, BorderLayout.LINE_END);
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel searchInPanel = new JPanel();
searchInPanel.setLayout(new BoxLayout(searchInPanel, BoxLayout.LINE_AXIS));
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS));
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD));
@@ -301,36 +304,50 @@ public class SearchDialog extends CommonSearchDialog {
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.RESOURCE));
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.comments"), SearchOptions.COMMENT));
packageField = new JTextField();
packageField.setMaximumSize(new Dimension(Integer.MAX_VALUE, searchField.getPreferredSize().height));
packageField = new JTextField(Math.min(100, getMaxPkgLen()));
TextStandardActions.attach(packageField);
packageField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
packageField.setToolTipText(NLS.str("search_dialog.limit_package"));
JPanel searchPackageOptions = new JPanel();
searchPackageOptions.setLayout(new BoxLayout(searchPackageOptions, BoxLayout.LINE_AXIS));
searchPackageOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.limit_package")));
searchPackageOptions.add(packageField);
JPanel searchPackagePanel = new JPanel(new BorderLayout());
searchPackagePanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.limit_package")));
searchPackagePanel.add(packageField, BorderLayout.CENTER);
Dimension minPanelSize = calcMinSizeForTitledBorder(searchPackagePanel);
searchPackagePanel
.setPreferredSize(new Dimension(Math.max(packageField.getPreferredSize().width, minPanelSize.width), minPanelSize.height));
JTextField fileExtField = new JTextField();
TextStandardActions.attach(fileExtField);
fileExtField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> {
String ext = fileExtField.getText();
mainWindow.getSettings().setSrhResourceFileExt(ext);
}));
fileExtField.setText(mainWindow.getSettings().getSrhResourceFileExt());
fileExtField.setMaximumSize(new Dimension(Integer.MAX_VALUE, searchField.getPreferredSize().height));
resExtField = new JTextField();
TextStandardActions.attach(resExtField);
resExtField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
resExtField.setToolTipText(NLS.str("preferences.res_file_ext"));
resExtField.setText(mainWindow.getProject().getSearchResourcesFilter());
JPanel searchExtFileOptions = new JPanel();
searchExtFileOptions.setLayout(new BoxLayout(searchExtFileOptions, BoxLayout.LINE_AXIS));
searchExtFileOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("preferences.res_file_ext")));
searchExtFileOptions.add(fileExtField);
JPanel resExtFilePanel = new JPanel(new BorderLayout());
resExtFilePanel.setBorder(BorderFactory.createTitledBorder(NLS.str("preferences.res_file_ext")));
resExtFilePanel.add(resExtField, BorderLayout.CENTER);
resExtFilePanel.setPreferredSize(calcMinSizeForTitledBorder(resExtFilePanel));
JPanel optionsPanel = new JPanel(new GridLayout(2, 2, 5, 5));
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
resSizeLimit = new JSpinner(new SpinnerNumberModel(mainWindow.getProject().getSearchResourcesSizeLimit(), 0, Integer.MAX_VALUE, 1));
resSizeLimit.setToolTipText(NLS.str("preferences.res_skip_file"));
JPanel sizeLimitPanel = new JPanel(new BorderLayout());
sizeLimitPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("preferences.res_skip_file")));
sizeLimitPanel.add(resSizeLimit, BorderLayout.CENTER);
sizeLimitPanel.setPreferredSize(calcMinSizeForTitledBorder(sizeLimitPanel));
JPanel optionsPanel = new JPanel(new WrapLayout(FlowLayout.LEFT));
optionsPanel.add(searchInPanel);
optionsPanel.add(searchPackageOptions);
optionsPanel.add(searchExtFileOptions);
optionsPanel.add(searchPackagePanel);
optionsPanel.add(resExtFilePanel);
optionsPanel.add(sizeLimitPanel);
optionsListener.addListener(searchOptions -> {
boolean codeSearch = Utils.isSetContainsAny(searchOptions, EnumSet.of(CODE, CLASS, METHOD, FIELD, COMMENT));
searchPackagePanel.setVisible(codeSearch);
boolean resSearch = searchOptions.contains(RESOURCE);
resExtFilePanel.setVisible(resSearch);
sizeLimitPanel.setVisible(resSearch);
});
JPanel searchPane = new JPanel();
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
@@ -354,6 +371,34 @@ public class SearchDialog extends CommonSearchDialog {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
private int getMaxPkgLen() {
CacheObject cacheObject = mainWindow.getCacheObject();
int maxPkgLength = cacheObject.getMaxPkgLength();
if (maxPkgLength != 0) {
return maxPkgLength;
}
int max = 1;
for (PackageNode pkg : mainWindow.getWrapper().getRootNode().getPackages()) {
int len = pkg.getPkgInfo().getFullName().length();
if (len > max) {
max = len;
}
}
cacheObject.setMaxPkgLength(max);
return max;
}
/**
* Workaround to calculate minimum size for a panel with titled border
*/
private Dimension calcMinSizeForTitledBorder(JPanel panel) {
TitledBorder border = (TitledBorder) panel.getBorder();
Insets borderInsets = border.getBorderInsets(panel);
int insets = 2 * (borderInsets.left + borderInsets.right);
double titleWidth = panel.getFontMetrics(border.getTitleFont()).stringWidth(border.getTitle());
return new Dimension((int) titleWidth + insets, panel.getPreferredSize().height);
}
private void addSearchHistoryButton() {
JButton searchHistoryButton = new JButton(new FlatSearchWithHistoryIcon(true));
searchHistoryButton.setToolTipText(NLS.str("search_dialog.search_history"));
@@ -441,17 +486,26 @@ public class SearchDialog extends CommonSearchDialog {
RxUtils.textFieldEnterPress(searchField),
RxUtils.textFieldChanges(packageField),
RxUtils.textFieldEnterPress(packageField),
RxUtils.textFieldChanges(resExtField),
RxUtils.textFieldEnterPress(resExtField),
RxUtils.spinnerChanges(resSizeLimit),
RxUtils.spinnerEnterPress(resSizeLimit),
searchEmitter.getFlowable()));
} else {
searchEvents = Flowable.merge(
searchEvents = Flowable.merge(List.of(
RxUtils.textFieldEnterPress(searchField),
RxUtils.textFieldEnterPress(packageField),
searchEmitter.getFlowable());
RxUtils.textFieldEnterPress(resExtField),
RxUtils.spinnerEnterPress(resSizeLimit),
searchEmitter.getFlowable()));
}
searchDisposable = searchEvents
.debounce(50, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.from(searchBackgroundExecutor))
.subscribe(t -> this.search(searchField.getText()));
// set initial values
optionsListener.sendUpdate(options);
}
private void search(String text) {
@@ -477,14 +531,17 @@ public class SearchDialog extends CommonSearchDialog {
return null;
}
// allow empty text for search in comments
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
if (text.isEmpty() && !options.contains(COMMENT)) {
return null;
}
LOG.debug("Building search for '{}', options: {}", text, options);
boolean ignoreCase = options.contains(IGNORE_CASE);
boolean useRegex = options.contains(USE_REGEX);
String searchPackageText = packageField.getText();
SearchSettings searchSettings = new SearchSettings(text, !ignoreCase, useRegex, searchPackageText);
SearchSettings searchSettings = new SearchSettings(text);
searchSettings.setIgnoreCase(options.contains(IGNORE_CASE));
searchSettings.setUseRegex(options.contains(USE_REGEX));
searchSettings.setSearchPkgStr(packageField.getText().trim());
searchSettings.setResFilterStr(resExtField.getText().trim());
searchSettings.setResSizeLimit((Integer) resSizeLimit.getValue());
String error = searchSettings.prepare(mainWindow);
UiUtils.highlightAsErrorField(searchField, !StringUtils.isEmpty(error));
if (!StringUtils.isEmpty(error)) {
@@ -496,6 +553,9 @@ public class SearchDialog extends CommonSearchDialog {
if (!buildSearch(newSearchTask, text, searchSettings)) {
return null;
}
// save search settings
mainWindow.getProject().setSearchResourcesFilter(searchSettings.getResFilterStr());
mainWindow.getProject().setSearchResourcesSizeLimit(searchSettings.getResSizeLimit());
return newSearchTask;
}
@@ -529,7 +589,7 @@ public class SearchDialog extends CommonSearchDialog {
.filter(searchSettings::isInSearchPkg)
.collect(Collectors.toList());
}
if (text.isEmpty() && options.contains(SearchOptions.COMMENT)) {
if (text.isEmpty() && options.contains(COMMENT)) {
// allow empty text for comment search
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings, searchClasses));
return true;
@@ -698,7 +758,6 @@ public class SearchDialog extends CommonSearchDialog {
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
final JCheckBox chBox = new JCheckBox(name);
chBox.setAlignmentX(LEFT_ALIGNMENT);
chBox.setSelected(options.contains(opt));
chBox.addItemListener(e -> {
if (chBox.isSelected()) {
@@ -706,6 +765,7 @@ public class SearchDialog extends CommonSearchDialog {
} else {
options.remove(opt);
}
optionsListener.sendUpdate(options);
searchEmitter.emitSearch();
});
return chBox;
@@ -723,6 +783,7 @@ public class SearchDialog extends CommonSearchDialog {
} else {
options.remove(opt);
}
optionsListener.sendUpdate(options);
searchEmitter.emitSearch();
});
return toggleButton;
@@ -744,7 +805,7 @@ public class SearchDialog extends CommonSearchDialog {
private void registerActiveTabListener() {
removeActiveTabListener();
activeTabListener = e -> {
if (options.contains(SearchOptions.ACTIVE_TAB)) {
if (options.contains(ACTIVE_TAB)) {
LOG.debug("active tab change event received");
searchEmitter.emitSearch();
}
@@ -18,6 +18,7 @@ public class CacheObject {
private String lastSearch;
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
private String lastSearchPackage;
private int maxPkgLength;
private volatile boolean fullDecompilationFinished;
@@ -54,6 +55,14 @@ public class CacheObject {
this.lastSearchPackage = lastSearchPackage;
}
public int getMaxPkgLength() {
return maxPkgLength;
}
public void setMaxPkgLength(int maxPkgLength) {
this.maxPkgLength = maxPkgLength;
}
public JNodeCache getNodeCache() {
return jNodeCache;
}
@@ -0,0 +1,23 @@
package jadx.gui.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class SimpleListener<T> {
private final List<Consumer<T>> listeners = new ArrayList<>();
public void sendUpdate(T data) {
for (Consumer<T> listener : listeners) {
listener.accept(data);
}
}
public void addListener(Consumer<T> listener) {
listeners.add(listener);
}
public boolean removeListener(Consumer<T> listener) {
return listeners.remove(listener);
}
}
@@ -9,6 +9,8 @@ import java.awt.Insets;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import org.intellij.lang.annotations.MagicConstant;
/**
* FlowLayout subclass that fully supports wrapping of components.
*/
@@ -34,7 +36,7 @@ public class WrapLayout extends FlowLayout {
*
* @param align the alignment value
*/
public WrapLayout(int align) {
public WrapLayout(@MagicConstant(valuesFromClass = FlowLayout.class) int align) {
super(align);
}
@@ -3,12 +3,18 @@ package jadx.gui.utils.rx;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.function.Supplier;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentListener;
import org.jetbrains.annotations.NotNull;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.FlowableEmitter;
import io.reactivex.rxjava3.core.FlowableOnSubscribe;
import jadx.gui.utils.ui.DocumentUpdateListener;
@@ -26,18 +32,40 @@ public class RxUtils {
public static Flowable<String> textFieldEnterPress(final JTextField textField) {
FlowableOnSubscribe<String> source = emitter -> {
KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent ev) {
if (ev.getKeyCode() == KeyEvent.VK_ENTER) {
emitter.onNext(textField.getText());
}
}
};
KeyListener keyListener = enterKeyListener(emitter, textField::getText);
textField.addKeyListener(keyListener);
emitter.setDisposable(new CustomDisposable(() -> textField.removeKeyListener(keyListener)));
};
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
}
public static Flowable<String> spinnerChanges(final JSpinner spinner) {
FlowableOnSubscribe<String> source = emitter -> {
ChangeListener changeListener = e -> emitter.onNext(String.valueOf(spinner.getValue()));
spinner.addChangeListener(changeListener);
emitter.setDisposable(new CustomDisposable(() -> spinner.removeChangeListener(changeListener)));
};
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
}
public static Flowable<String> spinnerEnterPress(final JSpinner spinner) {
FlowableOnSubscribe<String> source = emitter -> {
KeyListener keyListener = enterKeyListener(emitter, () -> String.valueOf(spinner.getValue()));
spinner.addKeyListener(keyListener);
emitter.setDisposable(new CustomDisposable(() -> spinner.removeKeyListener(keyListener)));
};
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
}
private static @NotNull KeyListener enterKeyListener(FlowableEmitter<String> emitter, Supplier<String> supplier) {
return new KeyAdapter() {
@Override
public void keyPressed(KeyEvent ev) {
if (ev.getKeyCode() == KeyEvent.VK_ENTER) {
emitter.onNext(supplier.get());
}
}
};
}
}
@@ -297,7 +297,6 @@ preferences.rename_valid=Um sie gültig zu machen
preferences.rename_printable=Um druckbar zu machen
preferences.rename_use_source_name_as_class_name_alias=Quelldateiname als Klassennamen-Alias verwenden
preferences.rename_source_name_repeat_limit=Verwendung des Quellnamens erlauben, wenn dieser seltener vorkommt als
preferences.search_group_title=Ressourcen durchsuchen
preferences.search_results_per_page=Ergebnisse pro Seite (0 - keine Begrenzung)
preferences.res_file_ext=Ressourcendatei-Erweiterungen ('xml|html', * für alle)
preferences.res_skip_file=Ressourcendateien überspringen, wenn sie größer sind (MB) (0 - ausschalten)
@@ -297,7 +297,6 @@ preferences.rename_valid=To make them valid
preferences.rename_printable=To make printable
preferences.rename_use_source_name_as_class_name_alias=Use source file name as class name alias
preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
preferences.search_group_title=Search
preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
preferences.res_skip_file=Skip resources files if larger (MB) (0 - disable)
@@ -297,7 +297,6 @@ preferences.reset_title=Reestablecer preferencias
#preferences.rename_printable=To make printable
preferences.rename_use_source_name_as_class_name_alias=Usar el nombre del source como alias para la clase
#preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
#preferences.search_group_title=Search
#preferences.search_results_per_page=Results per page (0 - no limit)
#preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
#preferences.res_skip_file=Skip resources files if larger (MB) (0 - disable)
@@ -297,7 +297,6 @@ preferences.rename_valid=Untuk membuatnya valid
preferences.rename_printable=Untuk membuatnya dapat dicetak
preferences.rename_use_source_name_as_class_name_alias=Gunakan nama berkas sumber sebagai alias nama kelas
#preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
preferences.search_group_title=Pencarian
preferences.search_results_per_page=Hasil per halaman (0 - tanpa batas)
preferences.res_file_ext=Ekstensi berkas sumber daya ('xml|html', * untuk semua)
preferences.res_skip_file=Lewati berkas sumber daya jika lebih besar (MB) (0 - nonaktifkan)
@@ -297,7 +297,6 @@ preferences.rename_valid=유효한 식별자로 바꾸기
preferences.rename_printable=출력 가능하게 바꾸기
preferences.rename_use_source_name_as_class_name_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
#preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
preferences.search_group_title=리소스 검색
#preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=파일 확장자 (예: .xml|.html) (* 은 전체를 의미)
preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB)
@@ -297,7 +297,6 @@ preferences.rename_valid=Deixá-las válidas
preferences.rename_printable=Deixá-las imprimíveis (printable)
preferences.rename_use_source_name_as_class_name_alias=Utilizar nome do arquivo como apelido da classe
#preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
preferences.search_group_title=Buscar recursos
#preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Extensões de arquivos (ex: .xml|.html), * significa todas
preferences.res_skip_file=Pular arquivos excedidos
@@ -297,7 +297,6 @@ preferences.rename_valid=И сделать их верными
preferences.rename_printable=И сделать их доступными для печати
preferences.rename_use_source_name_as_class_name_alias=Иcпользовать атрибут SOURCE
#preferences.rename_source_name_repeat_limit=Allow using source name if it appears less than a limit number
preferences.search_group_title=Поиск
preferences.search_results_per_page=Результатов на страницу (0 - без лимита)
preferences.res_file_ext=Расширения файлов ресурсов ('xml|html', * для всех)
preferences.res_skip_file=Пропускать ресурсы больше чем (в МБ)
@@ -297,7 +297,6 @@ preferences.rename_valid=标识符应该符合标准规范
preferences.rename_printable=标识符必须要能正常显示
preferences.rename_use_source_name_as_class_name_alias=使用资源名作为类的别名
preferences.rename_source_name_repeat_limit=如果源名称少于限制数,则允许使用源名称
preferences.search_group_title=搜索资源
preferences.search_results_per_page=每页结果数(0 - 无限制)
preferences.res_file_ext=文件扩展名(比如 .xml|.html),* 表示所有
preferences.res_skip_file=跳过文件大小(MB
@@ -297,7 +297,6 @@ preferences.rename_valid=以使其有效
preferences.rename_printable=以使其可列印
preferences.rename_use_source_name_as_class_name_alias=將原始檔案名稱作為類別別名
preferences.rename_source_name_repeat_limit=若出現次數少於限制數便允許使用來源名稱
preferences.search_group_title=搜尋資源
preferences.search_results_per_page=每頁的搜尋結果數 (0 - 無限制)
preferences.res_file_ext=副檔名 (e.g. .xml|.html), * 表示全部
preferences.res_skip_file=略過大於此值的檔案 (MB)