Skip to content

Commit 5bc1033

Browse files
committed
Add filter for search results
1 parent 3dbb5f1 commit 5bc1033

File tree

4 files changed

+248
-10
lines changed

4 files changed

+248
-10
lines changed

src/main/java/club/bytecode/the/jda/JDA.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,6 @@ public static List<ViewerFile> search(String needle) {
600600
}
601601
}
602602
}
603-
matches.sort(Comparator.comparing(ViewerFile::toString));
604603
return matches;
605604
}
606605
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package club.bytecode.the.jda.gui.components;
2+
3+
import javax.swing.*;
4+
import java.util.Collection;
5+
import java.util.Iterator;
6+
import java.util.SortedSet;
7+
import java.util.TreeSet;
8+
9+
public class SortedUniqueListModel<T> extends AbstractListModel implements Iterable<T> {
10+
SortedSet<T> model;
11+
private boolean deferringUpdates;
12+
13+
public SortedUniqueListModel() {
14+
model = new TreeSet<>();
15+
}
16+
17+
public int getSize() {
18+
return model.size();
19+
}
20+
21+
// performance hack
22+
public void deferUpdates() {
23+
deferringUpdates = true;
24+
}
25+
26+
public void commitUpdates() {
27+
deferringUpdates = false;
28+
fireUpdate();
29+
}
30+
31+
public T getElementAt(int index) {
32+
Iterator<T> it = model.iterator();
33+
for (int i = 0; i < index; i++)
34+
if (!it.hasNext()) return null;
35+
else it.next();
36+
if (!it.hasNext()) return null;
37+
return it.next();
38+
}
39+
40+
public void add(T element) {
41+
if (model.add(element)) {
42+
fireUpdate();
43+
}
44+
}
45+
46+
public void fireUpdate() {
47+
if (deferringUpdates)
48+
return;
49+
fireContentsChanged(this, 0, getSize());
50+
}
51+
52+
public void addAll(Collection<T> elements) {
53+
model.addAll(elements);
54+
fireUpdate();
55+
}
56+
57+
public void clear() {
58+
model.clear();
59+
fireUpdate();
60+
}
61+
62+
public boolean contains(T element) {
63+
return model.contains(element);
64+
}
65+
66+
public T firstElement() {
67+
return model.first();
68+
}
69+
70+
@Override
71+
public Iterator<T> iterator() {
72+
return new Iterator<T>() {
73+
Iterator<T> delegate = model.iterator();
74+
75+
@Override
76+
public boolean hasNext() {
77+
return delegate.hasNext();
78+
}
79+
80+
@Override
81+
public T next() {
82+
return delegate.next();
83+
}
84+
85+
@Override
86+
public void remove() {
87+
delegate.remove();
88+
fireUpdate();
89+
}
90+
};
91+
}
92+
93+
public T lastElement() {
94+
return model.last();
95+
}
96+
97+
public boolean removeElement(T element) {
98+
boolean removed = model.remove(element);
99+
if (removed) {
100+
fireUpdate();
101+
}
102+
return removed;
103+
}
104+
}
105+

src/main/java/club/bytecode/the/jda/gui/fileviewer/ViewerFile.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,40 @@
55
import java.util.Objects;
66

77
// really just a pair of FileContainer and String name
8-
public class ViewerFile {
8+
public class ViewerFile implements Comparable<ViewerFile> {
99
public final FileContainer container;
1010
public final String name;
11+
private final String toStringCached;
1112

1213
public ViewerFile(FileContainer container, String name) {
1314
this.container = container;
1415
this.name = name;
16+
toStringCached = container + "$" + name;
1517
}
1618

1719
@Override
1820
public String toString() {
19-
return container + "$" + name;
21+
return toStringCached;
2022
}
2123

2224
@Override
2325
public boolean equals(Object o) {
2426
if (this == o) return true;
2527
if (o == null || getClass() != o.getClass()) return false;
2628
ViewerFile that = (ViewerFile) o;
27-
return Objects.equals(container, that.container) &&
29+
boolean result = Objects.equals(container, that.container) &&
2830
Objects.equals(name, that.name);
31+
assert (result == (compareTo(that) == 0));
32+
return result;
2933
}
3034

3135
@Override
3236
public int hashCode() {
3337
return Objects.hash(container, name);
3438
}
39+
40+
@Override
41+
public int compareTo(ViewerFile other) {
42+
return toStringCached.compareTo(other.toStringCached);
43+
}
3544
}
Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,166 @@
11
package club.bytecode.the.jda.gui.search;
22

3-
import club.bytecode.the.jda.FileContainer;
43
import club.bytecode.the.jda.JDA;
4+
import club.bytecode.the.jda.gui.components.SortedUniqueListModel;
55
import club.bytecode.the.jda.gui.fileviewer.ViewerFile;
66
import club.bytecode.the.jda.settings.Settings;
77
import net.miginfocom.swing.MigLayout;
8-
import org.mapleir.stdlib.util.Pair;
98

109
import javax.swing.*;
10+
import javax.swing.event.DocumentEvent;
11+
import javax.swing.event.DocumentListener;
1112
import java.awt.*;
13+
import java.awt.event.KeyEvent;
14+
import java.awt.event.KeyListener;
1215
import java.awt.event.MouseAdapter;
1316
import java.awt.event.MouseEvent;
17+
import java.util.Iterator;
1418
import java.util.List;
1519

1620
public class SearchDialog extends JDialog {
21+
private final List<ViewerFile> searchResults;
22+
private final JList<ViewerFile> list;
23+
private final JTextArea searchBox;
24+
25+
private String oldFilter = "";
26+
1727
public SearchDialog(String needle, List<ViewerFile> matches) {
1828
super(new JFrame(), "Search Results", true);
29+
searchResults = matches;
1930
Container pane = getContentPane();
2031
pane.setPreferredSize(new Dimension(850, 400));
2132
pane.setLayout(new MigLayout("fill"));
2233
pane.add(new JLabel(needle + " found in:"), "pushx, growx, wrap");
23-
JList<Pair<FileContainer, String>> list = new JList(matches.toArray());
34+
list = new JList<>(createSortedListModel());
2435
list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
2536
list.setLayoutOrientation(JList.VERTICAL);
37+
2638
list.addMouseListener(new MouseAdapter() {
2739
public void mouseClicked(MouseEvent evt) {
2840
JList list = (JList)evt.getSource();
2941
if (evt.getClickCount() == 2) {
3042
int index = list.locationToIndex(evt.getPoint());
31-
ViewerFile vf = matches.get(index);
32-
JDA.viewer.navigator.openClassFileToWorkSpace(vf);
43+
openResult(index);
44+
}
45+
}
46+
});
47+
48+
searchBox = new JTextArea();
49+
searchBox.setRows(1);
50+
searchBox.addKeyListener(new KeyListener() {
51+
@Override
52+
public void keyTyped(KeyEvent e) {
53+
}
54+
55+
@Override
56+
public void keyPressed(KeyEvent e) {
57+
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
58+
e.consume();
59+
list.requestFocus();
3360
}
3461
}
62+
63+
@Override
64+
public void keyReleased(KeyEvent e) {
65+
}
66+
});
67+
68+
searchBox.getDocument().addDocumentListener(new DocumentListener(){
69+
@Override public void insertUpdate(DocumentEvent e) { filter(); }
70+
@Override public void removeUpdate(DocumentEvent e) { filter(); }
71+
@Override public void changedUpdate(DocumentEvent e) {}
72+
private void filter() {
73+
String filter = searchBox.getText();
74+
updateFilter((SortedUniqueListModel<ViewerFile>) list.getModel(), filter);
75+
oldFilter = filter;
76+
}
77+
});
78+
79+
list.addKeyListener(new KeyListener() {
80+
@Override
81+
public void keyTyped(KeyEvent e) {
82+
if (e.getKeyChar() == '\n') e.consume();
83+
else focusSearch(e);
84+
}
85+
86+
@Override
87+
public void keyPressed(KeyEvent e) {
88+
if (e.getKeyCode() == KeyEvent.VK_ENTER) openResult(list.getSelectedIndex());
89+
else focusSearch(e);
90+
}
91+
92+
@Override
93+
public void keyReleased(KeyEvent e) {
94+
if (e.getKeyCode() == KeyEvent.VK_ENTER) e.consume();
95+
else focusSearch(e);
96+
}
3597
});
98+
3699
list.setFont(Settings.getCodeFont());
37100
JScrollPane listScroller = new JScrollPane(list);
38-
pane.add(listScroller, "grow, push");
101+
102+
pane.add(listScroller, "grow, push, wrap");
103+
pane.add(searchBox, "grow");
39104
pack();
40105
}
106+
107+
public void openResult(int index) {
108+
ViewerFile vf = searchResults.get(index);
109+
JDA.viewer.navigator.openClassFileToWorkSpace(vf);
110+
}
111+
112+
public void focusSearch(KeyEvent e) {
113+
searchBox.setText("");
114+
searchBox.requestFocus();
115+
forwardKeyEvent(searchBox, e);
116+
}
117+
118+
private void forwardKeyEvent(Component receiver, KeyEvent e) {
119+
receiver.dispatchEvent(new KeyEvent(receiver, e.getID(), e.getWhen(), e.getModifiers(), e.getKeyCode(), e.getKeyChar()));
120+
}
121+
122+
private ListModel<ViewerFile> createSortedListModel() {
123+
SortedUniqueListModel<ViewerFile> model = new SortedUniqueListModel<>();
124+
model.addAll(searchResults);
125+
return model;
126+
}
127+
128+
public void updateFilter(SortedUniqueListModel<ViewerFile> model, String filter) {
129+
if (oldFilter.equals(filter))
130+
return;
131+
132+
if (filter.isEmpty()) {
133+
model.deferUpdates();
134+
model.clear();
135+
model.addAll(searchResults);
136+
model.commitUpdates();
137+
return;
138+
}
139+
140+
model.deferUpdates(); // make sure to commit me
141+
String filterLower = filter.toLowerCase();
142+
String oldFilterLower = oldFilter.toLowerCase();
143+
if (oldFilterLower.contains(filterLower)) {
144+
for (ViewerFile vf : searchResults) {
145+
String s = vf.toString().toLowerCase();
146+
if (s.contains(filterLower)) {
147+
model.add(vf);
148+
}
149+
}
150+
} else if (filterLower.contains(oldFilterLower)) {
151+
for (Iterator<ViewerFile> it = model.iterator(); it.hasNext(); ) {
152+
ViewerFile vf = it.next(); // copy because we remove as we iterate. inefficient
153+
if (!vf.toString().toLowerCase().contains(filterLower)) {
154+
it.remove();
155+
}
156+
}
157+
} else for (ViewerFile vf : searchResults) {
158+
if (!vf.toString().toLowerCase().contains(filter)) {
159+
model.removeElement(vf);
160+
} else {
161+
model.add(vf);
162+
}
163+
}
164+
model.commitUpdates();
165+
}
41166
}

0 commit comments

Comments
 (0)