summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar Runemoro2020-06-03 13:39:42 -0400
committerGravatar GitHub2020-06-03 18:39:42 +0100
commit0f47403d0220757fed189b76e2071e25b1025cb8 (patch)
tree879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /src/main/java/cuchaz/enigma/gui
parentFix search dialog hanging for a short time sometimes (#250) (diff)
downloadenigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip
Split GUI code to separate module (#242)
* Split into modules * Post merge compile fixes Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java28
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java532
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java73
-rw-r--r--src/main/java/cuchaz/enigma/gui/ConnectionState.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java159
-rw-r--r--src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java90
-rw-r--r--src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java44
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1058
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java729
-rw-r--r--src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java24
-rw-r--r--src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java42
-rw-r--r--src/main/java/cuchaz/enigma/gui/QuickFindAction.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/RefreshMode.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/SourceRemapper.java64
-rw-r--r--src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java35
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java69
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java50
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java82
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java105
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java65
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java159
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java109
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java261
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java82
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java40
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java386
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java125
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java10
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java11
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java69
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java31
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java58
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java26
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java171
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java32
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelObf.java37
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java197
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsMember.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java77
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/History.java49
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java110
45 files changed, 0 insertions, 5451 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
deleted file mode 100644
index af105db..0000000
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ /dev/null
@@ -1,28 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import javax.swing.text.DefaultCaret;
15
16public class BrowserCaret extends DefaultCaret {
17
18 @Override
19 public boolean isSelectionVisible() {
20 return true;
21 }
22
23 @Override
24 public boolean isVisible() {
25 return true;
26 }
27
28}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
deleted file mode 100644
index a23e24c..0000000
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ /dev/null
@@ -1,532 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent;
16import java.util.*;
17
18import javax.annotation.Nullable;
19import javax.swing.JOptionPane;
20import javax.swing.JTree;
21import javax.swing.event.CellEditorListener;
22import javax.swing.event.ChangeEvent;
23import javax.swing.tree.*;
24
25import com.google.common.collect.ArrayListMultimap;
26import com.google.common.collect.Lists;
27import com.google.common.collect.Maps;
28import com.google.common.collect.Multimap;
29import cuchaz.enigma.gui.node.ClassSelectorClassNode;
30import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
31import cuchaz.enigma.throwables.IllegalNameException;
32import cuchaz.enigma.translation.Translator;
33import cuchaz.enigma.translation.representation.entry.ClassEntry;
34
35public class ClassSelector extends JTree {
36
37 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
38
39 private final GuiController controller;
40
41 private DefaultMutableTreeNode rootNodes;
42 private ClassSelectionListener selectionListener;
43 private RenameSelectionListener renameSelectionListener;
44 private Comparator<ClassEntry> comparator;
45
46 private final Map<ClassEntry, ClassEntry> displayedObfToDeobf = new HashMap<>();
47
48 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) {
49 this.comparator = comparator;
50 this.controller = gui.getController();
51
52 // configure the tree control
53 setEditable(true);
54 setRootVisible(false);
55 setShowsRootHandles(false);
56 setModel(null);
57
58 // hook events
59 addMouseListener(new MouseAdapter() {
60 @Override
61 public void mouseClicked(MouseEvent event) {
62 if (selectionListener != null && event.getClickCount() == 2) {
63 // get the selected node
64 TreePath path = getSelectionPath();
65 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
66 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
67 selectionListener.onSelectClass(node.getObfEntry());
68 }
69 }
70 }
71 });
72
73 final JTree tree = this;
74
75 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
76 (DefaultTreeCellRenderer) tree.getCellRenderer()) {
77 @Override
78 public boolean isCellEditable(EventObject event) {
79 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
80 }
81 };
82 this.setCellEditor(editor);
83 editor.addCellEditorListener(new CellEditorListener() {
84 @Override
85 public void editingStopped(ChangeEvent e) {
86 String data = editor.getCellEditorValue().toString();
87 TreePath path = getSelectionPath();
88
89 Object realPath = path.getLastPathComponent();
90 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
91 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
92 TreeNode parentNode = node.getParent();
93 if (parentNode == null)
94 return;
95 boolean allowEdit = true;
96 for (int i = 0; i < parentNode.getChildCount(); i++) {
97 TreeNode childNode = parentNode.getChildAt(i);
98 if (childNode != null && childNode.toString().equals(data) && childNode != node) {
99 allowEdit = false;
100 break;
101 }
102 }
103 if (allowEdit && renameSelectionListener != null) {
104 Object prevData = node.getUserObject();
105 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
106 try {
107 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
108 node.setUserObject(objectData); // Make sure that it's modified
109 } catch (IllegalNameException ex) {
110 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
111 JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK");
112 editor.cancelCellEditing();
113 }
114 } else
115 editor.cancelCellEditing();
116 }
117
118 }
119
120 @Override
121 public void editingCanceled(ChangeEvent e) {
122 // NOP
123 }
124 });
125 // init defaults
126 this.selectionListener = null;
127 this.renameSelectionListener = null;
128 }
129
130 public boolean isDuplicate(Object[] nodes, String data) {
131 int count = 0;
132
133 for (Object node : nodes) {
134 if (node.toString().equals(data)) {
135 count++;
136 if (count == 2)
137 return true;
138 }
139 }
140 return false;
141 }
142
143 public void setSelectionListener(ClassSelectionListener val) {
144 this.selectionListener = val;
145 }
146
147 public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) {
148 this.renameSelectionListener = renameSelectionListener;
149 }
150
151 public void setClasses(Collection<ClassEntry> classEntries) {
152 displayedObfToDeobf.clear();
153
154 List<StateEntry> state = getExpansionState(this);
155 if (classEntries == null) {
156 setModel(null);
157 return;
158 }
159
160 Translator translator = controller.project.getMapper().getDeobfuscator();
161
162 // build the package names
163 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
164 for (ClassEntry obfClass : classEntries) {
165 ClassEntry deobfClass = translator.translate(obfClass);
166 packages.put(deobfClass.getPackageName(), null);
167 }
168
169 // sort the packages
170 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
171 sortedPackageNames.sort((a, b) ->
172 {
173 // I can never keep this rule straight when writing these damn things...
174 // a < b => -1, a == b => 0, a > b => +1
175
176 if (b == null || a == null) {
177 return 0;
178 }
179
180 String[] aparts = a.split("/");
181 String[] bparts = b.split("/");
182 for (int i = 0; true; i++) {
183 if (i >= aparts.length) {
184 return -1;
185 } else if (i >= bparts.length) {
186 return 1;
187 }
188
189 int result = aparts[i].compareTo(bparts[i]);
190 if (result != 0) {
191 return result;
192 }
193 }
194 });
195
196 // create the rootNodes node and the package nodes
197 rootNodes = new DefaultMutableTreeNode();
198 for (String packageName : sortedPackageNames) {
199 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
200 packages.put(packageName, node);
201 rootNodes.add(node);
202 }
203
204 // put the classes into packages
205 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
206 for (ClassEntry obfClass : classEntries) {
207 ClassEntry deobfClass = translator.translate(obfClass);
208 packagedClassEntries.put(deobfClass.getPackageName(), obfClass);
209 }
210
211 // build the class nodes
212 for (String packageName : packagedClassEntries.keySet()) {
213 // sort the class entries
214 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
215 classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2)));
216
217 // create the nodes in order
218 for (ClassEntry obfClass : classEntriesInPackage) {
219 ClassEntry deobfClass = translator.translate(obfClass);
220 ClassSelectorPackageNode node = packages.get(packageName);
221 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass);
222 displayedObfToDeobf.put(obfClass, deobfClass);
223 node.add(classNode);
224 }
225 }
226
227 // finally, update the tree control
228 setModel(new DefaultTreeModel(rootNodes));
229
230 restoreExpansionState(this, state);
231 }
232
233 public ClassEntry getSelectedClass() {
234 if (!isSelectionEmpty()) {
235 Object selectedNode = getSelectionPath().getLastPathComponent();
236 if (selectedNode instanceof ClassSelectorClassNode) {
237 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
238 return classNode.getClassEntry();
239 }
240 }
241 return null;
242 }
243
244 public String getSelectedPackage() {
245 if (!isSelectionEmpty()) {
246 Object selectedNode = getSelectionPath().getLastPathComponent();
247 if (selectedNode instanceof ClassSelectorPackageNode) {
248 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode;
249 return packageNode.getPackageName();
250 } else if (selectedNode instanceof ClassSelectorClassNode) {
251 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
252 return classNode.getClassEntry().getPackageName();
253 }
254 }
255 return null;
256 }
257
258 public boolean isDescendant(TreePath path1, TreePath path2) {
259 int count1 = path1.getPathCount();
260 int count2 = path2.getPathCount();
261 if (count1 <= count2) {
262 return false;
263 }
264 while (count1 != count2) {
265 path1 = path1.getParentPath();
266 count1--;
267 }
268 return path1.equals(path2);
269 }
270
271 public enum State {
272 EXPANDED,
273 SELECTED
274 }
275
276 public static class StateEntry {
277 public final State state;
278 public final TreePath path;
279
280 public StateEntry(State state, TreePath path) {
281 this.state = state;
282 this.path = path;
283 }
284 }
285
286 public List<StateEntry> getExpansionState(JTree tree) {
287 List<StateEntry> state = new ArrayList<>();
288 int rowCount = tree.getRowCount();
289 for (int i = 0; i < rowCount; i++) {
290 TreePath path = tree.getPathForRow(i);
291 if (tree.isPathSelected(path)) {
292 state.add(new StateEntry(State.SELECTED, path));
293 }
294 if (tree.isExpanded(path)) {
295 state.add(new StateEntry(State.EXPANDED, path));
296 }
297 }
298 return state;
299 }
300
301 public void restoreExpansionState(JTree tree, List<StateEntry> expansionState) {
302 tree.clearSelection();
303
304 for (StateEntry entry : expansionState) {
305 switch (entry.state) {
306 case SELECTED:
307 tree.addSelectionPath(entry.path);
308 break;
309 case EXPANDED:
310 tree.expandPath(entry.path);
311 break;
312 }
313 }
314 }
315
316 public List<ClassSelectorPackageNode> packageNodes() {
317 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
318 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
319 Enumeration<?> children = root.children();
320 while (children.hasMoreElements()) {
321 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
322 nodes.add(packageNode);
323 }
324 return nodes;
325 }
326
327 public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
328 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
329 Enumeration<?> children = packageNode.children();
330 while (children.hasMoreElements()) {
331 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
332 nodes.add(classNode);
333 }
334 return nodes;
335 }
336
337 public void expandPackage(String packageName) {
338 if (packageName == null) {
339 return;
340 }
341 for (ClassSelectorPackageNode packageNode : packageNodes()) {
342 if (packageNode.getPackageName().equals(packageName)) {
343 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
344 return;
345 }
346 }
347 }
348
349 public void expandAll() {
350 for (ClassSelectorPackageNode packageNode : packageNodes()) {
351 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
352 }
353 }
354
355 public ClassEntry getFirstClass() {
356 ClassSelectorPackageNode packageNode = packageNodes().get(0);
357 if (packageNode != null) {
358 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
359 if (classNode != null) {
360 return classNode.getClassEntry();
361 }
362 }
363 return null;
364 }
365
366 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
367 String packageName = entry.getPackageName();
368 if (packageName == null) {
369 packageName = "(none)";
370 }
371 for (ClassSelectorPackageNode packageNode : packageNodes()) {
372 if (packageNode.getPackageName().equals(packageName)) {
373 return packageNode;
374 }
375 }
376 return null;
377 }
378
379 @Nullable
380 public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) {
381 return displayedObfToDeobf.get(obfEntry);
382 }
383
384 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
385 ClassSelectorPackageNode packageNode = getPackageNode(entry);
386
387 if (selector != null && packageNode == null && selector.getPackageNode(entry) != null)
388 return selector.getPackageNode(entry);
389 return packageNode;
390 }
391
392 public ClassEntry getNextClass(ClassEntry entry) {
393 boolean foundIt = false;
394 for (ClassSelectorPackageNode packageNode : packageNodes()) {
395 if (!foundIt) {
396 // skip to the package with our target in it
397 if (packageNode.getPackageName().equals(entry.getPackageName())) {
398 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
399 if (!foundIt) {
400 if (classNode.getClassEntry().equals(entry)) {
401 foundIt = true;
402 }
403 } else {
404 // return the next class
405 return classNode.getClassEntry();
406 }
407 }
408 }
409 } else {
410 // return the next class
411 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
412 if (classNode != null) {
413 return classNode.getClassEntry();
414 }
415 }
416 }
417 return null;
418 }
419
420 public void setSelectionClass(ClassEntry classEntry) {
421 expandPackage(classEntry.getPackageName());
422 for (ClassSelectorPackageNode packageNode : packageNodes()) {
423 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
424 if (classNode.getClassEntry().equals(classEntry)) {
425 TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode});
426 setSelectionPath(path);
427 scrollPathToVisible(path);
428 }
429 }
430 }
431 }
432
433 public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) {
434 DefaultTreeModel model = (DefaultTreeModel) getModel();
435
436 if (packageNode == null)
437 return;
438
439 for (int i = 0; i < packageNode.getChildCount(); i++) {
440 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
441 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
442 model.removeNodeFromParent(childNode);
443 if (childNode instanceof ClassSelectorClassNode) {
444 displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry());
445 }
446 break;
447 }
448 }
449 }
450
451 public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) {
452 if (packageNode != null && packageNode.getChildCount() == 0)
453 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
454 }
455
456 public void moveClassIn(ClassEntry classEntry) {
457 removeEntry(classEntry);
458 insertNode(classEntry);
459 }
460
461 public void moveClassOut(ClassEntry classEntry) {
462 removeEntry(classEntry);
463 }
464
465 private void removeEntry(ClassEntry classEntry) {
466 ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry);
467 if (previousDeobf != null) {
468 ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf);
469 removeNode(packageNode, previousDeobf);
470 removeNodeIfEmpty(packageNode);
471 }
472 }
473
474 public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) {
475 DefaultTreeModel model = (DefaultTreeModel) getModel();
476 ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
477 if (newPackageNode == null) {
478 newPackageNode = new ClassSelectorPackageNode(entry.getPackageName());
479 model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode));
480 }
481 return newPackageNode;
482 }
483
484 public void insertNode(ClassEntry obfEntry) {
485 ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
486 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
487
488 DefaultTreeModel model = (DefaultTreeModel) getModel();
489 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry);
490 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
491
492 displayedObfToDeobf.put(obfEntry, deobfEntry);
493 }
494
495 public void reload() {
496 DefaultTreeModel model = (DefaultTreeModel) getModel();
497 model.reload(rootNodes);
498 }
499
500 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) {
501 List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode);
502 classNodes.add(classNode);
503 classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry()));
504 for (int i = 0; i < classNodes.size(); i++)
505 if (classNodes.get(i) == classNode)
506 return i;
507
508 return 0;
509 }
510
511 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) {
512 List<ClassSelectorPackageNode> packageNodes = packageNodes();
513 if (!packageNodes.contains(newPackageNode)) {
514 packageNodes.add(newPackageNode);
515 packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString));
516 }
517
518 for (int i = 0; i < packageNodes.size(); i++)
519 if (packageNodes.get(i) == newPackageNode)
520 return i;
521
522 return 0;
523 }
524
525 public interface ClassSelectionListener {
526 void onSelectClass(ClassEntry classEntry);
527 }
528
529 public interface RenameSelectionListener {
530 void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node);
531 }
532}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
deleted file mode 100644
index e119640..0000000
--- a/src/main/java/cuchaz/enigma/gui/CodeReader.java
+++ /dev/null
@@ -1,73 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.Token;
15
16import javax.swing.*;
17import javax.swing.text.BadLocationException;
18import javax.swing.text.Document;
19import javax.swing.text.Highlighter.HighlightPainter;
20import java.awt.*;
21import java.awt.event.ActionEvent;
22import java.awt.event.ActionListener;
23
24public class CodeReader extends JEditorPane {
25 private static final long serialVersionUID = 3673180950485748810L;
26
27 // HACKHACK: someday we can update the main GUI to use this code reader
28 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
29
30 // set the caret position to the token
31 Document document = editor.getDocument();
32 int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength());
33
34 editor.setCaretPosition(clampedPosition);
35 editor.grabFocus();
36
37 try {
38 // make sure the token is visible in the scroll window
39 Rectangle start = editor.modelToView(token.start);
40 Rectangle end = editor.modelToView(token.end);
41 final Rectangle show = start.union(end);
42 show.grow(start.width * 10, start.height * 6);
43 SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show));
44 } catch (BadLocationException ex) {
45 throw new Error(ex);
46 }
47
48 // highlight the token momentarily
49 final Timer timer = new Timer(200, new ActionListener() {
50 private int counter = 0;
51 private Object highlight = null;
52
53 @Override
54 public void actionPerformed(ActionEvent event) {
55 if (counter % 2 == 0) {
56 try {
57 highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
58 } catch (BadLocationException ex) {
59 // don't care
60 }
61 } else if (highlight != null) {
62 editor.getHighlighter().removeHighlight(highlight);
63 }
64
65 if (counter++ > 6) {
66 Timer timer = (Timer) event.getSource();
67 timer.stop();
68 }
69 }
70 });
71 timer.start();
72 }
73}
diff --git a/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/src/main/java/cuchaz/enigma/gui/ConnectionState.java
deleted file mode 100644
index db6590d..0000000
--- a/src/main/java/cuchaz/enigma/gui/ConnectionState.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui;
2
3public enum ConnectionState {
4 NOT_CONNECTED,
5 HOSTING,
6 CONNECTED,
7}
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
deleted file mode 100644
index 08df3e7..0000000
--- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
+++ /dev/null
@@ -1,159 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.EnigmaServices;
5import cuchaz.enigma.analysis.EntryReference;
6import cuchaz.enigma.analysis.Token;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.gui.highlight.TokenHighlightType;
9import cuchaz.enigma.source.SourceIndex;
10import cuchaz.enigma.translation.LocalNameGenerator;
11import cuchaz.enigma.translation.Translator;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.mapping.ResolutionStrategy;
14import cuchaz.enigma.translation.representation.TypeDescriptor;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import cuchaz.enigma.translation.representation.entry.Entry;
17import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry;
18
19import javax.annotation.Nullable;
20import java.util.*;
21
22public class DecompiledClassSource {
23 private final ClassEntry classEntry;
24
25 private final SourceIndex obfuscatedIndex;
26 private SourceIndex remappedIndex;
27
28 private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class);
29
30 public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) {
31 this.classEntry = classEntry;
32 this.obfuscatedIndex = index;
33 this.remappedIndex = index;
34 }
35
36 public static DecompiledClassSource text(ClassEntry classEntry, String text) {
37 return new DecompiledClassSource(classEntry, new SourceIndex(text));
38 }
39
40 public void remapSource(EnigmaProject project, Translator translator) {
41 highlightedTokens.clear();
42
43 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
44
45 SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator));
46 remappedIndex = obfuscatedIndex.remapTo(remapResult);
47 }
48
49 private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) {
50 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token);
51
52 Entry<?> entry = reference.getNameableEntry();
53 Entry<?> translatedEntry = translator.translate(entry);
54
55 if (project.isRenamable(reference)) {
56 if (isDeobfuscated(entry, translatedEntry)) {
57 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
58 return translatedEntry.getSourceRemapName();
59 } else {
60 Optional<String> proposedName = proposeName(project, entry);
61 if (proposedName.isPresent()) {
62 highlightToken(movedToken, TokenHighlightType.PROPOSED);
63 return proposedName.get();
64 }
65
66 highlightToken(movedToken, TokenHighlightType.OBFUSCATED);
67 }
68 }
69
70 String defaultName = generateDefaultName(translatedEntry);
71 if (defaultName != null) {
72 return defaultName;
73 }
74
75 return null;
76 }
77
78 private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) {
79 EnigmaServices services = project.getEnigma().getServices();
80
81 return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> {
82 EntryRemapper mapper = project.getMapper();
83 Collection<Entry<?>> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
84
85 return resolved.stream()
86 .map(e -> nameProposalService.proposeName(e, mapper))
87 .filter(Optional::isPresent)
88 .map(Optional::get);
89 }).findFirst();
90 }
91
92 @Nullable
93 private String generateDefaultName(Entry<?> entry) {
94 if (entry instanceof LocalVariableDefEntry) {
95 LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry;
96
97 int index = localVariable.getIndex();
98 if (localVariable.isArgument()) {
99 List<TypeDescriptor> arguments = localVariable.getParent().getDesc().getArgumentDescs();
100 return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments);
101 } else {
102 return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc());
103 }
104 }
105
106 return null;
107 }
108
109 private boolean isDeobfuscated(Entry<?> entry, Entry<?> translatedEntry) {
110 return !entry.getName().equals(translatedEntry.getName());
111 }
112
113 public ClassEntry getEntry() {
114 return classEntry;
115 }
116
117 public SourceIndex getIndex() {
118 return remappedIndex;
119 }
120
121 public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() {
122 return highlightedTokens;
123 }
124
125 private void highlightToken(Token token, TokenHighlightType highlightType) {
126 highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token);
127 }
128
129 public int getObfuscatedOffset(int deobfOffset) {
130 return getOffset(remappedIndex, obfuscatedIndex, deobfOffset);
131 }
132
133 public int getDeobfuscatedOffset(int obfOffset) {
134 return getOffset(obfuscatedIndex, remappedIndex, obfOffset);
135 }
136
137 private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) {
138 int relativeOffset = 0;
139
140 Iterator<Token> fromTokenItr = fromIndex.referenceTokens().iterator();
141 Iterator<Token> toTokenItr = toIndex.referenceTokens().iterator();
142 while (fromTokenItr.hasNext() && toTokenItr.hasNext()) {
143 Token fromToken = fromTokenItr.next();
144 Token toToken = toTokenItr.next();
145 if (fromToken.end > fromOffset) {
146 break;
147 }
148
149 relativeOffset = toToken.end - fromToken.end;
150 }
151
152 return fromOffset + relativeOffset;
153 }
154
155 @Override
156 public String toString() {
157 return remappedIndex.getSource();
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java b/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java
deleted file mode 100644
index c912be3..0000000
--- a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java
+++ /dev/null
@@ -1,90 +0,0 @@
1package cuchaz.enigma.gui;
2
3import de.sciss.syntaxpane.actions.DocumentSearchData;
4import de.sciss.syntaxpane.actions.gui.QuickFindDialog;
5
6import javax.swing.*;
7import javax.swing.text.JTextComponent;
8import java.awt.*;
9import java.awt.event.KeyAdapter;
10import java.awt.event.KeyEvent;
11import java.util.stream.IntStream;
12import java.util.stream.Stream;
13
14public class EnigmaQuickFindDialog extends QuickFindDialog {
15 public EnigmaQuickFindDialog(JTextComponent target) {
16 super(target, DocumentSearchData.getFromEditor(target));
17
18 JToolBar toolBar = getToolBar();
19 JTextField textField = getTextField(toolBar);
20
21 textField.addKeyListener(new KeyAdapter() {
22 @Override
23 public void keyPressed(KeyEvent e) {
24 super.keyPressed(e);
25 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
26 JToolBar toolBar = getToolBar();
27 boolean next = !e.isShiftDown();
28 JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar);
29 button.doClick();
30 }
31 }
32 });
33 }
34
35 @Override
36 public void showFor(JTextComponent target) {
37 String selectedText = target.getSelectedText();
38
39 try {
40 super.showFor(target);
41 } catch (Exception e) {
42 e.printStackTrace();
43 return;
44 }
45
46 Container view = target.getParent();
47 Point loc = new Point(0, view.getHeight() - getSize().height);
48 setLocationRelativeTo(view);
49 SwingUtilities.convertPointToScreen(loc, view);
50 setLocation(loc);
51
52 JToolBar toolBar = getToolBar();
53 JTextField textField = getTextField(toolBar);
54
55 if (selectedText != null) {
56 textField.setText(selectedText);
57 }
58
59 textField.selectAll();
60 }
61
62 private JToolBar getToolBar() {
63 return components(getContentPane(), JToolBar.class).findFirst().orElse(null);
64 }
65
66 private JTextField getTextField(JToolBar toolBar) {
67 return components(toolBar, JTextField.class).findFirst().orElse(null);
68 }
69
70 private JButton getNextButton(JToolBar toolBar) {
71 Stream<JButton> buttons = components(toolBar, JButton.class);
72 return buttons.skip(1).findFirst().orElse(null);
73 }
74
75 private JButton getPrevButton(JToolBar toolBar) {
76 Stream<JButton> buttons = components(toolBar, JButton.class);
77 return buttons.findFirst().orElse(null);
78 }
79
80 private static Stream<Component> components(Container container) {
81 return IntStream.range(0, container.getComponentCount())
82 .mapToObj(container::getComponent);
83 }
84
85 private static <T extends Component> Stream<T> components(Container container, Class<T> type) {
86 return components(container)
87 .filter(type::isInstance)
88 .map(type::cast);
89 }
90}
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
deleted file mode 100644
index 42eaa60..0000000
--- a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
+++ /dev/null
@@ -1,44 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.config.Config;
4import de.sciss.syntaxpane.components.LineNumbersRuler;
5import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit;
6import de.sciss.syntaxpane.util.Configuration;
7
8public class EnigmaSyntaxKit extends JavaSyntaxKit {
9 private static Configuration configuration = null;
10
11 @Override
12 public Configuration getConfig() {
13 if(configuration == null){
14 initConfig(super.getConfig(JavaSyntaxKit.class));
15 }
16 return configuration;
17 }
18
19 public void initConfig(Configuration baseConfig){
20 configuration = baseConfig;
21 //See de.sciss.syntaxpane.TokenType
22 configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0");
23 configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3");
24 configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0");
25 configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1");
26 configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1");
27 configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0");
28 configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1");
29 configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2");
30 configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1");
31 configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0");
32 configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0");
33 configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + "");
34 configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + "");
35 configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + "");
36 configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config
37
38 configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F");
39 }
40
41 public static void invalidate(){
42 configuration = null;
43 }
44}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
deleted file mode 100644
index ed32469..0000000
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ /dev/null
@@ -1,1058 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import java.awt.*;
15import java.awt.event.*;
16import java.nio.file.Path;
17import java.util.List;
18import java.util.*;
19import java.util.function.Function;
20
21import javax.swing.*;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter;
24import javax.swing.tree.*;
25
26import com.google.common.base.Strings;
27import com.google.common.collect.Lists;
28import cuchaz.enigma.Constants;
29import cuchaz.enigma.EnigmaProfile;
30import cuchaz.enigma.ExceptionIgnorer;
31import cuchaz.enigma.analysis.*;
32import cuchaz.enigma.config.Config;
33import cuchaz.enigma.config.Themes;
34import cuchaz.enigma.gui.dialog.CrashDialog;
35import cuchaz.enigma.gui.dialog.JavadocDialog;
36import cuchaz.enigma.gui.dialog.SearchDialog;
37import cuchaz.enigma.gui.elements.CollapsibleTabbedPane;
38import cuchaz.enigma.gui.elements.MenuBar;
39import cuchaz.enigma.gui.elements.PopupMenuBar;
40import cuchaz.enigma.gui.filechooser.FileChooserAny;
41import cuchaz.enigma.gui.filechooser.FileChooserFolder;
42import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
43import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
44import cuchaz.enigma.gui.highlight.TokenHighlightType;
45import cuchaz.enigma.gui.panels.PanelDeobf;
46import cuchaz.enigma.gui.panels.PanelEditor;
47import cuchaz.enigma.gui.panels.PanelIdentifier;
48import cuchaz.enigma.gui.panels.PanelObf;
49import cuchaz.enigma.gui.util.History;
50import cuchaz.enigma.network.packet.*;
51import cuchaz.enigma.throwables.IllegalNameException;
52import cuchaz.enigma.translation.mapping.*;
53import cuchaz.enigma.translation.representation.entry.*;
54import cuchaz.enigma.utils.I18n;
55import cuchaz.enigma.utils.Message;
56import cuchaz.enigma.gui.util.ScaleUtil;
57import cuchaz.enigma.utils.Utils;
58import de.sciss.syntaxpane.DefaultSyntaxKit;
59
60public class Gui {
61
62 public final PopupMenuBar popupMenu;
63 private final PanelObf obfPanel;
64 private final PanelDeobf deobfPanel;
65
66 private final MenuBar menuBar;
67 // state
68 public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory;
69 public EntryReference<Entry<?>, Entry<?>> renamingReference;
70 public EntryReference<Entry<?>, Entry<?>> cursorReference;
71 private boolean shouldNavigateOnClick;
72 private ConnectionState connectionState;
73 private boolean isJarOpen;
74
75 public FileDialog jarFileChooser;
76 public FileDialog tinyMappingsFileChooser;
77 public SearchDialog searchDialog;
78 public JFileChooser enigmaMappingsFileChooser;
79 public JFileChooser exportSourceFileChooser;
80 public FileDialog exportJarFileChooser;
81 private GuiController controller;
82 private JFrame frame;
83 public Config.LookAndFeel editorFeel;
84 public PanelEditor editor;
85 public JScrollPane sourceScroller;
86 private JPanel classesPanel;
87 private JSplitPane splitClasses;
88 private PanelIdentifier infoPanel;
89 public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters;
90 private SelectionHighlightPainter selectionHighlightPainter;
91 private JTree inheritanceTree;
92 private JTree implementationsTree;
93 private JTree callsTree;
94 private JList<Token> tokens;
95 private JTabbedPane tabs;
96
97 private JSplitPane splitRight;
98 private JSplitPane logSplit;
99 private CollapsibleTabbedPane logTabs;
100 private JList<String> users;
101 private DefaultListModel<String> userModel;
102 private JScrollPane messageScrollPane;
103 private JList<Message> messages;
104 private DefaultListModel<Message> messageModel;
105 private JTextField chatBox;
106
107 private JPanel statusBar;
108 private JLabel connectionStatusLabel;
109 private JLabel statusLabel;
110
111 public JTextField renameTextField;
112 public JTextArea javadocTextArea;
113
114 public void setEditorTheme(Config.LookAndFeel feel) {
115 if (editor != null && (editorFeel == null || editorFeel != feel)) {
116 editor.updateUI();
117 editor.setBackground(new Color(Config.getInstance().editorBackground));
118 if (editorFeel != null) {
119 getController().refreshCurrentClass();
120 }
121
122 editorFeel = feel;
123 }
124 }
125
126 public Gui(EnigmaProfile profile) {
127 Config.getInstance().lookAndFeel.setGlobalLAF();
128
129 // init frame
130 this.frame = new JFrame(Constants.NAME);
131 final Container pane = this.frame.getContentPane();
132 pane.setLayout(new BorderLayout());
133
134 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
135 // install a global exception handler to the event thread
136 CrashDialog.init(this.frame);
137 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
138 t.printStackTrace(System.err);
139 if (!ExceptionIgnorer.shouldIgnore(t)) {
140 CrashDialog.show(t);
141 }
142 });
143 }
144
145 this.controller = new GuiController(this, profile);
146
147 Themes.updateTheme(this);
148
149 // init file choosers
150 this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD);
151
152 this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD);
153 this.enigmaMappingsFileChooser = new FileChooserAny();
154 this.exportSourceFileChooser = new FileChooserFolder();
155 this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE);
156
157 this.obfPanel = new PanelObf(this);
158 this.deobfPanel = new PanelDeobf(this);
159
160 // set up classes panel (don't add the splitter yet)
161 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
162 splitClasses.setResizeWeight(0.3);
163 this.classesPanel = new JPanel();
164 this.classesPanel.setLayout(new BorderLayout());
165 this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0));
166
167 // init info panel
168 infoPanel = new PanelIdentifier(this);
169 infoPanel.clearReference();
170
171 // init editor
172 selectionHighlightPainter = new SelectionHighlightPainter();
173 this.editor = new PanelEditor(this);
174 this.sourceScroller = new JScrollPane(this.editor);
175 this.editor.setContentType("text/enigma-sources");
176 this.editor.setBackground(new Color(Config.getInstance().editorBackground));
177 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
178 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
179
180 // init editor popup menu
181 this.popupMenu = new PopupMenuBar(this);
182 this.editor.setComponentPopupMenu(this.popupMenu);
183
184 // init inheritance panel
185 inheritanceTree = new JTree();
186 inheritanceTree.setModel(null);
187 inheritanceTree.addMouseListener(new MouseAdapter() {
188 @Override
189 public void mouseClicked(MouseEvent event) {
190 if (event.getClickCount() >= 2) {
191 // get the selected node
192 TreePath path = inheritanceTree.getSelectionPath();
193 if (path == null) {
194 return;
195 }
196
197 Object node = path.getLastPathComponent();
198 if (node instanceof ClassInheritanceTreeNode) {
199 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
200 controller.navigateTo(new ClassEntry(classNode.getObfClassName()));
201 } else if (node instanceof MethodInheritanceTreeNode) {
202 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
203 if (methodNode.isImplemented()) {
204 controller.navigateTo(methodNode.getMethodEntry());
205 }
206 }
207 }
208 }
209 });
210 TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer();
211 inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer));
212
213 JPanel inheritancePanel = new JPanel();
214 inheritancePanel.setLayout(new BorderLayout());
215 inheritancePanel.add(new JScrollPane(inheritanceTree));
216
217 // init implementations panel
218 implementationsTree = new JTree();
219 implementationsTree.setModel(null);
220 implementationsTree.addMouseListener(new MouseAdapter() {
221 @Override
222 public void mouseClicked(MouseEvent event) {
223 if (event.getClickCount() >= 2) {
224 // get the selected node
225 TreePath path = implementationsTree.getSelectionPath();
226 if (path == null) {
227 return;
228 }
229
230 Object node = path.getLastPathComponent();
231 if (node instanceof ClassImplementationsTreeNode) {
232 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
233 controller.navigateTo(classNode.getClassEntry());
234 } else if (node instanceof MethodImplementationsTreeNode) {
235 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
236 controller.navigateTo(methodNode.getMethodEntry());
237 }
238 }
239 }
240 });
241 JPanel implementationsPanel = new JPanel();
242 implementationsPanel.setLayout(new BorderLayout());
243 implementationsPanel.add(new JScrollPane(implementationsTree));
244
245 // init call panel
246 callsTree = new JTree();
247 callsTree.setModel(null);
248 callsTree.addMouseListener(new MouseAdapter() {
249 @SuppressWarnings("unchecked")
250 @Override
251 public void mouseClicked(MouseEvent event) {
252 if (event.getClickCount() >= 2) {
253 // get the selected node
254 TreePath path = callsTree.getSelectionPath();
255 if (path == null) {
256 return;
257 }
258
259 Object node = path.getLastPathComponent();
260 if (node instanceof ReferenceTreeNode) {
261 ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node);
262 if (referenceNode.getReference() != null) {
263 controller.navigateTo(referenceNode.getReference());
264 } else {
265 controller.navigateTo(referenceNode.getEntry());
266 }
267 }
268 }
269 }
270 });
271 tokens = new JList<>();
272 tokens.setCellRenderer(new TokenListCellRenderer(this.controller));
273 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 tokens.setLayoutOrientation(JList.VERTICAL);
275 tokens.addMouseListener(new MouseAdapter() {
276 @Override
277 public void mouseClicked(MouseEvent event) {
278 if (event.getClickCount() == 2) {
279 Token selected = tokens.getSelectedValue();
280 if (selected != null) {
281 showToken(selected);
282 }
283 }
284 }
285 });
286 tokens.setPreferredSize(ScaleUtil.getDimension(0, 200));
287 tokens.setMinimumSize(ScaleUtil.getDimension(0, 200));
288 JSplitPane callPanel = new JSplitPane(
289 JSplitPane.VERTICAL_SPLIT,
290 true,
291 new JScrollPane(callsTree),
292 new JScrollPane(tokens)
293 );
294 callPanel.setResizeWeight(1); // let the top side take all the slack
295 callPanel.resetToPreferredSizes();
296
297 // layout controls
298 JPanel centerPanel = new JPanel();
299 centerPanel.setLayout(new BorderLayout());
300 centerPanel.add(infoPanel, BorderLayout.NORTH);
301 centerPanel.add(sourceScroller, BorderLayout.CENTER);
302 tabs = new JTabbedPane();
303 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0));
304 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel);
305 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel);
306 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel);
307 logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM);
308 userModel = new DefaultListModel<>();
309 users = new JList<>(userModel);
310 messageModel = new DefaultListModel<>();
311 messages = new JList<>(messageModel);
312 messages.setCellRenderer(new MessageListCellRenderer());
313 JPanel messagePanel = new JPanel(new BorderLayout());
314 messageScrollPane = new JScrollPane(this.messages);
315 messagePanel.add(messageScrollPane, BorderLayout.CENTER);
316 JPanel chatPanel = new JPanel(new BorderLayout());
317 chatBox = new JTextField();
318 AbstractAction sendListener = new AbstractAction("Send") {
319 @Override
320 public void actionPerformed(ActionEvent e) {
321 sendMessage();
322 }
323 };
324 chatBox.addActionListener(sendListener);
325 JButton chatSendButton = new JButton(sendListener);
326 chatPanel.add(chatBox, BorderLayout.CENTER);
327 chatPanel.add(chatSendButton, BorderLayout.EAST);
328 messagePanel.add(chatPanel, BorderLayout.SOUTH);
329 logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users));
330 logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel);
331 logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs);
332 logSplit.setResizeWeight(0.5);
333 logSplit.resetToPreferredSizes();
334 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
335 splitRight.setResizeWeight(1); // let the left side take all the slack
336 splitRight.resetToPreferredSizes();
337 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
338 splitCenter.setResizeWeight(0); // let the right side take all the slack
339 pane.add(splitCenter, BorderLayout.CENTER);
340
341 // init menus
342 this.menuBar = new MenuBar(this);
343 this.frame.setJMenuBar(this.menuBar);
344
345 // init status bar
346 statusBar = new JPanel(new BorderLayout());
347 statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
348 connectionStatusLabel = new JLabel();
349 statusLabel = new JLabel();
350 statusBar.add(statusLabel, BorderLayout.CENTER);
351 statusBar.add(connectionStatusLabel, BorderLayout.EAST);
352 pane.add(statusBar, BorderLayout.SOUTH);
353
354 // init state
355 setConnectionState(ConnectionState.NOT_CONNECTED);
356 onCloseJar();
357
358 this.frame.addWindowListener(new WindowAdapter() {
359 @Override
360 public void windowClosing(WindowEvent event) {
361 close();
362 }
363 });
364
365 // show the frame
366 pane.doLayout();
367 this.frame.setSize(ScaleUtil.getDimension(1024, 576));
368 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
369 this.frame.setVisible(true);
370 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
371 this.frame.setLocationRelativeTo(null);
372 }
373
374 public JFrame getFrame() {
375 return this.frame;
376 }
377
378 public GuiController getController() {
379 return this.controller;
380 }
381
382 public void onStartOpenJar() {
383 this.classesPanel.removeAll();
384 redraw();
385 }
386
387 public void onFinishOpenJar(String jarName) {
388 // update gui
389 this.frame.setTitle(Constants.NAME + " - " + jarName);
390 this.classesPanel.removeAll();
391 this.classesPanel.add(splitClasses);
392 setEditorText(null);
393
394 // update menu
395 isJarOpen = true;
396
397 updateUiState();
398 redraw();
399 }
400
401 public void onCloseJar() {
402
403 // update gui
404 this.frame.setTitle(Constants.NAME);
405 setObfClasses(null);
406 setDeobfClasses(null);
407 setEditorText(null);
408 this.classesPanel.removeAll();
409
410 // update menu
411 isJarOpen = false;
412 setMappingsFile(null);
413
414 updateUiState();
415 redraw();
416 }
417
418 public void setObfClasses(Collection<ClassEntry> obfClasses) {
419 this.obfPanel.obfClasses.setClasses(obfClasses);
420 }
421
422 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
423 this.deobfPanel.deobfClasses.setClasses(deobfClasses);
424 }
425
426 public void setMappingsFile(Path path) {
427 this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null);
428 updateUiState();
429 }
430
431 public void setEditorText(String source) {
432 this.editor.getHighlighter().removeAllHighlights();
433 this.editor.setText(source);
434 }
435
436 public void setSource(DecompiledClassSource source) {
437 editor.setText(source.toString());
438 setHighlightedTokens(source.getHighlightedTokens());
439 }
440
441 public void showToken(final Token token) {
442 if (token == null) {
443 throw new IllegalArgumentException("Token cannot be null!");
444 }
445 CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter);
446 redraw();
447 }
448
449 public void showTokens(Collection<Token> tokens) {
450 Vector<Token> sortedTokens = new Vector<>(tokens);
451 Collections.sort(sortedTokens);
452 if (sortedTokens.size() > 1) {
453 // sort the tokens and update the tokens panel
454 this.tokens.setListData(sortedTokens);
455 this.tokens.setSelectedIndex(0);
456 } else {
457 this.tokens.setListData(new Vector<>());
458 }
459
460 // show the first token
461 showToken(sortedTokens.get(0));
462 }
463
464 public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) {
465 // remove any old highlighters
466 this.editor.getHighlighter().removeAllHighlights();
467
468 if (boxHighlightPainters != null) {
469 for (TokenHighlightType type : tokens.keySet()) {
470 BoxHighlightPainter painter = boxHighlightPainters.get(type);
471 if (painter != null) {
472 setHighlightedTokens(tokens.get(type), painter);
473 }
474 }
475 }
476
477 redraw();
478 }
479
480 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
481 for (Token token : tokens) {
482 try {
483 this.editor.getHighlighter().addHighlight(token.start, token.end, painter);
484 } catch (BadLocationException ex) {
485 throw new IllegalArgumentException(ex);
486 }
487 }
488 }
489
490 private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) {
491 if (reference == null) {
492 infoPanel.clearReference();
493 return;
494 }
495
496 this.cursorReference = reference;
497
498 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference);
499
500 infoPanel.removeAll();
501 if (translatedReference.entry instanceof ClassEntry) {
502 showClassEntry((ClassEntry) translatedReference.entry);
503 } else if (translatedReference.entry instanceof FieldEntry) {
504 showFieldEntry((FieldEntry) translatedReference.entry);
505 } else if (translatedReference.entry instanceof MethodEntry) {
506 showMethodEntry((MethodEntry) translatedReference.entry);
507 } else if (translatedReference.entry instanceof LocalVariableEntry) {
508 showLocalVariableEntry((LocalVariableEntry) translatedReference.entry);
509 } else {
510 throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName());
511 }
512
513 redraw();
514 }
515
516 private void showLocalVariableEntry(LocalVariableEntry entry) {
517 addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName());
518 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName());
519 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName());
520 addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex()));
521 }
522
523 private void showClassEntry(ClassEntry entry) {
524 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName());
525 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
526 }
527
528 private void showFieldEntry(FieldEntry entry) {
529 addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName());
530 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
531 addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString());
532 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
533 }
534
535 private void showMethodEntry(MethodEntry entry) {
536 if (entry.isConstructor()) {
537 addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName());
538 } else {
539 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName());
540 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
541 }
542 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString());
543 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
544 }
545
546 private void addNameValue(JPanel container, String name, String value) {
547 JPanel panel = new JPanel();
548 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
549
550 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
551 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
552 panel.add(label);
553
554 panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT)));
555
556 container.add(panel);
557 }
558
559 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) {
560 if (!getController().project.isRenamable(entry))
561 return null;
562 JPanel panel = new JPanel();
563 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
564 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
565 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
566 panel.add(label);
567 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
568 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
569 combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
570
571 EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry);
572 if (mapping != null) {
573 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
574 } else {
575 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
576 }
577
578 combo.addItemListener(controller::modifierChange);
579
580 panel.add(combo);
581
582 container.add(panel);
583
584 return combo;
585 }
586
587 public void onCaretMove(int pos, boolean fromClick) {
588 if (controller.project == null)
589 return;
590 EntryRemapper mapper = controller.project.getMapper();
591 Token token = this.controller.getToken(pos);
592 boolean isToken = token != null;
593
594 cursorReference = this.controller.getReference(token);
595 Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null;
596
597 if (referenceEntry != null && shouldNavigateOnClick && fromClick) {
598 shouldNavigateOnClick = false;
599 Entry<?> navigationEntry = referenceEntry;
600 if (cursorReference.context == null) {
601 EntryResolver resolver = mapper.getObfResolver();
602 navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT);
603 }
604 controller.navigateTo(navigationEntry);
605 return;
606 }
607
608 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
609 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry;
610 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
611 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
612 boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference);
613
614 if (!isRenaming()) {
615 if (isToken) {
616 showCursorReference(cursorReference);
617 } else {
618 infoPanel.clearReference();
619 }
620 }
621
622 this.popupMenu.renameMenu.setEnabled(isRenamable);
623 this.popupMenu.editJavadocMenu.setEnabled(isRenamable);
624 this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
625 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
626 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
627 this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry);
628 this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
629 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference());
630 this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference());
631 this.popupMenu.toggleMappingMenu.setEnabled(isRenamable);
632
633 if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) {
634 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated"));
635 } else {
636 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated"));
637 }
638 }
639
640 public void startDocChange() {
641 EntryReference<Entry<?>, Entry<?>> curReference = cursorReference;
642 if (isRenaming()) {
643 finishRename(false);
644 }
645 renamingReference = curReference;
646
647 // init the text box
648 javadocTextArea = new JTextArea(10, 40);
649
650 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
651 javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs()));
652
653 JavadocDialog.init(frame, javadocTextArea, this::finishDocChange);
654 javadocTextArea.grabFocus();
655
656 redraw();
657 }
658
659 private void finishDocChange(JFrame ui, boolean saveName) {
660 String newName = javadocTextArea.getText();
661 if (saveName) {
662 try {
663 this.controller.changeDocs(renamingReference, newName);
664 this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName));
665 } catch (IllegalNameException ex) {
666 javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1));
667 javadocTextArea.setToolTipText(ex.getReason());
668 Utils.showToolTipNow(javadocTextArea);
669 return;
670 }
671
672 ui.setVisible(false);
673 showCursorReference(cursorReference);
674 return;
675 }
676
677 // abort the jd change
678 javadocTextArea = null;
679 ui.setVisible(false);
680 showCursorReference(cursorReference);
681
682 this.editor.grabFocus();
683
684 redraw();
685 }
686
687 public void startRename() {
688
689 // init the text box
690 renameTextField = new JTextField();
691
692 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
693 renameTextField.setText(translatedReference.getNameableName());
694
695 renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height)));
696 renameTextField.addKeyListener(new KeyAdapter() {
697 @Override
698 public void keyPressed(KeyEvent event) {
699 switch (event.getKeyCode()) {
700 case KeyEvent.VK_ENTER:
701 finishRename(true);
702 break;
703
704 case KeyEvent.VK_ESCAPE:
705 finishRename(false);
706 break;
707 default:
708 break;
709 }
710 }
711 });
712
713 // find the label with the name and replace it with the text box
714 JPanel panel = (JPanel) infoPanel.getComponent(0);
715 panel.remove(panel.getComponentCount() - 1);
716 panel.add(renameTextField);
717 renameTextField.grabFocus();
718
719 int offset = renameTextField.getText().lastIndexOf('/') + 1;
720 // If it's a class and isn't in the default package, assume that it's deobfuscated.
721 if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0)
722 renameTextField.select(offset, renameTextField.getText().length());
723 else
724 renameTextField.selectAll();
725
726 renamingReference = cursorReference;
727
728 redraw();
729 }
730
731 private void finishRename(boolean saveName) {
732 String newName = renameTextField.getText();
733
734 if (saveName && newName != null && !newName.isEmpty()) {
735 try {
736 this.controller.rename(renamingReference, newName, true);
737 this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true));
738 renameTextField = null;
739 } catch (IllegalNameException ex) {
740 renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1));
741 renameTextField.setToolTipText(ex.getReason());
742 Utils.showToolTipNow(renameTextField);
743 }
744 return;
745 }
746
747 renameTextField = null;
748
749 // abort the rename
750 showCursorReference(cursorReference);
751
752 this.editor.grabFocus();
753
754 redraw();
755 }
756
757 private boolean isRenaming() {
758 return renameTextField != null;
759 }
760
761 public void showInheritance() {
762
763 if (cursorReference == null) {
764 return;
765 }
766
767 inheritanceTree.setModel(null);
768
769 if (cursorReference.entry instanceof ClassEntry) {
770 // get the class inheritance
771 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry);
772
773 // show the tree at the root
774 TreePath path = getPathToRoot(classNode);
775 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
776 inheritanceTree.expandPath(path);
777 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
778 } else if (cursorReference.entry instanceof MethodEntry) {
779 // get the method inheritance
780 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry);
781
782 // show the tree at the root
783 TreePath path = getPathToRoot(classNode);
784 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
785 inheritanceTree.expandPath(path);
786 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
787 }
788
789 tabs.setSelectedIndex(0);
790
791 redraw();
792 }
793
794 public void showImplementations() {
795
796 if (cursorReference == null) {
797 return;
798 }
799
800 implementationsTree.setModel(null);
801
802 DefaultMutableTreeNode node = null;
803
804 // get the class implementations
805 if (cursorReference.entry instanceof ClassEntry)
806 node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry);
807 else // get the method implementations
808 if (cursorReference.entry instanceof MethodEntry)
809 node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry);
810
811 if (node != null) {
812 // show the tree at the root
813 TreePath path = getPathToRoot(node);
814 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
815 implementationsTree.expandPath(path);
816 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
817 }
818
819 tabs.setSelectedIndex(1);
820
821 redraw();
822 }
823
824 public void showCalls(boolean recurse) {
825 if (cursorReference == null) {
826 return;
827 }
828
829 if (cursorReference.entry instanceof ClassEntry) {
830 ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry);
831 callsTree.setModel(new DefaultTreeModel(node));
832 } else if (cursorReference.entry instanceof FieldEntry) {
833 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry);
834 callsTree.setModel(new DefaultTreeModel(node));
835 } else if (cursorReference.entry instanceof MethodEntry) {
836 MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse);
837 callsTree.setModel(new DefaultTreeModel(node));
838 }
839
840 tabs.setSelectedIndex(2);
841
842 redraw();
843 }
844
845 public void toggleMapping() {
846 Entry<?> obfEntry = cursorReference.entry;
847 Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
848
849 if (!Objects.equals(obfEntry, deobfEntry)) {
850 this.controller.removeMapping(cursorReference);
851 this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry()));
852 } else {
853 this.controller.markAsDeobfuscated(cursorReference);
854 this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry()));
855 }
856 }
857
858 private TreePath getPathToRoot(TreeNode node) {
859 List<TreeNode> nodes = Lists.newArrayList();
860 TreeNode n = node;
861 do {
862 nodes.add(n);
863 n = n.getParent();
864 } while (n != null);
865 Collections.reverse(nodes);
866 return new TreePath(nodes.toArray());
867 }
868
869 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
870 int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION,
871 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
872 callback.apply(response);
873 }
874
875 public void saveMapping() {
876 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
877 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
878 }
879
880 public void close() {
881 if (!this.controller.isDirty()) {
882 // everything is saved, we can exit safely
883 exit();
884 } else {
885 // ask to save before closing
886 showDiscardDiag((response) -> {
887 if (response == JOptionPane.YES_OPTION) {
888 this.saveMapping();
889 exit();
890 } else if (response == JOptionPane.NO_OPTION) {
891 exit();
892 }
893
894 return null;
895 }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel"));
896 }
897 }
898
899 private void exit() {
900 if (searchDialog != null) {
901 searchDialog.dispose();
902 }
903 this.frame.dispose();
904 System.exit(0);
905 }
906
907 public void redraw() {
908 this.frame.validate();
909 this.frame.repaint();
910 }
911
912 public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException {
913 // package rename
914 if (data instanceof String) {
915 for (int i = 0; i < node.getChildCount(); i++) {
916 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
917 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
918 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
919 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false);
920 this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false));
921 childNode.setUserObject(dataChild);
922 }
923 node.setUserObject(data);
924 // Ob package will never be modified, just reload deob view
925 this.deobfPanel.deobfClasses.reload();
926 }
927 // class rename
928 else if (data instanceof ClassEntry) {
929 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
930 this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false));
931 }
932 }
933
934 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) {
935 String oldEntry = obfReference.entry.getContainingClass().getPackageName();
936 String newEntry = new ClassEntry(newName).getPackageName();
937 moveClassTree(obfReference, oldEntry == null, newEntry == null);
938 }
939
940 // TODO: getExpansionState will *not* actually update itself based on name changes!
941 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) {
942 ClassEntry classEntry = obfReference.entry.getContainingClass();
943
944 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses);
945 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses);
946
947 // Ob -> deob
948 if (!isNewOb) {
949 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
950 this.obfPanel.obfClasses.moveClassOut(classEntry);
951 this.deobfPanel.deobfClasses.reload();
952 this.obfPanel.obfClasses.reload();
953 }
954 // Deob -> ob
955 else if (!isOldOb) {
956 this.obfPanel.obfClasses.moveClassIn(classEntry);
957 this.deobfPanel.deobfClasses.moveClassOut(classEntry);
958 this.deobfPanel.deobfClasses.reload();
959 this.obfPanel.obfClasses.reload();
960 }
961 // Local move
962 else if (isOldOb) {
963 this.obfPanel.obfClasses.moveClassIn(classEntry);
964 this.obfPanel.obfClasses.reload();
965 } else {
966 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
967 this.deobfPanel.deobfClasses.reload();
968 }
969
970 this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf);
971 this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf);
972 }
973
974 public PanelObf getObfPanel() {
975 return obfPanel;
976 }
977
978 public PanelDeobf getDeobfPanel() {
979 return deobfPanel;
980 }
981
982 public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) {
983 this.shouldNavigateOnClick = shouldNavigateOnClick;
984 }
985
986 public SearchDialog getSearchDialog() {
987 if (searchDialog == null) {
988 searchDialog = new SearchDialog(this);
989 }
990 return searchDialog;
991 }
992
993
994 public MenuBar getMenuBar() {
995 return menuBar;
996 }
997
998 public void addMessage(Message message) {
999 JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar();
1000 boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent();
1001 messageModel.addElement(message);
1002 if (isAtBottom) {
1003 SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent()));
1004 }
1005 statusLabel.setText(message.translate());
1006 }
1007
1008 public void setUserList(List<String> users) {
1009 userModel.clear();
1010 users.forEach(userModel::addElement);
1011 connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size()));
1012 }
1013
1014 private void sendMessage() {
1015 String text = chatBox.getText().trim();
1016 if (!text.isEmpty()) {
1017 getController().sendPacket(new MessageC2SPacket(text));
1018 }
1019 chatBox.setText("");
1020 }
1021
1022 /**
1023 * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state.
1024 * This is a central place to update the UI state to prevent multiple code paths from changing the same state,
1025 * causing inconsistencies.
1026 */
1027 public void updateUiState() {
1028 menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING);
1029 menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect"));
1030 menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED);
1031 menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop"));
1032
1033 menuBar.closeJarMenu.setEnabled(isJarOpen);
1034 menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1035 menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED);
1036 menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1037 menuBar.closeMappingsMenu.setEnabled(isJarOpen);
1038 menuBar.exportSourceMenu.setEnabled(isJarOpen);
1039 menuBar.exportJarMenu.setEnabled(isJarOpen);
1040
1041 connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected"));
1042
1043 if (connectionState == ConnectionState.NOT_CONNECTED) {
1044 logSplit.setLeftComponent(null);
1045 splitRight.setRightComponent(tabs);
1046 } else {
1047 splitRight.setRightComponent(logSplit);
1048 logSplit.setLeftComponent(tabs);
1049 }
1050 }
1051
1052 public void setConnectionState(ConnectionState state) {
1053 connectionState = state;
1054 statusLabel.setText(I18n.translate("status.ready"));
1055 updateUiState();
1056 }
1057
1058}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
deleted file mode 100644
index cccc9e8..0000000
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ /dev/null
@@ -1,729 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.Lists;
15import com.google.common.util.concurrent.ThreadFactoryBuilder;
16import cuchaz.enigma.Enigma;
17import cuchaz.enigma.EnigmaProfile;
18import cuchaz.enigma.EnigmaProject;
19import cuchaz.enigma.analysis.*;
20import cuchaz.enigma.api.service.ObfuscationTestService;
21import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
22import cuchaz.enigma.config.Config;
23import cuchaz.enigma.gui.dialog.ProgressDialog;
24import cuchaz.enigma.gui.stats.StatsGenerator;
25import cuchaz.enigma.gui.stats.StatsMember;
26import cuchaz.enigma.gui.util.History;
27import cuchaz.enigma.network.EnigmaClient;
28import cuchaz.enigma.network.EnigmaServer;
29import cuchaz.enigma.network.IntegratedEnigmaServer;
30import cuchaz.enigma.network.ServerPacketHandler;
31import cuchaz.enigma.network.packet.LoginC2SPacket;
32import cuchaz.enigma.network.packet.Packet;
33import cuchaz.enigma.source.*;
34import cuchaz.enigma.throwables.MappingParseException;
35import cuchaz.enigma.translation.Translator;
36import cuchaz.enigma.translation.mapping.*;
37import cuchaz.enigma.translation.mapping.serde.MappingFormat;
38import cuchaz.enigma.translation.mapping.tree.EntryTree;
39import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
40import cuchaz.enigma.translation.representation.entry.ClassEntry;
41import cuchaz.enigma.translation.representation.entry.Entry;
42import cuchaz.enigma.translation.representation.entry.FieldEntry;
43import cuchaz.enigma.translation.representation.entry.MethodEntry;
44import cuchaz.enigma.utils.I18n;
45import cuchaz.enigma.utils.Message;
46import cuchaz.enigma.utils.ReadableToken;
47import cuchaz.enigma.utils.Utils;
48import org.objectweb.asm.tree.ClassNode;
49
50import javax.annotation.Nullable;
51import javax.swing.JOptionPane;
52import javax.swing.SwingUtilities;
53import java.awt.*;
54import java.awt.event.ItemEvent;
55import java.io.*;
56import java.nio.file.Path;
57import java.util.Collection;
58import java.util.List;
59import java.util.Set;
60import java.util.concurrent.CompletableFuture;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
63import java.util.stream.Collectors;
64import java.util.stream.Stream;
65
66public class GuiController {
67 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(
68 new ThreadFactoryBuilder()
69 .setDaemon(true)
70 .setNameFormat("decompiler-thread")
71 .build()
72 );
73
74 private final Gui gui;
75 public final Enigma enigma;
76
77 public EnigmaProject project;
78 private DecompilerService decompilerService;
79 private Decompiler decompiler;
80 private IndexTreeBuilder indexTreeBuilder;
81
82 private Path loadedMappingPath;
83 private MappingFormat loadedMappingFormat;
84
85 private DecompiledClassSource currentSource;
86 private Source uncommentedSource;
87
88 private EnigmaClient client;
89 private EnigmaServer server;
90
91 public GuiController(Gui gui, EnigmaProfile profile) {
92 this.gui = gui;
93 this.enigma = Enigma.builder()
94 .setProfile(profile)
95 .build();
96
97 decompilerService = Config.getInstance().decompiler.service;
98 }
99
100 public boolean isDirty() {
101 return project != null && project.getMapper().isDirty();
102 }
103
104 public CompletableFuture<Void> openJar(final Path jarPath) {
105 this.gui.onStartOpenJar();
106
107 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
108 project = enigma.openJar(jarPath, progress);
109 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
110 decompiler = createDecompiler();
111 gui.onFinishOpenJar(jarPath.getFileName().toString());
112 refreshClasses();
113 });
114 }
115
116 private Decompiler createDecompiler() {
117 return decompilerService.create(name -> {
118 ClassNode node = project.getClassCache().getClassNode(name);
119
120 if (node == null) {
121 return null;
122 }
123
124 ClassNode fixedNode = new ClassNode();
125 node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex()));
126 return fixedNode;
127 }, new SourceSettings(true, true));
128 }
129
130 public void closeJar() {
131 this.project = null;
132 this.gui.onCloseJar();
133 }
134
135 public CompletableFuture<Void> openMappings(MappingFormat format, Path path) {
136 if (project == null) return CompletableFuture.completedFuture(null);
137
138 gui.setMappingsFile(path);
139
140 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
141 try {
142 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
143
144 EntryTree<EntryMapping> mappings = format.read(path, progress, saveParameters);
145 project.setMappings(mappings);
146
147 loadedMappingFormat = format;
148 loadedMappingPath = path;
149
150 refreshClasses();
151 refreshCurrentClass();
152 } catch (MappingParseException e) {
153 JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage());
154 }
155 });
156 }
157
158 public void openMappings(EntryTree<EntryMapping> mappings) {
159 if (project == null) return;
160
161 project.setMappings(mappings);
162 refreshClasses();
163 refreshCurrentClass();
164 }
165
166 public CompletableFuture<Void> saveMappings(Path path) {
167 return saveMappings(path, loadedMappingFormat);
168 }
169
170 public CompletableFuture<Void> saveMappings(Path path, MappingFormat format) {
171 if (project == null) return CompletableFuture.completedFuture(null);
172
173 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
174 EntryRemapper mapper = project.getMapper();
175 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
176
177 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
178 boolean saveAll = !path.equals(loadedMappingPath);
179
180 loadedMappingFormat = format;
181 loadedMappingPath = path;
182
183 if (saveAll) {
184 format.write(mapper.getObfToDeobf(), path, progress, saveParameters);
185 } else {
186 format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters);
187 }
188 });
189 }
190
191 public void closeMappings() {
192 if (project == null) return;
193
194 project.setMappings(null);
195
196 this.gui.setMappingsFile(null);
197 refreshClasses();
198 refreshCurrentClass();
199 }
200
201 public CompletableFuture<Void> dropMappings() {
202 if (project == null) return CompletableFuture.completedFuture(null);
203
204 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress));
205 }
206
207 public CompletableFuture<Void> exportSource(final Path path) {
208 if (project == null) return CompletableFuture.completedFuture(null);
209
210 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
211 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
212 EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService);
213
214 source.write(path, progress);
215 });
216 }
217
218 public CompletableFuture<Void> exportJar(final Path path) {
219 if (project == null) return CompletableFuture.completedFuture(null);
220
221 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
222 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
223 jar.write(path, progress);
224 });
225 }
226
227 public Token getToken(int pos) {
228 if (this.currentSource == null) {
229 return null;
230 }
231 return this.currentSource.getIndex().getReferenceToken(pos);
232 }
233
234 @Nullable
235 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
236 if (this.currentSource == null) {
237 return null;
238 }
239 return this.currentSource.getIndex().getReference(token);
240 }
241
242 public ReadableToken getReadableToken(Token token) {
243 if (this.currentSource == null) {
244 return null;
245 }
246
247 SourceIndex index = this.currentSource.getIndex();
248 return new ReadableToken(
249 index.getLineNumber(token.start),
250 index.getColumnNumber(token.start),
251 index.getColumnNumber(token.end)
252 );
253 }
254
255 /**
256 * Navigates to the declaration with respect to navigation history
257 *
258 * @param entry the entry whose declaration will be navigated to
259 */
260 public void openDeclaration(Entry<?> entry) {
261 if (entry == null) {
262 throw new IllegalArgumentException("Entry cannot be null!");
263 }
264 openReference(new EntryReference<>(entry, entry.getName()));
265 }
266
267 /**
268 * Navigates to the reference with respect to navigation history
269 *
270 * @param reference the reference
271 */
272 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
273 if (reference == null) {
274 throw new IllegalArgumentException("Reference cannot be null!");
275 }
276 if (this.gui.referenceHistory == null) {
277 this.gui.referenceHistory = new History<>(reference);
278 } else {
279 if (!reference.equals(this.gui.referenceHistory.getCurrent())) {
280 this.gui.referenceHistory.push(reference);
281 }
282 }
283 setReference(reference);
284 }
285
286 /**
287 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
288 *
289 * @param reference the reference
290 */
291 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
292 // get the reference target class
293 ClassEntry classEntry = reference.getLocationClassEntry();
294 if (!project.isRenamable(classEntry)) {
295 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
296 }
297
298 if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) {
299 // deobfuscate the class, then navigate to the reference
300 loadClass(classEntry, () -> showReference(reference));
301 } else {
302 showReference(reference);
303 }
304 }
305
306 /**
307 * Navigates to the reference without modifying history. Assumes the class is loaded.
308 *
309 * @param reference
310 */
311 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
312 Collection<Token> tokens = getTokensForReference(reference);
313 if (tokens.isEmpty()) {
314 // DEBUG
315 System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry()));
316 } else {
317 this.gui.showTokens(tokens);
318 }
319 }
320
321 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) {
322 EntryRemapper mapper = this.project.getMapper();
323
324 SourceIndex index = this.currentSource.getIndex();
325 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
326 .stream()
327 .flatMap(r -> index.getReferenceTokens(r).stream())
328 .collect(Collectors.toList());
329 }
330
331 public void openPreviousReference() {
332 if (hasPreviousReference()) {
333 setReference(gui.referenceHistory.goBack());
334 }
335 }
336
337 public boolean hasPreviousReference() {
338 return gui.referenceHistory != null && gui.referenceHistory.canGoBack();
339 }
340
341 public void openNextReference() {
342 if (hasNextReference()) {
343 setReference(gui.referenceHistory.goForward());
344 }
345 }
346
347 public boolean hasNextReference() {
348 return gui.referenceHistory != null && gui.referenceHistory.canGoForward();
349 }
350
351 public void navigateTo(Entry<?> entry) {
352 if (!project.isRenamable(entry)) {
353 // entry is not in the jar. Ignore it
354 return;
355 }
356 openDeclaration(entry);
357 }
358
359 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
360 if (!project.isRenamable(reference.getLocationClassEntry())) {
361 return;
362 }
363 openReference(reference);
364 }
365
366 private void refreshClasses() {
367 List<ClassEntry> obfClasses = Lists.newArrayList();
368 List<ClassEntry> deobfClasses = Lists.newArrayList();
369 this.addSeparatedClasses(obfClasses, deobfClasses);
370 this.gui.setObfClasses(obfClasses);
371 this.gui.setDeobfClasses(deobfClasses);
372 }
373
374 public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
375 EntryRemapper mapper = project.getMapper();
376
377 Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses();
378 Stream<ClassEntry> visibleClasses = classes.stream()
379 .filter(entry -> !entry.isInnerClass());
380
381 visibleClasses.forEach(entry -> {
382 ClassEntry deobfEntry = mapper.deobfuscate(entry);
383
384 List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
385 boolean obfuscated = deobfEntry.equals(entry);
386
387 if (obfuscated && !obfService.isEmpty()) {
388 if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) {
389 obfuscated = false;
390 }
391 }
392
393 if (obfuscated) {
394 obfClasses.add(entry);
395 } else {
396 deobfClasses.add(entry);
397 }
398 });
399 }
400
401 public void refreshCurrentClass() {
402 refreshCurrentClass(null);
403 }
404
405 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) {
406 refreshCurrentClass(reference, RefreshMode.MINIMAL);
407 }
408
409 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) {
410 if (currentSource != null) {
411 if (reference == null) {
412 int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart());
413 int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd());
414
415 Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect();
416 // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set
417 int anchorModelPos = gui.editor.getSelectionStart();
418 Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
419 if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) {
420 anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y));
421 anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
422 }
423 int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos);
424 Rectangle anchorViewPos_f = anchorViewPos;
425 int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue();
426
427 loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> {
428 int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos);
429 Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos);
430 int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y);
431
432 gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd));
433 // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so
434 // we need to wrap our change to the scroll position inside another invokeLater so it happens after
435 // the caret's own scrolling.
436 SwingUtilities.invokeLater(() -> {
437 gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum()));
438 gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum()));
439 });
440 }), mode);
441 } else {
442 loadClass(currentSource.getEntry(), () -> showReference(reference), mode);
443 }
444 }
445 }
446
447 private void loadClass(ClassEntry classEntry, Runnable callback) {
448 loadClass(classEntry, callback, RefreshMode.MINIMAL);
449 }
450
451 private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) {
452 ClassEntry targetClass = classEntry.getOutermostClass();
453
454 boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass);
455 if (requiresDecompile) {
456 currentSource = null; // Or the GUI may try to find a nonexistent token
457 gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling"));
458 }
459
460 DECOMPILER_SERVICE.submit(() -> {
461 try {
462 if (requiresDecompile || mode == RefreshMode.JAVADOCS) {
463 currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS);
464 }
465
466 remapSource(project.getMapper().getDeobfuscator());
467 callback.run();
468 } catch (Throwable t) {
469 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
470 t.printStackTrace(System.err);
471 }
472 });
473 }
474
475 private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) {
476 try {
477 if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) {
478 uncommentedSource = decompiler.getSource(targetClass.getFullName());
479 }
480
481 Source source = uncommentedSource.addJavadocs(project.getMapper());
482
483 if (source == null) {
484 gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass);
485 return DecompiledClassSource.text(targetClass, "Unable to find class");
486 }
487
488 SourceIndex index = source.index();
489 index.resolveReferences(project.getMapper().getObfResolver());
490
491 return new DecompiledClassSource(targetClass, index);
492 } catch (Throwable t) {
493 StringWriter traceWriter = new StringWriter();
494 t.printStackTrace(new PrintWriter(traceWriter));
495
496 return DecompiledClassSource.text(targetClass, traceWriter.toString());
497 }
498 }
499
500 private void remapSource(Translator translator) {
501 if (currentSource == null) {
502 return;
503 }
504
505 currentSource.remapSource(project, translator);
506
507 gui.setEditorTheme(Config.getInstance().lookAndFeel);
508 gui.setSource(currentSource);
509 }
510
511 public void modifierChange(ItemEvent event) {
512 if (event.getStateChange() == ItemEvent.SELECTED) {
513 EntryRemapper mapper = project.getMapper();
514 Entry<?> entry = gui.cursorReference.entry;
515 AccessModifier modifier = (AccessModifier) event.getItem();
516
517 EntryMapping mapping = mapper.getDeobfMapping(entry);
518 if (mapping != null) {
519 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
520 } else {
521 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
522 }
523
524 refreshCurrentClass();
525 }
526 }
527
528 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
529 Translator translator = project.getMapper().getDeobfuscator();
530 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
531 return ClassInheritanceTreeNode.findNode(rootNode, entry);
532 }
533
534 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
535 Translator translator = project.getMapper().getDeobfuscator();
536 return this.indexTreeBuilder.buildClassImplementations(translator, entry);
537 }
538
539 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
540 Translator translator = project.getMapper().getDeobfuscator();
541 MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry);
542 return MethodInheritanceTreeNode.findNode(rootNode, entry);
543 }
544
545 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
546 Translator translator = project.getMapper().getDeobfuscator();
547 List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry);
548 if (rootNodes.isEmpty()) {
549 return null;
550 }
551 if (rootNodes.size() > 1) {
552 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
553 }
554 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
555 }
556
557 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
558 Translator deobfuscator = project.getMapper().getDeobfuscator();
559 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
560 rootNode.load(project.getJarIndex(), true);
561 return rootNode;
562 }
563
564 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
565 Translator translator = project.getMapper().getDeobfuscator();
566 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
567 rootNode.load(project.getJarIndex(), true);
568 return rootNode;
569 }
570
571 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
572 Translator translator = project.getMapper().getDeobfuscator();
573 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
574 rootNode.load(project.getJarIndex(), true, recursive);
575 return rootNode;
576 }
577
578 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
579 rename(reference, newName, refreshClassTree, true);
580 }
581
582 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) {
583 Entry<?> entry = reference.getNameableEntry();
584 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
585
586 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
587 this.gui.moveClassTree(reference, newName);
588
589 refreshCurrentClass(jumpToReference ? reference : null);
590 }
591
592 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
593 removeMapping(reference, true);
594 }
595
596 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
597 project.getMapper().removeByObf(reference.getNameableEntry());
598
599 if (reference.entry instanceof ClassEntry)
600 this.gui.moveClassTree(reference, false, true);
601 refreshCurrentClass(jumpToReference ? reference : null);
602 }
603
604 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) {
605 changeDocs(reference, updatedDocs, true);
606 }
607
608 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) {
609 changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs);
610
611 refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS);
612 }
613
614 private void changeDoc(Entry<?> obfEntry, String newDoc) {
615 EntryRemapper mapper = project.getMapper();
616 if (mapper.getDeobfMapping(obfEntry) == null) {
617 markAsDeobfuscated(obfEntry, false); // NPE
618 }
619 mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false);
620 }
621
622 private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) {
623 EntryRemapper mapper = project.getMapper();
624 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming);
625 }
626
627 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
628 markAsDeobfuscated(reference, true);
629 }
630
631 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
632 EntryRemapper mapper = project.getMapper();
633 Entry<?> entry = reference.getNameableEntry();
634 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
635
636 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
637 this.gui.moveClassTree(reference, true, false);
638
639 refreshCurrentClass(jumpToReference ? reference : null);
640 }
641
642 public void openStats(Set<StatsMember> includedMembers) {
643 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
644 String data = new StatsGenerator(project).generate(progress, includedMembers);
645
646 try {
647 File statsFile = File.createTempFile("stats", ".html");
648
649 try (FileWriter w = new FileWriter(statsFile)) {
650 w.write(
651 Utils.readResourceToString("/stats.html")
652 .replace("/*data*/", data)
653 );
654 }
655
656 Desktop.getDesktop().open(statsFile);
657 } catch (IOException e) {
658 throw new Error(e);
659 }
660 });
661 }
662
663 public void setDecompiler(DecompilerService service) {
664 uncommentedSource = null;
665 decompilerService = service;
666 decompiler = createDecompiler();
667 refreshCurrentClass(null, RefreshMode.FULL);
668 }
669
670 public EnigmaClient getClient() {
671 return client;
672 }
673
674 public EnigmaServer getServer() {
675 return server;
676 }
677
678 public void createClient(String username, String ip, int port, char[] password) throws IOException {
679 client = new EnigmaClient(this, ip, port);
680 client.connect();
681 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username));
682 gui.setConnectionState(ConnectionState.CONNECTED);
683 }
684
685 public void createServer(int port, char[] password) throws IOException {
686 server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port);
687 server.start();
688 client = new EnigmaClient(this, "127.0.0.1", port);
689 client.connect();
690 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME));
691 gui.setConnectionState(ConnectionState.HOSTING);
692 }
693
694 public synchronized void disconnectIfConnected(String reason) {
695 if (client == null && server == null) {
696 return;
697 }
698
699 if (client != null) {
700 client.disconnect();
701 }
702 if (server != null) {
703 server.stop();
704 }
705 client = null;
706 server = null;
707 SwingUtilities.invokeLater(() -> {
708 if (reason != null) {
709 JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE);
710 }
711 gui.setConnectionState(ConnectionState.NOT_CONNECTED);
712 });
713 }
714
715 public void sendPacket(Packet<ServerPacketHandler> packet) {
716 if (client != null) {
717 client.sendPacket(packet);
718 }
719 }
720
721 public void addMessage(Message message) {
722 gui.addMessage(message);
723 }
724
725 public void updateUserList(List<String> users) {
726 gui.setUserList(users);
727 }
728
729}
diff --git a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java
deleted file mode 100644
index c9e38cb..0000000
--- a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package cuchaz.enigma.gui;
2
3import java.awt.Component;
4
5import javax.swing.DefaultListCellRenderer;
6import javax.swing.JList;
7
8import cuchaz.enigma.utils.Message;
9
10// For now, just render the translated text.
11// TODO: Icons or something later?
12public class MessageListCellRenderer extends DefaultListCellRenderer {
13
14 @Override
15 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
16 super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
17 Message message = (Message) value;
18 if (message != null) {
19 setText(message.translate());
20 }
21 return this;
22 }
23
24}
diff --git a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
deleted file mode 100644
index 05d90a9..0000000
--- a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
+++ /dev/null
@@ -1,42 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
15import cuchaz.enigma.config.Config;
16
17import javax.swing.*;
18import javax.swing.tree.TreeCellRenderer;
19import java.awt.*;
20
21class MethodTreeCellRenderer implements TreeCellRenderer {
22
23 private final TreeCellRenderer parent;
24
25 MethodTreeCellRenderer(TreeCellRenderer parent) {
26 this.parent = parent;
27 }
28
29 @Override
30 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
31 Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
32 Config config = Config.getInstance();
33 if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) {
34 ret.setForeground(new Color(config.defaultTextColor));
35 ret.setFont(ret.getFont().deriveFont(Font.PLAIN));
36 } else {
37 ret.setForeground(new Color(config.numberColor));
38 ret.setFont(ret.getFont().deriveFont(Font.ITALIC));
39 }
40 return ret;
41 }
42}
diff --git a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java b/src/main/java/cuchaz/enigma/gui/QuickFindAction.java
deleted file mode 100644
index b7fa2eb..0000000
--- a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java
+++ /dev/null
@@ -1,45 +0,0 @@
1package cuchaz.enigma.gui;
2
3import de.sciss.syntaxpane.SyntaxDocument;
4import de.sciss.syntaxpane.actions.DefaultSyntaxAction;
5
6import javax.swing.text.JTextComponent;
7import java.awt.event.ActionEvent;
8
9public final class QuickFindAction extends DefaultSyntaxAction {
10 public QuickFindAction() {
11 super("quick-find");
12 }
13
14 @Override
15 public void actionPerformed(JTextComponent target, SyntaxDocument document, int dot, ActionEvent event) {
16 Data data = Data.get(target);
17 data.showFindDialog(target);
18 }
19
20 private static class Data {
21 private static final String KEY = "enigma-find-data";
22 private EnigmaQuickFindDialog findDialog;
23
24 private Data() {
25 }
26
27 public static Data get(JTextComponent target) {
28 Object o = target.getDocument().getProperty(KEY);
29 if (o instanceof Data) {
30 return (Data) o;
31 }
32
33 Data data = new Data();
34 target.getDocument().putProperty(KEY, data);
35 return data;
36 }
37
38 public void showFindDialog(JTextComponent target) {
39 if (findDialog == null) {
40 findDialog = new EnigmaQuickFindDialog(target);
41 }
42 findDialog.showFor(target);
43 }
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/src/main/java/cuchaz/enigma/gui/RefreshMode.java
deleted file mode 100644
index 87cb83b..0000000
--- a/src/main/java/cuchaz/enigma/gui/RefreshMode.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui;
2
3public enum RefreshMode {
4 MINIMAL,
5 JAVADOCS,
6 FULL
7}
diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
deleted file mode 100644
index f38f44e..0000000
--- a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.analysis.Token;
4
5import java.util.HashMap;
6import java.util.Map;
7
8public class SourceRemapper {
9 private final String source;
10 private final Iterable<Token> tokens;
11
12 public SourceRemapper(String source, Iterable<Token> tokens) {
13 this.source = source;
14 this.tokens = tokens;
15 }
16
17 public Result remap(Remapper remapper) {
18 StringBuffer remappedSource = new StringBuffer(source);
19 Map<Token, Token> remappedTokens = new HashMap<>();
20
21 int accumulatedOffset = 0;
22 for (Token token : tokens) {
23 Token movedToken = token.move(accumulatedOffset);
24
25 String remappedName = remapper.remap(token, movedToken);
26 if (remappedName != null) {
27 accumulatedOffset += movedToken.getRenameOffset(remappedName);
28 movedToken.rename(remappedSource, remappedName);
29 }
30
31 if (!token.equals(movedToken)) {
32 remappedTokens.put(token, movedToken);
33 }
34 }
35
36 return new Result(remappedSource.toString(), remappedTokens);
37 }
38
39 public static class Result {
40 private final String remappedSource;
41 private final Map<Token, Token> remappedTokens;
42
43 Result(String remappedSource, Map<Token, Token> remappedTokens) {
44 this.remappedSource = remappedSource;
45 this.remappedTokens = remappedTokens;
46 }
47
48 public String getSource() {
49 return remappedSource;
50 }
51
52 public Token getRemappedToken(Token token) {
53 return remappedTokens.getOrDefault(token, token);
54 }
55
56 public boolean isEmpty() {
57 return remappedTokens.isEmpty();
58 }
59 }
60
61 public interface Remapper {
62 String remap(Token token, Token movedToken);
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
deleted file mode 100644
index 7375111..0000000
--- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
+++ /dev/null
@@ -1,35 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.Token;
15
16import javax.swing.*;
17import java.awt.*;
18
19public class TokenListCellRenderer implements ListCellRenderer<Token> {
20
21 private GuiController controller;
22 private DefaultListCellRenderer defaultRenderer;
23
24 public TokenListCellRenderer(GuiController controller) {
25 this.controller = controller;
26 this.defaultRenderer = new DefaultListCellRenderer();
27 }
28
29 @Override
30 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
31 JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
32 label.setText(this.controller.getReadableToken(token).toString());
33 return label;
34 }
35}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
deleted file mode 100644
index 43b8265..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
+++ /dev/null
@@ -1,69 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.utils.I18n;
16import cuchaz.enigma.gui.util.ScaleUtil;
17import cuchaz.enigma.utils.Utils;
18
19import javax.swing.*;
20import java.awt.*;
21import java.io.IOException;
22
23public class AboutDialog {
24
25 public static void show(JFrame parent) {
26 // init frame
27 final JFrame frame = new JFrame(String.format(I18n.translate("menu.help.about.title"), Constants.NAME));
28 final Container pane = frame.getContentPane();
29 pane.setLayout(new FlowLayout());
30
31 // load the content
32 try {
33 String html = Utils.readResourceToString("/about.html");
34 html = String.format(html, Constants.NAME, Constants.VERSION);
35 JLabel label = new JLabel(html);
36 label.setHorizontalAlignment(JLabel.CENTER);
37 pane.add(label);
38 } catch (IOException ex) {
39 throw new Error(ex);
40 }
41
42 // show the link
43 String html = "<html><a href=\"%s\">%s</a></html>";
44 html = String.format(html, Constants.URL, Constants.URL);
45 JButton link = new JButton(html);
46 link.addActionListener(event -> Utils.openUrl(Constants.URL));
47 link.setBorderPainted(false);
48 link.setOpaque(false);
49 link.setBackground(Color.WHITE);
50 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
51 link.setFocusable(false);
52 JPanel linkPanel = new JPanel();
53 linkPanel.add(link);
54 pane.add(linkPanel);
55
56 // show ok button
57 JButton okButton = new JButton(I18n.translate("menu.help.about.ok"));
58 pane.add(okButton);
59 okButton.addActionListener(arg0 -> frame.dispose());
60
61 // show the frame
62 pane.doLayout();
63 frame.setSize(ScaleUtil.getDimension(400, 220));
64 frame.setResizable(false);
65 frame.setLocationRelativeTo(parent);
66 frame.setVisible(true);
67 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
68 }
69}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
deleted file mode 100644
index 64219ab..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
+++ /dev/null
@@ -1,50 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import java.awt.BorderLayout;
4import java.awt.event.KeyAdapter;
5import java.awt.event.KeyEvent;
6
7import javax.swing.JButton;
8import javax.swing.JFrame;
9import javax.swing.JLabel;
10import javax.swing.JPanel;
11
12import cuchaz.enigma.gui.Gui;
13import cuchaz.enigma.utils.I18n;
14
15public class ChangeDialog {
16
17 public static void show(Gui gui) {
18 // init frame
19 JFrame frame = new JFrame(I18n.translate("menu.view.change.title"));
20 JPanel textPanel = new JPanel();
21 JPanel buttonPanel = new JPanel();
22 frame.setLayout(new BorderLayout());
23 frame.add(BorderLayout.NORTH, textPanel);
24 frame.add(BorderLayout.SOUTH, buttonPanel);
25
26 // show text
27 JLabel text = new JLabel((I18n.translate("menu.view.change.summary")));
28 text.setHorizontalAlignment(JLabel.CENTER);
29 textPanel.add(text);
30
31 // show ok button
32 JButton okButton = new JButton(I18n.translate("menu.view.change.ok"));
33 buttonPanel.add(okButton);
34 okButton.addActionListener(event -> frame.dispose());
35 okButton.addKeyListener(new KeyAdapter() {
36 @Override
37 public void keyPressed(KeyEvent e) {
38 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
39 frame.dispose();
40 }
41 }
42 });
43
44 // show the frame
45 frame.pack();
46 frame.setVisible(true);
47 frame.setResizable(false);
48 frame.setLocationRelativeTo(gui.getFrame());
49 }
50}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
deleted file mode 100644
index c5f505c..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
+++ /dev/null
@@ -1,82 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.Frame;
8
9public class ConnectToServerDialog {
10
11 public static Result show(Frame parentComponent) {
12 JTextField usernameField = new JTextField(System.getProperty("user.name"), 20);
13 JPanel usernameRow = new JPanel();
14 usernameRow.add(new JLabel(I18n.translate("prompt.connect.username")));
15 usernameRow.add(usernameField);
16 JTextField ipField = new JTextField(20);
17 JPanel ipRow = new JPanel();
18 ipRow.add(new JLabel(I18n.translate("prompt.connect.ip")));
19 ipRow.add(ipField);
20 JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10);
21 JPanel portRow = new JPanel();
22 portRow.add(new JLabel(I18n.translate("prompt.port")));
23 portRow.add(portField);
24 JPasswordField passwordField = new JPasswordField(20);
25 JPanel passwordRow = new JPanel();
26 passwordRow.add(new JLabel(I18n.translate("prompt.password")));
27 passwordRow.add(passwordField);
28
29 int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION);
30 if (response != JOptionPane.OK_OPTION) {
31 return null;
32 }
33
34 String username = usernameField.getText();
35 String ip = ipField.getText();
36 int port;
37 try {
38 port = Integer.parseInt(portField.getText());
39 } catch (NumberFormatException e) {
40 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE);
41 return null;
42 }
43 if (port < 0 || port >= 65536) {
44 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE);
45 return null;
46 }
47 char[] password = passwordField.getPassword();
48
49 return new Result(username, ip, port, password);
50 }
51
52 public static class Result {
53 private final String username;
54 private final String ip;
55 private final int port;
56 private final char[] password;
57
58 public Result(String username, String ip, int port, char[] password) {
59 this.username = username;
60 this.ip = ip;
61 this.port = port;
62 this.password = password;
63 }
64
65 public String getUsername() {
66 return username;
67 }
68
69 public String getIp() {
70 return ip;
71 }
72
73 public int getPort() {
74 return port;
75 }
76
77 public char[] getPassword() {
78 return password;
79 }
80 }
81
82}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
deleted file mode 100644
index 908b42e..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.utils.I18n;
16import cuchaz.enigma.gui.util.ScaleUtil;
17import cuchaz.enigma.utils.Utils;
18
19import javax.swing.*;
20import java.awt.*;
21import java.io.PrintWriter;
22import java.io.StringWriter;
23import java.io.FileWriter;
24import java.io.File;
25import java.io.IOException;
26
27public class CrashDialog {
28
29 private static CrashDialog instance = null;
30
31 private JFrame frame;
32 private JTextArea text;
33
34 private CrashDialog(JFrame parent) {
35 // init frame
36 frame = new JFrame(String.format(I18n.translate("crash.title"), Constants.NAME));
37 final Container pane = frame.getContentPane();
38 pane.setLayout(new BorderLayout());
39
40 JLabel label = new JLabel(String.format(I18n.translate("crash.summary"), Constants.NAME));
41 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
42 pane.add(label, BorderLayout.NORTH);
43
44 // report panel
45 text = new JTextArea();
46 text.setTabSize(2);
47 pane.add(new JScrollPane(text), BorderLayout.CENTER);
48
49 // buttons panel
50 JPanel buttonsPanel = new JPanel();
51 buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.LINE_AXIS));
52 JButton exportButton = new JButton(I18n.translate("crash.export"));
53 exportButton.addActionListener(event -> {
54 JFileChooser chooser = new JFileChooser();
55 chooser.setSelectedFile(new File("enigma_crash.log"));
56 if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
57 try {
58 File file = chooser.getSelectedFile();
59 FileWriter writer = new FileWriter(file);
60 writer.write(instance.text.getText());
61 writer.close();
62 } catch (IOException ex) {
63 ex.printStackTrace();
64 }
65 }
66 });
67 buttonsPanel.add(exportButton);
68 buttonsPanel.add(Box.createHorizontalGlue());
69 buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("crash.exit.warning"))));
70 JButton ignoreButton = new JButton(I18n.translate("crash.ignore"));
71 ignoreButton.addActionListener(event -> {
72 // close (hide) the dialog
73 frame.setVisible(false);
74 });
75 buttonsPanel.add(ignoreButton);
76 JButton exitButton = new JButton(I18n.translate("crash.exit"));
77 exitButton.addActionListener(event -> {
78 // exit enigma
79 System.exit(1);
80 });
81 buttonsPanel.add(exitButton);
82 pane.add(buttonsPanel, BorderLayout.SOUTH);
83
84 // show the frame
85 frame.setSize(ScaleUtil.getDimension(600, 400));
86 frame.setLocationRelativeTo(parent);
87 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
88 }
89
90 public static void init(JFrame parent) {
91 instance = new CrashDialog(parent);
92 }
93
94 public static void show(Throwable ex) {
95 // get the error report
96 StringWriter buf = new StringWriter();
97 ex.printStackTrace(new PrintWriter(buf));
98 String report = buf.toString();
99
100 // show it!
101 instance.text.setText(report);
102 instance.frame.doLayout();
103 instance.frame.setVisible(true);
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
deleted file mode 100644
index eea1dff..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
+++ /dev/null
@@ -1,65 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.*;
8
9public class CreateServerDialog {
10
11 public static Result show(Frame parentComponent) {
12 JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10);
13 JPanel portRow = new JPanel();
14 portRow.add(new JLabel(I18n.translate("prompt.port")));
15 portRow.add(portField);
16 JPasswordField passwordField = new JPasswordField(20);
17 JPanel passwordRow = new JPanel();
18 passwordRow.add(new JLabel(I18n.translate("prompt.password")));
19 passwordRow.add(passwordField);
20
21 int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION);
22 if (response != JOptionPane.OK_OPTION) {
23 return null;
24 }
25
26 int port;
27 try {
28 port = Integer.parseInt(portField.getText());
29 } catch (NumberFormatException e) {
30 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
31 return null;
32 }
33 if (port < 0 || port >= 65536) {
34 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
35 return null;
36 }
37
38 char[] password = passwordField.getPassword();
39 if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) {
40 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
41 return null;
42 }
43
44 return new Result(port, password);
45 }
46
47 public static class Result {
48 private final int port;
49 private final char[] password;
50
51 public Result(int port, char[] password) {
52 this.port = port;
53 this.password = password;
54 }
55
56 public int getPort() {
57 return port;
58 }
59
60 public char[] getPassword() {
61 return password;
62 }
63 }
64
65}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
deleted file mode 100644
index 7e41441..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
+++ /dev/null
@@ -1,159 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.utils.I18n;
15import cuchaz.enigma.gui.util.ScaleUtil;
16import cuchaz.enigma.utils.Utils;
17
18import javax.swing.*;
19import javax.swing.text.html.HTML;
20
21import java.awt.*;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24
25public class JavadocDialog {
26
27 private static JavadocDialog instance = null;
28
29 private JFrame frame;
30
31 private JavadocDialog(JFrame parent, JTextArea text, Callback callback) {
32 // init frame
33 frame = new JFrame(I18n.translate("javadocs.edit"));
34 final Container pane = frame.getContentPane();
35 pane.setLayout(new BorderLayout());
36
37 // editor panel
38 text.setTabSize(2);
39 pane.add(new JScrollPane(text), BorderLayout.CENTER);
40 text.addKeyListener(new KeyAdapter() {
41 @Override
42 public void keyPressed(KeyEvent event) {
43 switch (event.getKeyCode()) {
44 case KeyEvent.VK_ENTER:
45 if (event.isControlDown())
46 callback.closeUi(frame, true);
47 break;
48 case KeyEvent.VK_ESCAPE:
49 callback.closeUi(frame, false);
50 break;
51 default:
52 break;
53 }
54 }
55 });
56
57 // buttons panel
58 JPanel buttonsPanel = new JPanel();
59 FlowLayout buttonsLayout = new FlowLayout();
60 buttonsLayout.setAlignment(FlowLayout.RIGHT);
61 buttonsPanel.setLayout(buttonsLayout);
62 buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("javadocs.instruction"))));
63 JButton cancelButton = new JButton(I18n.translate("javadocs.cancel"));
64 cancelButton.addActionListener(event -> {
65 // close (hide) the dialog
66 callback.closeUi(frame, false);
67 });
68 buttonsPanel.add(cancelButton);
69 JButton saveButton = new JButton(I18n.translate("javadocs.save"));
70 saveButton.addActionListener(event -> {
71 // exit enigma
72 callback.closeUi(frame, true);
73 });
74 buttonsPanel.add(saveButton);
75 pane.add(buttonsPanel, BorderLayout.SOUTH);
76
77 // tags panel
78 JMenuBar tagsMenu = new JMenuBar();
79
80 // add javadoc tags
81 for (JavadocTag tag : JavadocTag.values()) {
82 JButton tagButton = new JButton(tag.getText());
83 tagButton.addActionListener(action -> {
84 boolean textSelected = text.getSelectedText() != null;
85 String tagText = tag.isInline() ? "{" + tag.getText() + " }" : tag.getText() + " ";
86
87 if (textSelected) {
88 if (tag.isInline()) {
89 tagText = "{" + tag.getText() + " " + text.getSelectedText() + "}";
90 } else {
91 tagText = tag.getText() + " " + text.getSelectedText();
92 }
93 text.replaceSelection(tagText);
94 } else {
95 text.insert(tagText, text.getCaretPosition());
96 }
97
98 if (tag.isInline()) {
99 text.setCaretPosition(text.getCaretPosition() - 1);
100 }
101 text.grabFocus();
102 });
103 tagsMenu.add(tagButton);
104 }
105
106 // add html tags
107 JComboBox<String> htmlList = new JComboBox<String>();
108 htmlList.setPreferredSize(new Dimension());
109 for (HTML.Tag htmlTag : HTML.getAllTags()) {
110 htmlList.addItem(htmlTag.toString());
111 }
112 htmlList.addActionListener(action -> {
113 String tagText = "<" + htmlList.getSelectedItem().toString() + ">";
114 text.insert(tagText, text.getCaretPosition());
115 text.grabFocus();
116 });
117 tagsMenu.add(htmlList);
118
119 pane.add(tagsMenu, BorderLayout.NORTH);
120
121 // show the frame
122 frame.setSize(ScaleUtil.getDimension(600, 400));
123 frame.setLocationRelativeTo(parent);
124 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
125 }
126
127 public static void init(JFrame parent, JTextArea area, Callback callback) {
128 instance = new JavadocDialog(parent, area, callback);
129 instance.frame.doLayout();
130 instance.frame.setVisible(true);
131 }
132
133 public interface Callback {
134 void closeUi(JFrame frame, boolean save);
135 }
136
137 private enum JavadocTag {
138 CODE(true),
139 LINK(true),
140 LINKPLAIN(true),
141 RETURN(false),
142 SEE(false),
143 THROWS(false);
144
145 private boolean inline;
146
147 private JavadocTag(boolean inline) {
148 this.inline = inline;
149 }
150
151 public String getText() {
152 return "@" + this.name().toLowerCase();
153 }
154
155 public boolean isInline() {
156 return this.inline;
157 }
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
deleted file mode 100644
index e33ae82..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
+++ /dev/null
@@ -1,109 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.ProgressListener;
16import cuchaz.enigma.utils.I18n;
17import cuchaz.enigma.gui.util.ScaleUtil;
18import cuchaz.enigma.utils.Utils;
19
20import javax.swing.*;
21import java.awt.*;
22import java.util.concurrent.CompletableFuture;
23
24public class ProgressDialog implements ProgressListener, AutoCloseable {
25
26 private JFrame frame;
27 private JLabel labelTitle;
28 private JLabel labelText;
29 private JProgressBar progress;
30
31 public ProgressDialog(JFrame parent) {
32
33 // init frame
34 this.frame = new JFrame(String.format(I18n.translate("progress.operation"), Constants.NAME));
35 final Container pane = this.frame.getContentPane();
36 FlowLayout layout = new FlowLayout();
37 layout.setAlignment(FlowLayout.LEFT);
38 pane.setLayout(layout);
39
40 this.labelTitle = new JLabel();
41 pane.add(this.labelTitle);
42
43 // set up the progress bar
44 JPanel panel = new JPanel();
45 pane.add(panel);
46 panel.setLayout(new BorderLayout());
47 this.labelText = Utils.unboldLabel(new JLabel());
48 this.progress = new JProgressBar();
49 this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
50 panel.add(this.labelText, BorderLayout.NORTH);
51 panel.add(this.progress, BorderLayout.CENTER);
52 panel.setPreferredSize(ScaleUtil.getDimension(360, 50));
53
54 // show the frame
55 pane.doLayout();
56 this.frame.setSize(ScaleUtil.getDimension(400, 120));
57 this.frame.setResizable(false);
58 this.frame.setLocationRelativeTo(parent);
59 this.frame.setVisible(true);
60 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
61 }
62
63 public static CompletableFuture<Void> runOffThread(final JFrame parent, final ProgressRunnable runnable) {
64 CompletableFuture<Void> future = new CompletableFuture<>();
65 new Thread(() ->
66 {
67 try (ProgressDialog progress = new ProgressDialog(parent)) {
68 runnable.run(progress);
69 future.complete(null);
70 } catch (Exception ex) {
71 future.completeExceptionally(ex);
72 throw new Error(ex);
73 }
74 }).start();
75 return future;
76 }
77
78 @Override
79 public void close() {
80 this.frame.dispose();
81 }
82
83 @Override
84 public void init(int totalWork, String title) {
85 this.labelTitle.setText(title);
86 this.progress.setMinimum(0);
87 this.progress.setMaximum(totalWork);
88 this.progress.setValue(0);
89 }
90
91 @Override
92 public void step(int numDone, String message) {
93 this.labelText.setText(message);
94 if (numDone != -1) {
95 this.progress.setValue(numDone);
96 this.progress.setIndeterminate(false);
97 } else {
98 this.progress.setIndeterminate(true);
99 }
100
101 // update the frame
102 this.frame.validate();
103 this.frame.repaint();
104 }
105
106 public interface ProgressRunnable {
107 void run(ProgressListener listener) throws Exception;
108 }
109}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
deleted file mode 100644
index b283a37..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
+++ /dev/null
@@ -1,261 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import java.awt.BorderLayout;
15import java.awt.Color;
16import java.awt.FlowLayout;
17import java.awt.Font;
18import java.awt.event.*;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.List;
22
23import javax.swing.*;
24import javax.swing.event.DocumentEvent;
25import javax.swing.event.DocumentListener;
26
27import cuchaz.enigma.gui.Gui;
28import cuchaz.enigma.gui.GuiController;
29import cuchaz.enigma.gui.util.AbstractListCellRenderer;
30import cuchaz.enigma.gui.util.ScaleUtil;
31import cuchaz.enigma.translation.representation.entry.ClassEntry;
32import cuchaz.enigma.utils.I18n;
33import cuchaz.enigma.utils.search.SearchEntry;
34import cuchaz.enigma.utils.search.SearchUtil;
35
36public class SearchDialog {
37
38 private final JTextField searchField;
39 private DefaultListModel<SearchEntryImpl> classListModel;
40 private final JList<SearchEntryImpl> classList;
41 private final JDialog dialog;
42
43 private final Gui parent;
44 private final SearchUtil<SearchEntryImpl> su;
45 private SearchUtil.SearchControl currentSearch;
46
47 public SearchDialog(Gui parent) {
48 this.parent = parent;
49
50 su = new SearchUtil<>();
51
52 dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true);
53 JPanel contentPane = new JPanel();
54 contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4));
55 contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4)));
56
57 searchField = new JTextField();
58 searchField.getDocument().addDocumentListener(new DocumentListener() {
59
60 @Override
61 public void insertUpdate(DocumentEvent e) {
62 updateList();
63 }
64
65 @Override
66 public void removeUpdate(DocumentEvent e) {
67 updateList();
68 }
69
70 @Override
71 public void changedUpdate(DocumentEvent e) {
72 updateList();
73 }
74
75 });
76 searchField.addKeyListener(new KeyAdapter() {
77 @Override
78 public void keyPressed(KeyEvent e) {
79 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
80 int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1;
81 classList.setSelectedIndex(next);
82 classList.ensureIndexIsVisible(next);
83 } else if (e.getKeyCode() == KeyEvent.VK_UP) {
84 int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1;
85 classList.setSelectedIndex(prev);
86 classList.ensureIndexIsVisible(prev);
87 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
88 close();
89 }
90 }
91 });
92 searchField.addActionListener(e -> openSelected());
93 contentPane.add(searchField, BorderLayout.NORTH);
94
95 classListModel = new DefaultListModel<>();
96 classList = new JList<>();
97 classList.setModel(classListModel);
98 classList.setCellRenderer(new ListCellRendererImpl());
99 classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
100 classList.addMouseListener(new MouseAdapter() {
101 @Override
102 public void mouseClicked(MouseEvent mouseEvent) {
103 if (mouseEvent.getClickCount() >= 2) {
104 int idx = classList.locationToIndex(mouseEvent.getPoint());
105 SearchEntryImpl entry = classList.getModel().getElementAt(idx);
106 openEntry(entry);
107 }
108 }
109 });
110 contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
111
112 JPanel buttonBar = new JPanel();
113 buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT));
114 JButton open = new JButton(I18n.translate("prompt.open"));
115 open.addActionListener(event -> openSelected());
116 buttonBar.add(open);
117 JButton cancel = new JButton(I18n.translate("prompt.cancel"));
118 cancel.addActionListener(event -> close());
119 buttonBar.add(cancel);
120 contentPane.add(buttonBar, BorderLayout.SOUTH);
121
122 // apparently the class list doesn't update by itself when the list
123 // state changes and the dialog is hidden
124 dialog.addComponentListener(new ComponentAdapter() {
125 @Override
126 public void componentShown(ComponentEvent e) {
127 classList.updateUI();
128 }
129 });
130
131 dialog.setContentPane(contentPane);
132 dialog.setSize(ScaleUtil.getDimension(400, 500));
133 dialog.setLocationRelativeTo(parent.getFrame());
134 }
135
136 public void show() {
137 su.clear();
138 parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream()
139 .filter(e -> !e.isInnerClass())
140 .map(e -> SearchEntryImpl.from(e, parent.getController()))
141 .map(SearchUtil.Entry::from)
142 .sequential()
143 .forEach(su::add);
144
145 updateList();
146
147 searchField.requestFocus();
148 searchField.selectAll();
149
150 dialog.setVisible(true);
151 }
152
153 private void openSelected() {
154 SearchEntryImpl selectedValue = classList.getSelectedValue();
155 if (selectedValue != null) {
156 openEntry(selectedValue);
157 }
158 }
159
160 private void openEntry(SearchEntryImpl e) {
161 close();
162 su.hit(e);
163 parent.getController().navigateTo(e.obf);
164 if (e.deobf != null) {
165 parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf);
166 } else {
167 parent.getObfPanel().obfClasses.setSelectionClass(e.obf);
168 }
169 }
170
171 private void close() {
172 dialog.setVisible(false);
173 }
174
175 // Updates the list of class names
176 private void updateList() {
177 if (currentSearch != null) currentSearch.stop();
178
179 DefaultListModel<SearchEntryImpl> classListModel = new DefaultListModel<>();
180 this.classListModel = classListModel;
181 classList.setModel(classListModel);
182
183 currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx)));
184 }
185
186 public void dispose() {
187 dialog.dispose();
188 }
189
190 private static final class SearchEntryImpl implements SearchEntry {
191
192 public final ClassEntry obf;
193 public final ClassEntry deobf;
194
195 private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) {
196 this.obf = obf;
197 this.deobf = deobf;
198 }
199
200 @Override
201 public List<String> getSearchableNames() {
202 if (deobf != null) {
203 return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName());
204 } else {
205 return Collections.singletonList(obf.getSimpleName());
206 }
207 }
208
209 @Override
210 public String getIdentifier() {
211 return obf.getFullName();
212 }
213
214 @Override
215 public String toString() {
216 return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf);
217 }
218
219 public static SearchEntryImpl from(ClassEntry e, GuiController controller) {
220 ClassEntry deobf = controller.project.getMapper().deobfuscate(e);
221 if (deobf.equals(e)) deobf = null;
222 return new SearchEntryImpl(e, deobf);
223 }
224
225 }
226
227 private static final class ListCellRendererImpl extends AbstractListCellRenderer<SearchEntryImpl> {
228
229 private final JLabel mainName;
230 private final JLabel secondaryName;
231
232 public ListCellRendererImpl() {
233 this.setLayout(new BorderLayout());
234
235 mainName = new JLabel();
236 this.add(mainName, BorderLayout.WEST);
237
238 secondaryName = new JLabel();
239 secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC));
240 secondaryName.setForeground(Color.GRAY);
241 this.add(secondaryName, BorderLayout.EAST);
242 }
243
244 @Override
245 public void updateUiForEntry(JList<? extends SearchEntryImpl> list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) {
246 if (value.deobf == null) {
247 mainName.setText(value.obf.getSimpleName());
248 mainName.setToolTipText(value.obf.getFullName());
249 secondaryName.setText("");
250 secondaryName.setToolTipText("");
251 } else {
252 mainName.setText(value.deobf.getSimpleName());
253 mainName.setToolTipText(value.deobf.getFullName());
254 secondaryName.setText(value.obf.getSimpleName());
255 secondaryName.setToolTipText(value.obf.getFullName());
256 }
257 }
258
259 }
260
261}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java
deleted file mode 100644
index 868eba7..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java
+++ /dev/null
@@ -1,82 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import java.awt.BorderLayout;
4import java.util.Arrays;
5import java.util.Locale;
6import java.util.Map;
7import java.util.Set;
8import java.util.stream.Collectors;
9
10import javax.swing.JButton;
11import javax.swing.JCheckBox;
12import javax.swing.JFrame;
13import javax.swing.JPanel;
14
15import cuchaz.enigma.gui.Gui;
16import cuchaz.enigma.gui.stats.StatsMember;
17import cuchaz.enigma.gui.util.ScaleUtil;
18import cuchaz.enigma.utils.I18n;
19
20public class StatsDialog {
21
22 public static void show(Gui gui) {
23 // init frame
24 JFrame frame = new JFrame(I18n.translate("menu.file.stats.title"));
25 JPanel checkboxesPanel = new JPanel();
26 JPanel buttonPanel = new JPanel();
27 frame.setLayout(new BorderLayout());
28 frame.add(BorderLayout.NORTH, checkboxesPanel);
29 frame.add(BorderLayout.SOUTH, buttonPanel);
30
31 // show checkboxes
32 Map<StatsMember, JCheckBox> checkboxes = Arrays
33 .stream(StatsMember.values())
34 .collect(Collectors.toMap(m -> m, m -> {
35 JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT)));
36 checkboxesPanel.add(checkbox);
37 return checkbox;
38 }));
39
40 // show generate button
41 JButton button = new JButton(I18n.translate("menu.file.stats.generate"));
42 buttonPanel.add(button);
43 button.setEnabled(false);
44 button.addActionListener(action -> {
45 frame.dispose();
46 generateStats(gui, checkboxes);
47 });
48
49 // add action listener to each checkbox
50 checkboxes.entrySet().forEach(checkbox -> {
51 checkbox.getValue().addActionListener(action -> {
52 if (!button.isEnabled()) {
53 button.setEnabled(true);
54 } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) {
55 button.setEnabled(false);
56 }
57 });
58 });
59
60 // show the frame
61 frame.pack();
62 frame.setVisible(true);
63 frame.setSize(ScaleUtil.getDimension(500, 120));
64 frame.setResizable(false);
65 frame.setLocationRelativeTo(gui.getFrame());
66 }
67
68 private static void generateStats(Gui gui, Map<StatsMember, JCheckBox> checkboxes) {
69 // get members from selected checkboxes
70 Set<StatsMember> includedMembers = checkboxes
71 .entrySet()
72 .stream()
73 .filter(entry -> entry.getValue().isSelected())
74 .map(Map.Entry::getKey)
75 .collect(Collectors.toSet());
76
77 // checks if a projet is open
78 if (gui.getController().project != null) {
79 gui.getController().openStats(includedMembers);
80 }
81 }
82}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java
deleted file mode 100644
index fb497b1..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.event.MouseEvent;
4
5import javax.swing.JTabbedPane;
6
7public class CollapsibleTabbedPane extends JTabbedPane {
8
9 public CollapsibleTabbedPane() {
10 }
11
12 public CollapsibleTabbedPane(int tabPlacement) {
13 super(tabPlacement);
14 }
15
16 public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) {
17 super(tabPlacement, tabLayoutPolicy);
18 }
19
20 @Override
21 protected void processMouseEvent(MouseEvent e) {
22 int id = e.getID();
23 if (id == MouseEvent.MOUSE_PRESSED) {
24 if (!isEnabled()) return;
25 int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY());
26 if (tabIndex >= 0 && isEnabledAt(tabIndex)) {
27 if (tabIndex == getSelectedIndex()) {
28 if (isFocusOwner() && isRequestFocusEnabled()) {
29 requestFocus();
30 } else {
31 setSelectedIndex(-1);
32 }
33 return;
34 }
35 }
36 }
37 super.processMouseEvent(e);
38 }
39
40}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
deleted file mode 100644
index dc2cf8f..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ /dev/null
@@ -1,386 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import cuchaz.enigma.config.Config;
4import cuchaz.enigma.config.Themes;
5import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.dialog.AboutDialog;
7import cuchaz.enigma.gui.dialog.ChangeDialog;
8import cuchaz.enigma.gui.dialog.ConnectToServerDialog;
9import cuchaz.enigma.gui.dialog.CreateServerDialog;
10import cuchaz.enigma.gui.dialog.StatsDialog;
11import cuchaz.enigma.gui.util.ScaleUtil;
12import cuchaz.enigma.translation.mapping.serde.MappingFormat;
13import cuchaz.enigma.utils.I18n;
14import cuchaz.enigma.utils.Pair;
15
16import java.awt.Desktop;
17import java.awt.event.InputEvent;
18import java.awt.event.KeyEvent;
19import java.io.File;
20import java.io.IOException;
21import java.net.URISyntaxException;
22import java.net.URL;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.nio.file.Paths;
26import java.util.*;
27import java.util.List;
28import java.util.stream.Collectors;
29import java.util.stream.IntStream;
30import javax.swing.*;
31
32public class MenuBar extends JMenuBar {
33
34 public final JMenuItem closeJarMenu;
35 public final List<JMenuItem> openMappingsMenus;
36 public final JMenuItem saveMappingsMenu;
37 public final List<JMenuItem> saveMappingsMenus;
38 public final JMenuItem closeMappingsMenu;
39 public final JMenuItem dropMappingsMenu;
40 public final JMenuItem exportSourceMenu;
41 public final JMenuItem exportJarMenu;
42 public final JMenuItem connectToServerMenu;
43 public final JMenuItem startServerMenu;
44 private final Gui gui;
45
46 public MenuBar(Gui gui) {
47 this.gui = gui;
48
49 /*
50 * File menu
51 */
52 {
53 JMenu menu = new JMenu(I18n.translate("menu.file"));
54 this.add(menu);
55 {
56 JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.open"));
57 menu.add(item);
58 item.addActionListener(event -> {
59 this.gui.jarFileChooser.setVisible(true);
60 String file = this.gui.jarFileChooser.getFile();
61 // checks if the file name is not empty
62 if (file != null) {
63 Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file);
64 // checks if the file name corresponds to an existing file
65 if (Files.exists(path)) {
66 gui.getController().openJar(path);
67 }
68 }
69 });
70 }
71 {
72 JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.close"));
73 menu.add(item);
74 item.addActionListener(event -> this.gui.getController().closeJar());
75 this.closeJarMenu = item;
76 }
77 menu.addSeparator();
78 JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open"));
79 menu.add(openMenu);
80 {
81 openMappingsMenus = new ArrayList<>();
82 for (MappingFormat format : MappingFormat.values()) {
83 if (format.getReader() != null) {
84 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
85 openMenu.add(item);
86 item.addActionListener(event -> {
87 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
88 File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile();
89 this.gui.getController().openMappings(format, selectedFile.toPath());
90 }
91 });
92 openMappingsMenus.add(item);
93 }
94 }
95 }
96 {
97 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.save"));
98 menu.add(item);
99 item.addActionListener(event -> {
100 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
101 });
102 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
103 this.saveMappingsMenu = item;
104 }
105 JMenu saveMenu = new JMenu(I18n.translate("menu.file.mappings.save_as"));
106 menu.add(saveMenu);
107 {
108 saveMappingsMenus = new ArrayList<>();
109 for (MappingFormat format : MappingFormat.values()) {
110 if (format.getWriter() != null) {
111 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
112 saveMenu.add(item);
113 item.addActionListener(event -> {
114 // TODO: Use a specific file chooser for it
115 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
116 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format);
117 this.saveMappingsMenu.setEnabled(true);
118 }
119 });
120 saveMappingsMenus.add(item);
121 }
122 }
123 }
124 {
125 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.close"));
126 menu.add(item);
127 item.addActionListener(event -> {
128 if (this.gui.getController().isDirty()) {
129 this.gui.showDiscardDiag((response -> {
130 if (response == JOptionPane.YES_OPTION) {
131 gui.saveMapping();
132 this.gui.getController().closeMappings();
133 } else if (response == JOptionPane.NO_OPTION)
134 this.gui.getController().closeMappings();
135 return null;
136 }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel"));
137 } else
138 this.gui.getController().closeMappings();
139
140 });
141 this.closeMappingsMenu = item;
142 }
143 {
144 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.drop"));
145 menu.add(item);
146 item.addActionListener(event -> this.gui.getController().dropMappings());
147 this.dropMappingsMenu = item;
148 }
149 menu.addSeparator();
150 {
151 JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.source"));
152 menu.add(item);
153 item.addActionListener(event -> {
154 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
155 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath());
156 }
157 });
158 this.exportSourceMenu = item;
159 }
160 {
161 JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.jar"));
162 menu.add(item);
163 item.addActionListener(event -> {
164 this.gui.exportJarFileChooser.setVisible(true);
165 if (this.gui.exportJarFileChooser.getFile() != null) {
166 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile());
167 this.gui.getController().exportJar(path);
168 }
169 });
170 this.exportJarMenu = item;
171 }
172 menu.addSeparator();
173 {
174 JMenuItem stats = new JMenuItem(I18n.translate("menu.file.stats"));
175 menu.add(stats);
176 stats.addActionListener(event -> StatsDialog.show(this.gui));
177 }
178 menu.addSeparator();
179 {
180 JMenuItem item = new JMenuItem(I18n.translate("menu.file.exit"));
181 menu.add(item);
182 item.addActionListener(event -> this.gui.close());
183 }
184 }
185
186 /*
187 * Decompiler menu
188 */
189 {
190 JMenu menu = new JMenu(I18n.translate("menu.decompiler"));
191 this.add(menu);
192
193 ButtonGroup decompilerGroup = new ButtonGroup();
194
195 for (Config.Decompiler decompiler : Config.Decompiler.values()) {
196 JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name);
197 decompilerGroup.add(decompilerButton);
198 if (decompiler.equals(Config.getInstance().decompiler)) {
199 decompilerButton.setSelected(true);
200 }
201 menu.add(decompilerButton);
202 decompilerButton.addActionListener(event -> {
203 gui.getController().setDecompiler(decompiler.service);
204
205 try {
206 Config.getInstance().decompiler = decompiler;
207 Config.getInstance().saveConfig();
208 } catch (IOException e) {
209 throw new RuntimeException(e);
210 }
211 });
212 }
213 }
214
215 /*
216 * View menu
217 */
218 {
219 JMenu menu = new JMenu(I18n.translate("menu.view"));
220 this.add(menu);
221 {
222 JMenu themes = new JMenu(I18n.translate("menu.view.themes"));
223 menu.add(themes);
224 ButtonGroup themeGroup = new ButtonGroup();
225 for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) {
226 JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT)));
227 themeGroup.add(themeButton);
228 if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) {
229 themeButton.setSelected(true);
230 }
231 themes.add(themeButton);
232 themeButton.addActionListener(event -> Themes.setLookAndFeel(gui, lookAndFeel));
233 }
234 }
235 {
236 JMenu languages = new JMenu(I18n.translate("menu.view.languages"));
237 menu.add(languages);
238 ButtonGroup languageGroup = new ButtonGroup();
239 for (String lang : I18n.getAvailableLanguages()) {
240 JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang));
241 languageGroup.add(languageButton);
242 if (lang.equals(Config.getInstance().language)) {
243 languageButton.setSelected(true);
244 }
245 languages.add(languageButton);
246 languageButton.addActionListener(event -> {
247 I18n.setLanguage(lang);
248 ChangeDialog.show(this.gui);
249 });
250 }
251 }
252 {
253 JMenu scale = new JMenu(I18n.translate("menu.view.scale"));
254 {
255 ButtonGroup scaleGroup = new ButtonGroup();
256 Map<Float, JRadioButtonMenuItem> map = IntStream.of(100, 125, 150, 175, 200)
257 .mapToObj(scaleFactor -> {
258 float realScaleFactor = scaleFactor / 100f;
259 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor));
260 menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor));
261 menuItem.addActionListener(event -> ChangeDialog.show(this.gui));
262 scaleGroup.add(menuItem);
263 scale.add(menuItem);
264 return new Pair<>(realScaleFactor, menuItem);
265 })
266 .collect(Collectors.toMap(x -> x.a, x -> x.b));
267
268 JMenuItem customScale = new JMenuItem(I18n.translate("menu.view.scale.custom"));
269 customScale.addActionListener(event -> {
270 String answer = (String) JOptionPane.showInputDialog(gui.getFrame(), I18n.translate("menu.view.scale.custom.title"), I18n.translate("menu.view.scale.custom.title"),
271 JOptionPane.QUESTION_MESSAGE, null, null, Float.toString(ScaleUtil.getScaleFactor() * 100));
272 if (answer == null) return;
273 float newScale = 1.0f;
274 try {
275 newScale = Float.parseFloat(answer) / 100f;
276 } catch (NumberFormatException ignored) {
277 }
278 ScaleUtil.setScaleFactor(newScale);
279 ChangeDialog.show(this.gui);
280 });
281 scale.add(customScale);
282 ScaleUtil.addListener((newScale, _oldScale) -> {
283 JRadioButtonMenuItem mi = map.get(newScale);
284 if (mi != null) {
285 mi.setSelected(true);
286 } else {
287 scaleGroup.clearSelection();
288 }
289 });
290 JRadioButtonMenuItem mi = map.get(ScaleUtil.getScaleFactor());
291 if (mi != null) {
292 mi.setSelected(true);
293 }
294 }
295 menu.add(scale);
296 }
297 menu.addSeparator();
298 {
299 JMenuItem search = new JMenuItem(I18n.translate("menu.view.search"));
300 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK));
301 menu.add(search);
302 search.addActionListener(event -> {
303 if (this.gui.getController().project != null) {
304 this.gui.getSearchDialog().show();
305 }
306 });
307 }
308 }
309
310 /*
311 * Collab menu
312 */
313 {
314 JMenu menu = new JMenu(I18n.translate("menu.collab"));
315 this.add(menu);
316 {
317 JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect"));
318 menu.add(item);
319 item.addActionListener(event -> {
320 if (this.gui.getController().getClient() != null) {
321 this.gui.getController().disconnectIfConnected(null);
322 return;
323 }
324 ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame());
325 if (result == null) {
326 return;
327 }
328 this.gui.getController().disconnectIfConnected(null);
329 try {
330 this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword());
331 } catch (IOException e) {
332 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE);
333 this.gui.getController().disconnectIfConnected(null);
334 }
335 Arrays.fill(result.getPassword(), (char)0);
336 });
337 this.connectToServerMenu = item;
338 }
339 {
340 JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start"));
341 menu.add(item);
342 item.addActionListener(event -> {
343 if (this.gui.getController().getServer() != null) {
344 this.gui.getController().disconnectIfConnected(null);
345 return;
346 }
347 CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame());
348 if (result == null) {
349 return;
350 }
351 this.gui.getController().disconnectIfConnected(null);
352 try {
353 this.gui.getController().createServer(result.getPort(), result.getPassword());
354 } catch (IOException e) {
355 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE);
356 this.gui.getController().disconnectIfConnected(null);
357 }
358 });
359 this.startServerMenu = item;
360 }
361 }
362
363 /*
364 * Help menu
365 */
366 {
367 JMenu menu = new JMenu(I18n.translate("menu.help"));
368 this.add(menu);
369 {
370 JMenuItem item = new JMenuItem(I18n.translate("menu.help.about"));
371 menu.add(item);
372 item.addActionListener(event -> AboutDialog.show(this.gui.getFrame()));
373 }
374 {
375 JMenuItem item = new JMenuItem(I18n.translate("menu.help.github"));
376 menu.add(item);
377 item.addActionListener(event -> {
378 try {
379 Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI());
380 } catch (URISyntaxException | IOException ignored) {
381 }
382 });
383 }
384 }
385 }
386}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
deleted file mode 100644
index b92041c..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
+++ /dev/null
@@ -1,125 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import cuchaz.enigma.gui.Gui;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.event.InputEvent;
8import java.awt.event.KeyEvent;
9
10public class PopupMenuBar extends JPopupMenu {
11
12 public final JMenuItem renameMenu;
13 public final JMenuItem editJavadocMenu;
14 public final JMenuItem showInheritanceMenu;
15 public final JMenuItem showImplementationsMenu;
16 public final JMenuItem showCallsMenu;
17 public final JMenuItem showCallsSpecificMenu;
18 public final JMenuItem openEntryMenu;
19 public final JMenuItem openPreviousMenu;
20 public final JMenuItem openNextMenu;
21 public final JMenuItem toggleMappingMenu;
22
23 public PopupMenuBar(Gui gui) {
24 {
25 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename"));
26 menu.addActionListener(event -> gui.startRename());
27 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK));
28 menu.setEnabled(false);
29 this.add(menu);
30 this.renameMenu = menu;
31 }
32 {
33 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc"));
34 menu.addActionListener(event -> gui.startDocChange());
35 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK));
36 menu.setEnabled(false);
37 this.add(menu);
38 this.editJavadocMenu = menu;
39 }
40 {
41 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance"));
42 menu.addActionListener(event -> gui.showInheritance());
43 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK));
44 menu.setEnabled(false);
45 this.add(menu);
46 this.showInheritanceMenu = menu;
47 }
48 {
49 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations"));
50 menu.addActionListener(event -> gui.showImplementations());
51 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK));
52 menu.setEnabled(false);
53 this.add(menu);
54 this.showImplementationsMenu = menu;
55 }
56 {
57 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls"));
58 menu.addActionListener(event -> gui.showCalls(true));
59 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
60 menu.setEnabled(false);
61 this.add(menu);
62 this.showCallsMenu = menu;
63 }
64 {
65 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific"));
66 menu.addActionListener(event -> gui.showCalls(false));
67 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK));
68 menu.setEnabled(false);
69 this.add(menu);
70 this.showCallsSpecificMenu = menu;
71 }
72 {
73 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration"));
74 menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry));
75 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
76 menu.setEnabled(false);
77 this.add(menu);
78 this.openEntryMenu = menu;
79 }
80 {
81 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.back"));
82 menu.addActionListener(event -> gui.getController().openPreviousReference());
83 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK));
84 menu.setEnabled(false);
85 this.add(menu);
86 this.openPreviousMenu = menu;
87 }
88 {
89 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.forward"));
90 menu.addActionListener(event -> gui.getController().openNextReference());
91 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK));
92 menu.setEnabled(false);
93 this.add(menu);
94 this.openNextMenu = menu;
95 }
96 {
97 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated"));
98 menu.addActionListener(event -> gui.toggleMapping());
99 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
100 menu.setEnabled(false);
101 this.add(menu);
102 this.toggleMappingMenu = menu;
103 }
104 {
105 this.add(new JSeparator());
106 }
107 {
108 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in"));
109 menu.addActionListener(event -> gui.editor.offsetEditorZoom(2));
110 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK));
111 this.add(menu);
112 }
113 {
114 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out"));
115 menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2));
116 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
117 this.add(menu);
118 }
119 {
120 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset"));
121 menu.addActionListener(event -> gui.editor.resetEditorZoom());
122 this.add(menu);
123 }
124 }
125}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
deleted file mode 100644
index f5f6628..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserAny extends JFileChooser {
6 public FileChooserAny() {
7 this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
8 this.setAcceptAllFileFilterUsed(false);
9 }
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
deleted file mode 100644
index cea11a6..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserFile extends JFileChooser {
6 public FileChooserFile() {
7 }
8}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
deleted file mode 100644
index c16e0af..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserFolder extends JFileChooser {
6
7 public FileChooserFolder() {
8 this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
9 this.setAcceptAllFileFilterUsed(false);
10 }
11}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
deleted file mode 100644
index cef6494..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ /dev/null
@@ -1,69 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.highlight;
13
14import cuchaz.enigma.config.Config;
15
16import javax.swing.text.BadLocationException;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19import java.awt.*;
20
21public class BoxHighlightPainter implements Highlighter.HighlightPainter {
22 private Color fillColor;
23 private Color borderColor;
24
25 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
26 this.fillColor = fillColor;
27 this.borderColor = borderColor;
28 }
29
30 public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) {
31 return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null);
32 }
33
34 public static Rectangle getBounds(JTextComponent text, int start, int end) {
35 try {
36 // determine the bounds of the text
37 Rectangle startRect = text.modelToView(start);
38 Rectangle endRect = text.modelToView(end);
39 Rectangle bounds = startRect.union(endRect);
40
41 // adjust the box so it looks nice
42 bounds.x -= 2;
43 bounds.width += 2;
44 bounds.y += 1;
45 bounds.height -= 2;
46
47 return bounds;
48 } catch (BadLocationException ex) {
49 // don't care... just return something
50 return new Rectangle(0, 0, 0, 0);
51 }
52 }
53
54 @Override
55 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
56 Rectangle bounds = getBounds(text, start, end);
57
58 // fill the area
59 if (this.fillColor != null) {
60 g.setColor(this.fillColor);
61 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
62 }
63
64 // draw a box around the area
65 g.setColor(this.borderColor);
66 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
67 }
68
69}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
deleted file mode 100644
index 81a70a9..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
+++ /dev/null
@@ -1,31 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.highlight;
13
14import cuchaz.enigma.config.Config;
15
16import javax.swing.text.Highlighter;
17import javax.swing.text.JTextComponent;
18import java.awt.*;
19
20public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
21
22 @Override
23 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
24 // draw a thick border
25 Graphics2D g2d = (Graphics2D) g;
26 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
27 g2d.setColor(new Color(Config.getInstance().selectionHighlightColor));
28 g2d.setStroke(new BasicStroke(2.0f));
29 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
30 }
31}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
deleted file mode 100644
index ae23f32..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui.highlight;
2
3public enum TokenHighlightType {
4 OBFUSCATED,
5 DEOBFUSCATED,
6 PROPOSED
7}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
deleted file mode 100644
index 922f8f2..0000000
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.node;
13
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15
16import javax.swing.tree.DefaultMutableTreeNode;
17
18public class ClassSelectorClassNode extends DefaultMutableTreeNode {
19
20 private final ClassEntry obfEntry;
21 private ClassEntry classEntry;
22
23 public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) {
24 this.obfEntry = obfEntry;
25 this.classEntry = classEntry;
26 this.setUserObject(classEntry);
27 }
28
29 public ClassEntry getObfEntry() {
30 return obfEntry;
31 }
32
33 public ClassEntry getClassEntry() {
34 return this.classEntry;
35 }
36
37 @Override
38 public String toString() {
39 return this.classEntry.getSimpleName();
40 }
41
42 @Override
43 public boolean equals(Object other) {
44 return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other);
45 }
46
47 @Override
48 public int hashCode() {
49 return 17 + (classEntry != null ? classEntry.hashCode() : 0);
50 }
51
52 @Override
53 public Object getUserObject() {
54 return classEntry;
55 }
56
57 @Override
58 public void setUserObject(Object userObject) {
59 String packageName = "";
60 if (classEntry.getPackageName() != null)
61 packageName = classEntry.getPackageName() + "/";
62 if (userObject instanceof String)
63 this.classEntry = new ClassEntry(packageName + userObject);
64 else if (userObject instanceof ClassEntry)
65 this.classEntry = (ClassEntry) userObject;
66 super.setUserObject(classEntry);
67 }
68
69 public boolean equals(ClassSelectorClassNode other) {
70 return this.classEntry.equals(other.classEntry);
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
deleted file mode 100644
index caa985c..0000000
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ /dev/null
@@ -1,58 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.node;
13
14import javax.swing.tree.DefaultMutableTreeNode;
15
16public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
17
18 private String packageName;
19
20 public ClassSelectorPackageNode(String packageName) {
21 this.packageName = packageName != null ? packageName : "(none)";
22 }
23
24 public String getPackageName() {
25 return packageName;
26 }
27
28 @Override
29 public Object getUserObject() {
30 return packageName;
31 }
32
33 @Override
34 public void setUserObject(Object userObject) {
35 if (userObject instanceof String)
36 this.packageName = (String) userObject;
37 super.setUserObject(userObject);
38 }
39
40 @Override
41 public String toString() {
42 return !packageName.equals("(none)") ? this.packageName : "(none)";
43 }
44
45 @Override
46 public boolean equals(Object other) {
47 return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other);
48 }
49
50 @Override
51 public int hashCode() {
52 return packageName.hashCode();
53 }
54
55 public boolean equals(ClassSelectorPackageNode other) {
56 return other != null && this.packageName.equals(other.packageName);
57 }
58}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
deleted file mode 100644
index c24226b..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
+++ /dev/null
@@ -1,26 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.utils.I18n;
6
7import javax.swing.*;
8import java.awt.*;
9
10public class PanelDeobf extends JPanel {
11
12 public final ClassSelector deobfClasses;
13 private final Gui gui;
14
15 public PanelDeobf(Gui gui) {
16 this.gui = gui;
17
18 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true);
19 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo);
20 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename);
21
22 this.setLayout(new BorderLayout());
23 this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH);
24 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER);
25 }
26}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
deleted file mode 100644
index 8637afd..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
+++ /dev/null
@@ -1,171 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.config.Config;
6import cuchaz.enigma.gui.BrowserCaret;
7import cuchaz.enigma.gui.Gui;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.gui.util.ScaleUtil;
11
12import javax.swing.*;
13import java.awt.*;
14import java.awt.event.KeyAdapter;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseAdapter;
17import java.awt.event.MouseEvent;
18
19public class PanelEditor extends JEditorPane {
20 private boolean mouseIsPressed = false;
21 public int fontSize = 12;
22
23 public PanelEditor(Gui gui) {
24 this.setEditable(false);
25 this.setSelectionColor(new Color(31, 46, 90));
26 this.setCaret(new BrowserCaret());
27 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
28 this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed));
29 final PanelEditor self = this;
30 this.addMouseListener(new MouseAdapter() {
31 @Override
32 public void mousePressed(MouseEvent mouseEvent) {
33 mouseIsPressed = true;
34 }
35
36 @Override
37 public void mouseReleased(MouseEvent e) {
38 switch (e.getButton()) {
39 case MouseEvent.BUTTON3: // Right click
40 self.setCaretPosition(self.viewToModel(e.getPoint()));
41 break;
42
43 case 4: // Back navigation
44 gui.getController().openPreviousReference();
45 break;
46
47 case 5: // Forward navigation
48 gui.getController().openNextReference();
49 break;
50 }
51 mouseIsPressed = false;
52 }
53 });
54 this.addKeyListener(new KeyAdapter() {
55 @Override
56 public void keyPressed(KeyEvent event) {
57 if (event.isControlDown()) {
58 gui.setShouldNavigateOnClick(false);
59 switch (event.getKeyCode()) {
60 case KeyEvent.VK_I:
61 gui.popupMenu.showInheritanceMenu.doClick();
62 break;
63
64 case KeyEvent.VK_M:
65 gui.popupMenu.showImplementationsMenu.doClick();
66 break;
67
68 case KeyEvent.VK_N:
69 gui.popupMenu.openEntryMenu.doClick();
70 break;
71
72 case KeyEvent.VK_P:
73 gui.popupMenu.openPreviousMenu.doClick();
74 break;
75
76 case KeyEvent.VK_E:
77 gui.popupMenu.openNextMenu.doClick();
78 break;
79
80 case KeyEvent.VK_C:
81 if (event.isShiftDown()) {
82 gui.popupMenu.showCallsSpecificMenu.doClick();
83 } else {
84 gui.popupMenu.showCallsMenu.doClick();
85 }
86 break;
87
88 case KeyEvent.VK_O:
89 gui.popupMenu.toggleMappingMenu.doClick();
90 break;
91
92 case KeyEvent.VK_R:
93 gui.popupMenu.renameMenu.doClick();
94 break;
95
96 case KeyEvent.VK_D:
97 gui.popupMenu.editJavadocMenu.doClick();
98 break;
99
100 case KeyEvent.VK_F5:
101 gui.getController().refreshCurrentClass();
102 break;
103
104 case KeyEvent.VK_F:
105 // prevent navigating on click when quick find activated
106 break;
107
108 case KeyEvent.VK_ADD:
109 case KeyEvent.VK_EQUALS:
110 case KeyEvent.VK_PLUS:
111 self.offsetEditorZoom(2);
112 break;
113 case KeyEvent.VK_SUBTRACT:
114 case KeyEvent.VK_MINUS:
115 self.offsetEditorZoom(-2);
116 break;
117
118 default:
119 gui.setShouldNavigateOnClick(true); // CTRL
120 break;
121 }
122 }
123 }
124
125 @Override
126 public void keyTyped(KeyEvent event) {
127 if (!gui.popupMenu.renameMenu.isEnabled()) return;
128
129 if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) {
130 EnigmaProject project = gui.getController().project;
131 EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference);
132 Entry<?> entry = reference.getNameableEntry();
133
134 String name = String.valueOf(event.getKeyChar());
135 if (entry instanceof ClassEntry && ((ClassEntry) entry).getParent() == null) {
136 String packageName = ((ClassEntry) entry).getPackageName();
137 if (packageName != null) {
138 name = packageName + "/" + name;
139 }
140 }
141
142 gui.popupMenu.renameMenu.doClick();
143 gui.renameTextField.setText(name);
144 }
145 }
146
147 @Override
148 public void keyReleased(KeyEvent event) {
149 gui.setShouldNavigateOnClick(event.isControlDown());
150 }
151 });
152 }
153
154 public void offsetEditorZoom(int zoomAmount) {
155 int newResult = this.fontSize + zoomAmount;
156 if (newResult > 8 && newResult < 72) {
157 this.fontSize = newResult;
158 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
159 }
160 }
161
162 public void resetEditorZoom() {
163 this.fontSize = 12;
164 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
165 }
166
167 @Override
168 public Color getCaretColor() {
169 return new Color(Config.getInstance().caretColor);
170 }
171}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
deleted file mode 100644
index de069bc..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.Gui;
4import cuchaz.enigma.utils.I18n;
5import cuchaz.enigma.gui.util.ScaleUtil;
6import cuchaz.enigma.utils.Utils;
7
8import javax.swing.*;
9import java.awt.*;
10
11public class PanelIdentifier extends JPanel {
12
13 private final Gui gui;
14
15 public PanelIdentifier(Gui gui) {
16 this.gui = gui;
17
18 this.setLayout(new GridLayout(4, 1, 0, 0));
19 this.setPreferredSize(ScaleUtil.getDimension(0, 100));
20 this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier")));
21 }
22
23 public void clearReference() {
24 this.removeAll();
25 JLabel label = new JLabel(I18n.translate("info_panel.identifier.none"));
26 Utils.unboldLabel(label);
27 label.setHorizontalAlignment(JLabel.CENTER);
28 this.add(label);
29
30 gui.redraw();
31 }
32}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
deleted file mode 100644
index dd7f9f9..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
+++ /dev/null
@@ -1,37 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.utils.I18n;
7
8import javax.swing.*;
9import java.awt.*;
10import java.util.Comparator;
11
12public class PanelObf extends JPanel {
13
14 public final ClassSelector obfClasses;
15 private final Gui gui;
16
17 public PanelObf(Gui gui) {
18 this.gui = gui;
19
20 Comparator<ClassEntry> obfClassComparator = (a, b) -> {
21 String aname = a.getFullName();
22 String bname = b.getFullName();
23 if (aname.length() != bname.length()) {
24 return aname.length() - bname.length();
25 }
26 return aname.compareTo(bname);
27 };
28
29 this.obfClasses = new ClassSelector(gui, obfClassComparator, false);
30 this.obfClasses.setSelectionListener(gui.getController()::navigateTo);
31 this.obfClasses.setRenameSelectionListener(gui::onPanelRename);
32
33 this.setLayout(new BorderLayout());
34 this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH);
35 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER);
36 }
37}
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
deleted file mode 100644
index e783530..0000000
--- a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
+++ /dev/null
@@ -1,197 +0,0 @@
1package cuchaz.enigma.gui.stats;
2
3import com.google.gson.GsonBuilder;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.analysis.index.EntryIndex;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.api.service.ObfuscationTestService;
9import cuchaz.enigma.translation.mapping.EntryRemapper;
10import cuchaz.enigma.translation.mapping.EntryResolver;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.*;
14import cuchaz.enigma.utils.I18n;
15
16import java.util.*;
17
18public class StatsGenerator {
19 private final EntryIndex entryIndex;
20 private final EntryRemapper mapper;
21 private final EntryResolver entryResolver;
22 private final List<ObfuscationTestService> obfuscationTestServices;
23 private final List<NameProposalService> nameProposalServices;
24
25 public StatsGenerator(EnigmaProject project) {
26 entryIndex = project.getJarIndex().getEntryIndex();
27 mapper = project.getMapper();
28 entryResolver = project.getJarIndex().getEntryResolver();
29 obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE);
30 nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE);
31 }
32
33 public String generate(ProgressListener progress, Set<StatsMember> includedMembers) {
34 includedMembers = EnumSet.copyOf(includedMembers);
35 int totalWork = 0;
36
37 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
38 totalWork += entryIndex.getMethods().size();
39 }
40
41 if (includedMembers.contains(StatsMember.FIELDS)) {
42 totalWork += entryIndex.getFields().size();
43 }
44
45 if (includedMembers.contains(StatsMember.CLASSES)) {
46 totalWork += entryIndex.getClasses().size();
47 }
48
49 progress.init(totalWork, I18n.translate("progress.stats"));
50
51 Map<String, Integer> counts = new HashMap<>();
52
53 int numDone = 0;
54 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
55 for (MethodEntry method : entryIndex.getMethods()) {
56 progress.step(numDone++, I18n.translate("type.methods"));
57 MethodEntry root = entryResolver
58 .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT)
59 .stream()
60 .findFirst()
61 .orElseThrow(AssertionError::new);
62
63 if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) {
64 if (includedMembers.contains(StatsMember.METHODS)) {
65 update(counts, method);
66 }
67
68 if (includedMembers.contains(StatsMember.PARAMETERS)) {
69 int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1;
70 for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) {
71 update(counts, new LocalVariableEntry(method, index, "", true,null));
72 index += argument.getSize();
73 }
74 }
75 }
76 }
77 }
78
79 if (includedMembers.contains(StatsMember.FIELDS)) {
80 for (FieldEntry field : entryIndex.getFields()) {
81 progress.step(numDone++, I18n.translate("type.fields"));
82 update(counts, field);
83 }
84 }
85
86 if (includedMembers.contains(StatsMember.CLASSES)) {
87 for (ClassEntry clazz : entryIndex.getClasses()) {
88 progress.step(numDone++, I18n.translate("type.classes"));
89 update(counts, clazz);
90 }
91 }
92
93 progress.step(-1, I18n.translate("progress.stats.data"));
94
95 Tree<Integer> tree = new Tree<>();
96
97 for (Map.Entry<String, Integer> entry : counts.entrySet()) {
98 if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection
99 tree.getNode(entry.getKey()).value = entry.getValue();
100 }
101
102 tree.collapse(tree.root);
103 return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root);
104 }
105
106 private void update(Map<String, Integer> counts, Entry<?> entry) {
107 if (isObfuscated(entry)) {
108 String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.');
109 counts.put(parent, counts.getOrDefault(parent, 0) + 1);
110 }
111 }
112
113 private boolean isObfuscated(Entry<?> entry) {
114 String name = entry.getName();
115
116 if (!obfuscationTestServices.isEmpty()) {
117 for (ObfuscationTestService service : obfuscationTestServices) {
118 if (service.testDeobfuscated(entry)) {
119 return false;
120 }
121 }
122 }
123
124 if (!nameProposalServices.isEmpty()) {
125 for (NameProposalService service : nameProposalServices) {
126 if (service.proposeName(entry, mapper).isPresent()) {
127 return false;
128 }
129 }
130 }
131
132 String mappedName = mapper.deobfuscate(entry).getName();
133 if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) {
134 return false;
135 }
136
137 return true;
138 }
139
140 private static class Tree<T> {
141 public final Node<T> root;
142 private final Map<String, Node<T>> nodes = new HashMap<>();
143
144 public static class Node<T> {
145 public String name;
146 public T value;
147 public List<Node<T>> children = new ArrayList<>();
148 private final transient Map<String, Node<T>> namedChildren = new HashMap<>();
149
150 public Node(String name, T value) {
151 this.name = name;
152 this.value = value;
153 }
154 }
155
156 public Tree() {
157 root = new Node<>("", null);
158 }
159
160 public Node<T> getNode(String name) {
161 Node<T> node = nodes.get(name);
162
163 if (node == null) {
164 node = root;
165
166 for (String part : name.split("\\.")) {
167 Node<T> child = node.namedChildren.get(part);
168
169 if (child == null) {
170 child = new Node<>(part, null);
171 node.namedChildren.put(part, child);
172 node.children.add(child);
173 }
174
175 node = child;
176 }
177
178 nodes.put(name, node);
179 }
180
181 return node;
182 }
183
184 public void collapse(Node<T> node) {
185 while (node.children.size() == 1) {
186 Node<T> child = node.children.get(0);
187 node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name;
188 node.children = child.children;
189 node.value = child.value;
190 }
191
192 for (Node<T> child : node.children) {
193 collapse(child);
194 }
195 }
196 }
197}
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
deleted file mode 100644
index 70b4f40..0000000
--- a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.stats;
2
3public enum StatsMember {
4 METHODS,
5 FIELDS,
6 PARAMETERS,
7 CLASSES
8}
diff --git a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java
deleted file mode 100644
index 612e3e9..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java
+++ /dev/null
@@ -1,77 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import java.awt.Component;
4import java.awt.event.MouseEvent;
5
6import javax.swing.*;
7import javax.swing.border.Border;
8
9public abstract class AbstractListCellRenderer<E> extends JPanel implements ListCellRenderer<E> {
10
11 private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
12
13 private Border noFocusBorder;
14
15 public AbstractListCellRenderer() {
16 setBorder(getNoFocusBorder());
17 }
18
19 protected Border getNoFocusBorder() {
20 if (noFocusBorder == null) {
21 Border border = UIManager.getLookAndFeel().getDefaults().getBorder("List.List.cellNoFocusBorder");
22 noFocusBorder = border != null ? border : NO_FOCUS_BORDER;
23 }
24 return noFocusBorder;
25 }
26
27 protected Border getBorder(boolean isSelected, boolean cellHasFocus) {
28 Border b = null;
29 if (cellHasFocus) {
30 UIDefaults defaults = UIManager.getLookAndFeel().getDefaults();
31 if (isSelected) {
32 b = defaults.getBorder("List.focusSelectedCellHighlightBorder");
33 }
34 if (b == null) {
35 b = defaults.getBorder("List.focusCellHighlightBorder");
36 }
37 } else {
38 b = getNoFocusBorder();
39 }
40 return b;
41 }
42
43 public abstract void updateUiForEntry(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus);
44
45 @Override
46 public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) {
47 updateUiForEntry(list, value, index, isSelected, cellHasFocus);
48
49 if (isSelected) {
50 setBackground(list.getSelectionBackground());
51 setForeground(list.getSelectionForeground());
52 } else {
53 setBackground(list.getBackground());
54 setForeground(list.getForeground());
55 }
56
57 setEnabled(list.isEnabled());
58 setFont(list.getFont());
59
60 setBorder(getBorder(isSelected, cellHasFocus));
61
62 // This isn't the width of the cell, but it's close enough for where it's needed (getComponentAt in getToolTipText)
63 setSize(list.getWidth(), getPreferredSize().height);
64
65 return this;
66 }
67
68 @Override
69 public String getToolTipText(MouseEvent event) {
70 Component c = getComponentAt(event.getPoint());
71 if (c instanceof JComponent) {
72 return ((JComponent) c).getToolTipText();
73 }
74 return getToolTipText();
75 }
76
77}
diff --git a/src/main/java/cuchaz/enigma/gui/util/History.java b/src/main/java/cuchaz/enigma/gui/util/History.java
deleted file mode 100644
index 94f3105..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/History.java
+++ /dev/null
@@ -1,49 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import com.google.common.collect.Queues;
4
5import java.util.Deque;
6
7public class History<T> {
8 private final Deque<T> previous = Queues.newArrayDeque();
9 private final Deque<T> next = Queues.newArrayDeque();
10 private T current;
11
12 public History(T initial) {
13 current = initial;
14 }
15
16 public T getCurrent() {
17 return current;
18 }
19
20 public void push(T value) {
21 previous.addLast(current);
22 current = value;
23 next.clear();
24 }
25
26 public void replace(T value) {
27 current = value;
28 }
29
30 public boolean canGoBack() {
31 return !previous.isEmpty();
32 }
33
34 public T goBack() {
35 next.addFirst(current);
36 current = previous.removeLast();
37 return current;
38 }
39
40 public boolean canGoForward() {
41 return !next.isEmpty();
42 }
43
44 public T goForward() {
45 previous.addLast(current);
46 current = next.removeFirst();
47 return current;
48 }
49}
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java b/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java
deleted file mode 100644
index d045c6d..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3@FunctionalInterface
4public interface ScaleChangeListener {
5
6 void onScaleChanged(float scale, float oldScale);
7
8}
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
deleted file mode 100644
index 9f722e9..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
+++ /dev/null
@@ -1,110 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import java.awt.Dimension;
4import java.awt.Font;
5import java.io.IOException;
6import java.lang.reflect.Field;
7import java.util.ArrayList;
8import java.util.List;
9
10import javax.swing.BorderFactory;
11import javax.swing.UIManager;
12import javax.swing.border.Border;
13
14import com.github.swingdpi.UiDefaultsScaler;
15import com.github.swingdpi.plaf.BasicTweaker;
16import com.github.swingdpi.plaf.MetalTweaker;
17import com.github.swingdpi.plaf.NimbusTweaker;
18import com.github.swingdpi.plaf.WindowsTweaker;
19import cuchaz.enigma.config.Config;
20import de.sciss.syntaxpane.DefaultSyntaxKit;
21
22public class ScaleUtil {
23
24 private static List<ScaleChangeListener> listeners = new ArrayList<>();
25
26 public static float getScaleFactor() {
27 return Config.getInstance().scaleFactor;
28 }
29
30 public static void setScaleFactor(float scaleFactor) {
31 float oldScale = getScaleFactor();
32 float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f);
33 Config.getInstance().scaleFactor = clamped;
34 try {
35 Config.getInstance().saveConfig();
36 } catch (IOException e) {
37 e.printStackTrace();
38 }
39 listeners.forEach(l -> l.onScaleChanged(clamped, oldScale));
40 }
41
42 public static void addListener(ScaleChangeListener listener) {
43 listeners.add(listener);
44 }
45
46 public static void removeListener(ScaleChangeListener listener) {
47 listeners.remove(listener);
48 }
49
50 public static Dimension getDimension(int width, int height) {
51 return new Dimension(scale(width), scale(height));
52 }
53
54 public static Font getFont(String fontName, int plain, int fontSize) {
55 return scaleFont(new Font(fontName, plain, fontSize));
56 }
57
58 public static Font scaleFont(Font font) {
59 return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font);
60 }
61
62 public static float scale(float f) {
63 return f * getScaleFactor();
64 }
65
66 public static float invert(float f) {
67 return f / getScaleFactor();
68 }
69
70 public static int scale(int i) {
71 return (int) (i * getScaleFactor());
72 }
73
74 public static Border createEmptyBorder(int top, int left, int bottom, int right) {
75 return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right));
76 }
77
78 public static int invert(int i) {
79 return (int) (i / getScaleFactor());
80 }
81
82 public static void applyScaling() {
83 float scale = getScaleFactor();
84 UiDefaultsScaler.updateAndApplyGlobalScaling((int) (100 * scale), true);
85 try {
86 Field defaultFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT");
87 defaultFontField.setAccessible(true);
88 Font font = (Font) defaultFontField.get(null);
89 font = font.deriveFont(12 * scale);
90 defaultFontField.set(null, font);
91 } catch (NoSuchFieldException | IllegalAccessException e) {
92 e.printStackTrace();
93 }
94 }
95
96 private static BasicTweaker createTweakerForCurrentLook(float dpiScaling) {
97 String testString = UIManager.getLookAndFeel().getName().toLowerCase();
98 if (testString.contains("windows")) {
99 return new WindowsTweaker(dpiScaling, testString.contains("classic"));
100 }
101 if (testString.contains("metal")) {
102 return new MetalTweaker(dpiScaling);
103 }
104 if (testString.contains("nimbus")) {
105 return new NimbusTweaker(dpiScaling);
106 }
107 return new BasicTweaker(dpiScaling);
108 }
109
110}