summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java654
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java31
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java12
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java86
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java125
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java14
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java16
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java158
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java34
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java34
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java50
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java20
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java137
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java20
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java3
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java104
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java67
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java4
19 files changed, 962 insertions, 609 deletions
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 cddedad7..cac0ea89 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -14,9 +14,12 @@ package cuchaz.enigma.gui;
14import java.awt.BorderLayout; 14import java.awt.BorderLayout;
15import java.awt.Container; 15import java.awt.Container;
16import java.awt.Point; 16import java.awt.Point;
17import java.awt.event.*; 17import java.awt.event.ActionEvent;
18import java.nio.file.Path; 18import java.nio.file.Path;
19import java.util.*; 19import java.util.Collection;
20import java.util.Collections;
21import java.util.List;
22import java.util.Set;
20import java.util.concurrent.CompletableFuture; 23import java.util.concurrent.CompletableFuture;
21import java.util.function.Consumer; 24import java.util.function.Consumer;
22import java.util.function.Function; 25import java.util.function.Function;
@@ -24,30 +27,24 @@ import java.util.function.Function;
24import javax.annotation.Nullable; 27import javax.annotation.Nullable;
25import javax.swing.*; 28import javax.swing.*;
26import javax.swing.tree.DefaultMutableTreeNode; 29import javax.swing.tree.DefaultMutableTreeNode;
27import javax.swing.tree.DefaultTreeModel;
28import javax.swing.tree.TreeNode; 30import javax.swing.tree.TreeNode;
29import javax.swing.tree.TreePath; 31import javax.swing.tree.TreePath;
30 32
31import com.google.common.collect.HashBiMap;
32import com.google.common.collect.Lists; 33import com.google.common.collect.Lists;
33 34
34import cuchaz.enigma.Enigma; 35import cuchaz.enigma.Enigma;
35import cuchaz.enigma.EnigmaProfile; 36import cuchaz.enigma.EnigmaProfile;
36import cuchaz.enigma.analysis.*; 37import cuchaz.enigma.analysis.EntryReference;
37import cuchaz.enigma.classhandle.ClassHandle;
38import cuchaz.enigma.gui.config.Themes; 38import cuchaz.enigma.gui.config.Themes;
39import cuchaz.enigma.gui.config.UiConfig; 39import cuchaz.enigma.gui.config.UiConfig;
40import cuchaz.enigma.gui.dialog.CrashDialog;
41import cuchaz.enigma.gui.dialog.JavadocDialog; 40import cuchaz.enigma.gui.dialog.JavadocDialog;
42import cuchaz.enigma.gui.dialog.SearchDialog; 41import cuchaz.enigma.gui.dialog.SearchDialog;
43import cuchaz.enigma.gui.elements.*; 42import cuchaz.enigma.gui.elements.*;
44import cuchaz.enigma.gui.events.EditorActionListener;
45import cuchaz.enigma.gui.panels.*; 43import cuchaz.enigma.gui.panels.*;
46import cuchaz.enigma.gui.renderer.CallsTreeCellRenderer;
47import cuchaz.enigma.gui.renderer.ImplementationsTreeCellRenderer;
48import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer;
49import cuchaz.enigma.gui.renderer.MessageListCellRenderer; 44import cuchaz.enigma.gui.renderer.MessageListCellRenderer;
50import cuchaz.enigma.gui.util.*; 45import cuchaz.enigma.gui.util.GuiUtil;
46import cuchaz.enigma.gui.util.LanguageUtil;
47import cuchaz.enigma.gui.util.ScaleUtil;
51import cuchaz.enigma.network.Message; 48import cuchaz.enigma.network.Message;
52import cuchaz.enigma.network.packet.MessageC2SPacket; 49import cuchaz.enigma.network.packet.MessageC2SPacket;
53import cuchaz.enigma.source.Token; 50import cuchaz.enigma.source.Token;
@@ -55,293 +52,109 @@ import cuchaz.enigma.translation.mapping.EntryChange;
55import cuchaz.enigma.translation.mapping.EntryRemapper; 52import cuchaz.enigma.translation.mapping.EntryRemapper;
56import cuchaz.enigma.translation.representation.entry.ClassEntry; 53import cuchaz.enigma.translation.representation.entry.ClassEntry;
57import cuchaz.enigma.translation.representation.entry.Entry; 54import cuchaz.enigma.translation.representation.entry.Entry;
58import cuchaz.enigma.translation.representation.entry.FieldEntry;
59import cuchaz.enigma.translation.representation.entry.MethodEntry;
60import cuchaz.enigma.utils.I18n; 55import cuchaz.enigma.utils.I18n;
61import cuchaz.enigma.utils.validation.ParameterizedMessage; 56import cuchaz.enigma.utils.validation.ParameterizedMessage;
62import cuchaz.enigma.utils.validation.ValidationContext; 57import cuchaz.enigma.utils.validation.ValidationContext;
63 58
64public class Gui implements LanguageChangeListener { 59public class Gui {
65 60
66 private final ObfPanel obfPanel; 61 private final MainWindow mainWindow = new MainWindow(Enigma.NAME);
67 private final DeobfPanel deobfPanel; 62 private final GuiController controller;
68
69 private final MenuBar menuBar;
70 63
71 // state
72 public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory;
73 private ConnectionState connectionState; 64 private ConnectionState connectionState;
74 private boolean isJarOpen; 65 private boolean isJarOpen;
75 private final Set<EditableType> editableTypes; 66 private final Set<EditableType> editableTypes;
76 private boolean singleClassTree; 67 private boolean singleClassTree;
77 68
78 public JFileChooser jarFileChooser; 69 private final MenuBar menuBar;
79 public JFileChooser tinyMappingsFileChooser; 70 private final ObfPanel obfPanel;
80 public JFileChooser enigmaMappingsFileChooser; 71 private final DeobfPanel deobfPanel;
81 public JFileChooser exportSourceFileChooser; 72 private final IdentifierPanel infoPanel;
82 public JFileChooser exportJarFileChooser; 73 private final StructurePanel structurePanel;
74 private final InheritanceTree inheritanceTree;
75 private final ImplementationsTree implementationsTree;
76 private final CallsTree callsTree;
77
78 private final EditorTabbedPane editorTabbedPane;
79
80 private final JPanel classesPanel = new JPanel(new BorderLayout());
81 private final JSplitPane splitClasses;
82 private final JTabbedPane tabs = new JTabbedPane();
83 private final CollapsibleTabbedPane logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM);
84 private final JSplitPane logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs);
85 private final JPanel centerPanel = new JPanel(new BorderLayout());
86 private final JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
87 private final JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
88
89 private final DefaultListModel<String> userModel = new DefaultListModel<>();
90 private final DefaultListModel<Message> messageModel = new DefaultListModel<>();
91 private final JList<String> users = new JList<>(userModel);
92 private final JList<Message> messages = new JList<>(messageModel);
93 private final JPanel messagePanel = new JPanel(new BorderLayout());
94 private final JScrollPane messageScrollPane = new JScrollPane(this.messages);
95 private final JTextField chatBox = new JTextField();
96
97 private final JLabel connectionStatusLabel = new JLabel();
98
99 public final JFileChooser jarFileChooser = new JFileChooser();
100 public final JFileChooser tinyMappingsFileChooser = new JFileChooser();
101 public final JFileChooser enigmaMappingsFileChooser = new JFileChooser();
102 public final JFileChooser exportSourceFileChooser = new JFileChooser();
103 public final JFileChooser exportJarFileChooser = new JFileChooser();
83 public SearchDialog searchDialog; 104 public SearchDialog searchDialog;
84 private GuiController controller;
85 private JFrame frame;
86 private JPanel classesPanel;
87 private JSplitPane splitClasses;
88 private IdentifierPanel infoPanel;
89 private StructurePanel structurePanel;
90 private JTree inheritanceTree;
91 private JTree implementationsTree;
92 private JTree callsTree;
93 private JList<Token> tokens;
94 private JTabbedPane tabs;
95
96 private JSplitPane splitCenter;
97 private JSplitPane splitRight;
98 private JSplitPane logSplit;
99 private CollapsibleTabbedPane logTabs;
100 private JList<String> users;
101 private DefaultListModel<String> userModel;
102 private JScrollPane messageScrollPane;
103 private JList<Message> messages;
104 private DefaultListModel<Message> messageModel;
105 private JTextField chatBox;
106
107 private JPanel statusBar;
108 private JLabel connectionStatusLabel;
109 private JLabel statusLabel;
110
111 private final EditorTabPopupMenu editorTabPopupMenu;
112 private final DeobfPanelPopupMenu deobfPanelPopupMenu;
113 private final JTabbedPane openFiles;
114 private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create();
115 105
116 public Gui(EnigmaProfile profile, Set<EditableType> editableTypes) { 106 public Gui(EnigmaProfile profile, Set<EditableType> editableTypes) {
117 this.editableTypes = editableTypes; 107 this.editableTypes = editableTypes;
108 this.controller = new GuiController(this, profile);
109 this.structurePanel = new StructurePanel(this);
110 this.deobfPanel = new DeobfPanel(this);
111 this.infoPanel = new IdentifierPanel(this);
112 this.obfPanel = new ObfPanel(this);
113 this.menuBar = new MenuBar(this);
114 this.inheritanceTree = new InheritanceTree(this);
115 this.implementationsTree = new ImplementationsTree(this);
116 this.callsTree = new CallsTree(this);
117 this.editorTabbedPane = new EditorTabbedPane(this);
118 this.splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
118 119
119 // init frame 120 this.setupUi();
120 this.frame = new JFrame(Enigma.NAME);
121 final Container pane = this.frame.getContentPane();
122 pane.setLayout(new BorderLayout());
123
124 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
125 // install a global exception handler to the event thread
126 CrashDialog.init(this.frame);
127 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
128 t.printStackTrace(System.err);
129 if (!ExceptionIgnorer.shouldIgnore(t)) {
130 CrashDialog.show(t);
131 }
132 });
133 }
134 121
122 LanguageUtil.addListener(this::retranslateUi);
135 Themes.addListener((lookAndFeel, boxHighlightPainters) -> SwingUtilities.updateComponentTreeUI(this.getFrame())); 123 Themes.addListener((lookAndFeel, boxHighlightPainters) -> SwingUtilities.updateComponentTreeUI(this.getFrame()));
136 124
137 this.controller = new GuiController(this, profile); 125 this.mainWindow.setVisible(true);
126 }
138 127
139 // init file choosers 128 private void setupUi() {
140 this.jarFileChooser = new JFileChooser();
141 this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open"));
142 this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 129 this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
143
144 this.tinyMappingsFileChooser = new JFileChooser();
145 this.tinyMappingsFileChooser.setDialogTitle("Open tiny Mappings");
146 this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 130 this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
147 131
148 this.enigmaMappingsFileChooser = new JFileChooser();
149 this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 132 this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
150 this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false); 133 this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false);
151 134
152 this.exportSourceFileChooser = new JFileChooser();
153 this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 135 this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
154 this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); 136 this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false);
155 137
156 this.exportJarFileChooser = new JFileChooser();
157 this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar"));
158 this.exportJarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 138 this.exportJarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
159 139
160 this.obfPanel = new ObfPanel(this); 140 this.splitClasses.setResizeWeight(0.3);
161 this.deobfPanel = new DeobfPanel(this);
162
163 // set up classes panel (don't add the splitter yet)
164 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
165 splitClasses.setResizeWeight(0.3);
166 this.classesPanel = new JPanel();
167 this.classesPanel.setLayout(new BorderLayout());
168 this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); 141 this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0));
169 142
170 // init info panel
171 infoPanel = new IdentifierPanel(this);
172
173 // init structure panel
174 this.structurePanel = new StructurePanel(this);
175
176 // init inheritance panel
177 inheritanceTree = new JTree();
178 inheritanceTree.setModel(null);
179 inheritanceTree.setCellRenderer(new InheritanceTreeCellRenderer(this));
180 inheritanceTree.setSelectionModel(new SingleTreeSelectionModel());
181 inheritanceTree.setShowsRootHandles(true);
182 inheritanceTree.addMouseListener(new MouseAdapter() {
183 @Override
184 public void mouseClicked(MouseEvent event) {
185 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
186 // get the selected node
187 TreePath path = inheritanceTree.getSelectionPath();
188 if (path == null) {
189 return;
190 }
191
192 Object node = path.getLastPathComponent();
193 if (node instanceof ClassInheritanceTreeNode) {
194 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
195 controller.navigateTo(new ClassEntry(classNode.getObfClassName()));
196 } else if (node instanceof MethodInheritanceTreeNode) {
197 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
198 if (methodNode.isImplemented()) {
199 controller.navigateTo(methodNode.getMethodEntry());
200 }
201 }
202 }
203 }
204 });
205
206 JPanel inheritancePanel = new JPanel();
207 inheritancePanel.setLayout(new BorderLayout());
208 inheritancePanel.add(new JScrollPane(inheritanceTree));
209
210 // init implementations panel
211 implementationsTree = new JTree();
212 implementationsTree.setModel(null);
213 implementationsTree.setCellRenderer(new ImplementationsTreeCellRenderer(this));
214 implementationsTree.setSelectionModel(new SingleTreeSelectionModel());
215 implementationsTree.setShowsRootHandles(true);
216 implementationsTree.addMouseListener(new MouseAdapter() {
217 @Override
218 public void mouseClicked(MouseEvent event) {
219 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
220 // get the selected node
221 TreePath path = implementationsTree.getSelectionPath();
222 if (path == null) {
223 return;
224 }
225
226 Object node = path.getLastPathComponent();
227 if (node instanceof ClassImplementationsTreeNode classNode) {
228 controller.navigateTo(classNode.getClassEntry());
229 } else if (node instanceof MethodImplementationsTreeNode methodNode) {
230 controller.navigateTo(methodNode.getMethodEntry());
231 }
232 }
233 }
234 });
235 JPanel implementationsPanel = new JPanel();
236 implementationsPanel.setLayout(new BorderLayout());
237 implementationsPanel.add(new JScrollPane(implementationsTree));
238
239 // init call panel
240 callsTree = new JTree();
241 callsTree.setModel(null);
242 callsTree.setCellRenderer(new CallsTreeCellRenderer(this));
243 callsTree.setSelectionModel(new SingleTreeSelectionModel());
244 callsTree.setShowsRootHandles(true);
245 callsTree.addMouseListener(new MouseAdapter() {
246 @SuppressWarnings("unchecked")
247 @Override
248 public void mouseClicked(MouseEvent event) {
249 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
250 // get the selected node
251 TreePath path = callsTree.getSelectionPath();
252 if (path == null) {
253 return;
254 }
255
256 Object node = path.getLastPathComponent();
257 if (node instanceof ReferenceTreeNode referenceNode) {
258 if (referenceNode.getReference() != null) {
259 controller.navigateTo(referenceNode.getReference());
260 } else {
261 controller.navigateTo(referenceNode.getEntry());
262 }
263 }
264 }
265 }
266 });
267 tokens = new JList<>();
268 tokens.setCellRenderer(new TokenListCellRenderer(controller));
269 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
270 tokens.setLayoutOrientation(JList.VERTICAL);
271 tokens.addMouseListener(new MouseAdapter() {
272 @Override
273 public void mouseClicked(MouseEvent event) {
274 if (event.getClickCount() == 2) {
275 Token selected = tokens.getSelectedValue();
276 if (selected != null) {
277 openClass(controller.getTokenHandle().getRef()).navigateToToken(selected);
278 }
279 }
280 }
281 });
282 tokens.setPreferredSize(ScaleUtil.getDimension(0, 200));
283 tokens.setMinimumSize(ScaleUtil.getDimension(0, 200));
284 JSplitPane callPanel = new JSplitPane(
285 JSplitPane.VERTICAL_SPLIT,
286 true,
287 new JScrollPane(callsTree),
288 new JScrollPane(tokens)
289 );
290 callPanel.setResizeWeight(1); // let the top side take all the slack
291 callPanel.resetToPreferredSizes();
292
293 editorTabPopupMenu = new EditorTabPopupMenu(this);
294 openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
295 openFiles.addMouseListener(new MouseAdapter() {
296 @Override
297 public void mousePressed(MouseEvent e) {
298 if (SwingUtilities.isRightMouseButton(e)) {
299 int i = openFiles.getUI().tabForCoordinate(openFiles, e.getX(), e.getY());
300 if (i != -1) {
301 editorTabPopupMenu.show(openFiles, e.getX(), e.getY(), EditorPanel.byUi(openFiles.getComponentAt(i)));
302 }
303 }
304
305 showStructure(getActiveEditor());
306 }
307 });
308
309 deobfPanelPopupMenu = new DeobfPanelPopupMenu(this);
310 deobfPanel.deobfClasses.addMouseListener(new MouseAdapter() {
311 @Override
312 public void mousePressed(MouseEvent e) {
313 if (SwingUtilities.isRightMouseButton(e)) {
314 deobfPanel.deobfClasses.setSelectionRow(deobfPanel.deobfClasses.getClosestRowForLocation(e.getX(), e.getY()));
315 int i = deobfPanel.deobfClasses.getRowForPath(deobfPanel.deobfClasses.getSelectionPath());
316 if (i != -1) {
317 deobfPanelPopupMenu.show(deobfPanel.deobfClasses, e.getX(), e.getY());
318 }
319 }
320 }
321 });
322
323 // layout controls 143 // layout controls
324 JPanel centerPanel = new JPanel(); 144 Container workArea = this.mainWindow.workArea();
325 centerPanel.setLayout(new BorderLayout()); 145 workArea.setLayout(new BorderLayout());
146
326 centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); 147 centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH);
327 centerPanel.add(openFiles, BorderLayout.CENTER); 148 centerPanel.add(this.editorTabbedPane.getUi(), BorderLayout.CENTER);
328 tabs = new JTabbedPane(); 149
329 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); 150 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0));
330 tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel); 151 tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel.getPanel());
331 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); 152 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritanceTree.getPanel());
332 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); 153 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsTree.getPanel());
333 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); 154 tabs.addTab(I18n.translate("info_panel.tree.calls"), callsTree.getPanel());
334 logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); 155
335 userModel = new DefaultListModel<>();
336 users = new JList<>(userModel);
337 messageModel = new DefaultListModel<>();
338 messages = new JList<>(messageModel);
339 messages.setCellRenderer(new MessageListCellRenderer()); 156 messages.setCellRenderer(new MessageListCellRenderer());
340 JPanel messagePanel = new JPanel(new BorderLayout());
341 messageScrollPane = new JScrollPane(this.messages);
342 messagePanel.add(messageScrollPane, BorderLayout.CENTER);
343 JPanel chatPanel = new JPanel(new BorderLayout()); 157 JPanel chatPanel = new JPanel(new BorderLayout());
344 chatBox = new JTextField();
345 AbstractAction sendListener = new AbstractAction("Send") { 158 AbstractAction sendListener = new AbstractAction("Send") {
346 @Override 159 @Override
347 public void actionPerformed(ActionEvent e) { 160 public void actionPerformed(ActionEvent e) {
@@ -352,18 +165,17 @@ public class Gui implements LanguageChangeListener {
352 JButton chatSendButton = new JButton(sendListener); 165 JButton chatSendButton = new JButton(sendListener);
353 chatPanel.add(chatBox, BorderLayout.CENTER); 166 chatPanel.add(chatBox, BorderLayout.CENTER);
354 chatPanel.add(chatSendButton, BorderLayout.EAST); 167 chatPanel.add(chatSendButton, BorderLayout.EAST);
168 messagePanel.add(messageScrollPane, BorderLayout.CENTER);
355 messagePanel.add(chatPanel, BorderLayout.SOUTH); 169 messagePanel.add(chatPanel, BorderLayout.SOUTH);
356 logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); 170 logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users));
357 logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); 171 logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel);
358 logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs);
359 logSplit.setResizeWeight(0.5); 172 logSplit.setResizeWeight(0.5);
360 logSplit.resetToPreferredSizes(); 173 logSplit.resetToPreferredSizes();
361 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
362 splitRight.setResizeWeight(1); // let the left side take all the slack 174 splitRight.setResizeWeight(1); // let the left side take all the slack
363 splitRight.resetToPreferredSizes(); 175 splitRight.resetToPreferredSizes();
364 splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
365 splitCenter.setResizeWeight(0); // let the right side take all the slack 176 splitCenter.setResizeWeight(0); // let the right side take all the slack
366 pane.add(splitCenter, BorderLayout.CENTER); 177
178 workArea.add(splitCenter, BorderLayout.CENTER);
367 179
368 // restore state 180 // restore state
369 int[] layout = UiConfig.getLayout(); 181 int[] layout = UiConfig.getLayout();
@@ -374,56 +186,41 @@ public class Gui implements LanguageChangeListener {
374 this.logSplit.setDividerLocation(layout[3]); 186 this.logSplit.setDividerLocation(layout[3]);
375 } 187 }
376 188
377 // init menus 189 this.mainWindow.statusBar().addPermanentComponent(this.connectionStatusLabel);
378 this.menuBar = new MenuBar(this);
379 this.frame.setJMenuBar(this.menuBar.getUi());
380
381 // init status bar
382 statusBar = new JPanel(new BorderLayout());
383 statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
384 connectionStatusLabel = new JLabel();
385 statusLabel = new JLabel();
386 statusBar.add(statusLabel, BorderLayout.CENTER);
387 statusBar.add(connectionStatusLabel, BorderLayout.EAST);
388 pane.add(statusBar, BorderLayout.SOUTH);
389 190
390 // init state 191 // init state
391 setConnectionState(ConnectionState.NOT_CONNECTED); 192 setConnectionState(ConnectionState.NOT_CONNECTED);
392 onCloseJar(); 193 onCloseJar();
393 194
394 this.frame.addWindowListener(new WindowAdapter() { 195 JFrame frame = this.mainWindow.frame();
395 @Override 196 frame.addWindowListener(GuiUtil.onWindowClose(e -> this.close()));
396 public void windowClosing(WindowEvent event) {
397 close();
398 }
399 });
400 197
401 // show the frame 198 frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576)));
402 pane.doLayout(); 199 frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
403 this.frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576))); 200 frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
404 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
405 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
406 201
407 Point windowPos = UiConfig.getWindowPos("Main Window", null); 202 Point windowPos = UiConfig.getWindowPos("Main Window", null);
408 if (windowPos != null) { 203 if (windowPos != null) {
409 this.frame.setLocation(windowPos); 204 frame.setLocation(windowPos);
410 } else { 205 } else {
411 this.frame.setLocationRelativeTo(null); 206 frame.setLocationRelativeTo(null);
412 } 207 }
413 208
414 this.frame.setVisible(true); 209 this.retranslateUi();
210 }
415 211
416 LanguageUtil.addListener(this); 212 public MainWindow getMainWindow() {
213 return this.mainWindow;
417 } 214 }
418 215
419 public JFrame getFrame() { 216 public JFrame getFrame() {
420 return this.frame; 217 return this.mainWindow.frame();
421 } 218 }
422 219
423 public GuiController getController() { 220 public GuiController getController() {
424 return this.controller; 221 return this.controller;
425 } 222 }
426 223
427 public void setSingleClassTree(boolean singleClassTree) { 224 public void setSingleClassTree(boolean singleClassTree) {
428 this.singleClassTree = singleClassTree; 225 this.singleClassTree = singleClassTree;
429 this.classesPanel.removeAll(); 226 this.classesPanel.removeAll();
@@ -431,11 +228,11 @@ public class Gui implements LanguageChangeListener {
431 getController().refreshClasses(); 228 getController().refreshClasses();
432 retranslateUi(); 229 retranslateUi();
433 } 230 }
434 231
435 public boolean isSingleClassTree() { 232 public boolean isSingleClassTree() {
436 return singleClassTree; 233 return singleClassTree;
437 } 234 }
438 235
439 public void onStartOpenJar() { 236 public void onStartOpenJar() {
440 this.classesPanel.removeAll(); 237 this.classesPanel.removeAll();
441 redraw(); 238 redraw();
@@ -443,10 +240,10 @@ public class Gui implements LanguageChangeListener {
443 240
444 public void onFinishOpenJar(String jarName) { 241 public void onFinishOpenJar(String jarName) {
445 // update gui 242 // update gui
446 this.frame.setTitle(Enigma.NAME + " - " + jarName); 243 this.mainWindow.setTitle(Enigma.NAME + " - " + jarName);
447 this.classesPanel.removeAll(); 244 this.classesPanel.removeAll();
448 this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses); 245 this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses);
449 closeAllEditorTabs(); 246 this.editorTabbedPane.closeAllEditorTabs();
450 247
451 // update menu 248 // update menu
452 isJarOpen = true; 249 isJarOpen = true;
@@ -457,10 +254,10 @@ public class Gui implements LanguageChangeListener {
457 254
458 public void onCloseJar() { 255 public void onCloseJar() {
459 // update gui 256 // update gui
460 this.frame.setTitle(Enigma.NAME); 257 this.mainWindow.setTitle(Enigma.NAME);
461 setObfClasses(null); 258 setObfClasses(null);
462 setDeobfClasses(null); 259 setDeobfClasses(null);
463 closeAllEditorTabs(); 260 this.editorTabbedPane.closeAllEditorTabs();
464 this.classesPanel.removeAll(); 261 this.classesPanel.removeAll();
465 262
466 // update menu 263 // update menu
@@ -472,53 +269,25 @@ public class Gui implements LanguageChangeListener {
472 } 269 }
473 270
474 public EditorPanel openClass(ClassEntry entry) { 271 public EditorPanel openClass(ClassEntry entry) {
475 EditorPanel editorPanel = editors.computeIfAbsent(entry, e -> { 272 return this.editorTabbedPane.openClass(entry);
476 ClassHandle ch = controller.getClassHandleProvider().openClass(entry); 273 }
477 if (ch == null) return null;
478 EditorPanel ed = new EditorPanel(this);
479 ed.setup();
480 ed.setClassHandle(ch);
481 openFiles.addTab(ed.getFileName(), ed.getUi());
482
483 ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> closeEditor(ed));
484 openFiles.setTabComponentAt(openFiles.indexOfComponent(ed.getUi()), titlePane.getUi());
485 titlePane.setTabbedPane(openFiles);
486
487 ed.addListener(new EditorActionListener() {
488 @Override
489 public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) {
490 updateSelectedReference(editor, ref);
491 }
492
493 @Override
494 public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) {
495 editors.remove(old);
496 editors.put(ch.getRef(), editor);
497 }
498 274
499 @Override 275 @Nullable
500 public void onTitleChanged(EditorPanel editor, String title) { 276 public EditorPanel getActiveEditor() {
501 titlePane.setText(editor.getFileName()); 277 return this.editorTabbedPane.getActiveEditor();
502 } 278 }
503 });
504
505 ed.getEditor().addKeyListener(new KeyAdapter() {
506 @Override
507 public void keyPressed(KeyEvent e) {
508 if (e.getKeyCode() == KeyEvent.VK_4 && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) {
509 closeEditor(ed);
510 }
511 }
512 });
513 279
514 return ed; 280 public void closeEditor(EditorPanel editor) {
515 }); 281 this.editorTabbedPane.closeEditor(editor);
516 if (editorPanel != null) { 282 }
517 openFiles.setSelectedComponent(editors.get(entry).getUi());
518 showStructure(editorPanel);
519 }
520 283
521 return editorPanel; 284 /**
285 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
286 *
287 * @param reference the reference
288 */
289 public void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
290 this.editorTabbedPane.openClass(reference.getLocationClassEntry().getOutermostClass()).showReference(reference);
522 } 291 }
523 292
524 public void setObfClasses(Collection<ClassEntry> obfClasses) { 293 public void setObfClasses(Collection<ClassEntry> obfClasses) {
@@ -534,202 +303,72 @@ public class Gui implements LanguageChangeListener {
534 updateUiState(); 303 updateUiState();
535 } 304 }
536 305
537 public void closeEditor(EditorPanel ed) { 306 public void showTokens(EditorPanel editor, List<Token> tokens) {
538 openFiles.remove(ed.getUi()); 307 if (tokens.size() > 1) {
539 editors.inverse().remove(ed);
540 showStructure(getActiveEditor());
541 ed.destroy();
542 }
543
544 public void closeAllEditorTabs() {
545 for (Iterator<EditorPanel> iter = editors.values().iterator(); iter.hasNext(); ) {
546 EditorPanel e = iter.next();
547 openFiles.remove(e.getUi());
548 e.destroy();
549 iter.remove();
550 }
551 }
552
553 public void closeTabsLeftOf(EditorPanel ed) {
554 int index = openFiles.indexOfComponent(ed.getUi());
555 for (int i = index - 1; i >= 0; i--) {
556 closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i)));
557 }
558 }
559
560 public void closeTabsRightOf(EditorPanel ed) {
561 int index = openFiles.indexOfComponent(ed.getUi());
562 for (int i = openFiles.getTabCount() - 1; i > index; i--) {
563 closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i)));
564 }
565 }
566
567 public void closeTabsExcept(EditorPanel ed) {
568 int index = openFiles.indexOfComponent(ed.getUi());
569 for (int i = openFiles.getTabCount() - 1; i >= 0; i--) {
570 if (i == index) continue;
571 closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i)));
572 }
573 }
574
575 public void showTokens(EditorPanel editor, Collection<Token> tokens) {
576 Vector<Token> sortedTokens = new Vector<>(tokens);
577 Collections.sort(sortedTokens);
578 if (sortedTokens.size() > 1) {
579 // sort the tokens and update the tokens panel
580 this.controller.setTokenHandle(editor.getClassHandle().copy()); 308 this.controller.setTokenHandle(editor.getClassHandle().copy());
581 this.tokens.setListData(sortedTokens); 309 this.callsTree.showTokens(tokens);
582 this.tokens.setSelectedIndex(0);
583 } else { 310 } else {
584 this.tokens.setListData(new Vector<>()); 311 this.callsTree.clearTokens();
585 } 312 }
586 313
587 // show the first token 314 // show the first token
588 editor.navigateToToken(sortedTokens.get(0)); 315 editor.navigateToToken(tokens.get(0));
589 } 316 }
590 317
591 private void updateSelectedReference(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) { 318 public void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) {
592 if (editor != getActiveEditor()) return;
593
594 showCursorReference(ref);
595 }
596
597 private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) {
598 infoPanel.setReference(reference == null ? null : reference.entry); 319 infoPanel.setReference(reference == null ? null : reference.entry);
599 } 320 }
600 321
601 @Nullable 322 @Nullable
602 public EditorPanel getActiveEditor() {
603 return EditorPanel.byUi(openFiles.getSelectedComponent());
604 }
605
606 @Nullable
607 public EntryReference<Entry<?>, Entry<?>> getCursorReference() { 323 public EntryReference<Entry<?>, Entry<?>> getCursorReference() {
608 EditorPanel activeEditor = getActiveEditor(); 324 EditorPanel activeEditor = this.editorTabbedPane.getActiveEditor();
609 return activeEditor == null ? null : activeEditor.getCursorReference(); 325 return activeEditor == null ? null : activeEditor.getCursorReference();
610 } 326 }
611 327
612 public void startDocChange(EditorPanel editor) { 328 public void startDocChange(EditorPanel editor) {
613 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 329 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
614 if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return; 330 if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return;
615 JavadocDialog.show(frame, getController(), cursorReference); 331 JavadocDialog.show(mainWindow.frame(), getController(), cursorReference);
616 } 332 }
617 333
618 public void startRename(EditorPanel editor, String text) { 334 public void startRename(EditorPanel editor, String text) {
619 if (editor != getActiveEditor()) return; 335 if (editor != this.editorTabbedPane.getActiveEditor()) return;
620 336
621 infoPanel.startRenaming(text); 337 infoPanel.startRenaming(text);
622 } 338 }
623 339
624 public void startRename(EditorPanel editor) { 340 public void startRename(EditorPanel editor) {
625 if (editor != getActiveEditor()) return; 341 if (editor != this.editorTabbedPane.getActiveEditor()) return;
626 342
627 infoPanel.startRenaming(); 343 infoPanel.startRenaming();
628 } 344 }
629 345
630 public void showStructure(EditorPanel editor) { 346 public void showStructure(EditorPanel editor) {
631 JTree structureTree = this.structurePanel.getStructureTree(); 347 this.structurePanel.showStructure(editor);
632 structureTree.setModel(null);
633
634 if (editor == null) {
635 this.structurePanel.getSortingPanel().setVisible(false);
636 return;
637 }
638
639 ClassEntry classEntry = editor.getClassHandle().getRef();
640 if (classEntry == null) return;
641
642 this.structurePanel.getSortingPanel().setVisible(true);
643
644 // get the class structure
645 StructureTreeNode node = this.controller.getClassStructure(classEntry, this.structurePanel.getOptions());
646
647 // show the tree at the root
648 TreePath path = getPathToRoot(node);
649 structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
650 structureTree.expandPath(path);
651 structureTree.setSelectionRow(structureTree.getRowForPath(path));
652
653 redraw();
654 } 348 }
655 349
656 public void showInheritance(EditorPanel editor) { 350 public void showInheritance(EditorPanel editor) {
657 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 351 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
658 if (cursorReference == null) return; 352 if (cursorReference == null) return;
659 353
660 inheritanceTree.setModel(null); 354 this.inheritanceTree.display(cursorReference.entry);
661
662 if (cursorReference.entry instanceof ClassEntry) {
663 // get the class inheritance
664 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry);
665
666 // show the tree at the root
667 TreePath path = getPathToRoot(classNode);
668 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
669 inheritanceTree.expandPath(path);
670 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
671 } else if (cursorReference.entry instanceof MethodEntry) {
672 // get the method inheritance
673 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry);
674
675 // show the tree at the root
676 TreePath path = getPathToRoot(classNode);
677 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
678 inheritanceTree.expandPath(path);
679 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
680 }
681
682 tabs.setSelectedIndex(1); 355 tabs.setSelectedIndex(1);
683
684 redraw();
685 } 356 }
686 357
687 public void showImplementations(EditorPanel editor) { 358 public void showImplementations(EditorPanel editor) {
688 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 359 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
689 if (cursorReference == null) return; 360 if (cursorReference == null) return;
690 361
691 implementationsTree.setModel(null); 362 this.implementationsTree.display(cursorReference.entry);
692
693 DefaultMutableTreeNode node = null;
694
695 // get the class implementations
696 if (cursorReference.entry instanceof ClassEntry)
697 node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry);
698 else // get the method implementations
699 if (cursorReference.entry instanceof MethodEntry)
700 node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry);
701
702 if (node != null) {
703 // show the tree at the root
704 TreePath path = getPathToRoot(node);
705 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
706 implementationsTree.expandPath(path);
707 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
708 }
709
710 tabs.setSelectedIndex(2); 363 tabs.setSelectedIndex(2);
711
712 redraw();
713 } 364 }
714 365
715 public void showCalls(EditorPanel editor, boolean recurse) { 366 public void showCalls(EditorPanel editor, boolean recurse) {
716 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 367 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
717 if (cursorReference == null) return; 368 if (cursorReference == null) return;
718 369
719 if (cursorReference.entry instanceof ClassEntry) { 370 this.callsTree.showCalls(cursorReference.entry, recurse);
720 ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry);
721 callsTree.setModel(new DefaultTreeModel(node));
722 } else if (cursorReference.entry instanceof FieldEntry) {
723 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry);
724 callsTree.setModel(new DefaultTreeModel(node));
725 } else if (cursorReference.entry instanceof MethodEntry) {
726 MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse);
727 callsTree.setModel(new DefaultTreeModel(node));
728 }
729
730 tabs.setSelectedIndex(3); 371 tabs.setSelectedIndex(3);
731
732 redraw();
733 } 372 }
734 373
735 public void toggleMapping(EditorPanel editor) { 374 public void toggleMapping(EditorPanel editor) {
@@ -757,13 +396,13 @@ public class Gui implements LanguageChangeListener {
757 } 396 }
758 397
759 public void showDiscardDiag(Function<Integer, Void> callback, String... options) { 398 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
760 int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, 399 int response = JOptionPane.showOptionDialog(this.mainWindow.frame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION,
761 JOptionPane.QUESTION_MESSAGE, null, options, options[2]); 400 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
762 callback.apply(response); 401 callback.apply(response);
763 } 402 }
764 403
765 public CompletableFuture<Void> saveMapping() { 404 public CompletableFuture<Void> saveMapping() {
766 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) 405 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.frame()) == JFileChooser.APPROVE_OPTION)
767 return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); 406 return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
768 return CompletableFuture.completedFuture(null); 407 return CompletableFuture.completedFuture(null);
769 } 408 }
@@ -788,8 +427,8 @@ public class Gui implements LanguageChangeListener {
788 } 427 }
789 428
790 private void exit() { 429 private void exit() {
791 UiConfig.setWindowPos("Main Window", this.frame.getLocationOnScreen()); 430 UiConfig.setWindowPos("Main Window", this.mainWindow.frame().getLocationOnScreen());
792 UiConfig.setWindowSize("Main Window", this.frame.getSize()); 431 UiConfig.setWindowSize("Main Window", this.mainWindow.frame().getSize());
793 UiConfig.setLayout( 432 UiConfig.setLayout(
794 this.splitClasses.getDividerLocation(), 433 this.splitClasses.getDividerLocation(),
795 this.splitCenter.getDividerLocation(), 434 this.splitCenter.getDividerLocation(),
@@ -800,13 +439,15 @@ public class Gui implements LanguageChangeListener {
800 if (searchDialog != null) { 439 if (searchDialog != null) {
801 searchDialog.dispose(); 440 searchDialog.dispose();
802 } 441 }
803 this.frame.dispose(); 442 this.mainWindow.frame().dispose();
804 System.exit(0); 443 System.exit(0);
805 } 444 }
806 445
807 public void redraw() { 446 public void redraw() {
808 this.frame.validate(); 447 JFrame frame = this.mainWindow.frame();
809 this.frame.repaint(); 448
449 frame.validate();
450 frame.repaint();
810 } 451 }
811 452
812 public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { 453 public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) {
@@ -896,19 +537,16 @@ public class Gui implements LanguageChangeListener {
896 return searchDialog; 537 return searchDialog;
897 } 538 }
898 539
899
900 public MenuBar getMenuBar() {
901 return menuBar;
902 }
903
904 public void addMessage(Message message) { 540 public void addMessage(Message message) {
905 JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); 541 JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar();
906 boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); 542 boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent();
907 messageModel.addElement(message); 543 messageModel.addElement(message);
544
908 if (isAtBottom) { 545 if (isAtBottom) {
909 SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); 546 SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent()));
910 } 547 }
911 statusLabel.setText(message.translate()); 548
549 this.mainWindow.statusBar().showMessage(message.translate(), 5000);
912 } 550 }
913 551
914 public void setUserList(List<String> users) { 552 public void setUserList(List<String> users) {
@@ -946,7 +584,6 @@ public class Gui implements LanguageChangeListener {
946 splitRight.setDividerLocation(splitRight.getDividerLocation()); 584 splitRight.setDividerLocation(splitRight.getDividerLocation());
947 } 585 }
948 586
949 @Override
950 public void retranslateUi() { 587 public void retranslateUi() {
951 this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open")); 588 this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open"));
952 this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar")); 589 this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar"));
@@ -963,16 +600,17 @@ public class Gui implements LanguageChangeListener {
963 this.menuBar.retranslateUi(); 600 this.menuBar.retranslateUi();
964 this.obfPanel.retranslateUi(); 601 this.obfPanel.retranslateUi();
965 this.deobfPanel.retranslateUi(); 602 this.deobfPanel.retranslateUi();
966 this.deobfPanelPopupMenu.retranslateUi();
967 this.infoPanel.retranslateUi(); 603 this.infoPanel.retranslateUi();
968 this.structurePanel.retranslateUi(); 604 this.structurePanel.retranslateUi();
969 this.editorTabPopupMenu.retranslateUi(); 605 this.editorTabbedPane.retranslateUi();
970 this.editors.values().forEach(EditorPanel::retranslateUi); 606 this.inheritanceTree.retranslateUi();
607 this.implementationsTree.retranslateUi();
608 this.structurePanel.retranslateUi();
609 this.callsTree.retranslateUi();
971 } 610 }
972 611
973 public void setConnectionState(ConnectionState state) { 612 public void setConnectionState(ConnectionState state) {
974 connectionState = state; 613 connectionState = state;
975 statusLabel.setText(I18n.translate("status.ready"));
976 updateUiState(); 614 updateUiState();
977 } 615 }
978 616
@@ -984,10 +622,6 @@ public class Gui implements LanguageChangeListener {
984 return this.connectionState; 622 return this.connectionState;
985 } 623 }
986 624
987 public IdentifierPanel getInfoPanel() {
988 return infoPanel;
989 }
990
991 public boolean validateImmediateAction(Consumer<ValidationContext> op) { 625 public boolean validateImmediateAction(Consumer<ValidationContext> op) {
992 ValidationContext vc = new ValidationContext(); 626 ValidationContext vc = new ValidationContext();
993 op.accept(vc); 627 op.accept(vc);
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 4a15b418..67ac6625 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -85,6 +85,8 @@ public class GuiController implements ClientPacketHandler {
85 private EnigmaClient client; 85 private EnigmaClient client;
86 private EnigmaServer server; 86 private EnigmaServer server;
87 87
88 private History<EntryReference<Entry<?>, Entry<?>>> referenceHistory;
89
88 public GuiController(Gui gui, EnigmaProfile profile) { 90 public GuiController(Gui gui, EnigmaProfile profile) {
89 this.gui = gui; 91 this.gui = gui;
90 this.enigma = Enigma.builder() 92 this.enigma = Enigma.builder()
@@ -299,53 +301,46 @@ public class GuiController implements ClientPacketHandler {
299 if (reference == null) { 301 if (reference == null) {
300 throw new IllegalArgumentException("Reference cannot be null!"); 302 throw new IllegalArgumentException("Reference cannot be null!");
301 } 303 }
302 if (this.gui.referenceHistory == null) { 304 if (this.referenceHistory == null) {
303 this.gui.referenceHistory = new History<>(reference); 305 this.referenceHistory = new History<>(reference);
304 } else { 306 } else {
305 if (!reference.equals(this.gui.referenceHistory.getCurrent())) { 307 if (!reference.equals(this.referenceHistory.getCurrent())) {
306 this.gui.referenceHistory.push(reference); 308 this.referenceHistory.push(reference);
307 } 309 }
308 } 310 }
309 setReference(reference);
310 }
311 311
312 /** 312 this.gui.showReference(reference);
313 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
314 *
315 * @param reference the reference
316 */
317 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
318 gui.openClass(reference.getLocationClassEntry().getOutermostClass()).showReference(reference);
319 } 313 }
320 314
321 public Collection<Token> getTokensForReference(DecompiledClassSource source, EntryReference<Entry<?>, Entry<?>> reference) { 315 public List<Token> getTokensForReference(DecompiledClassSource source, EntryReference<Entry<?>, Entry<?>> reference) {
322 EntryRemapper mapper = this.project.getMapper(); 316 EntryRemapper mapper = this.project.getMapper();
323 317
324 SourceIndex index = source.getIndex(); 318 SourceIndex index = source.getIndex();
325 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) 319 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
326 .stream() 320 .stream()
327 .flatMap(r -> index.getReferenceTokens(r).stream()) 321 .flatMap(r -> index.getReferenceTokens(r).stream())
322 .sorted()
328 .toList(); 323 .toList();
329 } 324 }
330 325
331 public void openPreviousReference() { 326 public void openPreviousReference() {
332 if (hasPreviousReference()) { 327 if (hasPreviousReference()) {
333 setReference(gui.referenceHistory.goBack()); 328 this.gui.showReference(referenceHistory.goBack());
334 } 329 }
335 } 330 }
336 331
337 public boolean hasPreviousReference() { 332 public boolean hasPreviousReference() {
338 return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); 333 return referenceHistory != null && referenceHistory.canGoBack();
339 } 334 }
340 335
341 public void openNextReference() { 336 public void openNextReference() {
342 if (hasNextReference()) { 337 if (hasNextReference()) {
343 setReference(gui.referenceHistory.goForward()); 338 this.gui.showReference(referenceHistory.goForward());
344 } 339 }
345 } 340 }
346 341
347 public boolean hasNextReference() { 342 public boolean hasNextReference() {
348 return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); 343 return referenceHistory != null && referenceHistory.canGoForward();
349 } 344 }
350 345
351 public void navigateTo(Entry<?> entry) { 346 public void navigateTo(Entry<?> entry) {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
index f6a03f54..1172a393 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
@@ -25,6 +25,7 @@ import joptsimple.*;
25import cuchaz.enigma.EnigmaProfile; 25import cuchaz.enigma.EnigmaProfile;
26import cuchaz.enigma.gui.config.Themes; 26import cuchaz.enigma.gui.config.Themes;
27import cuchaz.enigma.gui.config.UiConfig; 27import cuchaz.enigma.gui.config.UiConfig;
28import cuchaz.enigma.gui.dialog.CrashDialog;
28import cuchaz.enigma.translation.mapping.serde.MappingFormat; 29import cuchaz.enigma.translation.mapping.serde.MappingFormat;
29import cuchaz.enigma.utils.I18n; 30import cuchaz.enigma.utils.I18n;
30 31
@@ -114,6 +115,17 @@ public class Main {
114 gui.setSingleClassTree(true); 115 gui.setSingleClassTree(true);
115 } 116 }
116 117
118 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
119 // install a global exception handler to the event thread
120 CrashDialog.init(gui.getFrame());
121 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
122 t.printStackTrace(System.err);
123 if (!ExceptionIgnorer.shouldIgnore(t)) {
124 CrashDialog.show(t);
125 }
126 });
127 }
128
117 if (options.has(jar)) { 129 if (options.has(jar)) {
118 Path jarPath = options.valueOf(jar); 130 Path jarPath = options.valueOf(jar);
119 controller.openJar(jarPath) 131 controller.openJar(jarPath)
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java
new file mode 100644
index 00000000..39aa212f
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java
@@ -0,0 +1,86 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.BorderLayout;
4import java.awt.event.MouseEvent;
5
6import javax.annotation.Nullable;
7import javax.swing.JPanel;
8import javax.swing.JScrollPane;
9import javax.swing.JTree;
10import javax.swing.tree.*;
11
12import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
13import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
14import cuchaz.enigma.gui.Gui;
15import cuchaz.enigma.gui.util.GuiUtil;
16import cuchaz.enigma.gui.util.SingleTreeSelectionModel;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.Entry;
19
20public abstract class AbstractInheritanceTree {
21 private final JPanel panel = new JPanel(new BorderLayout());
22
23 private final JTree tree = new JTree();
24
25 protected final Gui gui;
26
27 public AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) {
28 this.gui = gui;
29
30 this.tree.setModel(null);
31 this.tree.setCellRenderer(cellRenderer);
32 this.tree.setSelectionModel(new SingleTreeSelectionModel());
33 this.tree.setShowsRootHandles(true);
34 this.tree.addMouseListener(GuiUtil.onMouseClick(this::onClick));
35
36 this.panel.add(new JScrollPane(this.tree));
37 }
38
39 private void onClick(MouseEvent event) {
40 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
41 // get the selected node
42 TreePath path = tree.getSelectionPath();
43 if (path == null) {
44 return;
45 }
46
47 Object node = path.getLastPathComponent();
48 if (node instanceof ClassInheritanceTreeNode classNode) {
49 gui.getController().navigateTo(new ClassEntry(classNode.getObfClassName()));
50 } else if (node instanceof MethodInheritanceTreeNode methodNode) {
51 if (methodNode.isImplemented()) {
52 gui.getController().navigateTo(methodNode.getMethodEntry());
53 }
54 }
55 }
56 }
57
58 public void display(Entry<?> entry) {
59 this.tree.setModel(null);
60
61 DefaultMutableTreeNode node = this.getNodeFor(entry);
62
63 if (node != null) {
64 // show the tree at the root
65 TreePath path = GuiUtil.getPathToRoot(node);
66 this.tree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
67 this.tree.expandPath(path);
68 this.tree.setSelectionRow(this.tree.getRowForPath(path));
69 }
70
71 this.panel.show();
72 }
73
74 public void retranslateUi() {
75
76 }
77
78 @Nullable
79 protected abstract DefaultMutableTreeNode getNodeFor(Entry<?> entry);
80
81 protected abstract String getPanelName();
82
83 public JPanel getPanel() {
84 return this.panel;
85 }
86}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java
new file mode 100644
index 00000000..c92534f0
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java
@@ -0,0 +1,125 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.BorderLayout;
4import java.awt.event.MouseEvent;
5import java.util.Collection;
6import java.util.Vector;
7
8import javax.swing.*;
9import javax.swing.tree.DefaultTreeModel;
10import javax.swing.tree.TreeNode;
11import javax.swing.tree.TreePath;
12
13import cuchaz.enigma.analysis.ReferenceTreeNode;
14import cuchaz.enigma.gui.Gui;
15import cuchaz.enigma.gui.TokenListCellRenderer;
16import cuchaz.enigma.gui.renderer.CallsTreeCellRenderer;
17import cuchaz.enigma.gui.util.GuiUtil;
18import cuchaz.enigma.gui.util.ScaleUtil;
19import cuchaz.enigma.gui.util.SingleTreeSelectionModel;
20import cuchaz.enigma.source.Token;
21import cuchaz.enigma.translation.representation.entry.ClassEntry;
22import cuchaz.enigma.translation.representation.entry.Entry;
23import cuchaz.enigma.translation.representation.entry.FieldEntry;
24import cuchaz.enigma.translation.representation.entry.MethodEntry;
25
26public class CallsTree {
27 private final JPanel panel = new JPanel(new BorderLayout());
28
29 private final JTree callsTree = new JTree();
30 private final JList<Token> tokens = new JList<>();
31
32 private final Gui gui;
33
34 public CallsTree(Gui gui) {
35 this.gui = gui;
36
37 this.callsTree.setModel(null);
38 this.callsTree.setCellRenderer(new CallsTreeCellRenderer(gui));
39 this.callsTree.setSelectionModel(new SingleTreeSelectionModel());
40 this.callsTree.setShowsRootHandles(true);
41 this.callsTree.addMouseListener(GuiUtil.onMouseClick(this::onTreeClicked));
42
43 this.tokens.setCellRenderer(new TokenListCellRenderer(gui.getController()));
44 this.tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
45 this.tokens.setLayoutOrientation(JList.VERTICAL);
46 this.tokens.addMouseListener(GuiUtil.onMouseClick(this::onTokenClicked));
47 this.tokens.setPreferredSize(ScaleUtil.getDimension(0, 200));
48 this.tokens.setMinimumSize(ScaleUtil.getDimension(0, 200));
49
50 JSplitPane contentPane = new JSplitPane(
51 JSplitPane.VERTICAL_SPLIT,
52 true,
53 new JScrollPane(this.callsTree),
54 new JScrollPane(this.tokens)
55 );
56
57 contentPane.setResizeWeight(1); // let the top side take all the slack
58 contentPane.resetToPreferredSizes();
59 this.panel.add(contentPane, BorderLayout.CENTER);
60 }
61
62 public void showCalls(Entry<?> entry, boolean recurse) {
63 TreeNode node = null;
64
65 if (entry instanceof ClassEntry classEntry) {
66 node = this.gui.getController().getClassReferences(classEntry);
67 } else if (entry instanceof FieldEntry fieldEntry) {
68 node = this.gui.getController().getFieldReferences(fieldEntry);
69 } else if (entry instanceof MethodEntry methodEntry) {
70 node = this.gui.getController().getMethodReferences(methodEntry, recurse);
71 }
72
73 this.callsTree.setModel(new DefaultTreeModel(node));
74
75 this.panel.show();
76 }
77
78 public void showTokens(Collection<Token> tokens) {
79 this.tokens.setListData(new Vector<>(tokens));
80 this.tokens.setSelectedIndex(0);
81 }
82
83 public void clearTokens() {
84 this.tokens.setListData(new Vector<>());
85 }
86
87 @SuppressWarnings("unchecked")
88 private void onTreeClicked(MouseEvent event) {
89 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
90 // get the selected node
91 TreePath path = this.callsTree.getSelectionPath();
92
93 if (path == null) {
94 return;
95 }
96
97 Object node = path.getLastPathComponent();
98
99 if (node instanceof ReferenceTreeNode referenceNode) {
100 if (referenceNode.getReference() != null) {
101 this.gui.getController().navigateTo(referenceNode.getReference());
102 } else {
103 this.gui.getController().navigateTo(referenceNode.getEntry());
104 }
105 }
106 }
107 }
108
109 private void onTokenClicked(MouseEvent event) {
110 if (event.getClickCount() == 2) {
111 Token selected = this.tokens.getSelectedValue();
112 if (selected != null) {
113 this.gui.openClass(this.gui.getController().getTokenHandle().getRef()).navigateToToken(selected);
114 }
115 }
116 }
117
118 public void retranslateUi() {
119
120 }
121
122 public JPanel getPanel() {
123 return this.panel;
124 }
125}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java
index 9481412e..0b44881f 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java
@@ -1,13 +1,13 @@
1package cuchaz.enigma.gui.elements; 1package cuchaz.enigma.gui.elements;
2 2
3import javax.swing.JMenuItem;
4import javax.swing.JPopupMenu;
5import javax.swing.tree.TreePath;
6
3import cuchaz.enigma.gui.ClassSelector; 7import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui; 8import cuchaz.enigma.gui.panels.DeobfPanel;
5import cuchaz.enigma.utils.I18n; 9import cuchaz.enigma.utils.I18n;
6 10
7import javax.swing.*;
8import javax.swing.tree.TreePath;
9import java.awt.*;
10
11public class DeobfPanelPopupMenu { 11public class DeobfPanelPopupMenu {
12 12
13 private final JPopupMenu ui; 13 private final JPopupMenu ui;
@@ -16,7 +16,7 @@ public class DeobfPanelPopupMenu {
16 private final JMenuItem expandAll = new JMenuItem(); 16 private final JMenuItem expandAll = new JMenuItem();
17 private final JMenuItem collapseAll = new JMenuItem(); 17 private final JMenuItem collapseAll = new JMenuItem();
18 18
19 public DeobfPanelPopupMenu(Gui gui) { 19 public DeobfPanelPopupMenu(DeobfPanel panel) {
20 this.ui = new JPopupMenu(); 20 this.ui = new JPopupMenu();
21 21
22 this.ui.add(this.renamePackage); 22 this.ui.add(this.renamePackage);
@@ -25,7 +25,7 @@ public class DeobfPanelPopupMenu {
25 this.ui.add(this.expandAll); 25 this.ui.add(this.expandAll);
26 this.ui.add(this.collapseAll); 26 this.ui.add(this.collapseAll);
27 27
28 ClassSelector deobfClasses = gui.getDeobfPanel().deobfClasses; 28 ClassSelector deobfClasses = panel.deobfClasses;
29 29
30 this.renamePackage.addActionListener(a -> { 30 this.renamePackage.addActionListener(a -> {
31 TreePath path; 31 TreePath path;
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java
index c2982cd0..0b4926eb 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java
@@ -7,7 +7,6 @@ import javax.swing.JMenuItem;
7import javax.swing.JPopupMenu; 7import javax.swing.JPopupMenu;
8import javax.swing.KeyStroke; 8import javax.swing.KeyStroke;
9 9
10import cuchaz.enigma.gui.Gui;
11import cuchaz.enigma.gui.panels.EditorPanel; 10import cuchaz.enigma.gui.panels.EditorPanel;
12import cuchaz.enigma.utils.I18n; 11import cuchaz.enigma.utils.I18n;
13 12
@@ -20,33 +19,30 @@ public class EditorTabPopupMenu {
20 private final JMenuItem closeLeft; 19 private final JMenuItem closeLeft;
21 private final JMenuItem closeRight; 20 private final JMenuItem closeRight;
22 21
23 private final Gui gui;
24 private EditorPanel editor; 22 private EditorPanel editor;
25 23
26 public EditorTabPopupMenu(Gui gui) { 24 public EditorTabPopupMenu(EditorTabbedPane pane) {
27 this.gui = gui;
28
29 this.ui = new JPopupMenu(); 25 this.ui = new JPopupMenu();
30 26
31 this.close = new JMenuItem(); 27 this.close = new JMenuItem();
32 this.close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_DOWN_MASK)); 28 this.close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_DOWN_MASK));
33 this.close.addActionListener(a -> gui.closeEditor(editor)); 29 this.close.addActionListener(a -> pane.closeEditor(editor));
34 this.ui.add(this.close); 30 this.ui.add(this.close);
35 31
36 this.closeAll = new JMenuItem(); 32 this.closeAll = new JMenuItem();
37 this.closeAll.addActionListener(a -> gui.closeAllEditorTabs()); 33 this.closeAll.addActionListener(a -> pane.closeAllEditorTabs());
38 this.ui.add(this.closeAll); 34 this.ui.add(this.closeAll);
39 35
40 this.closeOthers = new JMenuItem(); 36 this.closeOthers = new JMenuItem();
41 this.closeOthers.addActionListener(a -> gui.closeTabsExcept(editor)); 37 this.closeOthers.addActionListener(a -> pane.closeTabsExcept(editor));
42 this.ui.add(this.closeOthers); 38 this.ui.add(this.closeOthers);
43 39
44 this.closeLeft = new JMenuItem(); 40 this.closeLeft = new JMenuItem();
45 this.closeLeft.addActionListener(a -> gui.closeTabsLeftOf(editor)); 41 this.closeLeft.addActionListener(a -> pane.closeTabsLeftOf(editor));
46 this.ui.add(this.closeLeft); 42 this.ui.add(this.closeLeft);
47 43
48 this.closeRight = new JMenuItem(); 44 this.closeRight = new JMenuItem();
49 this.closeRight.addActionListener(a -> gui.closeTabsRightOf(editor)); 45 this.closeRight.addActionListener(a -> pane.closeTabsRightOf(editor));
50 this.ui.add(this.closeRight); 46 this.ui.add(this.closeRight);
51 47
52 this.retranslateUi(); 48 this.retranslateUi();
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java
new file mode 100644
index 00000000..ff0bba3f
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java
@@ -0,0 +1,158 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.Component;
4import java.awt.event.KeyAdapter;
5import java.awt.event.KeyEvent;
6import java.awt.event.MouseEvent;
7import java.util.Iterator;
8
9import javax.annotation.Nullable;
10import javax.swing.JTabbedPane;
11import javax.swing.SwingUtilities;
12
13import com.google.common.collect.HashBiMap;
14
15import cuchaz.enigma.analysis.EntryReference;
16import cuchaz.enigma.classhandle.ClassHandle;
17import cuchaz.enigma.gui.Gui;
18import cuchaz.enigma.gui.events.EditorActionListener;
19import cuchaz.enigma.gui.panels.ClosableTabTitlePane;
20import cuchaz.enigma.gui.panels.EditorPanel;
21import cuchaz.enigma.gui.util.GuiUtil;
22import cuchaz.enigma.translation.representation.entry.ClassEntry;
23import cuchaz.enigma.translation.representation.entry.Entry;
24
25public class EditorTabbedPane {
26 private final JTabbedPane openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
27 private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create();
28
29 private final EditorTabPopupMenu editorTabPopupMenu;
30 private final Gui gui;
31
32 public EditorTabbedPane(Gui gui) {
33 this.gui = gui;
34 this.editorTabPopupMenu = new EditorTabPopupMenu(this);
35
36 this.openFiles.addMouseListener(GuiUtil.onMousePress(this::onTabPressed));
37 }
38
39 public EditorPanel openClass(ClassEntry entry) {
40 EditorPanel editorPanel = this.editors.computeIfAbsent(entry, e -> {
41 ClassHandle ch = this.gui.getController().getClassHandleProvider().openClass(entry);
42 if (ch == null) return null;
43 EditorPanel ed = new EditorPanel(this.gui);
44 ed.setup();
45 ed.setClassHandle(ch);
46 this.openFiles.addTab(ed.getFileName(), ed.getUi());
47
48 ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> this.closeEditor(ed));
49 this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(ed.getUi()), titlePane.getUi());
50 titlePane.setTabbedPane(this.openFiles);
51
52 ed.addListener(new EditorActionListener() {
53 @Override
54 public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) {
55 if (editor == getActiveEditor()) {
56 gui.showCursorReference(ref);
57 }
58 }
59
60 @Override
61 public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) {
62 EditorTabbedPane.this.editors.remove(old);
63 EditorTabbedPane.this.editors.put(ch.getRef(), editor);
64 }
65
66 @Override
67 public void onTitleChanged(EditorPanel editor, String title) {
68 titlePane.setText(editor.getFileName());
69 }
70 });
71
72 ed.getEditor().addKeyListener(new KeyAdapter() {
73 @Override
74 public void keyPressed(KeyEvent e) {
75 if (e.getKeyCode() == KeyEvent.VK_4 && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) {
76 closeEditor(ed);
77 }
78 }
79 });
80
81 return ed;
82 });
83
84 if (editorPanel != null) {
85 this.openFiles.setSelectedComponent(this.editors.get(entry).getUi());
86 this.gui.showStructure(editorPanel);
87 }
88
89 return editorPanel;
90 }
91
92 public void closeEditor(EditorPanel ed) {
93 this.openFiles.remove(ed.getUi());
94 this.editors.inverse().remove(ed);
95 this.gui.showStructure(this.getActiveEditor());
96 ed.destroy();
97 }
98
99 public void closeAllEditorTabs() {
100 for (Iterator<EditorPanel> iter = this.editors.values().iterator(); iter.hasNext(); ) {
101 EditorPanel e = iter.next();
102 this.openFiles.remove(e.getUi());
103 e.destroy();
104 iter.remove();
105 }
106 }
107
108 public void closeTabsLeftOf(EditorPanel ed) {
109 int index = this.openFiles.indexOfComponent(ed.getUi());
110
111 for (int i = index - 1; i >= 0; i--) {
112 closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i)));
113 }
114 }
115
116 public void closeTabsRightOf(EditorPanel ed) {
117 int index = this.openFiles.indexOfComponent(ed.getUi());
118
119 for (int i = this.openFiles.getTabCount() - 1; i > index; i--) {
120 closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i)));
121 }
122 }
123
124 public void closeTabsExcept(EditorPanel ed) {
125 int index = this.openFiles.indexOfComponent(ed.getUi());
126
127 for (int i = this.openFiles.getTabCount() - 1; i >= 0; i--) {
128 if (i == index) continue;
129 closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i)));
130 }
131 }
132
133 @Nullable
134 public EditorPanel getActiveEditor() {
135 return EditorPanel.byUi(this.openFiles.getSelectedComponent());
136 }
137
138 private void onTabPressed(MouseEvent e) {
139 if (SwingUtilities.isRightMouseButton(e)) {
140 int i = this.openFiles.getUI().tabForCoordinate(this.openFiles, e.getX(), e.getY());
141
142 if (i != -1) {
143 this.editorTabPopupMenu.show(this.openFiles, e.getX(), e.getY(), EditorPanel.byUi(this.openFiles.getComponentAt(i)));
144 }
145 }
146
147 this.gui.showStructure(this.getActiveEditor());
148 }
149
150 public void retranslateUi() {
151 this.editorTabPopupMenu.retranslateUi();
152 this.editors.values().forEach(EditorPanel::retranslateUi);
153 }
154
155 public Component getUi() {
156 return this.openFiles;
157 }
158}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java
new file mode 100644
index 00000000..962cf273
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java
@@ -0,0 +1,34 @@
1package cuchaz.enigma.gui.elements;
2
3import javax.annotation.Nullable;
4import javax.swing.tree.DefaultMutableTreeNode;
5
6import cuchaz.enigma.gui.Gui;
7import cuchaz.enigma.gui.renderer.ImplementationsTreeCellRenderer;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.translation.representation.entry.MethodEntry;
11import cuchaz.enigma.utils.I18n;
12
13public class ImplementationsTree extends AbstractInheritanceTree {
14 public ImplementationsTree(Gui gui) {
15 super(gui, new ImplementationsTreeCellRenderer(gui));
16 }
17
18 @Nullable
19 @Override
20 protected DefaultMutableTreeNode getNodeFor(Entry<?> entry) {
21 if (entry instanceof ClassEntry classEntry) {
22 return this.gui.getController().getClassImplementations(classEntry);
23 } else if (entry instanceof MethodEntry methodEntry) {
24 return this.gui.getController().getMethodImplementations(methodEntry);
25 }
26
27 return null;
28 }
29
30 @Override
31 protected String getPanelName() {
32 return I18n.translate("info_panel.tree.implementations");
33 }
34}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java
new file mode 100644
index 00000000..aeb173ba
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java
@@ -0,0 +1,34 @@
1package cuchaz.enigma.gui.elements;
2
3import javax.annotation.Nullable;
4import javax.swing.tree.DefaultMutableTreeNode;
5
6import cuchaz.enigma.gui.Gui;
7import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.translation.representation.entry.MethodEntry;
11import cuchaz.enigma.utils.I18n;
12
13public class InheritanceTree extends AbstractInheritanceTree {
14 public InheritanceTree(Gui gui) {
15 super(gui, new InheritanceTreeCellRenderer(gui));
16 }
17
18 @Nullable
19 @Override
20 protected DefaultMutableTreeNode getNodeFor(Entry<?> entry) {
21 if (entry instanceof ClassEntry classEntry) {
22 return this.gui.getController().getClassInheritance(classEntry);
23 } else if (entry instanceof MethodEntry methodEntry) {
24 return this.gui.getController().getMethodInheritance(methodEntry);
25 }
26
27 return null;
28 }
29
30 @Override
31 protected String getPanelName() {
32 return I18n.translate("info_panel.tree.inheritance");
33 }
34}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java
new file mode 100644
index 00000000..3330948a
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java
@@ -0,0 +1,50 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.BorderLayout;
4import java.awt.Container;
5
6import javax.swing.JFrame;
7import javax.swing.JMenuBar;
8import javax.swing.JPanel;
9
10public class MainWindow {
11 private final JFrame frame;
12 private final JPanel workArea = new JPanel();
13
14 private final JMenuBar menuBar = new JMenuBar();
15 private final StatusBar statusBar = new StatusBar();
16
17 public MainWindow(String title) {
18 this.frame = new JFrame(title);
19 this.frame.setJMenuBar(this.menuBar);
20
21 Container contentPane = this.frame.getContentPane();
22 contentPane.setLayout(new BorderLayout());
23 contentPane.add(this.workArea, BorderLayout.CENTER);
24 contentPane.add(this.statusBar.getUi(), BorderLayout.SOUTH);
25 }
26
27 public void setVisible(boolean visible) {
28 this.frame.setVisible(visible);
29 }
30
31 public JMenuBar menuBar() {
32 return this.menuBar;
33 }
34
35 public StatusBar statusBar() {
36 return this.statusBar;
37 }
38
39 public Container workArea() {
40 return this.workArea;
41 }
42
43 public JFrame frame() {
44 return this.frame;
45 }
46
47 public void setTitle(String title) {
48 this.frame.setTitle(title);
49 }
50}
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 61f97803..eeb52ccf 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
@@ -30,8 +30,6 @@ import cuchaz.enigma.utils.Pair;
30 30
31public class MenuBar { 31public class MenuBar {
32 32
33 private final JMenuBar ui = new JMenuBar();
34
35 private final JMenu fileMenu = new JMenu(); 33 private final JMenu fileMenu = new JMenu();
36 private final JMenuItem jarOpenItem = new JMenuItem(); 34 private final JMenuItem jarOpenItem = new JMenuItem();
37 private final JMenuItem jarCloseItem = new JMenuItem(); 35 private final JMenuItem jarCloseItem = new JMenuItem();
@@ -74,6 +72,8 @@ public class MenuBar {
74 public MenuBar(Gui gui) { 72 public MenuBar(Gui gui) {
75 this.gui = gui; 73 this.gui = gui;
76 74
75 JMenuBar ui = gui.getMainWindow().menuBar();
76
77 this.retranslateUi(); 77 this.retranslateUi();
78 78
79 prepareOpenMenu(this.openMenu, gui); 79 prepareOpenMenu(this.openMenu, gui);
@@ -101,29 +101,29 @@ public class MenuBar {
101 this.fileMenu.add(this.statsItem); 101 this.fileMenu.add(this.statsItem);
102 this.fileMenu.addSeparator(); 102 this.fileMenu.addSeparator();
103 this.fileMenu.add(this.exitItem); 103 this.fileMenu.add(this.exitItem);
104 this.ui.add(this.fileMenu); 104 ui.add(this.fileMenu);
105 105
106 this.ui.add(this.decompilerMenu); 106 ui.add(this.decompilerMenu);
107 107
108 this.viewMenu.add(this.themesMenu); 108 this.viewMenu.add(this.themesMenu);
109 this.viewMenu.add(this.languagesMenu); 109 this.viewMenu.add(this.languagesMenu);
110 this.scaleMenu.add(this.customScaleItem); 110 this.scaleMenu.add(this.customScaleItem);
111 this.viewMenu.add(this.scaleMenu); 111 this.viewMenu.add(this.scaleMenu);
112 this.viewMenu.add(this.fontItem); 112 this.viewMenu.add(this.fontItem);
113 this.ui.add(this.viewMenu); 113 ui.add(this.viewMenu);
114 114
115 this.searchMenu.add(this.searchClassItem); 115 this.searchMenu.add(this.searchClassItem);
116 this.searchMenu.add(this.searchMethodItem); 116 this.searchMenu.add(this.searchMethodItem);
117 this.searchMenu.add(this.searchFieldItem); 117 this.searchMenu.add(this.searchFieldItem);
118 this.ui.add(this.searchMenu); 118 ui.add(this.searchMenu);
119 119
120 this.collabMenu.add(this.connectItem); 120 this.collabMenu.add(this.connectItem);
121 this.collabMenu.add(this.startServerItem); 121 this.collabMenu.add(this.startServerItem);
122 this.ui.add(this.collabMenu); 122 ui.add(this.collabMenu);
123 123
124 this.helpMenu.add(this.aboutItem); 124 this.helpMenu.add(this.aboutItem);
125 this.helpMenu.add(this.githubItem); 125 this.helpMenu.add(this.githubItem);
126 this.ui.add(this.helpMenu); 126 ui.add(this.helpMenu);
127 127
128 this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); 128 this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
129 this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_DOWN_MASK)); 129 this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_DOWN_MASK));
@@ -210,10 +210,6 @@ public class MenuBar {
210 this.githubItem.setText(I18n.translate("menu.help.github")); 210 this.githubItem.setText(I18n.translate("menu.help.github"));
211 } 211 }
212 212
213 public JMenuBar getUi() {
214 return this.ui;
215 }
216
217 private void onOpenJarClicked() { 213 private void onOpenJarClicked() {
218 JFileChooser d = this.gui.jarFileChooser; 214 JFileChooser d = this.gui.jarFileChooser;
219 d.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); 215 d.setCurrentDirectory(new File(UiConfig.getLastSelectedDir()));
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java
new file mode 100644
index 00000000..0c667c00
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java
@@ -0,0 +1,137 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.BorderLayout;
4import java.awt.Component;
5import java.awt.ComponentOrientation;
6import java.awt.GridLayout;
7
8import javax.swing.BoxLayout;
9import javax.swing.JLabel;
10import javax.swing.JPanel;
11import javax.swing.Timer;
12
13/**
14 * Implements a generic status bar for use in windows. The API is loosely based
15 * on Qt's QStatusBar.
16 */
17public class StatusBar {
18 private final JPanel ui = new JPanel(new BorderLayout());
19 private final JPanel leftPanel = new JPanel(new GridLayout(1, 1, 0, 0));
20 private final JPanel components = new JPanel();
21 private final JPanel permanentComponents = new JPanel();
22
23 private final JLabel temporaryMessage = new JLabel();
24 private final Timer timer = new Timer(0, e -> this.clearMessage());
25
26 public StatusBar() {
27 this.timer.setRepeats(false);
28
29 this.components.setLayout(new BoxLayout(this.components, BoxLayout.LINE_AXIS));
30 this.permanentComponents.setLayout(new BoxLayout(this.permanentComponents, BoxLayout.LINE_AXIS));
31 this.permanentComponents.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
32
33 this.leftPanel.add(this.components);
34 this.temporaryMessage.setHorizontalTextPosition(JLabel.LEFT);
35 this.ui.add(this.leftPanel, BorderLayout.CENTER);
36 this.ui.add(this.permanentComponents, BorderLayout.EAST);
37 }
38
39 /**
40 * Displays a temporary message in the status bar. The message is displayed
41 * until it is explicitly cleared through {@link #clearMessage()} or a new
42 * message is displayed.
43 *
44 * @param message the message to display
45 */
46 public void showMessage(String message) {
47 this.showMessage(message, 0);
48 }
49
50 /**
51 * Displays a temporary message in the status bar. The message is displayed
52 * until it is explicitly cleared through {@link #clearMessage()}, a new
53 * message is displayed, or the timeout, if any, is reached.
54 *
55 * @param message the message to display
56 * @param timeout the timeout in milliseconds to wait until clearing the
57 * message; if 0, the message is not automatically cleared
58 */
59 public void showMessage(String message, int timeout) {
60 this.timer.stop();
61
62 this.temporaryMessage.setText(message);
63 this.leftPanel.removeAll();
64 this.leftPanel.add(this.temporaryMessage);
65 this.leftPanel.revalidate();
66
67 if (timeout > 0) {
68 this.timer.setInitialDelay(timeout);
69 this.timer.start();
70 }
71 }
72
73 /**
74 * Clears any currently displayed temporary message.
75 */
76 public void clearMessage() {
77 this.timer.stop();
78
79 this.leftPanel.removeAll();
80 this.leftPanel.add(this.components);
81 this.leftPanel.revalidate();
82 this.temporaryMessage.setText("");
83 }
84
85 /**
86 * Returns the currently displayed message, or the empty string otherwise.
87 *
88 * @return the currently displayed message
89 */
90 public String currentMessage() {
91 return this.temporaryMessage.getText();
92 }
93
94 /**
95 * Adds a component to the status bar. These components are positioned on
96 * the left side of the status bar. When a temporary message is displayed,
97 * the component will be hidden until the message is cleared.
98 *
99 * @param comp the component to add
100 */
101 public void addComponent(Component comp) {
102 this.components.add(comp);
103 }
104
105 /**
106 * Removes a component from the status bar.
107 *
108 * @param comp the component to remove
109 */
110 public void removeComponent(Component comp) {
111 this.components.remove(comp);
112 }
113
114 /**
115 * Adds a permanent component to the status bar. These components will not
116 * be hidden by temporary messages and will be displayed on the right side
117 * of the status bar.
118 *
119 * @param comp the component to add
120 */
121 public void addPermanentComponent(Component comp) {
122 this.permanentComponents.add(comp);
123 }
124
125 /**
126 * Removes a permanent component from the status bar.
127 *
128 * @param comp the component to remove
129 */
130 public void removePermanentComponent(Component comp) {
131 this.permanentComponents.remove(comp);
132 }
133
134 public JPanel getUi() {
135 return this.ui;
136 }
137}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
index cd09c1a1..10fc5e1a 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
@@ -1,13 +1,17 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import java.awt.BorderLayout; 3import java.awt.BorderLayout;
4import java.awt.event.MouseEvent;
4 5
5import javax.swing.JLabel; 6import javax.swing.JLabel;
6import javax.swing.JPanel; 7import javax.swing.JPanel;
7import javax.swing.JScrollPane; 8import javax.swing.JScrollPane;
9import javax.swing.SwingUtilities;
8 10
9import cuchaz.enigma.gui.ClassSelector; 11import cuchaz.enigma.gui.ClassSelector;
10import cuchaz.enigma.gui.Gui; 12import cuchaz.enigma.gui.Gui;
13import cuchaz.enigma.gui.elements.DeobfPanelPopupMenu;
14import cuchaz.enigma.gui.util.GuiUtil;
11import cuchaz.enigma.utils.I18n; 15import cuchaz.enigma.utils.I18n;
12 16
13public class DeobfPanel extends JPanel { 17public class DeobfPanel extends JPanel {
@@ -15,6 +19,8 @@ public class DeobfPanel extends JPanel {
15 public final ClassSelector deobfClasses; 19 public final ClassSelector deobfClasses;
16 private final JLabel title = new JLabel(); 20 private final JLabel title = new JLabel();
17 21
22 public final DeobfPanelPopupMenu deobfPanelPopupMenu;
23
18 private final Gui gui; 24 private final Gui gui;
19 25
20 public DeobfPanel(Gui gui) { 26 public DeobfPanel(Gui gui) {
@@ -23,16 +29,30 @@ public class DeobfPanel extends JPanel {
23 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); 29 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true);
24 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); 30 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo);
25 this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); 31 this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree);
32 this.deobfPanelPopupMenu = new DeobfPanelPopupMenu(this);
26 33
27 this.setLayout(new BorderLayout()); 34 this.setLayout(new BorderLayout());
28 this.add(this.title, BorderLayout.NORTH); 35 this.add(this.title, BorderLayout.NORTH);
29 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); 36 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER);
30 37
38 this.deobfClasses.addMouseListener(GuiUtil.onMousePress(this::onPress));
39
31 this.retranslateUi(); 40 this.retranslateUi();
32 } 41 }
33 42
43 private void onPress(MouseEvent e) {
44 if (SwingUtilities.isRightMouseButton(e)) {
45 deobfClasses.setSelectionRow(deobfClasses.getClosestRowForLocation(e.getX(), e.getY()));
46 int i = deobfClasses.getRowForPath(deobfClasses.getSelectionPath());
47 if (i != -1) {
48 deobfPanelPopupMenu.show(deobfClasses, e.getX(), e.getY());
49 }
50 }
51 }
52
34 public void retranslateUi() { 53 public void retranslateUi() {
35 this.title.setText(I18n.translate(gui.isSingleClassTree() ? "info_panel.classes" : "info_panel.classes.deobfuscated")); 54 this.title.setText(I18n.translate(gui.isSingleClassTree() ? "info_panel.classes" : "info_panel.classes.deobfuscated"));
55 this.deobfPanelPopupMenu.retranslateUi();
36 } 56 }
37 57
38} 58}
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 2055309c..f4b190bc 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
@@ -518,7 +518,7 @@ public class EditorPanel {
518 if (this.source == null) return; 518 if (this.source == null) return;
519 if (reference == null) return; 519 if (reference == null) return;
520 520
521 Collection<Token> tokens = this.controller.getTokensForReference(this.source, reference); 521 List<Token> tokens = this.controller.getTokensForReference(this.source, reference);
522 if (tokens.isEmpty()) { 522 if (tokens.isEmpty()) {
523 // DEBUG 523 // DEBUG
524 System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.classHandle.getRef())); 524 System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.classHandle.getRef()));
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
index 4ae0b7be..e71894db 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
@@ -33,7 +33,7 @@ public class IdentifierPanel {
33 33
34 private final Gui gui; 34 private final Gui gui;
35 35
36 private final JPanel ui; 36 private final JPanel ui = new JPanel();
37 37
38 private Entry<?> entry; 38 private Entry<?> entry;
39 private Entry<?> deobfEntry; 39 private Entry<?> deobfEntry;
@@ -45,7 +45,6 @@ public class IdentifierPanel {
45 public IdentifierPanel(Gui gui) { 45 public IdentifierPanel(Gui gui) {
46 this.gui = gui; 46 this.gui = gui;
47 47
48 this.ui = new JPanel();
49 this.ui.setLayout(new GridBagLayout()); 48 this.ui.setLayout(new GridBagLayout());
50 this.ui.setPreferredSize(ScaleUtil.getDimension(0, 120)); 49 this.ui.setPreferredSize(ScaleUtil.getDimension(0, 120));
51 this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); 50 this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier")));
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java
index 1bff9a98..ccded45c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java
@@ -1,7 +1,16 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import cuchaz.enigma.analysis.StructureTreeOptions; 3import java.awt.*;
4import java.awt.event.MouseEvent;
5
6import javax.swing.*;
7import javax.swing.tree.DefaultTreeCellRenderer;
8import javax.swing.tree.DefaultTreeModel;
9import javax.swing.tree.TreeNode;
10import javax.swing.tree.TreePath;
11
4import cuchaz.enigma.analysis.StructureTreeNode; 12import cuchaz.enigma.analysis.StructureTreeNode;
13import cuchaz.enigma.analysis.StructureTreeOptions;
5import cuchaz.enigma.gui.Gui; 14import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer; 15import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer;
7import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; 16import cuchaz.enigma.gui.util.GridBagConstraintsBuilder;
@@ -13,14 +22,11 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry;
13import cuchaz.enigma.translation.representation.entry.ParentedEntry; 22import cuchaz.enigma.translation.representation.entry.ParentedEntry;
14import cuchaz.enigma.utils.I18n; 23import cuchaz.enigma.utils.I18n;
15 24
16import javax.swing.*; 25public class StructurePanel {
17import javax.swing.tree.DefaultTreeCellRenderer; 26 private final Gui gui;
18import javax.swing.tree.TreePath; 27
19import java.awt.*; 28 private final JPanel panel = new JPanel(new BorderLayout());
20import java.awt.event.MouseAdapter;
21import java.awt.event.MouseEvent;
22 29
23public class StructurePanel extends JPanel {
24 private final JPanel optionsPanel; 30 private final JPanel optionsPanel;
25 31
26 private final JLabel obfuscationVisibilityLabel = new JLabel(); 32 private final JLabel obfuscationVisibilityLabel = new JLabel();
@@ -34,6 +40,8 @@ public class StructurePanel extends JPanel {
34 private final JTree structureTree; 40 private final JTree structureTree;
35 41
36 public StructurePanel(Gui gui) { 42 public StructurePanel(Gui gui) {
43 this.gui = gui;
44
37 this.optionsPanel = new JPanel(new GridBagLayout()); 45 this.optionsPanel = new JPanel(new GridBagLayout());
38 this.optionsPanel.setVisible(false); 46 this.optionsPanel.setVisible(false);
39 47
@@ -42,19 +50,19 @@ public class StructurePanel extends JPanel {
42 this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build()); 50 this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build());
43 this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values()); 51 this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values());
44 this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer()); 52 this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer());
45 this.obfuscationVisibility.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); 53 this.obfuscationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor()));
46 this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build()); 54 this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build());
47 55
48 this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build()); 56 this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build());
49 this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values()); 57 this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values());
50 this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer()); 58 this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer());
51 this.documentationVisibility.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); 59 this.documentationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor()));
52 this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build()); 60 this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build());
53 61
54 this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build()); 62 this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build());
55 this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values()); 63 this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values());
56 this.sortingOrder.setRenderer(new StructureOptionListCellRenderer()); 64 this.sortingOrder.setRenderer(new StructureOptionListCellRenderer());
57 this.sortingOrder.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); 65 this.sortingOrder.addActionListener(event -> this.showStructure(gui.getActiveEditor()));
58 this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build()); 66 this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build());
59 67
60 this.structureTree = new JTree(); 68 this.structureTree = new JTree();
@@ -62,39 +70,57 @@ public class StructurePanel extends JPanel {
62 this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); 70 this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui));
63 this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); 71 this.structureTree.setSelectionModel(new SingleTreeSelectionModel());
64 this.structureTree.setShowsRootHandles(true); 72 this.structureTree.setShowsRootHandles(true);
65 this.structureTree.addMouseListener(new MouseAdapter() { 73 this.structureTree.addMouseListener(GuiUtil.onMouseClick(this::onClick));
66 @Override
67 public void mouseClicked(MouseEvent event) {
68 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
69 // get the selected node
70 TreePath path = structureTree.getSelectionPath();
71 if (path == null) {
72 return;
73 }
74
75 Object node = path.getLastPathComponent();
76 if (node instanceof StructureTreeNode) {
77 gui.getController().navigateTo(((StructureTreeNode) node).getEntry());
78 }
79 }
80 }
81 });
82 74
83 this.retranslateUi(); 75 this.retranslateUi();
84 76
85 this.setLayout(new BorderLayout()); 77 this.panel.add(this.optionsPanel, BorderLayout.NORTH);
86 this.add(this.optionsPanel, BorderLayout.NORTH); 78 this.panel.add(new JScrollPane(this.structureTree));
87 this.add(new JScrollPane(this.structureTree));
88 } 79 }
89 80
90 public JPanel getSortingPanel() { 81 public void showStructure(EditorPanel editor) {
91 return this.optionsPanel; 82 structureTree.setModel(null);
83
84 if (editor == null) {
85 this.optionsPanel.setVisible(false);
86 return;
87 }
88
89 ClassEntry classEntry = editor.getClassHandle().getRef();
90 if (classEntry == null) return;
91
92 this.optionsPanel.setVisible(true);
93
94 // get the class structure
95 StructureTreeNode node = this.gui.getController().getClassStructure(classEntry, this.getOptions());
96
97 // show the tree at the root
98 TreePath path = GuiUtil.getPathToRoot(node);
99 structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
100 structureTree.expandPath(path);
101 structureTree.setSelectionRow(structureTree.getRowForPath(path));
102 }
103
104 private void onClick(MouseEvent event) {
105 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
106 // get the selected node
107 TreePath path = structureTree.getSelectionPath();
108 if (path == null) {
109 return;
110 }
111
112 Object node = path.getLastPathComponent();
113
114 if (node instanceof StructureTreeNode) {
115 this.gui.getController().navigateTo(((StructureTreeNode) node).getEntry());
116 }
117 }
92 } 118 }
93 119
94 /** 120 /**
95 * Creates and returns the options of this structure panel. 121 * Creates and returns the options of this structure panel.
96 */ 122 */
97 public StructureTreeOptions getOptions() { 123 private StructureTreeOptions getOptions() {
98 return new StructureTreeOptions( 124 return new StructureTreeOptions(
99 (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(), 125 (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(),
100 (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(), 126 (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(),
@@ -102,17 +128,17 @@ public class StructurePanel extends JPanel {
102 ); 128 );
103 } 129 }
104 130
105 public JTree getStructureTree() {
106 return this.structureTree;
107 }
108
109 public void retranslateUi() { 131 public void retranslateUi() {
110 this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation")); 132 this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation"));
111 this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation")); 133 this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation"));
112 this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting")); 134 this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting"));
113 } 135 }
114 136
115 class StructureTreeCellRenderer extends DefaultTreeCellRenderer { 137 public JPanel getPanel() {
138 return this.panel;
139 }
140
141 private static class StructureTreeCellRenderer extends DefaultTreeCellRenderer {
116 private final Gui gui; 142 private final Gui gui;
117 143
118 StructureTreeCellRenderer(Gui gui) { 144 StructureTreeCellRenderer(Gui gui) {
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 e26c29b3..28b4043e 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
@@ -1,23 +1,30 @@
1package cuchaz.enigma.gui.util; 1package cuchaz.enigma.gui.util;
2 2
3import com.formdev.flatlaf.extras.FlatSVGIcon;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.translation.representation.AccessFlags;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.MethodEntry;
8import cuchaz.enigma.utils.Os;
9
10import javax.swing.*;
11import java.awt.*; 3import java.awt.*;
12import java.awt.datatransfer.StringSelection; 4import java.awt.datatransfer.StringSelection;
13import java.awt.event.MouseAdapter; 5import java.awt.event.*;
14import java.awt.event.MouseEvent;
15import java.awt.font.TextAttribute; 6import java.awt.font.TextAttribute;
16import java.io.IOException; 7import java.io.IOException;
17import java.net.URI; 8import java.net.URI;
18import java.net.URISyntaxException; 9import java.net.URISyntaxException;
10import java.util.Collections;
11import java.util.List;
19import java.util.Map; 12import java.util.Map;
20import java.util.NoSuchElementException; 13import java.util.NoSuchElementException;
14import java.util.function.Consumer;
15
16import javax.swing.*;
17import javax.swing.tree.TreeNode;
18import javax.swing.tree.TreePath;
19
20import com.formdev.flatlaf.extras.FlatSVGIcon;
21import com.google.common.collect.Lists;
22
23import cuchaz.enigma.gui.Gui;
24import cuchaz.enigma.translation.representation.AccessFlags;
25import cuchaz.enigma.translation.representation.entry.ClassEntry;
26import cuchaz.enigma.translation.representation.entry.MethodEntry;
27import cuchaz.enigma.utils.Os;
21 28
22public class GuiUtil { 29public class GuiUtil {
23 public static final Icon CLASS_ICON = loadIcon("class"); 30 public static final Icon CLASS_ICON = loadIcon("class");
@@ -135,4 +142,44 @@ public class GuiUtil {
135 } 142 }
136 return METHOD_ICON; 143 return METHOD_ICON;
137 } 144 }
145
146 public static TreePath getPathToRoot(TreeNode node) {
147 List<TreeNode> nodes = Lists.newArrayList();
148 TreeNode n = node;
149
150 do {
151 nodes.add(n);
152 n = n.getParent();
153 } while (n != null);
154
155 Collections.reverse(nodes);
156 return new TreePath(nodes.toArray());
157 }
158
159 public static MouseListener onMouseClick(Consumer<MouseEvent> op) {
160 return new MouseAdapter() {
161 @Override
162 public void mouseClicked(MouseEvent e) {
163 op.accept(e);
164 }
165 };
166 }
167
168 public static MouseListener onMousePress(Consumer<MouseEvent> op) {
169 return new MouseAdapter() {
170 @Override
171 public void mousePressed(MouseEvent e) {
172 op.accept(e);
173 }
174 };
175 }
176
177 public static WindowListener onWindowClose(Consumer<WindowEvent> op) {
178 return new WindowAdapter() {
179 @Override
180 public void windowClosing(WindowEvent e) {
181 op.accept(e);
182 }
183 };
184 }
138} 185}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java
index 69612288..9f53a44f 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java
@@ -4,4 +4,8 @@ public interface LanguageChangeListener {
4 4
5 void retranslateUi(); 5 void retranslateUi();
6 6
7 default boolean isValid() {
8 return true;
9 }
10
7} 11}