summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/dialog
diff options
context:
space:
mode:
authorGravatar 2xsaiko2020-04-29 18:24:29 +0200
committerGravatar GitHub2020-04-29 12:24:29 -0400
commit0eff06096bc4852f2580f20a0c5bf970ecf66987 (patch)
treefc6c996c66abde850aa8598a24da134c37d1d531 /src/main/java/cuchaz/enigma/gui/dialog
parentThis doesn't need to be scaled, potentially fixes circular class loading cras... (diff)
downloadenigma-fork-0eff06096bc4852f2580f20a0c5bf970ecf66987.tar.gz
enigma-fork-0eff06096bc4852f2580f20a0c5bf970ecf66987.tar.xz
enigma-fork-0eff06096bc4852f2580f20a0c5bf970ecf66987.zip
Rewrite search dialog (#233)
* Fix searching * Make buttons use localization * Fix rename field opening when pressing shift+space * Tweak search algorithm * Add a bit of documentation * Remove duplicate example line * Use max() when building the inner map instead of overwriting the old value * Keep search dialog state * Formatting * Fix cursor key selection not scrolling to selected item * Don't set font size * Rename close0 to exit * Fix wrong scrolling when selecting search dialog entry
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/dialog')
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java310
1 files changed, 204 insertions, 106 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
index 56ce751..b36ebfb 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
@@ -11,150 +11,248 @@
11 11
12package cuchaz.enigma.gui.dialog; 12package cuchaz.enigma.gui.dialog;
13 13
14import com.google.common.collect.Lists; 14import java.awt.BorderLayout;
15import java.awt.Color;
16import java.awt.FlowLayout;
17import java.awt.Font;
18import java.awt.event.*;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.List;
22
23import javax.swing.*;
24import javax.swing.event.DocumentEvent;
25import javax.swing.event.DocumentListener;
26
15import cuchaz.enigma.gui.Gui; 27import cuchaz.enigma.gui.Gui;
28import cuchaz.enigma.gui.GuiController;
29import cuchaz.enigma.gui.util.AbstractListCellRenderer;
30import cuchaz.enigma.gui.util.ScaleUtil;
16import cuchaz.enigma.translation.representation.entry.ClassEntry; 31import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import cuchaz.enigma.utils.I18n; 32import cuchaz.enigma.utils.I18n;
18import cuchaz.enigma.gui.util.ScaleUtil; 33import cuchaz.enigma.utils.search.SearchEntry;
19import me.xdrop.fuzzywuzzy.FuzzySearch; 34import cuchaz.enigma.utils.search.SearchUtil;
20import me.xdrop.fuzzywuzzy.model.ExtractedResult;
21
22import javax.swing.*;
23import javax.swing.border.EmptyBorder;
24import java.awt.*;
25import java.awt.event.*;
26import java.util.List;
27import java.util.function.Consumer;
28import java.util.stream.Collectors;
29 35
30public class SearchDialog { 36public class SearchDialog {
31 37
32 private JTextField searchField; 38 private final JTextField searchField;
33 private JList<String> classList; 39 private final DefaultListModel<SearchEntryImpl> classListModel;
34 private JFrame frame; 40 private final JList<SearchEntryImpl> classList;
35 41 private final JDialog dialog;
36 private Gui parent;
37 private List<ClassEntry> deobfClasses;
38 42
39 private KeyEventDispatcher keyEventDispatcher; 43 private final Gui parent;
44 private final SearchUtil<SearchEntryImpl> su;
40 45
41 public SearchDialog(Gui parent) { 46 public SearchDialog(Gui parent) {
42 this.parent = parent; 47 this.parent = parent;
43 48
44 deobfClasses = Lists.newArrayList(); 49 su = new SearchUtil<>();
45 this.parent.getController().addSeparatedClasses(Lists.newArrayList(), deobfClasses);
46 deobfClasses.removeIf(ClassEntry::isInnerClass);
47 }
48 50
49 public void show() { 51 dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true);
50 frame = new JFrame(I18n.translate("menu.view.search")); 52 JPanel contentPane = new JPanel();
51 frame.setVisible(false); 53 contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4));
52 JPanel pane = new JPanel(); 54 contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4)));
53 pane.setBorder(new EmptyBorder(5, 10, 5, 10));
54
55 addRow(pane, jPanel -> {
56 searchField = new JTextField("", 20);
57
58 searchField.addKeyListener(new KeyAdapter() {
59 @Override
60 public void keyTyped(KeyEvent keyEvent) {
61 updateList();
62 }
63 });
64 55
65 jPanel.add(searchField); 56 searchField = new JTextField();
66 }); 57 searchField.getDocument().addDocumentListener(new DocumentListener() {
67
68 addRow(pane, jPanel -> {
69 classList = new JList<>();
70 classList.setLayoutOrientation(JList.VERTICAL);
71 classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
72
73 classList.addMouseListener(new MouseAdapter() {
74 @Override
75 public void mouseClicked(MouseEvent mouseEvent) {
76 if(mouseEvent.getClickCount() >= 2){
77 openSelected();
78 }
79 }
80 });
81 jPanel.add(classList);
82 });
83 58
59 @Override
60 public void insertUpdate(DocumentEvent e) {
61 updateList();
62 }
84 63
85 keyEventDispatcher = keyEvent -> { 64 @Override
86 if(!frame.isVisible()){ 65 public void removeUpdate(DocumentEvent e) {
87 return false; 66 updateList();
88 } 67 }
89 if(keyEvent.getKeyCode() == KeyEvent.VK_DOWN){ 68
90 int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1; 69 @Override
91 classList.setSelectedIndex(next); 70 public void changedUpdate(DocumentEvent e) {
71 updateList();
92 } 72 }
93 if(keyEvent.getKeyCode() == KeyEvent.VK_UP){ 73
94 int next = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1; 74 });
95 classList.setSelectedIndex(next); 75 searchField.addKeyListener(new KeyAdapter() {
76 @Override
77 public void keyPressed(KeyEvent e) {
78 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
79 int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1;
80 classList.setSelectedIndex(next);
81 classList.ensureIndexIsVisible(next);
82 } else if (e.getKeyCode() == KeyEvent.VK_UP) {
83 int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1;
84 classList.setSelectedIndex(prev);
85 classList.ensureIndexIsVisible(prev);
86 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
87 close();
88 }
96 } 89 }
97 if(keyEvent.getKeyCode() == KeyEvent.VK_ENTER){ 90 });
98 openSelected(); 91 searchField.addActionListener(e -> openSelected());
92 contentPane.add(searchField, BorderLayout.NORTH);
93
94 classListModel = new DefaultListModel<>();
95 classList = new JList<>();
96 classList.setModel(classListModel);
97 classList.setCellRenderer(new ListCellRendererImpl());
98 classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
99 classList.addMouseListener(new MouseAdapter() {
100 @Override
101 public void mouseClicked(MouseEvent mouseEvent) {
102 if (mouseEvent.getClickCount() >= 2) {
103 int idx = classList.locationToIndex(mouseEvent.getPoint());
104 SearchEntryImpl entry = classList.getModel().getElementAt(idx);
105 openEntry(entry);
106 }
99 } 107 }
100 if(keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE){ 108 });
101 close(); 109 contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
110
111 JPanel buttonBar = new JPanel();
112 buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT));
113 JButton open = new JButton(I18n.translate("prompt.open"));
114 open.addActionListener(event -> openSelected());
115 buttonBar.add(open);
116 JButton cancel = new JButton(I18n.translate("prompt.cancel"));
117 cancel.addActionListener(event -> close());
118 buttonBar.add(cancel);
119 contentPane.add(buttonBar, BorderLayout.SOUTH);
120
121 // apparently the class list doesn't update by itself when the list
122 // state changes and the dialog is hidden
123 dialog.addComponentListener(new ComponentAdapter() {
124 @Override
125 public void componentShown(ComponentEvent e) {
126 classList.updateUI();
102 } 127 }
103 return false; 128 });
104 };
105 129
106 KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyEventDispatcher); 130 dialog.setContentPane(contentPane);
131 dialog.setSize(ScaleUtil.getDimension(400, 500));
132 dialog.setLocationRelativeTo(parent.getFrame());
133 }
134
135 public void show() {
136 su.clear();
137 parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream()
138 .filter(e -> !e.isInnerClass())
139 .map(e -> SearchEntryImpl.from(e, parent.getController()))
140 .map(SearchUtil.Entry::from)
141 .sequential()
142 .forEach(su::add);
107 143
108 frame.setContentPane(pane); 144 updateList();
109 frame.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
110 145
111 frame.setSize(ScaleUtil.getDimension(360, 500)); 146 searchField.requestFocus();
112 frame.setAlwaysOnTop(true); 147 searchField.selectAll();
113 frame.setResizable(false);
114 frame.setLocationRelativeTo(parent.getFrame());
115 frame.setVisible(true);
116 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
117 148
118 searchField.requestFocusInWindow(); 149 dialog.setVisible(true);
119 } 150 }
120 151
121 private void openSelected(){ 152 private void openSelected() {
122 close(); 153 SearchEntryImpl selectedValue = classList.getSelectedValue();
123 if(classList.isSelectionEmpty()){ 154 if (selectedValue != null) {
124 return; 155 openEntry(selectedValue);
125 } 156 }
126 deobfClasses.stream()
127 .filter(classEntry -> classEntry.getSimpleName().equals(classList.getSelectedValue())).
128 findFirst()
129 .ifPresent(classEntry -> {
130 parent.getController().navigateTo(classEntry);
131 parent.getDeobfPanel().deobfClasses.setSelectionClass(classEntry);
132 });
133 } 157 }
134 158
135 private void close(){ 159 private void openEntry(SearchEntryImpl e) {
136 frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); 160 close();
137 KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyEventDispatcher); 161 su.hit(e);
162 parent.getController().navigateTo(e.obf);
163 if (e.deobf != null) {
164 parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf);
165 } else {
166 parent.getObfPanel().obfClasses.setSelectionClass(e.obf);
167 }
138 } 168 }
139 169
140 private void addRow(JPanel pane, Consumer<JPanel> consumer) { 170 private void close() {
141 JPanel panel = new JPanel(new FlowLayout()); 171 dialog.setVisible(false);
142 consumer.accept(panel);
143 pane.add(panel, BorderLayout.CENTER);
144 } 172 }
145 173
146 //Updates the list of class names 174 // Updates the list of class names
147 private void updateList() { 175 private void updateList() {
148 DefaultListModel<String> listModel = new DefaultListModel<>(); 176 classListModel.clear();
149 177
150 //Basic search using the Fuzzy libary 178 su.search(searchField.getText())
151 //TODO improve on this, to not just work from string and to keep the ClassEntry 179 .limit(100)
152 List<ExtractedResult> results = FuzzySearch.extractTop(searchField.getText(), deobfClasses.stream().map(ClassEntry::getSimpleName).collect(Collectors.toList()), 25); 180 .forEach(classListModel::addElement);
153 results.forEach(extractedResult -> listModel.addElement(extractedResult.getString())); 181 }
154 182
155 classList.setModel(listModel); 183 public void dispose() {
184 dialog.dispose();
156 } 185 }
157 186
187 private static final class SearchEntryImpl implements SearchEntry {
188
189 public final ClassEntry obf;
190 public final ClassEntry deobf;
158 191
192 private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) {
193 this.obf = obf;
194 this.deobf = deobf;
195 }
196
197 @Override
198 public List<String> getSearchableNames() {
199 if (deobf != null) {
200 return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName());
201 } else {
202 return Collections.singletonList(obf.getSimpleName());
203 }
204 }
205
206 @Override
207 public String getIdentifier() {
208 return obf.getFullName();
209 }
210
211 @Override
212 public String toString() {
213 return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf);
214 }
215
216 public static SearchEntryImpl from(ClassEntry e, GuiController controller) {
217 ClassEntry deobf = controller.project.getMapper().deobfuscate(e);
218 if (deobf.equals(e)) deobf = null;
219 return new SearchEntryImpl(e, deobf);
220 }
221
222 }
223
224 private static final class ListCellRendererImpl extends AbstractListCellRenderer<SearchEntryImpl> {
225
226 private final JLabel mainName;
227 private final JLabel secondaryName;
228
229 public ListCellRendererImpl() {
230 this.setLayout(new BorderLayout());
231
232 mainName = new JLabel();
233 this.add(mainName, BorderLayout.WEST);
234
235 secondaryName = new JLabel();
236 secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC));
237 secondaryName.setForeground(Color.GRAY);
238 this.add(secondaryName, BorderLayout.EAST);
239 }
240
241 @Override
242 public void updateUiForEntry(JList<? extends SearchEntryImpl> list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) {
243 if (value.deobf == null) {
244 mainName.setText(value.obf.getSimpleName());
245 mainName.setToolTipText(value.obf.getFullName());
246 secondaryName.setText("");
247 secondaryName.setToolTipText("");
248 } else {
249 mainName.setText(value.deobf.getSimpleName());
250 mainName.setToolTipText(value.deobf.getFullName());
251 secondaryName.setText(value.obf.getSimpleName());
252 secondaryName.setToolTipText(value.obf.getFullName());
253 }
254 }
255
256 }
159 257
160} 258}