summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar Michael Smith2015-05-21 23:30:00 +0100
committerGravatar Michael Smith2015-05-21 23:30:00 +0100
commite3f452250e51b7271f3989c7dfd12e4422934942 (patch)
tree5aa482f9a6e21eb318a3e23e7d8274d77c73faf6 /src/cuchaz/enigma/gui
downloadenigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.gz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.xz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.zip
Support Gradle alongside SSJB
This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :)
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/ClassMatchingGui.java589
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java293
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/cuchaz/enigma/gui/CodeReader.java222
-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.java1122
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java358
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/cuchaz/enigma/gui/MemberMatchingGui.java499
-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/ScoredClassEntry.java30
-rw-r--r--src/cuchaz/enigma/gui/SelectionHighlightPainter.java34
-rw-r--r--src/cuchaz/enigma/gui/TokenListCellRenderer.java38
23 files changed, 3889 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..3eba1e5
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
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 *
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..e5e0557
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
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 *
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..6af4d24
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
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 *
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..cde3e4c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
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 *
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/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 0000000..89b19c3
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,589 @@
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 *
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;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.util.Collection;
20import java.util.List;
21import java.util.Map;
22
23import javax.swing.BoxLayout;
24import javax.swing.ButtonGroup;
25import javax.swing.JButton;
26import javax.swing.JCheckBox;
27import javax.swing.JFrame;
28import javax.swing.JLabel;
29import javax.swing.JPanel;
30import javax.swing.JRadioButton;
31import javax.swing.JScrollPane;
32import javax.swing.JSplitPane;
33import javax.swing.SwingConstants;
34import javax.swing.WindowConstants;
35
36import com.google.common.collect.BiMap;
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.convert.ClassIdentifier;
43import cuchaz.enigma.convert.ClassIdentity;
44import cuchaz.enigma.convert.ClassMatch;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.ClassMatching;
47import cuchaz.enigma.convert.ClassNamer;
48import cuchaz.enigma.convert.MappingsConverter;
49import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.Mappings;
52import cuchaz.enigma.mapping.MappingsChecker;
53import de.sciss.syntaxpane.DefaultSyntaxKit;
54
55
56public class ClassMatchingGui {
57
58 private static enum SourceType {
59 Matched {
60
61 @Override
62 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
63 return matches.getUniqueMatches().keySet();
64 }
65 },
66 Unmatched {
67
68 @Override
69 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
70 return matches.getUnmatchedSourceClasses();
71 }
72 },
73 Ambiguous {
74
75 @Override
76 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
77 return matches.getAmbiguouslyMatchedSourceClasses();
78 }
79 };
80
81 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
82 JRadioButton button = new JRadioButton(name(), this == getDefault());
83 button.setActionCommand(name());
84 button.addActionListener(listener);
85 group.add(button);
86 return button;
87 }
88
89 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
90
91 public static SourceType getDefault() {
92 return values()[0];
93 }
94 }
95
96 public static interface SaveListener {
97 public void save(ClassMatches matches);
98 }
99
100 // controls
101 private JFrame m_frame;
102 private ClassSelector m_sourceClasses;
103 private ClassSelector m_destClasses;
104 private CodeReader m_sourceReader;
105 private CodeReader m_destReader;
106 private JLabel m_sourceClassLabel;
107 private JLabel m_destClassLabel;
108 private JButton m_matchButton;
109 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
110 private JCheckBox m_advanceCheck;
111
112 private ClassMatches m_classMatches;
113 private Deobfuscator m_sourceDeobfuscator;
114 private Deobfuscator m_destDeobfuscator;
115 private ClassEntry m_sourceClass;
116 private ClassEntry m_destClass;
117 private SourceType m_sourceType;
118 private SaveListener m_saveListener;
119
120 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
121
122 m_classMatches = matches;
123 m_sourceDeobfuscator = sourceDeobfuscator;
124 m_destDeobfuscator = destDeobfuscator;
125
126 // init frame
127 m_frame = new JFrame(Constants.Name + " - Class Matcher");
128 final Container pane = m_frame.getContentPane();
129 pane.setLayout(new BorderLayout());
130
131 // init source side
132 JPanel sourcePanel = new JPanel();
133 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
134 sourcePanel.setPreferredSize(new Dimension(200, 0));
135 pane.add(sourcePanel, BorderLayout.WEST);
136 sourcePanel.add(new JLabel("Source Classes"));
137
138 // init source type radios
139 JPanel sourceTypePanel = new JPanel();
140 sourcePanel.add(sourceTypePanel);
141 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
142 ActionListener sourceTypeListener = new ActionListener() {
143 @Override
144 public void actionPerformed(ActionEvent event) {
145 setSourceType(SourceType.valueOf(event.getActionCommand()));
146 }
147 };
148 ButtonGroup sourceTypeButtons = new ButtonGroup();
149 m_sourceTypeButtons = Maps.newHashMap();
150 for (SourceType sourceType : SourceType.values()) {
151 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
152 m_sourceTypeButtons.put(sourceType, button);
153 sourceTypePanel.add(button);
154 }
155
156 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
157 m_sourceClasses.setListener(new ClassSelectionListener() {
158 @Override
159 public void onSelectClass(ClassEntry classEntry) {
160 setSourceClass(classEntry);
161 }
162 });
163 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
164 sourcePanel.add(sourceScroller);
165
166 // init dest side
167 JPanel destPanel = new JPanel();
168 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
169 destPanel.setPreferredSize(new Dimension(200, 0));
170 pane.add(destPanel, BorderLayout.WEST);
171 destPanel.add(new JLabel("Destination Classes"));
172
173 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
174 m_destClasses.setListener(new ClassSelectionListener() {
175 @Override
176 public void onSelectClass(ClassEntry classEntry) {
177 setDestClass(classEntry);
178 }
179 });
180 JScrollPane destScroller = new JScrollPane(m_destClasses);
181 destPanel.add(destScroller);
182
183 JButton autoMatchButton = new JButton("AutoMatch");
184 autoMatchButton.addActionListener(new ActionListener() {
185 @Override
186 public void actionPerformed(ActionEvent event) {
187 autoMatch();
188 }
189 });
190 destPanel.add(autoMatchButton);
191
192 // init source panels
193 DefaultSyntaxKit.initKit();
194 m_sourceReader = new CodeReader();
195 m_destReader = new CodeReader();
196
197 // init all the splits
198 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
199 splitLeft.setResizeWeight(0); // let the right side take all the slack
200 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
201 splitRight.setResizeWeight(1); // let the left side take all the slack
202 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
203 splitCenter.setResizeWeight(0.5); // resize 50:50
204 pane.add(splitCenter, BorderLayout.CENTER);
205 splitCenter.resetToPreferredSizes();
206
207 // init bottom panel
208 JPanel bottomPanel = new JPanel();
209 bottomPanel.setLayout(new FlowLayout());
210
211 m_sourceClassLabel = new JLabel();
212 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
213 m_destClassLabel = new JLabel();
214 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
215
216 m_matchButton = new JButton();
217
218 m_advanceCheck = new JCheckBox("Advance to next likely match");
219 m_advanceCheck.addActionListener(new ActionListener() {
220 @Override
221 public void actionPerformed(ActionEvent event) {
222 if (m_advanceCheck.isSelected()) {
223 advance();
224 }
225 }
226 });
227
228 bottomPanel.add(m_sourceClassLabel);
229 bottomPanel.add(m_matchButton);
230 bottomPanel.add(m_destClassLabel);
231 bottomPanel.add(m_advanceCheck);
232 pane.add(bottomPanel, BorderLayout.SOUTH);
233
234 // show the frame
235 pane.doLayout();
236 m_frame.setSize(1024, 576);
237 m_frame.setMinimumSize(new Dimension(640, 480));
238 m_frame.setVisible(true);
239 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
240
241 // init state
242 updateDestMappings();
243 setSourceType(SourceType.getDefault());
244 updateMatchButton();
245 m_saveListener = null;
246 }
247
248 public void setSaveListener(SaveListener val) {
249 m_saveListener = val;
250 }
251
252 private void updateDestMappings() {
253
254 Mappings newMappings = MappingsConverter.newMappings(
255 m_classMatches,
256 m_sourceDeobfuscator.getMappings(),
257 m_sourceDeobfuscator,
258 m_destDeobfuscator
259 );
260
261 // look for dropped mappings
262 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
263 checker.dropBrokenMappings(newMappings);
264
265 // count them
266 int numDroppedFields = checker.getDroppedFieldMappings().size();
267 int numDroppedMethods = checker.getDroppedMethodMappings().size();
268 System.out.println(String.format(
269 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
270 numDroppedFields + numDroppedMethods,
271 numDroppedFields,
272 numDroppedMethods
273 ));
274
275 m_destDeobfuscator.setMappings(newMappings);
276 }
277
278 protected void setSourceType(SourceType val) {
279
280 // show the source classes
281 m_sourceType = val;
282 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
283
284 // update counts
285 for (SourceType sourceType : SourceType.values()) {
286 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
287 sourceType.name(),
288 sourceType.getSourceClasses(m_classMatches).size()
289 ));
290 }
291 }
292
293 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
294 List<ClassEntry> out = Lists.newArrayList();
295 for (ClassEntry entry : in) {
296
297 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
298
299 // make sure we preserve any scores
300 if (entry instanceof ScoredClassEntry) {
301 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
302 }
303
304 out.add(deobf);
305 }
306 return out;
307 }
308
309 protected void setSourceClass(ClassEntry classEntry) {
310
311 Runnable onGetDestClasses = null;
312 if (m_advanceCheck.isSelected()) {
313 onGetDestClasses = new Runnable() {
314 @Override
315 public void run() {
316 pickBestDestClass();
317 }
318 };
319 }
320
321 setSourceClass(classEntry, onGetDestClasses);
322 }
323
324 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
325
326 // update the current source class
327 m_sourceClass = classEntry;
328 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
329
330 if (m_sourceClass != null) {
331
332 // show the dest class(es)
333 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
334 assert(match != null);
335 if (match.destClasses.isEmpty()) {
336
337 m_destClasses.setClasses(null);
338
339 // run in a separate thread to keep ui responsive
340 new Thread() {
341 @Override
342 public void run() {
343 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
344 m_destClasses.expandAll();
345
346 if (onGetDestClasses != null) {
347 onGetDestClasses.run();
348 }
349 }
350 }.start();
351
352 } else {
353
354 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
355 m_destClasses.expandAll();
356
357 if (onGetDestClasses != null) {
358 onGetDestClasses.run();
359 }
360 }
361 }
362
363 setDestClass(null);
364 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() {
365 @Override
366 public void run() {
367 m_sourceReader.navigateToClassDeclaration(m_sourceClass);
368 }
369 });
370
371 updateMatchButton();
372 }
373
374 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
375
376 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
377
378 // set up identifiers
379 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
380 ClassIdentifier sourceIdentifier = new ClassIdentifier(
381 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
382 namer.getSourceNamer(), true
383 );
384 ClassIdentifier destIdentifier = new ClassIdentifier(
385 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
386 namer.getDestNamer(), true
387 );
388
389 try {
390
391 // rank all the unmatched dest classes against the source class
392 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
393 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
394 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
395 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
396 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
397 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
398 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
399 }
400 return scoredDestClasses;
401
402 } catch (ClassNotFoundException ex) {
403 throw new Error("Unable to find class " + ex.getMessage());
404 }
405 }
406
407 protected void setDestClass(ClassEntry classEntry) {
408
409 // update the current source class
410 m_destClass = classEntry;
411 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
412
413 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() {
414 @Override
415 public void run() {
416 m_destReader.navigateToClassDeclaration(m_destClass);
417 }
418 });
419
420 updateMatchButton();
421 }
422
423 private void updateMatchButton() {
424
425 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
426 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
427
428 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
429 boolean twoSelected = m_sourceClass != null && m_destClass != null;
430 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
431 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
432
433 GuiTricks.deactivateButton(m_matchButton);
434 if (twoSelected) {
435 if (isMatched) {
436 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
437 @Override
438 public void actionPerformed(ActionEvent event) {
439 onUnmatchClick();
440 }
441 });
442 } else if (canMatch) {
443 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 onMatchClick();
447 }
448 });
449 }
450 }
451 }
452
453 private void onMatchClick() {
454 // precondition: source and dest classes are set correctly
455
456 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
457 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
458
459 // remove the classes from their match
460 m_classMatches.removeSource(obfSource);
461 m_classMatches.removeDest(obfDest);
462
463 // add them as matched classes
464 m_classMatches.add(new ClassMatch(obfSource, obfDest));
465
466 ClassEntry nextClass = null;
467 if (m_advanceCheck.isSelected()) {
468 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
469 }
470
471 save();
472 updateMatches();
473
474 if (nextClass != null) {
475 advance(nextClass);
476 }
477 }
478
479 private void onUnmatchClick() {
480 // precondition: source and dest classes are set to a unique match
481
482 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
483
484 // remove the source to break the match, then add the source back as unmatched
485 m_classMatches.removeSource(obfSource);
486 m_classMatches.add(new ClassMatch(obfSource, null));
487
488 save();
489 updateMatches();
490 }
491
492 private void updateMatches() {
493 updateDestMappings();
494 setDestClass(null);
495 m_destClasses.setClasses(null);
496 updateMatchButton();
497
498 // remember where we were in the source tree
499 String packageName = m_sourceClasses.getSelectedPackage();
500
501 setSourceType(m_sourceType);
502
503 m_sourceClasses.expandPackage(packageName);
504 }
505
506 private void save() {
507 if (m_saveListener != null) {
508 m_saveListener.save(m_classMatches);
509 }
510 }
511
512 private void autoMatch() {
513
514 System.out.println("Automatching...");
515
516 // compute a new matching
517 ClassMatching matching = MappingsConverter.computeMatching(
518 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
519 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
520 m_classMatches.getUniqueMatches()
521 );
522 ClassMatches newMatches = new ClassMatches(matching.matches());
523 System.out.println(String.format("Automatch found %d new matches",
524 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
525 ));
526
527 // update the current matches
528 m_classMatches = newMatches;
529 save();
530 updateMatches();
531 }
532
533 private void advance() {
534 advance(null);
535 }
536
537 private void advance(ClassEntry sourceClass) {
538
539 // make sure we have a source class
540 if (sourceClass == null) {
541 sourceClass = m_sourceClasses.getSelectedClass();
542 if (sourceClass != null) {
543 sourceClass = m_sourceClasses.getNextClass(sourceClass);
544 } else {
545 sourceClass = m_sourceClasses.getFirstClass();
546 }
547 }
548
549 // set the source class
550 setSourceClass(sourceClass, new Runnable() {
551 @Override
552 public void run() {
553 pickBestDestClass();
554 }
555 });
556 m_sourceClasses.setSelectionClass(sourceClass);
557 }
558
559 private void pickBestDestClass() {
560
561 // then, pick the best dest class
562 ClassEntry firstClass = null;
563 ScoredClassEntry bestDestClass = null;
564 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
565 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
566 if (firstClass == null) {
567 firstClass = classNode.getClassEntry();
568 }
569 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
570 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
571 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
572 bestDestClass = scoredClass;
573 }
574 }
575 }
576 }
577
578 // pick the entry to show
579 ClassEntry destClass = null;
580 if (bestDestClass != null) {
581 destClass = bestDestClass;
582 } else if (firstClass != null) {
583 destClass = firstClass;
584 }
585
586 setDestClass(destClass);
587 m_destClasses.setSelectionClass(destClass);
588 }
589}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 0000000..11333a9
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,293 @@
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 *
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.Enumeration;
19import java.util.List;
20import java.util.Map;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import com.google.common.collect.ArrayListMultimap;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.google.common.collect.Multimap;
31
32import cuchaz.enigma.mapping.ClassEntry;
33
34public class ClassSelector extends JTree {
35
36 private static final long serialVersionUID = -7632046902384775977L;
37
38 public interface ClassSelectionListener {
39 void onSelectClass(ClassEntry classEntry);
40 }
41
42 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
43 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
44
45 static {
46 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
47 @Override
48 public int compare(ClassEntry a, ClassEntry b) {
49 String aname = a.getName();
50 String bname = a.getName();
51 if (aname.length() != bname.length()) {
52 return aname.length() - bname.length();
53 }
54 return aname.compareTo(bname);
55 }
56 };
57
58 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
59 @Override
60 public int compare(ClassEntry a, ClassEntry b) {
61 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
62 return Float.compare(
63 ((ScoredClassEntry)b).getScore(),
64 ((ScoredClassEntry)a).getScore()
65 );
66 }
67 return a.getName().compareTo(b.getName());
68 }
69 };
70 }
71
72 private ClassSelectionListener m_listener;
73 private Comparator<ClassEntry> m_comparator;
74
75 public ClassSelector(Comparator<ClassEntry> comparator) {
76 m_comparator = comparator;
77
78 // configure the tree control
79 setRootVisible(false);
80 setShowsRootHandles(false);
81 setModel(null);
82
83 // hook events
84 addMouseListener(new MouseAdapter() {
85 @Override
86 public void mouseClicked(MouseEvent event) {
87 if (m_listener != null && event.getClickCount() == 2) {
88 // get the selected node
89 TreePath path = getSelectionPath();
90 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
91 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
92 m_listener.onSelectClass(node.getClassEntry());
93 }
94 }
95 }
96 });
97
98 // init defaults
99 m_listener = null;
100 }
101
102 public void setListener(ClassSelectionListener val) {
103 m_listener = val;
104 }
105
106 public void setClasses(Collection<ClassEntry> classEntries) {
107 if (classEntries == null) {
108 setModel(null);
109 return;
110 }
111
112 // build the package names
113 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
114 for (ClassEntry classEntry : classEntries) {
115 packages.put(classEntry.getPackageName(), null);
116 }
117
118 // sort the packages
119 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
120 Collections.sort(sortedPackageNames, new Comparator<String>() {
121 @Override
122 public int compare(String a, String b) {
123 // I can never keep this rule straight when writing these damn things...
124 // a < b => -1, a == b => 0, a > b => +1
125
126 String[] aparts = a.split("/");
127 String[] bparts = b.split("/");
128 for (int i = 0; true; i++) {
129 if (i >= aparts.length) {
130 return -1;
131 } else if (i >= bparts.length) {
132 return 1;
133 }
134
135 int result = aparts[i].compareTo(bparts[i]);
136 if (result != 0) {
137 return result;
138 }
139 }
140 }
141 });
142
143 // create the root node and the package nodes
144 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
145 for (String packageName : sortedPackageNames) {
146 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
147 packages.put(packageName, node);
148 root.add(node);
149 }
150
151 // put the classes into packages
152 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
153 for (ClassEntry classEntry : classEntries) {
154 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
155 }
156
157 // build the class nodes
158 for (String packageName : packagedClassEntries.keySet()) {
159 // sort the class entries
160 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
161 Collections.sort(classEntriesInPackage, m_comparator);
162
163 // create the nodes in order
164 for (ClassEntry classEntry : classEntriesInPackage) {
165 ClassSelectorPackageNode node = packages.get(packageName);
166 node.add(new ClassSelectorClassNode(classEntry));
167 }
168 }
169
170 // finally, update the tree control
171 setModel(new DefaultTreeModel(root));
172 }
173
174 public ClassEntry getSelectedClass() {
175 if (!isSelectionEmpty()) {
176 Object selectedNode = getSelectionPath().getLastPathComponent();
177 if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
179 return classNode.getClassEntry();
180 }
181 }
182 return null;
183 }
184
185 public String getSelectedPackage() {
186 if (!isSelectionEmpty()) {
187 Object selectedNode = getSelectionPath().getLastPathComponent();
188 if (selectedNode instanceof ClassSelectorPackageNode) {
189 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
190 return packageNode.getPackageName();
191 } else if (selectedNode instanceof ClassSelectorClassNode) {
192 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
193 return classNode.getClassEntry().getPackageName();
194 }
195 }
196 return null;
197 }
198
199 public Iterable<ClassSelectorPackageNode> packageNodes() {
200 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
201 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
202 Enumeration<?> children = root.children();
203 while (children.hasMoreElements()) {
204 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
205 nodes.add(packageNode);
206 }
207 return nodes;
208 }
209
210 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
211 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
212 Enumeration<?> children = packageNode.children();
213 while (children.hasMoreElements()) {
214 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
215 nodes.add(classNode);
216 }
217 return nodes;
218 }
219
220 public void expandPackage(String packageName) {
221 if (packageName == null) {
222 return;
223 }
224 for (ClassSelectorPackageNode packageNode : packageNodes()) {
225 if (packageNode.getPackageName().equals(packageName)) {
226 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
227 return;
228 }
229 }
230 }
231
232 public void expandAll() {
233 for (ClassSelectorPackageNode packageNode : packageNodes()) {
234 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
235 }
236 }
237
238 public ClassEntry getFirstClass() {
239 for (ClassSelectorPackageNode packageNode : packageNodes()) {
240 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
241 return classNode.getClassEntry();
242 }
243 }
244 return null;
245 }
246
247 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
248 for (ClassSelectorPackageNode packageNode : packageNodes()) {
249 if (packageNode.getPackageName().equals(entry.getPackageName())) {
250 return packageNode;
251 }
252 }
253 return null;
254 }
255
256 public ClassEntry getNextClass(ClassEntry entry) {
257 boolean foundIt = false;
258 for (ClassSelectorPackageNode packageNode : packageNodes()) {
259 if (!foundIt) {
260 // skip to the package with our target in it
261 if (packageNode.getPackageName().equals(entry.getPackageName())) {
262 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
263 if (!foundIt) {
264 if (classNode.getClassEntry().equals(entry)) {
265 foundIt = true;
266 }
267 } else {
268 // return the next class
269 return classNode.getClassEntry();
270 }
271 }
272 }
273 } else {
274 // return the next class
275 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
276 return classNode.getClassEntry();
277 }
278 }
279 }
280 return null;
281 }
282
283 public void setSelectionClass(ClassEntry classEntry) {
284 expandPackage(classEntry.getPackageName());
285 for (ClassSelectorPackageNode packageNode : packageNodes()) {
286 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
287 if (classNode.getClassEntry().equals(classEntry)) {
288 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
289 }
290 }
291 }
292 }
293}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 0000000..1219e89
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,50 @@
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 *
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 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int)((ScoredClassEntry)m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode)other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 0000000..7259f54
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,45 @@
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 *
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
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode)other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 0000000..5033a2c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,222 @@
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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Rectangle;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16
17import javax.swing.JEditorPane;
18import javax.swing.SwingUtilities;
19import javax.swing.Timer;
20import javax.swing.event.CaretEvent;
21import javax.swing.event.CaretListener;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter.HighlightPainter;
24
25import com.strobel.decompiler.languages.java.ast.CompilationUnit;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public static interface SelectionListener {
43 void onSelect(EntryReference<Entry,Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer)event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 0000000..904273c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
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 *
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..57210a8
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
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 *
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..f9192d3
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1122 @@
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 *
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.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.awt.event.InputEvent;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import java.io.File;
29import java.io.IOException;
30import java.lang.Thread.UncaughtExceptionHandler;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.List;
34import java.util.Vector;
35import java.util.jar.JarFile;
36
37import javax.swing.BorderFactory;
38import javax.swing.JEditorPane;
39import javax.swing.JFileChooser;
40import javax.swing.JFrame;
41import javax.swing.JLabel;
42import javax.swing.JList;
43import javax.swing.JMenu;
44import javax.swing.JMenuBar;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JPopupMenu;
49import javax.swing.JScrollPane;
50import javax.swing.JSplitPane;
51import javax.swing.JTabbedPane;
52import javax.swing.JTextField;
53import javax.swing.JTree;
54import javax.swing.KeyStroke;
55import javax.swing.ListSelectionModel;
56import javax.swing.WindowConstants;
57import javax.swing.event.CaretEvent;
58import javax.swing.event.CaretListener;
59import javax.swing.text.BadLocationException;
60import javax.swing.text.Highlighter;
61import javax.swing.tree.DefaultTreeModel;
62import javax.swing.tree.TreeNode;
63import javax.swing.tree.TreePath;
64
65import com.google.common.collect.Lists;
66
67import cuchaz.enigma.Constants;
68import cuchaz.enigma.ExceptionIgnorer;
69import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
70import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
71import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
72import cuchaz.enigma.analysis.EntryReference;
73import cuchaz.enigma.analysis.FieldReferenceTreeNode;
74import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
75import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
76import cuchaz.enigma.analysis.ReferenceTreeNode;
77import cuchaz.enigma.analysis.Token;
78import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
79import cuchaz.enigma.mapping.ArgumentEntry;
80import cuchaz.enigma.mapping.ClassEntry;
81import cuchaz.enigma.mapping.ConstructorEntry;
82import cuchaz.enigma.mapping.Entry;
83import cuchaz.enigma.mapping.FieldEntry;
84import cuchaz.enigma.mapping.IllegalNameException;
85import cuchaz.enigma.mapping.MappingParseException;
86import cuchaz.enigma.mapping.MethodEntry;
87import cuchaz.enigma.mapping.Signature;
88import de.sciss.syntaxpane.DefaultSyntaxKit;
89
90public class Gui {
91
92 private GuiController m_controller;
93
94 // controls
95 private JFrame m_frame;
96 private ClassSelector m_obfClasses;
97 private ClassSelector m_deobfClasses;
98 private JEditorPane m_editor;
99 private JPanel m_classesPanel;
100 private JSplitPane m_splitClasses;
101 private JPanel m_infoPanel;
102 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
103 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
104 private OtherHighlightPainter m_otherHighlightPainter;
105 private SelectionHighlightPainter m_selectionHighlightPainter;
106 private JTree m_inheritanceTree;
107 private JTree m_implementationsTree;
108 private JTree m_callsTree;
109 private JList<Token> m_tokens;
110 private JTabbedPane m_tabs;
111
112 // dynamic menu items
113 private JMenuItem m_closeJarMenu;
114 private JMenuItem m_openMappingsMenu;
115 private JMenuItem m_saveMappingsMenu;
116 private JMenuItem m_saveMappingsAsMenu;
117 private JMenuItem m_closeMappingsMenu;
118 private JMenuItem m_renameMenu;
119 private JMenuItem m_showInheritanceMenu;
120 private JMenuItem m_openEntryMenu;
121 private JMenuItem m_openPreviousMenu;
122 private JMenuItem m_showCallsMenu;
123 private JMenuItem m_showImplementationsMenu;
124 private JMenuItem m_toggleMappingMenu;
125 private JMenuItem m_exportSourceMenu;
126 private JMenuItem m_exportJarMenu;
127
128 // state
129 private EntryReference<Entry,Entry> m_reference;
130 private JFileChooser m_jarFileChooser;
131 private JFileChooser m_mappingsFileChooser;
132 private JFileChooser m_exportSourceFileChooser;
133 private JFileChooser m_exportJarFileChooser;
134
135 public Gui() {
136
137 // init frame
138 m_frame = new JFrame(Constants.Name);
139 final Container pane = m_frame.getContentPane();
140 pane.setLayout(new BorderLayout());
141
142 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
143 // install a global exception handler to the event thread
144 CrashDialog.init(m_frame);
145 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
146 @Override
147 public void uncaughtException(Thread thread, Throwable t) {
148 t.printStackTrace(System.err);
149 if (!ExceptionIgnorer.shouldIgnore(t)) {
150 CrashDialog.show(t);
151 }
152 }
153 });
154 }
155
156 m_controller = new GuiController(this);
157
158 // init file choosers
159 m_jarFileChooser = new JFileChooser();
160 m_mappingsFileChooser = new JFileChooser();
161 m_exportSourceFileChooser = new JFileChooser();
162 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
163 m_exportJarFileChooser = new JFileChooser();
164
165 // init obfuscated classes list
166 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
167 m_obfClasses.setListener(new ClassSelectionListener() {
168 @Override
169 public void onSelectClass(ClassEntry classEntry) {
170 navigateTo(classEntry);
171 }
172 });
173 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
174 JPanel obfPanel = new JPanel();
175 obfPanel.setLayout(new BorderLayout());
176 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
177 obfPanel.add(obfScroller, BorderLayout.CENTER);
178
179 // init deobfuscated classes list
180 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
181 m_deobfClasses.setListener(new ClassSelectionListener() {
182 @Override
183 public void onSelectClass(ClassEntry classEntry) {
184 navigateTo(classEntry);
185 }
186 });
187 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
188 JPanel deobfPanel = new JPanel();
189 deobfPanel.setLayout(new BorderLayout());
190 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
191 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
192
193 // set up classes panel (don't add the splitter yet)
194 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
195 m_splitClasses.setResizeWeight(0.3);
196 m_classesPanel = new JPanel();
197 m_classesPanel.setLayout(new BorderLayout());
198 m_classesPanel.setPreferredSize(new Dimension(250, 0));
199
200 // init info panel
201 m_infoPanel = new JPanel();
202 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
203 m_infoPanel.setPreferredSize(new Dimension(0, 100));
204 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
205 clearReference();
206
207 // init editor
208 DefaultSyntaxKit.initKit();
209 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
210 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
211 m_otherHighlightPainter = new OtherHighlightPainter();
212 m_selectionHighlightPainter = new SelectionHighlightPainter();
213 m_editor = new JEditorPane();
214 m_editor.setEditable(false);
215 m_editor.setCaret(new BrowserCaret());
216 JScrollPane sourceScroller = new JScrollPane(m_editor);
217 m_editor.setContentType("text/java");
218 m_editor.addCaretListener(new CaretListener() {
219 @Override
220 public void caretUpdate(CaretEvent event) {
221 onCaretMove(event.getDot());
222 }
223 });
224 m_editor.addKeyListener(new KeyAdapter() {
225 @Override
226 public void keyPressed(KeyEvent event) {
227 switch (event.getKeyCode()) {
228 case KeyEvent.VK_R:
229 m_renameMenu.doClick();
230 break;
231
232 case KeyEvent.VK_I:
233 m_showInheritanceMenu.doClick();
234 break;
235
236 case KeyEvent.VK_M:
237 m_showImplementationsMenu.doClick();
238 break;
239
240 case KeyEvent.VK_N:
241 m_openEntryMenu.doClick();
242 break;
243
244 case KeyEvent.VK_P:
245 m_openPreviousMenu.doClick();
246 break;
247
248 case KeyEvent.VK_C:
249 m_showCallsMenu.doClick();
250 break;
251
252 case KeyEvent.VK_T:
253 m_toggleMappingMenu.doClick();
254 break;
255 }
256 }
257 });
258
259 // turn off token highlighting (it's wrong most of the time anyway...)
260 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
261 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
262
263 // init editor popup menu
264 JPopupMenu popupMenu = new JPopupMenu();
265 m_editor.setComponentPopupMenu(popupMenu);
266 {
267 JMenuItem menu = new JMenuItem("Rename");
268 menu.addActionListener(new ActionListener() {
269 @Override
270 public void actionPerformed(ActionEvent event) {
271 startRename();
272 }
273 });
274 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
275 menu.setEnabled(false);
276 popupMenu.add(menu);
277 m_renameMenu = menu;
278 }
279 {
280 JMenuItem menu = new JMenuItem("Show Inheritance");
281 menu.addActionListener(new ActionListener() {
282 @Override
283 public void actionPerformed(ActionEvent event) {
284 showInheritance();
285 }
286 });
287 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
288 menu.setEnabled(false);
289 popupMenu.add(menu);
290 m_showInheritanceMenu = menu;
291 }
292 {
293 JMenuItem menu = new JMenuItem("Show Implementations");
294 menu.addActionListener(new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent event) {
297 showImplementations();
298 }
299 });
300 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
301 menu.setEnabled(false);
302 popupMenu.add(menu);
303 m_showImplementationsMenu = menu;
304 }
305 {
306 JMenuItem menu = new JMenuItem("Show Calls");
307 menu.addActionListener(new ActionListener() {
308 @Override
309 public void actionPerformed(ActionEvent event) {
310 showCalls();
311 }
312 });
313 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
314 menu.setEnabled(false);
315 popupMenu.add(menu);
316 m_showCallsMenu = menu;
317 }
318 {
319 JMenuItem menu = new JMenuItem("Go to Declaration");
320 menu.addActionListener(new ActionListener() {
321 @Override
322 public void actionPerformed(ActionEvent event) {
323 navigateTo(m_reference.entry);
324 }
325 });
326 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
327 menu.setEnabled(false);
328 popupMenu.add(menu);
329 m_openEntryMenu = menu;
330 }
331 {
332 JMenuItem menu = new JMenuItem("Go to previous");
333 menu.addActionListener(new ActionListener() {
334 @Override
335 public void actionPerformed(ActionEvent event) {
336 m_controller.openPreviousReference();
337 }
338 });
339 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
340 menu.setEnabled(false);
341 popupMenu.add(menu);
342 m_openPreviousMenu = menu;
343 }
344 {
345 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
346 menu.addActionListener(new ActionListener() {
347 @Override
348 public void actionPerformed(ActionEvent event) {
349 toggleMapping();
350 }
351 });
352 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
353 menu.setEnabled(false);
354 popupMenu.add(menu);
355 m_toggleMappingMenu = menu;
356 }
357
358 // init inheritance panel
359 m_inheritanceTree = new JTree();
360 m_inheritanceTree.setModel(null);
361 m_inheritanceTree.addMouseListener(new MouseAdapter() {
362 @Override
363 public void mouseClicked(MouseEvent event) {
364 if (event.getClickCount() == 2) {
365 // get the selected node
366 TreePath path = m_inheritanceTree.getSelectionPath();
367 if (path == null) {
368 return;
369 }
370
371 Object node = path.getLastPathComponent();
372 if (node instanceof ClassInheritanceTreeNode) {
373 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
374 navigateTo(new ClassEntry(classNode.getObfClassName()));
375 } else if (node instanceof MethodInheritanceTreeNode) {
376 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
377 if (methodNode.isImplemented()) {
378 navigateTo(methodNode.getMethodEntry());
379 }
380 }
381 }
382 }
383 });
384 JPanel inheritancePanel = new JPanel();
385 inheritancePanel.setLayout(new BorderLayout());
386 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
387
388 // init implementations panel
389 m_implementationsTree = new JTree();
390 m_implementationsTree.setModel(null);
391 m_implementationsTree.addMouseListener(new MouseAdapter() {
392 @Override
393 public void mouseClicked(MouseEvent event) {
394 if (event.getClickCount() == 2) {
395 // get the selected node
396 TreePath path = m_implementationsTree.getSelectionPath();
397 if (path == null) {
398 return;
399 }
400
401 Object node = path.getLastPathComponent();
402 if (node instanceof ClassImplementationsTreeNode) {
403 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
404 navigateTo(classNode.getClassEntry());
405 } else if (node instanceof MethodImplementationsTreeNode) {
406 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
407 navigateTo(methodNode.getMethodEntry());
408 }
409 }
410 }
411 });
412 JPanel implementationsPanel = new JPanel();
413 implementationsPanel.setLayout(new BorderLayout());
414 implementationsPanel.add(new JScrollPane(m_implementationsTree));
415
416 // init call panel
417 m_callsTree = new JTree();
418 m_callsTree.setModel(null);
419 m_callsTree.addMouseListener(new MouseAdapter() {
420 @SuppressWarnings("unchecked")
421 @Override
422 public void mouseClicked(MouseEvent event) {
423 if (event.getClickCount() == 2) {
424 // get the selected node
425 TreePath path = m_callsTree.getSelectionPath();
426 if (path == null) {
427 return;
428 }
429
430 Object node = path.getLastPathComponent();
431 if (node instanceof ReferenceTreeNode) {
432 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
433 if (referenceNode.getReference() != null) {
434 navigateTo(referenceNode.getReference());
435 } else {
436 navigateTo(referenceNode.getEntry());
437 }
438 }
439 }
440 }
441 });
442 m_tokens = new JList<Token>();
443 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
444 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
445 m_tokens.setLayoutOrientation(JList.VERTICAL);
446 m_tokens.addMouseListener(new MouseAdapter() {
447 @Override
448 public void mouseClicked(MouseEvent event) {
449 if (event.getClickCount() == 2) {
450 Token selected = m_tokens.getSelectedValue();
451 if (selected != null) {
452 showToken(selected);
453 }
454 }
455 }
456 });
457 m_tokens.setPreferredSize(new Dimension(0, 200));
458 m_tokens.setMinimumSize(new Dimension(0, 200));
459 JSplitPane callPanel = new JSplitPane(
460 JSplitPane.VERTICAL_SPLIT,
461 true,
462 new JScrollPane(m_callsTree),
463 new JScrollPane(m_tokens)
464 );
465 callPanel.setResizeWeight(1); // let the top side take all the slack
466 callPanel.resetToPreferredSizes();
467
468 // layout controls
469 JPanel centerPanel = new JPanel();
470 centerPanel.setLayout(new BorderLayout());
471 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
472 centerPanel.add(sourceScroller, BorderLayout.CENTER);
473 m_tabs = new JTabbedPane();
474 m_tabs.setPreferredSize(new Dimension(250, 0));
475 m_tabs.addTab("Inheritance", inheritancePanel);
476 m_tabs.addTab("Implementations", implementationsPanel);
477 m_tabs.addTab("Call Graph", callPanel);
478 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
479 splitRight.setResizeWeight(1); // let the left side take all the slack
480 splitRight.resetToPreferredSizes();
481 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
482 splitCenter.setResizeWeight(0); // let the right side take all the slack
483 pane.add(splitCenter, BorderLayout.CENTER);
484
485 // init menus
486 JMenuBar menuBar = new JMenuBar();
487 m_frame.setJMenuBar(menuBar);
488 {
489 JMenu menu = new JMenu("File");
490 menuBar.add(menu);
491 {
492 JMenuItem item = new JMenuItem("Open Jar...");
493 menu.add(item);
494 item.addActionListener(new ActionListener() {
495 @Override
496 public void actionPerformed(ActionEvent event) {
497 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
498 // load the jar in a separate thread
499 new Thread() {
500 @Override
501 public void run() {
502 try {
503 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
504 } catch (IOException ex) {
505 throw new Error(ex);
506 }
507 }
508 }.start();
509 }
510 }
511 });
512 }
513 {
514 JMenuItem item = new JMenuItem("Close Jar");
515 menu.add(item);
516 item.addActionListener(new ActionListener() {
517 @Override
518 public void actionPerformed(ActionEvent event) {
519 m_controller.closeJar();
520 }
521 });
522 m_closeJarMenu = item;
523 }
524 menu.addSeparator();
525 {
526 JMenuItem item = new JMenuItem("Open Mappings...");
527 menu.add(item);
528 item.addActionListener(new ActionListener() {
529 @Override
530 public void actionPerformed(ActionEvent event) {
531 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
532 try {
533 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
534 } catch (IOException ex) {
535 throw new Error(ex);
536 } catch (MappingParseException ex) {
537 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
538 }
539 }
540 }
541 });
542 m_openMappingsMenu = item;
543 }
544 {
545 JMenuItem item = new JMenuItem("Save Mappings");
546 menu.add(item);
547 item.addActionListener(new ActionListener() {
548 @Override
549 public void actionPerformed(ActionEvent event) {
550 try {
551 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
552 } catch (IOException ex) {
553 throw new Error(ex);
554 }
555 }
556 });
557 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
558 m_saveMappingsMenu = item;
559 }
560 {
561 JMenuItem item = new JMenuItem("Save Mappings As...");
562 menu.add(item);
563 item.addActionListener(new ActionListener() {
564 @Override
565 public void actionPerformed(ActionEvent event) {
566 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
567 try {
568 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
569 m_saveMappingsMenu.setEnabled(true);
570 } catch (IOException ex) {
571 throw new Error(ex);
572 }
573 }
574 }
575 });
576 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
577 m_saveMappingsAsMenu = item;
578 }
579 {
580 JMenuItem item = new JMenuItem("Close Mappings");
581 menu.add(item);
582 item.addActionListener(new ActionListener() {
583 @Override
584 public void actionPerformed(ActionEvent event) {
585 m_controller.closeMappings();
586 }
587 });
588 m_closeMappingsMenu = item;
589 }
590 menu.addSeparator();
591 {
592 JMenuItem item = new JMenuItem("Export Source...");
593 menu.add(item);
594 item.addActionListener(new ActionListener() {
595 @Override
596 public void actionPerformed(ActionEvent event) {
597 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
598 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
599 }
600 }
601 });
602 m_exportSourceMenu = item;
603 }
604 {
605 JMenuItem item = new JMenuItem("Export Jar...");
606 menu.add(item);
607 item.addActionListener(new ActionListener() {
608 @Override
609 public void actionPerformed(ActionEvent event) {
610 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
611 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
612 }
613 }
614 });
615 m_exportJarMenu = item;
616 }
617 menu.addSeparator();
618 {
619 JMenuItem item = new JMenuItem("Exit");
620 menu.add(item);
621 item.addActionListener(new ActionListener() {
622 @Override
623 public void actionPerformed(ActionEvent event) {
624 close();
625 }
626 });
627 }
628 }
629 {
630 JMenu menu = new JMenu("Help");
631 menuBar.add(menu);
632 {
633 JMenuItem item = new JMenuItem("About");
634 menu.add(item);
635 item.addActionListener(new ActionListener() {
636 @Override
637 public void actionPerformed(ActionEvent event) {
638 AboutDialog.show(m_frame);
639 }
640 });
641 }
642 }
643
644 // init state
645 onCloseJar();
646
647 m_frame.addWindowListener(new WindowAdapter() {
648 @Override
649 public void windowClosing(WindowEvent event) {
650 close();
651 }
652 });
653
654 // show the frame
655 pane.doLayout();
656 m_frame.setSize(1024, 576);
657 m_frame.setMinimumSize(new Dimension(640, 480));
658 m_frame.setVisible(true);
659 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
660 }
661
662 public JFrame getFrame() {
663 return m_frame;
664 }
665
666 public GuiController getController() {
667 return m_controller;
668 }
669
670 public void onStartOpenJar() {
671 m_classesPanel.removeAll();
672 JPanel panel = new JPanel();
673 panel.setLayout(new FlowLayout());
674 panel.add(new JLabel("Loading..."));
675 m_classesPanel.add(panel);
676 redraw();
677 }
678
679 public void onFinishOpenJar(String jarName) {
680 // update gui
681 m_frame.setTitle(Constants.Name + " - " + jarName);
682 m_classesPanel.removeAll();
683 m_classesPanel.add(m_splitClasses);
684 setSource(null);
685
686 // update menu
687 m_closeJarMenu.setEnabled(true);
688 m_openMappingsMenu.setEnabled(true);
689 m_saveMappingsMenu.setEnabled(false);
690 m_saveMappingsAsMenu.setEnabled(true);
691 m_closeMappingsMenu.setEnabled(true);
692 m_exportSourceMenu.setEnabled(true);
693 m_exportJarMenu.setEnabled(true);
694
695 redraw();
696 }
697
698 public void onCloseJar() {
699 // update gui
700 m_frame.setTitle(Constants.Name);
701 setObfClasses(null);
702 setDeobfClasses(null);
703 setSource(null);
704 m_classesPanel.removeAll();
705
706 // update menu
707 m_closeJarMenu.setEnabled(false);
708 m_openMappingsMenu.setEnabled(false);
709 m_saveMappingsMenu.setEnabled(false);
710 m_saveMappingsAsMenu.setEnabled(false);
711 m_closeMappingsMenu.setEnabled(false);
712 m_exportSourceMenu.setEnabled(false);
713 m_exportJarMenu.setEnabled(false);
714
715 redraw();
716 }
717
718 public void setObfClasses(Collection<ClassEntry> obfClasses) {
719 m_obfClasses.setClasses(obfClasses);
720 }
721
722 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
723 m_deobfClasses.setClasses(deobfClasses);
724 }
725
726 public void setMappingsFile(File file) {
727 m_mappingsFileChooser.setSelectedFile(file);
728 m_saveMappingsMenu.setEnabled(file != null);
729 }
730
731 public void setSource(String source) {
732 m_editor.getHighlighter().removeAllHighlights();
733 m_editor.setText(source);
734 }
735
736 public void showToken(final Token token) {
737 if (token == null) {
738 throw new IllegalArgumentException("Token cannot be null!");
739 }
740 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
741 redraw();
742 }
743
744 public void showTokens(Collection<Token> tokens) {
745 Vector<Token> sortedTokens = new Vector<Token>(tokens);
746 Collections.sort(sortedTokens);
747 if (sortedTokens.size() > 1) {
748 // sort the tokens and update the tokens panel
749 m_tokens.setListData(sortedTokens);
750 m_tokens.setSelectedIndex(0);
751 } else {
752 m_tokens.setListData(new Vector<Token>());
753 }
754
755 // show the first token
756 showToken(sortedTokens.get(0));
757 }
758
759 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
760
761 // remove any old highlighters
762 m_editor.getHighlighter().removeAllHighlights();
763
764 // color things based on the index
765 if (obfuscatedTokens != null) {
766 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
767 }
768 if (deobfuscatedTokens != null) {
769 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
770 }
771 if (otherTokens != null) {
772 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
773 }
774
775 redraw();
776 }
777
778 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
779 for (Token token : tokens) {
780 try {
781 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
782 } catch (BadLocationException ex) {
783 throw new IllegalArgumentException(ex);
784 }
785 }
786 }
787
788 private void clearReference() {
789 m_infoPanel.removeAll();
790 JLabel label = new JLabel("No identifier selected");
791 GuiTricks.unboldLabel(label);
792 label.setHorizontalAlignment(JLabel.CENTER);
793 m_infoPanel.add(label);
794
795 redraw();
796 }
797
798 private void showReference(EntryReference<Entry,Entry> reference) {
799 if (reference == null) {
800 clearReference();
801 return;
802 }
803
804 m_reference = reference;
805
806 m_infoPanel.removeAll();
807 if (reference.entry instanceof ClassEntry) {
808 showClassEntry((ClassEntry)m_reference.entry);
809 } else if (m_reference.entry instanceof FieldEntry) {
810 showFieldEntry((FieldEntry)m_reference.entry);
811 } else if (m_reference.entry instanceof MethodEntry) {
812 showMethodEntry((MethodEntry)m_reference.entry);
813 } else if (m_reference.entry instanceof ConstructorEntry) {
814 showConstructorEntry((ConstructorEntry)m_reference.entry);
815 } else if (m_reference.entry instanceof ArgumentEntry) {
816 showArgumentEntry((ArgumentEntry)m_reference.entry);
817 } else {
818 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
819 }
820
821 redraw();
822 }
823
824 private void showClassEntry(ClassEntry entry) {
825 addNameValue(m_infoPanel, "Class", entry.getName());
826 }
827
828 private void showFieldEntry(FieldEntry entry) {
829 addNameValue(m_infoPanel, "Field", entry.getName());
830 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
831 addNameValue(m_infoPanel, "Type", entry.getType().toString());
832 }
833
834 private void showMethodEntry(MethodEntry entry) {
835 addNameValue(m_infoPanel, "Method", entry.getName());
836 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
837 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
838 }
839
840 private void showConstructorEntry(ConstructorEntry entry) {
841 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
842 if (!entry.isStatic()) {
843 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
844 }
845 }
846
847 private void showArgumentEntry(ArgumentEntry entry) {
848 addNameValue(m_infoPanel, "Argument", entry.getName());
849 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
850 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
851 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
852 }
853
854 private void addNameValue(JPanel container, String name, String value) {
855 JPanel panel = new JPanel();
856 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
857 container.add(panel);
858
859 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
860 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
861 panel.add(label);
862
863 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
864 }
865
866 private void onCaretMove(int pos) {
867
868 Token token = m_controller.getToken(pos);
869 boolean isToken = token != null;
870
871 m_reference = m_controller.getDeobfReference(token);
872 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
873 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
874 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
875 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
876 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
877 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
878
879 if (isToken) {
880 showReference(m_reference);
881 } else {
882 clearReference();
883 }
884
885 m_renameMenu.setEnabled(isRenameable && isToken);
886 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
887 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
888 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
889 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
890 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
891 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
892
893 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
894 m_toggleMappingMenu.setText("Reset to obfuscated");
895 } else {
896 m_toggleMappingMenu.setText("Mark as deobfuscated");
897 }
898 }
899
900 private void navigateTo(Entry entry) {
901 if (!m_controller.entryIsInJar(entry)) {
902 // entry is not in the jar. Ignore it
903 return;
904 }
905 if (m_reference != null) {
906 m_controller.savePreviousReference(m_reference);
907 }
908 m_controller.openDeclaration(entry);
909 }
910
911 private void navigateTo(EntryReference<Entry,Entry> reference) {
912 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
913 // reference is not in the jar. Ignore it
914 return;
915 }
916 if (m_reference != null) {
917 m_controller.savePreviousReference(m_reference);
918 }
919 m_controller.openReference(reference);
920 }
921
922 private void startRename() {
923
924 // init the text box
925 final JTextField text = new JTextField();
926 text.setText(m_reference.getNamableName());
927 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
928 text.addKeyListener(new KeyAdapter() {
929 @Override
930 public void keyPressed(KeyEvent event) {
931 switch (event.getKeyCode()) {
932 case KeyEvent.VK_ENTER:
933 finishRename(text, true);
934 break;
935
936 case KeyEvent.VK_ESCAPE:
937 finishRename(text, false);
938 break;
939 }
940 }
941 });
942
943 // find the label with the name and replace it with the text box
944 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
945 panel.remove(panel.getComponentCount() - 1);
946 panel.add(text);
947 text.grabFocus();
948 text.selectAll();
949
950 redraw();
951 }
952
953 private void finishRename(JTextField text, boolean saveName) {
954 String newName = text.getText();
955 if (saveName && newName != null && newName.length() > 0) {
956 try {
957 m_controller.rename(m_reference, newName);
958 } catch (IllegalNameException ex) {
959 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
960 text.setToolTipText(ex.getReason());
961 GuiTricks.showToolTipNow(text);
962 }
963 return;
964 }
965
966 // abort the rename
967 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
968 panel.remove(panel.getComponentCount() - 1);
969 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
970
971 m_editor.grabFocus();
972
973 redraw();
974 }
975
976 private void showInheritance() {
977
978 if (m_reference == null) {
979 return;
980 }
981
982 m_inheritanceTree.setModel(null);
983
984 if (m_reference.entry instanceof ClassEntry) {
985 // get the class inheritance
986 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
987
988 // show the tree at the root
989 TreePath path = getPathToRoot(classNode);
990 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
991 m_inheritanceTree.expandPath(path);
992 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
993 } else if (m_reference.entry instanceof MethodEntry) {
994 // get the method inheritance
995 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
996
997 // show the tree at the root
998 TreePath path = getPathToRoot(classNode);
999 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1000 m_inheritanceTree.expandPath(path);
1001 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1002 }
1003
1004 m_tabs.setSelectedIndex(0);
1005 redraw();
1006 }
1007
1008 private void showImplementations() {
1009
1010 if (m_reference == null) {
1011 return;
1012 }
1013
1014 m_implementationsTree.setModel(null);
1015
1016 if (m_reference.entry instanceof ClassEntry) {
1017 // get the class implementations
1018 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1019 if (node != null) {
1020 // show the tree at the root
1021 TreePath path = getPathToRoot(node);
1022 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1023 m_implementationsTree.expandPath(path);
1024 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1025 }
1026 } else if (m_reference.entry instanceof MethodEntry) {
1027 // get the method implementations
1028 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1029 if (node != null) {
1030 // show the tree at the root
1031 TreePath path = getPathToRoot(node);
1032 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1033 m_implementationsTree.expandPath(path);
1034 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1035 }
1036 }
1037
1038 m_tabs.setSelectedIndex(1);
1039 redraw();
1040 }
1041
1042 private void showCalls() {
1043
1044 if (m_reference == null) {
1045 return;
1046 }
1047
1048 if (m_reference.entry instanceof ClassEntry) {
1049 // look for calls to the default constructor
1050 // TODO: get a list of all the constructors and find calls to all of them
1051 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1052 m_callsTree.setModel(new DefaultTreeModel(node));
1053 } else if (m_reference.entry instanceof FieldEntry) {
1054 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1055 m_callsTree.setModel(new DefaultTreeModel(node));
1056 } else if (m_reference.entry instanceof MethodEntry) {
1057 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1058 m_callsTree.setModel(new DefaultTreeModel(node));
1059 } else if (m_reference.entry instanceof ConstructorEntry) {
1060 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1061 m_callsTree.setModel(new DefaultTreeModel(node));
1062 }
1063
1064 m_tabs.setSelectedIndex(2);
1065 redraw();
1066 }
1067
1068 private void toggleMapping() {
1069 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1070 m_controller.removeMapping(m_reference);
1071 } else {
1072 m_controller.markAsDeobfuscated(m_reference);
1073 }
1074 }
1075
1076 private TreePath getPathToRoot(TreeNode node) {
1077 List<TreeNode> nodes = Lists.newArrayList();
1078 TreeNode n = node;
1079 do {
1080 nodes.add(n);
1081 n = n.getParent();
1082 } while (n != null);
1083 Collections.reverse(nodes);
1084 return new TreePath(nodes.toArray());
1085 }
1086
1087 private void close() {
1088 if (!m_controller.isDirty()) {
1089 // everything is saved, we can exit safely
1090 m_frame.dispose();
1091 } else {
1092 // ask to save before closing
1093 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1094 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,
1095 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1096 switch (response) {
1097 case JOptionPane.YES_OPTION: // save and exit
1098 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1099 try {
1100 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1101 m_frame.dispose();
1102 } catch (IOException ex) {
1103 throw new Error(ex);
1104 }
1105 }
1106 break;
1107
1108 case JOptionPane.NO_OPTION:
1109 // don't save, exit
1110 m_frame.dispose();
1111 break;
1112
1113 // cancel means do nothing
1114 }
1115 }
1116 }
1117
1118 private void redraw() {
1119 m_frame.validate();
1120 m_frame.repaint();
1121 }
1122}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 0000000..6690622
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,358 @@
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 *
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 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNodes.isEmpty()) {
194 return null;
195 }
196 if (rootNodes.size() > 1) {
197 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
198 }
199 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
200 }
201
202 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
203 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
204 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
205 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
206 obfFieldEntry
207 );
208 rootNode.load(m_deobfuscator.getJarIndex(), true);
209 return rootNode;
210 }
211
212 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
213 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
214 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
215 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
216 obfBehaviorEntry
217 );
218 rootNode.load(m_deobfuscator.getJarIndex(), true);
219 return rootNode;
220 }
221
222 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
223 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
224 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
225 m_isDirty = true;
226 refreshClasses();
227 refreshCurrentClass(obfReference);
228 }
229
230 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
231 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
232 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
233 m_isDirty = true;
234 refreshClasses();
235 refreshCurrentClass(obfReference);
236 }
237
238 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
239 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
240 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
241 m_isDirty = true;
242 refreshClasses();
243 refreshCurrentClass(obfReference);
244 }
245
246 public void openDeclaration(Entry deobfEntry) {
247 if (deobfEntry == null) {
248 throw new IllegalArgumentException("Entry cannot be null!");
249 }
250 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
251 }
252
253 public void openReference(EntryReference<Entry,Entry> deobfReference) {
254 if (deobfReference == null) {
255 throw new IllegalArgumentException("Reference cannot be null!");
256 }
257
258 // get the reference target class
259 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
260 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
261 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
262 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
263 }
264 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
265 // deobfuscate the class, then navigate to the reference
266 m_currentObfClass = obfClassEntry;
267 deobfuscate(m_currentObfClass, obfReference);
268 } else {
269 showReference(obfReference);
270 }
271 }
272
273 private void showReference(EntryReference<Entry,Entry> obfReference) {
274 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
275 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
276 if (tokens.isEmpty()) {
277 // DEBUG
278 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
279 } else {
280 m_gui.showTokens(tokens);
281 }
282 }
283
284 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
285 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
286 }
287
288 public void openPreviousReference() {
289 if (hasPreviousLocation()) {
290 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
291 }
292 }
293
294 public boolean hasPreviousLocation() {
295 return !m_referenceStack.isEmpty();
296 }
297
298 private void refreshClasses() {
299 List<ClassEntry> obfClasses = Lists.newArrayList();
300 List<ClassEntry> deobfClasses = Lists.newArrayList();
301 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
302 m_gui.setObfClasses(obfClasses);
303 m_gui.setDeobfClasses(deobfClasses);
304 }
305
306 private void refreshCurrentClass() {
307 refreshCurrentClass(null);
308 }
309
310 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
311 if (m_currentObfClass != null) {
312 deobfuscate(m_currentObfClass, obfReference);
313 }
314 }
315
316 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
317
318 m_gui.setSource("(deobfuscating...)");
319
320 // run the deobfuscator in a separate thread so we don't block the GUI event queue
321 new Thread() {
322 @Override
323 public void run() {
324 // decompile,deobfuscate the bytecode
325 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
326 if (sourceTree == null) {
327 // decompilation of this class is not supported
328 m_gui.setSource("Unable to find class: " + classEntry);
329 return;
330 }
331 String source = m_deobfuscator.getSource(sourceTree);
332 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
333 m_gui.setSource(m_index.getSource());
334 if (obfReference != null) {
335 showReference(obfReference);
336 }
337
338 // set the highlighted tokens
339 List<Token> obfuscatedTokens = Lists.newArrayList();
340 List<Token> deobfuscatedTokens = Lists.newArrayList();
341 List<Token> otherTokens = Lists.newArrayList();
342 for (Token token : m_index.referenceTokens()) {
343 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
344 if (referenceIsRenameable(reference)) {
345 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
346 deobfuscatedTokens.add(token);
347 } else {
348 obfuscatedTokens.add(token);
349 }
350 } else {
351 otherTokens.add(token);
352 }
353 }
354 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
355 }
356 }.start();
357 }
358}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 0000000..5dc3ffb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 0000000..150eaad
--- /dev/null
+++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,499 @@
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 *
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;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.awt.event.KeyAdapter;
20import java.awt.event.KeyEvent;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map;
24
25import javax.swing.BoxLayout;
26import javax.swing.ButtonGroup;
27import javax.swing.JButton;
28import javax.swing.JFrame;
29import javax.swing.JLabel;
30import javax.swing.JPanel;
31import javax.swing.JRadioButton;
32import javax.swing.JScrollPane;
33import javax.swing.JSplitPane;
34import javax.swing.WindowConstants;
35import javax.swing.text.Highlighter.HighlightPainter;
36
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.SourceIndex;
44import cuchaz.enigma.analysis.Token;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.MemberMatches;
47import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
48import cuchaz.enigma.mapping.ClassEntry;
49import cuchaz.enigma.mapping.Entry;
50import de.sciss.syntaxpane.DefaultSyntaxKit;
51
52
53public class MemberMatchingGui<T extends Entry> {
54
55 private static enum SourceType {
56 Matched {
57
58 @Override
59 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
60 return matches.getSourceClassesWithoutUnmatchedEntries();
61 }
62 },
63 Unmatched {
64
65 @Override
66 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
67 return matches.getSourceClassesWithUnmatchedEntries();
68 }
69 };
70
71 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
72 JRadioButton button = new JRadioButton(name(), this == getDefault());
73 button.setActionCommand(name());
74 button.addActionListener(listener);
75 group.add(button);
76 return button;
77 }
78
79 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
80
81 public static SourceType getDefault() {
82 return values()[0];
83 }
84 }
85
86 public static interface SaveListener<T extends Entry> {
87 public void save(MemberMatches<T> matches);
88 }
89
90 // controls
91 private JFrame m_frame;
92 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
93 private ClassSelector m_sourceClasses;
94 private CodeReader m_sourceReader;
95 private CodeReader m_destReader;
96 private JButton m_matchButton;
97 private JButton m_unmatchableButton;
98 private JLabel m_sourceLabel;
99 private JLabel m_destLabel;
100 private HighlightPainter m_unmatchedHighlightPainter;
101 private HighlightPainter m_matchedHighlightPainter;
102
103 private ClassMatches m_classMatches;
104 private MemberMatches<T> m_memberMatches;
105 private Deobfuscator m_sourceDeobfuscator;
106 private Deobfuscator m_destDeobfuscator;
107 private SaveListener<T> m_saveListener;
108 private SourceType m_sourceType;
109 private ClassEntry m_obfSourceClass;
110 private ClassEntry m_obfDestClass;
111 private T m_obfSourceEntry;
112 private T m_obfDestEntry;
113
114 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
115
116 m_classMatches = classMatches;
117 m_memberMatches = fieldMatches;
118 m_sourceDeobfuscator = sourceDeobfuscator;
119 m_destDeobfuscator = destDeobfuscator;
120
121 // init frame
122 m_frame = new JFrame(Constants.Name + " - Member Matcher");
123 final Container pane = m_frame.getContentPane();
124 pane.setLayout(new BorderLayout());
125
126 // init classes side
127 JPanel classesPanel = new JPanel();
128 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
129 classesPanel.setPreferredSize(new Dimension(200, 0));
130 pane.add(classesPanel, BorderLayout.WEST);
131 classesPanel.add(new JLabel("Classes"));
132
133 // init source type radios
134 JPanel sourceTypePanel = new JPanel();
135 classesPanel.add(sourceTypePanel);
136 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
137 ActionListener sourceTypeListener = new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent event) {
140 setSourceType(SourceType.valueOf(event.getActionCommand()));
141 }
142 };
143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
145 for (SourceType sourceType : SourceType.values()) {
146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
149 }
150
151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
152 m_sourceClasses.setListener(new ClassSelectionListener() {
153 @Override
154 public void onSelectClass(ClassEntry classEntry) {
155 setSourceClass(classEntry);
156 }
157 });
158 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
159 classesPanel.add(sourceScroller);
160
161 // init readers
162 DefaultSyntaxKit.initKit();
163 m_sourceReader = new CodeReader();
164 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry,Entry> reference) {
167 if (reference != null) {
168 onSelectSource(reference.entry);
169 } else {
170 onSelectSource(null);
171 }
172 }
173 });
174 m_destReader = new CodeReader();
175 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
176 @Override
177 public void onSelect(EntryReference<Entry,Entry> reference) {
178 if (reference != null) {
179 onSelectDest(reference.entry);
180 } else {
181 onSelectDest(null);
182 }
183 }
184 });
185
186 // add key bindings
187 KeyAdapter keyListener = new KeyAdapter() {
188 @Override
189 public void keyPressed(KeyEvent event) {
190 switch (event.getKeyCode()) {
191 case KeyEvent.VK_M:
192 m_matchButton.doClick();
193 break;
194 }
195 }
196 };
197 m_sourceReader.addKeyListener(keyListener);
198 m_destReader.addKeyListener(keyListener);
199
200 // init all the splits
201 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
202 splitRight.setResizeWeight(0.5); // resize 50:50
203 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
204 splitLeft.setResizeWeight(0); // let the right side take all the slack
205 pane.add(splitLeft, BorderLayout.CENTER);
206 splitLeft.resetToPreferredSizes();
207
208 // init bottom panel
209 JPanel bottomPanel = new JPanel();
210 bottomPanel.setLayout(new FlowLayout());
211 pane.add(bottomPanel, BorderLayout.SOUTH);
212
213 m_matchButton = new JButton();
214 m_unmatchableButton = new JButton();
215
216 m_sourceLabel = new JLabel();
217 bottomPanel.add(m_sourceLabel);
218 bottomPanel.add(m_matchButton);
219 bottomPanel.add(m_unmatchableButton);
220 m_destLabel = new JLabel();
221 bottomPanel.add(m_destLabel);
222
223 // show the frame
224 pane.doLayout();
225 m_frame.setSize(1024, 576);
226 m_frame.setMinimumSize(new Dimension(640, 480));
227 m_frame.setVisible(true);
228 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
229
230 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
231 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
232
233 // init state
234 m_saveListener = null;
235 m_obfSourceClass = null;
236 m_obfDestClass = null;
237 m_obfSourceEntry = null;
238 m_obfDestEntry = null;
239 setSourceType(SourceType.getDefault());
240 updateButtons();
241 }
242
243 protected void setSourceType(SourceType val) {
244 m_sourceType = val;
245 updateSourceClasses();
246 }
247
248 public void setSaveListener(SaveListener<T> val) {
249 m_saveListener = val;
250 }
251
252 private void updateSourceClasses() {
253
254 String selectedPackage = m_sourceClasses.getSelectedPackage();
255
256 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
257 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
258 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
259 }
260 m_sourceClasses.setClasses(deobfClassEntries);
261
262 if (selectedPackage != null) {
263 m_sourceClasses.expandPackage(selectedPackage);
264 }
265
266 for (SourceType sourceType : SourceType.values()) {
267 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
268 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
269 ));
270 }
271 }
272
273 protected void setSourceClass(ClassEntry sourceClass) {
274
275 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
276 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
277 if (m_obfDestClass == null) {
278 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
279 }
280
281 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
282 @Override
283 public void run() {
284 updateSourceHighlights();
285 }
286 });
287 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
288 @Override
289 public void run() {
290 updateDestHighlights();
291 }
292 });
293 }
294
295 protected void updateSourceHighlights() {
296 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
297 }
298
299 protected void updateDestHighlights() {
300 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
301 }
302
303 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
304 reader.clearHighlights();
305 SourceIndex index = reader.getSourceIndex();
306
307 // matched fields
308 for (T obfT : obfMatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_matchedHighlightPainter);
313 }
314 }
315
316 // unmatched fields
317 for (T obfT : obfUnmatchedEntries) {
318 T deobfT = deobfuscator.deobfuscateEntry(obfT);
319 Token token = index.getDeclarationToken(deobfT);
320 if (token != null) {
321 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
322 }
323 }
324 }
325
326 private boolean isSelectionMatched() {
327 return m_obfSourceEntry != null && m_obfDestEntry != null
328 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
329 }
330
331 protected void onSelectSource(Entry source) {
332
333 // start with no selection
334 if (isSelectionMatched()) {
335 setDest(null);
336 }
337 setSource(null);
338
339 // then look for a valid source selection
340 if (source != null) {
341
342 // this looks really scary, but it's actually ok
343 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
344 // and MemberMatches.hasSource() will only pass entries that actually match T
345 @SuppressWarnings("unchecked")
346 T sourceEntry = (T)source;
347
348 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
349 if (m_memberMatches.hasSource(obfSourceEntry)) {
350 setSource(obfSourceEntry);
351
352 // look for a matched dest too
353 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
354 if (obfDestEntry != null) {
355 setDest(obfDestEntry);
356 }
357 }
358 }
359
360 updateButtons();
361 }
362
363 protected void onSelectDest(Entry dest) {
364
365 // start with no selection
366 if (isSelectionMatched()) {
367 setSource(null);
368 }
369 setDest(null);
370
371 // then look for a valid dest selection
372 if (dest != null) {
373
374 // this looks really scary, but it's actually ok
375 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
376 // and MemberMatches.hasSource() will only pass entries that actually match T
377 @SuppressWarnings("unchecked")
378 T destEntry = (T)dest;
379
380 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
381 if (m_memberMatches.hasDest(obfDestEntry)) {
382 setDest(obfDestEntry);
383
384 // look for a matched source too
385 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
386 if (obfSourceEntry != null) {
387 setSource(obfSourceEntry);
388 }
389 }
390 }
391
392 updateButtons();
393 }
394
395 private void setSource(T obfEntry) {
396 if (obfEntry == null) {
397 m_obfSourceEntry = obfEntry;
398 m_sourceLabel.setText("");
399 } else {
400 m_obfSourceEntry = obfEntry;
401 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
402 }
403 }
404
405 private void setDest(T obfEntry) {
406 if (obfEntry == null) {
407 m_obfDestEntry = obfEntry;
408 m_destLabel.setText("");
409 } else {
410 m_obfDestEntry = obfEntry;
411 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
412 }
413 }
414
415 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
416 // show obfuscated and deobfuscated names, but no types/signatures
417 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
418 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
419 }
420
421 private void updateButtons() {
422
423 GuiTricks.deactivateButton(m_matchButton);
424 GuiTricks.deactivateButton(m_unmatchableButton);
425
426 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
427 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
428 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
429 @Override
430 public void actionPerformed(ActionEvent event) {
431 unmatch();
432 }
433 });
434 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
435 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
436 @Override
437 public void actionPerformed(ActionEvent event) {
438 match();
439 }
440 });
441 }
442 } else if (m_obfSourceEntry != null) {
443 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 unmatchable();
447 }
448 });
449 }
450 }
451
452 protected void match() {
453
454 // update the field matches
455 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
456 save();
457
458 // update the ui
459 onSelectSource(null);
460 onSelectDest(null);
461 updateSourceHighlights();
462 updateDestHighlights();
463 updateSourceClasses();
464 }
465
466 protected void unmatch() {
467
468 // update the field matches
469 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
470 save();
471
472 // update the ui
473 onSelectSource(null);
474 onSelectDest(null);
475 updateSourceHighlights();
476 updateDestHighlights();
477 updateSourceClasses();
478 }
479
480 protected void unmatchable() {
481
482 // update the field matches
483 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
484 save();
485
486 // update the ui
487 onSelectSource(null);
488 onSelectDest(null);
489 updateSourceHighlights();
490 updateDestHighlights();
491 updateSourceClasses();
492 }
493
494 private void save() {
495 if (m_saveListener != null) {
496 m_saveListener.save(m_memberMatches);
497 }
498 }
499}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 0000000..4c3714a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
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 *
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..8d3fbe8
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
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 *
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..1c20f10
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
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 *
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..0741af3
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
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 *
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..8b515bb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
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 *
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/ScoredClassEntry.java b/src/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 0000000..6070452
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -0,0 +1,30 @@
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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.ClassEntry;
14
15
16public class ScoredClassEntry extends ClassEntry {
17
18 private static final long serialVersionUID = -8798725308554217105L;
19
20 private float m_score;
21
22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other);
24 m_score = score;
25 }
26
27 public float getScore() {
28 return m_score;
29 }
30}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 0000000..4165da4
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
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 *
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..e4f7c87
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
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 *
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}