From ee4b23764448a004d0e6d1c1244e52c4ea53d192 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 5 Sep 2023 09:15:17 -0500 Subject: [PATCH] obf entry navigator --- .../java/cuchaz/enigma/gui/GuiController.java | 3 + .../enigma/gui/NotificationManager.java | 9 +- .../enigma/gui/elements/NavigatorPanel.java | 99 +++++++++++++++++++ .../cuchaz/enigma/gui/panels/EditorPanel.java | 21 +++- .../java/cuchaz/enigma/gui/util/GuiUtil.java | 12 +++ .../cuchaz/enigma/source/SourceIndex.java | 4 +- 6 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/NavigatorPanel.java 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 3fc9188b7..cad0b452b 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -520,6 +520,9 @@ private void applyChange0(ValidationContext vc, EntryChange change, boolean u if (vc.canProceed()) { boolean renamed = !change.getDeobfName().isUnchanged(); this.gui.updateStructure(this.gui.getActiveEditor()); + if (this.gui.getActiveEditor() != null) { + this.gui.getActiveEditor().onRename(target); + } if (!Objects.equals(prev.targetName(), mapping.targetName())) { this.chp.invalidateMapped(); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/NotificationManager.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/NotificationManager.java index e514a193b..9f788b642 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/NotificationManager.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/NotificationManager.java @@ -1,6 +1,7 @@ package cuchaz.enigma.gui; import cuchaz.enigma.gui.docker.NotificationsDocker; +import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.utils.I18n; import cuchaz.enigma.utils.validation.Message; import cuchaz.enigma.utils.validation.ParameterizedMessage; @@ -101,13 +102,7 @@ public void notify(ParameterizedMessage message) { JPanel glass = (JPanel) this.gui.getFrame().getGlassPane(); this.glassPane = glass; - - // set up glass pane to actually display elements - glass.setOpaque(false); - glass.setVisible(true); - glass.setLayout(null); - glass.revalidate(); - + GuiUtil.setUpGlassPane(glass); glass.add(notificationPanel); // set up notification panel diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/NavigatorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/NavigatorPanel.java new file mode 100644 index 000000000..b6c51cce1 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/NavigatorPanel.java @@ -0,0 +1,99 @@ +package cuchaz.enigma.gui.elements; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.LineBorder; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * A panel with buttons to navigate to the next and previous items in its entry collection. + */ +public class NavigatorPanel extends JPanel { + private final Gui gui; + private final JLabel statsLabel; + private final List> entries = new ArrayList<>(); + private final Function, Boolean> validityChecker; + + private int currentIndex = 0; + + /** + * Creates a new navigator panel. + * @param gui the parent gui + * @param validityChecker a function that check if an entry should still be accessible from this panel. returns {@code true} if yes, or {@code false} if it should be removed + */ + public NavigatorPanel(Gui gui, Function, Boolean> validityChecker) { + super(); + this.gui = gui; + this.validityChecker = validityChecker; + this.statsLabel = new JLabel("0/0"); + + JButton up = new JButton("up"); + up.addActionListener(event -> { + if (!this.entries.isEmpty()) { + this.currentIndex --; + if (this.currentIndex < 0) { + this.currentIndex = this.entries.size() - 1; + } + + this.tryNavigate(); + } + }); + + JButton down = new JButton("down"); + down.addActionListener(event -> { + if (!this.entries.isEmpty()) { + this.currentIndex ++; + if (this.currentIndex >= this.entries.size()) { + this.currentIndex = 0; + } + + this.tryNavigate(); + } + }); + + this.add(up); + this.add(down); + this.add(this.statsLabel); + this.setBorder(new LineBorder(Color.BLACK)); + } + + private void tryNavigate() { + this.checkForRemoval(this.entries.get(this.currentIndex)); + this.updateStatsLabel(); + this.gui.getController().navigateTo(this.entries.get(this.currentIndex)); + } + + /** + * Adds the entry if it's valid for this navigator. + * @param entry the entry to add + */ + public void tryAddEntry(@Nullable Entry entry) { + if (entry != null && !this.entries.contains(entry) && this.validityChecker.apply(entry)) { + this.entries.add(entry); + this.statsLabel.setText((this.currentIndex + 1) + "/" + this.entries.size()); + } + } + + /** + * Checks if the entry should be removed, and if so handles removal and updates. + * @param target the entry to check + */ + public void checkForRemoval(Entry target) { + if (!this.validityChecker.apply(target)) { + this.entries.remove(target); + this.updateStatsLabel(); + } + } + + private void updateStatsLabel() { + this.statsLabel.setText((this.currentIndex + 1) + "/" + this.entries.size()); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java index f78926f81..187f6c8cc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java @@ -14,6 +14,7 @@ import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.config.keybind.KeyBinds; import cuchaz.enigma.gui.elements.EditorPopupMenu; +import cuchaz.enigma.gui.elements.NavigatorPanel; import cuchaz.enigma.gui.events.EditorActionListener; import cuchaz.enigma.gui.events.ThemeChangeListener; import cuchaz.enigma.gui.highlight.BoxHighlightPainter; @@ -35,6 +36,7 @@ import java.awt.Color; import java.awt.Component; +import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -83,6 +85,7 @@ public class EditorPanel { private final JTextArea errorTextArea = new JTextArea(); private final JScrollPane errorScrollPane = new JScrollPane(this.errorTextArea); private final JButton retryButton = new JButton(I18n.translate("prompt.retry")); + private final NavigatorPanel navigatorPanel; private DisplayMode mode = DisplayMode.INACTIVE; @@ -111,6 +114,7 @@ public EditorPanel(Gui gui) { this.controller = gui.getController(); this.editor.setEditable(false); + this.editor.setLayout(new FlowLayout(FlowLayout.RIGHT)); this.editor.setSelectionColor(new Color(31, 46, 90)); this.editor.setCaret(new BrowserCaret()); this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); @@ -129,6 +133,11 @@ public EditorPanel(Gui gui) { this.popupMenu = new EditorPopupMenu(this, gui); this.editor.setComponentPopupMenu(this.popupMenu.getUi()); + // navigator panel + // todo needs to stick when scrolling + this.navigatorPanel = new NavigatorPanel(gui, entry -> this.gui.getController().getProject().isObfuscated(entry) && this.gui.getController().getProject().isRenamable(entry)); + this.editor.add(this.navigatorPanel); + this.decompilingLabel.setFont(ScaleUtil.getFont(this.decompilingLabel.getFont().getFontName(), Font.BOLD, 26)); this.decompilingProgressBar.setIndeterminate(true); this.errorTextArea.setEditable(false); @@ -224,6 +233,10 @@ public void keyReleased(KeyEvent event) { this.ui.putClientProperty(EditorPanel.class, this); } + public void onRename(Entry target) { + this.navigatorPanel.checkForRemoval(target); + } + @Nullable public static EditorPanel byUi(Component ui) { if (ui instanceof JComponent component) { @@ -452,11 +465,17 @@ public void setSource(DecompiledClassSource source) { this.source = source; this.editor.getHighlighter().removeAllHighlights(); this.editor.setText(source.toString()); + + this.setHighlightedTokens(source.getHighlightedTokens()); if (this.source != null) { this.editor.setCaretPosition(newCaretPos); + + for (Entry entry : this.source.getIndex().declarations()) { + // todo confirm that the entry comes from this class - overridden methods, even deobf ones, are being added here + this.navigatorPanel.tryAddEntry(entry); + } } - this.setHighlightedTokens(source.getHighlightedTokens()); this.setCursorReference(this.getReference(this.getToken(this.editor.getCaretPosition()))); } finally { this.settingSource = false; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java index 57d0c61ab..d9bbd62f5 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java @@ -12,6 +12,7 @@ import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; +import javax.swing.JPanel; import javax.swing.JToolTip; import javax.swing.Popup; import javax.swing.PopupFactory; @@ -226,4 +227,15 @@ public void windowClosing(WindowEvent e) { } }; } + + /** + * A hack method to set up the Swing glass pane, because of course it doesn't actually display anything + * unless you do this. Thanks Swing! + */ + public static void setUpGlassPane(JPanel glass) { + glass.setOpaque(false); + glass.setVisible(true); + glass.setLayout(null); + glass.revalidate(); + } } diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java index 29b6b0a68..b6ef1aba6 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java @@ -9,7 +9,7 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -24,7 +24,7 @@ public class SourceIndex { public SourceIndex() { this.tokenToReference = new TreeMap<>(); this.referenceToTokens = HashMultimap.create(); - this.declarationToToken = new HashMap<>(); + this.declarationToToken = new LinkedHashMap<>(); } public SourceIndex(String source) {