From 65a8ff63bae4f6f2e025e3dbf0b7b8eb64193039 Mon Sep 17 00:00:00 2001 From: Erlend Ã…mdal Date: Sun, 12 May 2019 09:47:41 +0200 Subject: Add forward and backward reference history with mouse navigation (#132) * Add History * Add forward and backward reference history * Update PopupMenuBar text for history * Fix indentation * Fix more indentation --- src/main/java/cuchaz/enigma/gui/Gui.java | 100 +++++++++------------ src/main/java/cuchaz/enigma/gui/GuiController.java | 69 +++++++++++--- .../cuchaz/enigma/gui/dialog/SearchDialog.java | 2 +- .../cuchaz/enigma/gui/elements/PopupMenuBar.java | 13 ++- .../java/cuchaz/enigma/gui/panels/PanelDeobf.java | 2 +- .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 19 +++- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 2 +- src/main/java/cuchaz/enigma/gui/util/History.java | 49 ++++++++++ 8 files changed, 178 insertions(+), 78 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/gui/util/History.java (limited to 'src/main/java') diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 92c68ac..3e85920 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -29,6 +29,7 @@ import cuchaz.enigma.gui.panels.PanelDeobf; import cuchaz.enigma.gui.panels.PanelEditor; import cuchaz.enigma.gui.panels.PanelIdentifier; import cuchaz.enigma.gui.panels.PanelObf; +import cuchaz.enigma.gui.util.History; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.translation.mapping.AccessModifier; import cuchaz.enigma.translation.representation.entry.*; @@ -54,7 +55,8 @@ public class Gui { private final MenuBar menuBar; // state - public EntryReference, Entry> reference; + public History, Entry>> referenceHistory; + public EntryReference, Entry> cursorReference; private boolean shouldNavigateOnClick; public FileDialog jarFileChooser; @@ -162,11 +164,11 @@ public class Gui { Object node = path.getLastPathComponent(); if (node instanceof ClassInheritanceTreeNode) { ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; - navigateTo(new ClassEntry(classNode.getObfClassName())); + controller.navigateTo(new ClassEntry(classNode.getObfClassName())); } else if (node instanceof MethodInheritanceTreeNode) { MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; if (methodNode.isImplemented()) { - navigateTo(methodNode.getMethodEntry()); + controller.navigateTo(methodNode.getMethodEntry()); } } } @@ -195,10 +197,10 @@ public class Gui { Object node = path.getLastPathComponent(); if (node instanceof ClassImplementationsTreeNode) { ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; - navigateTo(classNode.getClassEntry()); + controller.navigateTo(classNode.getClassEntry()); } else if (node instanceof MethodImplementationsTreeNode) { MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; - navigateTo(methodNode.getMethodEntry()); + controller.navigateTo(methodNode.getMethodEntry()); } } } @@ -225,9 +227,9 @@ public class Gui { if (node instanceof ReferenceTreeNode) { ReferenceTreeNode, Entry> referenceNode = ((ReferenceTreeNode, Entry>) node); if (referenceNode.getReference() != null) { - navigateTo(referenceNode.getReference()); + controller.navigateTo(referenceNode.getReference()); } else { - navigateTo(referenceNode.getEntry()); + controller.navigateTo(referenceNode.getEntry()); } } } @@ -433,13 +435,13 @@ public class Gui { } } - private void showReference(EntryReference, Entry> reference) { + private void showCursorReference(EntryReference, Entry> reference) { if (reference == null) { infoPanel.clearReference(); return; } - this.reference = reference; + this.cursorReference = reference; EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(reference); @@ -526,12 +528,12 @@ public class Gui { Token token = this.controller.getToken(pos); boolean isToken = token != null; - reference = this.controller.getReference(token); - Entry referenceEntry = reference != null ? reference.entry : null; + cursorReference = this.controller.getReference(token); + Entry referenceEntry = cursorReference != null ? cursorReference.entry : null; if (referenceEntry != null && shouldNavigateOnClick) { shouldNavigateOnClick = false; - navigateTo(referenceEntry); + this.controller.navigateTo(referenceEntry); return; } @@ -540,10 +542,10 @@ public class Gui { boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); - boolean isRenamable = isToken && this.controller.getDeobfuscator().isRenamable(reference); + boolean isRenamable = isToken && this.controller.getDeobfuscator().isRenamable(cursorReference); if (isToken) { - showReference(reference); + showCursorReference(cursorReference); } else { infoPanel.clearReference(); } @@ -554,7 +556,8 @@ public class Gui { this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); - this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); + this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); + this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) { @@ -564,33 +567,12 @@ public class Gui { } } - public void navigateTo(Entry entry) { - if (!this.controller.entryIsInJar(entry)) { - // entry is not in the jar. Ignore it - return; - } - if (reference != null) { - this.controller.savePreviousReference(reference); - } - this.controller.openDeclaration(entry); - } - - private void navigateTo(EntryReference, Entry> reference) { - if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { - return; - } - if (this.reference != null) { - this.controller.savePreviousReference(this.reference); - } - this.controller.openReference(reference); - } - public void startRename() { // init the text box final JTextField text = new JTextField(); - EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(reference); + EntryReference, Entry> translatedReference = controller.getDeobfuscator().deobfuscate(cursorReference); text.setText(translatedReference.getNameableName()); text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); @@ -631,7 +613,7 @@ public class Gui { String newName = text.getText(); if (saveName && newName != null && !newName.isEmpty()) { try { - this.controller.rename(reference, newName, true); + this.controller.rename(cursorReference, newName, true); } catch (IllegalNameException ex) { text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); text.setToolTipText(ex.getReason()); @@ -643,7 +625,7 @@ public class Gui { // abort the rename JPanel panel = (JPanel) infoPanel.getComponent(0); panel.remove(panel.getComponentCount() - 1); - panel.add(Utils.unboldLabel(new JLabel(reference.getNameableName(), JLabel.LEFT))); + panel.add(Utils.unboldLabel(new JLabel(cursorReference.getNameableName(), JLabel.LEFT))); this.editor.grabFocus(); @@ -652,24 +634,24 @@ public class Gui { public void showInheritance() { - if (reference == null) { + if (cursorReference == null) { return; } inheritanceTree.setModel(null); - if (reference.entry instanceof ClassEntry) { + if (cursorReference.entry instanceof ClassEntry) { // get the class inheritance - ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); + ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); // show the tree at the root TreePath path = getPathToRoot(classNode); inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); inheritanceTree.expandPath(path); inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } else if (reference.entry instanceof MethodEntry) { + } else if (cursorReference.entry instanceof MethodEntry) { // get the method inheritance - MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); + MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); // show the tree at the root TreePath path = getPathToRoot(classNode); @@ -685,7 +667,7 @@ public class Gui { public void showImplementations() { - if (reference == null) { + if (cursorReference == null) { return; } @@ -694,11 +676,11 @@ public class Gui { DefaultMutableTreeNode node = null; // get the class implementations - if (reference.entry instanceof ClassEntry) - node = this.controller.getClassImplementations((ClassEntry) reference.entry); + if (cursorReference.entry instanceof ClassEntry) + node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); else // get the method implementations - if (reference.entry instanceof MethodEntry) - node = this.controller.getMethodImplementations((MethodEntry) reference.entry); + if (cursorReference.entry instanceof MethodEntry) + node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); if (node != null) { // show the tree at the root @@ -714,18 +696,18 @@ public class Gui { } public void showCalls(boolean recurse) { - if (reference == null) { + if (cursorReference == null) { return; } - if (reference.entry instanceof ClassEntry) { - ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) reference.entry); + if (cursorReference.entry instanceof ClassEntry) { + ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof FieldEntry) { - FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); + } else if (cursorReference.entry instanceof FieldEntry) { + FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof MethodEntry) { - MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry, recurse); + } else if (cursorReference.entry instanceof MethodEntry) { + MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); callsTree.setModel(new DefaultTreeModel(node)); } @@ -735,10 +717,10 @@ public class Gui { } public void toggleMapping() { - if (this.controller.getDeobfuscator().isRemapped(reference.entry)) { - this.controller.removeMapping(reference); + if (this.controller.getDeobfuscator().isRemapped(cursorReference.entry)) { + this.controller.removeMapping(cursorReference); } else { - this.controller.markAsDeobfuscated(reference); + this.controller.markAsDeobfuscated(cursorReference); } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 4155062..5610233 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -12,7 +12,6 @@ package cuchaz.enigma.gui; import com.google.common.collect.Lists; -import com.google.common.collect.Queues; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Deobfuscator; @@ -20,6 +19,7 @@ import cuchaz.enigma.SourceProvider; import cuchaz.enigma.analysis.*; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.dialog.ProgressDialog; +import cuchaz.enigma.gui.util.History; import cuchaz.enigma.throwables.MappingParseException; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.*; @@ -40,7 +40,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.Collection; -import java.util.Deque; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -53,14 +52,13 @@ public class GuiController { private final Gui gui; private Deobfuscator deobfuscator; private DecompiledClassSource currentSource; - private Deque, Entry>> referenceStack; + private Path loadedMappingPath; private MappingFormat loadedMappingFormat; public GuiController(Gui gui) { this.gui = gui; - this.referenceStack = Queues.newArrayDeque(); } public boolean isDirty() { @@ -246,6 +244,10 @@ public class GuiController { refreshCurrentClass(reference); } + /** + * Navigates to the declaration with respect to navigation history + * @param entry the entry whose declaration will be navigated to + */ public void openDeclaration(Entry entry) { if (entry == null) { throw new IllegalArgumentException("Entry cannot be null!"); @@ -253,11 +255,29 @@ public class GuiController { openReference(new EntryReference<>(entry, entry.getName())); } + /** + * Navigates to the reference with respect to navigation history + * @param reference the reference + */ public void openReference(EntryReference, Entry> reference) { if (reference == null) { throw new IllegalArgumentException("Reference cannot be null!"); } + if (this.gui.referenceHistory == null) { + this.gui.referenceHistory = new History<>(reference); + } else { + if (!reference.equals(this.gui.referenceHistory.getCurrent())) { + this.gui.referenceHistory.push(reference); + } + } + setReference(reference); + } + /** + * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. + * @param reference the reference + */ + private void setReference(EntryReference, Entry> reference) { // get the reference target class ClassEntry classEntry = reference.getLocationClassEntry(); if (!this.deobfuscator.isRenamable(classEntry)) { @@ -272,6 +292,10 @@ public class GuiController { } } + /** + * Navigates to the reference without modifying history. Assumes the class is loaded. + * @param reference + */ private void showReference(EntryReference, Entry> reference) { EntryRemapper mapper = this.deobfuscator.getMapper(); @@ -289,18 +313,39 @@ public class GuiController { } } - public void savePreviousReference(EntryReference, Entry> reference) { - this.referenceStack.push(reference); + public void openPreviousReference() { + if (hasPreviousReference()) { + setReference(gui.referenceHistory.goBack()); + } } - public void openPreviousReference() { - if (hasPreviousLocation()) { - openReference(this.referenceStack.pop()); + public boolean hasPreviousReference() { + return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); + } + + public void openNextReference() { + if (hasNextReference()) { + setReference(gui.referenceHistory.goForward()); } } - public boolean hasPreviousLocation() { - return !this.referenceStack.isEmpty(); + public boolean hasNextReference() { + return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); + } + + public void navigateTo(Entry entry) { + if (!entryIsInJar(entry)) { + // entry is not in the jar. Ignore it + return; + } + openDeclaration(entry); + } + + public void navigateTo(EntryReference, Entry> reference) { + if (!entryIsInJar(reference.getLocationClassEntry())) { + return; + } + openReference(reference); } private void refreshClasses() { @@ -390,7 +435,7 @@ public class GuiController { public void modifierChange(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - deobfuscator.changeModifier(gui.reference.entry, (AccessModifier) event.getItem()); + deobfuscator.changeModifier(gui.cursorReference.entry, (AccessModifier) event.getItem()); refreshCurrentClass(); } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java index da09c52..a122bd8 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java @@ -125,7 +125,7 @@ public class SearchDialog { .filter(classEntry -> classEntry.getSimpleName().equals(classList.getSelectedValue())). findFirst() .ifPresent(classEntry -> { - parent.navigateTo(classEntry); + parent.getController().navigateTo(classEntry); parent.getDeobfPanel().deobfClasses.setSelectionClass(classEntry); }); } diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java index 32f9172..fbf39ac 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java @@ -15,6 +15,7 @@ public class PopupMenuBar extends JPopupMenu { public final JMenuItem showCallsSpecificMenu; public final JMenuItem openEntryMenu; public final JMenuItem openPreviousMenu; + public final JMenuItem openNextMenu; public final JMenuItem toggleMappingMenu; public PopupMenuBar(Gui gui) { @@ -60,20 +61,28 @@ public class PopupMenuBar extends JPopupMenu { } { JMenuItem menu = new JMenuItem("Go to Declaration"); - menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); + menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry)); menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); menu.setEnabled(false); this.add(menu); this.openEntryMenu = menu; } { - JMenuItem menu = new JMenuItem("Go to previous"); + JMenuItem menu = new JMenuItem("Go back"); menu.addActionListener(event -> gui.getController().openPreviousReference()); menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); menu.setEnabled(false); this.add(menu); this.openPreviousMenu = menu; } + { + JMenuItem menu = new JMenuItem("Go forward"); + menu.addActionListener(event -> gui.getController().openNextReference()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0)); + menu.setEnabled(false); + this.add(menu); + this.openNextMenu = menu; + } { JMenuItem menu = new JMenuItem("Mark as deobfuscated"); menu.addActionListener(event -> gui.toggleMapping()); diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java index 68cc8e1..2a4e2d7 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java @@ -15,7 +15,7 @@ public class PanelDeobf extends JPanel { this.gui = gui; this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); - this.deobfClasses.setSelectionListener(gui::navigateTo); + this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); this.setLayout(new BorderLayout()); diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java index f766743..f19d98f 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java @@ -21,8 +21,19 @@ public class PanelEditor extends JEditorPane { this.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON3) - self.setCaretPosition(self.viewToModel(e.getPoint())); + switch (e.getButton()) { + case MouseEvent.BUTTON3: // Right click + self.setCaretPosition(self.viewToModel(e.getPoint())); + break; + + case 4: // Back navigation + gui.getController().openPreviousReference(); + break; + + case 5: // Forward navigation + gui.getController().openNextReference(); + break; + } } }); this.addKeyListener(new KeyAdapter() { @@ -49,6 +60,10 @@ public class PanelEditor extends JEditorPane { gui.popupMenu.openPreviousMenu.doClick(); break; + case KeyEvent.VK_E: + gui.popupMenu.openNextMenu.doClick(); + break; + case KeyEvent.VK_C: gui.popupMenu.showCallsMenu.doClick(); break; diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index ccdc9f8..8ee564b 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -26,7 +26,7 @@ public class PanelObf extends JPanel { }; this.obfClasses = new ClassSelector(gui, obfClassComparator, false); - this.obfClasses.setSelectionListener(gui::navigateTo); + this.obfClasses.setSelectionListener(gui.getController()::navigateTo); this.obfClasses.setRenameSelectionListener(gui::onPanelRename); this.setLayout(new BorderLayout()); diff --git a/src/main/java/cuchaz/enigma/gui/util/History.java b/src/main/java/cuchaz/enigma/gui/util/History.java new file mode 100644 index 0000000..94f3105 --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/util/History.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.gui.util; + +import com.google.common.collect.Queues; + +import java.util.Deque; + +public class History { + private final Deque previous = Queues.newArrayDeque(); + private final Deque next = Queues.newArrayDeque(); + private T current; + + public History(T initial) { + current = initial; + } + + public T getCurrent() { + return current; + } + + public void push(T value) { + previous.addLast(current); + current = value; + next.clear(); + } + + public void replace(T value) { + current = value; + } + + public boolean canGoBack() { + return !previous.isEmpty(); + } + + public T goBack() { + next.addFirst(current); + current = previous.removeLast(); + return current; + } + + public boolean canGoForward() { + return !next.isEmpty(); + } + + public T goForward() { + previous.addLast(current); + current = next.removeFirst(); + return current; + } +} -- cgit v1.2.3