summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/Gui.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/Gui.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1058
1 files changed, 0 insertions, 1058 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
deleted file mode 100644
index ed32469..0000000
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ /dev/null
@@ -1,1058 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import java.awt.*;
15import java.awt.event.*;
16import java.nio.file.Path;
17import java.util.List;
18import java.util.*;
19import java.util.function.Function;
20
21import javax.swing.*;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter;
24import javax.swing.tree.*;
25
26import com.google.common.base.Strings;
27import com.google.common.collect.Lists;
28import cuchaz.enigma.Constants;
29import cuchaz.enigma.EnigmaProfile;
30import cuchaz.enigma.ExceptionIgnorer;
31import cuchaz.enigma.analysis.*;
32import cuchaz.enigma.config.Config;
33import cuchaz.enigma.config.Themes;
34import cuchaz.enigma.gui.dialog.CrashDialog;
35import cuchaz.enigma.gui.dialog.JavadocDialog;
36import cuchaz.enigma.gui.dialog.SearchDialog;
37import cuchaz.enigma.gui.elements.CollapsibleTabbedPane;
38import cuchaz.enigma.gui.elements.MenuBar;
39import cuchaz.enigma.gui.elements.PopupMenuBar;
40import cuchaz.enigma.gui.filechooser.FileChooserAny;
41import cuchaz.enigma.gui.filechooser.FileChooserFolder;
42import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
43import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
44import cuchaz.enigma.gui.highlight.TokenHighlightType;
45import cuchaz.enigma.gui.panels.PanelDeobf;
46import cuchaz.enigma.gui.panels.PanelEditor;
47import cuchaz.enigma.gui.panels.PanelIdentifier;
48import cuchaz.enigma.gui.panels.PanelObf;
49import cuchaz.enigma.gui.util.History;
50import cuchaz.enigma.network.packet.*;
51import cuchaz.enigma.throwables.IllegalNameException;
52import cuchaz.enigma.translation.mapping.*;
53import cuchaz.enigma.translation.representation.entry.*;
54import cuchaz.enigma.utils.I18n;
55import cuchaz.enigma.utils.Message;
56import cuchaz.enigma.gui.util.ScaleUtil;
57import cuchaz.enigma.utils.Utils;
58import de.sciss.syntaxpane.DefaultSyntaxKit;
59
60public class Gui {
61
62 public final PopupMenuBar popupMenu;
63 private final PanelObf obfPanel;
64 private final PanelDeobf deobfPanel;
65
66 private final MenuBar menuBar;
67 // state
68 public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory;
69 public EntryReference<Entry<?>, Entry<?>> renamingReference;
70 public EntryReference<Entry<?>, Entry<?>> cursorReference;
71 private boolean shouldNavigateOnClick;
72 private ConnectionState connectionState;
73 private boolean isJarOpen;
74
75 public FileDialog jarFileChooser;
76 public FileDialog tinyMappingsFileChooser;
77 public SearchDialog searchDialog;
78 public JFileChooser enigmaMappingsFileChooser;
79 public JFileChooser exportSourceFileChooser;
80 public FileDialog exportJarFileChooser;
81 private GuiController controller;
82 private JFrame frame;
83 public Config.LookAndFeel editorFeel;
84 public PanelEditor editor;
85 public JScrollPane sourceScroller;
86 private JPanel classesPanel;
87 private JSplitPane splitClasses;
88 private PanelIdentifier infoPanel;
89 public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters;
90 private SelectionHighlightPainter selectionHighlightPainter;
91 private JTree inheritanceTree;
92 private JTree implementationsTree;
93 private JTree callsTree;
94 private JList<Token> tokens;
95 private JTabbedPane tabs;
96
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 public JTextField renameTextField;
112 public JTextArea javadocTextArea;
113
114 public void setEditorTheme(Config.LookAndFeel feel) {
115 if (editor != null && (editorFeel == null || editorFeel != feel)) {
116 editor.updateUI();
117 editor.setBackground(new Color(Config.getInstance().editorBackground));
118 if (editorFeel != null) {
119 getController().refreshCurrentClass();
120 }
121
122 editorFeel = feel;
123 }
124 }
125
126 public Gui(EnigmaProfile profile) {
127 Config.getInstance().lookAndFeel.setGlobalLAF();
128
129 // init frame
130 this.frame = new JFrame(Constants.NAME);
131 final Container pane = this.frame.getContentPane();
132 pane.setLayout(new BorderLayout());
133
134 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
135 // install a global exception handler to the event thread
136 CrashDialog.init(this.frame);
137 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
138 t.printStackTrace(System.err);
139 if (!ExceptionIgnorer.shouldIgnore(t)) {
140 CrashDialog.show(t);
141 }
142 });
143 }
144
145 this.controller = new GuiController(this, profile);
146
147 Themes.updateTheme(this);
148
149 // init file choosers
150 this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD);
151
152 this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD);
153 this.enigmaMappingsFileChooser = new FileChooserAny();
154 this.exportSourceFileChooser = new FileChooserFolder();
155 this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE);
156
157 this.obfPanel = new PanelObf(this);
158 this.deobfPanel = new PanelDeobf(this);
159
160 // set up classes panel (don't add the splitter yet)
161 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
162 splitClasses.setResizeWeight(0.3);
163 this.classesPanel = new JPanel();
164 this.classesPanel.setLayout(new BorderLayout());
165 this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0));
166
167 // init info panel
168 infoPanel = new PanelIdentifier(this);
169 infoPanel.clearReference();
170
171 // init editor
172 selectionHighlightPainter = new SelectionHighlightPainter();
173 this.editor = new PanelEditor(this);
174 this.sourceScroller = new JScrollPane(this.editor);
175 this.editor.setContentType("text/enigma-sources");
176 this.editor.setBackground(new Color(Config.getInstance().editorBackground));
177 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
178 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
179
180 // init editor popup menu
181 this.popupMenu = new PopupMenuBar(this);
182 this.editor.setComponentPopupMenu(this.popupMenu);
183
184 // init inheritance panel
185 inheritanceTree = new JTree();
186 inheritanceTree.setModel(null);
187 inheritanceTree.addMouseListener(new MouseAdapter() {
188 @Override
189 public void mouseClicked(MouseEvent event) {
190 if (event.getClickCount() >= 2) {
191 // get the selected node
192 TreePath path = inheritanceTree.getSelectionPath();
193 if (path == null) {
194 return;
195 }
196
197 Object node = path.getLastPathComponent();
198 if (node instanceof ClassInheritanceTreeNode) {
199 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
200 controller.navigateTo(new ClassEntry(classNode.getObfClassName()));
201 } else if (node instanceof MethodInheritanceTreeNode) {
202 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
203 if (methodNode.isImplemented()) {
204 controller.navigateTo(methodNode.getMethodEntry());
205 }
206 }
207 }
208 }
209 });
210 TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer();
211 inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer));
212
213 JPanel inheritancePanel = new JPanel();
214 inheritancePanel.setLayout(new BorderLayout());
215 inheritancePanel.add(new JScrollPane(inheritanceTree));
216
217 // init implementations panel
218 implementationsTree = new JTree();
219 implementationsTree.setModel(null);
220 implementationsTree.addMouseListener(new MouseAdapter() {
221 @Override
222 public void mouseClicked(MouseEvent event) {
223 if (event.getClickCount() >= 2) {
224 // get the selected node
225 TreePath path = implementationsTree.getSelectionPath();
226 if (path == null) {
227 return;
228 }
229
230 Object node = path.getLastPathComponent();
231 if (node instanceof ClassImplementationsTreeNode) {
232 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
233 controller.navigateTo(classNode.getClassEntry());
234 } else if (node instanceof MethodImplementationsTreeNode) {
235 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
236 controller.navigateTo(methodNode.getMethodEntry());
237 }
238 }
239 }
240 });
241 JPanel implementationsPanel = new JPanel();
242 implementationsPanel.setLayout(new BorderLayout());
243 implementationsPanel.add(new JScrollPane(implementationsTree));
244
245 // init call panel
246 callsTree = new JTree();
247 callsTree.setModel(null);
248 callsTree.addMouseListener(new MouseAdapter() {
249 @SuppressWarnings("unchecked")
250 @Override
251 public void mouseClicked(MouseEvent event) {
252 if (event.getClickCount() >= 2) {
253 // get the selected node
254 TreePath path = callsTree.getSelectionPath();
255 if (path == null) {
256 return;
257 }
258
259 Object node = path.getLastPathComponent();
260 if (node instanceof ReferenceTreeNode) {
261 ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node);
262 if (referenceNode.getReference() != null) {
263 controller.navigateTo(referenceNode.getReference());
264 } else {
265 controller.navigateTo(referenceNode.getEntry());
266 }
267 }
268 }
269 }
270 });
271 tokens = new JList<>();
272 tokens.setCellRenderer(new TokenListCellRenderer(this.controller));
273 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 tokens.setLayoutOrientation(JList.VERTICAL);
275 tokens.addMouseListener(new MouseAdapter() {
276 @Override
277 public void mouseClicked(MouseEvent event) {
278 if (event.getClickCount() == 2) {
279 Token selected = tokens.getSelectedValue();
280 if (selected != null) {
281 showToken(selected);
282 }
283 }
284 }
285 });
286 tokens.setPreferredSize(ScaleUtil.getDimension(0, 200));
287 tokens.setMinimumSize(ScaleUtil.getDimension(0, 200));
288 JSplitPane callPanel = new JSplitPane(
289 JSplitPane.VERTICAL_SPLIT,
290 true,
291 new JScrollPane(callsTree),
292 new JScrollPane(tokens)
293 );
294 callPanel.setResizeWeight(1); // let the top side take all the slack
295 callPanel.resetToPreferredSizes();
296
297 // layout controls
298 JPanel centerPanel = new JPanel();
299 centerPanel.setLayout(new BorderLayout());
300 centerPanel.add(infoPanel, BorderLayout.NORTH);
301 centerPanel.add(sourceScroller, BorderLayout.CENTER);
302 tabs = new JTabbedPane();
303 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0));
304 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel);
305 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel);
306 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel);
307 logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM);
308 userModel = new DefaultListModel<>();
309 users = new JList<>(userModel);
310 messageModel = new DefaultListModel<>();
311 messages = new JList<>(messageModel);
312 messages.setCellRenderer(new MessageListCellRenderer());
313 JPanel messagePanel = new JPanel(new BorderLayout());
314 messageScrollPane = new JScrollPane(this.messages);
315 messagePanel.add(messageScrollPane, BorderLayout.CENTER);
316 JPanel chatPanel = new JPanel(new BorderLayout());
317 chatBox = new JTextField();
318 AbstractAction sendListener = new AbstractAction("Send") {
319 @Override
320 public void actionPerformed(ActionEvent e) {
321 sendMessage();
322 }
323 };
324 chatBox.addActionListener(sendListener);
325 JButton chatSendButton = new JButton(sendListener);
326 chatPanel.add(chatBox, BorderLayout.CENTER);
327 chatPanel.add(chatSendButton, BorderLayout.EAST);
328 messagePanel.add(chatPanel, BorderLayout.SOUTH);
329 logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users));
330 logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel);
331 logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs);
332 logSplit.setResizeWeight(0.5);
333 logSplit.resetToPreferredSizes();
334 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
335 splitRight.setResizeWeight(1); // let the left side take all the slack
336 splitRight.resetToPreferredSizes();
337 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
338 splitCenter.setResizeWeight(0); // let the right side take all the slack
339 pane.add(splitCenter, BorderLayout.CENTER);
340
341 // init menus
342 this.menuBar = new MenuBar(this);
343 this.frame.setJMenuBar(this.menuBar);
344
345 // init status bar
346 statusBar = new JPanel(new BorderLayout());
347 statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
348 connectionStatusLabel = new JLabel();
349 statusLabel = new JLabel();
350 statusBar.add(statusLabel, BorderLayout.CENTER);
351 statusBar.add(connectionStatusLabel, BorderLayout.EAST);
352 pane.add(statusBar, BorderLayout.SOUTH);
353
354 // init state
355 setConnectionState(ConnectionState.NOT_CONNECTED);
356 onCloseJar();
357
358 this.frame.addWindowListener(new WindowAdapter() {
359 @Override
360 public void windowClosing(WindowEvent event) {
361 close();
362 }
363 });
364
365 // show the frame
366 pane.doLayout();
367 this.frame.setSize(ScaleUtil.getDimension(1024, 576));
368 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
369 this.frame.setVisible(true);
370 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
371 this.frame.setLocationRelativeTo(null);
372 }
373
374 public JFrame getFrame() {
375 return this.frame;
376 }
377
378 public GuiController getController() {
379 return this.controller;
380 }
381
382 public void onStartOpenJar() {
383 this.classesPanel.removeAll();
384 redraw();
385 }
386
387 public void onFinishOpenJar(String jarName) {
388 // update gui
389 this.frame.setTitle(Constants.NAME + " - " + jarName);
390 this.classesPanel.removeAll();
391 this.classesPanel.add(splitClasses);
392 setEditorText(null);
393
394 // update menu
395 isJarOpen = true;
396
397 updateUiState();
398 redraw();
399 }
400
401 public void onCloseJar() {
402
403 // update gui
404 this.frame.setTitle(Constants.NAME);
405 setObfClasses(null);
406 setDeobfClasses(null);
407 setEditorText(null);
408 this.classesPanel.removeAll();
409
410 // update menu
411 isJarOpen = false;
412 setMappingsFile(null);
413
414 updateUiState();
415 redraw();
416 }
417
418 public void setObfClasses(Collection<ClassEntry> obfClasses) {
419 this.obfPanel.obfClasses.setClasses(obfClasses);
420 }
421
422 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
423 this.deobfPanel.deobfClasses.setClasses(deobfClasses);
424 }
425
426 public void setMappingsFile(Path path) {
427 this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null);
428 updateUiState();
429 }
430
431 public void setEditorText(String source) {
432 this.editor.getHighlighter().removeAllHighlights();
433 this.editor.setText(source);
434 }
435
436 public void setSource(DecompiledClassSource source) {
437 editor.setText(source.toString());
438 setHighlightedTokens(source.getHighlightedTokens());
439 }
440
441 public void showToken(final Token token) {
442 if (token == null) {
443 throw new IllegalArgumentException("Token cannot be null!");
444 }
445 CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter);
446 redraw();
447 }
448
449 public void showTokens(Collection<Token> tokens) {
450 Vector<Token> sortedTokens = new Vector<>(tokens);
451 Collections.sort(sortedTokens);
452 if (sortedTokens.size() > 1) {
453 // sort the tokens and update the tokens panel
454 this.tokens.setListData(sortedTokens);
455 this.tokens.setSelectedIndex(0);
456 } else {
457 this.tokens.setListData(new Vector<>());
458 }
459
460 // show the first token
461 showToken(sortedTokens.get(0));
462 }
463
464 public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) {
465 // remove any old highlighters
466 this.editor.getHighlighter().removeAllHighlights();
467
468 if (boxHighlightPainters != null) {
469 for (TokenHighlightType type : tokens.keySet()) {
470 BoxHighlightPainter painter = boxHighlightPainters.get(type);
471 if (painter != null) {
472 setHighlightedTokens(tokens.get(type), painter);
473 }
474 }
475 }
476
477 redraw();
478 }
479
480 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
481 for (Token token : tokens) {
482 try {
483 this.editor.getHighlighter().addHighlight(token.start, token.end, painter);
484 } catch (BadLocationException ex) {
485 throw new IllegalArgumentException(ex);
486 }
487 }
488 }
489
490 private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) {
491 if (reference == null) {
492 infoPanel.clearReference();
493 return;
494 }
495
496 this.cursorReference = reference;
497
498 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference);
499
500 infoPanel.removeAll();
501 if (translatedReference.entry instanceof ClassEntry) {
502 showClassEntry((ClassEntry) translatedReference.entry);
503 } else if (translatedReference.entry instanceof FieldEntry) {
504 showFieldEntry((FieldEntry) translatedReference.entry);
505 } else if (translatedReference.entry instanceof MethodEntry) {
506 showMethodEntry((MethodEntry) translatedReference.entry);
507 } else if (translatedReference.entry instanceof LocalVariableEntry) {
508 showLocalVariableEntry((LocalVariableEntry) translatedReference.entry);
509 } else {
510 throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName());
511 }
512
513 redraw();
514 }
515
516 private void showLocalVariableEntry(LocalVariableEntry entry) {
517 addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName());
518 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName());
519 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName());
520 addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex()));
521 }
522
523 private void showClassEntry(ClassEntry entry) {
524 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName());
525 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
526 }
527
528 private void showFieldEntry(FieldEntry entry) {
529 addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName());
530 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
531 addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString());
532 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
533 }
534
535 private void showMethodEntry(MethodEntry entry) {
536 if (entry.isConstructor()) {
537 addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName());
538 } else {
539 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName());
540 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
541 }
542 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString());
543 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
544 }
545
546 private void addNameValue(JPanel container, String name, String value) {
547 JPanel panel = new JPanel();
548 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
549
550 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
551 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
552 panel.add(label);
553
554 panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT)));
555
556 container.add(panel);
557 }
558
559 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) {
560 if (!getController().project.isRenamable(entry))
561 return null;
562 JPanel panel = new JPanel();
563 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
564 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
565 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
566 panel.add(label);
567 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
568 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
569 combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
570
571 EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry);
572 if (mapping != null) {
573 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
574 } else {
575 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
576 }
577
578 combo.addItemListener(controller::modifierChange);
579
580 panel.add(combo);
581
582 container.add(panel);
583
584 return combo;
585 }
586
587 public void onCaretMove(int pos, boolean fromClick) {
588 if (controller.project == null)
589 return;
590 EntryRemapper mapper = controller.project.getMapper();
591 Token token = this.controller.getToken(pos);
592 boolean isToken = token != null;
593
594 cursorReference = this.controller.getReference(token);
595 Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null;
596
597 if (referenceEntry != null && shouldNavigateOnClick && fromClick) {
598 shouldNavigateOnClick = false;
599 Entry<?> navigationEntry = referenceEntry;
600 if (cursorReference.context == null) {
601 EntryResolver resolver = mapper.getObfResolver();
602 navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT);
603 }
604 controller.navigateTo(navigationEntry);
605 return;
606 }
607
608 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
609 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry;
610 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
611 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
612 boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference);
613
614 if (!isRenaming()) {
615 if (isToken) {
616 showCursorReference(cursorReference);
617 } else {
618 infoPanel.clearReference();
619 }
620 }
621
622 this.popupMenu.renameMenu.setEnabled(isRenamable);
623 this.popupMenu.editJavadocMenu.setEnabled(isRenamable);
624 this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
625 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
626 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
627 this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry);
628 this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
629 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference());
630 this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference());
631 this.popupMenu.toggleMappingMenu.setEnabled(isRenamable);
632
633 if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) {
634 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated"));
635 } else {
636 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated"));
637 }
638 }
639
640 public void startDocChange() {
641 EntryReference<Entry<?>, Entry<?>> curReference = cursorReference;
642 if (isRenaming()) {
643 finishRename(false);
644 }
645 renamingReference = curReference;
646
647 // init the text box
648 javadocTextArea = new JTextArea(10, 40);
649
650 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
651 javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs()));
652
653 JavadocDialog.init(frame, javadocTextArea, this::finishDocChange);
654 javadocTextArea.grabFocus();
655
656 redraw();
657 }
658
659 private void finishDocChange(JFrame ui, boolean saveName) {
660 String newName = javadocTextArea.getText();
661 if (saveName) {
662 try {
663 this.controller.changeDocs(renamingReference, newName);
664 this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName));
665 } catch (IllegalNameException ex) {
666 javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1));
667 javadocTextArea.setToolTipText(ex.getReason());
668 Utils.showToolTipNow(javadocTextArea);
669 return;
670 }
671
672 ui.setVisible(false);
673 showCursorReference(cursorReference);
674 return;
675 }
676
677 // abort the jd change
678 javadocTextArea = null;
679 ui.setVisible(false);
680 showCursorReference(cursorReference);
681
682 this.editor.grabFocus();
683
684 redraw();
685 }
686
687 public void startRename() {
688
689 // init the text box
690 renameTextField = new JTextField();
691
692 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
693 renameTextField.setText(translatedReference.getNameableName());
694
695 renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height)));
696 renameTextField.addKeyListener(new KeyAdapter() {
697 @Override
698 public void keyPressed(KeyEvent event) {
699 switch (event.getKeyCode()) {
700 case KeyEvent.VK_ENTER:
701 finishRename(true);
702 break;
703
704 case KeyEvent.VK_ESCAPE:
705 finishRename(false);
706 break;
707 default:
708 break;
709 }
710 }
711 });
712
713 // find the label with the name and replace it with the text box
714 JPanel panel = (JPanel) infoPanel.getComponent(0);
715 panel.remove(panel.getComponentCount() - 1);
716 panel.add(renameTextField);
717 renameTextField.grabFocus();
718
719 int offset = renameTextField.getText().lastIndexOf('/') + 1;
720 // If it's a class and isn't in the default package, assume that it's deobfuscated.
721 if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0)
722 renameTextField.select(offset, renameTextField.getText().length());
723 else
724 renameTextField.selectAll();
725
726 renamingReference = cursorReference;
727
728 redraw();
729 }
730
731 private void finishRename(boolean saveName) {
732 String newName = renameTextField.getText();
733
734 if (saveName && newName != null && !newName.isEmpty()) {
735 try {
736 this.controller.rename(renamingReference, newName, true);
737 this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true));
738 renameTextField = null;
739 } catch (IllegalNameException ex) {
740 renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1));
741 renameTextField.setToolTipText(ex.getReason());
742 Utils.showToolTipNow(renameTextField);
743 }
744 return;
745 }
746
747 renameTextField = null;
748
749 // abort the rename
750 showCursorReference(cursorReference);
751
752 this.editor.grabFocus();
753
754 redraw();
755 }
756
757 private boolean isRenaming() {
758 return renameTextField != null;
759 }
760
761 public void showInheritance() {
762
763 if (cursorReference == null) {
764 return;
765 }
766
767 inheritanceTree.setModel(null);
768
769 if (cursorReference.entry instanceof ClassEntry) {
770 // get the class inheritance
771 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry);
772
773 // show the tree at the root
774 TreePath path = getPathToRoot(classNode);
775 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
776 inheritanceTree.expandPath(path);
777 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
778 } else if (cursorReference.entry instanceof MethodEntry) {
779 // get the method inheritance
780 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry);
781
782 // show the tree at the root
783 TreePath path = getPathToRoot(classNode);
784 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
785 inheritanceTree.expandPath(path);
786 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
787 }
788
789 tabs.setSelectedIndex(0);
790
791 redraw();
792 }
793
794 public void showImplementations() {
795
796 if (cursorReference == null) {
797 return;
798 }
799
800 implementationsTree.setModel(null);
801
802 DefaultMutableTreeNode node = null;
803
804 // get the class implementations
805 if (cursorReference.entry instanceof ClassEntry)
806 node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry);
807 else // get the method implementations
808 if (cursorReference.entry instanceof MethodEntry)
809 node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry);
810
811 if (node != null) {
812 // show the tree at the root
813 TreePath path = getPathToRoot(node);
814 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
815 implementationsTree.expandPath(path);
816 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
817 }
818
819 tabs.setSelectedIndex(1);
820
821 redraw();
822 }
823
824 public void showCalls(boolean recurse) {
825 if (cursorReference == null) {
826 return;
827 }
828
829 if (cursorReference.entry instanceof ClassEntry) {
830 ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry);
831 callsTree.setModel(new DefaultTreeModel(node));
832 } else if (cursorReference.entry instanceof FieldEntry) {
833 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry);
834 callsTree.setModel(new DefaultTreeModel(node));
835 } else if (cursorReference.entry instanceof MethodEntry) {
836 MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse);
837 callsTree.setModel(new DefaultTreeModel(node));
838 }
839
840 tabs.setSelectedIndex(2);
841
842 redraw();
843 }
844
845 public void toggleMapping() {
846 Entry<?> obfEntry = cursorReference.entry;
847 Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
848
849 if (!Objects.equals(obfEntry, deobfEntry)) {
850 this.controller.removeMapping(cursorReference);
851 this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry()));
852 } else {
853 this.controller.markAsDeobfuscated(cursorReference);
854 this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry()));
855 }
856 }
857
858 private TreePath getPathToRoot(TreeNode node) {
859 List<TreeNode> nodes = Lists.newArrayList();
860 TreeNode n = node;
861 do {
862 nodes.add(n);
863 n = n.getParent();
864 } while (n != null);
865 Collections.reverse(nodes);
866 return new TreePath(nodes.toArray());
867 }
868
869 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
870 int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION,
871 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
872 callback.apply(response);
873 }
874
875 public void saveMapping() {
876 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
877 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
878 }
879
880 public void close() {
881 if (!this.controller.isDirty()) {
882 // everything is saved, we can exit safely
883 exit();
884 } else {
885 // ask to save before closing
886 showDiscardDiag((response) -> {
887 if (response == JOptionPane.YES_OPTION) {
888 this.saveMapping();
889 exit();
890 } else if (response == JOptionPane.NO_OPTION) {
891 exit();
892 }
893
894 return null;
895 }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel"));
896 }
897 }
898
899 private void exit() {
900 if (searchDialog != null) {
901 searchDialog.dispose();
902 }
903 this.frame.dispose();
904 System.exit(0);
905 }
906
907 public void redraw() {
908 this.frame.validate();
909 this.frame.repaint();
910 }
911
912 public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException {
913 // package rename
914 if (data instanceof String) {
915 for (int i = 0; i < node.getChildCount(); i++) {
916 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
917 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
918 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
919 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false);
920 this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false));
921 childNode.setUserObject(dataChild);
922 }
923 node.setUserObject(data);
924 // Ob package will never be modified, just reload deob view
925 this.deobfPanel.deobfClasses.reload();
926 }
927 // class rename
928 else if (data instanceof ClassEntry) {
929 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
930 this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false));
931 }
932 }
933
934 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) {
935 String oldEntry = obfReference.entry.getContainingClass().getPackageName();
936 String newEntry = new ClassEntry(newName).getPackageName();
937 moveClassTree(obfReference, oldEntry == null, newEntry == null);
938 }
939
940 // TODO: getExpansionState will *not* actually update itself based on name changes!
941 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) {
942 ClassEntry classEntry = obfReference.entry.getContainingClass();
943
944 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses);
945 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses);
946
947 // Ob -> deob
948 if (!isNewOb) {
949 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
950 this.obfPanel.obfClasses.moveClassOut(classEntry);
951 this.deobfPanel.deobfClasses.reload();
952 this.obfPanel.obfClasses.reload();
953 }
954 // Deob -> ob
955 else if (!isOldOb) {
956 this.obfPanel.obfClasses.moveClassIn(classEntry);
957 this.deobfPanel.deobfClasses.moveClassOut(classEntry);
958 this.deobfPanel.deobfClasses.reload();
959 this.obfPanel.obfClasses.reload();
960 }
961 // Local move
962 else if (isOldOb) {
963 this.obfPanel.obfClasses.moveClassIn(classEntry);
964 this.obfPanel.obfClasses.reload();
965 } else {
966 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
967 this.deobfPanel.deobfClasses.reload();
968 }
969
970 this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf);
971 this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf);
972 }
973
974 public PanelObf getObfPanel() {
975 return obfPanel;
976 }
977
978 public PanelDeobf getDeobfPanel() {
979 return deobfPanel;
980 }
981
982 public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) {
983 this.shouldNavigateOnClick = shouldNavigateOnClick;
984 }
985
986 public SearchDialog getSearchDialog() {
987 if (searchDialog == null) {
988 searchDialog = new SearchDialog(this);
989 }
990 return searchDialog;
991 }
992
993
994 public MenuBar getMenuBar() {
995 return menuBar;
996 }
997
998 public void addMessage(Message message) {
999 JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar();
1000 boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent();
1001 messageModel.addElement(message);
1002 if (isAtBottom) {
1003 SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent()));
1004 }
1005 statusLabel.setText(message.translate());
1006 }
1007
1008 public void setUserList(List<String> users) {
1009 userModel.clear();
1010 users.forEach(userModel::addElement);
1011 connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size()));
1012 }
1013
1014 private void sendMessage() {
1015 String text = chatBox.getText().trim();
1016 if (!text.isEmpty()) {
1017 getController().sendPacket(new MessageC2SPacket(text));
1018 }
1019 chatBox.setText("");
1020 }
1021
1022 /**
1023 * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state.
1024 * This is a central place to update the UI state to prevent multiple code paths from changing the same state,
1025 * causing inconsistencies.
1026 */
1027 public void updateUiState() {
1028 menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING);
1029 menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect"));
1030 menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED);
1031 menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop"));
1032
1033 menuBar.closeJarMenu.setEnabled(isJarOpen);
1034 menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1035 menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED);
1036 menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1037 menuBar.closeMappingsMenu.setEnabled(isJarOpen);
1038 menuBar.exportSourceMenu.setEnabled(isJarOpen);
1039 menuBar.exportJarMenu.setEnabled(isJarOpen);
1040
1041 connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected"));
1042
1043 if (connectionState == ConnectionState.NOT_CONNECTED) {
1044 logSplit.setLeftComponent(null);
1045 splitRight.setRightComponent(tabs);
1046 } else {
1047 splitRight.setRightComponent(logSplit);
1048 logSplit.setLeftComponent(tabs);
1049 }
1050 }
1051
1052 public void setConnectionState(ConnectionState state) {
1053 connectionState = state;
1054 statusLabel.setText(I18n.translate("status.ready"));
1055 updateUiState();
1056 }
1057
1058}