diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 319d4e3e4..bb07f9604 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -423,6 +423,15 @@ public class Utils { return collection.iterator().next(); } + public static boolean isSetContainsAny(Set inputSet, Set searchKeys) { + for (T t : inputSet) { + if (searchKeys.contains(t)) { + return true; + } + } + return false; + } + @Nullable public static T first(List list) { if (list.isEmpty()) { diff --git a/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java b/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java index 6b2179e39..0bbbbbae0 100644 --- a/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/search/SearchSettings.java @@ -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; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java b/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java index 9ef3c7710..30460e6ad 100644 --- a/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java +++ b/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java @@ -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 buildAllowedFilesExtensions(String srhResourceFileExt) { + String str = srhResourceFileExt.trim(); + if (str.isEmpty() || str.equals("*")) { + anyExt = true; + return Collections.emptySet(); + } Set set = new HashSet<>(); - for (String extStr : srhResourceFileExt.split("[|.]")) { + for (String extStr : str.split("[|.]")) { String ext = extStr.trim(); if (!ext.isEmpty()) { anyExt = ext.equals("*"); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java index e0a3c19a2..31b13e0ff 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java @@ -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) { 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 e53590bf9..7e24cb510 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java index a78b222a1..adfd8dee9 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java @@ -22,6 +22,9 @@ public class ProjectData { private @Nullable String cacheDir; // don't use relative path adapter private boolean enableLiveReload = false; private List searchHistory = new ArrayList<>(); + private String searchResourcesFilter = "*"; + private int searchResourcesSizeLimit = 0; // in MB + protected Map pluginOptions = new HashMap<>(); public List 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 getPluginOptions() { return pluginOptions; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index b796c42fc..157526484 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java index c415d8efd..aa86f70fd 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java @@ -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 options; + private final transient SimpleListener> 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 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(); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index ff3e45daa..b4adbf126 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -18,6 +18,7 @@ public class CacheObject { private String lastSearch; private Map> 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; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/SimpleListener.java b/jadx-gui/src/main/java/jadx/gui/utils/SimpleListener.java new file mode 100644 index 000000000..5fc3bb69d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/SimpleListener.java @@ -0,0 +1,23 @@ +package jadx.gui.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class SimpleListener { + private final List> listeners = new ArrayList<>(); + + public void sendUpdate(T data) { + for (Consumer listener : listeners) { + listener.accept(data); + } + } + + public void addListener(Consumer listener) { + listeners.add(listener); + } + + public boolean removeListener(Consumer listener) { + return listeners.remove(listener); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/layout/WrapLayout.java b/jadx-gui/src/main/java/jadx/gui/utils/layout/WrapLayout.java index f97752ff4..51fd2e718 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/layout/WrapLayout.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/layout/WrapLayout.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java index 64709c611..f60235089 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java @@ -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 textFieldEnterPress(final JTextField textField) { FlowableOnSubscribe 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 spinnerChanges(final JSpinner spinner) { + FlowableOnSubscribe 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 spinnerEnterPress(final JSpinner spinner) { + FlowableOnSubscribe 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 emitter, Supplier supplier) { + return new KeyAdapter() { + @Override + public void keyPressed(KeyEvent ev) { + if (ev.getKeyCode() == KeyEvent.VK_ENTER) { + emitter.onNext(supplier.get()); + } + } + }; + } + } 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 9d82372d6..7e59ef475 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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) 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 f72738613..1b976f441 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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) 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 100b006c8..87e6791f9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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) diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 737f1f6fd..52a579aba 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -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) 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 e8e2e76a2..8417b9adc 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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) diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index d2a5f9a1c..aa357fd34 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 3dcbcd418..b48bf45ca 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -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=Пропускать ресурсы больше чем (в МБ) 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 7cb3e8cfb..c8d4aa824 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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) diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 5776e0dfe..38cde4125 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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)