Unverified Commit a7903f31 authored by skylot's avatar skylot Committed by GitHub

Merge pull request #228 from jpstotz/master

Search dialog improvements
parents d9b0365c c134837c
......@@ -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,10 +46,12 @@ public abstract class CommonSearchDialog extends JDialog {
protected ResultsModel resultsModel;
protected ResultsTable resultsTable;
protected JLabel resultsInfoLabel;
protected JLabel warnLabel;
protected ProgressPanel progressPane;
protected String highlightText;
protected boolean highlightTextCaseInsensitive = false;
public CommonSearchDialog(MainWindow mainWindow) {
super(mainWindow);
......@@ -93,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) {
......@@ -140,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);
......@@ -148,12 +152,7 @@ public abstract class CommonSearchDialog extends JDialog {
resultsTable.setColumnSelectionAllowed(false);
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
resultsTable.setAutoscrolls(false);
Enumeration<TableColumn> columns = resultsTable.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
column.setCellRenderer(renderer);
}
resultsTable.setDefaultRenderer(Object.class, renderer);
resultsTable.addMouseListener(new MouseAdapter() {
@Override
......@@ -182,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;
......@@ -238,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<JNode> rows = new ArrayList<>();
private final transient ArrayList<JNode> 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<? extends JNode> nodes) {
protected void addAll(Collection<? extends JNode> 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);
}
}
......@@ -267,6 +294,7 @@ public abstract class CommonSearchDialog extends JDialog {
}
public void clear() {
start = 0;
addDescColumn = false;
rows.clear();
renderer.clear();
......@@ -276,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
......@@ -293,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);
}
}
......@@ -315,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) {
......@@ -359,7 +417,7 @@ public abstract class CommonSearchDialog extends JDialog {
textArea.setColumns(textArea.getText().length());
if (highlightText != null) {
SearchContext searchContext = new SearchContext(highlightText);
searchContext.setMatchCase(true);
searchContext.setMatchCase(!highlightTextCaseInsensitive);
searchContext.setMarkAll(true);
SearchEngine.markAll(textArea, searchContext);
}
......
......@@ -31,6 +31,7 @@ public class SearchDialog extends CommonSearchDialog {
private Set<SearchOptions> options = EnumSet.allOf(SearchOptions.class);
private JTextField searchField;
private JCheckBox caseChBox;
public SearchDialog(MainWindow mainWindow, Set<SearchOptions> options) {
super(mainWindow);
......@@ -52,33 +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;
}
if (options.contains(SearchOptions.CLASS)) {
resultsModel.addAll(index.searchClsName(text));
}
if (options.contains(SearchOptions.METHOD)) {
resultsModel.addAll(index.searchMthName(text));
}
if (options.contains(SearchOptions.FIELD)) {
resultsModel.addAll(index.searchFldName(text));
}
if (options.contains(SearchOptions.CODE)) {
resultsModel.addAll(index.searchCode(text));
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();
}
highlightText = text;
resultsTable.updateTable();
}
private class SearchFieldListener implements DocumentListener, ActionListener {
......@@ -89,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();
}
}
......@@ -114,24 +120,38 @@ public class SearchDialog extends CommonSearchDialog {
private void initUI() {
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
searchField = new JTextField();
searchField.setAlignmentX(LEFT_ALIGNMENT);
searchField.getDocument().addDocumentListener(new SearchFieldListener());
new TextStandardActions(searchField);
caseChBox = new JCheckBox(NLS.str("search_dialog.ignorecase"));
caseChBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
performSearch();
}
});
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
JCheckBox codeChBox = makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE);
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchInPanel.add(clsChBox);
searchInPanel.add(mthChBox);
searchInPanel.add(fldChBox);
searchInPanel.add(codeChBox);
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchOptions.add(clsChBox);
searchOptions.add(mthChBox);
searchOptions.add(fldChBox);
searchOptions.add(codeChBox);
searchOptions.setAlignmentX(LEFT_ALIGNMENT);
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
searchOptions.add(caseChBox);
Box box = Box.createHorizontalBox();
box.setAlignmentX(LEFT_ALIGNMENT);
box.add(searchInPanel);
box.add(searchOptions);
JPanel searchPane = new JPanel();
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
......@@ -140,7 +160,7 @@ public class SearchDialog extends CommonSearchDialog {
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
searchPane.add(searchField);
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
searchPane.add(searchOptions);
searchPane.add(box);
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
initCommon();
......
......@@ -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() {
......
......@@ -109,4 +109,11 @@ public class Utils {
private static String format(long mem) {
return Long.toString((long) (mem / 1024. / 1024.)) + "MB";
}
/**
* Adapt character case for case insensitive searches
*/
public static char caseChar(char ch, boolean toLower) {
return toLower ? Character.toLowerCase(ch) : ch;
}
}
......@@ -7,7 +7,7 @@ import java.util.List;
public class CodeIndex<T> implements SearchIndex<T> {
private final List<StringRef> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>();
private final List<T> values = new ArrayList<T>();
@Override
public void put(String str, T value) {
......@@ -29,15 +29,18 @@ public class CodeIndex<T> implements SearchIndex<T> {
}
@Override
public List<T> getValuesForKeysContaining(String str) {
public List<T> getValuesForKeysContaining(String str, boolean caseInsensitive) {
int size = size();
if (size == 0) {
return Collections.emptyList();
}
if (caseInsensitive) {
str = str.toLowerCase();
}
List<T> results = new ArrayList<>();
for (int i = 0; i < size; i++) {
StringRef key = keys.get(i);
if (key.indexOf(str) != -1) {
if (key.indexOf(str, caseInsensitive) != -1) {
results.add(values.get(i));
}
}
......
......@@ -10,7 +10,7 @@ public interface SearchIndex<V> {
boolean isStringRefSupported();
List<V> getValuesForKeysContaining(String str);
List<V> getValuesForKeysContaining(String str, boolean caseInsensitive);
int size();
}
......@@ -7,7 +7,7 @@ import java.util.List;
public class SimpleIndex<T> implements SearchIndex<T> {
private final List<String> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>();
private final List<T> values = new ArrayList<T>();
@Override
public void put(String str, T value) {
......@@ -26,14 +26,20 @@ public class SimpleIndex<T> implements SearchIndex<T> {
}
@Override
public List<T> getValuesForKeysContaining(String str) {
public List<T> getValuesForKeysContaining(String str, boolean caseInsensitive) {
int size = size();
if (size == 0) {
return Collections.emptyList();
}
if (caseInsensitive) {
str = str.toLowerCase();
}
List<T> results = new ArrayList<>();
for (int i = 0; i < size; i++) {
String key = keys.get(i);
if (caseInsensitive) {
key = key.toLowerCase();
}
if (key.contains(str)) {
results.add(values.get(i));
}
......
package jadx.gui.utils.search;
import static jadx.gui.utils.Utils.caseChar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -68,13 +69,21 @@ public class StringRef implements CharSequence {
return indexOf(str, 0);
}
public int indexOf(String str, boolean caseInsensitive) {
return indexOf(str, 0, caseInsensitive);
}
public int indexOf(String str, int from, boolean caseInsensitive) {
return indexOf(refStr, offset, length, str, 0, str.length(), from, caseInsensitive);
}
public int indexOf(String str, int from) {
return indexOf(refStr, offset, length, str, 0, str.length(), from);
return indexOf(refStr, offset, length, str, 0, str.length(), from, false);
}
private static int indexOf(String source, int sourceOffset, int sourceCount,
String target, int targetOffset, int targetCount,
int fromIndex) {
int fromIndex, boolean caseInsensitive) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
......@@ -84,18 +93,18 @@ public class StringRef implements CharSequence {
if (targetCount == 0) {
return -1;
}
char first = target.charAt(targetOffset);
char first = caseChar(target.charAt(targetOffset), caseInsensitive);
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
if (source.charAt(i) != first) {
while (++i <= max && source.charAt(i) != first) {
if (caseChar(source.charAt(i), caseInsensitive) != first) {
while (++i <= max && caseChar(source.charAt(i), caseInsensitive) != first) {
}
}
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
int k = targetOffset + 1;
while (j < end && source.charAt(j) == target.charAt(k)) {
while (j < end && caseChar(source.charAt(j), caseInsensitive) == caseChar(target.charAt(k), caseInsensitive)) {
j++;
k++;
}
......@@ -117,7 +126,7 @@ public class StringRef implements CharSequence {
List<StringRef> list = new ArrayList<>();
while (true) {
int start = pos + targetLen;
pos = indexOf(str, 0, len, splitBy, 0, targetLen, start);
pos = indexOf(str, 0, len, splitBy, 0, targetLen, start, false);
if (pos == -1) {
if (start != len) {
list.add(subString(str, start, len));
......@@ -178,4 +187,5 @@ public class StringRef implements CharSequence {
int offset = this.offset;
return refStr.substring(offset, offset + len);
}
}
......@@ -73,22 +73,22 @@ public class TextSearchIndex {
}
}
public List<JNode> searchClsName(String text) {
return clsNamesIndex.getValuesForKeysContaining(text);
public List<JNode> searchClsName(String text, boolean caseInsensitive) {
return clsNamesIndex.getValuesForKeysContaining(text, caseInsensitive);
}
public List<JNode> searchMthName(String text) {
return mthNamesIndex.getValuesForKeysContaining(text);
public List<JNode> searchMthName(String text, boolean caseInsensitive) {
return mthNamesIndex.getValuesForKeysContaining(text, caseInsensitive);
}
public List<JNode> searchFldName(String text) {
return fldNamesIndex.getValuesForKeysContaining(text);
public List<JNode> searchFldName(String text, boolean caseInsensitive) {
return fldNamesIndex.getValuesForKeysContaining(text, caseInsensitive);
}
public List<CodeNode> searchCode(String text) {
public List<CodeNode> searchCode(String text, boolean caseInsensitive) {
List<CodeNode> items;
if (codeIndex.size() > 0) {
items = codeIndex.getValuesForKeysContaining(text);
items = codeIndex.getValuesForKeysContaining(text, caseInsensitive);
if (skippedClasses.isEmpty()) {
return items;
}
......@@ -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;
}
}
......
......@@ -49,6 +49,11 @@ search_dialog.class=Class
search_dialog.method=Method
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:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment