summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar jeff2015-02-08 21:29:25 -0500
committerGravatar jeff2015-02-08 21:29:25 -0500
commited9b5cdfc648e86fd463bfa8d86b94c41671e14c (patch)
tree2619bbc7e04dfa3b82f8dfd3b1d31f529766cd4b /src/cuchaz/enigma/gui
downloadenigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.gz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.xz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.zip
switch all classes to new signature/type system
Diffstat (limited to 'src/cuchaz/enigma/gui')
-rw-r--r--src/cuchaz/enigma/gui/AboutDialog.java86
-rw-r--r--src/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/cuchaz/enigma/gui/BrowserCaret.java45
-rw-r--r--src/cuchaz/enigma/gui/ClassListCellRenderer.java36
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java164
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java35
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java33
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1165
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java355
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java36
-rw-r--r--src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/OtherHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/ProgressDialog.java105
-rw-r--r--src/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/cuchaz/enigma/gui/RenameListener.java17
-rw-r--r--src/cuchaz/enigma/gui/SelectionHighlightPainter.java34
-rw-r--r--src/cuchaz/enigma/gui/TokenListCellRenderer.java38
19 files changed, 2413 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 0000000..2476b56
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.io.IOException;
20
21import javax.swing.JButton;
22import javax.swing.JFrame;
23import javax.swing.JLabel;
24import javax.swing.JPanel;
25import javax.swing.WindowConstants;
26
27import cuchaz.enigma.Constants;
28import cuchaz.enigma.Util;
29
30public class AboutDialog {
31
32 public static void show(JFrame parent) {
33 // init frame
34 final JFrame frame = new JFrame(Constants.Name + " - About");
35 final Container pane = frame.getContentPane();
36 pane.setLayout(new FlowLayout());
37
38 // load the content
39 try {
40 String html = Util.readResourceToString("/about.html");
41 html = String.format(html, Constants.Name, Constants.Version);
42 JLabel label = new JLabel(html);
43 label.setHorizontalAlignment(JLabel.CENTER);
44 pane.add(label);
45 } catch (IOException ex) {
46 throw new Error(ex);
47 }
48
49 // show the link
50 String html = "<html><a href=\"%s\">%s</a></html>";
51 html = String.format(html, Constants.Url, Constants.Url);
52 JButton link = new JButton(html);
53 link.addActionListener(new ActionListener() {
54 @Override
55 public void actionPerformed(ActionEvent event) {
56 Util.openUrl(Constants.Url);
57 }
58 });
59 link.setBorderPainted(false);
60 link.setOpaque(false);
61 link.setBackground(Color.WHITE);
62 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
63 link.setFocusable(false);
64 JPanel linkPanel = new JPanel();
65 linkPanel.add(link);
66 pane.add(linkPanel);
67
68 // show ok button
69 JButton okButton = new JButton("Ok");
70 pane.add(okButton);
71 okButton.addActionListener(new ActionListener() {
72 @Override
73 public void actionPerformed(ActionEvent arg0) {
74 frame.dispose();
75 }
76 });
77
78 // show the frame
79 pane.doLayout();
80 frame.setSize(400, 220);
81 frame.setResizable(false);
82 frame.setLocationRelativeTo(parent);
83 frame.setVisible(true);
84 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
85 }
86}
diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
new file mode 100644
index 0000000..db7c85b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java
new file mode 100644
index 0000000..acee483
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Graphics;
14import java.awt.Shape;
15
16import javax.swing.text.DefaultCaret;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19
20public class BrowserCaret extends DefaultCaret {
21
22 private static final long serialVersionUID = 1158977422507969940L;
23
24 private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() {
25 @Override
26 public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
27 // don't paint anything
28 }
29 };
30
31 @Override
32 public boolean isSelectionVisible() {
33 return false;
34 }
35
36 @Override
37 public boolean isVisible() {
38 return true;
39 }
40
41 @Override
42 public Highlighter.HighlightPainter getSelectionPainter() {
43 return m_selectionPainter;
44 }
45}
diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
new file mode 100644
index 0000000..d0f01e6
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javassist.bytecode.Descriptor;
16
17import javax.swing.DefaultListCellRenderer;
18import javax.swing.JLabel;
19import javax.swing.JList;
20import javax.swing.ListCellRenderer;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> {
23
24 private DefaultListCellRenderer m_defaultRenderer;
25
26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer();
28 }
29
30 @Override
31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) {
32 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus);
33 label.setText(Descriptor.toJavaName(className));
34 return label;
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 0000000..654bfbe
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.List;
19import java.util.Map;
20
21import javax.swing.JTree;
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.DefaultTreeModel;
24import javax.swing.tree.TreePath;
25
26import com.google.common.collect.ArrayListMultimap;
27import com.google.common.collect.Lists;
28import com.google.common.collect.Maps;
29import com.google.common.collect.Multimap;
30
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class ClassSelector extends JTree {
34
35 private static final long serialVersionUID = -7632046902384775977L;
36
37 public interface ClassSelectionListener {
38 void onSelectClass(ClassEntry classEntry);
39 }
40
41 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
42 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
43
44 static {
45 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
46 @Override
47 public int compare(ClassEntry a, ClassEntry b) {
48 if (a.getName().length() != b.getName().length()) {
49 return a.getName().length() - b.getName().length();
50 }
51 return a.getName().compareTo(b.getName());
52 }
53 };
54
55 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
56 @Override
57 public int compare(ClassEntry a, ClassEntry b) {
58 return a.getName().compareTo(b.getName());
59 }
60 };
61 }
62
63 private ClassSelectionListener m_listener;
64 private Comparator<ClassEntry> m_comparator;
65
66 public ClassSelector(Comparator<ClassEntry> comparator) {
67 m_comparator = comparator;
68
69 // configure the tree control
70 setRootVisible(false);
71 setShowsRootHandles(false);
72 setModel(null);
73
74 // hook events
75 addMouseListener(new MouseAdapter() {
76 @Override
77 public void mouseClicked(MouseEvent event) {
78 if (m_listener != null && event.getClickCount() == 2) {
79 // get the selected node
80 TreePath path = getSelectionPath();
81 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
82 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
83 m_listener.onSelectClass(node.getClassEntry());
84 }
85 }
86 }
87 });
88
89 // init defaults
90 m_listener = null;
91 }
92
93 public void setListener(ClassSelectionListener val) {
94 m_listener = val;
95 }
96
97 public void setClasses(Collection<ClassEntry> classEntries) {
98 if (classEntries == null) {
99 setModel(null);
100 return;
101 }
102
103 // build the package names
104 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
105 for (ClassEntry classEntry : classEntries) {
106 packages.put(classEntry.getPackageName(), null);
107 }
108
109 // sort the packages
110 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
111 Collections.sort(sortedPackageNames, new Comparator<String>() {
112 @Override
113 public int compare(String a, String b) {
114 // I can never keep this rule straight when writing these damn things...
115 // a < b => -1, a == b => 0, a > b => +1
116
117 String[] aparts = a.split("/");
118 String[] bparts = b.split("/");
119 for (int i = 0; true; i++) {
120 if (i >= aparts.length) {
121 return -1;
122 } else if (i >= bparts.length) {
123 return 1;
124 }
125
126 int result = aparts[i].compareTo(bparts[i]);
127 if (result != 0) {
128 return result;
129 }
130 }
131 }
132 });
133
134 // create the root node and the package nodes
135 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
136 for (String packageName : sortedPackageNames) {
137 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
138 packages.put(packageName, node);
139 root.add(node);
140 }
141
142 // put the classes into packages
143 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
144 for (ClassEntry classEntry : classEntries) {
145 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
146 }
147
148 // build the class nodes
149 for (String packageName : packagedClassEntries.keySet()) {
150 // sort the class entries
151 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
152 Collections.sort(classEntriesInPackage, m_comparator);
153
154 // create the nodes in order
155 for (ClassEntry classEntry : classEntriesInPackage) {
156 ClassSelectorPackageNode node = packages.get(packageName);
157 node.add(new ClassSelectorClassNode(classEntry));
158 }
159 }
160
161 // finally, update the tree control
162 setModel(new DefaultTreeModel(root));
163 }
164}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 0000000..66e931b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 return m_classEntry.getSimpleName();
34 }
35}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 0000000..451d380
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 0000000..360091a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.BorderFactory;
22import javax.swing.JButton;
23import javax.swing.JFrame;
24import javax.swing.JLabel;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.JTextArea;
28import javax.swing.WindowConstants;
29
30import cuchaz.enigma.Constants;
31
32public class CrashDialog {
33
34 private static CrashDialog m_instance = null;
35
36 private JFrame m_frame;
37 private JTextArea m_text;
38
39 private CrashDialog(JFrame parent) {
40 // init frame
41 m_frame = new JFrame(Constants.Name + " - Crash Report");
42 final Container pane = m_frame.getContentPane();
43 pane.setLayout(new BorderLayout());
44
45 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
46 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
47 pane.add(label, BorderLayout.NORTH);
48
49 // report panel
50 m_text = new JTextArea();
51 m_text.setTabSize(2);
52 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
53
54 // buttons panel
55 JPanel buttonsPanel = new JPanel();
56 FlowLayout buttonsLayout = new FlowLayout();
57 buttonsLayout.setAlignment(FlowLayout.RIGHT);
58 buttonsPanel.setLayout(buttonsLayout);
59 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
60 JButton ignoreButton = new JButton("Ignore");
61 ignoreButton.addActionListener(new ActionListener() {
62 @Override
63 public void actionPerformed(ActionEvent event) {
64 // close (hide) the dialog
65 m_frame.setVisible(false);
66 }
67 });
68 buttonsPanel.add(ignoreButton);
69 JButton exitButton = new JButton("Exit");
70 exitButton.addActionListener(new ActionListener() {
71 @Override
72 public void actionPerformed(ActionEvent event) {
73 // exit enigma
74 System.exit(1);
75 }
76 });
77 buttonsPanel.add(exitButton);
78 pane.add(buttonsPanel, BorderLayout.SOUTH);
79
80 // show the frame
81 m_frame.setSize(600, 400);
82 m_frame.setLocationRelativeTo(parent);
83 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
84 }
85
86 public static void init(JFrame parent) {
87 m_instance = new CrashDialog(parent);
88 }
89
90 public static void show(Throwable ex) {
91 // get the error report
92 StringWriter buf = new StringWriter();
93 ex.printStackTrace(new PrintWriter(buf));
94 String report = buf.toString();
95
96 // show it!
97 m_instance.m_text.setText(report);
98 m_instance.m_frame.doLayout();
99 m_instance.m_frame.setVisible(true);
100 }
101}
diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
new file mode 100644
index 0000000..26a3163
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public DeobfuscatedHighlightPainter() {
18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 0000000..ca39c42
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1165 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Color;
15import java.awt.Container;
16import java.awt.Dimension;
17import java.awt.FlowLayout;
18import java.awt.GridLayout;
19import java.awt.Rectangle;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.InputEvent;
23import java.awt.event.KeyAdapter;
24import java.awt.event.KeyEvent;
25import java.awt.event.MouseAdapter;
26import java.awt.event.MouseEvent;
27import java.awt.event.WindowAdapter;
28import java.awt.event.WindowEvent;
29import java.io.File;
30import java.io.IOException;
31import java.lang.Thread.UncaughtExceptionHandler;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.List;
35import java.util.Vector;
36import java.util.jar.JarFile;
37
38import javax.swing.BorderFactory;
39import javax.swing.JEditorPane;
40import javax.swing.JFileChooser;
41import javax.swing.JFrame;
42import javax.swing.JLabel;
43import javax.swing.JList;
44import javax.swing.JMenu;
45import javax.swing.JMenuBar;
46import javax.swing.JMenuItem;
47import javax.swing.JOptionPane;
48import javax.swing.JPanel;
49import javax.swing.JPopupMenu;
50import javax.swing.JScrollPane;
51import javax.swing.JSplitPane;
52import javax.swing.JTabbedPane;
53import javax.swing.JTextField;
54import javax.swing.JTree;
55import javax.swing.KeyStroke;
56import javax.swing.ListSelectionModel;
57import javax.swing.SwingUtilities;
58import javax.swing.Timer;
59import javax.swing.WindowConstants;
60import javax.swing.event.CaretEvent;
61import javax.swing.event.CaretListener;
62import javax.swing.text.BadLocationException;
63import javax.swing.text.Highlighter;
64import javax.swing.tree.DefaultTreeModel;
65import javax.swing.tree.TreeNode;
66import javax.swing.tree.TreePath;
67
68import jsyntaxpane.DefaultSyntaxKit;
69
70import com.google.common.collect.Lists;
71
72import cuchaz.enigma.Constants;
73import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
74import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
75import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
76import cuchaz.enigma.analysis.EntryReference;
77import cuchaz.enigma.analysis.FieldReferenceTreeNode;
78import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
79import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
80import cuchaz.enigma.analysis.ReferenceTreeNode;
81import cuchaz.enigma.analysis.Token;
82import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
83import cuchaz.enigma.mapping.ArgumentEntry;
84import cuchaz.enigma.mapping.ClassEntry;
85import cuchaz.enigma.mapping.ConstructorEntry;
86import cuchaz.enigma.mapping.Entry;
87import cuchaz.enigma.mapping.FieldEntry;
88import cuchaz.enigma.mapping.IllegalNameException;
89import cuchaz.enigma.mapping.MappingParseException;
90import cuchaz.enigma.mapping.MethodEntry;
91import cuchaz.enigma.mapping.Signature;
92
93public class Gui {
94
95 private GuiController m_controller;
96
97 // controls
98 private JFrame m_frame;
99 private ClassSelector m_obfClasses;
100 private ClassSelector m_deobfClasses;
101 private JEditorPane m_editor;
102 private JPanel m_classesPanel;
103 private JSplitPane m_splitClasses;
104 private JPanel m_infoPanel;
105 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
106 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
107 private OtherHighlightPainter m_otherHighlightPainter;
108 private SelectionHighlightPainter m_selectionHighlightPainter;
109 private JTree m_inheritanceTree;
110 private JTree m_implementationsTree;
111 private JTree m_callsTree;
112 private JList<Token> m_tokens;
113 private JTabbedPane m_tabs;
114
115 // dynamic menu items
116 private JMenuItem m_closeJarMenu;
117 private JMenuItem m_openMappingsMenu;
118 private JMenuItem m_saveMappingsMenu;
119 private JMenuItem m_saveMappingsAsMenu;
120 private JMenuItem m_closeMappingsMenu;
121 private JMenuItem m_renameMenu;
122 private JMenuItem m_showInheritanceMenu;
123 private JMenuItem m_openEntryMenu;
124 private JMenuItem m_openPreviousMenu;
125 private JMenuItem m_showCallsMenu;
126 private JMenuItem m_showImplementationsMenu;
127 private JMenuItem m_toggleMappingMenu;
128 private JMenuItem m_exportSourceMenu;
129 private JMenuItem m_exportJarMenu;
130
131 // state
132 private EntryReference<Entry,Entry> m_reference;
133 private JFileChooser m_jarFileChooser;
134 private JFileChooser m_mappingsFileChooser;
135 private JFileChooser m_exportSourceFileChooser;
136 private JFileChooser m_exportJarFileChooser;
137
138 public Gui() {
139
140 // init frame
141 m_frame = new JFrame(Constants.Name);
142 final Container pane = m_frame.getContentPane();
143 pane.setLayout(new BorderLayout());
144
145 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
146 // install a global exception handler to the event thread
147 CrashDialog.init(m_frame);
148 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
149 @Override
150 public void uncaughtException(Thread thread, Throwable ex) {
151 ex.printStackTrace(System.err);
152 CrashDialog.show(ex);
153 }
154 });
155 }
156
157 m_controller = new GuiController(this);
158
159 // init file choosers
160 m_jarFileChooser = new JFileChooser();
161 m_mappingsFileChooser = new JFileChooser();
162 m_exportSourceFileChooser = new JFileChooser();
163 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
164 m_exportJarFileChooser = new JFileChooser();
165
166 // init obfuscated classes list
167 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
168 m_obfClasses.setListener(new ClassSelectionListener() {
169 @Override
170 public void onSelectClass(ClassEntry classEntry) {
171 navigateTo(classEntry);
172 }
173 });
174 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
175 JPanel obfPanel = new JPanel();
176 obfPanel.setLayout(new BorderLayout());
177 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
178 obfPanel.add(obfScroller, BorderLayout.CENTER);
179
180 // init deobfuscated classes list
181 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
182 m_deobfClasses.setListener(new ClassSelectionListener() {
183 @Override
184 public void onSelectClass(ClassEntry classEntry) {
185 navigateTo(classEntry);
186 }
187 });
188 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
189 JPanel deobfPanel = new JPanel();
190 deobfPanel.setLayout(new BorderLayout());
191 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
192 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
193
194 // set up classes panel (don't add the splitter yet)
195 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
196 m_splitClasses.setResizeWeight(0.3);
197 m_classesPanel = new JPanel();
198 m_classesPanel.setLayout(new BorderLayout());
199 m_classesPanel.setPreferredSize(new Dimension(250, 0));
200
201 // init info panel
202 m_infoPanel = new JPanel();
203 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
204 m_infoPanel.setPreferredSize(new Dimension(0, 100));
205 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
206 clearReference();
207
208 // init editor
209 DefaultSyntaxKit.initKit();
210 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
211 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
212 m_otherHighlightPainter = new OtherHighlightPainter();
213 m_selectionHighlightPainter = new SelectionHighlightPainter();
214 m_editor = new JEditorPane();
215 m_editor.setEditable(false);
216 m_editor.setCaret(new BrowserCaret());
217 JScrollPane sourceScroller = new JScrollPane(m_editor);
218 m_editor.setContentType("text/java");
219 m_editor.addCaretListener(new CaretListener() {
220 @Override
221 public void caretUpdate(CaretEvent event) {
222 onCaretMove(event.getDot());
223 }
224 });
225 m_editor.addKeyListener(new KeyAdapter() {
226 @Override
227 public void keyPressed(KeyEvent event) {
228 switch (event.getKeyCode()) {
229 case KeyEvent.VK_R:
230 m_renameMenu.doClick();
231 break;
232
233 case KeyEvent.VK_I:
234 m_showInheritanceMenu.doClick();
235 break;
236
237 case KeyEvent.VK_M:
238 m_showImplementationsMenu.doClick();
239 break;
240
241 case KeyEvent.VK_N:
242 m_openEntryMenu.doClick();
243 break;
244
245 case KeyEvent.VK_P:
246 m_openPreviousMenu.doClick();
247 break;
248
249 case KeyEvent.VK_C:
250 m_showCallsMenu.doClick();
251 break;
252
253 case KeyEvent.VK_T:
254 m_toggleMappingMenu.doClick();
255 break;
256 }
257 }
258 });
259
260 // turn off token highlighting (it's wrong most of the time anyway...)
261 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
262 kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker");
263
264 // init editor popup menu
265 JPopupMenu popupMenu = new JPopupMenu();
266 m_editor.setComponentPopupMenu(popupMenu);
267 {
268 JMenuItem menu = new JMenuItem("Rename");
269 menu.addActionListener(new ActionListener() {
270 @Override
271 public void actionPerformed(ActionEvent event) {
272 startRename();
273 }
274 });
275 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
276 menu.setEnabled(false);
277 popupMenu.add(menu);
278 m_renameMenu = menu;
279 }
280 {
281 JMenuItem menu = new JMenuItem("Show Inheritance");
282 menu.addActionListener(new ActionListener() {
283 @Override
284 public void actionPerformed(ActionEvent event) {
285 showInheritance();
286 }
287 });
288 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
289 menu.setEnabled(false);
290 popupMenu.add(menu);
291 m_showInheritanceMenu = menu;
292 }
293 {
294 JMenuItem menu = new JMenuItem("Show Implementations");
295 menu.addActionListener(new ActionListener() {
296 @Override
297 public void actionPerformed(ActionEvent event) {
298 showImplementations();
299 }
300 });
301 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
302 menu.setEnabled(false);
303 popupMenu.add(menu);
304 m_showImplementationsMenu = menu;
305 }
306 {
307 JMenuItem menu = new JMenuItem("Show Calls");
308 menu.addActionListener(new ActionListener() {
309 @Override
310 public void actionPerformed(ActionEvent event) {
311 showCalls();
312 }
313 });
314 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
315 menu.setEnabled(false);
316 popupMenu.add(menu);
317 m_showCallsMenu = menu;
318 }
319 {
320 JMenuItem menu = new JMenuItem("Go to Declaration");
321 menu.addActionListener(new ActionListener() {
322 @Override
323 public void actionPerformed(ActionEvent event) {
324 navigateTo(m_reference.entry);
325 }
326 });
327 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
328 menu.setEnabled(false);
329 popupMenu.add(menu);
330 m_openEntryMenu = menu;
331 }
332 {
333 JMenuItem menu = new JMenuItem("Go to previous");
334 menu.addActionListener(new ActionListener() {
335 @Override
336 public void actionPerformed(ActionEvent event) {
337 m_controller.openPreviousReference();
338 }
339 });
340 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
341 menu.setEnabled(false);
342 popupMenu.add(menu);
343 m_openPreviousMenu = menu;
344 }
345 {
346 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
347 menu.addActionListener(new ActionListener() {
348 @Override
349 public void actionPerformed(ActionEvent event) {
350 toggleMapping();
351 }
352 });
353 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
354 menu.setEnabled(false);
355 popupMenu.add(menu);
356 m_toggleMappingMenu = menu;
357 }
358
359 // init inheritance panel
360 m_inheritanceTree = new JTree();
361 m_inheritanceTree.setModel(null);
362 m_inheritanceTree.addMouseListener(new MouseAdapter() {
363 @Override
364 public void mouseClicked(MouseEvent event) {
365 if (event.getClickCount() == 2) {
366 // get the selected node
367 TreePath path = m_inheritanceTree.getSelectionPath();
368 if (path == null) {
369 return;
370 }
371
372 Object node = path.getLastPathComponent();
373 if (node instanceof ClassInheritanceTreeNode) {
374 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
375 navigateTo(new ClassEntry(classNode.getObfClassName()));
376 } else if (node instanceof MethodInheritanceTreeNode) {
377 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
378 if (methodNode.isImplemented()) {
379 navigateTo(methodNode.getMethodEntry());
380 }
381 }
382 }
383 }
384 });
385 JPanel inheritancePanel = new JPanel();
386 inheritancePanel.setLayout(new BorderLayout());
387 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
388
389 // init implementations panel
390 m_implementationsTree = new JTree();
391 m_implementationsTree.setModel(null);
392 m_implementationsTree.addMouseListener(new MouseAdapter() {
393 @Override
394 public void mouseClicked(MouseEvent event) {
395 if (event.getClickCount() == 2) {
396 // get the selected node
397 TreePath path = m_implementationsTree.getSelectionPath();
398 if (path == null) {
399 return;
400 }
401
402 Object node = path.getLastPathComponent();
403 if (node instanceof ClassImplementationsTreeNode) {
404 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
405 navigateTo(classNode.getClassEntry());
406 } else if (node instanceof MethodImplementationsTreeNode) {
407 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
408 navigateTo(methodNode.getMethodEntry());
409 }
410 }
411 }
412 });
413 JPanel implementationsPanel = new JPanel();
414 implementationsPanel.setLayout(new BorderLayout());
415 implementationsPanel.add(new JScrollPane(m_implementationsTree));
416
417 // init call panel
418 m_callsTree = new JTree();
419 m_callsTree.setModel(null);
420 m_callsTree.addMouseListener(new MouseAdapter() {
421 @SuppressWarnings("unchecked")
422 @Override
423 public void mouseClicked(MouseEvent event) {
424 if (event.getClickCount() == 2) {
425 // get the selected node
426 TreePath path = m_callsTree.getSelectionPath();
427 if (path == null) {
428 return;
429 }
430
431 Object node = path.getLastPathComponent();
432 if (node instanceof ReferenceTreeNode) {
433 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
434 if (referenceNode.getReference() != null) {
435 navigateTo(referenceNode.getReference());
436 } else {
437 navigateTo(referenceNode.getEntry());
438 }
439 }
440 }
441 }
442 });
443 m_tokens = new JList<Token>();
444 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
445 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
446 m_tokens.setLayoutOrientation(JList.VERTICAL);
447 m_tokens.addMouseListener(new MouseAdapter() {
448 @Override
449 public void mouseClicked(MouseEvent event) {
450 if (event.getClickCount() == 2) {
451 Token selected = m_tokens.getSelectedValue();
452 if (selected != null) {
453 showToken(selected);
454 }
455 }
456 }
457 });
458 m_tokens.setPreferredSize(new Dimension(0, 200));
459 m_tokens.setMinimumSize(new Dimension(0, 200));
460 JSplitPane callPanel = new JSplitPane(
461 JSplitPane.VERTICAL_SPLIT,
462 true,
463 new JScrollPane(m_callsTree),
464 new JScrollPane(m_tokens)
465 );
466 callPanel.setResizeWeight(1); // let the top side take all the slack
467 callPanel.resetToPreferredSizes();
468
469 // layout controls
470 JPanel centerPanel = new JPanel();
471 centerPanel.setLayout(new BorderLayout());
472 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
473 centerPanel.add(sourceScroller, BorderLayout.CENTER);
474 m_tabs = new JTabbedPane();
475 m_tabs.setPreferredSize(new Dimension(250, 0));
476 m_tabs.addTab("Inheritance", inheritancePanel);
477 m_tabs.addTab("Implementations", implementationsPanel);
478 m_tabs.addTab("Call Graph", callPanel);
479 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
480 splitRight.setResizeWeight(1); // let the left side take all the slack
481 splitRight.resetToPreferredSizes();
482 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
483 splitCenter.setResizeWeight(0); // let the right side take all the slack
484 pane.add(splitCenter, BorderLayout.CENTER);
485
486 // init menus
487 JMenuBar menuBar = new JMenuBar();
488 m_frame.setJMenuBar(menuBar);
489 {
490 JMenu menu = new JMenu("File");
491 menuBar.add(menu);
492 {
493 JMenuItem item = new JMenuItem("Open Jar...");
494 menu.add(item);
495 item.addActionListener(new ActionListener() {
496 @Override
497 public void actionPerformed(ActionEvent event) {
498 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
499 // load the jar in a separate thread
500 new Thread() {
501 @Override
502 public void run() {
503 try {
504 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
505 } catch (IOException ex) {
506 throw new Error(ex);
507 }
508 }
509 }.start();
510 }
511 }
512 });
513 }
514 {
515 JMenuItem item = new JMenuItem("Close Jar");
516 menu.add(item);
517 item.addActionListener(new ActionListener() {
518 @Override
519 public void actionPerformed(ActionEvent event) {
520 m_controller.closeJar();
521 }
522 });
523 m_closeJarMenu = item;
524 }
525 menu.addSeparator();
526 {
527 JMenuItem item = new JMenuItem("Open Mappings...");
528 menu.add(item);
529 item.addActionListener(new ActionListener() {
530 @Override
531 public void actionPerformed(ActionEvent event) {
532 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
533 try {
534 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
535 } catch (IOException ex) {
536 throw new Error(ex);
537 } catch (MappingParseException ex) {
538 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
539 }
540 }
541 }
542 });
543 m_openMappingsMenu = item;
544 }
545 {
546 JMenuItem item = new JMenuItem("Save Mappings");
547 menu.add(item);
548 item.addActionListener(new ActionListener() {
549 @Override
550 public void actionPerformed(ActionEvent event) {
551 try {
552 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
553 } catch (IOException ex) {
554 throw new Error(ex);
555 }
556 }
557 });
558 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
559 m_saveMappingsMenu = item;
560 }
561 {
562 JMenuItem item = new JMenuItem("Save Mappings As...");
563 menu.add(item);
564 item.addActionListener(new ActionListener() {
565 @Override
566 public void actionPerformed(ActionEvent event) {
567 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
568 try {
569 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
570 m_saveMappingsMenu.setEnabled(true);
571 } catch (IOException ex) {
572 throw new Error(ex);
573 }
574 }
575 }
576 });
577 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
578 m_saveMappingsAsMenu = item;
579 }
580 {
581 JMenuItem item = new JMenuItem("Close Mappings");
582 menu.add(item);
583 item.addActionListener(new ActionListener() {
584 @Override
585 public void actionPerformed(ActionEvent event) {
586 m_controller.closeMappings();
587 }
588 });
589 m_closeMappingsMenu = item;
590 }
591 menu.addSeparator();
592 {
593 JMenuItem item = new JMenuItem("Export Source...");
594 menu.add(item);
595 item.addActionListener(new ActionListener() {
596 @Override
597 public void actionPerformed(ActionEvent event) {
598 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
599 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
600 }
601 }
602 });
603 m_exportSourceMenu = item;
604 }
605 {
606 JMenuItem item = new JMenuItem("Export Jar...");
607 menu.add(item);
608 item.addActionListener(new ActionListener() {
609 @Override
610 public void actionPerformed(ActionEvent event) {
611 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
612 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
613 }
614 }
615 });
616 m_exportJarMenu = item;
617 }
618 menu.addSeparator();
619 {
620 JMenuItem item = new JMenuItem("Exit");
621 menu.add(item);
622 item.addActionListener(new ActionListener() {
623 @Override
624 public void actionPerformed(ActionEvent event) {
625 close();
626 }
627 });
628 }
629 }
630 {
631 JMenu menu = new JMenu("Help");
632 menuBar.add(menu);
633 {
634 JMenuItem item = new JMenuItem("About");
635 menu.add(item);
636 item.addActionListener(new ActionListener() {
637 @Override
638 public void actionPerformed(ActionEvent event) {
639 AboutDialog.show(m_frame);
640 }
641 });
642 }
643 }
644
645 // init state
646 onCloseJar();
647
648 m_frame.addWindowListener(new WindowAdapter() {
649 @Override
650 public void windowClosing(WindowEvent event) {
651 close();
652 }
653 });
654
655 // show the frame
656 pane.doLayout();
657 m_frame.setSize(1024, 576);
658 m_frame.setMinimumSize(new Dimension(640, 480));
659 m_frame.setVisible(true);
660 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
661 }
662
663 public JFrame getFrame() {
664 return m_frame;
665 }
666
667 public GuiController getController() {
668 return m_controller;
669 }
670
671 public void onStartOpenJar() {
672 m_classesPanel.removeAll();
673 JPanel panel = new JPanel();
674 panel.setLayout(new FlowLayout());
675 panel.add(new JLabel("Loading..."));
676 m_classesPanel.add(panel);
677 redraw();
678 }
679
680 public void onFinishOpenJar(String jarName) {
681 // update gui
682 m_frame.setTitle(Constants.Name + " - " + jarName);
683 m_classesPanel.removeAll();
684 m_classesPanel.add(m_splitClasses);
685 setSource(null);
686
687 // update menu
688 m_closeJarMenu.setEnabled(true);
689 m_openMappingsMenu.setEnabled(true);
690 m_saveMappingsMenu.setEnabled(false);
691 m_saveMappingsAsMenu.setEnabled(true);
692 m_closeMappingsMenu.setEnabled(true);
693 m_exportSourceMenu.setEnabled(true);
694 m_exportJarMenu.setEnabled(true);
695
696 redraw();
697 }
698
699 public void onCloseJar() {
700 // update gui
701 m_frame.setTitle(Constants.Name);
702 setObfClasses(null);
703 setDeobfClasses(null);
704 setSource(null);
705 m_classesPanel.removeAll();
706
707 // update menu
708 m_closeJarMenu.setEnabled(false);
709 m_openMappingsMenu.setEnabled(false);
710 m_saveMappingsMenu.setEnabled(false);
711 m_saveMappingsAsMenu.setEnabled(false);
712 m_closeMappingsMenu.setEnabled(false);
713 m_exportSourceMenu.setEnabled(false);
714 m_exportJarMenu.setEnabled(false);
715
716 redraw();
717 }
718
719 public void setObfClasses(Collection<ClassEntry> obfClasses) {
720 m_obfClasses.setClasses(obfClasses);
721 }
722
723 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
724 m_deobfClasses.setClasses(deobfClasses);
725 }
726
727 public void setMappingsFile(File file) {
728 m_mappingsFileChooser.setSelectedFile(file);
729 m_saveMappingsMenu.setEnabled(file != null);
730 }
731
732 public void setSource(String source) {
733 m_editor.getHighlighter().removeAllHighlights();
734 m_editor.setText(source);
735 }
736
737 public void showToken(final Token token) {
738 if (token == null) {
739 throw new IllegalArgumentException("Token cannot be null!");
740 }
741
742 // set the caret position to the token
743 m_editor.setCaretPosition(token.start);
744 m_editor.grabFocus();
745
746 try {
747 // make sure the token is visible in the scroll window
748 Rectangle start = m_editor.modelToView(token.start);
749 Rectangle end = m_editor.modelToView(token.end);
750 final Rectangle show = start.union(end);
751 show.grow(start.width * 10, start.height * 6);
752 SwingUtilities.invokeLater(new Runnable() {
753 @Override
754 public void run() {
755 m_editor.scrollRectToVisible(show);
756 }
757 });
758 } catch (BadLocationException ex) {
759 throw new Error(ex);
760 }
761
762 // highlight the token momentarily
763 final Timer timer = new Timer(200, new ActionListener() {
764 private int m_counter = 0;
765 private Object m_highlight = null;
766
767 @Override
768 public void actionPerformed(ActionEvent event) {
769 if (m_counter % 2 == 0) {
770 try {
771 m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter);
772 } catch (BadLocationException ex) {
773 // don't care
774 }
775 } else if (m_highlight != null) {
776 m_editor.getHighlighter().removeHighlight(m_highlight);
777 }
778
779 if (m_counter++ > 6) {
780 Timer timer = (Timer)event.getSource();
781 timer.stop();
782 }
783 }
784 });
785 timer.start();
786
787 redraw();
788 }
789
790 public void showTokens(Collection<Token> tokens) {
791 Vector<Token> sortedTokens = new Vector<Token>(tokens);
792 Collections.sort(sortedTokens);
793 if (sortedTokens.size() > 1) {
794 // sort the tokens and update the tokens panel
795 m_tokens.setListData(sortedTokens);
796 m_tokens.setSelectedIndex(0);
797 } else {
798 m_tokens.setListData(new Vector<Token>());
799 }
800
801 // show the first token
802 showToken(sortedTokens.get(0));
803 }
804
805 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
806
807 // remove any old highlighters
808 m_editor.getHighlighter().removeAllHighlights();
809
810 // color things based on the index
811 if (obfuscatedTokens != null) {
812 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
813 }
814 if (deobfuscatedTokens != null) {
815 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
816 }
817 if (otherTokens != null) {
818 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
819 }
820
821 redraw();
822 }
823
824 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
825 for (Token token : tokens) {
826 try {
827 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
828 } catch (BadLocationException ex) {
829 throw new IllegalArgumentException(ex);
830 }
831 }
832 }
833
834 private void clearReference() {
835 m_infoPanel.removeAll();
836 JLabel label = new JLabel("No identifier selected");
837 GuiTricks.unboldLabel(label);
838 label.setHorizontalAlignment(JLabel.CENTER);
839 m_infoPanel.add(label);
840
841 redraw();
842 }
843
844 private void showReference(EntryReference<Entry,Entry> reference) {
845 if (reference == null) {
846 clearReference();
847 return;
848 }
849
850 m_reference = reference;
851
852 m_infoPanel.removeAll();
853 if (reference.entry instanceof ClassEntry) {
854 showClassEntry((ClassEntry)m_reference.entry);
855 } else if (m_reference.entry instanceof FieldEntry) {
856 showFieldEntry((FieldEntry)m_reference.entry);
857 } else if (m_reference.entry instanceof MethodEntry) {
858 showMethodEntry((MethodEntry)m_reference.entry);
859 } else if (m_reference.entry instanceof ConstructorEntry) {
860 showConstructorEntry((ConstructorEntry)m_reference.entry);
861 } else if (m_reference.entry instanceof ArgumentEntry) {
862 showArgumentEntry((ArgumentEntry)m_reference.entry);
863 } else {
864 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
865 }
866
867 redraw();
868 }
869
870 private void showClassEntry(ClassEntry entry) {
871 addNameValue(m_infoPanel, "Class", entry.getName());
872 }
873
874 private void showFieldEntry(FieldEntry entry) {
875 addNameValue(m_infoPanel, "Field", entry.getName());
876 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
877 }
878
879 private void showMethodEntry(MethodEntry entry) {
880 addNameValue(m_infoPanel, "Method", entry.getName());
881 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
882 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
883 }
884
885 private void showConstructorEntry(ConstructorEntry entry) {
886 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
887 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
888 }
889
890 private void showArgumentEntry(ArgumentEntry entry) {
891 addNameValue(m_infoPanel, "Argument", entry.getName());
892 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
893 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
894 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
895 }
896
897 private void addNameValue(JPanel container, String name, String value) {
898 JPanel panel = new JPanel();
899 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
900 container.add(panel);
901
902 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
903 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
904 panel.add(label);
905
906 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
907 }
908
909 private void onCaretMove(int pos) {
910
911 Token token = m_controller.getToken(pos);
912 boolean isToken = token != null;
913
914 m_reference = m_controller.getDeobfReference(token);
915 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
916 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
917 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
918 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
919 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
920 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
921
922 if (isToken) {
923 showReference(m_reference);
924 } else {
925 clearReference();
926 }
927
928 m_renameMenu.setEnabled(isRenameable && isToken);
929 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
930 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
931 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
932 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
933 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
934 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
935
936 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
937 m_toggleMappingMenu.setText("Reset to obfuscated");
938 } else {
939 m_toggleMappingMenu.setText("Mark as deobfuscated");
940 }
941 }
942
943 private void navigateTo(Entry entry) {
944 if (!m_controller.entryIsInJar(entry)) {
945 // entry is not in the jar. Ignore it
946 return;
947 }
948 if (m_reference != null) {
949 m_controller.savePreviousReference(m_reference);
950 }
951 m_controller.openDeclaration(entry);
952 }
953
954 private void navigateTo(EntryReference<Entry,Entry> reference) {
955 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
956 // reference is not in the jar. Ignore it
957 return;
958 }
959 if (m_reference != null) {
960 m_controller.savePreviousReference(m_reference);
961 }
962 m_controller.openReference(reference);
963 }
964
965 private void startRename() {
966
967 // init the text box
968 final JTextField text = new JTextField();
969 text.setText(m_reference.getNamableName());
970 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
971 text.addKeyListener(new KeyAdapter() {
972 @Override
973 public void keyPressed(KeyEvent event) {
974 switch (event.getKeyCode()) {
975 case KeyEvent.VK_ENTER:
976 finishRename(text, true);
977 break;
978
979 case KeyEvent.VK_ESCAPE:
980 finishRename(text, false);
981 break;
982 }
983 }
984 });
985
986 // find the label with the name and replace it with the text box
987 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
988 panel.remove(panel.getComponentCount() - 1);
989 panel.add(text);
990 text.grabFocus();
991 text.selectAll();
992
993 redraw();
994 }
995
996 private void finishRename(JTextField text, boolean saveName) {
997 String newName = text.getText();
998 if (saveName && newName != null && newName.length() > 0) {
999 try {
1000 m_controller.rename(m_reference, newName);
1001 } catch (IllegalNameException ex) {
1002 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
1003 text.setToolTipText(ex.getReason());
1004 GuiTricks.showToolTipNow(text);
1005 }
1006 return;
1007 }
1008
1009 // abort the rename
1010 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
1011 panel.remove(panel.getComponentCount() - 1);
1012 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
1013
1014 m_editor.grabFocus();
1015
1016 redraw();
1017 }
1018
1019 private void showInheritance() {
1020
1021 if (m_reference == null) {
1022 return;
1023 }
1024
1025 m_inheritanceTree.setModel(null);
1026
1027 if (m_reference.entry instanceof ClassEntry) {
1028 // get the class inheritance
1029 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
1030
1031 // show the tree at the root
1032 TreePath path = getPathToRoot(classNode);
1033 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1034 m_inheritanceTree.expandPath(path);
1035 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1036 } else if (m_reference.entry instanceof MethodEntry) {
1037 // get the method inheritance
1038 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
1039
1040 // show the tree at the root
1041 TreePath path = getPathToRoot(classNode);
1042 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1043 m_inheritanceTree.expandPath(path);
1044 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1045 }
1046
1047 m_tabs.setSelectedIndex(0);
1048 redraw();
1049 }
1050
1051 private void showImplementations() {
1052
1053 if (m_reference == null) {
1054 return;
1055 }
1056
1057 m_implementationsTree.setModel(null);
1058
1059 if (m_reference.entry instanceof ClassEntry) {
1060 // get the class implementations
1061 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1062 if (node != null) {
1063 // show the tree at the root
1064 TreePath path = getPathToRoot(node);
1065 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1066 m_implementationsTree.expandPath(path);
1067 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1068 }
1069 } else if (m_reference.entry instanceof MethodEntry) {
1070 // get the method implementations
1071 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1072 if (node != null) {
1073 // show the tree at the root
1074 TreePath path = getPathToRoot(node);
1075 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1076 m_implementationsTree.expandPath(path);
1077 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1078 }
1079 }
1080
1081 m_tabs.setSelectedIndex(1);
1082 redraw();
1083 }
1084
1085 private void showCalls() {
1086
1087 if (m_reference == null) {
1088 return;
1089 }
1090
1091 if (m_reference.entry instanceof ClassEntry) {
1092 // look for calls to the default constructor
1093 // TODO: get a list of all the constructors and find calls to all of them
1094 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1095 m_callsTree.setModel(new DefaultTreeModel(node));
1096 } else if (m_reference.entry instanceof FieldEntry) {
1097 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1098 m_callsTree.setModel(new DefaultTreeModel(node));
1099 } else if (m_reference.entry instanceof MethodEntry) {
1100 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1101 m_callsTree.setModel(new DefaultTreeModel(node));
1102 } else if (m_reference.entry instanceof ConstructorEntry) {
1103 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1104 m_callsTree.setModel(new DefaultTreeModel(node));
1105 }
1106
1107 m_tabs.setSelectedIndex(2);
1108 redraw();
1109 }
1110
1111 private void toggleMapping() {
1112 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1113 m_controller.removeMapping(m_reference);
1114 } else {
1115 m_controller.markAsDeobfuscated(m_reference);
1116 }
1117 }
1118
1119 private TreePath getPathToRoot(TreeNode node) {
1120 List<TreeNode> nodes = Lists.newArrayList();
1121 TreeNode n = node;
1122 do {
1123 nodes.add(n);
1124 n = n.getParent();
1125 } while (n != null);
1126 Collections.reverse(nodes);
1127 return new TreePath(nodes.toArray());
1128 }
1129
1130 private void close() {
1131 if (!m_controller.isDirty()) {
1132 // everything is saved, we can exit safely
1133 m_frame.dispose();
1134 } else {
1135 // ask to save before closing
1136 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1137 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1138 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1139 switch (response) {
1140 case JOptionPane.YES_OPTION: // save and exit
1141 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1142 try {
1143 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1144 m_frame.dispose();
1145 } catch (IOException ex) {
1146 throw new Error(ex);
1147 }
1148 }
1149 break;
1150
1151 case JOptionPane.NO_OPTION:
1152 // don't save, exit
1153 m_frame.dispose();
1154 break;
1155
1156 // cancel means do nothing
1157 }
1158 }
1159 }
1160
1161 private void redraw() {
1162 m_frame.validate();
1163 m_frame.repaint();
1164 }
1165}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 0000000..61fea9c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,355 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.Deque;
19import java.util.List;
20import java.util.jar.JarFile;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Queues;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25
26import cuchaz.enigma.Deobfuscator;
27import cuchaz.enigma.Deobfuscator.ProgressListener;
28import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
29import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
30import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
31import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.analysis.FieldReferenceTreeNode;
33import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
34import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.FieldEntry;
42import cuchaz.enigma.mapping.MappingParseException;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.TranslationDirection;
47
48public class GuiController {
49
50 private Deobfuscator m_deobfuscator;
51 private Gui m_gui;
52 private SourceIndex m_index;
53 private ClassEntry m_currentObfClass;
54 private boolean m_isDirty;
55 private Deque<EntryReference<Entry,Entry>> m_referenceStack;
56
57 public GuiController(Gui gui) {
58 m_gui = gui;
59 m_deobfuscator = null;
60 m_index = null;
61 m_currentObfClass = null;
62 m_isDirty = false;
63 m_referenceStack = Queues.newArrayDeque();
64 }
65
66 public boolean isDirty() {
67 return m_isDirty;
68 }
69
70 public void openJar(final JarFile jar) throws IOException {
71 m_gui.onStartOpenJar();
72 m_deobfuscator = new Deobfuscator(jar);
73 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
74 refreshClasses();
75 }
76
77 public void closeJar() {
78 m_deobfuscator = null;
79 m_gui.onCloseJar();
80 }
81
82 public void openMappings(File file) throws IOException, MappingParseException {
83 FileReader in = new FileReader(file);
84 m_deobfuscator.setMappings(new MappingsReader().read(in));
85 in.close();
86 m_isDirty = false;
87 m_gui.setMappingsFile(file);
88 refreshClasses();
89 refreshCurrentClass();
90 }
91
92 public void saveMappings(File file) throws IOException {
93 FileWriter out = new FileWriter(file);
94 new MappingsWriter().write(out, m_deobfuscator.getMappings());
95 out.close();
96 m_isDirty = false;
97 }
98
99 public void closeMappings() {
100 m_deobfuscator.setMappings(null);
101 m_gui.setMappingsFile(null);
102 refreshClasses();
103 refreshCurrentClass();
104 }
105
106 public void exportSource(final File dirOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) throws Exception {
110 m_deobfuscator.writeSources(dirOut, progress);
111 }
112 });
113 }
114
115 public void exportJar(final File fileOut) {
116 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
117 @Override
118 public void run(ProgressListener progress) {
119 m_deobfuscator.writeJar(fileOut, progress);
120 }
121 });
122 }
123
124 public Token getToken(int pos) {
125 if (m_index == null) {
126 return null;
127 }
128 return m_index.getReferenceToken(pos);
129 }
130
131 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
132 if (m_index == null) {
133 return null;
134 }
135 return m_index.getDeobfReference(token);
136 }
137
138 public ReadableToken getReadableToken(Token token) {
139 if (m_index == null) {
140 return null;
141 }
142 return new ReadableToken(
143 m_index.getLineNumber(token.start),
144 m_index.getColumnNumber(token.start),
145 m_index.getColumnNumber(token.end)
146 );
147 }
148
149 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
150 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
151 }
152
153 public boolean entryIsInJar(Entry deobfEntry) {
154 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
155 }
156
157 public boolean referenceIsRenameable(EntryReference<Entry,Entry> deobfReference) {
158 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
159 }
160
161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
168 }
169
170 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
171 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
172 return m_deobfuscator.getJarIndex().getClassImplementations(
173 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
174 obfClassEntry
175 );
176 }
177
178 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
185 }
186
187 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
188 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
189 MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNode == null) {
194 return null;
195 }
196 return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry);
197 }
198
199 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
200 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
201 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
202 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
203 obfFieldEntry
204 );
205 rootNode.load(m_deobfuscator.getJarIndex(), true);
206 return rootNode;
207 }
208
209 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
210 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
211 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
212 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
213 obfBehaviorEntry
214 );
215 rootNode.load(m_deobfuscator.getJarIndex(), true);
216 return rootNode;
217 }
218
219 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
220 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
221 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
222 m_isDirty = true;
223 refreshClasses();
224 refreshCurrentClass(obfReference);
225 }
226
227 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
228 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
229 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
230 m_isDirty = true;
231 refreshClasses();
232 refreshCurrentClass(obfReference);
233 }
234
235 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
236 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
237 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
238 m_isDirty = true;
239 refreshClasses();
240 refreshCurrentClass(obfReference);
241 }
242
243 public void openDeclaration(Entry deobfEntry) {
244 if (deobfEntry == null) {
245 throw new IllegalArgumentException("Entry cannot be null!");
246 }
247 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
248 }
249
250 public void openReference(EntryReference<Entry,Entry> deobfReference) {
251 if (deobfReference == null) {
252 throw new IllegalArgumentException("Reference cannot be null!");
253 }
254
255 // get the reference target class
256 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
257 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry();
258 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
259 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
260 }
261 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
262 // deobfuscate the class, then navigate to the reference
263 m_currentObfClass = obfClassEntry;
264 deobfuscate(m_currentObfClass, obfReference);
265 } else {
266 showReference(obfReference);
267 }
268 }
269
270 private void showReference(EntryReference<Entry,Entry> obfReference) {
271 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
272 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
273 if (tokens.isEmpty()) {
274 // DEBUG
275 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
276 } else {
277 m_gui.showTokens(tokens);
278 }
279 }
280
281 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
282 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
283 }
284
285 public void openPreviousReference() {
286 if (hasPreviousLocation()) {
287 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
288 }
289 }
290
291 public boolean hasPreviousLocation() {
292 return !m_referenceStack.isEmpty();
293 }
294
295 private void refreshClasses() {
296 List<ClassEntry> obfClasses = Lists.newArrayList();
297 List<ClassEntry> deobfClasses = Lists.newArrayList();
298 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
299 m_gui.setObfClasses(obfClasses);
300 m_gui.setDeobfClasses(deobfClasses);
301 }
302
303 private void refreshCurrentClass() {
304 refreshCurrentClass(null);
305 }
306
307 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
308 if (m_currentObfClass != null) {
309 deobfuscate(m_currentObfClass, obfReference);
310 }
311 }
312
313 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
314
315 m_gui.setSource("(deobfuscating...)");
316
317 // run the deobfuscator in a separate thread so we don't block the GUI event queue
318 new Thread() {
319 @Override
320 public void run() {
321 // decompile,deobfuscate the bytecode
322 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
323 if (sourceTree == null) {
324 // decompilation of this class is not supported
325 m_gui.setSource("Unable to find class: " + classEntry);
326 return;
327 }
328 String source = m_deobfuscator.getSource(sourceTree);
329 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
330 m_gui.setSource(m_index.getSource());
331 if (obfReference != null) {
332 showReference(obfReference);
333 }
334
335 // set the highlighted tokens
336 List<Token> obfuscatedTokens = Lists.newArrayList();
337 List<Token> deobfuscatedTokens = Lists.newArrayList();
338 List<Token> otherTokens = Lists.newArrayList();
339 for (Token token : m_index.referenceTokens()) {
340 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
341 if (referenceIsRenameable(reference)) {
342 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
343 deobfuscatedTokens.add(token);
344 } else {
345 obfuscatedTokens.add(token);
346 }
347 } else {
348 otherTokens.add(token);
349 }
350 }
351 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
352 }
353 }.start();
354 }
355}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 0000000..df9e221
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.MouseEvent;
15
16import javax.swing.JComponent;
17import javax.swing.JLabel;
18import javax.swing.ToolTipManager;
19
20public class GuiTricks {
21
22 public static JLabel unboldLabel(JLabel label) {
23 Font font = label.getFont();
24 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
25 return label;
26 }
27
28 public static void showToolTipNow(JComponent component) {
29 // HACKHACK: trick the tooltip manager into showing the tooltip right now
30 ToolTipManager manager = ToolTipManager.sharedInstance();
31 int oldDelay = manager.getInitialDelay();
32 manager.setInitialDelay(0);
33 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
34 manager.setInitialDelay(oldDelay);
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 0000000..177835f
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public ObfuscatedHighlightPainter() {
18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
new file mode 100644
index 0000000..4e9c870
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class OtherHighlightPainter extends BoxHighlightPainter {
16
17 public OtherHighlightPainter() {
18 // grey
19 super(null, new Color(180, 180, 180));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 0000000..b864fdb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.BorderFactory;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JProgressBar;
23import javax.swing.WindowConstants;
24
25import cuchaz.enigma.Constants;
26import cuchaz.enigma.Deobfuscator.ProgressListener;
27
28public class ProgressDialog implements ProgressListener, AutoCloseable {
29
30 private JFrame m_frame;
31 private JLabel m_title;
32 private JLabel m_text;
33 private JProgressBar m_progress;
34
35 public ProgressDialog(JFrame parent) {
36
37 // init frame
38 m_frame = new JFrame(Constants.Name + " - Operation in progress");
39 final Container pane = m_frame.getContentPane();
40 FlowLayout layout = new FlowLayout();
41 layout.setAlignment(FlowLayout.LEFT);
42 pane.setLayout(layout);
43
44 m_title = new JLabel();
45 pane.add(m_title);
46
47 // set up the progress bar
48 JPanel panel = new JPanel();
49 pane.add(panel);
50 panel.setLayout(new BorderLayout());
51 m_text = GuiTricks.unboldLabel(new JLabel());
52 m_progress = new JProgressBar();
53 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
54 panel.add(m_text, BorderLayout.NORTH);
55 panel.add(m_progress, BorderLayout.CENTER);
56 panel.setPreferredSize(new Dimension(360, 50));
57
58 // show the frame
59 pane.doLayout();
60 m_frame.setSize(400, 120);
61 m_frame.setResizable(false);
62 m_frame.setLocationRelativeTo(parent);
63 m_frame.setVisible(true);
64 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
65 }
66
67 public void close() {
68 m_frame.dispose();
69 }
70
71 @Override
72 public void init(int totalWork, String title) {
73 m_title.setText(title);
74 m_progress.setMinimum(0);
75 m_progress.setMaximum(totalWork);
76 m_progress.setValue(0);
77 }
78
79 @Override
80 public void onProgress(int numDone, String message) {
81 m_text.setText(message);
82 m_progress.setValue(numDone);
83
84 // update the frame
85 m_frame.validate();
86 m_frame.repaint();
87 }
88
89 public static interface ProgressRunnable {
90 void run(ProgressListener listener) throws Exception;
91 }
92
93 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
94 new Thread() {
95 @Override
96 public void run() {
97 try (ProgressDialog progress = new ProgressDialog(parent)) {
98 runnable.run(progress);
99 } catch (Exception ex) {
100 throw new Error(ex);
101 }
102 }
103 }.start();
104 }
105}
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 0000000..66bcbc2
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13public class ReadableToken {
14
15 public int line;
16 public int startColumn;
17 public int endColumn;
18
19 public ReadableToken(int line, int startColumn, int endColumn) {
20 this.line = line;
21 this.startColumn = startColumn;
22 this.endColumn = endColumn;
23 }
24
25 @Override
26 public String toString() {
27 StringBuilder buf = new StringBuilder();
28 buf.append("line ");
29 buf.append(line);
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 }
36}
diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java
new file mode 100644
index 0000000..abeda0c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface RenameListener {
16 void rename(Entry obfEntry, String newName);
17}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 0000000..5e189d2
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BasicStroke;
14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Graphics2D;
17import java.awt.Rectangle;
18import java.awt.Shape;
19
20import javax.swing.text.Highlighter;
21import javax.swing.text.JTextComponent;
22
23public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
24
25 @Override
26 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
27 // draw a thick border
28 Graphics2D g2d = (Graphics2D)g;
29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
30 g2d.setColor(Color.black);
31 g2d.setStroke(new BasicStroke(2.0f));
32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
33 }
34}
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
new file mode 100644
index 0000000..a49be37
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import cuchaz.enigma.analysis.Token;
21
22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23
24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer;
26
27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer();
30 }
31
32 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(m_controller.getReadableToken(token).toString());
36 return label;
37 }
38}