diff options
| author | 2023-09-25 23:34:30 +0200 | |
|---|---|---|
| committer | 2023-09-25 22:34:30 +0100 | |
| commit | 1554e32e9384a6bc2cd168e061595917e1687e25 (patch) | |
| tree | a77971e5a21912bda38a5b7723369bbf92972248 | |
| parent | Fix documenting constructors (again) (#530) (diff) | |
| download | enigma-fork-1554e32e9384a6bc2cd168e061595917e1687e25.tar.gz enigma-fork-1554e32e9384a6bc2cd168e061595917e1687e25.tar.xz enigma-fork-1554e32e9384a6bc2cd168e061595917e1687e25.zip | |
Search dialog improvements (#529)
* Improve search dialogs
* fix checkstyle
* change search class keystroke
* better dialog title & don't calculate score if not needed
4 files changed, 47 insertions, 9 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java index 7814dd8..536ebca 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java | |||
| @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; | |||
| 29 | 29 | ||
| 30 | import javax.swing.DefaultListModel; | 30 | import javax.swing.DefaultListModel; |
| 31 | import javax.swing.JButton; | 31 | import javax.swing.JButton; |
| 32 | import javax.swing.JCheckBox; | ||
| 32 | import javax.swing.JDialog; | 33 | import javax.swing.JDialog; |
| 33 | import javax.swing.JLabel; | 34 | import javax.swing.JLabel; |
| 34 | import javax.swing.JList; | 35 | import javax.swing.JList; |
| @@ -55,7 +56,9 @@ import cuchaz.enigma.translation.representation.entry.ParentedEntry; | |||
| 55 | import cuchaz.enigma.utils.I18n; | 56 | import cuchaz.enigma.utils.I18n; |
| 56 | 57 | ||
| 57 | public class SearchDialog { | 58 | public class SearchDialog { |
| 59 | private final JPanel inputPanel; | ||
| 58 | private final JTextField searchField; | 60 | private final JTextField searchField; |
| 61 | private final JCheckBox onlyExactMatchesCheckbox; | ||
| 59 | private DefaultListModel<SearchEntryImpl> classListModel; | 62 | private DefaultListModel<SearchEntryImpl> classListModel; |
| 60 | private final JList<SearchEntryImpl> classList; | 63 | private final JList<SearchEntryImpl> classList; |
| 61 | private final JDialog dialog; | 64 | private final JDialog dialog; |
| @@ -69,11 +72,15 @@ public class SearchDialog { | |||
| 69 | 72 | ||
| 70 | su = new SearchUtil<>(); | 73 | su = new SearchUtil<>(); |
| 71 | 74 | ||
| 72 | dialog = new JDialog(parent.getFrame(), I18n.translate("menu.search"), true); | 75 | dialog = new JDialog(parent.getFrame(), true); |
| 73 | JPanel contentPane = new JPanel(); | 76 | JPanel contentPane = new JPanel(); |
| 74 | contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4)); | 77 | contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4)); |
| 75 | contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); | 78 | contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); |
| 76 | 79 | ||
| 80 | inputPanel = new JPanel(); | ||
| 81 | inputPanel.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); | ||
| 82 | contentPane.add(inputPanel, BorderLayout.NORTH); | ||
| 83 | |||
| 77 | searchField = new JTextField(); | 84 | searchField = new JTextField(); |
| 78 | searchField.getDocument().addDocumentListener(new DocumentListener() { | 85 | searchField.getDocument().addDocumentListener(new DocumentListener() { |
| 79 | @Override | 86 | @Override |
| @@ -108,7 +115,11 @@ public class SearchDialog { | |||
| 108 | } | 115 | } |
| 109 | }); | 116 | }); |
| 110 | searchField.addActionListener(e -> openSelected()); | 117 | searchField.addActionListener(e -> openSelected()); |
| 111 | contentPane.add(searchField, BorderLayout.NORTH); | 118 | inputPanel.add(searchField, BorderLayout.NORTH); |
| 119 | |||
| 120 | onlyExactMatchesCheckbox = new JCheckBox(I18n.translate("menu.search.only_exact_matches")); | ||
| 121 | onlyExactMatchesCheckbox.addActionListener(e -> updateList()); | ||
| 122 | inputPanel.add(onlyExactMatchesCheckbox, BorderLayout.SOUTH); | ||
| 112 | 123 | ||
| 113 | classListModel = new DefaultListModel<>(); | 124 | classListModel = new DefaultListModel<>(); |
| 114 | classList = new JList<>(); | 125 | classList = new JList<>(); |
| @@ -167,6 +178,7 @@ public class SearchDialog { | |||
| 167 | searchField.requestFocus(); | 178 | searchField.requestFocus(); |
| 168 | searchField.selectAll(); | 179 | searchField.selectAll(); |
| 169 | 180 | ||
| 181 | dialog.setTitle(I18n.translate(type.getTranslationKey())); | ||
| 170 | dialog.setVisible(true); | 182 | dialog.setVisible(true); |
| 171 | } | 183 | } |
| 172 | 184 | ||
| @@ -238,7 +250,7 @@ public class SearchDialog { | |||
| 238 | } | 250 | } |
| 239 | }; | 251 | }; |
| 240 | 252 | ||
| 241 | currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> queue.add(new Order(idx, e))); | 253 | currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> queue.add(new Order(idx, e)), onlyExactMatchesCheckbox.isSelected()); |
| 242 | SwingUtilities.invokeLater(updater); | 254 | SwingUtilities.invokeLater(updater); |
| 243 | } | 255 | } |
| 244 | 256 | ||
| @@ -328,8 +340,18 @@ public class SearchDialog { | |||
| 328 | } | 340 | } |
| 329 | 341 | ||
| 330 | public enum Type { | 342 | public enum Type { |
| 331 | CLASS, | 343 | CLASS("menu.search.class"), |
| 332 | METHOD, | 344 | METHOD("menu.search.method"), |
| 333 | FIELD | 345 | FIELD("menu.search.field"); |
| 346 | |||
| 347 | private final String translationKey; | ||
| 348 | |||
| 349 | Type(String translationKey) { | ||
| 350 | this.translationKey = translationKey; | ||
| 351 | } | ||
| 352 | |||
| 353 | public String getTranslationKey() { | ||
| 354 | return translationKey; | ||
| 355 | } | ||
| 334 | } | 356 | } |
| 335 | } | 357 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 24a69b6..1cfad50 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -138,7 +138,9 @@ public class MenuBar { | |||
| 138 | ui.add(this.helpMenu); | 138 | ui.add(this.helpMenu); |
| 139 | 139 | ||
| 140 | this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); | 140 | this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); |
| 141 | this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_DOWN_MASK)); | 141 | this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.CTRL_DOWN_MASK)); |
| 142 | this.searchMethodItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2, InputEvent.CTRL_DOWN_MASK)); | ||
| 143 | this.searchFieldItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3, InputEvent.CTRL_DOWN_MASK)); | ||
| 142 | 144 | ||
| 143 | this.jarOpenItem.addActionListener(_e -> this.onOpenJarClicked()); | 145 | this.jarOpenItem.addActionListener(_e -> this.onOpenJarClicked()); |
| 144 | this.jarCloseItem.addActionListener(_e -> this.gui.getController().closeJar()); | 146 | this.jarCloseItem.addActionListener(_e -> this.gui.getController().closeJar()); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java index c8212ce..67414ca 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java | |||
| @@ -55,7 +55,7 @@ public class SearchUtil<T extends SearchEntry> { | |||
| 55 | return entries.values().parallelStream().map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0)))).filter(e -> e.b > 0).sorted(Comparator.comparingDouble(o -> -o.b)).map(e -> e.a.searchEntry).sequential(); | 55 | return entries.values().parallelStream().map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0)))).filter(e -> e.b > 0).sorted(Comparator.comparingDouble(o -> -o.b)).map(e -> e.a.searchEntry).sequential(); |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | public SearchControl asyncSearch(String term, SearchResultConsumer<T> consumer) { | 58 | public SearchControl asyncSearch(String term, SearchResultConsumer<T> consumer, boolean onlyExactMatches) { |
| 59 | Map<String, Integer> hitCount = new HashMap<>(this.hitCount); | 59 | Map<String, Integer> hitCount = new HashMap<>(this.hitCount); |
| 60 | Map<T, Entry<T>> entries = new HashMap<>(this.entries); | 60 | Map<T, Entry<T>> entries = new HashMap<>(this.entries); |
| 61 | float[] scores = new float[entries.size()]; | 61 | float[] scores = new float[entries.size()]; |
| @@ -71,6 +71,11 @@ public class SearchUtil<T extends SearchEntry> { | |||
| 71 | return; | 71 | return; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | // if onlyExactMatches is true, don't add any entries that don't have an exact match | ||
| 75 | if (onlyExactMatches && value.searchEntry.getSearchableNames().stream().noneMatch(name -> name.equalsIgnoreCase(term))) { | ||
| 76 | return; | ||
| 77 | } | ||
| 78 | |||
| 74 | float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); | 79 | float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); |
| 75 | 80 | ||
| 76 | if (score <= 0) { | 81 | if (score <= 0) { |
| @@ -140,7 +145,15 @@ public class SearchUtil<T extends SearchEntry> { | |||
| 140 | 145 | ||
| 141 | public float getScore(String term, int hits) { | 146 | public float getScore(String term, int hits) { |
| 142 | String ucTerm = term.toUpperCase(Locale.ROOT); | 147 | String ucTerm = term.toUpperCase(Locale.ROOT); |
| 143 | float maxScore = (float) Arrays.stream(components).mapToDouble(name -> getScoreFor(ucTerm, name)).max().orElse(0.0); | 148 | float maxScore; |
| 149 | |||
| 150 | // if exact match, make sure it's at the top of the list | ||
| 151 | if (searchEntry.getSearchableNames().stream().anyMatch(name -> name.equalsIgnoreCase(term))) { | ||
| 152 | maxScore = Float.MAX_VALUE / 2; | ||
| 153 | } else { | ||
| 154 | maxScore = (float) Arrays.stream(components).mapToDouble(name -> getScoreFor(ucTerm, name)).max().orElse(0.0); | ||
| 155 | } | ||
| 156 | |||
| 144 | return maxScore * (hits + 1); | 157 | return maxScore * (hits + 1); |
| 145 | } | 158 | } |
| 146 | 159 | ||
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 5d7ee25..421620e 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json | |||
| @@ -53,6 +53,7 @@ | |||
| 53 | "menu.search.class": "Search Classes", | 53 | "menu.search.class": "Search Classes", |
| 54 | "menu.search.method": "Search Methods", | 54 | "menu.search.method": "Search Methods", |
| 55 | "menu.search.field": "Search Fields", | 55 | "menu.search.field": "Search Fields", |
| 56 | "menu.search.only_exact_matches": "Only show exact matches", | ||
| 56 | "menu.collab": "Collab", | 57 | "menu.collab": "Collab", |
| 57 | "menu.collab.connect": "Connect to Server", | 58 | "menu.collab.connect": "Connect to Server", |
| 58 | "menu.collab.connect.error": "Error connecting to server", | 59 | "menu.collab.connect.error": "Error connecting to server", |