summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java17
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java990
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java968
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java362
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1594
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java640
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java41
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java815
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java42
-rw-r--r--src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java35
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java93
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java118
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java157
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java400
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java139
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java11
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java6
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java10
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java87
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java9
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java9
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java9
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java22
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java81
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java81
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java31
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java108
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java40
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelObf.java57
29 files changed, 3432 insertions, 3540 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
index bcdff51..af105db 100644
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -8,20 +8,21 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import javax.swing.text.DefaultCaret; 14import javax.swing.text.DefaultCaret;
14 15
15public class BrowserCaret extends DefaultCaret { 16public class BrowserCaret extends DefaultCaret {
16 17
17 @Override 18 @Override
18 public boolean isSelectionVisible() { 19 public boolean isSelectionVisible() {
19 return true; 20 return true;
20 } 21 }
21 22
22 @Override 23 @Override
23 public boolean isVisible() { 24 public boolean isVisible() {
24 return true; 25 return true;
25 } 26 }
26 27
27} 28}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
index dcbe1c5..05501f4 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.google.common.collect.BiMap; 14import com.google.common.collect.BiMap;
@@ -31,508 +32,505 @@ import java.util.Collection;
31import java.util.List; 32import java.util.List;
32import java.util.Map; 33import java.util.Map;
33 34
34
35public class ClassMatchingGui { 35public class ClassMatchingGui {
36 36
37 private enum SourceType { 37 // controls
38 Matched { 38 private JFrame frame;
39 @Override 39 private ClassSelector sourceClasses;
40 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { 40 private ClassSelector destClasses;
41 return matches.getUniqueMatches().keySet(); 41 private CodeReader sourceReader;
42 } 42 private CodeReader destReader;
43 }, 43 private JLabel sourceClassLabel;
44 Unmatched { 44 private JLabel destClassLabel;
45 @Override 45 private JButton matchButton;
46 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { 46 private Map<SourceType, JRadioButton> sourceTypeButtons;
47 return matches.getUnmatchedSourceClasses(); 47 private JCheckBox advanceCheck;
48 } 48 private JCheckBox top10Matches;
49 }, 49 private ClassMatches classMatches;
50 Ambiguous { 50 private Deobfuscator sourceDeobfuscator;
51 @Override 51 private Deobfuscator destDeobfuscator;
52 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { 52 private ClassEntry sourceClass;
53 return matches.getAmbiguouslyMatchedSourceClasses(); 53 private ClassEntry destClass;
54 } 54 private SourceType sourceType;
55 }; 55 private SaveListener saveListener;
56 56 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
57 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { 57
58 JRadioButton button = new JRadioButton(name(), this == getDefault()); 58 classMatches = matches;
59 button.setActionCommand(name()); 59 this.sourceDeobfuscator = sourceDeobfuscator;
60 button.addActionListener(listener); 60 this.destDeobfuscator = destDeobfuscator;
61 group.add(button); 61
62 return button; 62 // init frame
63 } 63 frame = new JFrame(Constants.NAME + " - Class Matcher");
64 64 final Container pane = frame.getContentPane();
65 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches); 65 pane.setLayout(new BorderLayout());
66 66
67 public static SourceType getDefault() { 67 // init source side
68 return values()[0]; 68 JPanel sourcePanel = new JPanel();
69 } 69 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
70 } 70 sourcePanel.setPreferredSize(new Dimension(200, 0));
71 71 pane.add(sourcePanel, BorderLayout.WEST);
72 public interface SaveListener { 72 sourcePanel.add(new JLabel("Source Classes"));
73 void save(ClassMatches matches); 73
74 } 74 // init source type radios
75 75 JPanel sourceTypePanel = new JPanel();
76 // controls 76 sourcePanel.add(sourceTypePanel);
77 private JFrame frame; 77 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
78 private ClassSelector sourceClasses; 78 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
79 private ClassSelector destClasses; 79 ButtonGroup sourceTypeButtons = new ButtonGroup();
80 private CodeReader sourceReader; 80 this.sourceTypeButtons = Maps.newHashMap();
81 private CodeReader destReader; 81 for (SourceType sourceType : SourceType.values()) {
82 private JLabel sourceClassLabel; 82 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
83 private JLabel destClassLabel; 83 this.sourceTypeButtons.put(sourceType, button);
84 private JButton matchButton; 84 sourceTypePanel.add(button);
85 private Map<SourceType, JRadioButton> sourceTypeButtons; 85 }
86 private JCheckBox advanceCheck; 86
87 private JCheckBox top10Matches; 87 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
88 88 sourceClasses.setSelectionListener(this::setSourceClass);
89 private ClassMatches classMatches; 89 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
90 private Deobfuscator sourceDeobfuscator; 90 sourcePanel.add(sourceScroller);
91 private Deobfuscator destDeobfuscator; 91
92 private ClassEntry sourceClass; 92 // init dest side
93 private ClassEntry destClass; 93 JPanel destPanel = new JPanel();
94 private SourceType sourceType; 94 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
95 private SaveListener saveListener; 95 destPanel.setPreferredSize(new Dimension(200, 0));
96 96 pane.add(destPanel, BorderLayout.WEST);
97 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { 97 destPanel.add(new JLabel("Destination Classes"));
98 98
99 classMatches = matches; 99 top10Matches = new JCheckBox("Show only top 10 matches");
100 this.sourceDeobfuscator = sourceDeobfuscator; 100 destPanel.add(top10Matches);
101 this.destDeobfuscator = destDeobfuscator; 101 top10Matches.addActionListener(event -> toggleTop10Matches());
102 102
103 // init frame 103 destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
104 frame = new JFrame(Constants.NAME + " - Class Matcher"); 104 destClasses.setSelectionListener(this::setDestClass);
105 final Container pane = frame.getContentPane(); 105 JScrollPane destScroller = new JScrollPane(destClasses);
106 pane.setLayout(new BorderLayout()); 106 destPanel.add(destScroller);
107 107
108 // init source side 108 JButton autoMatchButton = new JButton("AutoMatch");
109 JPanel sourcePanel = new JPanel(); 109 autoMatchButton.addActionListener(event -> autoMatch());
110 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); 110 destPanel.add(autoMatchButton);
111 sourcePanel.setPreferredSize(new Dimension(200, 0)); 111
112 pane.add(sourcePanel, BorderLayout.WEST); 112 // init source panels
113 sourcePanel.add(new JLabel("Source Classes")); 113 DefaultSyntaxKit.initKit();
114 114 sourceReader = new CodeReader();
115 // init source type radios 115 destReader = new CodeReader();
116 JPanel sourceTypePanel = new JPanel(); 116
117 sourcePanel.add(sourceTypePanel); 117 // init all the splits
118 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); 118 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(
119 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); 119 sourceReader));
120 ButtonGroup sourceTypeButtons = new ButtonGroup(); 120 splitLeft.setResizeWeight(0); // let the right side take all the slack
121 this.sourceTypeButtons = Maps.newHashMap(); 121 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel);
122 for (SourceType sourceType : SourceType.values()) { 122 splitRight.setResizeWeight(1); // let the left side take all the slack
123 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); 123 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
124 this.sourceTypeButtons.put(sourceType, button); 124 splitCenter.setResizeWeight(0.5); // resize 50:50
125 sourceTypePanel.add(button); 125 pane.add(splitCenter, BorderLayout.CENTER);
126 } 126 splitCenter.resetToPreferredSizes();
127 127
128 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); 128 // init bottom panel
129 sourceClasses.setSelectionListener(this::setSourceClass); 129 JPanel bottomPanel = new JPanel();
130 JScrollPane sourceScroller = new JScrollPane(sourceClasses); 130 bottomPanel.setLayout(new FlowLayout());
131 sourcePanel.add(sourceScroller); 131
132 132 sourceClassLabel = new JLabel();
133 // init dest side 133 sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
134 JPanel destPanel = new JPanel(); 134 destClassLabel = new JLabel();
135 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); 135 destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
136 destPanel.setPreferredSize(new Dimension(200, 0)); 136
137 pane.add(destPanel, BorderLayout.WEST); 137 matchButton = new JButton();
138 destPanel.add(new JLabel("Destination Classes")); 138
139 139 advanceCheck = new JCheckBox("Advance to next likely match");
140 top10Matches = new JCheckBox("Show only top 10 matches"); 140 advanceCheck.addActionListener(event -> {
141 destPanel.add(top10Matches); 141 if (advanceCheck.isSelected()) {
142 top10Matches.addActionListener(event -> toggleTop10Matches()); 142 advance();
143 143 }
144 destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); 144 });
145 destClasses.setSelectionListener(this::setDestClass); 145
146 JScrollPane destScroller = new JScrollPane(destClasses); 146 bottomPanel.add(sourceClassLabel);
147 destPanel.add(destScroller); 147 bottomPanel.add(matchButton);
148 148 bottomPanel.add(destClassLabel);
149 JButton autoMatchButton = new JButton("AutoMatch"); 149 bottomPanel.add(advanceCheck);
150 autoMatchButton.addActionListener(event -> autoMatch()); 150 pane.add(bottomPanel, BorderLayout.SOUTH);
151 destPanel.add(autoMatchButton); 151
152 152 // show the frame
153 // init source panels 153 pane.doLayout();
154 DefaultSyntaxKit.initKit(); 154 frame.setSize(1024, 576);
155 sourceReader = new CodeReader(); 155 frame.setMinimumSize(new Dimension(640, 480));
156 destReader = new CodeReader(); 156 frame.setVisible(true);
157 157 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
158 // init all the splits 158
159 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane( 159 // init state
160 sourceReader)); 160 updateDestMappings();
161 splitLeft.setResizeWeight(0); // let the right side take all the slack 161 setSourceType(SourceType.getDefault());
162 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel); 162 updateMatchButton();
163 splitRight.setResizeWeight(1); // let the left side take all the slack 163 saveListener = null;
164 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); 164 }
165 splitCenter.setResizeWeight(0.5); // resize 50:50 165
166 pane.add(splitCenter, BorderLayout.CENTER); 166 public void setSaveListener(SaveListener val) {
167 splitCenter.resetToPreferredSizes(); 167 saveListener = val;
168 168 }
169 // init bottom panel 169
170 JPanel bottomPanel = new JPanel(); 170 private void updateDestMappings() {
171 bottomPanel.setLayout(new FlowLayout()); 171 try {
172 172 Mappings newMappings = MappingsConverter.newMappings(classMatches,
173 sourceClassLabel = new JLabel(); 173 sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator
174 sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); 174 );
175 destClassLabel = new JLabel(); 175
176 destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); 176 // look for dropped mappings
177 177 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
178 matchButton = new JButton(); 178 checker.dropBrokenMappings(newMappings);
179 179
180 advanceCheck = new JCheckBox("Advance to next likely match"); 180 // count them
181 advanceCheck.addActionListener(event -> { 181 int numDroppedFields = checker.getDroppedFieldMappings().size();
182 if (advanceCheck.isSelected()) { 182 int numDroppedMethods = checker.getDroppedMethodMappings().size();
183 advance(); 183 System.out.println(String.format(
184 } 184 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
185 }); 185 numDroppedFields + numDroppedMethods,
186 186 numDroppedFields,
187 bottomPanel.add(sourceClassLabel); 187 numDroppedMethods
188 bottomPanel.add(matchButton); 188 ));
189 bottomPanel.add(destClassLabel); 189
190 bottomPanel.add(advanceCheck); 190 destDeobfuscator.setMappings(newMappings);
191 pane.add(bottomPanel, BorderLayout.SOUTH); 191 } catch (MappingConflict ex) {
192 192 System.out.println(ex.getMessage());
193 // show the frame 193 ex.printStackTrace();
194 pane.doLayout(); 194 return;
195 frame.setSize(1024, 576); 195 }
196 frame.setMinimumSize(new Dimension(640, 480)); 196 }
197 frame.setVisible(true); 197
198 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 198 protected void setSourceType(SourceType val) {
199 199
200 // init state 200 // show the source classes
201 updateDestMappings(); 201 sourceType = val;
202 setSourceType(SourceType.getDefault()); 202 sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator));
203 updateMatchButton(); 203
204 saveListener = null; 204 // update counts
205 } 205 for (SourceType sourceType : SourceType.values()) {
206 206 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
207 public void setSaveListener(SaveListener val) { 207 sourceType.name(),
208 saveListener = val; 208 sourceType.getSourceClasses(classMatches).size()
209 } 209 ));
210 210 }
211 private void updateDestMappings() { 211 }
212 try { 212
213 Mappings newMappings = MappingsConverter.newMappings(classMatches, 213 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
214 sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator 214 List<ClassEntry> out = Lists.newArrayList();
215 ); 215 for (ClassEntry entry : in) {
216 216
217 // look for dropped mappings 217 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
218 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); 218
219 checker.dropBrokenMappings(newMappings); 219 // make sure we preserve any scores
220 220 if (entry instanceof ScoredClassEntry) {
221 // count them 221 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
222 int numDroppedFields = checker.getDroppedFieldMappings().size(); 222 }
223 int numDroppedMethods = checker.getDroppedMethodMappings().size(); 223
224 System.out.println(String.format( 224 out.add(deobf);
225 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods", 225 }
226 numDroppedFields + numDroppedMethods, 226 return out;
227 numDroppedFields, 227 }
228 numDroppedMethods 228
229 )); 229 protected void setSourceClass(ClassEntry classEntry) {
230 230
231 destDeobfuscator.setMappings(newMappings); 231 Runnable onGetDestClasses = null;
232 } catch (MappingConflict ex) { 232 if (advanceCheck.isSelected()) {
233 System.out.println(ex.getMessage()); 233 onGetDestClasses = this::pickBestDestClass;
234 ex.printStackTrace(); 234 }
235 return; 235
236 } 236 setSourceClass(classEntry, onGetDestClasses);
237 } 237 }
238 238
239 protected void setSourceType(SourceType val) { 239 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
240 240
241 // show the source classes 241 // update the current source class
242 sourceType = val; 242 sourceClass = classEntry;
243 sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator)); 243 sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : "");
244 244
245 // update counts 245 if (sourceClass != null) {
246 for (SourceType sourceType : SourceType.values()) { 246
247 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", 247 // show the dest class(es)
248 sourceType.name(), 248 ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass));
249 sourceType.getSourceClasses(classMatches).size() 249 assert (match != null);
250 )); 250 if (match.destClasses.isEmpty()) {
251 } 251
252 } 252 destClasses.setClasses(null);
253 253
254 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) { 254 // run in a separate thread to keep ui responsive
255 List<ClassEntry> out = Lists.newArrayList(); 255 new Thread(() ->
256 for (ClassEntry entry : in) { 256 {
257 257 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
258 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); 258 destClasses.expandAll();
259 259
260 // make sure we preserve any scores 260 if (onGetDestClasses != null) {
261 if (entry instanceof ScoredClassEntry) { 261 onGetDestClasses.run();
262 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore()); 262 }
263 } 263 }).start();
264 264
265 out.add(deobf); 265 } else {
266 } 266
267 return out; 267 destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator));
268 } 268 destClasses.expandAll();
269 269
270 protected void setSourceClass(ClassEntry classEntry) { 270 if (onGetDestClasses != null) {
271 271 onGetDestClasses.run();
272 Runnable onGetDestClasses = null; 272 }
273 if (advanceCheck.isSelected()) { 273 }
274 onGetDestClasses = this::pickBestDestClass; 274 }
275 } 275
276 276 setDestClass(null);
277 setSourceClass(classEntry, onGetDestClasses); 277 sourceReader.decompileClass(
278 } 278 sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass));
279 279
280 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { 280 updateMatchButton();
281 281 }
282 // update the current source class 282
283 sourceClass = classEntry; 283 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
284 sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : ""); 284
285 285 ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
286 if (sourceClass != null) { 286
287 287 // set up identifiers
288 // show the dest class(es) 288 ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches());
289 ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass)); 289 ClassIdentifier sourceIdentifier = new ClassIdentifier(
290 assert (match != null); 290 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
291 if (match.destClasses.isEmpty()) { 291 namer.getSourceNamer(), true
292 292 );
293 destClasses.setClasses(null); 293 ClassIdentifier destIdentifier = new ClassIdentifier(
294 294 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
295 // run in a separate thread to keep ui responsive 295 namer.getDestNamer(), true
296 new Thread(() -> 296 );
297 {
298 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
299 destClasses.expandAll();
300
301 if (onGetDestClasses != null) {
302 onGetDestClasses.run();
303 }
304 }).start();
305
306 } else {
307
308 destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator));
309 destClasses.expandAll();
310
311 if (onGetDestClasses != null) {
312 onGetDestClasses.run();
313 }
314 }
315 }
316
317 setDestClass(null);
318 sourceReader.decompileClass(
319 sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass));
320
321 updateMatchButton();
322 }
323
324 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
325
326 ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
327
328 // set up identifiers
329 ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches());
330 ClassIdentifier sourceIdentifier = new ClassIdentifier(
331 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
332 namer.getSourceNamer(), true
333 );
334 ClassIdentifier destIdentifier = new ClassIdentifier(
335 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
336 namer.getDestNamer(), true
337 );
338 297
339 try { 298 try {
340 299
341 // rank all the unmatched dest classes against the source class 300 // rank all the unmatched dest classes against the source class
342 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); 301 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
343 List<ClassEntry> scoredDestClasses = Lists.newArrayList(); 302 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
344 for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) { 303 for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) {
345 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); 304 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
346 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) 305 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
347 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); 306 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
348 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); 307 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
349 } 308 }
350 309
351 if (top10Matches.isSelected() && scoredDestClasses.size() > 10) { 310 if (top10Matches.isSelected() && scoredDestClasses.size() > 10) {
352 scoredDestClasses.sort((a, b) -> 311 scoredDestClasses.sort((a, b) ->
353 { 312 {
354 ScoredClassEntry sa = (ScoredClassEntry) a; 313 ScoredClassEntry sa = (ScoredClassEntry) a;
355 ScoredClassEntry sb = (ScoredClassEntry) b; 314 ScoredClassEntry sb = (ScoredClassEntry) b;
356 return -Float.compare(sa.getScore(), sb.getScore()); 315 return -Float.compare(sa.getScore(), sb.getScore());
357 }); 316 });
358 scoredDestClasses = scoredDestClasses.subList(0, 10); 317 scoredDestClasses = scoredDestClasses.subList(0, 10);
359 } 318 }
360 319
361 return scoredDestClasses; 320 return scoredDestClasses;
362 321
363 } catch (ClassNotFoundException ex) { 322 } catch (ClassNotFoundException ex) {
364 throw new Error("Unable to find class " + ex.getMessage()); 323 throw new Error("Unable to find class " + ex.getMessage());
365 } 324 }
366 } 325 }
367 326
368 protected void setDestClass(ClassEntry classEntry) { 327 protected void setDestClass(ClassEntry classEntry) {
369 328
370 // update the current source class 329 // update the current source class
371 destClass = classEntry; 330 destClass = classEntry;
372 destClassLabel.setText(destClass != null ? destClass.getName() : ""); 331 destClassLabel.setText(destClass != null ? destClass.getName() : "");
373 332
374 destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass)); 333 destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass));
375 334
376 updateMatchButton(); 335 updateMatchButton();
377 } 336 }
378 337
379 private void updateMatchButton() { 338 private void updateMatchButton() {
380 339
381 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); 340 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
382 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); 341 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
383 342
384 BiMap<ClassEntry, ClassEntry> uniqueMatches = classMatches.getUniqueMatches(); 343 BiMap<ClassEntry, ClassEntry> uniqueMatches = classMatches.getUniqueMatches();
385 boolean twoSelected = sourceClass != null && destClass != null; 344 boolean twoSelected = sourceClass != null && destClass != null;
386 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); 345 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
387 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest); 346 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
388 347
389 GuiTricks.deactivateButton(matchButton); 348 GuiTricks.deactivateButton(matchButton);
390 if (twoSelected) { 349 if (twoSelected) {
391 if (isMatched) { 350 if (isMatched) {
392 GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick()); 351 GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick());
393 } else if (canMatch) { 352 } else if (canMatch) {
394 GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick()); 353 GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick());
395 } 354 }
396 } 355 }
397 } 356 }
398 357
399 private void onMatchClick() { 358 private void onMatchClick() {
400 // precondition: source and dest classes are set correctly 359 // precondition: source and dest classes are set correctly
401 360
402 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); 361 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
403 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); 362 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
404 363
405 // remove the classes from their match 364 // remove the classes from their match
406 classMatches.removeSource(obfSource); 365 classMatches.removeSource(obfSource);
407 classMatches.removeDest(obfDest); 366 classMatches.removeDest(obfDest);
408 367
409 // add them as matched classes 368 // add them as matched classes
410 classMatches.add(new ClassMatch(obfSource, obfDest)); 369 classMatches.add(new ClassMatch(obfSource, obfDest));
411 370
412 ClassEntry nextClass = null; 371 ClassEntry nextClass = null;
413 if (advanceCheck.isSelected()) { 372 if (advanceCheck.isSelected()) {
414 nextClass = sourceClasses.getNextClass(sourceClass); 373 nextClass = sourceClasses.getNextClass(sourceClass);
415 } 374 }
416 375
417 save(); 376 save();
418 updateMatches(); 377 updateMatches();
419 378
420 if (nextClass != null) { 379 if (nextClass != null) {
421 advance(nextClass); 380 advance(nextClass);
422 } 381 }
423 } 382 }
424 383
425 private void onUnmatchClick() { 384 private void onUnmatchClick() {
426 // precondition: source and dest classes are set to a unique match 385 // precondition: source and dest classes are set to a unique match
427 386
428 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); 387 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
429 388
430 // remove the source to break the match, then add the source back as unmatched 389 // remove the source to break the match, then add the source back as unmatched
431 classMatches.removeSource(obfSource); 390 classMatches.removeSource(obfSource);
432 classMatches.add(new ClassMatch(obfSource, null)); 391 classMatches.add(new ClassMatch(obfSource, null));
433 392
434 save(); 393 save();
435 updateMatches(); 394 updateMatches();
436 } 395 }
437 396
438 private void updateMatches() { 397 private void updateMatches() {
439 updateDestMappings(); 398 updateDestMappings();
440 setDestClass(null); 399 setDestClass(null);
441 destClasses.setClasses(null); 400 destClasses.setClasses(null);
442 updateMatchButton(); 401 updateMatchButton();
443 402
444 // remember where we were in the source tree 403 // remember where we were in the source tree
445 String packageName = sourceClasses.getSelectedPackage(); 404 String packageName = sourceClasses.getSelectedPackage();
446 405
447 setSourceType(sourceType); 406 setSourceType(sourceType);
448 407
449 sourceClasses.expandPackage(packageName); 408 sourceClasses.expandPackage(packageName);
450 } 409 }
451 410
452 private void save() { 411 private void save() {
453 if (saveListener != null) { 412 if (saveListener != null) {
454 saveListener.save(classMatches); 413 saveListener.save(classMatches);
455 } 414 }
456 } 415 }
457 416
458 private void autoMatch() { 417 private void autoMatch() {
459 418
460 System.out.println("Automatching..."); 419 System.out.println("Automatching...");
461 420
462 // compute a new matching 421 // compute a new matching
463 ClassMatching matching = MappingsConverter.computeMatching( 422 ClassMatching matching = MappingsConverter.computeMatching(
464 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), 423 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
465 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), 424 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
466 classMatches.getUniqueMatches() 425 classMatches.getUniqueMatches()
467 ); 426 );
468 ClassMatches newMatches = new ClassMatches(matching.matches()); 427 ClassMatches newMatches = new ClassMatches(matching.matches());
469 System.out.println(String.format("Automatch found %d new matches", 428 System.out.println(String.format("Automatch found %d new matches",
470 newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size() 429 newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size()
471 )); 430 ));
472 431
473 // update the current matches 432 // update the current matches
474 classMatches = newMatches; 433 classMatches = newMatches;
475 save(); 434 save();
476 updateMatches(); 435 updateMatches();
477 } 436 }
478 437
479 private void advance() { 438 private void advance() {
480 advance(null); 439 advance(null);
481 } 440 }
482 441
483 private void advance(ClassEntry sourceClass) { 442 private void advance(ClassEntry sourceClass) {
484 443
485 // make sure we have a source class 444 // make sure we have a source class
486 if (sourceClass == null) { 445 if (sourceClass == null) {
487 sourceClass = sourceClasses.getSelectedClass(); 446 sourceClass = sourceClasses.getSelectedClass();
488 if (sourceClass != null) { 447 if (sourceClass != null) {
489 sourceClass = sourceClasses.getNextClass(sourceClass); 448 sourceClass = sourceClasses.getNextClass(sourceClass);
490 } else { 449 } else {
491 sourceClass = sourceClasses.getFirstClass(); 450 sourceClass = sourceClasses.getFirstClass();
492 } 451 }
493 } 452 }
494 453
495 // set the source class 454 // set the source class
496 setSourceClass(sourceClass, this::pickBestDestClass); 455 setSourceClass(sourceClass, this::pickBestDestClass);
497 sourceClasses.setSelectionClass(sourceClass); 456 sourceClasses.setSelectionClass(sourceClass);
498 } 457 }
499 458
500 private void pickBestDestClass() { 459 private void pickBestDestClass() {
501 460
502 // then, pick the best dest class 461 // then, pick the best dest class
503 ClassEntry firstClass = null; 462 ClassEntry firstClass = null;
504 ScoredClassEntry bestDestClass = null; 463 ScoredClassEntry bestDestClass = null;
505 for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) { 464 for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) {
506 for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) { 465 for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) {
507 if (firstClass == null) { 466 if (firstClass == null) {
508 firstClass = classNode.getClassEntry(); 467 firstClass = classNode.getClassEntry();
509 } 468 }
510 if (classNode.getClassEntry() instanceof ScoredClassEntry) { 469 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
511 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry(); 470 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
512 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { 471 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
513 bestDestClass = scoredClass; 472 bestDestClass = scoredClass;
514 } 473 }
515 } 474 }
516 } 475 }
517 } 476 }
518 477
519 // pick the entry to show 478 // pick the entry to show
520 ClassEntry destClass = null; 479 ClassEntry destClass = null;
521 if (bestDestClass != null) { 480 if (bestDestClass != null) {
522 destClass = bestDestClass; 481 destClass = bestDestClass;
523 } else if (firstClass != null) { 482 } else if (firstClass != null) {
524 destClass = firstClass; 483 destClass = firstClass;
525 } 484 }
526 485
527 setDestClass(destClass); 486 setDestClass(destClass);
528 destClasses.setSelectionClass(destClass); 487 destClasses.setSelectionClass(destClass);
529 } 488 }
530 489
531 private void toggleTop10Matches() { 490 private void toggleTop10Matches() {
532 if (sourceClass != null) { 491 if (sourceClass != null) {
533 destClasses.clearSelection(); 492 destClasses.clearSelection();
534 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); 493 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
535 destClasses.expandAll(); 494 destClasses.expandAll();
536 } 495 }
537 } 496 }
497
498 private enum SourceType {
499 Matched {
500 @Override
501 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
502 return matches.getUniqueMatches().keySet();
503 }
504 },
505 Unmatched {
506 @Override
507 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
508 return matches.getUnmatchedSourceClasses();
509 }
510 },
511 Ambiguous {
512 @Override
513 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
514 return matches.getAmbiguouslyMatchedSourceClasses();
515 }
516 };
517
518 public static SourceType getDefault() {
519 return values()[0];
520 }
521
522 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
523 JRadioButton button = new JRadioButton(name(), this == getDefault());
524 button.setActionCommand(name());
525 button.addActionListener(listener);
526 group.add(button);
527 return button;
528 }
529
530 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
531 }
532
533 public interface SaveListener {
534 void save(ClassMatches matches);
535 }
538} 536}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 8ece0a0..f7d7703 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.google.common.collect.ArrayListMultimap; 14import com.google.common.collect.ArrayListMultimap;
@@ -29,505 +30,470 @@ import java.util.*;
29 30
30public class ClassSelector extends JTree { 31public class ClassSelector extends JTree {
31 32
32 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); 33 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName);
33 private DefaultMutableTreeNode rootNodes; 34 private DefaultMutableTreeNode rootNodes;
34 35 private ClassSelectionListener selectionListener;
35 public interface ClassSelectionListener { 36 private RenameSelectionListener renameSelectionListener;
36 void onSelectClass(ClassEntry classEntry); 37 private Comparator<ClassEntry> comparator;
37 } 38 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) {
38 39 this.comparator = comparator;
39 public interface RenameSelectionListener { 40
40 void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); 41 // configure the tree control
41 } 42 setEditable(gui != null);
42 43 setRootVisible(false);
43 private ClassSelectionListener selectionListener; 44 setShowsRootHandles(false);
44 private RenameSelectionListener renameSelectionListener; 45 setModel(null);
45 private Comparator<ClassEntry> comparator; 46
46 47 // hook events
47 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) { 48 addMouseListener(new MouseAdapter() {
48 this.comparator = comparator; 49 @Override
49 50 public void mouseClicked(MouseEvent event) {
50 // configure the tree control 51 if (selectionListener != null && event.getClickCount() == 2) {
51 setEditable(gui != null); 52 // get the selected node
52 setRootVisible(false); 53 TreePath path = getSelectionPath();
53 setShowsRootHandles(false); 54 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
54 setModel(null); 55 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
55 56 selectionListener.onSelectClass(node.getClassEntry());
56 // hook events 57 }
57 addMouseListener(new MouseAdapter() { 58 }
58 @Override 59 }
59 public void mouseClicked(MouseEvent event) { 60 });
60 if (selectionListener != null && event.getClickCount() == 2) { 61
61 // get the selected node 62 if (gui != null) {
62 TreePath path = getSelectionPath(); 63 final JTree tree = this;
63 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { 64
64 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); 65 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
65 selectionListener.onSelectClass(node.getClassEntry()); 66 (DefaultTreeCellRenderer) tree.getCellRenderer()) {
66 } 67 @Override
67 } 68 public boolean isCellEditable(EventObject event) {
68 } 69 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
69 }); 70 }
70 71 };
71 if (gui != null) 72 this.setCellEditor(editor);
72 { 73 editor.addCellEditorListener(new CellEditorListener() {
73 final JTree tree = this; 74 @Override
74 75 public void editingStopped(ChangeEvent e) {
75 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, 76 String data = editor.getCellEditorValue().toString();
76 (DefaultTreeCellRenderer) tree.getCellRenderer()) 77 TreePath path = getSelectionPath();
77 { 78
78 @Override public boolean isCellEditable(EventObject event) 79 Object realPath = path.getLastPathComponent();
79 { 80 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
80 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); 81 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
81 } 82 TreeNode parentNode = node.getParent();
82 }; 83 if (parentNode == null)
83 this.setCellEditor(editor); 84 return;
84 editor.addCellEditorListener(new CellEditorListener() 85 boolean allowEdit = true;
85 { 86 for (int i = 0; i < parentNode.getChildCount(); i++) {
86 @Override public void editingStopped(ChangeEvent e) 87 TreeNode childNode = parentNode.getChildAt(i);
87 { 88 if (childNode != null && childNode.toString().equals(data) && childNode != node) {
88 String data = editor.getCellEditorValue().toString(); 89 allowEdit = false;
89 TreePath path = getSelectionPath(); 90 break;
90 91 }
91 Object realPath = path.getLastPathComponent(); 92 }
92 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) 93 if (allowEdit && renameSelectionListener != null) {
93 { 94 Object prevData = node.getUserObject();
94 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; 95 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
95 TreeNode parentNode = node.getParent(); 96 try {
96 if (parentNode == null) 97 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
97 return; 98 node.setUserObject(objectData); // Make sure that it's modified
98 boolean allowEdit = true; 99 } catch (IllegalNameException ex) {
99 for (int i = 0; i < parentNode.getChildCount(); i++) 100 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
100 { 101 JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK");
101 TreeNode childNode = parentNode.getChildAt(i); 102 editor.cancelCellEditing();
102 if (childNode != null && childNode.toString().equals(data) && childNode != node) 103 }
103 { 104 } else
104 allowEdit = false; 105 editor.cancelCellEditing();
105 break; 106 }
106 } 107
107 } 108 }
108 if (allowEdit && renameSelectionListener != null) 109
109 { 110 @Override
110 Object prevData = node.getUserObject(); 111 public void editingCanceled(ChangeEvent e) {
111 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry)prevData).getPackageName() + "/" + data) : data; 112 // NOP
112 try 113 }
113 { 114 });
114 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); 115 }
115 node.setUserObject(objectData); // Make sure that it's modified 116 // init defaults
116 } catch (IllegalNameException ex) 117 this.selectionListener = null;
117 { 118 this.renameSelectionListener = null;
118 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, 119 }
119 JOptionPane.ERROR_MESSAGE, null, new String[] {"Ok"}, "OK"); 120
120 editor.cancelCellEditing(); 121 public boolean isDuplicate(Object[] nodes, String data) {
121 } 122 int count = 0;
122 } 123
123 else 124 for (Object node : nodes) {
124 editor.cancelCellEditing(); 125 if (node.toString().equals(data)) {
125 } 126 count++;
126 127 if (count == 2)
127 } 128 return true;
128 129 }
129 @Override public void editingCanceled(ChangeEvent e) 130 }
130 { 131 return false;
131 // NOP 132 }
132 } 133
133 }); 134 public void setSelectionListener(ClassSelectionListener val) {
134 } 135 this.selectionListener = val;
135 // init defaults 136 }
136 this.selectionListener = null; 137
137 this.renameSelectionListener = null; 138 public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) {
138 } 139 this.renameSelectionListener = renameSelectionListener;
139 140 }
140 public boolean isDuplicate(Object[] nodes, String data) 141
141 { 142 public void setClasses(Collection<ClassEntry> classEntries) {
142 int count = 0; 143 String state = getExpansionState(this, 0);
143 144 if (classEntries == null) {
144 for (Object node : nodes) 145 setModel(null);
145 { 146 return;
146 if (node.toString().equals(data)) 147 }
147 { 148
148 count++; 149 // build the package names
149 if (count == 2) 150 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
150 return true; 151 for (ClassEntry classEntry : classEntries) {
151 } 152 packages.put(classEntry.getPackageName(), null);
152 } 153 }
153 return false; 154
154 } 155 // sort the packages
155 156 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
156 public void setSelectionListener(ClassSelectionListener val) { 157 sortedPackageNames.sort((a, b) ->
157 this.selectionListener = val; 158 {
158 } 159 // I can never keep this rule straight when writing these damn things...
159 160 // a < b => -1, a == b => 0, a > b => +1
160 public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) 161
161 { 162 if (b == null || a == null) {
162 this.renameSelectionListener = renameSelectionListener; 163 return 0;
163 } 164 }
164 165
165 public void setClasses(Collection<ClassEntry> classEntries) { 166 String[] aparts = a.split("/");
166 String state = getExpansionState(this, 0); 167 String[] bparts = b.split("/");
167 if (classEntries == null) { 168 for (int i = 0; true; i++) {
168 setModel(null); 169 if (i >= aparts.length) {
169 return; 170 return -1;
170 } 171 } else if (i >= bparts.length) {
171 172 return 1;
172 // build the package names 173 }
173 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap(); 174
174 for (ClassEntry classEntry : classEntries) { 175 int result = aparts[i].compareTo(bparts[i]);
175 packages.put(classEntry.getPackageName(), null); 176 if (result != 0) {
176 } 177 return result;
177 178 }
178 // sort the packages 179 }
179 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet()); 180 });
180 sortedPackageNames.sort((a, b) -> 181
181 { 182 // create the rootNodes node and the package nodes
182 // I can never keep this rule straight when writing these damn things... 183 rootNodes = new DefaultMutableTreeNode();
183 // a < b => -1, a == b => 0, a > b => +1 184 for (String packageName : sortedPackageNames) {
184 185 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
185 if (b == null || a == null) 186 packages.put(packageName, node);
186 { 187 rootNodes.add(node);
187 return 0; 188 }
188 } 189
189 190 // put the classes into packages
190 String[] aparts = a.split("/"); 191 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
191 String[] bparts = b.split("/"); 192 for (ClassEntry classEntry : classEntries) {
192 for (int i = 0; true; i++) 193 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
193 { 194 }
194 if (i >= aparts.length) 195
195 { 196 // build the class nodes
196 return -1; 197 for (String packageName : packagedClassEntries.keySet()) {
197 } 198 // sort the class entries
198 else if (i >= bparts.length) 199 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
199 { 200 classEntriesInPackage.sort(this.comparator);
200 return 1; 201
201 } 202 // create the nodes in order
202 203 for (ClassEntry classEntry : classEntriesInPackage) {
203 int result = aparts[i].compareTo(bparts[i]); 204 ClassSelectorPackageNode node = packages.get(packageName);
204 if (result != 0) 205 node.add(new ClassSelectorClassNode(classEntry));
205 { 206 }
206 return result; 207 }
207 } 208
208 } 209 // finally, update the tree control
209 }); 210 setModel(new DefaultTreeModel(rootNodes));
210 211
211 // create the rootNodes node and the package nodes 212 restoreExpanstionState(this, 0, state);
212 rootNodes = new DefaultMutableTreeNode(); 213 }
213 for (String packageName : sortedPackageNames) { 214
214 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); 215 public ClassEntry getSelectedClass() {
215 packages.put(packageName, node); 216 if (!isSelectionEmpty()) {
216 rootNodes.add(node); 217 Object selectedNode = getSelectionPath().getLastPathComponent();
217 } 218 if (selectedNode instanceof ClassSelectorClassNode) {
218 219 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
219 // put the classes into packages 220 return classNode.getClassEntry();
220 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create(); 221 }
221 for (ClassEntry classEntry : classEntries) { 222 }
222 packagedClassEntries.put(classEntry.getPackageName(), classEntry); 223 return null;
223 } 224 }
224 225
225 // build the class nodes 226 public String getSelectedPackage() {
226 for (String packageName : packagedClassEntries.keySet()) { 227 if (!isSelectionEmpty()) {
227 // sort the class entries 228 Object selectedNode = getSelectionPath().getLastPathComponent();
228 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); 229 if (selectedNode instanceof ClassSelectorPackageNode) {
229 classEntriesInPackage.sort(this.comparator); 230 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode;
230 231 return packageNode.getPackageName();
231 // create the nodes in order 232 } else if (selectedNode instanceof ClassSelectorClassNode) {
232 for (ClassEntry classEntry : classEntriesInPackage) { 233 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
233 ClassSelectorPackageNode node = packages.get(packageName); 234 return classNode.getClassEntry().getPackageName();
234 node.add(new ClassSelectorClassNode(classEntry)); 235 }
235 } 236 }
236 } 237 return null;
237 238 }
238 // finally, update the tree control 239
239 setModel(new DefaultTreeModel(rootNodes)); 240 public boolean isDescendant(TreePath path1, TreePath path2) {
240 241 int count1 = path1.getPathCount();
241 restoreExpanstionState(this, 0, state); 242 int count2 = path2.getPathCount();
242 } 243 if (count1 <= count2) {
243 244 return false;
244 public ClassEntry getSelectedClass() { 245 }
245 if (!isSelectionEmpty()) { 246 while (count1 != count2) {
246 Object selectedNode = getSelectionPath().getLastPathComponent(); 247 path1 = path1.getParentPath();
247 if (selectedNode instanceof ClassSelectorClassNode) { 248 count1--;
248 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; 249 }
249 return classNode.getClassEntry(); 250 return path1.equals(path2);
250 } 251 }
251 } 252
252 return null; 253 public String getExpansionState(JTree tree, int row) {
253 } 254 TreePath rowPath = tree.getPathForRow(row);
254 255 StringBuilder buf = new StringBuilder();
255 public String getSelectedPackage() { 256 int rowCount = tree.getRowCount();
256 if (!isSelectionEmpty()) { 257 for (int i = row; i < rowCount; i++) {
257 Object selectedNode = getSelectionPath().getLastPathComponent(); 258 TreePath path = tree.getPathForRow(i);
258 if (selectedNode instanceof ClassSelectorPackageNode) { 259 if (i == row || isDescendant(path, rowPath)) {
259 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode; 260 if (tree.isExpanded(path)) {
260 return packageNode.getPackageName(); 261 buf.append(",").append((i - row));
261 } else if (selectedNode instanceof ClassSelectorClassNode) { 262 }
262 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; 263 } else {
263 return classNode.getClassEntry().getPackageName(); 264 break;
264 } 265 }
265 } 266 }
266 return null; 267 return buf.toString();
267 } 268 }
268 269
269 public boolean isDescendant(TreePath path1, TreePath path2) { 270 public void restoreExpanstionState(JTree tree, int row, String expansionState) {
270 int count1 = path1.getPathCount(); 271 StringTokenizer stok = new StringTokenizer(expansionState, ",");
271 int count2 = path2.getPathCount(); 272 while (stok.hasMoreTokens()) {
272 if (count1 <= count2) { 273 int token = row + Integer.parseInt(stok.nextToken());
273 return false; 274 tree.expandRow(token);
274 } 275 }
275 while (count1 != count2) { 276 }
276 path1 = path1.getParentPath(); 277
277 count1--; 278 public List<ClassSelectorPackageNode> packageNodes() {
278 } 279 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
279 return path1.equals(path2); 280 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
280 } 281 Enumeration<?> children = root.children();
281 282 while (children.hasMoreElements()) {
282 public String getExpansionState(JTree tree, int row) { 283 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
283 TreePath rowPath = tree.getPathForRow(row); 284 nodes.add(packageNode);
284 StringBuilder buf = new StringBuilder(); 285 }
285 int rowCount = tree.getRowCount(); 286 return nodes;
286 for (int i = row; i < rowCount; i++) { 287 }
287 TreePath path = tree.getPathForRow(i); 288
288 if (i == row || isDescendant(path, rowPath)) { 289 public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
289 if (tree.isExpanded(path)) { 290 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
290 buf.append(",").append(String.valueOf(i - row)); 291 Enumeration<?> children = packageNode.children();
291 } 292 while (children.hasMoreElements()) {
292 } else { 293 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
293 break; 294 nodes.add(classNode);
294 } 295 }
295 } 296 return nodes;
296 return buf.toString(); 297 }
297 } 298
298 299 public void expandPackage(String packageName) {
299 public void restoreExpanstionState(JTree tree, int row, String expansionState) { 300 if (packageName == null) {
300 StringTokenizer stok = new StringTokenizer(expansionState, ","); 301 return;
301 while (stok.hasMoreTokens()) { 302 }
302 int token = row + Integer.parseInt(stok.nextToken()); 303 for (ClassSelectorPackageNode packageNode : packageNodes()) {
303 tree.expandRow(token); 304 if (packageNode.getPackageName().equals(packageName)) {
304 } 305 expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode }));
305 } 306 return;
306 307 }
307 public List<ClassSelectorPackageNode> packageNodes() { 308 }
308 List<ClassSelectorPackageNode> nodes = Lists.newArrayList(); 309 }
309 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot(); 310
310 Enumeration<?> children = root.children(); 311 public void expandAll() {
311 while (children.hasMoreElements()) { 312 for (ClassSelectorPackageNode packageNode : packageNodes()) {
312 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement(); 313 expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode }));
313 nodes.add(packageNode); 314 }
314 } 315 }
315 return nodes; 316
316 } 317 public ClassEntry getFirstClass() {
317 318 for (ClassSelectorPackageNode packageNode : packageNodes()) {
318 public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) { 319 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
319 List<ClassSelectorClassNode> nodes = Lists.newArrayList(); 320 return classNode.getClassEntry();
320 Enumeration<?> children = packageNode.children(); 321 }
321 while (children.hasMoreElements()) { 322 }
322 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement(); 323 return null;
323 nodes.add(classNode); 324 }
324 } 325
325 return nodes; 326 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
326 } 327 for (ClassSelectorPackageNode packageNode : packageNodes()) {
327 328 if (packageNode.getPackageName().equals(entry.getPackageName())) {
328 public void expandPackage(String packageName) { 329 return packageNode;
329 if (packageName == null) { 330 }
330 return; 331 }
331 } 332 return null;
332 for (ClassSelectorPackageNode packageNode : packageNodes()) { 333 }
333 if (packageNode.getPackageName().equals(packageName)) { 334
334 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); 335 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
335 return; 336 ClassSelectorPackageNode packageNode = getPackageNode(entry);
336 } 337
337 } 338 if (selector != null && packageNode == null && selector.getPackageNode(entry) != null)
338 } 339 return selector.getPackageNode(entry);
339 340 return packageNode;
340 public void expandAll() { 341 }
341 for (ClassSelectorPackageNode packageNode : packageNodes()) { 342
342 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); 343 public ClassEntry getNextClass(ClassEntry entry) {
343 } 344 boolean foundIt = false;
344 } 345 for (ClassSelectorPackageNode packageNode : packageNodes()) {
345 346 if (!foundIt) {
346 public ClassEntry getFirstClass() { 347 // skip to the package with our target in it
347 for (ClassSelectorPackageNode packageNode : packageNodes()) { 348 if (packageNode.getPackageName().equals(entry.getPackageName())) {
348 for (ClassSelectorClassNode classNode : classNodes(packageNode)) { 349 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
349 return classNode.getClassEntry(); 350 if (!foundIt) {
350 } 351 if (classNode.getClassEntry().equals(entry)) {
351 } 352 foundIt = true;
352 return null; 353 }
353 } 354 } else {
354 355 // return the next class
355 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { 356 return classNode.getClassEntry();
356 for (ClassSelectorPackageNode packageNode : packageNodes()) { 357 }
357 if (packageNode.getPackageName().equals(entry.getPackageName())) { 358 }
358 return packageNode; 359 }
359 } 360 } else {
360 } 361 // return the next class
361 return null; 362 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
362 } 363 return classNode.getClassEntry();
363 364 }
364 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) 365 }
365 { 366 }
366 ClassSelectorPackageNode packageNode = getPackageNode(entry); 367 return null;
367 368 }
368 if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) 369
369 return selector.getPackageNode(entry); 370 public void setSelectionClass(ClassEntry classEntry) {
370 return packageNode; 371 expandPackage(classEntry.getPackageName());
371 } 372 for (ClassSelectorPackageNode packageNode : packageNodes()) {
372 373 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
373 public ClassEntry getNextClass(ClassEntry entry) { 374 if (classNode.getClassEntry().equals(classEntry)) {
374 boolean foundIt = false; 375 setSelectionPath(new TreePath(new Object[] { getModel().getRoot(), packageNode, classNode }));
375 for (ClassSelectorPackageNode packageNode : packageNodes()) { 376 }
376 if (!foundIt) { 377 }
377 // skip to the package with our target in it 378 }
378 if (packageNode.getPackageName().equals(entry.getPackageName())) { 379 }
379 for (ClassSelectorClassNode classNode : classNodes(packageNode)) { 380
380 if (!foundIt) { 381 public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) {
381 if (classNode.getClassEntry().equals(entry)) { 382 DefaultTreeModel model = (DefaultTreeModel) getModel();
382 foundIt = true; 383
383 } 384 if (packageNode == null)
384 } else { 385 return;
385 // return the next class 386
386 return classNode.getClassEntry(); 387 for (int i = 0; i < packageNode.getChildCount(); i++) {
387 } 388 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
388 } 389 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
389 } 390 model.removeNodeFromParent(childNode);
390 } else { 391 break;
391 // return the next class 392 }
392 for (ClassSelectorClassNode classNode : classNodes(packageNode)) { 393 }
393 return classNode.getClassEntry(); 394 }
394 } 395
395 } 396 public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) {
396 } 397 if (packageNode != null && packageNode.getChildCount() == 0)
397 return null; 398 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
398 } 399 }
399 400
400 public void setSelectionClass(ClassEntry classEntry) { 401 public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) {
401 expandPackage(classEntry.getPackageName()); 402 if (otherSelector == null)
402 for (ClassSelectorPackageNode packageNode : packageNodes()) { 403 removeNode(getPackageNode(oldClassEntry), oldClassEntry);
403 for (ClassSelectorClassNode classNode : classNodes(packageNode)) { 404 insertNode(getOrCreate(newClassEntry), newClassEntry);
404 if (classNode.getClassEntry().equals(classEntry)) { 405 }
405 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode})); 406
406 } 407 public ClassSelectorPackageNode getOrCreate(ClassEntry entry) {
407 } 408 DefaultTreeModel model = (DefaultTreeModel) getModel();
408 } 409 ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
409 } 410 if (newPackageNode == null) {
410 411 newPackageNode = new ClassSelectorPackageNode(entry.getPackageName());
411 public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) 412 model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode));
412 { 413 }
413 DefaultTreeModel model = (DefaultTreeModel) getModel(); 414 return newPackageNode;
414 415 }
415 if (packageNode == null) 416
416 return; 417 public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) {
417 418 DefaultTreeModel model = (DefaultTreeModel) getModel();
418 for (int i = 0; i < packageNode.getChildCount(); i++) 419 ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry);
419 { 420 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
420 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); 421 }
421 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) 422
422 { 423 public void reload() {
423 model.removeNodeFromParent(childNode); 424 DefaultTreeModel model = (DefaultTreeModel) getModel();
424 break; 425 model.reload(sort(rootNodes));
425 } 426 }
426 } 427
427 } 428 private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) {
428 429
429 public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) 430 for (int i = 0; i < node.getChildCount() - 1; i++) {
430 { 431 DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
431 if (packageNode != null && packageNode.getChildCount() == 0) 432 if (child == null)
432 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); 433 continue;
433 } 434 String nt = child.toString();
434 435
435 public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) 436 for (int j = i + 1; j <= node.getChildCount() - 1; j++) {
436 { 437 DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j);
437 if (otherSelector == null) 438 if (prevNode == null || prevNode.getUserObject() == null)
438 removeNode(getPackageNode(oldClassEntry), oldClassEntry); 439 continue;
439 insertNode(getOrCreate(newClassEntry), newClassEntry); 440 String np = prevNode.getUserObject().toString();
440 } 441
441 442 if (nt.compareToIgnoreCase(np) > 0) {
442 public ClassSelectorPackageNode getOrCreate(ClassEntry entry) 443 node.insert(child, j);
443 { 444 node.insert(prevNode, i);
444 DefaultTreeModel model = (DefaultTreeModel) getModel(); 445 }
445 ClassSelectorPackageNode newPackageNode = getPackageNode(entry); 446 }
446 if (newPackageNode == null) 447 if (child.getChildCount() > 0) {
447 { 448 sort(child);
448 newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); 449 }
449 model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); 450 }
450 } 451
451 return newPackageNode; 452 for (int i = 0; i < node.getChildCount() - 1; i++) {
452 } 453 DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
453 454 for (int j = i + 1; j <= node.getChildCount() - 1; j++) {
454 public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) 455 DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j);
455 { 456
456 DefaultTreeModel model = (DefaultTreeModel) getModel(); 457 if (!prevNode.isLeaf() && child.isLeaf()) {
457 ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); 458 node.insert(child, j);
458 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); 459 node.insert(prevNode, i);
459 } 460 }
460 461 }
461 public void reload() 462 }
462 { 463
463 DefaultTreeModel model = (DefaultTreeModel) getModel(); 464 return node;
464 model.reload(sort(rootNodes)); 465 }
465 } 466
466 467 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) {
467 private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { 468 List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode);
468 469 classNodes.add(classNode);
469 for(int i = 0; i < node.getChildCount() - 1; i++) { 470 classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString));
470 DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); 471 for (int i = 0; i < classNodes.size(); i++)
471 if (child == null) 472 if (classNodes.get(i) == classNode)
472 continue; 473 return i;
473 String nt = child.toString(); 474
474 475 return 0;
475 for(int j = i + 1; j <= node.getChildCount() - 1; j++) { 476 }
476 DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); 477
477 if (prevNode == null || prevNode.getUserObject() == null) 478 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) {
478 continue; 479 List<ClassSelectorPackageNode> packageNodes = packageNodes();
479 String np = prevNode.getUserObject().toString(); 480 if (!packageNodes.contains(newPackageNode)) {
480 481 packageNodes.add(newPackageNode);
481 if(nt.compareToIgnoreCase(np) > 0) { 482 packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString));
482 node.insert(child, j); 483 }
483 node.insert(prevNode, i); 484
484 } 485 for (int i = 0; i < packageNodes.size(); i++)
485 } 486 if (packageNodes.get(i) == newPackageNode)
486 if(child.getChildCount() > 0) { 487 return i;
487 sort(child); 488
488 } 489 return 0;
489 } 490 }
490 491
491 for(int i = 0; i < node.getChildCount() - 1; i++) { 492 public interface ClassSelectionListener {
492 DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); 493 void onSelectClass(ClassEntry classEntry);
493 for(int j = i + 1; j <= node.getChildCount() - 1; j++) { 494 }
494 DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); 495
495 496 public interface RenameSelectionListener {
496 if(!prevNode.isLeaf() && child.isLeaf()) { 497 void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node);
497 node.insert(child, j); 498 }
498 node.insert(prevNode, i);
499 }
500 }
501 }
502
503 return node;
504 }
505
506 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode)
507 {
508 List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode);
509 classNodes.add(classNode);
510 classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString));
511 for (int i = 0; i < classNodes.size(); i++)
512 if (classNodes.get(i) == classNode)
513 return i;
514
515 return 0;
516 }
517
518 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode)
519 {
520 List<ClassSelectorPackageNode> packageNodes = packageNodes();
521 if (!packageNodes.contains(newPackageNode))
522 {
523 packageNodes.add(newPackageNode);
524 packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString));
525 }
526
527 for (int i = 0; i < packageNodes.size(); i++)
528 if (packageNodes.get(i) == newPackageNode)
529 return i;
530
531 return 0;
532 }
533} 499}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
index 8225d8f..2e235dc 100644
--- a/src/main/java/cuchaz/enigma/gui/CodeReader.java
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -8,20 +8,10 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.strobel.decompiler.languages.java.ast.CompilationUnit; 14import com.strobel.decompiler.languages.java.ast.CompilationUnit;
14
15import java.awt.Rectangle;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18
19import javax.swing.JEditorPane;
20import javax.swing.SwingUtilities;
21import javax.swing.Timer;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter.HighlightPainter;
24
25import cuchaz.enigma.Deobfuscator; 15import cuchaz.enigma.Deobfuscator;
26import cuchaz.enigma.analysis.EntryReference; 16import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.SourceIndex; 17import cuchaz.enigma.analysis.SourceIndex;
@@ -31,180 +21,184 @@ import cuchaz.enigma.mapping.ClassEntry;
31import cuchaz.enigma.mapping.Entry; 21import cuchaz.enigma.mapping.Entry;
32import de.sciss.syntaxpane.DefaultSyntaxKit; 22import de.sciss.syntaxpane.DefaultSyntaxKit;
33 23
24import javax.swing.*;
25import javax.swing.text.BadLocationException;
26import javax.swing.text.Highlighter.HighlightPainter;
27import java.awt.*;
28import java.awt.event.ActionEvent;
29import java.awt.event.ActionListener;
34 30
35public class CodeReader extends JEditorPane { 31public class CodeReader extends JEditorPane {
36 32
37 private static final long serialVersionUID = 3673180950485748810L; 33 private static final long serialVersionUID = 3673180950485748810L;
38 34
39 private static final Object lock = new Object(); 35 private static final Object lock = new Object();
40 36 private SelectionHighlightPainter selectionHighlightPainter;
41 public interface SelectionListener { 37 private SourceIndex sourceIndex;
42 void onSelect(EntryReference<Entry, Entry> reference); 38 private SelectionListener selectionListener;
43 } 39 public CodeReader() {
44 40
45 private SelectionHighlightPainter selectionHighlightPainter; 41 setEditable(false);
46 private SourceIndex sourceIndex; 42 setContentType("text/java");
47 private SelectionListener selectionListener; 43
48 44 // turn off token highlighting (it's wrong most of the time anyway...)
49 public CodeReader() { 45 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
50 46 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
51 setEditable(false); 47
52 setContentType("text/java"); 48 // hook events
53 49 addCaretListener(event ->
54 // turn off token highlighting (it's wrong most of the time anyway...) 50 {
55 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); 51 if (selectionListener != null && sourceIndex != null) {
56 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); 52 Token token = sourceIndex.getReferenceToken(event.getDot());
57 53 if (token != null) {
58 // hook events 54 selectionListener.onSelect(sourceIndex.getDeobfReference(token));
59 addCaretListener(event -> 55 } else {
60 { 56 selectionListener.onSelect(null);
61 if (selectionListener != null && sourceIndex != null) { 57 }
62 Token token = sourceIndex.getReferenceToken(event.getDot()); 58 }
63 if (token != null) { 59 });
64 selectionListener.onSelect(sourceIndex.getDeobfReference(token)); 60
65 } else { 61 selectionHighlightPainter = new SelectionHighlightPainter();
66 selectionListener.onSelect(null); 62 }
67 } 63
68 } 64 // HACKHACK: someday we can update the main GUI to use this code reader
69 }); 65 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
70 66
71 selectionHighlightPainter = new SelectionHighlightPainter(); 67 // set the caret position to the token
72 } 68 editor.setCaretPosition(token.start);
73 69 editor.grabFocus();
74 public void setSelectionListener(SelectionListener val) { 70
75 selectionListener = val; 71 try {
76 } 72 // make sure the token is visible in the scroll window
77 73 Rectangle start = editor.modelToView(token.start);
78 public void setCode(String code) { 74 Rectangle end = editor.modelToView(token.end);
79 // sadly, the java lexer is not thread safe, so we have to serialize all these calls 75 final Rectangle show = start.union(end);
80 synchronized (lock) { 76 show.grow(start.width * 10, start.height * 6);
81 setText(code); 77 SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show));
82 } 78 } catch (BadLocationException ex) {
83 } 79 throw new Error(ex);
84 80 }
85 public SourceIndex getSourceIndex() { 81
86 return sourceIndex; 82 // highlight the token momentarily
87 } 83 final Timer timer = new Timer(200, new ActionListener() {
88 84 private int counter = 0;
89 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { 85 private Object highlight = null;
90 decompileClass(classEntry, deobfuscator, null); 86
91 } 87 @Override
92 88 public void actionPerformed(ActionEvent event) {
93 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) { 89 if (counter % 2 == 0) {
94 decompileClass(classEntry, deobfuscator, null, callback); 90 try {
95 } 91 highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
96 92 } catch (BadLocationException ex) {
97 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) { 93 // don't care
98 94 }
99 if (classEntry == null) { 95 } else if (highlight != null) {
100 setCode(null); 96 editor.getHighlighter().removeHighlight(highlight);
101 return; 97 }
102 } 98
103 99 if (counter++ > 6) {
104 setCode("(decompiling...)"); 100 Timer timer = (Timer) event.getSource();
105 101 timer.stop();
106 // run decompilation in a separate thread to keep ui responsive 102 }
107 new Thread(() -> 103 }
108 { 104 });
109 105 timer.start();
110 // decompile it 106 }
111 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); 107
112 String source = deobfuscator.getSource(sourceTree); 108 public void setSelectionListener(SelectionListener val) {
113 setCode(source); 109 selectionListener = val;
114 sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); 110 }
115 111
116 if (callback != null) { 112 public void setCode(String code) {
117 callback.run(); 113 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
118 } 114 synchronized (lock) {
119 }).start(); 115 setText(code);
120 } 116 }
121 117 }
122 public void navigateToClassDeclaration(ClassEntry classEntry) { 118
123 119 public SourceIndex getSourceIndex() {
124 // navigate to the class declaration 120 return sourceIndex;
125 Token token = sourceIndex.getDeclarationToken(classEntry); 121 }
126 if (token == null) { 122
127 // couldn't find the class declaration token, might be an anonymous class 123 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
128 // look for any declaration in that class instead 124 decompileClass(classEntry, deobfuscator, null);
129 for (Entry entry : sourceIndex.declarations()) { 125 }
130 if (entry.getClassEntry().equals(classEntry)) { 126
131 token = sourceIndex.getDeclarationToken(entry); 127 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
132 break; 128 decompileClass(classEntry, deobfuscator, null, callback);
133 } 129 }
134 } 130
135 } 131 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
136 132
137 if (token != null) { 133 if (classEntry == null) {
138 navigateToToken(token); 134 setCode(null);
139 } else { 135 return;
140 // couldn't find anything =( 136 }
141 System.out.println("Unable to find declaration in source for " + classEntry); 137
142 } 138 setCode("(decompiling...)");
143 } 139
144 140 // run decompilation in a separate thread to keep ui responsive
145 public void navigateToToken(final Token token) { 141 new Thread(() ->
146 navigateToToken(this, token, selectionHighlightPainter); 142 {
147 } 143
148 144 // decompile it
149 // HACKHACK: someday we can update the main GUI to use this code reader 145 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
150 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { 146 String source = deobfuscator.getSource(sourceTree);
151 147 setCode(source);
152 // set the caret position to the token 148 sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
153 editor.setCaretPosition(token.start); 149
154 editor.grabFocus(); 150 if (callback != null) {
155 151 callback.run();
156 try { 152 }
157 // make sure the token is visible in the scroll window 153 }).start();
158 Rectangle start = editor.modelToView(token.start); 154 }
159 Rectangle end = editor.modelToView(token.end); 155
160 final Rectangle show = start.union(end); 156 public void navigateToClassDeclaration(ClassEntry classEntry) {
161 show.grow(start.width * 10, start.height * 6); 157
162 SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); 158 // navigate to the class declaration
163 } catch (BadLocationException ex) { 159 Token token = sourceIndex.getDeclarationToken(classEntry);
164 throw new Error(ex); 160 if (token == null) {
165 } 161 // couldn't find the class declaration token, might be an anonymous class
166 162 // look for any declaration in that class instead
167 // highlight the token momentarily 163 for (Entry entry : sourceIndex.declarations()) {
168 final Timer timer = new Timer(200, new ActionListener() { 164 if (entry.getClassEntry().equals(classEntry)) {
169 private int counter = 0; 165 token = sourceIndex.getDeclarationToken(entry);
170 private Object highlight = null; 166 break;
171 167 }
172 @Override 168 }
173 public void actionPerformed(ActionEvent event) { 169 }
174 if (counter % 2 == 0) { 170
175 try { 171 if (token != null) {
176 highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); 172 navigateToToken(token);
177 } catch (BadLocationException ex) { 173 } else {
178 // don't care 174 // couldn't find anything =(
179 } 175 System.out.println("Unable to find declaration in source for " + classEntry);
180 } else if (highlight != null) { 176 }
181 editor.getHighlighter().removeHighlight(highlight); 177 }
182 } 178
183 179 public void navigateToToken(final Token token) {
184 if (counter++ > 6) { 180 navigateToToken(this, token, selectionHighlightPainter);
185 Timer timer = (Timer) event.getSource(); 181 }
186 timer.stop(); 182
187 } 183 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
188 } 184 for (Token token : tokens) {
189 }); 185 setHighlightedToken(token, painter);
190 timer.start(); 186 }
191 } 187 }
192 188
193 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) { 189 public void setHighlightedToken(Token token, HighlightPainter painter) {
194 for (Token token : tokens) { 190 try {
195 setHighlightedToken(token, painter); 191 getHighlighter().addHighlight(token.start, token.end, painter);
196 } 192 } catch (BadLocationException ex) {
197 } 193 throw new IllegalArgumentException(ex);
198 194 }
199 public void setHighlightedToken(Token token, HighlightPainter painter) { 195 }
200 try { 196
201 getHighlighter().addHighlight(token.start, token.end, painter); 197 public void clearHighlights() {
202 } catch (BadLocationException ex) { 198 getHighlighter().removeAllHighlights();
203 throw new IllegalArgumentException(ex); 199 }
204 } 200
205 } 201 public interface SelectionListener {
206 202 void onSelect(EntryReference<Entry, Entry> reference);
207 public void clearHighlights() { 203 }
208 getHighlighter().removeAllHighlights();
209 }
210} 204}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index 7cb494f..9f8d6fc 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
@@ -54,811 +55,790 @@ import java.util.function.Function;
54 55
55public class Gui { 56public class Gui {
56 57
57 private GuiController controller; 58 public final PopupMenuBar popupMenu;
58 59 private final PanelObf obfPanel;
59 private final PanelObf obfPanel; 60 private final PanelDeobf deobfPanel;
60 private final PanelDeobf deobfPanel; 61
61 62 private final MenuBar menuBar;
62 private final MenuBar menuBar; 63 // state
63 public final PopupMenuBar popupMenu; 64 public EntryReference<Entry, Entry> reference;
64 65 public JFileChooser jarFileChooser;
65 private JFrame frame; 66 public JFileChooser enigmaMappingsFileChooser;
66 private PanelEditor editor; 67 public JFileChooser exportSourceFileChooser;
67 private JPanel classesPanel; 68 public JFileChooser exportJarFileChooser;
68 private JSplitPane splitClasses; 69 private GuiController controller;
69 private PanelIdentifier infoPanel; 70 private JFrame frame;
70 private ObfuscatedHighlightPainter obfuscatedHighlightPainter; 71 private PanelEditor editor;
71 private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter; 72 private JPanel classesPanel;
72 private OtherHighlightPainter otherHighlightPainter; 73 private JSplitPane splitClasses;
73 private SelectionHighlightPainter selectionHighlightPainter; 74 private PanelIdentifier infoPanel;
74 private JTree inheritanceTree; 75 private ObfuscatedHighlightPainter obfuscatedHighlightPainter;
75 private JTree implementationsTree; 76 private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter;
76 private JTree callsTree; 77 private OtherHighlightPainter otherHighlightPainter;
77 private JList<Token> tokens; 78 private SelectionHighlightPainter selectionHighlightPainter;
78 private JTabbedPane tabs; 79 private JTree inheritanceTree;
79 80 private JTree implementationsTree;
80 // state 81 private JTree callsTree;
81 public EntryReference<Entry, Entry> reference; 82 private JList<Token> tokens;
82 83 private JTabbedPane tabs;
83 public JFileChooser jarFileChooser; 84
84 public JFileChooser enigmaMappingsFileChooser; 85 public Gui() {
85 86
86 public JFileChooser exportSourceFileChooser; 87 // init frame
87 public JFileChooser exportJarFileChooser; 88 this.frame = new JFrame(Constants.NAME);
88 89 final Container pane = this.frame.getContentPane();
89 public Gui() { 90 pane.setLayout(new BorderLayout());
90 91
91 // init frame 92 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
92 this.frame = new JFrame(Constants.NAME); 93 // install a global exception handler to the event thread
93 final Container pane = this.frame.getContentPane(); 94 CrashDialog.init(this.frame);
94 pane.setLayout(new BorderLayout()); 95 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
95 96 t.printStackTrace(System.err);
96 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { 97 if (!ExceptionIgnorer.shouldIgnore(t)) {
97 // install a global exception handler to the event thread 98 CrashDialog.show(t);
98 CrashDialog.init(this.frame); 99 }
99 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { 100 });
100 t.printStackTrace(System.err); 101 }
101 if (!ExceptionIgnorer.shouldIgnore(t)) { 102
102 CrashDialog.show(t); 103 this.controller = new GuiController(this);
103 } 104
104 }); 105 // init file choosers
105 } 106 this.jarFileChooser = new FileChooserFile();
106 107
107 this.controller = new GuiController(this); 108 this.enigmaMappingsFileChooser = new FileChooserAny();
108 109 this.exportSourceFileChooser = new FileChooserFolder();
109 // init file choosers 110 this.exportJarFileChooser = new FileChooserFile();
110 this.jarFileChooser = new FileChooserFile(); 111
111 112 this.obfPanel = new PanelObf(this);
112 113 this.deobfPanel = new PanelDeobf(this);
113 this.enigmaMappingsFileChooser = new FileChooserAny(); 114
114 this.exportSourceFileChooser = new FileChooserFolder(); 115 // set up classes panel (don't add the splitter yet)
115 this.exportJarFileChooser = new FileChooserFile(); 116 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
116 117 splitClasses.setResizeWeight(0.3);
117 this.obfPanel = new PanelObf(this); 118 this.classesPanel = new JPanel();
118 this.deobfPanel = new PanelDeobf(this); 119 this.classesPanel.setLayout(new BorderLayout());
119 120 this.classesPanel.setPreferredSize(new Dimension(250, 0));
120 // set up classes panel (don't add the splitter yet) 121
121 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); 122 // init info panel
122 splitClasses.setResizeWeight(0.3); 123 infoPanel = new PanelIdentifier(this);
123 this.classesPanel = new JPanel(); 124 infoPanel.clearReference();
124 this.classesPanel.setLayout(new BorderLayout()); 125
125 this.classesPanel.setPreferredSize(new Dimension(250, 0)); 126 // init editor
126 127 DefaultSyntaxKit.initKit();
127 // init info panel 128 obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
128 infoPanel = new PanelIdentifier(this); 129 deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
129 infoPanel.clearReference(); 130 otherHighlightPainter = new OtherHighlightPainter();
130 131 selectionHighlightPainter = new SelectionHighlightPainter();
131 // init editor 132 this.editor = new PanelEditor(this);
132 DefaultSyntaxKit.initKit(); 133 JScrollPane sourceScroller = new JScrollPane(this.editor);
133 obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); 134 this.editor.setContentType("text/java");
134 deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); 135 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
135 otherHighlightPainter = new OtherHighlightPainter(); 136 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
136 selectionHighlightPainter = new SelectionHighlightPainter(); 137
137 this.editor = new PanelEditor(this); 138 // init editor popup menu
138 JScrollPane sourceScroller = new JScrollPane(this.editor); 139 this.popupMenu = new PopupMenuBar(this);
139 this.editor.setContentType("text/java"); 140 this.editor.setComponentPopupMenu(this.popupMenu);
140 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); 141
141 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); 142 // init inheritance panel
142 143 inheritanceTree = new JTree();
143 // init editor popup menu 144 inheritanceTree.setModel(null);
144 this.popupMenu = new PopupMenuBar(this); 145 inheritanceTree.addMouseListener(new MouseAdapter() {
145 this.editor.setComponentPopupMenu(this.popupMenu); 146 @Override
146 147 public void mouseClicked(MouseEvent event) {
147 // init inheritance panel 148 if (event.getClickCount() == 2) {
148 inheritanceTree = new JTree(); 149 // get the selected node
149 inheritanceTree.setModel(null); 150 TreePath path = inheritanceTree.getSelectionPath();
150 inheritanceTree.addMouseListener(new MouseAdapter() { 151 if (path == null) {
151 @Override 152 return;
152 public void mouseClicked(MouseEvent event) { 153 }
153 if (event.getClickCount() == 2) { 154
154 // get the selected node 155 Object node = path.getLastPathComponent();
155 TreePath path = inheritanceTree.getSelectionPath(); 156 if (node instanceof ClassInheritanceTreeNode) {
156 if (path == null) { 157 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
157 return; 158 navigateTo(new ClassEntry(classNode.getObfClassName()));
158 } 159 } else if (node instanceof MethodInheritanceTreeNode) {
159 160 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
160 Object node = path.getLastPathComponent(); 161 if (methodNode.isImplemented()) {
161 if (node instanceof ClassInheritanceTreeNode) { 162 navigateTo(methodNode.getMethodEntry());
162 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; 163 }
163 navigateTo(new ClassEntry(classNode.getObfClassName())); 164 }
164 } else if (node instanceof MethodInheritanceTreeNode) { 165 }
165 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; 166 }
166 if (methodNode.isImplemented()) { 167 });
167 navigateTo(methodNode.getMethodEntry()); 168 JPanel inheritancePanel = new JPanel();
168 } 169 inheritancePanel.setLayout(new BorderLayout());
169 } 170 inheritancePanel.add(new JScrollPane(inheritanceTree));
170 } 171
171 } 172 // init implementations panel
172 }); 173 implementationsTree = new JTree();
173 JPanel inheritancePanel = new JPanel(); 174 implementationsTree.setModel(null);
174 inheritancePanel.setLayout(new BorderLayout()); 175 implementationsTree.addMouseListener(new MouseAdapter() {
175 inheritancePanel.add(new JScrollPane(inheritanceTree)); 176 @Override
176 177 public void mouseClicked(MouseEvent event) {
177 // init implementations panel 178 if (event.getClickCount() == 2) {
178 implementationsTree = new JTree(); 179 // get the selected node
179 implementationsTree.setModel(null); 180 TreePath path = implementationsTree.getSelectionPath();
180 implementationsTree.addMouseListener(new MouseAdapter() { 181 if (path == null) {
181 @Override 182 return;
182 public void mouseClicked(MouseEvent event) { 183 }
183 if (event.getClickCount() == 2) { 184
184 // get the selected node 185 Object node = path.getLastPathComponent();
185 TreePath path = implementationsTree.getSelectionPath(); 186 if (node instanceof ClassImplementationsTreeNode) {
186 if (path == null) { 187 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
187 return; 188 navigateTo(classNode.getClassEntry());
188 } 189 } else if (node instanceof MethodImplementationsTreeNode) {
189 190 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
190 Object node = path.getLastPathComponent(); 191 navigateTo(methodNode.getMethodEntry());
191 if (node instanceof ClassImplementationsTreeNode) { 192 }
192 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; 193 }
193 navigateTo(classNode.getClassEntry()); 194 }
194 } else if (node instanceof MethodImplementationsTreeNode) { 195 });
195 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; 196 JPanel implementationsPanel = new JPanel();
196 navigateTo(methodNode.getMethodEntry()); 197 implementationsPanel.setLayout(new BorderLayout());
197 } 198 implementationsPanel.add(new JScrollPane(implementationsTree));
198 } 199
199 } 200 // init call panel
200 }); 201 callsTree = new JTree();
201 JPanel implementationsPanel = new JPanel(); 202 callsTree.setModel(null);
202 implementationsPanel.setLayout(new BorderLayout()); 203 callsTree.addMouseListener(new MouseAdapter() {
203 implementationsPanel.add(new JScrollPane(implementationsTree)); 204 @SuppressWarnings("unchecked")
204 205 @Override
205 // init call panel 206 public void mouseClicked(MouseEvent event) {
206 callsTree = new JTree(); 207 if (event.getClickCount() == 2) {
207 callsTree.setModel(null); 208 // get the selected node
208 callsTree.addMouseListener(new MouseAdapter() { 209 TreePath path = callsTree.getSelectionPath();
209 @SuppressWarnings("unchecked") 210 if (path == null) {
210 @Override 211 return;
211 public void mouseClicked(MouseEvent event) { 212 }
212 if (event.getClickCount() == 2) { 213
213 // get the selected node 214 Object node = path.getLastPathComponent();
214 TreePath path = callsTree.getSelectionPath(); 215 if (node instanceof ReferenceTreeNode) {
215 if (path == null) { 216 ReferenceTreeNode<Entry, Entry> referenceNode = ((ReferenceTreeNode<Entry, Entry>) node);
216 return; 217 if (referenceNode.getReference() != null) {
217 } 218 navigateTo(referenceNode.getReference());
218 219 } else {
219 Object node = path.getLastPathComponent(); 220 navigateTo(referenceNode.getEntry());
220 if (node instanceof ReferenceTreeNode) { 221 }
221 ReferenceTreeNode<Entry, Entry> referenceNode = ((ReferenceTreeNode<Entry, Entry>) node); 222 }
222 if (referenceNode.getReference() != null) { 223 }
223 navigateTo(referenceNode.getReference()); 224 }
224 } else { 225 });
225 navigateTo(referenceNode.getEntry()); 226 tokens = new JList<>();
226 } 227 tokens.setCellRenderer(new TokenListCellRenderer(this.controller));
227 } 228 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
228 } 229 tokens.setLayoutOrientation(JList.VERTICAL);
229 } 230 tokens.addMouseListener(new MouseAdapter() {
230 }); 231 @Override
231 tokens = new JList<>(); 232 public void mouseClicked(MouseEvent event) {
232 tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); 233 if (event.getClickCount() == 2) {
233 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 234 Token selected = tokens.getSelectedValue();
234 tokens.setLayoutOrientation(JList.VERTICAL); 235 if (selected != null) {
235 tokens.addMouseListener(new MouseAdapter() { 236 showToken(selected);
236 @Override 237 }
237 public void mouseClicked(MouseEvent event) { 238 }
238 if (event.getClickCount() == 2) { 239 }
239 Token selected = tokens.getSelectedValue(); 240 });
240 if (selected != null) { 241 tokens.setPreferredSize(new Dimension(0, 200));
241 showToken(selected); 242 tokens.setMinimumSize(new Dimension(0, 200));
242 } 243 JSplitPane callPanel = new JSplitPane(
243 } 244 JSplitPane.VERTICAL_SPLIT,
244 } 245 true,
245 }); 246 new JScrollPane(callsTree),
246 tokens.setPreferredSize(new Dimension(0, 200)); 247 new JScrollPane(tokens)
247 tokens.setMinimumSize(new Dimension(0, 200)); 248 );
248 JSplitPane callPanel = new JSplitPane( 249 callPanel.setResizeWeight(1); // let the top side take all the slack
249 JSplitPane.VERTICAL_SPLIT, 250 callPanel.resetToPreferredSizes();
250 true, 251
251 new JScrollPane(callsTree), 252 // layout controls
252 new JScrollPane(tokens) 253 JPanel centerPanel = new JPanel();
253 ); 254 centerPanel.setLayout(new BorderLayout());
254 callPanel.setResizeWeight(1); // let the top side take all the slack 255 centerPanel.add(infoPanel, BorderLayout.NORTH);
255 callPanel.resetToPreferredSizes(); 256 centerPanel.add(sourceScroller, BorderLayout.CENTER);
256 257 tabs = new JTabbedPane();
257 // layout controls 258 tabs.setPreferredSize(new Dimension(250, 0));
258 JPanel centerPanel = new JPanel(); 259 tabs.addTab("Inheritance", inheritancePanel);
259 centerPanel.setLayout(new BorderLayout()); 260 tabs.addTab("Implementations", implementationsPanel);
260 centerPanel.add(infoPanel, BorderLayout.NORTH); 261 tabs.addTab("Call Graph", callPanel);
261 centerPanel.add(sourceScroller, BorderLayout.CENTER); 262 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs);
262 tabs = new JTabbedPane(); 263 splitRight.setResizeWeight(1); // let the left side take all the slack
263 tabs.setPreferredSize(new Dimension(250, 0)); 264 splitRight.resetToPreferredSizes();
264 tabs.addTab("Inheritance", inheritancePanel); 265 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
265 tabs.addTab("Implementations", implementationsPanel); 266 splitCenter.setResizeWeight(0); // let the right side take all the slack
266 tabs.addTab("Call Graph", callPanel); 267 pane.add(splitCenter, BorderLayout.CENTER);
267 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); 268
268 splitRight.setResizeWeight(1); // let the left side take all the slack 269 // init menus
269 splitRight.resetToPreferredSizes(); 270 this.menuBar = new MenuBar(this);
270 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); 271 this.frame.setJMenuBar(this.menuBar);
271 splitCenter.setResizeWeight(0); // let the right side take all the slack 272
272 pane.add(splitCenter, BorderLayout.CENTER); 273 // init state
273 274 onCloseJar();
274 // init menus 275
275 this.menuBar = new MenuBar(this); 276 this.frame.addWindowListener(new WindowAdapter() {
276 this.frame.setJMenuBar(this.menuBar); 277 @Override
277 278 public void windowClosing(WindowEvent event) {
278 // init state 279 close();
279 onCloseJar(); 280 }
280 281 });
281 this.frame.addWindowListener(new WindowAdapter() { 282
282 @Override 283 // show the frame
283 public void windowClosing(WindowEvent event) { 284 pane.doLayout();
284 close(); 285 this.frame.setSize(1024, 576);
285 } 286 this.frame.setMinimumSize(new Dimension(640, 480));
286 }); 287 this.frame.setVisible(true);
287 288 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
288 // show the frame 289 }
289 pane.doLayout(); 290
290 this.frame.setSize(1024, 576); 291 public JFrame getFrame() {
291 this.frame.setMinimumSize(new Dimension(640, 480)); 292 return this.frame;
292 this.frame.setVisible(true); 293 }
293 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 294
294 } 295 public GuiController getController() {
295 296 return this.controller;
296 public JFrame getFrame() { 297 }
297 return this.frame; 298
298 } 299 public void onStartOpenJar() {
299 300 this.classesPanel.removeAll();
300 public GuiController getController() { 301 JPanel panel = new JPanel();
301 return this.controller; 302 panel.setLayout(new FlowLayout());
302 } 303 panel.add(new JLabel("Loading..."));
303 304 this.classesPanel.add(panel);
304 public void onStartOpenJar() { 305 redraw();
305 this.classesPanel.removeAll(); 306 }
306 JPanel panel = new JPanel(); 307
307 panel.setLayout(new FlowLayout()); 308 public void onFinishOpenJar(String jarName) {
308 panel.add(new JLabel("Loading...")); 309 // update gui
309 this.classesPanel.add(panel); 310 this.frame.setTitle(Constants.NAME + " - " + jarName);
310 redraw(); 311 this.classesPanel.removeAll();
311 } 312 this.classesPanel.add(splitClasses);
312 313 setSource(null);
313 public void onFinishOpenJar(String jarName) { 314
314 // update gui 315 // update menu
315 this.frame.setTitle(Constants.NAME + " - " + jarName); 316 this.menuBar.closeJarMenu.setEnabled(true);
316 this.classesPanel.removeAll(); 317 this.menuBar.openEnigmaMappingsMenu.setEnabled(true);
317 this.classesPanel.add(splitClasses); 318 this.menuBar.saveMappingsMenu.setEnabled(false);
318 setSource(null); 319 this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true);
319 320 this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true);
320 // update menu 321 this.menuBar.saveMappingsSrgMenu.setEnabled(true);
321 this.menuBar.closeJarMenu.setEnabled(true); 322 this.menuBar.closeMappingsMenu.setEnabled(true);
322 this.menuBar.openEnigmaMappingsMenu.setEnabled(true); 323 this.menuBar.exportSourceMenu.setEnabled(true);
323 this.menuBar.saveMappingsMenu.setEnabled(false); 324 this.menuBar.exportJarMenu.setEnabled(true);
324 this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true); 325
325 this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true); 326 redraw();
326 this.menuBar.saveMappingsSrgMenu.setEnabled(true); 327 }
327 this.menuBar.closeMappingsMenu.setEnabled(true); 328
328 this.menuBar.exportSourceMenu.setEnabled(true); 329 public void onCloseJar() {
329 this.menuBar.exportJarMenu.setEnabled(true); 330 // update gui
330 331 this.frame.setTitle(Constants.NAME);
331 redraw(); 332 setObfClasses(null);
332 } 333 setDeobfClasses(null);
333 334 setSource(null);
334 public void onCloseJar() { 335 this.classesPanel.removeAll();
335 // update gui 336
336 this.frame.setTitle(Constants.NAME); 337 // update menu
337 setObfClasses(null); 338 this.menuBar.closeJarMenu.setEnabled(false);
338 setDeobfClasses(null); 339 this.menuBar.openEnigmaMappingsMenu.setEnabled(false);
339 setSource(null); 340 this.menuBar.saveMappingsMenu.setEnabled(false);
340 this.classesPanel.removeAll(); 341 this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false);
341 342 this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false);
342 // update menu 343 this.menuBar.saveMappingsSrgMenu.setEnabled(false);
343 this.menuBar.closeJarMenu.setEnabled(false); 344 this.menuBar.closeMappingsMenu.setEnabled(false);
344 this.menuBar.openEnigmaMappingsMenu.setEnabled(false); 345 this.menuBar.exportSourceMenu.setEnabled(false);
345 this.menuBar.saveMappingsMenu.setEnabled(false); 346 this.menuBar.exportJarMenu.setEnabled(false);
346 this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false); 347
347 this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false); 348 redraw();
348 this.menuBar.saveMappingsSrgMenu.setEnabled(false); 349 }
349 this.menuBar.closeMappingsMenu.setEnabled(false); 350
350 this.menuBar.exportSourceMenu.setEnabled(false); 351 public void setObfClasses(Collection<ClassEntry> obfClasses) {
351 this.menuBar.exportJarMenu.setEnabled(false); 352 this.obfPanel.obfClasses.setClasses(obfClasses);
352 353 }
353 redraw(); 354
354 } 355 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
355 356 this.deobfPanel.deobfClasses.setClasses(deobfClasses);
356 public void setObfClasses(Collection<ClassEntry> obfClasses) { 357 }
357 this.obfPanel.obfClasses.setClasses(obfClasses); 358
358 } 359 public void setMappingsFile(File file) {
359 360 this.enigmaMappingsFileChooser.setSelectedFile(file);
360 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) { 361 this.menuBar.saveMappingsMenu.setEnabled(file != null);
361 this.deobfPanel.deobfClasses.setClasses(deobfClasses); 362 }
362 } 363
363 364 public void setSource(String source) {
364 public void setMappingsFile(File file) { 365 this.editor.getHighlighter().removeAllHighlights();
365 this.enigmaMappingsFileChooser.setSelectedFile(file); 366 this.editor.setText(source);
366 this.menuBar.saveMappingsMenu.setEnabled(file != null); 367 }
367 } 368
368 369 public void showToken(final Token token) {
369 public void setSource(String source) { 370 if (token == null) {
370 this.editor.getHighlighter().removeAllHighlights(); 371 throw new IllegalArgumentException("Token cannot be null!");
371 this.editor.setText(source); 372 }
372 } 373 CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter);
373 374 redraw();
374 public void showToken(final Token token) { 375 }
375 if (token == null) { 376
376 throw new IllegalArgumentException("Token cannot be null!"); 377 public void showTokens(Collection<Token> tokens) {
377 } 378 Vector<Token> sortedTokens = new Vector<>(tokens);
378 CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); 379 Collections.sort(sortedTokens);
379 redraw(); 380 if (sortedTokens.size() > 1) {
380 } 381 // sort the tokens and update the tokens panel
381 382 this.tokens.setListData(sortedTokens);
382 public void showTokens(Collection<Token> tokens) { 383 this.tokens.setSelectedIndex(0);
383 Vector<Token> sortedTokens = new Vector<>(tokens); 384 } else {
384 Collections.sort(sortedTokens); 385 this.tokens.setListData(new Vector<>());
385 if (sortedTokens.size() > 1) { 386 }
386 // sort the tokens and update the tokens panel 387
387 this.tokens.setListData(sortedTokens); 388 // show the first token
388 this.tokens.setSelectedIndex(0); 389 showToken(sortedTokens.get(0));
389 } else { 390 }
390 this.tokens.setListData(new Vector<>()); 391
391 } 392 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
392 393
393 // show the first token 394 // remove any old highlighters
394 showToken(sortedTokens.get(0)); 395 this.editor.getHighlighter().removeAllHighlights();
395 } 396
396 397 // color things based on the index
397 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) { 398 if (obfuscatedTokens != null) {
398 399 setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter);
399 // remove any old highlighters 400 }
400 this.editor.getHighlighter().removeAllHighlights(); 401 if (deobfuscatedTokens != null) {
401 402 setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter);
402 // color things based on the index 403 }
403 if (obfuscatedTokens != null) { 404 if (otherTokens != null) {
404 setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter); 405 setHighlightedTokens(otherTokens, otherHighlightPainter);
405 } 406 }
406 if (deobfuscatedTokens != null) { 407
407 setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter); 408 redraw();
408 } 409 }
409 if (otherTokens != null) { 410
410 setHighlightedTokens(otherTokens, otherHighlightPainter); 411 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
411 } 412 for (Token token : tokens) {
412 413 try {
413 redraw(); 414 this.editor.getHighlighter().addHighlight(token.start, token.end, painter);
414 } 415 } catch (BadLocationException ex) {
415 416 throw new IllegalArgumentException(ex);
416 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { 417 }
417 for (Token token : tokens) { 418 }
418 try { 419 }
419 this.editor.getHighlighter().addHighlight(token.start, token.end, painter); 420
420 } catch (BadLocationException ex) { 421 private void showReference(EntryReference<Entry, Entry> reference) {
421 throw new IllegalArgumentException(ex); 422 if (reference == null) {
422 } 423 infoPanel.clearReference();
423 } 424 return;
424 } 425 }
425 426
426 private void showReference(EntryReference<Entry, Entry> reference) { 427 this.reference = reference;
427 if (reference == null) { 428
428 infoPanel.clearReference(); 429 infoPanel.removeAll();
429 return; 430 if (reference.entry instanceof ClassEntry) {
430 } 431 showClassEntry((ClassEntry) this.reference.entry);
431 432 } else if (this.reference.entry instanceof FieldEntry) {
432 this.reference = reference; 433 showFieldEntry((FieldEntry) this.reference.entry);
433 434 } else if (this.reference.entry instanceof MethodEntry) {
434 infoPanel.removeAll(); 435 showMethodEntry((MethodEntry) this.reference.entry);
435 if (reference.entry instanceof ClassEntry) { 436 } else if (this.reference.entry instanceof ConstructorEntry) {
436 showClassEntry((ClassEntry) this.reference.entry); 437 showConstructorEntry((ConstructorEntry) this.reference.entry);
437 } else if (this.reference.entry instanceof FieldEntry) { 438 } else if (this.reference.entry instanceof ArgumentEntry) {
438 showFieldEntry((FieldEntry) this.reference.entry); 439 showArgumentEntry((ArgumentEntry) this.reference.entry);
439 } else if (this.reference.entry instanceof MethodEntry) { 440 } else if (this.reference.entry instanceof LocalVariableEntry) {
440 showMethodEntry((MethodEntry) this.reference.entry); 441 showLocalVariableEntry((LocalVariableEntry) this.reference.entry);
441 } else if (this.reference.entry instanceof ConstructorEntry) { 442 } else {
442 showConstructorEntry((ConstructorEntry) this.reference.entry); 443 throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName());
443 } else if (this.reference.entry instanceof ArgumentEntry) { 444 }
444 showArgumentEntry((ArgumentEntry) this.reference.entry); 445
445 } else if (this.reference.entry instanceof LocalVariableEntry) { 446 redraw();
446 showLocalVariableEntry((LocalVariableEntry) this.reference.entry); 447 }
447 } else { 448
448 throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); 449 private void showLocalVariableEntry(LocalVariableEntry entry) {
449 } 450 addNameValue(infoPanel, "Variable", entry.getName());
450 451 addNameValue(infoPanel, "Class", entry.getClassEntry().getName());
451 redraw(); 452 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName());
452 } 453 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex()));
453 454 addNameValue(infoPanel, "Type", entry.getType().toString());
454 private void showLocalVariableEntry(LocalVariableEntry entry) { 455 }
455 addNameValue(infoPanel, "Variable", entry.getName()); 456
456 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 457 private void showClassEntry(ClassEntry entry) {
457 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); 458 addNameValue(infoPanel, "Class", entry.getName());
458 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); 459 addModifierComboBox(infoPanel, "Modifier", entry);
459 addNameValue(infoPanel, "Type", entry.getType().toString()); 460 }
460 } 461
461 462 private void showFieldEntry(FieldEntry entry) {
462 private void showClassEntry(ClassEntry entry) { 463 addNameValue(infoPanel, "Field", entry.getName());
463 addNameValue(infoPanel, "Class", entry.getName()); 464 addNameValue(infoPanel, "Class", entry.getClassEntry().getName());
464 addModifierComboBox(infoPanel, "Modifier", entry); 465 addNameValue(infoPanel, "Type", entry.getType().toString());
465 } 466 addModifierComboBox(infoPanel, "Modifier", entry);
466 467 }
467 private void showFieldEntry(FieldEntry entry) { 468
468 addNameValue(infoPanel, "Field", entry.getName()); 469 private void showMethodEntry(MethodEntry entry) {
469 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 470 addNameValue(infoPanel, "Method", entry.getName());
470 addNameValue(infoPanel, "Type", entry.getType().toString()); 471 addNameValue(infoPanel, "Class", entry.getClassEntry().getName());
471 addModifierComboBox(infoPanel, "Modifier", entry); 472 addNameValue(infoPanel, "Signature", entry.getSignature().toString());
472 } 473 addModifierComboBox(infoPanel, "Modifier", entry);
473 474
474 private void showMethodEntry(MethodEntry entry) { 475 }
475 addNameValue(infoPanel, "Method", entry.getName()); 476
476 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 477 private void showConstructorEntry(ConstructorEntry entry) {
477 addNameValue(infoPanel, "Signature", entry.getSignature().toString()); 478 addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName());
478 addModifierComboBox(infoPanel, "Modifier", entry); 479 if (!entry.isStatic()) {
479 480 addNameValue(infoPanel, "Signature", entry.getSignature().toString());
480 } 481 addModifierComboBox(infoPanel, "Modifier", entry);
481 482 }
482 private void showConstructorEntry(ConstructorEntry entry) { 483 }
483 addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); 484
484 if (!entry.isStatic()) { 485 private void showArgumentEntry(ArgumentEntry entry) {
485 addNameValue(infoPanel, "Signature", entry.getSignature().toString()); 486 addNameValue(infoPanel, "Argument", entry.getName());
486 addModifierComboBox(infoPanel, "Modifier", entry); 487 addNameValue(infoPanel, "Class", entry.getClassEntry().getName());
487 } 488 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName());
488 } 489 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex()));
489 490 }
490 private void showArgumentEntry(ArgumentEntry entry) { 491
491 addNameValue(infoPanel, "Argument", entry.getName()); 492 private void addNameValue(JPanel container, String name, String value) {
492 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 493 JPanel panel = new JPanel();
493 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); 494 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
494 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); 495 container.add(panel);
495 } 496
496 497 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
497 private void addNameValue(JPanel container, String name, String value) { 498 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
498 JPanel panel = new JPanel(); 499 panel.add(label);
499 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); 500
500 container.add(panel); 501 panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT)));
501 502 }
502 JLabel label = new JLabel(name + ":", JLabel.RIGHT); 503
503 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 504 private JComboBox<Mappings.EntryModifier> addModifierComboBox(JPanel container, String name, Entry entry) {
504 panel.add(label); 505 if (!getController().entryIsInJar(entry))
505 506 return null;
506 panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); 507 JPanel panel = new JPanel();
507 } 508 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
508 509 container.add(panel);
509 private JComboBox<Mappings.EntryModifier> addModifierComboBox(JPanel container, String name, Entry entry) 510 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
510 { 511 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
511 if (!getController().entryIsInJar(entry)) 512 panel.add(label);
512 return null; 513 JComboBox<Mappings.EntryModifier> combo = new JComboBox<>(Mappings.EntryModifier.values());
513 JPanel panel = new JPanel(); 514 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
514 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); 515 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
515 container.add(panel); 516 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal());
516 JLabel label = new JLabel(name + ":", JLabel.RIGHT); 517 combo.addItemListener(getController()::modifierChange);
517 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 518 panel.add(combo);
518 panel.add(label); 519 return combo;
519 JComboBox<Mappings.EntryModifier> combo = new JComboBox<>(Mappings.EntryModifier.values()); 520 }
520 ((JLabel)combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); 521
521 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 522 public void onCaretMove(int pos) {
522 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); 523
523 combo.addItemListener(getController()::modifierChange); 524 Token token = this.controller.getToken(pos);
524 panel.add(combo); 525 boolean isToken = token != null;
525 return combo; 526
526 } 527 reference = this.controller.getDeobfReference(token);
527 528 boolean isClassEntry = isToken && reference.entry instanceof ClassEntry;
528 public void onCaretMove(int pos) { 529 boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry;
529 530 boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry;
530 Token token = this.controller.getToken(pos); 531 boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry;
531 boolean isToken = token != null; 532 boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry);
532 533 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference);
533 reference = this.controller.getDeobfReference(token); 534
534 boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; 535 if (isToken) {
535 boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; 536 showReference(reference);
536 boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; 537 } else {
537 boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; 538 infoPanel.clearReference();
538 boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); 539 }
539 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); 540
540 541 this.popupMenu.renameMenu.setEnabled(isRenameable);
541 if (isToken) { 542 this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
542 showReference(reference); 543 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
543 } else { 544 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
544 infoPanel.clearReference(); 545 this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
545 } 546 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation());
546 547 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable);
547 this.popupMenu.renameMenu.setEnabled(isRenameable); 548
548 this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); 549 if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) {
549 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); 550 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated");
550 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); 551 } else {
551 this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); 552 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated");
552 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); 553 }
553 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); 554 }
554 555
555 if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { 556 public void navigateTo(Entry entry) {
556 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); 557 if (!this.controller.entryIsInJar(entry)) {
557 } else { 558 // entry is not in the jar. Ignore it
558 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); 559 return;
559 } 560 }
560 } 561 if (reference != null) {
561 562 this.controller.savePreviousReference(reference);
562 public void navigateTo(Entry entry) { 563 }
563 if (!this.controller.entryIsInJar(entry)) { 564 this.controller.openDeclaration(entry);
564 // entry is not in the jar. Ignore it 565 }
565 return; 566
566 } 567 private void navigateTo(EntryReference<Entry, Entry> reference) {
567 if (reference != null) { 568 if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) {
568 this.controller.savePreviousReference(reference); 569 return;
569 } 570 }
570 this.controller.openDeclaration(entry); 571 if (this.reference != null) {
571 } 572 this.controller.savePreviousReference(this.reference);
572 573 }
573 private void navigateTo(EntryReference<Entry, Entry> reference) { 574 this.controller.openReference(reference);
574 if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { 575 }
575 return; 576
576 } 577 public void startRename() {
577 if (this.reference != null) { 578
578 this.controller.savePreviousReference(this.reference); 579 // init the text box
579 } 580 final JTextField text = new JTextField();
580 this.controller.openReference(reference); 581 text.setText(reference.getNamableName());
581 } 582 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
582 583 text.addKeyListener(new KeyAdapter() {
583 public void startRename() { 584 @Override
584 585 public void keyPressed(KeyEvent event) {
585 // init the text box 586 switch (event.getKeyCode()) {
586 final JTextField text = new JTextField(); 587 case KeyEvent.VK_ENTER:
587 text.setText(reference.getNamableName()); 588 finishRename(text, true);
588 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); 589 break;
589 text.addKeyListener(new KeyAdapter() { 590
590 @Override 591 case KeyEvent.VK_ESCAPE:
591 public void keyPressed(KeyEvent event) { 592 finishRename(text, false);
592 switch (event.getKeyCode()) { 593 break;
593 case KeyEvent.VK_ENTER: 594 default:
594 finishRename(text, true); 595 break;
595 break; 596 }
596 597 }
597 case KeyEvent.VK_ESCAPE: 598 });
598 finishRename(text, false); 599
599 break; 600 // find the label with the name and replace it with the text box
600 default: 601 JPanel panel = (JPanel) infoPanel.getComponent(0);
601 break; 602 panel.remove(panel.getComponentCount() - 1);
602 } 603 panel.add(text);
603 } 604 text.grabFocus();
604 }); 605
605 606 int offset = text.getText().lastIndexOf('/') + 1;
606 // find the label with the name and replace it with the text box 607 // If it's a class and isn't in the default package, assume that it's deobfuscated.
607 JPanel panel = (JPanel) infoPanel.getComponent(0); 608 if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0)
608 panel.remove(panel.getComponentCount() - 1); 609 text.select(offset, text.getText().length());
609 panel.add(text); 610 else
610 text.grabFocus(); 611 text.selectAll();
611 612
612 int offset = text.getText().lastIndexOf('/') + 1; 613 redraw();
613 // If it's a class and isn't in the default package, assume that it's deobfuscated. 614 }
614 if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) 615
615 text.select(offset, text.getText().length()); 616 private void finishRename(JTextField text, boolean saveName) {
616 else 617 String newName = text.getText();
617 text.selectAll(); 618 if (saveName && newName != null && !newName.isEmpty()) {
618 619 try {
619 redraw(); 620 this.controller.rename(reference, newName);
620 } 621 } catch (IllegalNameException ex) {
621 622 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
622 private void finishRename(JTextField text, boolean saveName) { 623 text.setToolTipText(ex.getReason());
623 String newName = text.getText(); 624 Utils.showToolTipNow(text);
624 if (saveName && newName != null && newName.length() > 0) { 625 }
625 try { 626 return;
626 this.controller.rename(reference, newName); 627 }
627 } catch (IllegalNameException ex) { 628
628 text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); 629 // abort the rename
629 text.setToolTipText(ex.getReason()); 630 JPanel panel = (JPanel) infoPanel.getComponent(0);
630 Utils.showToolTipNow(text); 631 panel.remove(panel.getComponentCount() - 1);
631 } 632 panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT)));
632 return; 633
633 } 634 this.editor.grabFocus();
634 635
635 // abort the rename 636 redraw();
636 JPanel panel = (JPanel) infoPanel.getComponent(0); 637 }
637 panel.remove(panel.getComponentCount() - 1); 638
638 panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT))); 639 public void showInheritance() {
639 640
640 this.editor.grabFocus(); 641 if (reference == null) {
641 642 return;
642 redraw(); 643 }
643 } 644
644 645 inheritanceTree.setModel(null);
645 public void showInheritance() { 646
646 647 if (reference.entry instanceof ClassEntry) {
647 if (reference == null) { 648 // get the class inheritance
648 return; 649 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry);
649 } 650
650 651 // show the tree at the root
651 inheritanceTree.setModel(null); 652 TreePath path = getPathToRoot(classNode);
652 653 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
653 if (reference.entry instanceof ClassEntry) { 654 inheritanceTree.expandPath(path);
654 // get the class inheritance 655 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
655 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); 656 } else if (reference.entry instanceof MethodEntry) {
656 657 // get the method inheritance
657 // show the tree at the root 658 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry);
658 TreePath path = getPathToRoot(classNode); 659
659 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); 660 // show the tree at the root
660 inheritanceTree.expandPath(path); 661 TreePath path = getPathToRoot(classNode);
661 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); 662 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
662 } else if (reference.entry instanceof MethodEntry) { 663 inheritanceTree.expandPath(path);
663 // get the method inheritance 664 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
664 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); 665 }
665 666
666 // show the tree at the root 667 tabs.setSelectedIndex(0);
667 TreePath path = getPathToRoot(classNode); 668 redraw();
668 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); 669 }
669 inheritanceTree.expandPath(path); 670
670 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); 671 public void showImplementations() {
671 } 672
672 673 if (reference == null) {
673 tabs.setSelectedIndex(0); 674 return;
674 redraw(); 675 }
675 } 676
676 677 implementationsTree.setModel(null);
677 public void showImplementations() { 678
678 679 DefaultMutableTreeNode node = null;
679 if (reference == null) { 680
680 return; 681 // get the class implementations
681 } 682 if (reference.entry instanceof ClassEntry)
682 683 node = this.controller.getClassImplementations((ClassEntry) reference.entry);
683 implementationsTree.setModel(null); 684 else // get the method implementations
684 685 if (reference.entry instanceof MethodEntry)
685 DefaultMutableTreeNode node = null; 686 node = this.controller.getMethodImplementations((MethodEntry) reference.entry);
686 687
687 // get the class implementations 688 if (node != null) {
688 if (reference.entry instanceof ClassEntry) 689 // show the tree at the root
689 node = this.controller.getClassImplementations((ClassEntry) reference.entry); 690 TreePath path = getPathToRoot(node);
690 else // get the method implementations 691 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
691 if (reference.entry instanceof MethodEntry) 692 implementationsTree.expandPath(path);
692 node = this.controller.getMethodImplementations((MethodEntry) reference.entry); 693 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
693 694 }
694 if (node != null) { 695
695 // show the tree at the root 696 tabs.setSelectedIndex(1);
696 TreePath path = getPathToRoot(node); 697 redraw();
697 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); 698 }
698 implementationsTree.expandPath(path); 699
699 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); 700 public void showCalls() {
700 } 701 if (reference == null) {
701 702 return;
702 tabs.setSelectedIndex(1); 703 }
703 redraw(); 704
704 } 705 if (reference.entry instanceof ClassEntry) {
705 706 // look for calls to the default constructor
706 public void showCalls() { 707 // TODO: get a list of all the constructors and find calls to all of them
707 if (reference == null) { 708 BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V")));
708 return; 709 callsTree.setModel(new DefaultTreeModel(node));
709 } 710 } else if (reference.entry instanceof FieldEntry) {
710 711 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry);
711 if (reference.entry instanceof ClassEntry) { 712 callsTree.setModel(new DefaultTreeModel(node));
712 // look for calls to the default constructor 713 } else if (reference.entry instanceof MethodEntry) {
713 // TODO: get a list of all the constructors and find calls to all of them 714 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry);
714 BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); 715 callsTree.setModel(new DefaultTreeModel(node));
715 callsTree.setModel(new DefaultTreeModel(node)); 716 } else if (reference.entry instanceof ConstructorEntry) {
716 } else if (reference.entry instanceof FieldEntry) { 717 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry);
717 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); 718 callsTree.setModel(new DefaultTreeModel(node));
718 callsTree.setModel(new DefaultTreeModel(node)); 719 }
719 } else if (reference.entry instanceof MethodEntry) { 720
720 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); 721 tabs.setSelectedIndex(2);
721 callsTree.setModel(new DefaultTreeModel(node)); 722 redraw();
722 } else if (reference.entry instanceof ConstructorEntry) { 723 }
723 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); 724
724 callsTree.setModel(new DefaultTreeModel(node)); 725 public void toggleMapping() {
725 } 726 if (this.controller.entryHasDeobfuscatedName(reference.entry)) {
726 727 this.controller.removeMapping(reference);
727 tabs.setSelectedIndex(2); 728 } else {
728 redraw(); 729 this.controller.markAsDeobfuscated(reference);
729 } 730 }
730 731 }
731 public void toggleMapping() { 732
732 if (this.controller.entryHasDeobfuscatedName(reference.entry)) { 733 private TreePath getPathToRoot(TreeNode node) {
733 this.controller.removeMapping(reference); 734 List<TreeNode> nodes = Lists.newArrayList();
734 } else { 735 TreeNode n = node;
735 this.controller.markAsDeobfuscated(reference); 736 do {
736 } 737 nodes.add(n);
737 } 738 n = n.getParent();
738 739 } while (n != null);
739 private TreePath getPathToRoot(TreeNode node) { 740 Collections.reverse(nodes);
740 List<TreeNode> nodes = Lists.newArrayList(); 741 return new TreePath(nodes.toArray());
741 TreeNode n = node; 742 }
742 do { 743
743 nodes.add(n); 744 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
744 n = n.getParent(); 745 int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
745 } while (n != null); 746 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
746 Collections.reverse(nodes); 747 callback.apply(response);
747 return new TreePath(nodes.toArray()); 748 }
748 } 749
749 750 public void saveMapping() throws IOException {
750 public void showDiscardDiag(Function<Integer, Void> callback, String... options) 751 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
751 { 752 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile());
752 int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, 753 }
753 JOptionPane.QUESTION_MESSAGE, null, options, options[2]); 754
754 callback.apply(response); 755 public void close() {
755 } 756 if (!this.controller.isDirty()) {
756 757 // everything is saved, we can exit safely
757 public void saveMapping() throws IOException 758 this.frame.dispose();
758 { 759 System.exit(0);
759 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) 760 } else {
760 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); 761 // ask to save before closing
761 } 762 showDiscardDiag((response) -> {
762 763 if (response == JOptionPane.YES_OPTION) {
763 public void close() { 764 try {
764 if (!this.controller.isDirty()) { 765 this.saveMapping();
765 // everything is saved, we can exit safely 766 this.frame.dispose();
766 this.frame.dispose(); 767
767 System.exit(0); 768 } catch (IOException ex) {
768 } else { 769 throw new Error(ex);
769 // ask to save before closing 770 }
770 showDiscardDiag((response) -> { 771 } else if (response == JOptionPane.NO_OPTION)
771 if (response == JOptionPane.YES_OPTION) 772 this.frame.dispose();
772 { 773
773 try { 774 return null;
774 this.saveMapping(); 775 }, "Save and exit", "Discard changes", "Cancel");
775 this.frame.dispose(); 776 }
776 777 }
777 } catch (IOException ex) { 778
778 throw new Error(ex); 779 public void redraw() {
779 } 780 this.frame.validate();
780 } 781 this.frame.repaint();
781 else if (response == JOptionPane.NO_OPTION) 782 }
782 this.frame.dispose(); 783
783 784 public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException {
784 return null; 785 // package rename
785 }, "Save and exit", "Discard changes", "Cancel"); 786 if (data instanceof String) {
786 } 787 for (int i = 0; i < node.getChildCount(); i++) {
787 } 788 data = Descriptor.toJvmName((String) data);
788 789 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
789 public void redraw() { 790 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
790 this.frame.validate(); 791 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
791 this.frame.repaint(); 792 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount());
792 } 793 childNode.setUserObject(dataChild);
793 794 }
794 public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException 795 node.setUserObject(data);
795 { 796 // Ob package will never be modified, just reload deob view
796 // package rename 797 this.deobfPanel.deobfClasses.reload();
797 if (data instanceof String) 798 }
798 { 799 // class rename
799 for (int i = 0; i < node.getChildCount(); i++) 800 else if (data instanceof ClassEntry)
800 { 801 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true);
801 data = Descriptor.toJvmName((String) data); 802 }
802 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); 803
803 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); 804 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName) {
804 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); 805 String oldEntry = deobfReference.entry.getClassEntry().getPackageName();
805 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); 806 String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName();
806 childNode.setUserObject(dataChild); 807 moveClassTree(deobfReference, newName, oldEntry == null,
807 } 808 newEntry == null);
808 node.setUserObject(data); 809 }
809 // Ob package will never be modified, just reload deob view 810
810 this.deobfPanel.deobfClasses.reload(); 811 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) {
811 } 812 ClassEntry oldEntry = deobfReference.entry.getClassEntry();
812 // class rename 813 ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName));
813 else if (data instanceof ClassEntry) 814
814 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); 815 // Ob -> deob
815 } 816 if (isOldOb && !isNewOb) {
816 817 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses);
817 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName) 818 ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry);
818 { 819 this.obfPanel.obfClasses.removeNode(packageNode, oldEntry);
819 String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); 820 this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode);
820 String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); 821 this.deobfPanel.deobfClasses.reload();
821 moveClassTree(deobfReference, newName, oldEntry == null, 822 this.obfPanel.obfClasses.reload();
822 newEntry == null); 823 }
823 } 824 // Deob -> ob
824 825 else if (isNewOb && !isOldOb) {
825 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) 826 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses);
826 { 827 ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry);
827 ClassEntry oldEntry = deobfReference.entry.getClassEntry(); 828 this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry);
828 ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); 829 this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode);
829 830 this.deobfPanel.deobfClasses.reload();
830 // Ob -> deob 831 this.obfPanel.obfClasses.reload();
831 if (isOldOb && !isNewOb) 832 }
832 { 833 // Local move
833 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); 834 else if (isOldOb) {
834 ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); 835 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null);
835 this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); 836 this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry));
836 this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); 837 this.obfPanel.obfClasses.reload();
837 this.deobfPanel.deobfClasses.reload(); 838 } else {
838 this.obfPanel.obfClasses.reload(); 839 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null);
839 } 840 this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry));
840 // Deob -> ob 841 this.deobfPanel.deobfClasses.reload();
841 else if (isNewOb && !isOldOb) 842 }
842 { 843 }
843 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses);
844 ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry);
845 this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry);
846 this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode);
847 this.deobfPanel.deobfClasses.reload();
848 this.obfPanel.obfClasses.reload();
849 }
850 // Local move
851 else if (isOldOb)
852 {
853 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null);
854 this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry));
855 this.obfPanel.obfClasses.reload();
856 }
857 else
858 {
859 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null);
860 this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry));
861 this.deobfPanel.deobfClasses.reload();
862 }
863 }
864} 844}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index 68fd484..1b461da 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
@@ -30,326 +31,321 @@ import java.util.jar.JarFile;
30 31
31public class GuiController { 32public class GuiController {
32 33
33 private Deobfuscator deobfuscator; 34 private Deobfuscator deobfuscator;
34 private Gui gui; 35 private Gui gui;
35 private SourceIndex index; 36 private SourceIndex index;
36 private ClassEntry currentObfClass; 37 private ClassEntry currentObfClass;
37 private boolean isDirty; 38 private boolean isDirty;
38 private Deque<EntryReference<Entry, Entry>> referenceStack; 39 private Deque<EntryReference<Entry, Entry>> referenceStack;
39 40
40 public GuiController(Gui gui) { 41 public GuiController(Gui gui) {
41 this.gui = gui; 42 this.gui = gui;
42 this.deobfuscator = null; 43 this.deobfuscator = null;
43 this.index = null; 44 this.index = null;
44 this.currentObfClass = null; 45 this.currentObfClass = null;
45 this.isDirty = false; 46 this.isDirty = false;
46 this.referenceStack = Queues.newArrayDeque(); 47 this.referenceStack = Queues.newArrayDeque();
47 } 48 }
48 49
49 public boolean isDirty() { 50 public boolean isDirty() {
50 return this.isDirty; 51 return this.isDirty;
51 } 52 }
52 53
53 public void openJar(final JarFile jar) { 54 public void openJar(final JarFile jar) {
54 this.gui.onStartOpenJar(); 55 this.gui.onStartOpenJar();
55 this.deobfuscator = new Deobfuscator(jar); 56 this.deobfuscator = new Deobfuscator(jar);
56 this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); 57 this.gui.onFinishOpenJar(this.deobfuscator.getJarName());
57 refreshClasses(); 58 refreshClasses();
58 } 59 }
59 60
60 public void closeJar() { 61 public void closeJar() {
61 this.deobfuscator = null; 62 this.deobfuscator = null;
62 this.gui.onCloseJar(); 63 this.gui.onCloseJar();
63 } 64 }
64 65
65 public void openEnigmaMappings(File file) throws IOException, MappingParseException { 66 public void openEnigmaMappings(File file) throws IOException, MappingParseException {
66 this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); 67 this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file));
67 this.isDirty = false; 68 this.isDirty = false;
68 this.gui.setMappingsFile(file); 69 this.gui.setMappingsFile(file);
69 refreshClasses(); 70 refreshClasses();
70 refreshCurrentClass(); 71 refreshCurrentClass();
71 } 72 }
72 73
73 public void saveMappings(File file) throws IOException { 74 public void saveMappings(File file) throws IOException {
74 Mappings mappings = this.deobfuscator.getMappings(); 75 Mappings mappings = this.deobfuscator.getMappings();
75 switch (mappings.getOriginMappingFormat()) 76 switch (mappings.getOriginMappingFormat()) {
76 { 77 case SRG_FILE:
77 case SRG_FILE: 78 saveSRGMappings(file);
78 saveSRGMappings(file); 79 break;
79 break; 80 default:
80 default: 81 saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat());
81 saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); 82 break;
82 break; 83 }
83 } 84
84 85 }
85 } 86
86 87 public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException {
87 public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { 88 this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat);
88 this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); 89 this.isDirty = false;
89 this.isDirty = false; 90 }
90 } 91
91 92 public void saveSRGMappings(File file) throws IOException {
92 public void saveSRGMappings(File file) throws IOException { 93 this.deobfuscator.getMappings().saveSRGMappings(file);
93 this.deobfuscator.getMappings().saveSRGMappings(file); 94 this.isDirty = false;
94 this.isDirty = false; 95 }
95 } 96
96 97 public void closeMappings() {
97 public void closeMappings() { 98 this.deobfuscator.setMappings(null);
98 this.deobfuscator.setMappings(null); 99 this.gui.setMappingsFile(null);
99 this.gui.setMappingsFile(null); 100 refreshClasses();
100 refreshClasses(); 101 refreshCurrentClass();
101 refreshCurrentClass(); 102 }
102 } 103
103 104 public void rebuildMethodNames() {
104 public void rebuildMethodNames() { 105 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress));
105 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); 106 }
106 } 107
107 108 public void exportSource(final File dirOut) {
108 public void exportSource(final File dirOut) { 109 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress));
109 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); 110 }
110 } 111
111 112 public void exportJar(final File fileOut) {
112 public void exportJar(final File fileOut) { 113 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress));
113 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); 114 }
114 } 115
115 116 public Token getToken(int pos) {
116 public Token getToken(int pos) { 117 if (this.index == null) {
117 if (this.index == null) { 118 return null;
118 return null; 119 }
119 } 120 return this.index.getReferenceToken(pos);
120 return this.index.getReferenceToken(pos); 121 }
121 } 122
122 123 public EntryReference<Entry, Entry> getDeobfReference(Token token) {
123 public EntryReference<Entry, Entry> getDeobfReference(Token token) { 124 if (this.index == null) {
124 if (this.index == null) { 125 return null;
125 return null; 126 }
126 } 127 return this.index.getDeobfReference(token);
127 return this.index.getDeobfReference(token); 128 }
128 } 129
129 130 public ReadableToken getReadableToken(Token token) {
130 public ReadableToken getReadableToken(Token token) { 131 if (this.index == null) {
131 if (this.index == null) { 132 return null;
132 return null; 133 }
133 } 134 return new ReadableToken(
134 return new ReadableToken( 135 this.index.getLineNumber(token.start),
135 this.index.getLineNumber(token.start), 136 this.index.getColumnNumber(token.start),
136 this.index.getColumnNumber(token.start), 137 this.index.getColumnNumber(token.end)
137 this.index.getColumnNumber(token.end) 138 );
138 ); 139 }
139 } 140
140 141 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
141 public boolean entryHasDeobfuscatedName(Entry deobfEntry) { 142 return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry));
142 return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); 143 }
143 } 144
144 145 public boolean entryIsInJar(Entry deobfEntry) {
145 public boolean entryIsInJar(Entry deobfEntry) { 146 return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry));
146 return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); 147 }
147 } 148
148 149 public boolean referenceIsRenameable(EntryReference<Entry, Entry> deobfReference) {
149 public boolean referenceIsRenameable(EntryReference<Entry, Entry> deobfReference) { 150 return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true);
150 return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true); 151 }
151 } 152
152 153 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
153 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { 154 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry);
154 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 155 ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry);
155 ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); 156 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
156 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); 157 }
157 } 158
158 159 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
159 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { 160 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry);
160 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 161 return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry);
161 return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); 162 }
162 } 163
163 164 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
164 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { 165 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
165 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 166 MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry);
166 MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); 167 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
167 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); 168 }
168 } 169
169 170 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
170 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { 171 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
171 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 172 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry);
172 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); 173 if (rootNodes.isEmpty()) {
173 if (rootNodes.isEmpty()) { 174 return null;
174 return null; 175 }
175 } 176 if (rootNodes.size() > 1) {
176 if (rootNodes.size() > 1) { 177 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
177 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); 178 }
178 } 179 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
179 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); 180 }
180 } 181
181 182 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
182 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { 183 FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry);
183 FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); 184 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry);
184 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); 185 rootNode.load(this.deobfuscator.getJarIndex(), true);
185 rootNode.load(this.deobfuscator.getJarIndex(), true); 186 return rootNode;
186 return rootNode; 187 }
187 } 188
188 189 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
189 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { 190 BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry);
190 BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); 191 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry);
191 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); 192 rootNode.load(this.deobfuscator.getJarIndex(), true);
192 rootNode.load(this.deobfuscator.getJarIndex(), true); 193 return rootNode;
193 return rootNode; 194 }
194 } 195
195 196 public void rename(EntryReference<Entry, Entry> deobfReference, String newName) {
196 public void rename(EntryReference<Entry, Entry> deobfReference, String newName) { 197 rename(deobfReference, newName, true, true);
197 rename(deobfReference, newName, true, true); 198 }
198 } 199
199 200 public void rename(EntryReference<Entry, Entry> deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) {
200 public void rename(EntryReference<Entry, Entry> deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) 201 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference);
201 { 202 this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache);
202 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 203 this.isDirty = true;
203 this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); 204
204 this.isDirty = true; 205 if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
205 206 this.gui.moveClassTree(deobfReference, newName);
206 if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) 207 refreshCurrentClass(obfReference);
207 this.gui.moveClassTree(deobfReference, newName); 208
208 refreshCurrentClass(obfReference); 209 }
209 210
210 } 211 public void removeMapping(EntryReference<Entry, Entry> deobfReference) {
211 212 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference);
212 public void removeMapping(EntryReference<Entry, Entry> deobfReference) { 213 this.deobfuscator.removeMapping(obfReference.getNameableEntry());
213 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 214 this.isDirty = true;
214 this.deobfuscator.removeMapping(obfReference.getNameableEntry()); 215 if (deobfReference.entry instanceof ClassEntry)
215 this.isDirty = true; 216 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true);
216 if (deobfReference.entry instanceof ClassEntry) 217 refreshCurrentClass(obfReference);
217 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); 218 }
218 refreshCurrentClass(obfReference); 219
219 } 220 public void markAsDeobfuscated(EntryReference<Entry, Entry> deobfReference) {
220 221 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference);
221 public void markAsDeobfuscated(EntryReference<Entry, Entry> deobfReference) { 222 this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
222 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 223 this.isDirty = true;
223 this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); 224 if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
224 this.isDirty = true; 225 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false);
225 if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) 226 refreshCurrentClass(obfReference);
226 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); 227 }
227 refreshCurrentClass(obfReference); 228
228 } 229 public void openDeclaration(Entry deobfEntry) {
229 230 if (deobfEntry == null) {
230 public void openDeclaration(Entry deobfEntry) { 231 throw new IllegalArgumentException("Entry cannot be null!");
231 if (deobfEntry == null) { 232 }
232 throw new IllegalArgumentException("Entry cannot be null!"); 233 openReference(new EntryReference<>(deobfEntry, deobfEntry.getName()));
233 } 234 }
234 openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); 235
235 } 236 public void openReference(EntryReference<Entry, Entry> deobfReference) {
236 237 if (deobfReference == null) {
237 public void openReference(EntryReference<Entry, Entry> deobfReference) { 238 throw new IllegalArgumentException("Reference cannot be null!");
238 if (deobfReference == null) { 239 }
239 throw new IllegalArgumentException("Reference cannot be null!"); 240
240 } 241 // get the reference target class
241 242 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference);
242 // get the reference target class 243 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
243 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 244 if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
244 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); 245 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
245 if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { 246 }
246 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); 247 if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) {
247 } 248 // deobfuscate the class, then navigate to the reference
248 if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { 249 this.currentObfClass = obfClassEntry;
249 // deobfuscate the class, then navigate to the reference 250 deobfuscate(this.currentObfClass, obfReference);
250 this.currentObfClass = obfClassEntry; 251 } else {
251 deobfuscate(this.currentObfClass, obfReference); 252 showReference(obfReference);
252 } else { 253 }
253 showReference(obfReference); 254 }
254 } 255
255 } 256 private void showReference(EntryReference<Entry, Entry> obfReference) {
256 257 EntryReference<Entry, Entry> deobfReference = this.deobfuscator.deobfuscateReference(obfReference);
257 private void showReference(EntryReference<Entry, Entry> obfReference) { 258 Collection<Token> tokens = this.index.getReferenceTokens(deobfReference);
258 EntryReference<Entry, Entry> deobfReference = this.deobfuscator.deobfuscateReference(obfReference); 259 if (tokens.isEmpty()) {
259 Collection<Token> tokens = this.index.getReferenceTokens(deobfReference); 260 // DEBUG
260 if (tokens.isEmpty()) { 261 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass));
261 // DEBUG 262 } else {
262 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); 263 this.gui.showTokens(tokens);
263 } else { 264 }
264 this.gui.showTokens(tokens); 265 }
265 } 266
266 } 267 public void savePreviousReference(EntryReference<Entry, Entry> deobfReference) {
267 268 this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference));
268 public void savePreviousReference(EntryReference<Entry, Entry> deobfReference) { 269 }
269 this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); 270
270 } 271 public void openPreviousReference() {
271 272 if (hasPreviousLocation()) {
272 public void openPreviousReference() { 273 openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop()));
273 if (hasPreviousLocation()) { 274 }
274 openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); 275 }
275 } 276
276 } 277 public boolean hasPreviousLocation() {
277 278 return !this.referenceStack.isEmpty();
278 public boolean hasPreviousLocation() { 279 }
279 return !this.referenceStack.isEmpty(); 280
280 } 281 private void refreshClasses() {
281 282 List<ClassEntry> obfClasses = Lists.newArrayList();
282 private void refreshClasses() { 283 List<ClassEntry> deobfClasses = Lists.newArrayList();
283 List<ClassEntry> obfClasses = Lists.newArrayList(); 284 this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
284 List<ClassEntry> deobfClasses = Lists.newArrayList(); 285 this.gui.setObfClasses(obfClasses);
285 this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); 286 this.gui.setDeobfClasses(deobfClasses);
286 this.gui.setObfClasses(obfClasses); 287 }
287 this.gui.setDeobfClasses(deobfClasses); 288
288 } 289 public void refreshCurrentClass() {
289 290 refreshCurrentClass(null);
290 public void refreshCurrentClass() { 291 }
291 refreshCurrentClass(null); 292
292 } 293 private void refreshCurrentClass(EntryReference<Entry, Entry> obfReference) {
293 294 if (this.currentObfClass != null) {
294 private void refreshCurrentClass(EntryReference<Entry, Entry> obfReference) { 295 deobfuscate(this.currentObfClass, obfReference);
295 if (this.currentObfClass != null) { 296 }
296 deobfuscate(this.currentObfClass, obfReference); 297 }
297 } 298
298 } 299 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry, Entry> obfReference) {
299 300
300 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry, Entry> obfReference) { 301 this.gui.setSource("(deobfuscating...)");
301 302
302 this.gui.setSource("(deobfuscating...)"); 303 // run the deobfuscator in a separate thread so we don't block the GUI event queue
303 304 new Thread(() ->
304 // run the deobfuscator in a separate thread so we don't block the GUI event queue 305 {
305 new Thread(() -> 306 // decompile,deobfuscate the bytecode
306 { 307 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName());
307 // decompile,deobfuscate the bytecode 308 if (sourceTree == null) {
308 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); 309 // decompilation of this class is not supported
309 if (sourceTree == null) { 310 gui.setSource("Unable to find class: " + classEntry);
310 // decompilation of this class is not supported 311 return;
311 gui.setSource("Unable to find class: " + classEntry); 312 }
312 return; 313 String source = deobfuscator.getSource(sourceTree);
313 } 314 index = deobfuscator.getSourceIndex(sourceTree, source);
314 String source = deobfuscator.getSource(sourceTree); 315 gui.setSource(index.getSource());
315 index = deobfuscator.getSourceIndex(sourceTree, source); 316 if (obfReference != null) {
316 gui.setSource(index.getSource()); 317 showReference(obfReference);
317 if (obfReference != null) { 318 }
318 showReference(obfReference); 319
319 } 320 // set the highlighted tokens
320 321 List<Token> obfuscatedTokens = Lists.newArrayList();
321 // set the highlighted tokens 322 List<Token> deobfuscatedTokens = Lists.newArrayList();
322 List<Token> obfuscatedTokens = Lists.newArrayList(); 323 List<Token> otherTokens = Lists.newArrayList();
323 List<Token> deobfuscatedTokens = Lists.newArrayList(); 324 for (Token token : index.referenceTokens()) {
324 List<Token> otherTokens = Lists.newArrayList(); 325 EntryReference<Entry, Entry> reference = index.getDeobfReference(token);
325 for (Token token : index.referenceTokens()) { 326 if (referenceIsRenameable(reference)) {
326 EntryReference<Entry, Entry> reference = index.getDeobfReference(token); 327 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
327 if (referenceIsRenameable(reference)) { 328 deobfuscatedTokens.add(token);
328 if (entryHasDeobfuscatedName(reference.getNameableEntry())) { 329 } else {
329 deobfuscatedTokens.add(token); 330 obfuscatedTokens.add(token);
330 } else { 331 }
331 obfuscatedTokens.add(token); 332 } else {
332 } 333 otherTokens.add(token);
333 } else { 334 }
334 otherTokens.add(token); 335 }
335 } 336 gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
336 } 337 }).start();
337 gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); 338 }
338 }).start(); 339
339 } 340 public Deobfuscator getDeobfuscator() {
340 341 return deobfuscator;
341 public Deobfuscator getDeobfuscator() 342 }
342 { 343
343 return deobfuscator; 344 public void modifierChange(ItemEvent event) {
344 } 345 if (event.getStateChange() == ItemEvent.SELECTED) {
345 346 deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem());
346 public void modifierChange(ItemEvent event) 347 this.isDirty = true;
347 { 348 refreshCurrentClass();
348 if (event.getStateChange() == ItemEvent.SELECTED) 349 }
349 { 350 }
350 deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem());
351 this.isDirty = true;
352 refreshCurrentClass();
353 }
354 }
355} 351}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
index 85b65b0..8bf57d3 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import javax.swing.*; 14import javax.swing.*;
@@ -17,26 +18,26 @@ import java.util.Arrays;
17 18
18public class GuiTricks { 19public class GuiTricks {
19 20
20 public static JLabel unboldLabel(JLabel label) { 21 public static JLabel unboldLabel(JLabel label) {
21 Font font = label.getFont(); 22 Font font = label.getFont();
22 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); 23 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
23 return label; 24 return label;
24 } 25 }
25 26
26 public static void deactivateButton(JButton button) { 27 public static void deactivateButton(JButton button) {
27 button.setEnabled(false); 28 button.setEnabled(false);
28 button.setText(""); 29 button.setText("");
29 for (ActionListener listener : Arrays.asList(button.getActionListeners())) { 30 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
30 button.removeActionListener(listener); 31 button.removeActionListener(listener);
31 } 32 }
32 } 33 }
33 34
34 public static void activateButton(JButton button, String text, ActionListener newListener) { 35 public static void activateButton(JButton button, String text, ActionListener newListener) {
35 button.setText(text); 36 button.setText(text);
36 button.setEnabled(true); 37 button.setEnabled(true);
37 for (ActionListener listener : Arrays.asList(button.getActionListeners())) { 38 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
38 button.removeActionListener(listener); 39 button.removeActionListener(listener);
39 } 40 }
40 button.addActionListener(newListener); 41 button.addActionListener(newListener);
41 } 42 }
42} 43}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
index 671f85f..4f5231f 100644
--- a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -8,25 +8,11 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
14import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
15
16import java.awt.BorderLayout;
17import java.awt.Container;
18import java.awt.Dimension;
19import java.awt.FlowLayout;
20import java.awt.event.ActionListener;
21import java.awt.event.KeyAdapter;
22import java.awt.event.KeyEvent;
23import java.util.Collection;
24import java.util.List;
25import java.util.Map;
26
27import javax.swing.*;
28import javax.swing.text.Highlighter.HighlightPainter;
29
30import cuchaz.enigma.Constants; 16import cuchaz.enigma.Constants;
31import cuchaz.enigma.Deobfuscator; 17import cuchaz.enigma.Deobfuscator;
32import cuchaz.enigma.analysis.SourceIndex; 18import cuchaz.enigma.analysis.SourceIndex;
@@ -39,403 +25,410 @@ import cuchaz.enigma.mapping.ClassEntry;
39import cuchaz.enigma.mapping.Entry; 25import cuchaz.enigma.mapping.Entry;
40import de.sciss.syntaxpane.DefaultSyntaxKit; 26import de.sciss.syntaxpane.DefaultSyntaxKit;
41 27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30import java.awt.*;
31import java.awt.event.ActionListener;
32import java.awt.event.KeyAdapter;
33import java.awt.event.KeyEvent;
34import java.util.Collection;
35import java.util.List;
36import java.util.Map;
42 37
43public class MemberMatchingGui<T extends Entry> { 38public class MemberMatchingGui<T extends Entry> {
44 39
45 private enum SourceType { 40 // controls
46 Matched { 41 private JFrame frame;
47 @Override 42 private Map<SourceType, JRadioButton> sourceTypeButtons;
48 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) { 43 private ClassSelector sourceClasses;
49 return matches.getSourceClassesWithoutUnmatchedEntries(); 44 private CodeReader sourceReader;
50 } 45 private CodeReader destReader;
51 }, 46 private JButton matchButton;
52 Unmatched { 47 private JButton unmatchableButton;
53 @Override 48 private JLabel sourceLabel;
54 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) { 49 private JLabel destLabel;
55 return matches.getSourceClassesWithUnmatchedEntries(); 50 private HighlightPainter unmatchedHighlightPainter;
56 } 51 private HighlightPainter matchedHighlightPainter;
57 }; 52 private ClassMatches classMatches;
58 53 private MemberMatches<T> memberMatches;
59 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { 54 private Deobfuscator sourceDeobfuscator;
60 JRadioButton button = new JRadioButton(name(), this == getDefault()); 55 private Deobfuscator destDeobfuscator;
61 button.setActionCommand(name()); 56 private SaveListener<T> saveListener;
62 button.addActionListener(listener); 57 private SourceType sourceType;
63 group.add(button); 58 private ClassEntry obfSourceClass;
64 return button; 59 private ClassEntry obfDestClass;
65 } 60 private T obfSourceEntry;
66 61 private T obfDestEntry;
67 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches); 62 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
68 63
69 public static SourceType getDefault() { 64 this.classMatches = classMatches;
70 return values()[0]; 65 memberMatches = fieldMatches;
71 } 66 this.sourceDeobfuscator = sourceDeobfuscator;
72 } 67 this.destDeobfuscator = destDeobfuscator;
73 68
74 public interface SaveListener<T extends Entry> { 69 // init frame
75 void save(MemberMatches<T> matches); 70 frame = new JFrame(Constants.NAME + " - Member Matcher");
76 } 71 final Container pane = frame.getContentPane();
77 72 pane.setLayout(new BorderLayout());
78 // controls 73
79 private JFrame frame; 74 // init classes side
80 private Map<SourceType, JRadioButton> sourceTypeButtons; 75 JPanel classesPanel = new JPanel();
81 private ClassSelector sourceClasses; 76 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
82 private CodeReader sourceReader; 77 classesPanel.setPreferredSize(new Dimension(200, 0));
83 private CodeReader destReader; 78 pane.add(classesPanel, BorderLayout.WEST);
84 private JButton matchButton; 79 classesPanel.add(new JLabel("Classes"));
85 private JButton unmatchableButton; 80
86 private JLabel sourceLabel; 81 // init source type radios
87 private JLabel destLabel; 82 JPanel sourceTypePanel = new JPanel();
88 private HighlightPainter unmatchedHighlightPainter; 83 classesPanel.add(sourceTypePanel);
89 private HighlightPainter matchedHighlightPainter; 84 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
90 private ClassMatches classMatches; 85 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
91 private MemberMatches<T> memberMatches; 86 ButtonGroup sourceTypeButtons = new ButtonGroup();
92 private Deobfuscator sourceDeobfuscator; 87 this.sourceTypeButtons = Maps.newHashMap();
93 private Deobfuscator destDeobfuscator; 88 for (SourceType sourceType : SourceType.values()) {
94 private SaveListener<T> saveListener; 89 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
95 private SourceType sourceType; 90 this.sourceTypeButtons.put(sourceType, button);
96 private ClassEntry obfSourceClass; 91 sourceTypePanel.add(button);
97 private ClassEntry obfDestClass; 92 }
98 private T obfSourceEntry; 93
99 private T obfDestEntry; 94 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
100 95 sourceClasses.setSelectionListener(this::setSourceClass);
101 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { 96 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
102 97 classesPanel.add(sourceScroller);
103 this.classMatches = classMatches; 98
104 memberMatches = fieldMatches; 99 // init readers
105 this.sourceDeobfuscator = sourceDeobfuscator; 100 DefaultSyntaxKit.initKit();
106 this.destDeobfuscator = destDeobfuscator; 101 sourceReader = new CodeReader();
107 102 sourceReader.setSelectionListener(reference ->
108 // init frame 103 {
109 frame = new JFrame(Constants.NAME + " - Member Matcher"); 104 if (reference != null) {
110 final Container pane = frame.getContentPane(); 105 onSelectSource(reference.entry);
111 pane.setLayout(new BorderLayout()); 106 } else {
112 107 onSelectSource(null);
113 // init classes side 108 }
114 JPanel classesPanel = new JPanel(); 109 });
115 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); 110 destReader = new CodeReader();
116 classesPanel.setPreferredSize(new Dimension(200, 0)); 111 destReader.setSelectionListener(reference ->
117 pane.add(classesPanel, BorderLayout.WEST); 112 {
118 classesPanel.add(new JLabel("Classes")); 113 if (reference != null) {
119 114 onSelectDest(reference.entry);
120 // init source type radios 115 } else {
121 JPanel sourceTypePanel = new JPanel(); 116 onSelectDest(null);
122 classesPanel.add(sourceTypePanel); 117 }
123 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); 118 });
124 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); 119
125 ButtonGroup sourceTypeButtons = new ButtonGroup(); 120 // add key bindings
126 this.sourceTypeButtons = Maps.newHashMap(); 121 KeyAdapter keyListener = new KeyAdapter() {
127 for (SourceType sourceType : SourceType.values()) { 122 @Override
128 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); 123 public void keyPressed(KeyEvent event) {
129 this.sourceTypeButtons.put(sourceType, button); 124 if (event.getKeyCode() == KeyEvent.VK_M)
130 sourceTypePanel.add(button); 125 matchButton.doClick();
131 } 126 }
132 127 };
133 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); 128 sourceReader.addKeyListener(keyListener);
134 sourceClasses.setSelectionListener(this::setSourceClass); 129 destReader.addKeyListener(keyListener);
135 JScrollPane sourceScroller = new JScrollPane(sourceClasses); 130
136 classesPanel.add(sourceScroller); 131 // init all the splits
137 132 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane(
138 // init readers 133 destReader));
139 DefaultSyntaxKit.initKit(); 134 splitRight.setResizeWeight(0.5); // resize 50:50
140 sourceReader = new CodeReader(); 135 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
141 sourceReader.setSelectionListener(reference -> 136 splitLeft.setResizeWeight(0); // let the right side take all the slack
142 { 137 pane.add(splitLeft, BorderLayout.CENTER);
143 if (reference != null) { 138 splitLeft.resetToPreferredSizes();
144 onSelectSource(reference.entry); 139
145 } else { 140 // init bottom panel
146 onSelectSource(null); 141 JPanel bottomPanel = new JPanel();
147 } 142 bottomPanel.setLayout(new FlowLayout());
148 }); 143 pane.add(bottomPanel, BorderLayout.SOUTH);
149 destReader = new CodeReader(); 144
150 destReader.setSelectionListener(reference -> 145 matchButton = new JButton();
151 { 146 unmatchableButton = new JButton();
152 if (reference != null) { 147
153 onSelectDest(reference.entry); 148 sourceLabel = new JLabel();
154 } else { 149 bottomPanel.add(sourceLabel);
155 onSelectDest(null); 150 bottomPanel.add(matchButton);
156 } 151 bottomPanel.add(unmatchableButton);
157 }); 152 destLabel = new JLabel();
158 153 bottomPanel.add(destLabel);
159 // add key bindings 154
160 KeyAdapter keyListener = new KeyAdapter() { 155 // show the frame
161 @Override 156 pane.doLayout();
162 public void keyPressed(KeyEvent event) { 157 frame.setSize(1024, 576);
163 if (event.getKeyCode() == KeyEvent.VK_M) 158 frame.setMinimumSize(new Dimension(640, 480));
164 matchButton.doClick(); 159 frame.setVisible(true);
165 } 160 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
166 }; 161
167 sourceReader.addKeyListener(keyListener); 162 unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
168 destReader.addKeyListener(keyListener); 163 matchedHighlightPainter = new DeobfuscatedHighlightPainter();
169 164
170 // init all the splits 165 // init state
171 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane( 166 saveListener = null;
172 destReader)); 167 obfSourceClass = null;
173 splitRight.setResizeWeight(0.5); // resize 50:50 168 obfDestClass = null;
174 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); 169 obfSourceEntry = null;
175 splitLeft.setResizeWeight(0); // let the right side take all the slack 170 obfDestEntry = null;
176 pane.add(splitLeft, BorderLayout.CENTER); 171 setSourceType(SourceType.getDefault());
177 splitLeft.resetToPreferredSizes(); 172 updateButtons();
178 173 }
179 // init bottom panel 174
180 JPanel bottomPanel = new JPanel(); 175 protected void setSourceType(SourceType val) {
181 bottomPanel.setLayout(new FlowLayout()); 176 sourceType = val;
182 pane.add(bottomPanel, BorderLayout.SOUTH); 177 updateSourceClasses();
183 178 }
184 matchButton = new JButton(); 179
185 unmatchableButton = new JButton(); 180 public void setSaveListener(SaveListener<T> val) {
186 181 saveListener = val;
187 sourceLabel = new JLabel(); 182 }
188 bottomPanel.add(sourceLabel); 183
189 bottomPanel.add(matchButton); 184 private void updateSourceClasses() {
190 bottomPanel.add(unmatchableButton); 185
191 destLabel = new JLabel(); 186 String selectedPackage = sourceClasses.getSelectedPackage();
192 bottomPanel.add(destLabel); 187
193 188 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
194 // show the frame 189 for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) {
195 pane.doLayout(); 190 deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry));
196 frame.setSize(1024, 576); 191 }
197 frame.setMinimumSize(new Dimension(640, 480)); 192 sourceClasses.setClasses(deobfClassEntries);
198 frame.setVisible(true); 193
199 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 194 if (selectedPackage != null) {
200 195 sourceClasses.expandPackage(selectedPackage);
201 unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); 196 }
202 matchedHighlightPainter = new DeobfuscatedHighlightPainter(); 197
203 198 for (SourceType sourceType : SourceType.values()) {
204 // init state 199 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
205 saveListener = null; 200 sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size()
206 obfSourceClass = null; 201 ));
207 obfDestClass = null; 202 }
208 obfSourceEntry = null; 203 }
209 obfDestEntry = null; 204
210 setSourceType(SourceType.getDefault()); 205 protected void setSourceClass(ClassEntry sourceClass) {
211 updateButtons(); 206
212 } 207 obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
213 208 obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
214 protected void setSourceType(SourceType val) { 209 if (obfDestClass == null) {
215 sourceType = val; 210 throw new Error("No matching dest class for source class: " + obfSourceClass);
216 updateSourceClasses(); 211 }
217 } 212
218 213 sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights);
219 public void setSaveListener(SaveListener<T> val) { 214 destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights);
220 saveListener = val; 215 }
221 } 216
222 217 protected void updateSourceHighlights() {
223 private void updateSourceClasses() { 218 highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries());
224 219 }
225 String selectedPackage = sourceClasses.getSelectedPackage(); 220
226 221 protected void updateDestHighlights() {
227 List<ClassEntry> deobfClassEntries = Lists.newArrayList(); 222 highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries());
228 for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) { 223 }
229 deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry)); 224
230 } 225 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
231 sourceClasses.setClasses(deobfClassEntries); 226 reader.clearHighlights();
232 227 // matched fields
233 if (selectedPackage != null) { 228 updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter);
234 sourceClasses.expandPackage(selectedPackage); 229 // unmatched fields
235 } 230 updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter);
236 231 }
237 for (SourceType sourceType : SourceType.values()) { 232
238 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", 233 private void updateHighlighted(Collection<T> entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) {
239 sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size() 234 SourceIndex index = reader.getSourceIndex();
240 )); 235 for (T obfT : entries) {
241 } 236 T deobfT = deobfuscator.deobfuscateEntry(obfT);
242 } 237 Token token = index.getDeclarationToken(deobfT);
243 238 if (token != null) {
244 protected void setSourceClass(ClassEntry sourceClass) { 239 reader.setHighlightedToken(token, painter);
245 240 }
246 obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); 241 }
247 obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); 242 }
248 if (obfDestClass == null) { 243
249 throw new Error("No matching dest class for source class: " + obfSourceClass); 244 private boolean isSelectionMatched() {
250 } 245 return obfSourceEntry != null && obfDestEntry != null
251 246 && memberMatches.isMatched(obfSourceEntry, obfDestEntry);
252 sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights); 247 }
253 destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights); 248
254 } 249 protected void onSelectSource(Entry source) {
255 250
256 protected void updateSourceHighlights() { 251 // start with no selection
257 highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries()); 252 if (isSelectionMatched()) {
258 } 253 setDest(null);
259 254 }
260 protected void updateDestHighlights() { 255 setSource(null);
261 highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries()); 256
262 } 257 // then look for a valid source selection
263 258 if (source != null) {
264 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) { 259
265 reader.clearHighlights(); 260 // this looks really scary, but it's actually ok
266 // matched fields 261 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
267 updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter); 262 // and MemberMatches.hasSource() will only pass entries that actually match T
268 // unmatched fields 263 @SuppressWarnings("unchecked")
269 updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter); 264 T sourceEntry = (T) source;
270 } 265
271 266 T obfSourceEntry = sourceDeobfuscator.obfuscateEntry(sourceEntry);
272 private void updateHighlighted(Collection<T> entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) 267 if (memberMatches.hasSource(obfSourceEntry)) {
273 { 268 setSource(obfSourceEntry);
274 SourceIndex index = reader.getSourceIndex(); 269
275 for (T obfT : entries) { 270 // look for a matched dest too
276 T deobfT = deobfuscator.deobfuscateEntry(obfT); 271 T obfDestEntry = memberMatches.matches().get(obfSourceEntry);
277 Token token = index.getDeclarationToken(deobfT); 272 if (obfDestEntry != null) {
278 if (token != null) { 273 setDest(obfDestEntry);
279 reader.setHighlightedToken(token, painter); 274 }
280 } 275 }
281 } 276 }
282 } 277
283 278 updateButtons();
284 private boolean isSelectionMatched() { 279 }
285 return obfSourceEntry != null && obfDestEntry != null 280
286 && memberMatches.isMatched(obfSourceEntry, obfDestEntry); 281 protected void onSelectDest(Entry dest) {
287 } 282
288 283 // start with no selection
289 protected void onSelectSource(Entry source) { 284 if (isSelectionMatched()) {
290 285 setSource(null);
291 // start with no selection 286 }
292 if (isSelectionMatched()) { 287 setDest(null);
293 setDest(null); 288
294 } 289 // then look for a valid dest selection
295 setSource(null); 290 if (dest != null) {
296 291
297 // then look for a valid source selection 292 // this looks really scary, but it's actually ok
298 if (source != null) { 293 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
299 294 // and MemberMatches.hasSource() will only pass entries that actually match T
300 // this looks really scary, but it's actually ok 295 @SuppressWarnings("unchecked")
301 // Deobfuscator.obfuscateEntry can handle all implementations of Entry 296 T destEntry = (T) dest;
302 // and MemberMatches.hasSource() will only pass entries that actually match T 297
303 @SuppressWarnings("unchecked") 298 T obfDestEntry = destDeobfuscator.obfuscateEntry(destEntry);
304 T sourceEntry = (T) source; 299 if (memberMatches.hasDest(obfDestEntry)) {
305 300 setDest(obfDestEntry);
306 T obfSourceEntry = sourceDeobfuscator.obfuscateEntry(sourceEntry); 301
307 if (memberMatches.hasSource(obfSourceEntry)) { 302 // look for a matched source too
308 setSource(obfSourceEntry); 303 T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry);
309 304 if (obfSourceEntry != null) {
310 // look for a matched dest too 305 setSource(obfSourceEntry);
311 T obfDestEntry = memberMatches.matches().get(obfSourceEntry); 306 }
312 if (obfDestEntry != null) { 307 }
313 setDest(obfDestEntry); 308 }
314 } 309
315 } 310 updateButtons();
316 } 311 }
317 312
318 updateButtons(); 313 private void setSource(T obfEntry) {
319 } 314 if (obfEntry == null) {
320 315 obfSourceEntry = null;
321 protected void onSelectDest(Entry dest) { 316 sourceLabel.setText("");
322 317 } else {
323 // start with no selection 318 obfSourceEntry = obfEntry;
324 if (isSelectionMatched()) { 319 sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator));
325 setSource(null); 320 }
326 } 321 }
327 setDest(null); 322
328 323 private void setDest(T obfEntry) {
329 // then look for a valid dest selection 324 if (obfEntry == null) {
330 if (dest != null) { 325 obfDestEntry = null;
331 326 destLabel.setText("");
332 // this looks really scary, but it's actually ok 327 } else {
333 // Deobfuscator.obfuscateEntry can handle all implementations of Entry 328 obfDestEntry = obfEntry;
334 // and MemberMatches.hasSource() will only pass entries that actually match T 329 destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator));
335 @SuppressWarnings("unchecked") 330 }
336 T destEntry = (T) dest; 331 }
337 332
338 T obfDestEntry = destDeobfuscator.obfuscateEntry(destEntry); 333 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
339 if (memberMatches.hasDest(obfDestEntry)) { 334 // show obfuscated and deobfuscated names, but no types/signatures
340 setDest(obfDestEntry); 335 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
341 336 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
342 // look for a matched source too 337 }
343 T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry); 338
344 if (obfSourceEntry != null) { 339 private void updateButtons() {
345 setSource(obfSourceEntry); 340
346 } 341 GuiTricks.deactivateButton(matchButton);
347 } 342 GuiTricks.deactivateButton(unmatchableButton);
348 } 343
349 344 if (obfSourceEntry != null && obfDestEntry != null) {
350 updateButtons(); 345 if (memberMatches.isMatched(obfSourceEntry, obfDestEntry))
351 } 346 GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch());
352 347 else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry(
353 private void setSource(T obfEntry) { 348 obfDestEntry))
354 if (obfEntry == null) { 349 GuiTricks.activateButton(matchButton, "Match", event -> match());
355 obfSourceEntry = null; 350 } else if (obfSourceEntry != null)
356 sourceLabel.setText(""); 351 GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable());
357 } else { 352 }
358 obfSourceEntry = obfEntry; 353
359 sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator)); 354 protected void match() {
360 } 355
361 } 356 // update the field matches
362 357 memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
363 private void setDest(T obfEntry) { 358 save();
364 if (obfEntry == null) { 359
365 obfDestEntry = null; 360 // update the ui
366 destLabel.setText(""); 361 onSelectSource(null);
367 } else { 362 onSelectDest(null);
368 obfDestEntry = obfEntry; 363 updateSourceHighlights();
369 destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator)); 364 updateDestHighlights();
370 } 365 updateSourceClasses();
371 } 366 }
372 367
373 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { 368 protected void unmatch() {
374 // show obfuscated and deobfuscated names, but no types/signatures 369
375 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); 370 // update the field matches
376 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName()); 371 memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
377 } 372 save();
378 373
379 private void updateButtons() { 374 // update the ui
380 375 onSelectSource(null);
381 GuiTricks.deactivateButton(matchButton); 376 onSelectDest(null);
382 GuiTricks.deactivateButton(unmatchableButton); 377 updateSourceHighlights();
383 378 updateDestHighlights();
384 if (obfSourceEntry != null && obfDestEntry != null) { 379 updateSourceClasses();
385 if (memberMatches.isMatched(obfSourceEntry, obfDestEntry)) 380 }
386 GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch()); 381
387 else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry( 382 protected void unmatchable() {
388 obfDestEntry)) 383
389 GuiTricks.activateButton(matchButton, "Match", event -> match()); 384 // update the field matches
390 } else if (obfSourceEntry != null) 385 memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator);
391 GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable()); 386 save();
392 } 387
393 388 // update the ui
394 protected void match() { 389 onSelectSource(null);
395 390 onSelectDest(null);
396 // update the field matches 391 updateSourceHighlights();
397 memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); 392 updateDestHighlights();
398 save(); 393 updateSourceClasses();
399 394 }
400 // update the ui 395
401 onSelectSource(null); 396 private void save() {
402 onSelectDest(null); 397 if (saveListener != null) {
403 updateSourceHighlights(); 398 saveListener.save(memberMatches);
404 updateDestHighlights(); 399 }
405 updateSourceClasses(); 400 }
406 } 401
407 402 private enum SourceType {
408 protected void unmatch() { 403 Matched {
409 404 @Override
410 // update the field matches 405 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
411 memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); 406 return matches.getSourceClassesWithoutUnmatchedEntries();
412 save(); 407 }
413 408 },
414 // update the ui 409 Unmatched {
415 onSelectSource(null); 410 @Override
416 onSelectDest(null); 411 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
417 updateSourceHighlights(); 412 return matches.getSourceClassesWithUnmatchedEntries();
418 updateDestHighlights(); 413 }
419 updateSourceClasses(); 414 };
420 } 415
421 416 public static SourceType getDefault() {
422 protected void unmatchable() { 417 return values()[0];
423 418 }
424 // update the field matches 419
425 memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator); 420 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
426 save(); 421 JRadioButton button = new JRadioButton(name(), this == getDefault());
427 422 button.setActionCommand(name());
428 // update the ui 423 button.addActionListener(listener);
429 onSelectSource(null); 424 group.add(button);
430 onSelectDest(null); 425 return button;
431 updateSourceHighlights(); 426 }
432 updateDestHighlights(); 427
433 updateSourceClasses(); 428 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
434 } 429 }
435 430
436 private void save() { 431 public interface SaveListener<T extends Entry> {
437 if (saveListener != null) { 432 void save(MemberMatches<T> matches);
438 saveListener.save(memberMatches); 433 }
439 }
440 }
441} 434}
diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
index bd9fe3d..1fd2fa8 100644
--- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
+++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -8,37 +8,37 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
12 13
13import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.ClassEntry;
14 15
15
16public class ScoredClassEntry extends ClassEntry { 16public class ScoredClassEntry extends ClassEntry {
17 17
18 private static final long serialVersionUID = -8798725308554217105L; 18 private static final long serialVersionUID = -8798725308554217105L;
19 19
20 private float score; 20 private float score;
21 21
22 public ScoredClassEntry(ClassEntry other, float score) { 22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other); 23 super(other);
24 this.score = score; 24 this.score = score;
25 } 25 }
26 26
27 public float getScore() { 27 public float getScore() {
28 return score; 28 return score;
29 } 29 }
30 30
31 @Override 31 @Override
32 public int hashCode() { 32 public int hashCode() {
33 return Float.hashCode(score) + super.hashCode(); 33 return Float.hashCode(score) + super.hashCode();
34 } 34 }
35 35
36 @Override 36 @Override
37 public boolean equals(Object other) { 37 public boolean equals(Object other) {
38 return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); 38 return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other);
39 } 39 }
40 40
41 public boolean equals(ScoredClassEntry other) { 41 public boolean equals(ScoredClassEntry other) {
42 return other != null && score == other.score; 42 return other != null && Float.compare(score, other.score) == 0;
43 } 43 }
44} 44}
diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
index 518055f..7375111 100644
--- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
+++ b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -8,31 +8,28 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14 11
15import javax.swing.DefaultListCellRenderer; 12package cuchaz.enigma.gui;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19 13
20import cuchaz.enigma.analysis.Token; 14import cuchaz.enigma.analysis.Token;
21 15
16import javax.swing.*;
17import java.awt.*;
18
22public class TokenListCellRenderer implements ListCellRenderer<Token> { 19public class TokenListCellRenderer implements ListCellRenderer<Token> {
23 20
24 private GuiController controller; 21 private GuiController controller;
25 private DefaultListCellRenderer defaultRenderer; 22 private DefaultListCellRenderer defaultRenderer;
26 23
27 public TokenListCellRenderer(GuiController controller) { 24 public TokenListCellRenderer(GuiController controller) {
28 this.controller = controller; 25 this.controller = controller;
29 this.defaultRenderer = new DefaultListCellRenderer(); 26 this.defaultRenderer = new DefaultListCellRenderer();
30 } 27 }
31 28
32 @Override 29 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) { 30 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); 31 JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(this.controller.getReadableToken(token).toString()); 32 label.setText(this.controller.getReadableToken(token).toString());
36 return label; 33 return label;
37 } 34 }
38} 35}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
index f690b15..7b3234d 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
@@ -8,63 +8,60 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.dialog;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.io.IOException;
18 11
19import javax.swing.*; 12package cuchaz.enigma.gui.dialog;
20 13
21import cuchaz.enigma.Constants; 14import cuchaz.enigma.Constants;
22import cuchaz.enigma.utils.Utils; 15import cuchaz.enigma.utils.Utils;
23 16
17import javax.swing.*;
18import java.awt.*;
19import java.io.IOException;
20
24public class AboutDialog { 21public class AboutDialog {
25 22
26 public static void show(JFrame parent) { 23 public static void show(JFrame parent) {
27 // init frame 24 // init frame
28 final JFrame frame = new JFrame(Constants.NAME + " - About"); 25 final JFrame frame = new JFrame(Constants.NAME + " - About");
29 final Container pane = frame.getContentPane(); 26 final Container pane = frame.getContentPane();
30 pane.setLayout(new FlowLayout()); 27 pane.setLayout(new FlowLayout());
31 28
32 // load the content 29 // load the content
33 try { 30 try {
34 String html = Utils.readResourceToString("/about.html"); 31 String html = Utils.readResourceToString("/about.html");
35 html = String.format(html, Constants.NAME, Constants.VERSION); 32 html = String.format(html, Constants.NAME, Constants.VERSION);
36 JLabel label = new JLabel(html); 33 JLabel label = new JLabel(html);
37 label.setHorizontalAlignment(JLabel.CENTER); 34 label.setHorizontalAlignment(JLabel.CENTER);
38 pane.add(label); 35 pane.add(label);
39 } catch (IOException ex) { 36 } catch (IOException ex) {
40 throw new Error(ex); 37 throw new Error(ex);
41 } 38 }
42 39
43 // show the link 40 // show the link
44 String html = "<html><a href=\"%s\">%s</a></html>"; 41 String html = "<html><a href=\"%s\">%s</a></html>";
45 html = String.format(html, Constants.URL, Constants.URL); 42 html = String.format(html, Constants.URL, Constants.URL);
46 JButton link = new JButton(html); 43 JButton link = new JButton(html);
47 link.addActionListener(event -> Utils.openUrl(Constants.URL)); 44 link.addActionListener(event -> Utils.openUrl(Constants.URL));
48 link.setBorderPainted(false); 45 link.setBorderPainted(false);
49 link.setOpaque(false); 46 link.setOpaque(false);
50 link.setBackground(Color.WHITE); 47 link.setBackground(Color.WHITE);
51 link.setCursor(new Cursor(Cursor.HAND_CURSOR)); 48 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
52 link.setFocusable(false); 49 link.setFocusable(false);
53 JPanel linkPanel = new JPanel(); 50 JPanel linkPanel = new JPanel();
54 linkPanel.add(link); 51 linkPanel.add(link);
55 pane.add(linkPanel); 52 pane.add(linkPanel);
56 53
57 // show ok button 54 // show ok button
58 JButton okButton = new JButton("Ok"); 55 JButton okButton = new JButton("Ok");
59 pane.add(okButton); 56 pane.add(okButton);
60 okButton.addActionListener(arg0 -> frame.dispose()); 57 okButton.addActionListener(arg0 -> frame.dispose());
61 58
62 // show the frame 59 // show the frame
63 pane.doLayout(); 60 pane.doLayout();
64 frame.setSize(400, 220); 61 frame.setSize(400, 220);
65 frame.setResizable(false); 62 frame.setResizable(false);
66 frame.setLocationRelativeTo(parent); 63 frame.setLocationRelativeTo(parent);
67 frame.setVisible(true); 64 frame.setVisible(true);
68 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 65 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
69 } 66 }
70} 67}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
index 5c1d9ff..04dd5d7 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
@@ -8,80 +8,78 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.dialog;
12 11
13import java.awt.BorderLayout; 12package cuchaz.enigma.gui.dialog;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.io.PrintWriter;
17import java.io.StringWriter;
18
19import javax.swing.*;
20 13
21import cuchaz.enigma.Constants; 14import cuchaz.enigma.Constants;
22import cuchaz.enigma.utils.Utils; 15import cuchaz.enigma.utils.Utils;
23 16
17import javax.swing.*;
18import java.awt.*;
19import java.io.PrintWriter;
20import java.io.StringWriter;
21
24public class CrashDialog { 22public class CrashDialog {
25 23
26 private static CrashDialog instance = null; 24 private static CrashDialog instance = null;
27 25
28 private JFrame frame; 26 private JFrame frame;
29 private JTextArea text; 27 private JTextArea text;
30 28
31 private CrashDialog(JFrame parent) { 29 private CrashDialog(JFrame parent) {
32 // init frame 30 // init frame
33 frame = new JFrame(Constants.NAME + " - Crash Report"); 31 frame = new JFrame(Constants.NAME + " - Crash Report");
34 final Container pane = frame.getContentPane(); 32 final Container pane = frame.getContentPane();
35 pane.setLayout(new BorderLayout()); 33 pane.setLayout(new BorderLayout());
36 34
37 JLabel label = new JLabel(Constants.NAME + " has crashed! =("); 35 JLabel label = new JLabel(Constants.NAME + " has crashed! =(");
38 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 36 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
39 pane.add(label, BorderLayout.NORTH); 37 pane.add(label, BorderLayout.NORTH);
40 38
41 // report panel 39 // report panel
42 text = new JTextArea(); 40 text = new JTextArea();
43 text.setTabSize(2); 41 text.setTabSize(2);
44 pane.add(new JScrollPane(text), BorderLayout.CENTER); 42 pane.add(new JScrollPane(text), BorderLayout.CENTER);
45 43
46 // buttons panel 44 // buttons panel
47 JPanel buttonsPanel = new JPanel(); 45 JPanel buttonsPanel = new JPanel();
48 FlowLayout buttonsLayout = new FlowLayout(); 46 FlowLayout buttonsLayout = new FlowLayout();
49 buttonsLayout.setAlignment(FlowLayout.RIGHT); 47 buttonsLayout.setAlignment(FlowLayout.RIGHT);
50 buttonsPanel.setLayout(buttonsLayout); 48 buttonsPanel.setLayout(buttonsLayout);
51 buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); 49 buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
52 JButton ignoreButton = new JButton("Ignore"); 50 JButton ignoreButton = new JButton("Ignore");
53 ignoreButton.addActionListener(event -> { 51 ignoreButton.addActionListener(event -> {
54 // close (hide) the dialog 52 // close (hide) the dialog
55 frame.setVisible(false); 53 frame.setVisible(false);
56 }); 54 });
57 buttonsPanel.add(ignoreButton); 55 buttonsPanel.add(ignoreButton);
58 JButton exitButton = new JButton("Exit"); 56 JButton exitButton = new JButton("Exit");
59 exitButton.addActionListener(event -> { 57 exitButton.addActionListener(event -> {
60 // exit enigma 58 // exit enigma
61 System.exit(1); 59 System.exit(1);
62 }); 60 });
63 buttonsPanel.add(exitButton); 61 buttonsPanel.add(exitButton);
64 pane.add(buttonsPanel, BorderLayout.SOUTH); 62 pane.add(buttonsPanel, BorderLayout.SOUTH);
65 63
66 // show the frame 64 // show the frame
67 frame.setSize(600, 400); 65 frame.setSize(600, 400);
68 frame.setLocationRelativeTo(parent); 66 frame.setLocationRelativeTo(parent);
69 frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 67 frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
70 } 68 }
71 69
72 public static void init(JFrame parent) { 70 public static void init(JFrame parent) {
73 instance = new CrashDialog(parent); 71 instance = new CrashDialog(parent);
74 } 72 }
75 73
76 public static void show(Throwable ex) { 74 public static void show(Throwable ex) {
77 // get the error report 75 // get the error report
78 StringWriter buf = new StringWriter(); 76 StringWriter buf = new StringWriter();
79 ex.printStackTrace(new PrintWriter(buf)); 77 ex.printStackTrace(new PrintWriter(buf));
80 String report = buf.toString(); 78 String report = buf.toString();
81 79
82 // show it! 80 // show it!
83 instance.text.setText(report); 81 instance.text.setText(report);
84 instance.frame.doLayout(); 82 instance.frame.doLayout();
85 instance.frame.setVisible(true); 83 instance.frame.setVisible(true);
86 } 84 }
87} 85}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
index 8df22a7..5f04833 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
@@ -8,92 +8,89 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.dialog;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17 11
18import javax.swing.*; 12package cuchaz.enigma.gui.dialog;
19 13
20import cuchaz.enigma.Constants; 14import cuchaz.enigma.Constants;
21import cuchaz.enigma.Deobfuscator.ProgressListener; 15import cuchaz.enigma.Deobfuscator.ProgressListener;
22import cuchaz.enigma.utils.Utils; 16import cuchaz.enigma.utils.Utils;
23 17
18import javax.swing.*;
19import java.awt.*;
20
24public class ProgressDialog implements ProgressListener, AutoCloseable { 21public class ProgressDialog implements ProgressListener, AutoCloseable {
25 22
26 private JFrame frame; 23 private JFrame frame;
27 private JLabel labelTitle; 24 private JLabel labelTitle;
28 private JLabel labelText; 25 private JLabel labelText;
29 private JProgressBar progress; 26 private JProgressBar progress;
30 27
31 public ProgressDialog(JFrame parent) { 28 public ProgressDialog(JFrame parent) {
32 29
33 // init frame 30 // init frame
34 this.frame = new JFrame(Constants.NAME + " - Operation in progress"); 31 this.frame = new JFrame(Constants.NAME + " - Operation in progress");
35 final Container pane = this.frame.getContentPane(); 32 final Container pane = this.frame.getContentPane();
36 FlowLayout layout = new FlowLayout(); 33 FlowLayout layout = new FlowLayout();
37 layout.setAlignment(FlowLayout.LEFT); 34 layout.setAlignment(FlowLayout.LEFT);
38 pane.setLayout(layout); 35 pane.setLayout(layout);
39 36
40 this.labelTitle = new JLabel(); 37 this.labelTitle = new JLabel();
41 pane.add(this.labelTitle); 38 pane.add(this.labelTitle);
42 39
43 // set up the progress bar 40 // set up the progress bar
44 JPanel panel = new JPanel(); 41 JPanel panel = new JPanel();
45 pane.add(panel); 42 pane.add(panel);
46 panel.setLayout(new BorderLayout()); 43 panel.setLayout(new BorderLayout());
47 this.labelText = Utils.unboldLabel(new JLabel()); 44 this.labelText = Utils.unboldLabel(new JLabel());
48 this.progress = new JProgressBar(); 45 this.progress = new JProgressBar();
49 this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); 46 this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
50 panel.add(this.labelText, BorderLayout.NORTH); 47 panel.add(this.labelText, BorderLayout.NORTH);
51 panel.add(this.progress, BorderLayout.CENTER); 48 panel.add(this.progress, BorderLayout.CENTER);
52 panel.setPreferredSize(new Dimension(360, 50)); 49 panel.setPreferredSize(new Dimension(360, 50));
53 50
54 // show the frame 51 // show the frame
55 pane.doLayout(); 52 pane.doLayout();
56 this.frame.setSize(400, 120); 53 this.frame.setSize(400, 120);
57 this.frame.setResizable(false); 54 this.frame.setResizable(false);
58 this.frame.setLocationRelativeTo(parent); 55 this.frame.setLocationRelativeTo(parent);
59 this.frame.setVisible(true); 56 this.frame.setVisible(true);
60 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 57 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
61 } 58 }
62 59
63 public void close() { 60 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
64 this.frame.dispose(); 61 new Thread(() ->
65 } 62 {
66 63 try (ProgressDialog progress = new ProgressDialog(parent)) {
67 @Override 64 runnable.run(progress);
68 public void init(int totalWork, String title) { 65 } catch (Exception ex) {
69 this.labelTitle.setText(title); 66 throw new Error(ex);
70 this.progress.setMinimum(0); 67 }
71 this.progress.setMaximum(totalWork); 68 }).start();
72 this.progress.setValue(0); 69 }
73 } 70
74 71 public void close() {
75 @Override 72 this.frame.dispose();
76 public void onProgress(int numDone, String message) { 73 }
77 this.labelText.setText(message); 74
78 this.progress.setValue(numDone); 75 @Override
79 76 public void init(int totalWork, String title) {
80 // update the frame 77 this.labelTitle.setText(title);
81 this.frame.validate(); 78 this.progress.setMinimum(0);
82 this.frame.repaint(); 79 this.progress.setMaximum(totalWork);
83 } 80 this.progress.setValue(0);
84 81 }
85 public interface ProgressRunnable { 82
86 void run(ProgressListener listener) throws Exception; 83 @Override
87 } 84 public void onProgress(int numDone, String message) {
88 85 this.labelText.setText(message);
89 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { 86 this.progress.setValue(numDone);
90 new Thread(() -> 87
91 { 88 // update the frame
92 try (ProgressDialog progress = new ProgressDialog(parent)) { 89 this.frame.validate();
93 runnable.run(progress); 90 this.frame.repaint();
94 } catch (Exception ex) { 91 }
95 throw new Error(ex); 92
96 } 93 public interface ProgressRunnable {
97 }).start(); 94 void run(ProgressListener listener) throws Exception;
98 } 95 }
99} 96}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index 0ccd537..cd11aca 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -1,219 +1,207 @@
1package cuchaz.enigma.gui.elements; 1package cuchaz.enigma.gui.elements;
2 2
3import cuchaz.enigma.gui.Gui;
4import cuchaz.enigma.gui.dialog.AboutDialog;
5import cuchaz.enigma.throwables.MappingParseException;
6
7import javax.swing.*;
3import java.awt.event.InputEvent; 8import java.awt.event.InputEvent;
4import java.awt.event.KeyEvent; 9import java.awt.event.KeyEvent;
5import java.io.IOException; 10import java.io.IOException;
6import java.util.jar.JarFile; 11import java.util.jar.JarFile;
7 12
8import javax.swing.*;
9
10import cuchaz.enigma.gui.Gui;
11import cuchaz.enigma.gui.dialog.AboutDialog;
12import cuchaz.enigma.throwables.MappingParseException;
13
14public class MenuBar extends JMenuBar { 13public class MenuBar extends JMenuBar {
15 14
16 private final Gui gui; 15 public final JMenuItem closeJarMenu;
17 16 public final JMenuItem openEnigmaMappingsMenu;
18 public final JMenuItem closeJarMenu; 17 public final JMenuItem saveMappingsMenu;
19 18 public final JMenuItem saveMappingEnigmaFileMenu;
20 public final JMenuItem openEnigmaMappingsMenu; 19 public final JMenuItem saveMappingEnigmaDirectoryMenu;
21 20 public final JMenuItem saveMappingsSrgMenu;
22 public final JMenuItem saveMappingsMenu; 21 public final JMenuItem closeMappingsMenu;
23 public final JMenuItem saveMappingEnigmaFileMenu; 22 public final JMenuItem rebuildMethodNamesMenu;
24 public final JMenuItem saveMappingEnigmaDirectoryMenu; 23 public final JMenuItem exportSourceMenu;
25 public final JMenuItem saveMappingsSrgMenu; 24 public final JMenuItem exportJarMenu;
26 public final JMenuItem closeMappingsMenu; 25 private final Gui gui;
27
28 public final JMenuItem rebuildMethodNamesMenu;
29
30 public final JMenuItem exportSourceMenu;
31 public final JMenuItem exportJarMenu;
32 26
33 public MenuBar(Gui gui) { 27 public MenuBar(Gui gui) {
34 this.gui = gui; 28 this.gui = gui;
35 29
36 { 30 {
37 JMenu menu = new JMenu("File"); 31 JMenu menu = new JMenu("File");
38 this.add(menu); 32 this.add(menu);
39 { 33 {
40 JMenuItem item = new JMenuItem("Open Jar..."); 34 JMenuItem item = new JMenuItem("Open Jar...");
41 menu.add(item); 35 menu.add(item);
42 item.addActionListener(event -> { 36 item.addActionListener(event -> {
43 if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 37 if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
44 // load the jar in a separate thread 38 // load the jar in a separate thread
45 new Thread(() -> 39 new Thread(() ->
46 { 40 {
47 try { 41 try {
48 gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile())); 42 gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile()));
49 } catch (IOException ex) { 43 } catch (IOException ex) {
50 throw new Error(ex); 44 throw new Error(ex);
51 } 45 }
52 }).start(); 46 }).start();
53 } 47 }
54 }); 48 });
55 } 49 }
56 { 50 {
57 JMenuItem item = new JMenuItem("Close Jar"); 51 JMenuItem item = new JMenuItem("Close Jar");
58 menu.add(item); 52 menu.add(item);
59 item.addActionListener(event -> this.gui.getController().closeJar()); 53 item.addActionListener(event -> this.gui.getController().closeJar());
60 this.closeJarMenu = item; 54 this.closeJarMenu = item;
61 } 55 }
62 menu.addSeparator(); 56 menu.addSeparator();
63 JMenu openMenu = new JMenu("Open Mappings..."); 57 JMenu openMenu = new JMenu("Open Mappings...");
64 menu.add(openMenu); 58 menu.add(openMenu);
65 { 59 {
66 JMenuItem item = new JMenuItem("Enigma"); 60 JMenuItem item = new JMenuItem("Enigma");
67 openMenu.add(item); 61 openMenu.add(item);
68 item.addActionListener(event -> { 62 item.addActionListener(event -> {
69 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 63 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
70 try { 64 try {
71 this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); 65 this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile());
72 } catch (IOException ex) { 66 } catch (IOException ex) {
73 throw new Error(ex); 67 throw new Error(ex);
74 } catch (MappingParseException ex) { 68 } catch (MappingParseException ex) {
75 JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage()); 69 JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage());
76 } 70 }
77 } 71 }
78 }); 72 });
79 this.openEnigmaMappingsMenu = item; 73 this.openEnigmaMappingsMenu = item;
80 } 74 }
81 { 75 {
82 JMenuItem item = new JMenuItem("Save Mappings"); 76 JMenuItem item = new JMenuItem("Save Mappings");
83 menu.add(item); 77 menu.add(item);
84 item.addActionListener(event -> { 78 item.addActionListener(event -> {
85 try { 79 try {
86 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); 80 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile());
87 } catch (IOException ex) { 81 } catch (IOException ex) {
88 throw new Error(ex); 82 throw new Error(ex);
89 } 83 }
90 }); 84 });
91 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); 85 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
92 this.saveMappingsMenu = item; 86 this.saveMappingsMenu = item;
93 } 87 }
94 JMenu saveMenu = new JMenu("Save Mappings As..."); 88 JMenu saveMenu = new JMenu("Save Mappings As...");
95 menu.add(saveMenu); 89 menu.add(saveMenu);
96 { 90 {
97 JMenuItem item = new JMenuItem("Enigma (single file)"); 91 JMenuItem item = new JMenuItem("Enigma (single file)");
98 saveMenu.add(item); 92 saveMenu.add(item);
99 item.addActionListener(event -> { 93 item.addActionListener(event -> {
100 // TODO: Use a specific file chooser for it 94 // TODO: Use a specific file chooser for it
101 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 95 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
102 try { 96 try {
103 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); 97 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false);
104 this.saveMappingsMenu.setEnabled(true); 98 this.saveMappingsMenu.setEnabled(true);
105 } catch (IOException ex) { 99 } catch (IOException ex) {
106 throw new Error(ex); 100 throw new Error(ex);
107 } 101 }
108 } 102 }
109 }); 103 });
110 this.saveMappingEnigmaFileMenu = item; 104 this.saveMappingEnigmaFileMenu = item;
111 } 105 }
112 { 106 {
113 JMenuItem item = new JMenuItem("Enigma (directory)"); 107 JMenuItem item = new JMenuItem("Enigma (directory)");
114 saveMenu.add(item); 108 saveMenu.add(item);
115 item.addActionListener(event -> { 109 item.addActionListener(event -> {
116 // TODO: Use a specific file chooser for it 110 // TODO: Use a specific file chooser for it
117 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 111 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
118 try { 112 try {
119 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); 113 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true);
120 this.saveMappingsMenu.setEnabled(true); 114 this.saveMappingsMenu.setEnabled(true);
121 } catch (IOException ex) { 115 } catch (IOException ex) {
122 throw new Error(ex); 116 throw new Error(ex);
123 } 117 }
124 } 118 }
125 }); 119 });
126 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); 120 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
127 this.saveMappingEnigmaDirectoryMenu = item; 121 this.saveMappingEnigmaDirectoryMenu = item;
128 } 122 }
129 { 123 {
130 JMenuItem item = new JMenuItem("SRG (single file)"); 124 JMenuItem item = new JMenuItem("SRG (single file)");
131 saveMenu.add(item); 125 saveMenu.add(item);
132 item.addActionListener(event -> { 126 item.addActionListener(event -> {
133 // TODO: Use a specific file chooser for it 127 // TODO: Use a specific file chooser for it
134 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 128 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
135 try { 129 try {
136 this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); 130 this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile());
137 this.saveMappingsMenu.setEnabled(true); 131 this.saveMappingsMenu.setEnabled(true);
138 } catch (IOException ex) { 132 } catch (IOException ex) {
139 throw new Error(ex); 133 throw new Error(ex);
140 } 134 }
141 } 135 }
142 }); 136 });
143 this.saveMappingsSrgMenu = item; 137 this.saveMappingsSrgMenu = item;
144 } 138 }
145 { 139 {
146 JMenuItem item = new JMenuItem("Close Mappings"); 140 JMenuItem item = new JMenuItem("Close Mappings");
147 menu.add(item); 141 menu.add(item);
148 item.addActionListener(event -> { 142 item.addActionListener(event -> {
149 if (this.gui.getController().isDirty()) 143 if (this.gui.getController().isDirty()) {
150 { 144 this.gui.showDiscardDiag((response -> {
151 this.gui.showDiscardDiag((response -> { 145 if (response == JOptionPane.YES_OPTION) {
152 if (response == JOptionPane.YES_OPTION) 146 try {
153 { 147 gui.saveMapping();
154 try 148 this.gui.getController().closeMappings();
155 { 149 } catch (IOException e) {
156 gui.saveMapping(); 150 throw new Error(e);
157 this.gui.getController().closeMappings(); 151 }
158 } catch (IOException e) 152 } else if (response == JOptionPane.NO_OPTION)
159 { 153 this.gui.getController().closeMappings();
160 throw new Error(e); 154 return null;
161 } 155 }), "Save and close", "Discard changes", "Cancel");
162 } 156 } else
163 else if (response == JOptionPane.NO_OPTION) 157 this.gui.getController().closeMappings();
164 this.gui.getController().closeMappings();
165 return null;
166 }), "Save and close", "Discard changes", "Cancel");
167 }
168 else
169 this.gui.getController().closeMappings();
170 158
171 }); 159 });
172 this.closeMappingsMenu = item; 160 this.closeMappingsMenu = item;
173 } 161 }
174 menu.addSeparator(); 162 menu.addSeparator();
175 { 163 {
176 JMenuItem item = new JMenuItem("Rebuild Method Names"); 164 JMenuItem item = new JMenuItem("Rebuild Method Names");
177 menu.add(item); 165 menu.add(item);
178 item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); 166 item.addActionListener(event -> this.gui.getController().rebuildMethodNames());
179 this.rebuildMethodNamesMenu = item; 167 this.rebuildMethodNamesMenu = item;
180 } 168 }
181 menu.addSeparator(); 169 menu.addSeparator();
182 { 170 {
183 JMenuItem item = new JMenuItem("Export Source..."); 171 JMenuItem item = new JMenuItem("Export Source...");
184 menu.add(item); 172 menu.add(item);
185 item.addActionListener(event -> { 173 item.addActionListener(event -> {
186 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 174 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
187 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); 175 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile());
188 } 176 }
189 }); 177 });
190 this.exportSourceMenu = item; 178 this.exportSourceMenu = item;
191 } 179 }
192 { 180 {
193 JMenuItem item = new JMenuItem("Export Jar..."); 181 JMenuItem item = new JMenuItem("Export Jar...");
194 menu.add(item); 182 menu.add(item);
195 item.addActionListener(event -> { 183 item.addActionListener(event -> {
196 if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 184 if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
197 this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile()); 185 this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile());
198 } 186 }
199 }); 187 });
200 this.exportJarMenu = item; 188 this.exportJarMenu = item;
201 } 189 }
202 menu.addSeparator(); 190 menu.addSeparator();
203 { 191 {
204 JMenuItem item = new JMenuItem("Exit"); 192 JMenuItem item = new JMenuItem("Exit");
205 menu.add(item); 193 menu.add(item);
206 item.addActionListener(event -> this.gui.close()); 194 item.addActionListener(event -> this.gui.close());
207 } 195 }
208 } 196 }
209 { 197 {
210 JMenu menu = new JMenu("Help"); 198 JMenu menu = new JMenu("Help");
211 this.add(menu); 199 this.add(menu);
212 { 200 {
213 JMenuItem item = new JMenuItem("About"); 201 JMenuItem item = new JMenuItem("About");
214 menu.add(item); 202 menu.add(item);
215 item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); 203 item.addActionListener(event -> AboutDialog.show(this.gui.getFrame()));
216 } 204 }
217 } 205 }
218 } 206 }
219} 207}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
index e387ed3..2f6d96c 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
@@ -1,79 +1,76 @@
1package cuchaz.enigma.gui.elements; 1package cuchaz.enigma.gui.elements;
2 2
3import java.awt.event.KeyEvent;
4
5import javax.swing.JMenuItem;
6import javax.swing.JPopupMenu;
7import javax.swing.KeyStroke;
8
9import cuchaz.enigma.gui.Gui; 3import cuchaz.enigma.gui.Gui;
10 4
5import javax.swing.*;
6import java.awt.event.KeyEvent;
7
11public class PopupMenuBar extends JPopupMenu { 8public class PopupMenuBar extends JPopupMenu {
12 9
13 public final JMenuItem renameMenu; 10 public final JMenuItem renameMenu;
14 public final JMenuItem showInheritanceMenu; 11 public final JMenuItem showInheritanceMenu;
15 public final JMenuItem showImplementationsMenu; 12 public final JMenuItem showImplementationsMenu;
16 public final JMenuItem showCallsMenu; 13 public final JMenuItem showCallsMenu;
17 public final JMenuItem openEntryMenu; 14 public final JMenuItem openEntryMenu;
18 public final JMenuItem openPreviousMenu; 15 public final JMenuItem openPreviousMenu;
19 public final JMenuItem toggleMappingMenu; 16 public final JMenuItem toggleMappingMenu;
20 17
21 public PopupMenuBar(Gui gui) { 18 public PopupMenuBar(Gui gui) {
22 { 19 {
23 JMenuItem menu = new JMenuItem("Rename"); 20 JMenuItem menu = new JMenuItem("Rename");
24 menu.addActionListener(event -> gui.startRename()); 21 menu.addActionListener(event -> gui.startRename());
25 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); 22 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
26 menu.setEnabled(false); 23 menu.setEnabled(false);
27 this.add(menu); 24 this.add(menu);
28 this.renameMenu = menu; 25 this.renameMenu = menu;
29 } 26 }
30 { 27 {
31 JMenuItem menu = new JMenuItem("Show Inheritance"); 28 JMenuItem menu = new JMenuItem("Show Inheritance");
32 menu.addActionListener(event -> gui.showInheritance()); 29 menu.addActionListener(event -> gui.showInheritance());
33 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); 30 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
34 menu.setEnabled(false); 31 menu.setEnabled(false);
35 this.add(menu); 32 this.add(menu);
36 this.showInheritanceMenu = menu; 33 this.showInheritanceMenu = menu;
37 } 34 }
38 { 35 {
39 JMenuItem menu = new JMenuItem("Show Implementations"); 36 JMenuItem menu = new JMenuItem("Show Implementations");
40 menu.addActionListener(event -> gui.showImplementations()); 37 menu.addActionListener(event -> gui.showImplementations());
41 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); 38 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
42 menu.setEnabled(false); 39 menu.setEnabled(false);
43 this.add(menu); 40 this.add(menu);
44 this.showImplementationsMenu = menu; 41 this.showImplementationsMenu = menu;
45 } 42 }
46 { 43 {
47 JMenuItem menu = new JMenuItem("Show Calls"); 44 JMenuItem menu = new JMenuItem("Show Calls");
48 menu.addActionListener(event -> gui.showCalls()); 45 menu.addActionListener(event -> gui.showCalls());
49 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); 46 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
50 menu.setEnabled(false); 47 menu.setEnabled(false);
51 this.add(menu); 48 this.add(menu);
52 this.showCallsMenu = menu; 49 this.showCallsMenu = menu;
53 } 50 }
54 { 51 {
55 JMenuItem menu = new JMenuItem("Go to Declaration"); 52 JMenuItem menu = new JMenuItem("Go to Declaration");
56 menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); 53 menu.addActionListener(event -> gui.navigateTo(gui.reference.entry));
57 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); 54 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
58 menu.setEnabled(false); 55 menu.setEnabled(false);
59 this.add(menu); 56 this.add(menu);
60 this.openEntryMenu = menu; 57 this.openEntryMenu = menu;
61 } 58 }
62 { 59 {
63 JMenuItem menu = new JMenuItem("Go to previous"); 60 JMenuItem menu = new JMenuItem("Go to previous");
64 menu.addActionListener(event -> gui.getController().openPreviousReference()); 61 menu.addActionListener(event -> gui.getController().openPreviousReference());
65 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); 62 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
66 menu.setEnabled(false); 63 menu.setEnabled(false);
67 this.add(menu); 64 this.add(menu);
68 this.openPreviousMenu = menu; 65 this.openPreviousMenu = menu;
69 } 66 }
70 { 67 {
71 JMenuItem menu = new JMenuItem("Mark as deobfuscated"); 68 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
72 menu.addActionListener(event -> gui.toggleMapping()); 69 menu.addActionListener(event -> gui.toggleMapping());
73 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)); 70 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0));
74 menu.setEnabled(false); 71 menu.setEnabled(false);
75 this.add(menu); 72 this.add(menu);
76 this.toggleMappingMenu = menu; 73 this.toggleMappingMenu = menu;
77 } 74 }
78 } 75 }
79} 76}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
index 2339334..f5f6628 100644
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
+++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
@@ -2,10 +2,9 @@ package cuchaz.enigma.gui.filechooser;
2 2
3import javax.swing.*; 3import javax.swing.*;
4 4
5public class FileChooserAny extends JFileChooser 5public class FileChooserAny extends JFileChooser {
6{ 6 public FileChooserAny() {
7 public FileChooserAny() { 7 this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
8 this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 8 this.setAcceptAllFileFilterUsed(false);
9 this.setAcceptAllFileFilterUsed(false); 9 }
10 }
11} \ No newline at end of file 10} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
index 62a0f20..cea11a6 100644
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
+++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
@@ -1,8 +1,8 @@
1package cuchaz.enigma.gui.filechooser; 1package cuchaz.enigma.gui.filechooser;
2 2
3import javax.swing.JFileChooser; 3import javax.swing.*;
4 4
5public class FileChooserFile extends JFileChooser { 5public class FileChooserFile extends JFileChooser {
6 public FileChooserFile() { 6 public FileChooserFile() {
7 } 7 }
8} 8}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
index 93ca5d6..c16e0af 100644
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
+++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
@@ -1,11 +1,11 @@
1package cuchaz.enigma.gui.filechooser; 1package cuchaz.enigma.gui.filechooser;
2 2
3import javax.swing.JFileChooser; 3import javax.swing.*;
4 4
5public class FileChooserFolder extends JFileChooser { 5public class FileChooserFolder extends JFileChooser {
6 6
7 public FileChooserFolder() { 7 public FileChooserFolder() {
8 this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 8 this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
9 this.setAcceptAllFileFilterUsed(false); 9 this.setAcceptAllFileFilterUsed(false);
10 } 10 }
11} 11}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
index 0a73088..0f64927 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
@@ -8,57 +8,54 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.highlight;
12 11
13import java.awt.Color; 12package cuchaz.enigma.gui.highlight;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17 13
18import javax.swing.text.BadLocationException; 14import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter; 15import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent; 16import javax.swing.text.JTextComponent;
17import java.awt.*;
21 18
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter { 19public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23 20
24 private Color fillColor; 21 private Color fillColor;
25 private Color borderColor; 22 private Color borderColor;
26 23
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) { 24 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 this.fillColor = fillColor; 25 this.fillColor = fillColor;
29 this.borderColor = borderColor; 26 this.borderColor = borderColor;
30 } 27 }
31 28
32 @Override 29 public static Rectangle getBounds(JTextComponent text, int start, int end) {
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { 30 try {
34 Rectangle bounds = getBounds(text, start, end); 31 // determine the bounds of the text
35 32 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
36 // fill the area 33
37 if (this.fillColor != null) { 34 // adjust the box so it looks nice
38 g.setColor(this.fillColor); 35 bounds.x -= 2;
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); 36 bounds.width += 2;
40 } 37 bounds.y += 1;
41 38 bounds.height -= 2;
42 // draw a box around the area 39
43 g.setColor(this.borderColor); 40 return bounds;
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); 41 } catch (BadLocationException ex) {
45 } 42 // don't care... just return something
46 43 return new Rectangle(0, 0, 0, 0);
47 public static Rectangle getBounds(JTextComponent text, int start, int end) { 44 }
48 try { 45 }
49 // determine the bounds of the text 46
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); 47 @Override
51 48 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
52 // adjust the box so it looks nice 49 Rectangle bounds = getBounds(text, start, end);
53 bounds.x -= 2; 50
54 bounds.width += 2; 51 // fill the area
55 bounds.y += 1; 52 if (this.fillColor != null) {
56 bounds.height -= 2; 53 g.setColor(this.fillColor);
57 54 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
58 return bounds; 55 }
59 } catch (BadLocationException ex) { 56
60 // don't care... just return something 57 // draw a box around the area
61 return new Rectangle(0, 0, 0, 0); 58 g.setColor(this.borderColor);
62 } 59 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
63 } 60 }
64} 61}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java
index 5d57203..a2d2884 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java
@@ -8,13 +8,14 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui.highlight; 12package cuchaz.enigma.gui.highlight;
12 13
13import java.awt.Color; 14import java.awt.*;
14 15
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { 16public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16 17
17 public DeobfuscatedHighlightPainter() { 18 public DeobfuscatedHighlightPainter() {
18 super(new Color(220, 255, 220), new Color(80, 160, 80)); 19 super(new Color(220, 255, 220), new Color(80, 160, 80));
19 } 20 }
20} 21}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java
index ee64d86..0947d4b 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java
@@ -8,13 +8,14 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui.highlight; 12package cuchaz.enigma.gui.highlight;
12 13
13import java.awt.Color; 14import java.awt.*;
14 15
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter { 16public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16 17
17 public ObfuscatedHighlightPainter() { 18 public ObfuscatedHighlightPainter() {
18 super(new Color(255, 220, 220), new Color(160, 80, 80)); 19 super(new Color(255, 220, 220), new Color(160, 80, 80));
19 } 20 }
20} 21}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java
index 43d8352..74e7273 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java
@@ -8,13 +8,14 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui.highlight; 12package cuchaz.enigma.gui.highlight;
12 13
13import java.awt.Color; 14import java.awt.*;
14 15
15public class OtherHighlightPainter extends BoxHighlightPainter { 16public class OtherHighlightPainter extends BoxHighlightPainter {
16 17
17 public OtherHighlightPainter() { 18 public OtherHighlightPainter() {
18 super(null, new Color(180, 180, 180)); 19 super(null, new Color(180, 180, 180));
19 } 20 }
20} 21}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
index f772284..5cbeabc 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
@@ -8,22 +8,22 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.highlight;
12 11
13import java.awt.*; 12package cuchaz.enigma.gui.highlight;
14 13
15import javax.swing.text.Highlighter; 14import javax.swing.text.Highlighter;
16import javax.swing.text.JTextComponent; 15import javax.swing.text.JTextComponent;
16import java.awt.*;
17 17
18public class SelectionHighlightPainter implements Highlighter.HighlightPainter { 18public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
19 19
20 @Override 20 @Override
21 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { 21 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
22 // draw a thick border 22 // draw a thick border
23 Graphics2D g2d = (Graphics2D) g; 23 Graphics2D g2d = (Graphics2D) g;
24 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); 24 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
25 g2d.setColor(Color.black); 25 g2d.setColor(Color.black);
26 g2d.setStroke(new BasicStroke(2.0f)); 26 g2d.setStroke(new BasicStroke(2.0f));
27 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); 27 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
28 } 28 }
29} 29}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
index 9f391f2..dc933cd 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
@@ -8,58 +8,59 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui.node;
12 11
13import javax.swing.tree.DefaultMutableTreeNode; 12package cuchaz.enigma.gui.node;
14 13
15import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.ClassEntry;
16 15
16import javax.swing.tree.DefaultMutableTreeNode;
17
17public class ClassSelectorClassNode extends DefaultMutableTreeNode { 18public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18 19
19 private ClassEntry classEntry; 20 private ClassEntry classEntry;
20 21
21 public ClassSelectorClassNode(ClassEntry classEntry) { 22 public ClassSelectorClassNode(ClassEntry classEntry) {
22 this.classEntry = classEntry; 23 this.classEntry = classEntry;
23 this.setUserObject(classEntry); 24 this.setUserObject(classEntry);
24 } 25 }
25 26
26 public ClassEntry getClassEntry() { 27 public ClassEntry getClassEntry() {
27 return this.classEntry; 28 return this.classEntry;
28 } 29 }
29 30
30 @Override 31 @Override
31 public String toString() { 32 public String toString() {
32 return this.classEntry.getSimpleName(); 33 return this.classEntry.getSimpleName();
33 } 34 }
34 35
35 @Override 36 @Override
36 public boolean equals(Object other) { 37 public boolean equals(Object other) {
37 return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); 38 return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other);
38 } 39 }
39 40
40 @Override public int hashCode() 41 @Override
41 { 42 public int hashCode() {
42 return 17 + (classEntry != null ? classEntry.hashCode() : 0); 43 return 17 + (classEntry != null ? classEntry.hashCode() : 0);
43 } 44 }
44 45
45 @Override public void setUserObject(Object userObject) 46 @Override
46 { 47 public Object getUserObject() {
47 String packageName = ""; 48 return classEntry;
48 if (classEntry.getPackageName() != null) 49 }
49 packageName = classEntry.getPackageName() + "/";
50 if (userObject instanceof String)
51 this.classEntry = new ClassEntry(packageName + userObject);
52 else if (userObject instanceof ClassEntry)
53 this.classEntry = (ClassEntry) userObject;
54 super.setUserObject(classEntry);
55 }
56 50
57 @Override public Object getUserObject() 51 @Override
58 { 52 public void setUserObject(Object userObject) {
59 return classEntry; 53 String packageName = "";
60 } 54 if (classEntry.getPackageName() != null)
55 packageName = classEntry.getPackageName() + "/";
56 if (userObject instanceof String)
57 this.classEntry = new ClassEntry(packageName + userObject);
58 else if (userObject instanceof ClassEntry)
59 this.classEntry = (ClassEntry) userObject;
60 super.setUserObject(classEntry);
61 }
61 62
62 public boolean equals(ClassSelectorClassNode other) { 63 public boolean equals(ClassSelectorClassNode other) {
63 return this.classEntry.equals(other.classEntry); 64 return this.classEntry.equals(other.classEntry);
64 } 65 }
65} 66}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
index b3eb90e..f80abba 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.gui.node; 12package cuchaz.enigma.gui.node;
12 13
13import javassist.bytecode.Descriptor; 14import javassist.bytecode.Descriptor;
@@ -16,44 +17,44 @@ import javax.swing.tree.DefaultMutableTreeNode;
16 17
17public class ClassSelectorPackageNode extends DefaultMutableTreeNode { 18public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
18 19
19 private String packageName; 20 private String packageName;
20 21
21 public ClassSelectorPackageNode(String packageName) { 22 public ClassSelectorPackageNode(String packageName) {
22 this.packageName = packageName != null ? packageName : "(none)"; 23 this.packageName = packageName != null ? packageName : "(none)";
23 } 24 }
24 25
25 public String getPackageName() { 26 public String getPackageName() {
26 return packageName; 27 return packageName;
27 } 28 }
28 29
29 @Override public void setUserObject(Object userObject) 30 @Override
30 { 31 public Object getUserObject() {
31 if (userObject instanceof String) 32 return packageName;
32 this.packageName = (String) userObject; 33 }
33 super.setUserObject(userObject); 34
34 } 35 @Override
35 36 public void setUserObject(Object userObject) {
36 @Override public Object getUserObject() 37 if (userObject instanceof String)
37 { 38 this.packageName = (String) userObject;
38 return packageName; 39 super.setUserObject(userObject);
39 } 40 }
40 41
41 @Override 42 @Override
42 public String toString() { 43 public String toString() {
43 return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; 44 return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)";
44 } 45 }
45 46
46 @Override 47 @Override
47 public boolean equals(Object other) { 48 public boolean equals(Object other) {
48 return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); 49 return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other);
49 } 50 }
50 51
51 @Override public int hashCode() 52 @Override
52 { 53 public int hashCode() {
53 return packageName.hashCode(); 54 return packageName.hashCode();
54 } 55 }
55 56
56 public boolean equals(ClassSelectorPackageNode other) { 57 public boolean equals(ClassSelectorPackageNode other) {
57 return other != null && this.packageName.equals(other.packageName); 58 return other != null && this.packageName.equals(other.packageName);
58 } 59 }
59} 60}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
index 4f55175..68cc8e1 100644
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
+++ b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
@@ -1,28 +1,25 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import java.awt.BorderLayout;
4
5import javax.swing.JLabel;
6import javax.swing.JPanel;
7import javax.swing.JScrollPane;
8
9import cuchaz.enigma.gui.ClassSelector; 3import cuchaz.enigma.gui.ClassSelector;
10import cuchaz.enigma.gui.Gui; 4import cuchaz.enigma.gui.Gui;
11 5
6import javax.swing.*;
7import java.awt.*;
8
12public class PanelDeobf extends JPanel { 9public class PanelDeobf extends JPanel {
13 10
14 public final ClassSelector deobfClasses; 11 public final ClassSelector deobfClasses;
15 private final Gui gui; 12 private final Gui gui;
16 13
17 public PanelDeobf(Gui gui) { 14 public PanelDeobf(Gui gui) {
18 this.gui = gui; 15 this.gui = gui;
19 16
20 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); 17 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true);
21 this.deobfClasses.setSelectionListener(gui::navigateTo); 18 this.deobfClasses.setSelectionListener(gui::navigateTo);
22 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); 19 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename);
23 20
24 this.setLayout(new BorderLayout()); 21 this.setLayout(new BorderLayout());
25 this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); 22 this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
26 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); 23 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER);
27 } 24 }
28} 25}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
index 914952b..fd30e38 100644
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
+++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
@@ -1,73 +1,71 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import cuchaz.enigma.gui.BrowserCaret;
4import cuchaz.enigma.gui.Gui;
5
6import javax.swing.*;
3import java.awt.*; 7import java.awt.*;
4import java.awt.event.KeyAdapter; 8import java.awt.event.KeyAdapter;
5import java.awt.event.KeyEvent; 9import java.awt.event.KeyEvent;
6import java.awt.event.MouseAdapter; 10import java.awt.event.MouseAdapter;
7import java.awt.event.MouseEvent; 11import java.awt.event.MouseEvent;
8 12
9import javax.swing.JEditorPane;
10
11import cuchaz.enigma.gui.BrowserCaret;
12import cuchaz.enigma.gui.Gui;
13
14public class PanelEditor extends JEditorPane { 13public class PanelEditor extends JEditorPane {
15 private final Gui gui; 14 private final Gui gui;
16 15
17 public PanelEditor(Gui gui) { 16 public PanelEditor(Gui gui) {
18 this.gui = gui; 17 this.gui = gui;
19 18
20 this.setEditable(false); 19 this.setEditable(false);
21 this.setSelectionColor(new Color(31, 46, 90)); 20 this.setSelectionColor(new Color(31, 46, 90));
22 this.setCaret(new BrowserCaret()); 21 this.setCaret(new BrowserCaret());
23 this.addCaretListener(event -> gui.onCaretMove(event.getDot())); 22 this.addCaretListener(event -> gui.onCaretMove(event.getDot()));
24 final PanelEditor self = this; 23 final PanelEditor self = this;
25 this.addMouseListener(new MouseAdapter() 24 this.addMouseListener(new MouseAdapter() {
26 { 25 @Override
27 @Override public void mouseReleased(MouseEvent e) 26 public void mouseReleased(MouseEvent e) {
28 { 27 if (e.getButton() == MouseEvent.BUTTON3)
29 if (e.getButton() == MouseEvent.BUTTON3) 28 self.setCaretPosition(self.viewToModel(e.getPoint()));
30 self.setCaretPosition(self.viewToModel(e.getPoint())); 29 }
31 } 30 });
32 }); 31 this.addKeyListener(new KeyAdapter() {
33 this.addKeyListener(new KeyAdapter() { 32 @Override
34 @Override 33 public void keyPressed(KeyEvent event) {
35 public void keyPressed(KeyEvent event) { 34 switch (event.getKeyCode()) {
36 switch (event.getKeyCode()) { 35 case KeyEvent.VK_R:
37 case KeyEvent.VK_R: 36 gui.popupMenu.renameMenu.doClick();
38 gui.popupMenu.renameMenu.doClick(); 37 break;
39 break;
40 38
41 case KeyEvent.VK_I: 39 case KeyEvent.VK_I:
42 gui.popupMenu.showInheritanceMenu.doClick(); 40 gui.popupMenu.showInheritanceMenu.doClick();
43 break; 41 break;
44 42
45 case KeyEvent.VK_M: 43 case KeyEvent.VK_M:
46 gui.popupMenu.showImplementationsMenu.doClick(); 44 gui.popupMenu.showImplementationsMenu.doClick();
47 break; 45 break;
48 46
49 case KeyEvent.VK_N: 47 case KeyEvent.VK_N:
50 gui.popupMenu.openEntryMenu.doClick(); 48 gui.popupMenu.openEntryMenu.doClick();
51 break; 49 break;
52 50
53 case KeyEvent.VK_P: 51 case KeyEvent.VK_P:
54 gui.popupMenu.openPreviousMenu.doClick(); 52 gui.popupMenu.openPreviousMenu.doClick();
55 break; 53 break;
56 54
57 case KeyEvent.VK_C: 55 case KeyEvent.VK_C:
58 gui.popupMenu.showCallsMenu.doClick(); 56 gui.popupMenu.showCallsMenu.doClick();
59 break; 57 break;
60 58
61 case KeyEvent.VK_T: 59 case KeyEvent.VK_T:
62 gui.popupMenu.toggleMappingMenu.doClick(); 60 gui.popupMenu.toggleMappingMenu.doClick();
63 break; 61 break;
64 case KeyEvent.VK_F5: 62 case KeyEvent.VK_F5:
65 gui.getController().refreshCurrentClass(); 63 gui.getController().refreshCurrentClass();
66 break; 64 break;
67 default: 65 default:
68 break; 66 break;
69 } 67 }
70 } 68 }
71 }); 69 });
72 } 70 }
73} 71}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
index faa023b..1bf6887 100644
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
+++ b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
@@ -1,34 +1,30 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import java.awt.Dimension;
4import java.awt.GridLayout;
5
6import javax.swing.BorderFactory;
7import javax.swing.JLabel;
8import javax.swing.JPanel;
9
10import cuchaz.enigma.gui.Gui; 3import cuchaz.enigma.gui.Gui;
11import cuchaz.enigma.utils.Utils; 4import cuchaz.enigma.utils.Utils;
12 5
6import javax.swing.*;
7import java.awt.*;
8
13public class PanelIdentifier extends JPanel { 9public class PanelIdentifier extends JPanel {
14 10
15 private final Gui gui; 11 private final Gui gui;
16 12
17 public PanelIdentifier(Gui gui) { 13 public PanelIdentifier(Gui gui) {
18 this.gui = gui; 14 this.gui = gui;
19 15
20 this.setLayout(new GridLayout(4, 1, 0, 0)); 16 this.setLayout(new GridLayout(4, 1, 0, 0));
21 this.setPreferredSize(new Dimension(0, 100)); 17 this.setPreferredSize(new Dimension(0, 100));
22 this.setBorder(BorderFactory.createTitledBorder("Identifier Info")); 18 this.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
23 } 19 }
24 20
25 public void clearReference() { 21 public void clearReference() {
26 this.removeAll(); 22 this.removeAll();
27 JLabel label = new JLabel("No identifier selected"); 23 JLabel label = new JLabel("No identifier selected");
28 Utils.unboldLabel(label); 24 Utils.unboldLabel(label);
29 label.setHorizontalAlignment(JLabel.CENTER); 25 label.setHorizontalAlignment(JLabel.CENTER);
30 this.add(label); 26 this.add(label);
31 27
32 gui.redraw(); 28 gui.redraw();
33 } 29 }
34} 30}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
index 27bb70b..4bbd32b 100644
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
+++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
@@ -1,39 +1,36 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import java.awt.BorderLayout;
4import java.util.Comparator;
5
6import javax.swing.JLabel;
7import javax.swing.JPanel;
8import javax.swing.JScrollPane;
9
10import cuchaz.enigma.gui.ClassSelector; 3import cuchaz.enigma.gui.ClassSelector;
11import cuchaz.enigma.gui.Gui; 4import cuchaz.enigma.gui.Gui;
12import cuchaz.enigma.mapping.ClassEntry; 5import cuchaz.enigma.mapping.ClassEntry;
13 6
7import javax.swing.*;
8import java.awt.*;
9import java.util.Comparator;
10
14public class PanelObf extends JPanel { 11public class PanelObf extends JPanel {
15 12
16 private final Gui gui; 13 public final ClassSelector obfClasses;
17 public final ClassSelector obfClasses; 14 private final Gui gui;
18 15
19 public PanelObf(Gui gui) { 16 public PanelObf(Gui gui) {
20 this.gui = gui; 17 this.gui = gui;
21 18
22 Comparator<ClassEntry> obfClassComparator = (a, b) -> { 19 Comparator<ClassEntry> obfClassComparator = (a, b) -> {
23 String aname = a.getName(); 20 String aname = a.getName();
24 String bname = b.getName(); 21 String bname = b.getName();
25 if (aname.length() != bname.length()) { 22 if (aname.length() != bname.length()) {
26 return aname.length() - bname.length(); 23 return aname.length() - bname.length();
27 } 24 }
28 return aname.compareTo(bname); 25 return aname.compareTo(bname);
29 }; 26 };
30 27
31 this.obfClasses = new ClassSelector(gui, obfClassComparator, false); 28 this.obfClasses = new ClassSelector(gui, obfClassComparator, false);
32 this.obfClasses.setSelectionListener(gui::navigateTo); 29 this.obfClasses.setSelectionListener(gui::navigateTo);
33 this.obfClasses.setRenameSelectionListener(gui::onPanelRename); 30 this.obfClasses.setRenameSelectionListener(gui::onPanelRename);
34 31
35 this.setLayout(new BorderLayout()); 32 this.setLayout(new BorderLayout());
36 this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); 33 this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
37 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); 34 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER);
38 } 35 }
39} 36}