summaryrefslogtreecommitdiff
path: root/enigma-swing
diff options
context:
space:
mode:
authorGravatar ramidzkh2022-09-13 03:28:25 +1000
committerGravatar GitHub2022-09-12 18:28:25 +0100
commit0a80622efae2fd54df3ed991eb856b3718d6820c (patch)
tree260cc7e50b4b1fa6f51dba1da38c1516632a1765 /enigma-swing
parentEnsure the root method is used when mapping specialized methods (#459) (diff)
downloadenigma-0a80622efae2fd54df3ed991eb856b3718d6820c.tar.gz
enigma-0a80622efae2fd54df3ed991eb856b3718d6820c.tar.xz
enigma-0a80622efae2fd54df3ed991eb856b3718d6820c.zip
Nested packages in Swing UI (#458)
* WIP nested packages * Fix deobfuscated panel package having obfuscated packages * Remove stream * Mass toggle classes obfuscation status from selector * Fix deobfuscated classes with the same name being hidden from class selectors * Open classes by pressing enter
Diffstat (limited to 'enigma-swing')
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java401
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java26
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java6
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java124
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java4
5 files changed, 209 insertions, 352 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 57b23c9a..b7d3c4ef 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -11,40 +11,33 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import java.awt.Component;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.util.*;
18
19import javax.annotation.Nullable;
20import javax.swing.JTree;
21import javax.swing.event.CellEditorListener;
22import javax.swing.event.ChangeEvent;
23import javax.swing.tree.*;
24
25import com.google.common.collect.ArrayListMultimap;
26import com.google.common.collect.Lists;
27import com.google.common.collect.Maps;
28import com.google.common.collect.Multimap;
29import cuchaz.enigma.gui.node.ClassSelectorClassNode; 14import cuchaz.enigma.gui.node.ClassSelectorClassNode;
30import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
31import cuchaz.enigma.gui.util.GuiUtil; 15import cuchaz.enigma.gui.util.GuiUtil;
32import cuchaz.enigma.translation.Translator;
33import cuchaz.enigma.translation.representation.entry.ClassEntry; 16import cuchaz.enigma.translation.representation.entry.ClassEntry;
34import cuchaz.enigma.utils.validation.ValidationContext; 17import cuchaz.enigma.utils.validation.ValidationContext;
35 18
19import javax.swing.*;
20import javax.swing.event.CellEditorListener;
21import javax.swing.event.ChangeEvent;
22import javax.swing.tree.*;
23import java.awt.*;
24import java.awt.event.KeyAdapter;
25import java.awt.event.KeyEvent;
26import java.awt.event.MouseAdapter;
27import java.awt.event.MouseEvent;
28import java.util.List;
29import java.util.*;
30
36public class ClassSelector extends JTree { 31public class ClassSelector extends JTree {
37 32
38 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); 33 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
39 34
35 private final Comparator<ClassEntry> comparator;
40 private final GuiController controller; 36 private final GuiController controller;
41 37
42 private DefaultMutableTreeNode rootNodes; 38 private NestedPackages packageManager;
43 private ClassSelectionListener selectionListener; 39 private ClassSelectionListener selectionListener;
44 private RenameSelectionListener renameSelectionListener; 40 private RenameSelectionListener renameSelectionListener;
45 private Comparator<ClassEntry> comparator;
46
47 private final Map<ClassEntry, ClassEntry> displayedObfToDeobf = new HashMap<>();
48 41
49 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) { 42 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) {
50 this.comparator = comparator; 43 this.comparator = comparator;
@@ -69,6 +62,30 @@ public class ClassSelector extends JTree {
69 } 62 }
70 } 63 }
71 }); 64 });
65 addKeyListener(new KeyAdapter() {
66 @Override
67 public void keyPressed(KeyEvent e) {
68 TreePath[] paths = getSelectionPaths();
69
70 if (paths != null) {
71 if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_O) {
72 for (TreePath path : paths) {
73 if (path.getLastPathComponent() instanceof ClassSelectorClassNode node) {
74 gui.toggleMappingFromEntry(node.getObfEntry());
75 }
76 }
77 }
78
79 if (selectionListener != null && e.getKeyCode() == KeyEvent.VK_ENTER) {
80 for (TreePath path : paths) {
81 if (path.getLastPathComponent() instanceof ClassSelectorClassNode node) {
82 selectionListener.onSelectClass(node.getObfEntry());
83 }
84 }
85 }
86 }
87 }
88 });
72 89
73 final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() { 90 final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() {
74 { 91 {
@@ -143,19 +160,6 @@ public class ClassSelector extends JTree {
143 this.renameSelectionListener = null; 160 this.renameSelectionListener = null;
144 } 161 }
145 162
146 public boolean isDuplicate(Object[] nodes, String data) {
147 int count = 0;
148
149 for (Object node : nodes) {
150 if (node.toString().equals(data)) {
151 count++;
152 if (count == 2)
153 return true;
154 }
155 }
156 return false;
157 }
158
159 public void setSelectionListener(ClassSelectionListener val) { 163 public void setSelectionListener(ClassSelectionListener val) {
160 this.selectionListener = val; 164 this.selectionListener = val;
161 } 165 }
@@ -165,376 +169,109 @@ public class ClassSelector extends JTree {
165 } 169 }
166 170
167 public void setClasses(Collection<ClassEntry> classEntries) { 171 public void setClasses(Collection<ClassEntry> classEntries) {
168 displayedObfToDeobf.clear(); 172 List<StateEntry> state = getExpansionState();
169 173
170 List<StateEntry> state = getExpansionState(this);
171 if (classEntries == null) { 174 if (classEntries == null) {
172 setModel(null); 175 setModel(null);
173 return; 176 return;
174 } 177 }
175 178
176 Translator translator = controller.project.getMapper().getDeobfuscator(); 179 // update the tree control
177 180 packageManager = new NestedPackages(classEntries, comparator, controller.project.getMapper());
178 // build the package names 181 setModel(new DefaultTreeModel(packageManager.getRoot()));
179 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
180 for (ClassEntry obfClass : classEntries) {
181 ClassEntry deobfClass = translator.translate(obfClass);
182 packages.put(deobfClass.getPackageName(), null);
183 }
184 182
185 // sort the packages 183 restoreExpansionState(state);
186 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
187 sortedPackageNames.sort((a, b) ->
188 {
189 // I can never keep this rule straight when writing these damn things...
190 // a < b => -1, a == b => 0, a > b => +1
191
192 if (b == null || a == null) {
193 return 0;
194 }
195
196 String[] aparts = a.split("/");
197 String[] bparts = b.split("/");
198 for (int i = 0; true; i++) {
199 if (i >= aparts.length) {
200 return -1;
201 } else if (i >= bparts.length) {
202 return 1;
203 }
204
205 int result = aparts[i].compareTo(bparts[i]);
206 if (result != 0) {
207 return result;
208 }
209 }
210 });
211
212 // create the rootNodes node and the package nodes
213 rootNodes = new DefaultMutableTreeNode();
214 for (String packageName : sortedPackageNames) {
215 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
216 packages.put(packageName, node);
217 rootNodes.add(node);
218 }
219
220 // put the classes into packages
221 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
222 for (ClassEntry obfClass : classEntries) {
223 ClassEntry deobfClass = translator.translate(obfClass);
224 packagedClassEntries.put(deobfClass.getPackageName(), obfClass);
225 }
226
227 // build the class nodes
228 for (String packageName : packagedClassEntries.keySet()) {
229 // sort the class entries
230 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
231 classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2)));
232
233 // create the nodes in order
234 for (ClassEntry obfClass : classEntriesInPackage) {
235 ClassEntry deobfClass = translator.translate(obfClass);
236 ClassSelectorPackageNode node = packages.get(packageName);
237 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass);
238 displayedObfToDeobf.put(obfClass, deobfClass);
239 node.add(classNode);
240 }
241 }
242
243 // finally, update the tree control
244 setModel(new DefaultTreeModel(rootNodes));
245
246 restoreExpansionState(this, state);
247 } 184 }
248 185
249 public ClassEntry getSelectedClass() { 186 public ClassEntry getSelectedClass() {
250 if (!isSelectionEmpty()) { 187 if (!isSelectionEmpty()) {
251 Object selectedNode = getSelectionPath().getLastPathComponent(); 188 Object selectedNode = getSelectionPath().getLastPathComponent();
189
252 if (selectedNode instanceof ClassSelectorClassNode classNode) { 190 if (selectedNode instanceof ClassSelectorClassNode classNode) {
253 return classNode.getClassEntry(); 191 return classNode.getClassEntry();
254 } 192 }
255 } 193 }
256 return null;
257 }
258 194
259 public String getSelectedPackage() {
260 if (!isSelectionEmpty()) {
261 Object selectedNode = getSelectionPath().getLastPathComponent();
262 if (selectedNode instanceof ClassSelectorPackageNode packageNode) {
263 return packageNode.getPackageName();
264 } else if (selectedNode instanceof ClassSelectorClassNode classNode) {
265 return classNode.getClassEntry().getPackageName();
266 }
267 }
268 return null; 195 return null;
269 } 196 }
270 197
271 public boolean isDescendant(TreePath path1, TreePath path2) {
272 int count1 = path1.getPathCount();
273 int count2 = path2.getPathCount();
274 if (count1 <= count2) {
275 return false;
276 }
277 while (count1 != count2) {
278 path1 = path1.getParentPath();
279 count1--;
280 }
281 return path1.equals(path2);
282 }
283
284 public enum State { 198 public enum State {
285 EXPANDED, 199 EXPANDED,
286 SELECTED 200 SELECTED
287 } 201 }
288 202
289 public static class StateEntry { 203 public record StateEntry(State state, TreePath path) {
290 public final State state;
291 public final TreePath path;
292
293 public StateEntry(State state, TreePath path) {
294 this.state = state;
295 this.path = path;
296 }
297 } 204 }
298 205
299 public List<StateEntry> getExpansionState(JTree tree) { 206 public List<StateEntry> getExpansionState() {
300 List<StateEntry> state = new ArrayList<>(); 207 List<StateEntry> state = new ArrayList<>();
301 int rowCount = tree.getRowCount(); 208 int rowCount = getRowCount();
302 for (int i = 0; i < rowCount; i++) { 209 for (int i = 0; i < rowCount; i++) {
303 TreePath path = tree.getPathForRow(i); 210 TreePath path = getPathForRow(i);
304 if (tree.isPathSelected(path)) { 211 if (isPathSelected(path)) {
305 state.add(new StateEntry(State.SELECTED, path)); 212 state.add(new StateEntry(State.SELECTED, path));
306 } 213 }
307 if (tree.isExpanded(path)) { 214 if (isExpanded(path)) {
308 state.add(new StateEntry(State.EXPANDED, path)); 215 state.add(new StateEntry(State.EXPANDED, path));
309 } 216 }
310 } 217 }
311 return state; 218 return state;
312 } 219 }
313 220
314 public void restoreExpansionState(JTree tree, List<StateEntry> expansionState) { 221 public void restoreExpansionState(List<StateEntry> expansionState) {
315 tree.clearSelection(); 222 clearSelection();
316 223
317 for (StateEntry entry : expansionState) { 224 for (StateEntry entry : expansionState) {
318 switch (entry.state) { 225 switch (entry.state) {
319 case SELECTED -> tree.addSelectionPath(entry.path); 226 case SELECTED -> addSelectionPath(entry.path);
320 case EXPANDED -> tree.expandPath(entry.path); 227 case EXPANDED -> expandPath(entry.path);
321 } 228 }
322 } 229 }
323 } 230 }
324 231
325 public List<ClassSelectorPackageNode> packageNodes() {
326 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
327 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
328 Enumeration<?> children = root.children();
329 while (children.hasMoreElements()) {
330 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
331 nodes.add(packageNode);
332 }
333 return nodes;
334 }
335
336 public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
337 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
338 Enumeration<?> children = packageNode.children();
339 while (children.hasMoreElements()) {
340 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
341 nodes.add(classNode);
342 }
343 return nodes;
344 }
345
346 public void expandPackage(String packageName) { 232 public void expandPackage(String packageName) {
347 if (packageName == null) { 233 if (packageName == null) {
348 return; 234 return;
349 } 235 }
350 for (ClassSelectorPackageNode packageNode : packageNodes()) { 236
351 if (packageNode.getPackageName().equals(packageName)) { 237 expandPath(packageManager.getPackagePath(packageName));
352 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
353 return;
354 }
355 }
356 } 238 }
357 239
358 public void expandAll() { 240 public void expandAll() {
359 for (ClassSelectorPackageNode packageNode : packageNodes()) { 241 for (DefaultMutableTreeNode packageNode : packageManager.getPackageNodes()) {
360 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); 242 expandPath(new TreePath(packageNode.getPath()));
361 } 243 }
362 } 244 }
363 245
364 public void collapseAll() { 246 public void collapseAll() {
365 for (ClassSelectorPackageNode packageNode : packageNodes()) { 247 for (DefaultMutableTreeNode packageNode : packageManager.getPackageNodes()) {
366 collapsePath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); 248 collapsePath(new TreePath(packageNode.getPath()));
367 }
368 }
369
370 public ClassEntry getFirstClass() {
371 ClassSelectorPackageNode packageNode = packageNodes().get(0);
372 if (packageNode != null) {
373 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
374 if (classNode != null) {
375 return classNode.getClassEntry();
376 }
377 }
378 return null;
379 }
380
381 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
382 String packageName = entry.getPackageName();
383 if (packageName == null) {
384 packageName = "(none)";
385 }
386 for (ClassSelectorPackageNode packageNode : packageNodes()) {
387 if (packageNode.getPackageName().equals(packageName)) {
388 return packageNode;
389 }
390 } 249 }
391 return null;
392 }
393
394 @Nullable
395 public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) {
396 return displayedObfToDeobf.get(obfEntry);
397 }
398
399 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
400 ClassSelectorPackageNode packageNode = getPackageNode(entry);
401
402 if (selector != null && packageNode == null && selector.getPackageNode(entry) != null)
403 return selector.getPackageNode(entry);
404 return packageNode;
405 }
406
407 public ClassEntry getNextClass(ClassEntry entry) {
408 boolean foundIt = false;
409 for (ClassSelectorPackageNode packageNode : packageNodes()) {
410 if (!foundIt) {
411 // skip to the package with our target in it
412 if (packageNode.getPackageName().equals(entry.getPackageName())) {
413 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
414 if (!foundIt) {
415 if (classNode.getClassEntry().equals(entry)) {
416 foundIt = true;
417 }
418 } else {
419 // return the next class
420 return classNode.getClassEntry();
421 }
422 }
423 }
424 } else {
425 // return the next class
426 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
427 if (classNode != null) {
428 return classNode.getClassEntry();
429 }
430 }
431 }
432 return null;
433 } 250 }
434 251
435 public void setSelectionClass(ClassEntry classEntry) { 252 public void setSelectionClass(ClassEntry classEntry) {
436 expandPackage(classEntry.getPackageName()); 253 expandPackage(classEntry.getPackageName());
437 for (ClassSelectorPackageNode packageNode : packageNodes()) { 254 ClassSelectorClassNode node = packageManager.getClassNode(classEntry);
438 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
439 if (classNode.getClassEntry().equals(classEntry)) {
440 TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode});
441 setSelectionPath(path);
442 scrollPathToVisible(path);
443 }
444 }
445 }
446 }
447 255
448 public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { 256 if (node != null) {
449 DefaultTreeModel model = (DefaultTreeModel) getModel(); 257 TreePath path = new TreePath(node.getPath());
450 258 setSelectionPath(path);
451 if (packageNode == null) 259 scrollPathToVisible(path);
452 return;
453
454 for (int i = 0; i < packageNode.getChildCount(); i++) {
455 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
456 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
457 model.removeNodeFromParent(childNode);
458 if (childNode instanceof ClassSelectorClassNode) {
459 displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry());
460 }
461 break;
462 }
463 } 260 }
464 } 261 }
465 262
466 public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) {
467 if (packageNode != null && packageNode.getChildCount() == 0)
468 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
469 }
470
471 public void moveClassIn(ClassEntry classEntry) { 263 public void moveClassIn(ClassEntry classEntry) {
472 removeEntry(classEntry); 264 removeEntry(classEntry);
473 insertNode(classEntry); 265 packageManager.addEntry(classEntry);
474 }
475
476 public void moveClassOut(ClassEntry classEntry) {
477 removeEntry(classEntry);
478 } 266 }
479 267
480 private void removeEntry(ClassEntry classEntry) { 268 public void removeEntry(ClassEntry classEntry) {
481 ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry); 269 packageManager.removeClassNode(classEntry);
482 if (previousDeobf != null) {
483 ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf);
484 removeNode(packageNode, previousDeobf);
485 removeNodeIfEmpty(packageNode);
486 }
487 }
488
489 public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) {
490 DefaultTreeModel model = (DefaultTreeModel) getModel();
491 ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
492 if (newPackageNode == null) {
493 newPackageNode = new ClassSelectorPackageNode(entry.getPackageName());
494 model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode));
495 }
496 return newPackageNode;
497 }
498
499 public void insertNode(ClassEntry obfEntry) {
500 ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
501 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
502
503 DefaultTreeModel model = (DefaultTreeModel) getModel();
504 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry);
505 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
506
507 displayedObfToDeobf.put(obfEntry, deobfEntry);
508 } 270 }
509 271
510 public void reload() { 272 public void reload() {
511 DefaultTreeModel model = (DefaultTreeModel) getModel(); 273 DefaultTreeModel model = (DefaultTreeModel) getModel();
512 model.reload(rootNodes); 274 model.reload(packageManager.getRoot());
513 }
514
515 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) {
516 List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode);
517 classNodes.add(classNode);
518 classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry()));
519 for (int i = 0; i < classNodes.size(); i++)
520 if (classNodes.get(i) == classNode)
521 return i;
522
523 return 0;
524 }
525
526 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) {
527 List<ClassSelectorPackageNode> packageNodes = packageNodes();
528 if (!packageNodes.contains(newPackageNode)) {
529 packageNodes.add(newPackageNode);
530 packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString));
531 }
532
533 for (int i = 0; i < packageNodes.size(); i++)
534 if (packageNodes.get(i) == newPackageNode)
535 return i;
536
537 return 0;
538 } 275 }
539 276
540 public interface ClassSelectionListener { 277 public interface ClassSelectionListener {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
index cac0ea89..5a1e3d8b 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -376,7 +376,10 @@ public class Gui {
376 if (cursorReference == null) return; 376 if (cursorReference == null) return;
377 377
378 Entry<?> obfEntry = cursorReference.entry; 378 Entry<?> obfEntry = cursorReference.entry;
379 toggleMappingFromEntry(obfEntry);
380 }
379 381
382 public void toggleMappingFromEntry(Entry<?> obfEntry) {
380 if (this.controller.project.getMapper().getDeobfMapping(obfEntry).targetName() != null) { 383 if (this.controller.project.getMapper().getDeobfMapping(obfEntry).targetName() != null) {
381 validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).clearDeobfName())); 384 validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).clearDeobfName()));
382 } else { 385 } else {
@@ -384,17 +387,6 @@ public class Gui {
384 } 387 }
385 } 388 }
386 389
387 private TreePath getPathToRoot(TreeNode node) {
388 List<TreeNode> nodes = Lists.newArrayList();
389 TreeNode n = node;
390 do {
391 nodes.add(n);
392 n = n.getParent();
393 } while (n != null);
394 Collections.reverse(nodes);
395 return new TreePath(nodes.toArray());
396 }
397
398 public void showDiscardDiag(Function<Integer, Void> callback, String... options) { 390 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
399 int response = JOptionPane.showOptionDialog(this.mainWindow.frame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, 391 int response = JOptionPane.showOptionDialog(this.mainWindow.frame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION,
400 JOptionPane.QUESTION_MESSAGE, null, options, options[2]); 392 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
@@ -492,20 +484,20 @@ public class Gui {
492 public void moveClassTree(Entry<?> obfEntry, boolean isOldOb, boolean isNewOb) { 484 public void moveClassTree(Entry<?> obfEntry, boolean isOldOb, boolean isNewOb) {
493 ClassEntry classEntry = obfEntry.getContainingClass(); 485 ClassEntry classEntry = obfEntry.getContainingClass();
494 486
495 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); 487 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState();
496 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); 488 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState();
497 489
498 // Ob -> deob 490 // Ob -> deob
499 if (!isNewOb) { 491 if (!isNewOb) {
500 this.deobfPanel.deobfClasses.moveClassIn(classEntry); 492 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
501 this.obfPanel.obfClasses.moveClassOut(classEntry); 493 this.obfPanel.obfClasses.removeEntry(classEntry);
502 this.deobfPanel.deobfClasses.reload(); 494 this.deobfPanel.deobfClasses.reload();
503 this.obfPanel.obfClasses.reload(); 495 this.obfPanel.obfClasses.reload();
504 } 496 }
505 // Deob -> ob 497 // Deob -> ob
506 else if (!isOldOb) { 498 else if (!isOldOb) {
507 this.obfPanel.obfClasses.moveClassIn(classEntry); 499 this.obfPanel.obfClasses.moveClassIn(classEntry);
508 this.deobfPanel.deobfClasses.moveClassOut(classEntry); 500 this.deobfPanel.deobfClasses.removeEntry(classEntry);
509 this.deobfPanel.deobfClasses.reload(); 501 this.deobfPanel.deobfClasses.reload();
510 this.obfPanel.obfClasses.reload(); 502 this.obfPanel.obfClasses.reload();
511 } 503 }
@@ -518,8 +510,8 @@ public class Gui {
518 this.deobfPanel.deobfClasses.reload(); 510 this.deobfPanel.deobfClasses.reload();
519 } 511 }
520 512
521 this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf); 513 this.deobfPanel.deobfClasses.restoreExpansionState(stateDeobf);
522 this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf); 514 this.obfPanel.obfClasses.restoreExpansionState(stateObf);
523 } 515 }
524 516
525 public ObfPanel getObfPanel() { 517 public ObfPanel getObfPanel() {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
index 67ac6625..47a854f9 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -52,6 +52,7 @@ import cuchaz.enigma.source.DecompiledClassSource;
52import cuchaz.enigma.source.DecompilerService; 52import cuchaz.enigma.source.DecompilerService;
53import cuchaz.enigma.source.SourceIndex; 53import cuchaz.enigma.source.SourceIndex;
54import cuchaz.enigma.source.Token; 54import cuchaz.enigma.source.Token;
55import cuchaz.enigma.translation.TranslateResult;
55import cuchaz.enigma.translation.Translator; 56import cuchaz.enigma.translation.Translator;
56import cuchaz.enigma.translation.mapping.*; 57import cuchaz.enigma.translation.mapping.*;
57import cuchaz.enigma.translation.mapping.serde.MappingFormat; 58import cuchaz.enigma.translation.mapping.serde.MappingFormat;
@@ -381,10 +382,11 @@ public class GuiController implements ClientPacketHandler {
381 return; 382 return;
382 } 383 }
383 384
384 ClassEntry deobfEntry = mapper.deobfuscate(entry); 385 TranslateResult<ClassEntry> result = mapper.extendedDeobfuscate(entry);
386 ClassEntry deobfEntry = result.getValue();
385 387
386 List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE); 388 List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
387 boolean obfuscated = deobfEntry.equals(entry); 389 boolean obfuscated = result.isObfuscated() && deobfEntry.equals(entry);
388 390
389 if (obfuscated && !obfService.isEmpty()) { 391 if (obfuscated && !obfService.isEmpty()) {
390 if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { 392 if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java
new file mode 100644
index 00000000..309f9106
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java
@@ -0,0 +1,124 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.gui.node.ClassSelectorClassNode;
4import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
5import cuchaz.enigma.translation.mapping.EntryRemapper;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7
8import javax.swing.tree.DefaultMutableTreeNode;
9import javax.swing.tree.MutableTreeNode;
10import javax.swing.tree.TreeNode;
11import javax.swing.tree.TreePath;
12import java.util.Collection;
13import java.util.Comparator;
14import java.util.HashMap;
15import java.util.Map;
16
17public class NestedPackages {
18
19 private final DefaultMutableTreeNode root = new DefaultMutableTreeNode();
20 private final Map<String, DefaultMutableTreeNode> packageToNode = new HashMap<>();
21 private final Map<ClassEntry, ClassSelectorClassNode> classToNode = new HashMap<>();
22 private final EntryRemapper remapper;
23 private final Comparator<TreeNode> comparator;
24
25 public NestedPackages(Iterable<ClassEntry> entries, Comparator<ClassEntry> entryComparator, EntryRemapper remapper) {
26 this.remapper = remapper;
27 this.comparator = (a, b) -> {
28 if (a instanceof ClassSelectorPackageNode pA) {
29 if (b instanceof ClassSelectorPackageNode pB) {
30 return pA.getPackageName().compareTo(pB.getPackageName());
31 } else {
32 return -1;
33 }
34 } else if (a instanceof ClassSelectorClassNode cA) {
35 if (b instanceof ClassSelectorClassNode cB) {
36 return entryComparator.compare(cA.getClassEntry(), cB.getClassEntry());
37 } else {
38 return 1;
39 }
40 }
41
42 return 0;
43 };
44
45 for (var entry : entries) {
46 addEntry(entry);
47 }
48 }
49
50 public void addEntry(ClassEntry entry) {
51 var translated = remapper.deobfuscate(entry);
52 var me = new ClassSelectorClassNode(entry, translated);
53 classToNode.put(entry, me);
54 insert(getPackage(translated.getPackageName()), me);
55 }
56
57 public DefaultMutableTreeNode getPackage(String packageName) {
58 var node = packageToNode.get(packageName);
59
60 if (packageName == null) {
61 return root;
62 }
63
64 if (node == null) {
65 node = new ClassSelectorPackageNode(packageName);
66 insert(getPackage(ClassEntry.getParentPackage(packageName)), node);
67 packageToNode.put(packageName, node);
68 }
69
70 return node;
71 }
72
73 public DefaultMutableTreeNode getRoot() {
74 return root;
75 }
76
77 public TreePath getPackagePath(String packageName) {
78 var node = packageToNode.getOrDefault(packageName, root);
79 return new TreePath(node.getPath());
80 }
81
82 public ClassSelectorClassNode getClassNode(ClassEntry entry) {
83 return classToNode.get(entry);
84 }
85
86 public void removeClassNode(ClassEntry entry) {
87 var node = classToNode.remove(entry);
88
89 if (node != null) {
90 node.removeFromParent();
91 // remove dangling packages
92 var packageNode = packageToNode.get(entry.getPackageName());
93
94 while (packageNode != null && packageNode.getChildCount() == 0) {
95 var theNode = packageNode;
96 packageNode = (DefaultMutableTreeNode) packageNode.getParent();
97 theNode.removeFromParent();
98
99 if (theNode instanceof ClassSelectorPackageNode pn) {
100 packageToNode.remove(pn.getPackageName());
101 }
102 }
103 }
104 }
105
106 public Collection<DefaultMutableTreeNode> getPackageNodes() {
107 return packageToNode.values();
108 }
109
110 private void insert(DefaultMutableTreeNode parent, MutableTreeNode child) {
111 var index = 0;
112 var children = parent.children();
113
114 while (children.hasMoreElements()) {
115 if (comparator.compare(children.nextElement(), child) < 0) {
116 index++;
117 } else {
118 break;
119 }
120 }
121
122 parent.insert(child, index);
123 }
124}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
index caa985c9..c1c7d387 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
@@ -11,6 +11,8 @@
11 11
12package cuchaz.enigma.gui.node; 12package cuchaz.enigma.gui.node;
13 13
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15
14import javax.swing.tree.DefaultMutableTreeNode; 16import javax.swing.tree.DefaultMutableTreeNode;
15 17
16public class ClassSelectorPackageNode extends DefaultMutableTreeNode { 18public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
@@ -39,7 +41,7 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
39 41
40 @Override 42 @Override
41 public String toString() { 43 public String toString() {
42 return !packageName.equals("(none)") ? this.packageName : "(none)"; 44 return !packageName.equals("(none)") ? ClassEntry.getNameInPackage(this.packageName) : "(none)";
43 } 45 }
44 46
45 @Override 47 @Override