summaryrefslogtreecommitdiff
path: root/enigma-swing
diff options
context:
space:
mode:
authorGravatar Marco Rebhan2021-07-07 10:09:23 +0200
committerGravatar Marco Rebhan2021-09-05 13:25:38 +0200
commit03f39ef355bac7cf3d7233a9e60bd60440d4a2ae (patch)
treeb598f19481be18f4cb7a3b246fd81a40e5d93e1e /enigma-swing
parentUpdate to shadow 7.0.0, second try (diff)
downloadenigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.tar.gz
enigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.tar.xz
enigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.zip
Refactor and clean up Gui class
(cherry-picked from feature/customizable-ui branch) Clean up Gui constructor Separate out code from Gui class Separate out inheritance tree Separate out implementations tree Move click listener to separate method Make StructurePanel not extend JPanel Handle DeobfPanelPopupMenu in DeobfPanel Fix the deobf panel popup menu init failing Common code for implementations & inheritance tree Move call tree code to separate class Move methods from MouseListenerUtil to GuiUtil Move editor tab code to separate class Replace WindowAdapter with GuiUtil.onWindowClose Move showStructure to StructurePanel
Diffstat (limited to 'enigma-swing')
-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}