From c134837ca60884d4dee5d1c55be0933012ccc106 Mon Sep 17 00:00:00 2001 From: Jan Peter Stotz Date: Mon, 12 Feb 2018 16:20:12 +0100 Subject: [PATCH] pagination for search results. --- .../java/jadx/gui/ui/CommonSearchDialog.java | 105 ++++++++++++++---- .../main/java/jadx/gui/ui/SearchDialog.java | 51 +++++---- .../main/java/jadx/gui/ui/UsageDialog.java | 5 +- .../gui/utils/search/TextSearchIndex.java | 2 +- .../resources/i18n/Messages_en_US.properties | 3 + 5 files changed, 115 insertions(+), 51 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index adf7bb3e7..c099268b9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -12,11 +12,8 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; +import java.util.*; import java.util.List; -import java.util.Map; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -30,7 +27,6 @@ import jadx.gui.jobs.BackgroundJob; import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.DecompileJob; import jadx.gui.treemodel.JNode; -import jadx.gui.treemodel.TextNode; import jadx.gui.utils.CacheObject; import jadx.gui.utils.NLS; import jadx.gui.utils.Position; @@ -41,7 +37,7 @@ public abstract class CommonSearchDialog extends JDialog { private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class); private static final long serialVersionUID = 8939332306115370276L; - public static final int MAX_RESULTS_COUNT = 100; + public static final int RESULTS_PER_PAGE = 100; protected final transient TabbedPane tabbedPane; protected final transient CacheObject cache; @@ -50,6 +46,7 @@ public abstract class CommonSearchDialog extends JDialog { protected ResultsModel resultsModel; protected ResultsTable resultsTable; + protected JLabel resultsInfoLabel; protected JLabel warnLabel; protected ProgressPanel progressPane; @@ -94,6 +91,11 @@ public abstract class CommonSearchDialog extends JDialog { }); } + protected synchronized void performSearch() { + resultsTable.updateTable(); + updateProgressLabel(); + } + protected void openSelectedItem() { int selectedId = resultsTable.getSelectedRow(); if (selectedId == -1) { @@ -141,6 +143,7 @@ public abstract class CommonSearchDialog extends JDialog { protected JPanel initResultsTable() { ResultsTableCellRenderer renderer = new ResultsTableCellRenderer(); resultsModel = new ResultsModel(renderer); + resultsModel.addTableModelListener((e) -> updateProgressLabel()); resultsTable = new ResultsTable(resultsModel); resultsTable.setShowHorizontalLines(false); resultsTable.setDragEnabled(false); @@ -149,12 +152,7 @@ public abstract class CommonSearchDialog extends JDialog { resultsTable.setColumnSelectionAllowed(false); resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); resultsTable.setAutoscrolls(false); - - Enumeration columns = resultsTable.getColumnModel().getColumns(); - while (columns.hasMoreElements()) { - TableColumn column = columns.nextElement(); - column.setCellRenderer(renderer); - } + resultsTable.setDefaultRenderer(Object.class, renderer); resultsTable.addMouseListener(new MouseAdapter() { @Override @@ -183,10 +181,43 @@ public abstract class CommonSearchDialog extends JDialog { resultsPanel.add(new JScrollPane(resultsTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + + JPanel paginationPanel = new JPanel(); + paginationPanel.setAlignmentX( Component.LEFT_ALIGNMENT ); + paginationPanel.setLayout(new BoxLayout(paginationPanel, BoxLayout.X_AXIS)); + resultsInfoLabel = new JLabel(""); + + JButton nextPageButton = new JButton("->"); + nextPageButton.setToolTipText(NLS.str("search_dialog.next_page")); + nextPageButton.addActionListener((e) -> { + resultsModel.nextPage(); + resultsTable.updateTable(); + resultsTable.scrollRectToVisible(new Rectangle(0,0,1,1)); + }); + + JButton prevPageButton = new JButton("<-"); + prevPageButton.setToolTipText(NLS.str("search_dialog.prev_page")); + prevPageButton.addActionListener((e) -> { + resultsModel.prevPage(); + resultsTable.updateTable(); + resultsTable.scrollRectToVisible(new Rectangle(0,0,1,1)); + }); + + paginationPanel.add(prevPageButton); + paginationPanel.add(nextPageButton); + paginationPanel.add(resultsInfoLabel); + + resultsPanel.add(paginationPanel); resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); return resultsPanel; } + protected void updateProgressLabel() { + String statusText = String.format(NLS.str("search_dialog.info_label"), resultsModel.getDisplayedResultsStart(), + resultsModel.getDisplayedResultsEnd(), resultsModel.getResultCount()); + resultsInfoLabel.setText(statusText); + } + protected static class ResultsTable extends JTable { private static final long serialVersionUID = 3901184054736618969L; @@ -239,23 +270,18 @@ public abstract class CommonSearchDialog extends JDialog { private static final long serialVersionUID = -7821286846923903208L; private static final String[] COLUMN_NAMES = {"Node", "Code"}; - private final transient List rows = new ArrayList<>(); + private final transient ArrayList rows = new ArrayList<>(); private final transient ResultsTableCellRenderer renderer; private transient boolean addDescColumn; + private transient int start = 0; public ResultsModel(ResultsTableCellRenderer renderer) { this.renderer = renderer; } - protected void addAll(Iterable nodes) { + protected void addAll(Collection nodes) { + rows.ensureCapacity(rows.size() + nodes.size()); for (JNode node : nodes) { - int size = getRowCount(); - if (size >= MAX_RESULTS_COUNT) { - if (size == MAX_RESULTS_COUNT) { - add(new TextNode("Search results truncated (limit: " + MAX_RESULTS_COUNT + ")")); - } - return; - } add(node); } } @@ -268,6 +294,7 @@ public abstract class CommonSearchDialog extends JDialog { } public void clear() { + start = 0; addDescColumn = false; rows.clear(); renderer.clear(); @@ -277,9 +304,39 @@ public abstract class CommonSearchDialog extends JDialog { return addDescColumn; } + public int getResultCount() { + return rows.size(); + } + + public int getDisplayedResultsStart() { + if (rows.size() == 0) + return 0; + return start + 1; + } + + public int getDisplayedResultsEnd() { + return Math.min(rows.size(), start + RESULTS_PER_PAGE); + } + + public void nextPage() { + if (start + RESULTS_PER_PAGE < rows.size()) { + renderer.clear(); + start += RESULTS_PER_PAGE; + fireTableStructureChanged(); + } + } + + public void prevPage() { + if (start - RESULTS_PER_PAGE >= 0) { + renderer.clear(); + start -= RESULTS_PER_PAGE; + fireTableStructureChanged(); + } + } + @Override public int getRowCount() { - return rows.size(); + return rows.size() - start; } @Override @@ -294,7 +351,7 @@ public abstract class CommonSearchDialog extends JDialog { @Override public Object getValueAt(int rowIndex, int columnIndex) { - return rows.get(rowIndex); + return rows.get(rowIndex + start); } } @@ -316,7 +373,7 @@ public abstract class CommonSearchDialog extends JDialog { @Override public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, - boolean hasFocus, int row, int column) { + boolean hasFocus, int row, int column) { int id = row << 2 | column; Component comp = componentCache.get(id); if (comp == null) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 2c68b789d..85c576ad1 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -53,35 +53,37 @@ public class SearchDialog extends CommonSearchDialog { searchField.requestFocus(); } - private synchronized void performSearch() { + @Override + protected synchronized void performSearch() { resultsModel.clear(); String text = searchField.getText(); if (text == null || text.isEmpty() || options.isEmpty()) { - resultsTable.updateTable(); return; } - cache.setLastSearch(text); - TextSearchIndex index = cache.getTextIndex(); - if (index == null) { - resultsTable.updateTable(); - return; + try { + cache.setLastSearch(text); + TextSearchIndex index = cache.getTextIndex(); + if (index == null) { + return; + } + boolean caseInsensitive = caseChBox.isSelected(); + if (options.contains(SearchOptions.CLASS)) { + resultsModel.addAll(index.searchClsName(text, caseInsensitive)); + } + if (options.contains(SearchOptions.METHOD)) { + resultsModel.addAll(index.searchMthName(text, caseInsensitive)); + } + if (options.contains(SearchOptions.FIELD)) { + resultsModel.addAll(index.searchFldName(text, caseInsensitive)); + } + if (options.contains(SearchOptions.CODE)) { + resultsModel.addAll(index.searchCode(text, caseInsensitive)); + } + highlightText = text; + highlightTextCaseInsensitive = caseInsensitive; + } finally { + super.performSearch(); } - boolean caseInsensitive = caseChBox.isSelected(); - if (options.contains(SearchOptions.CLASS)) { - resultsModel.addAll(index.searchClsName(text, caseInsensitive)); - } - if (options.contains(SearchOptions.METHOD)) { - resultsModel.addAll(index.searchMthName(text, caseInsensitive)); - } - if (options.contains(SearchOptions.FIELD)) { - resultsModel.addAll(index.searchFldName(text, caseInsensitive)); - } - if (options.contains(SearchOptions.CODE)) { - resultsModel.addAll(index.searchCode(text, caseInsensitive)); - } - highlightText = text; - highlightTextCaseInsensitive = caseInsensitive; - resultsTable.updateTable(); } private class SearchFieldListener implements DocumentListener, ActionListener { @@ -92,7 +94,8 @@ public class SearchDialog extends CommonSearchDialog { if (timer != null) { timer.restart(); } else { - timer = new Timer(300, this); + timer = new Timer(400, this); + timer.setRepeats(false); timer.start(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java index 87451567a..49accb725 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java @@ -37,7 +37,8 @@ public class UsageDialog extends CommonSearchDialog { // no op } - private synchronized void performSearch() { + @Override + protected synchronized void performSearch() { resultsModel.clear(); CodeUsageInfo usageInfo = cache.getUsageInfo(); @@ -47,7 +48,7 @@ public class UsageDialog extends CommonSearchDialog { resultsModel.addAll(usageInfo.getUsageList(node)); // TODO: highlight only needed node usage highlightText = null; - resultsTable.updateTable(); + super.performSearch(); } private void initUI() { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java index ea4119d30..ccc13af29 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -106,7 +106,7 @@ public class TextSearchIndex { while (pos != -1) { pos = searchNext(list, text, javaClass, code, pos); } - if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) { + if (list.size() > CommonSearchDialog.RESULTS_PER_PAGE) { return; } } 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 b85c92409..577311e17 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -51,6 +51,9 @@ search_dialog.field=Field search_dialog.code=Code search_dialog.options=Search options \: search_dialog.ignorecase=Case insensitive +search_dialog.next_page=Show next page +search_dialog.prev_page=Show previous page +search_dialog.info_label=Showing results %d to %d of %d usage_dialog.title=Usage search usage_dialog.label=Usage for: