summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java32
-rw-r--r--enigma-swing/build.gradle1
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java74
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java66
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java9
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java15
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java15
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java263
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java17
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java67
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java57
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java26
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java83
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java330
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java109
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java16
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java17
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java5
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java126
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java15
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java205
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java10
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java7
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java11
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java13
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java5
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java11
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java24
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java7
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java25
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java27
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java97
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java40
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java183
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java303
-rw-r--r--enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/Os.java33
-rw-r--r--enigma/src/main/resources/lang/de_de.json15
-rw-r--r--enigma/src/main/resources/lang/en_us.json22
-rw-r--r--enigma/src/main/resources/lang/fr_fr.json15
-rw-r--r--enigma/src/main/resources/lang/zh_cn.json10
-rw-r--r--enigma/src/test/java/cuchaz/enigma/ConfigTest.java86
44 files changed, 1985 insertions, 522 deletions
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
index 6027a6bd..75981c3b 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
@@ -1,40 +1,22 @@
1package cuchaz.enigma.network; 1package cuchaz.enigma.network;
2 2
3import cuchaz.enigma.network.packet.KickS2CPacket; 3import java.io.*;
4import cuchaz.enigma.network.packet.MessageS2CPacket;
5import cuchaz.enigma.network.packet.Packet;
6import cuchaz.enigma.network.packet.PacketRegistry;
7import cuchaz.enigma.network.packet.RemoveMappingS2CPacket;
8import cuchaz.enigma.network.packet.RenameS2CPacket;
9import cuchaz.enigma.network.packet.UserListS2CPacket;
10import cuchaz.enigma.translation.mapping.EntryMapping;
11import cuchaz.enigma.translation.mapping.EntryRemapper;
12import cuchaz.enigma.translation.representation.entry.Entry;
13
14import java.io.DataInput;
15import java.io.DataInputStream;
16import java.io.DataOutput;
17import java.io.DataOutputStream;
18import java.io.EOFException;
19import java.io.IOException;
20import java.net.ServerSocket; 4import java.net.ServerSocket;
21import java.net.Socket; 5import java.net.Socket;
22import java.net.SocketException; 6import java.net.SocketException;
23import java.util.ArrayList; 7import java.util.*;
24import java.util.Collections;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30import java.util.concurrent.CopyOnWriteArrayList; 8import java.util.concurrent.CopyOnWriteArrayList;
31 9
10import cuchaz.enigma.network.packet.*;
11import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.representation.entry.Entry;
14
32public abstract class EnigmaServer { 15public abstract class EnigmaServer {
33 16
34 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 17 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347
35 public static final int DEFAULT_PORT = 34712; 18 public static final int DEFAULT_PORT = 34712;
36 public static final int PROTOCOL_VERSION = 0; 19 public static final int PROTOCOL_VERSION = 0;
37 public static final String OWNER_USERNAME = "Owner";
38 public static final int CHECKSUM_SIZE = 20; 20 public static final int CHECKSUM_SIZE = 20;
39 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet 21 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet
40 22
diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle
index 0ebc90c5..6d2cd5f2 100644
--- a/enigma-swing/build.gradle
+++ b/enigma-swing/build.gradle
@@ -10,6 +10,7 @@ dependencies {
10 implementation 'com.bulenkov:darcula:1.0.0-bobbylight' 10 implementation 'com.bulenkov:darcula:1.0.0-bobbylight'
11 implementation 'de.sciss:syntaxpane:1.2.0' 11 implementation 'de.sciss:syntaxpane:1.2.0'
12 implementation 'com.github.lukeu:swing-dpi:0.6' 12 implementation 'com.github.lukeu:swing-dpi:0.6'
13 implementation 'org.drjekyll:fontchooser:2.4'
13} 14}
14 15
15jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.gui.Main' 16jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.gui.Main'
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
index 45f87bb4..27c866ca 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
@@ -5,22 +5,22 @@ import de.sciss.syntaxpane.components.LineNumbersRuler;
5import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; 5import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit;
6import de.sciss.syntaxpane.util.Configuration; 6import de.sciss.syntaxpane.util.Configuration;
7 7
8import cuchaz.enigma.gui.config.Config; 8import cuchaz.enigma.gui.config.UiConfig;
9 9
10public class EnigmaSyntaxKit extends JavaSyntaxKit { 10public class EnigmaSyntaxKit extends JavaSyntaxKit {
11 11
12 private static Configuration configuration = null; 12 private static Configuration configuration = null;
13 13
14 @Override 14 @Override
15 public Configuration getConfig() { 15 public Configuration getConfig() {
16 if (configuration == null) { 16 if (configuration == null) {
17 initConfig(DefaultSyntaxKit.getConfig(JavaSyntaxKit.class)); 17 initConfig(DefaultSyntaxKit.getConfig(JavaSyntaxKit.class));
18 } 18 }
19 return configuration; 19 return configuration;
20 } 20 }
21 21
22 public void initConfig(Configuration baseConfig) { 22 public void initConfig(Configuration baseConfig) {
23 configuration = flattenConfiguration(baseConfig, EnigmaSyntaxKit.class); 23 configuration = flattenConfiguration(baseConfig, EnigmaSyntaxKit.class);
24 24
25 // Remove all actions except a select few because they disregard the 25 // Remove all actions except a select few because they disregard the
26 // editable state of the editor, or at least are useless anyway because 26 // editable state of the editor, or at least are useless anyway because
@@ -34,38 +34,36 @@ public class EnigmaSyntaxKit extends JavaSyntaxKit {
34 s.startsWith("Action.jump-to-pair") || 34 s.startsWith("Action.jump-to-pair") ||
35 s.startsWith("Action.quick-find"))); 35 s.startsWith("Action.quick-find")));
36 36
37 // See de.sciss.syntaxpane.TokenType 37 // See de.sciss.syntaxpane.TokenType
38 configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); 38 configuration.put("Style.KEYWORD", String.format("%d, 0", UiConfig.getHighlightColor().getRGB()));
39 configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3"); 39 configuration.put("Style.KEYWORD2", String.format("%d, 3", UiConfig.getHighlightColor().getRGB()));
40 configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0"); 40 configuration.put("Style.STRING", String.format("%d, 0", UiConfig.getStringColor().getRGB()));
41 configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1"); 41 configuration.put("Style.STRING2", String.format("%d, 1", UiConfig.getStringColor().getRGB()));
42 configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1"); 42 configuration.put("Style.NUMBER", String.format("%d, 1", UiConfig.getNumberColor().getRGB()));
43 configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0"); 43 configuration.put("Style.OPERATOR", String.format("%d, 0", UiConfig.getOperatorColor().getRGB()));
44 configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1"); 44 configuration.put("Style.DELIMITER", String.format("%d, 1", UiConfig.getDelimiterColor().getRGB()));
45 configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2"); 45 configuration.put("Style.TYPE", String.format("%d, 2", UiConfig.getTypeColor().getRGB()));
46 configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1"); 46 configuration.put("Style.TYPE2", String.format("%d, 1", UiConfig.getTypeColor().getRGB()));
47 configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0"); 47 configuration.put("Style.IDENTIFIER", String.format("%d, 0", UiConfig.getIdentifierColor().getRGB()));
48 configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0"); 48 configuration.put("Style.DEFAULT", String.format("%d, 0", UiConfig.getTextColor().getRGB()));
49 configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + ""); 49 configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, String.format("%d", UiConfig.getLineNumbersBackgroundColor().getRGB()));
50 configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + ""); 50 configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, String.format("%d", UiConfig.getLineNumbersForegroundColor().getRGB()));
51 configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + ""); 51 configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, String.format("%d", UiConfig.getLineNumbersSelectedColor().getRGB()));
52 configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config 52 configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config
53 53
54 configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); 54 configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F");
55 55
56 if (Config.getInstance().editorFont != null) { 56 configuration.put("DefaultFont", UiConfig.encodeFont(UiConfig.getEditorFont()));
57 configuration.put("DefaultFont", Config.getInstance().editorFont);
58 }
59 } 57 }
60 58
61 /** 59 /**
62 * Creates a new configuration from the passed configuration so that it has 60 * Creates a new configuration from the passed configuration so that it has
63 * no parents and all its values are on the same level. This is needed since 61 * no parents and all its values are on the same level. This is needed since
64 * there is no way to remove map entries from parent configurations. 62 * there is no way to remove map entries from parent configurations.
65 * 63 *
66 * @param source the configuration to flatten 64 * @param source the configuration to flatten
67 * @param configClass the class for the new configuration 65 * @param configClass the class for the new configuration
68 * @return a new configuration 66 * @return a new configuration
69 */ 67 */
70 private static Configuration flattenConfiguration(Configuration source, Class<?> configClass) { 68 private static Configuration flattenConfiguration(Configuration source, Class<?> configClass) {
71 Configuration config = new Configuration(configClass, null); 69 Configuration config = new Configuration(configClass, null);
@@ -73,10 +71,10 @@ public class EnigmaSyntaxKit extends JavaSyntaxKit {
73 config.put(p, source.getString(p)); 71 config.put(p, source.getString(p));
74 } 72 }
75 return config; 73 return config;
76 } 74 }
77 75
78 public static void invalidate() { 76 public static void invalidate() {
79 configuration = null; 77 configuration = null;
80 } 78 }
81 79
82} 80}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
index 3aec6b82..92b6e52e 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -14,6 +14,7 @@ package cuchaz.enigma.gui;
14import java.awt.BorderLayout; 14import java.awt.BorderLayout;
15import java.awt.Container; 15import java.awt.Container;
16import java.awt.FileDialog; 16import java.awt.FileDialog;
17import java.awt.Point;
17import java.awt.event.*; 18import java.awt.event.*;
18import java.nio.file.Path; 19import java.nio.file.Path;
19import java.util.*; 20import java.util.*;
@@ -31,8 +32,8 @@ import cuchaz.enigma.Enigma;
31import cuchaz.enigma.EnigmaProfile; 32import cuchaz.enigma.EnigmaProfile;
32import cuchaz.enigma.analysis.*; 33import cuchaz.enigma.analysis.*;
33import cuchaz.enigma.classhandle.ClassHandle; 34import cuchaz.enigma.classhandle.ClassHandle;
34import cuchaz.enigma.gui.config.Config;
35import cuchaz.enigma.gui.config.Themes; 35import cuchaz.enigma.gui.config.Themes;
36import cuchaz.enigma.gui.config.UiConfig;
36import cuchaz.enigma.gui.dialog.CrashDialog; 37import cuchaz.enigma.gui.dialog.CrashDialog;
37import cuchaz.enigma.gui.dialog.JavadocDialog; 38import cuchaz.enigma.gui.dialog.JavadocDialog;
38import cuchaz.enigma.gui.dialog.SearchDialog; 39import cuchaz.enigma.gui.dialog.SearchDialog;
@@ -43,6 +44,8 @@ import cuchaz.enigma.gui.elements.ValidatableUi;
43import cuchaz.enigma.gui.events.EditorActionListener; 44import cuchaz.enigma.gui.events.EditorActionListener;
44import cuchaz.enigma.gui.panels.*; 45import cuchaz.enigma.gui.panels.*;
45import cuchaz.enigma.gui.util.History; 46import cuchaz.enigma.gui.util.History;
47import cuchaz.enigma.gui.util.LanguageChangeListener;
48import cuchaz.enigma.gui.util.LanguageUtil;
46import cuchaz.enigma.gui.util.ScaleUtil; 49import cuchaz.enigma.gui.util.ScaleUtil;
47import cuchaz.enigma.network.Message; 50import cuchaz.enigma.network.Message;
48import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket; 51import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket;
@@ -59,7 +62,7 @@ import cuchaz.enigma.utils.I18n;
59import cuchaz.enigma.utils.validation.ParameterizedMessage; 62import cuchaz.enigma.utils.validation.ParameterizedMessage;
60import cuchaz.enigma.utils.validation.ValidationContext; 63import cuchaz.enigma.utils.validation.ValidationContext;
61 64
62public class Gui { 65public class Gui implements LanguageChangeListener {
63 66
64 private final ObfPanel obfPanel; 67 private final ObfPanel obfPanel;
65 private final DeobfPanel deobfPanel; 68 private final DeobfPanel deobfPanel;
@@ -73,10 +76,10 @@ public class Gui {
73 76
74 public FileDialog jarFileChooser; 77 public FileDialog jarFileChooser;
75 public FileDialog tinyMappingsFileChooser; 78 public FileDialog tinyMappingsFileChooser;
76 public SearchDialog searchDialog;
77 public JFileChooser enigmaMappingsFileChooser; 79 public JFileChooser enigmaMappingsFileChooser;
78 public JFileChooser exportSourceFileChooser; 80 public JFileChooser exportSourceFileChooser;
79 public FileDialog exportJarFileChooser; 81 public FileDialog exportJarFileChooser;
82 public SearchDialog searchDialog;
80 private GuiController controller; 83 private GuiController controller;
81 private JFrame frame; 84 private JFrame frame;
82 private JPanel classesPanel; 85 private JPanel classesPanel;
@@ -88,6 +91,7 @@ public class Gui {
88 private JList<Token> tokens; 91 private JList<Token> tokens;
89 private JTabbedPane tabs; 92 private JTabbedPane tabs;
90 93
94 private JSplitPane splitCenter;
91 private JSplitPane splitRight; 95 private JSplitPane splitRight;
92 private JSplitPane logSplit; 96 private JSplitPane logSplit;
93 private CollapsibleTabbedPane logTabs; 97 private CollapsibleTabbedPane logTabs;
@@ -107,7 +111,7 @@ public class Gui {
107 private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create(); 111 private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create();
108 112
109 public Gui(EnigmaProfile profile) { 113 public Gui(EnigmaProfile profile) {
110 Config.getInstance().lookAndFeel.setGlobalLAF(); 114 UiConfig.getLookAndFeel().setGlobalLAF();
111 115
112 // init frame 116 // init frame
113 this.frame = new JFrame(Enigma.NAME); 117 this.frame = new JFrame(Enigma.NAME);
@@ -328,10 +332,19 @@ public class Gui {
328 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); 332 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
329 splitRight.setResizeWeight(1); // let the left side take all the slack 333 splitRight.setResizeWeight(1); // let the left side take all the slack
330 splitRight.resetToPreferredSizes(); 334 splitRight.resetToPreferredSizes();
331 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); 335 splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
332 splitCenter.setResizeWeight(0); // let the right side take all the slack 336 splitCenter.setResizeWeight(0); // let the right side take all the slack
333 pane.add(splitCenter, BorderLayout.CENTER); 337 pane.add(splitCenter, BorderLayout.CENTER);
334 338
339 // restore state
340 int[] layout = UiConfig.getLayout();
341 if (layout.length >= 4) {
342 this.splitClasses.setDividerLocation(layout[0]);
343 this.splitCenter.setDividerLocation(layout[1]);
344 this.splitRight.setDividerLocation(layout[2]);
345 this.logSplit.setDividerLocation(layout[3]);
346 }
347
335 // init menus 348 // init menus
336 this.menuBar = new MenuBar(this); 349 this.menuBar = new MenuBar(this);
337 this.frame.setJMenuBar(this.menuBar.getUi()); 350 this.frame.setJMenuBar(this.menuBar.getUi());
@@ -358,11 +371,20 @@ public class Gui {
358 371
359 // show the frame 372 // show the frame
360 pane.doLayout(); 373 pane.doLayout();
361 this.frame.setSize(ScaleUtil.getDimension(1024, 576)); 374 this.frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576)));
362 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); 375 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
363 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 376 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
364 this.frame.setLocationRelativeTo(null); 377
378 Point windowPos = UiConfig.getWindowPos("Main Window", null);
379 if (windowPos != null) {
380 this.frame.setLocation(windowPos);
381 } else {
382 this.frame.setLocationRelativeTo(null);
383 }
384
365 this.frame.setVisible(true); 385 this.frame.setVisible(true);
386
387 LanguageUtil.addListener(this);
366 } 388 }
367 389
368 public JFrame getFrame() { 390 public JFrame getFrame() {
@@ -692,11 +714,20 @@ public class Gui {
692 } 714 }
693 715
694 return null; 716 return null;
695 }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); 717 }, I18n.translate("prompt.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.cancel"));
696 } 718 }
697 } 719 }
698 720
699 private void exit() { 721 private void exit() {
722 UiConfig.setWindowPos("Main Window", this.frame.getLocationOnScreen());
723 UiConfig.setWindowSize("Main Window", this.frame.getSize());
724 UiConfig.setLayout(
725 this.splitClasses.getDividerLocation(),
726 this.splitCenter.getDividerLocation(),
727 this.splitRight.getDividerLocation(),
728 this.logSplit.getDividerLocation());
729 UiConfig.save();
730
700 if (searchDialog != null) { 731 if (searchDialog != null) {
701 searchDialog.dispose(); 732 searchDialog.dispose();
702 } 733 }
@@ -848,6 +879,25 @@ public class Gui {
848 } 879 }
849 } 880 }
850 881
882 @Override
883 public void retranslateUi() {
884 this.jarFileChooser.setTitle(I18n.translate("menu.file.jar.open"));
885 this.exportJarFileChooser.setTitle(I18n.translate("menu.file.export.jar"));
886 this.tabs.setTitleAt(0, I18n.translate("info_panel.tree.inheritance"));
887 this.tabs.setTitleAt(1, I18n.translate("info_panel.tree.implementations"));
888 this.tabs.setTitleAt(2, I18n.translate("info_panel.tree.calls"));
889 this.logTabs.setTitleAt(0, I18n.translate("log_panel.users"));
890 this.logTabs.setTitleAt(1, I18n.translate("log_panel.messages"));
891 this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected"));
892
893 this.updateUiState();
894
895 this.menuBar.retranslateUi();
896 this.obfPanel.retranslateUi();
897 this.deobfPanel.retranslateUi();
898 this.infoPanel.retranslateUi();
899 }
900
851 public void setConnectionState(ConnectionState state) { 901 public void setConnectionState(ConnectionState state) {
852 connectionState = state; 902 connectionState = state;
853 statusLabel.setText(I18n.translate("status.ready")); 903 statusLabel.setText(I18n.translate("status.ready"));
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
index 2f5e5e1e..88ebd64e 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -34,10 +34,11 @@ import cuchaz.enigma.EnigmaProfile;
34import cuchaz.enigma.EnigmaProject; 34import cuchaz.enigma.EnigmaProject;
35import cuchaz.enigma.analysis.*; 35import cuchaz.enigma.analysis.*;
36import cuchaz.enigma.api.service.ObfuscationTestService; 36import cuchaz.enigma.api.service.ObfuscationTestService;
37import cuchaz.enigma.classprovider.ClasspathClassProvider;
38import cuchaz.enigma.classhandle.ClassHandle; 37import cuchaz.enigma.classhandle.ClassHandle;
39import cuchaz.enigma.classhandle.ClassHandleProvider; 38import cuchaz.enigma.classhandle.ClassHandleProvider;
40import cuchaz.enigma.gui.config.Config; 39import cuchaz.enigma.classprovider.ClasspathClassProvider;
40import cuchaz.enigma.gui.config.NetConfig;
41import cuchaz.enigma.gui.config.UiConfig;
41import cuchaz.enigma.gui.dialog.ProgressDialog; 42import cuchaz.enigma.gui.dialog.ProgressDialog;
42import cuchaz.enigma.gui.stats.StatsGenerator; 43import cuchaz.enigma.gui.stats.StatsGenerator;
43import cuchaz.enigma.gui.stats.StatsMember; 44import cuchaz.enigma.gui.stats.StatsMember;
@@ -98,7 +99,7 @@ public class GuiController implements ClientPacketHandler {
98 return ProgressDialog.runOffThread(gui.getFrame(), progress -> { 99 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
99 project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress); 100 project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress);
100 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); 101 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
101 chp = new ClassHandleProvider(project, Config.getInstance().decompiler.service); 102 chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service);
102 gui.onFinishOpenJar(jarPath.getFileName().toString()); 103 gui.onFinishOpenJar(jarPath.getFileName().toString());
103 refreshClasses(); 104 refreshClasses();
104 }); 105 });
@@ -563,7 +564,7 @@ public class GuiController implements ClientPacketHandler {
563 server.start(); 564 server.start();
564 client = new EnigmaClient(this, "127.0.0.1", port); 565 client = new EnigmaClient(this, "127.0.0.1", port);
565 client.connect(); 566 client.connect();
566 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); 567 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, NetConfig.getUsername()));
567 gui.setConnectionState(ConnectionState.HOSTING); 568 gui.setConnectionState(ConnectionState.HOSTING);
568 } 569 }
569 570
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
index 1f3aa2cb..0589f362 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
@@ -11,19 +11,18 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import cuchaz.enigma.EnigmaProfile;
15import cuchaz.enigma.gui.config.Config;
16import cuchaz.enigma.translation.mapping.serde.MappingFormat;
17
18import cuchaz.enigma.utils.I18n;
19import joptsimple.*;
20
21import java.io.IOException; 14import java.io.IOException;
22import java.nio.file.Files; 15import java.nio.file.Files;
23import java.nio.file.Path; 16import java.nio.file.Path;
24import java.nio.file.Paths; 17import java.nio.file.Paths;
25 18
26import com.google.common.io.MoreFiles; 19import com.google.common.io.MoreFiles;
20import joptsimple.*;
21
22import cuchaz.enigma.EnigmaProfile;
23import cuchaz.enigma.gui.config.UiConfig;
24import cuchaz.enigma.translation.mapping.serde.MappingFormat;
25import cuchaz.enigma.utils.I18n;
27 26
28public class Main { 27public class Main {
29 28
@@ -54,7 +53,7 @@ public class Main {
54 53
55 EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile)); 54 EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile));
56 55
57 I18n.setLanguage(Config.getInstance().language); 56 I18n.setLanguage(UiConfig.getLanguage());
58 Gui gui = new Gui(parsedProfile); 57 Gui gui = new Gui(parsedProfile);
59 GuiController controller = gui.getController(); 58 GuiController controller = gui.getController();
60 59
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
index 1eead6eb..3f38f4fc 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
@@ -11,12 +11,14 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import cuchaz.enigma.analysis.MethodInheritanceTreeNode; 14import java.awt.Component;
15import cuchaz.enigma.gui.config.Config; 15import java.awt.Font;
16 16
17import javax.swing.*; 17import javax.swing.JTree;
18import javax.swing.tree.TreeCellRenderer; 18import javax.swing.tree.TreeCellRenderer;
19import java.awt.*; 19
20import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
21import cuchaz.enigma.gui.config.UiConfig;
20 22
21class MethodTreeCellRenderer implements TreeCellRenderer { 23class MethodTreeCellRenderer implements TreeCellRenderer {
22 24
@@ -29,12 +31,11 @@ class MethodTreeCellRenderer implements TreeCellRenderer {
29 @Override 31 @Override
30 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { 32 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); 33 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 if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) {
34 ret.setForeground(new Color(config.defaultTextColor)); 35 ret.setForeground(UiConfig.getTextColor());
35 ret.setFont(ret.getFont().deriveFont(Font.PLAIN)); 36 ret.setFont(ret.getFont().deriveFont(Font.PLAIN));
36 } else { 37 } else {
37 ret.setForeground(new Color(config.numberColor)); 38 ret.setForeground(UiConfig.getNumberColor());
38 ret.setFont(ret.getFont().deriveFont(Font.ITALIC)); 39 ret.setFont(ret.getFont().deriveFont(Font.ITALIC));
39 } 40 }
40 return ret; 41 return ret;
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java
deleted file mode 100644
index a389196f..00000000
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java
+++ /dev/null
@@ -1,263 +0,0 @@
1package cuchaz.enigma.gui.config;
2
3import com.bulenkov.darcula.DarculaLaf;
4import com.google.common.io.Files;
5import com.google.gson.*;
6import cuchaz.enigma.source.DecompilerService;
7import cuchaz.enigma.source.Decompilers;
8
9import cuchaz.enigma.utils.I18n;
10
11import javax.swing.*;
12import javax.swing.plaf.metal.MetalLookAndFeel;
13import java.awt.*;
14import java.awt.image.BufferedImage;
15import java.io.File;
16import java.io.IOException;
17import java.lang.reflect.Type;
18import java.nio.charset.Charset;
19
20public class Config {
21 public static class AlphaColorEntry {
22 public Integer rgb;
23 public float alpha = 1.0f;
24
25 public AlphaColorEntry(Integer rgb, float alpha) {
26 this.rgb = rgb;
27 this.alpha = alpha;
28 }
29
30 public Color get() {
31 if (rgb == null) {
32 return new Color(0, 0, 0, 0);
33 }
34
35 Color baseColor = new Color(rgb);
36 return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha));
37 }
38 }
39
40 public enum LookAndFeel {
41 DEFAULT("Default"),
42 DARCULA("Darcula"),
43 SYSTEM("System"),
44 NONE("None (JVM default)");
45
46 // the "JVM default" look and feel, get it at the beginning and store it so we can set it later
47 private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel();
48 private final String name;
49
50 LookAndFeel(String name) {
51 this.name = name;
52 }
53
54 public String getName() {
55 return name;
56 }
57
58 public void setGlobalLAF() {
59 try {
60 switch (this) {
61 case NONE:
62 UIManager.setLookAndFeel(NONE_LAF);
63 break;
64 case DEFAULT:
65 UIManager.setLookAndFeel(new MetalLookAndFeel());
66 break;
67 case DARCULA:
68 UIManager.setLookAndFeel(new DarculaLaf());
69 break;
70 case SYSTEM:
71 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
72 }
73 } catch (Exception e){
74 throw new Error("Failed to set global look and feel", e);
75 }
76 }
77
78 public static boolean isDarkLaf() {
79 // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel
80 JPanel panel = new JPanel();
81 panel.setSize(new Dimension(10, 10));
82 panel.doLayout();
83
84 BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB);
85 panel.printAll(image.getGraphics());
86
87 Color c = new Color(image.getRGB(0, 0));
88
89 // convert the color we got to grayscale
90 int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue());
91 return b < 85;
92 }
93
94 public void apply(Config config) {
95 boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf();
96 if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139
97 config.lineNumbersForeground = 0x333300;
98 config.lineNumbersBackground = 0xEEEEFF;
99 config.lineNumbersSelected = 0xCCCCEE;
100 config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f);
101 config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f);
102 config.proposedColor = new AlphaColorEntry(0x000000, 0.075f);
103 config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f);
104 config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f);
105 config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f);
106 config.editorBackground = 0xFFFFFF;
107 config.highlightColor = 0x3333EE;
108 config.caretColor = 0x000000;
109 config.selectionHighlightColor = 0x000000;
110 config.stringColor = 0xCC6600;
111 config.numberColor = 0x999933;
112 config.operatorColor = 0x000000;
113 config.delimiterColor = 0x000000;
114 config.typeColor = 0x000000;
115 config.identifierColor = 0x000000;
116 config.defaultTextColor = 0x000000;
117 } else {//Based off colors found here: https://github.com/dracula/dracula-theme/
118 config.lineNumbersForeground = 0xA4A4A3;
119 config.lineNumbersBackground = 0x313335;
120 config.lineNumbersSelected = 0x606366;
121 config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f);
122 config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f);
123 config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f);
124 config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f);
125 config.proposedColor = new AlphaColorEntry(0x606366, 0.3f);
126 config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f);
127 config.editorBackground = 0x282A36;
128 config.highlightColor = 0xFF79C6;
129 config.caretColor = 0xF8F8F2;
130 config.selectionHighlightColor = 0xF8F8F2;
131 config.stringColor = 0xF1FA8C;
132 config.numberColor = 0xBD93F9;
133 config.operatorColor = 0xF8F8F2;
134 config.delimiterColor = 0xF8F8F2;
135 config.typeColor = 0xF8F8F2;
136 config.identifierColor = 0xF8F8F2;
137 config.defaultTextColor = 0xF8F8F2;
138 }
139 }
140 }
141
142 public enum Decompiler {
143 PROCYON("Procyon", Decompilers.PROCYON),
144 CFR("CFR", Decompilers.CFR);
145
146 public final DecompilerService service;
147 public final String name;
148
149 Decompiler(String name, DecompilerService service) {
150 this.name = name;
151 this.service = service;
152 }
153 }
154
155 private static final File DIR_HOME = new File(System.getProperty("user.home"));
156 private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma");
157 private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json");
158 private static final Config INSTANCE = new Config();
159
160 private final transient Gson gson; // transient to exclude it from being exposed
161
162 public AlphaColorEntry obfuscatedColor;
163 public AlphaColorEntry obfuscatedColorOutline;
164 public AlphaColorEntry proposedColor;
165 public AlphaColorEntry proposedColorOutline;
166 public AlphaColorEntry deobfuscatedColor;
167 public AlphaColorEntry deobfuscatedColorOutline;
168
169 public String editorFont;
170
171 public Integer editorBackground;
172 public Integer highlightColor;
173 public Integer caretColor;
174 public Integer selectionHighlightColor;
175
176 public Integer stringColor;
177 public Integer numberColor;
178 public Integer operatorColor;
179 public Integer delimiterColor;
180 public Integer typeColor;
181 public Integer identifierColor;
182 public Integer defaultTextColor;
183
184 public Integer lineNumbersBackground;
185 public Integer lineNumbersSelected;
186 public Integer lineNumbersForeground;
187
188 public String language = I18n.DEFAULT_LANGUAGE;
189
190 public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT;
191
192 public float scaleFactor = 1.0f;
193
194 public Decompiler decompiler = Decompiler.PROCYON;
195
196 private Config() {
197 gson = new GsonBuilder()
198 .registerTypeAdapter(Integer.class, new IntSerializer())
199 .registerTypeAdapter(Integer.class, new IntDeserializer())
200 .registerTypeAdapter(Config.class, (InstanceCreator<Config>) type -> this)
201 .setPrettyPrinting()
202 .create();
203 try {
204 this.loadConfig();
205 } catch (IOException ignored) {
206 try {
207 this.reset();
208 } catch (IOException ignored1) {
209 }
210 }
211 }
212
213 public void loadConfig() throws IOException {
214 if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs();
215 File configFile = new File(ENIGMA_DIR, "config.json");
216 boolean loaded = false;
217
218 if (configFile.exists()) {
219 try {
220 gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class);
221 loaded = true;
222 } catch (Exception e) {
223 e.printStackTrace();
224 }
225 }
226
227 if (!loaded) {
228 this.reset();
229 Files.touch(configFile);
230 }
231 saveConfig();
232 }
233
234 public void saveConfig() throws IOException {
235 Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this));
236 }
237
238 public void reset() throws IOException {
239 this.lookAndFeel = LookAndFeel.DEFAULT;
240 this.lookAndFeel.apply(this);
241 this.decompiler = Decompiler.PROCYON;
242 this.language = I18n.DEFAULT_LANGUAGE;
243 this.saveConfig();
244 }
245
246 private static class IntSerializer implements JsonSerializer<Integer> {
247 @Override
248 public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
249 return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase());
250 }
251 }
252
253 private static class IntDeserializer implements JsonDeserializer<Integer> {
254 @Override
255 public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
256 return (int) Long.parseLong(json.getAsString().replace("#", ""), 16);
257 }
258 }
259
260 public static Config getInstance() {
261 return INSTANCE;
262 }
263}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java
new file mode 100644
index 00000000..0f9e41c6
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java
@@ -0,0 +1,17 @@
1package cuchaz.enigma.gui.config;
2
3import cuchaz.enigma.source.DecompilerService;
4import cuchaz.enigma.source.Decompilers;
5
6public enum Decompiler {
7 PROCYON("Procyon", Decompilers.PROCYON),
8 CFR("CFR", Decompilers.CFR);
9
10 public final DecompilerService service;
11 public final String name;
12
13 Decompiler(String name, DecompilerService service) {
14 this.name = name;
15 this.service = service;
16 }
17}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java
new file mode 100644
index 00000000..1c70d439
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java
@@ -0,0 +1,67 @@
1package cuchaz.enigma.gui.config;
2
3import java.awt.Color;
4import java.awt.Dimension;
5import java.awt.image.BufferedImage;
6
7import javax.swing.JPanel;
8import javax.swing.UIManager;
9import javax.swing.plaf.metal.MetalLookAndFeel;
10
11import com.bulenkov.darcula.DarculaLaf;
12
13public enum LookAndFeel {
14 DEFAULT("Default"),
15 DARCULA("Darcula"),
16 SYSTEM("System"),
17 NONE("None (JVM default)");
18
19 // the "JVM default" look and feel, get it at the beginning and store it so we can set it later
20 private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel();
21 private final String name;
22
23 LookAndFeel(String name) {
24 this.name = name;
25 }
26
27 public String getName() {
28 return name;
29 }
30
31 public void setGlobalLAF() {
32 try {
33 switch (this) {
34 case NONE:
35 UIManager.setLookAndFeel(NONE_LAF);
36 break;
37 case DEFAULT:
38 UIManager.setLookAndFeel(new MetalLookAndFeel());
39 break;
40 case DARCULA:
41 UIManager.setLookAndFeel(new DarculaLaf());
42 break;
43 case SYSTEM:
44 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
45 }
46 } catch (Exception e) {
47 throw new Error("Failed to set global look and feel", e);
48 }
49 }
50
51 public static boolean isDarkLaf() {
52 // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel
53 JPanel panel = new JPanel();
54 panel.setSize(new Dimension(10, 10));
55 panel.doLayout();
56
57 BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB);
58 panel.printAll(image.getGraphics());
59
60 Color c = new Color(image.getRGB(0, 0));
61
62 // convert the color we got to grayscale
63 int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue());
64 return b < 85;
65 }
66
67}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java
new file mode 100644
index 00000000..4439cb8a
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java
@@ -0,0 +1,57 @@
1package cuchaz.enigma.gui.config;
2
3import cuchaz.enigma.config.ConfigContainer;
4import cuchaz.enigma.network.EnigmaServer;
5
6public final class NetConfig {
7
8 private NetConfig() {
9 }
10
11 private static final ConfigContainer cfg = ConfigContainer.getOrCreate("enigma/net");
12
13 public static void save() {
14 cfg.save();
15 }
16
17 public static String getUsername() {
18 return cfg.data().section("User").setIfAbsentString("Username", System.getProperty("user.name", "user"));
19 }
20
21 public static void setUsername(String username) {
22 cfg.data().section("User").setString("Username", username);
23 }
24
25 public static String getPassword() {
26 return cfg.data().section("Remote").getString("Password").orElse("");
27 }
28
29 public static void setPassword(String password) {
30 cfg.data().section("Remote").setString("Password", password);
31 }
32
33 public static String getRemoteAddress() {
34 return cfg.data().section("Remote").getString("Address").orElse("");
35 }
36
37 public static void setRemoteAddress(String address) {
38 cfg.data().section("Remote").setString("Address", address);
39 }
40
41 public static String getServerPassword() {
42 return cfg.data().section("Server").getString("Password").orElse("");
43 }
44
45 public static void setServerPassword(String password) {
46 cfg.data().section("Server").setString("Password", password);
47 }
48
49 public static int getServerPort() {
50 return cfg.data().section("Server").setIfAbsentInt("Port", EnigmaServer.DEFAULT_PORT);
51 }
52
53 public static void setServerPort(int port) {
54 cfg.data().section("Server").setInt("Port", port);
55 }
56
57}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java
new file mode 100644
index 00000000..660d2313
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java
@@ -0,0 +1,26 @@
1package cuchaz.enigma.gui.config;
2
3import java.awt.Font;
4
5import cuchaz.enigma.gui.config.legacy.Config;
6
7public final class OldConfigImporter {
8
9 private OldConfigImporter() {
10 }
11
12 @SuppressWarnings("deprecation")
13 public static void doImport() {
14 if (Config.CONFIG_FILE.exists()) {
15 Config config = new Config();
16 if (config.editorFont != null) {
17 UiConfig.setEditorFont(Font.decode(config.editorFont));
18 }
19 UiConfig.setLanguage(config.language);
20 UiConfig.setLookAndFeel(config.lookAndFeel);
21 UiConfig.setScaleFactor(config.scaleFactor);
22 UiConfig.setDecompiler(config.decompiler);
23 }
24 }
25
26}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java
index fd40cb74..4905b1c5 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java
@@ -1,49 +1,92 @@
1package cuchaz.enigma.gui.config; 1package cuchaz.enigma.gui.config;
2 2
3import java.io.IOException; 3import java.awt.Font;
4import java.util.HashSet; 4import java.util.HashSet;
5import java.util.Set; 5import java.util.Set;
6 6
7import javax.swing.UIManager;
8
7import com.google.common.collect.ImmutableMap; 9import com.google.common.collect.ImmutableMap;
10import de.sciss.syntaxpane.DefaultSyntaxKit;
11
8import cuchaz.enigma.gui.EnigmaSyntaxKit; 12import cuchaz.enigma.gui.EnigmaSyntaxKit;
9import cuchaz.enigma.gui.events.ThemeChangeListener; 13import cuchaz.enigma.gui.events.ThemeChangeListener;
10import cuchaz.enigma.gui.highlight.BoxHighlightPainter; 14import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
11import cuchaz.enigma.gui.util.ScaleUtil; 15import cuchaz.enigma.gui.util.ScaleUtil;
12import cuchaz.enigma.source.RenamableTokenType; 16import cuchaz.enigma.source.RenamableTokenType;
13import de.sciss.syntaxpane.DefaultSyntaxKit;
14 17
15public class Themes { 18public class Themes {
16 19
17 private static final Set<ThemeChangeListener> listeners = new HashSet<>(); 20 private static final Set<ThemeChangeListener> listeners = new HashSet<>();
18 21
19 public static void setLookAndFeel(Config.LookAndFeel lookAndFeel) {
20 Config.getInstance().lookAndFeel = lookAndFeel;
21 updateTheme();
22 }
23
24 public static void updateTheme() { 22 public static void updateTheme() {
25 Config config = Config.getInstance(); 23 LookAndFeel laf = UiConfig.getLookAndFeel();
26 config.lookAndFeel.setGlobalLAF(); 24 laf.setGlobalLAF();
27 config.lookAndFeel.apply(config); 25 setFonts();
28 try { 26 UiConfig.setLookAndFeelDefaults(laf, LookAndFeel.isDarkLaf());
29 config.saveConfig();
30 } catch (IOException e) {
31 e.printStackTrace();
32 }
33 EnigmaSyntaxKit.invalidate(); 27 EnigmaSyntaxKit.invalidate();
34 DefaultSyntaxKit.initKit(); 28 DefaultSyntaxKit.initKit();
35 DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); 29 DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName());
36 ImmutableMap<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters = getBoxHighlightPainters(); 30 ImmutableMap<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters = getBoxHighlightPainters();
37 listeners.forEach(l -> l.onThemeChanged(config.lookAndFeel, boxHighlightPainters)); 31 listeners.forEach(l -> l.onThemeChanged(laf, boxHighlightPainters));
38 ScaleUtil.applyScaling(); 32 ScaleUtil.applyScaling();
33 UiConfig.save();
34 }
35
36 private static void setFonts() {
37 if (UiConfig.shouldUseCustomFonts()) {
38 Font small = UiConfig.getSmallFont();
39 Font bold = UiConfig.getDefaultFont();
40 Font normal = UiConfig.getDefault2Font();
41
42 UIManager.put("CheckBox.font", bold);
43 UIManager.put("CheckBoxMenuItem.font", bold);
44 UIManager.put("CheckBoxMenuItem.acceleratorFont", small);
45 UIManager.put("ColorChooser.font", normal);
46 UIManager.put("ComboBox.font", bold);
47 UIManager.put("DesktopIcon.font", bold);
48 UIManager.put("EditorPane.font", normal);
49 UIManager.put("InternalFrame.titleFont", bold);
50 UIManager.put("FormattedTextField.font", normal);
51 UIManager.put("Label.font", bold);
52 UIManager.put("List.font", bold);
53 UIManager.put("Menu.acceleratorFont", small);
54 UIManager.put("Menu.font", bold);
55 UIManager.put("MenuBar.font", bold);
56 UIManager.put("MenuItem.acceleratorFont", small);
57 UIManager.put("MenuItem.font", bold);
58 UIManager.put("OptionPane.font", normal);
59 UIManager.put("Panel.font", normal);
60 UIManager.put("PasswordField.font", normal);
61 UIManager.put("PopupMenu.font", bold);
62 UIManager.put("ProgressBar.font", bold);
63 UIManager.put("RadioButton.font", bold);
64 UIManager.put("RadioButtonMenuItem.acceleratorFont", small);
65 UIManager.put("RadioButtonMenuItem.font", bold);
66 UIManager.put("ScrollPane.font", normal);
67 UIManager.put("Slider.font", bold);
68 UIManager.put("Spinner.font", bold);
69 UIManager.put("TabbedPane.font", bold);
70 UIManager.put("Table.font", normal);
71 UIManager.put("TableHeader.font", normal);
72 UIManager.put("TextArea.font", normal);
73 UIManager.put("TextField.font", normal);
74 UIManager.put("TextPane.font", normal);
75 UIManager.put("TitledBorder.font", bold);
76 UIManager.put("ToggleButton.font", bold);
77 UIManager.put("ToolBar.font", bold);
78 UIManager.put("ToolTip.font", normal);
79 UIManager.put("Tree.font", normal);
80 UIManager.put("Viewport.font", normal);
81 UIManager.put("Button.font", bold);
82 }
39 } 83 }
40 84
41 public static ImmutableMap<RenamableTokenType, BoxHighlightPainter> getBoxHighlightPainters() { 85 public static ImmutableMap<RenamableTokenType, BoxHighlightPainter> getBoxHighlightPainters() {
42 Config config = Config.getInstance();
43 return ImmutableMap.of( 86 return ImmutableMap.of(
44 RenamableTokenType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), 87 RenamableTokenType.OBFUSCATED, BoxHighlightPainter.create(UiConfig.getObfuscatedColor(), UiConfig.getObfuscatedOutlineColor()),
45 RenamableTokenType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), 88 RenamableTokenType.PROPOSED, BoxHighlightPainter.create(UiConfig.getProposedColor(), UiConfig.getProposedOutlineColor()),
46 RenamableTokenType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) 89 RenamableTokenType.DEOBFUSCATED, BoxHighlightPainter.create(UiConfig.getDeobfuscatedColor(), UiConfig.getDeobfuscatedOutlineColor())
47 ); 90 );
48 } 91 }
49 92
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java
new file mode 100644
index 00000000..fa2c4e32
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java
@@ -0,0 +1,330 @@
1package cuchaz.enigma.gui.config;
2
3import java.awt.*;
4import java.util.Optional;
5import java.util.OptionalInt;
6
7import cuchaz.enigma.config.ConfigContainer;
8import cuchaz.enigma.config.ConfigSection;
9import cuchaz.enigma.gui.util.ScaleUtil;
10import cuchaz.enigma.utils.I18n;
11
12public final class UiConfig {
13
14 private UiConfig() {
15 }
16
17 // General UI configuration such as localization
18 private static final ConfigContainer ui = ConfigContainer.getOrCreate("enigma/enigmaui");
19 // Swing specific configuration such as theming
20 private static final ConfigContainer swing = ConfigContainer.getOrCreate("enigma/enigmaswing");
21
22 static {
23 if (!swing.existsOnDisk() && !ui.existsOnDisk()) {
24 OldConfigImporter.doImport();
25 }
26 }
27
28 public static void save() {
29 ui.save();
30 swing.save();
31 }
32
33 public static String getLanguage() {
34 return ui.data().section("General").setIfAbsentString("Language", I18n.DEFAULT_LANGUAGE);
35 }
36
37 public static void setLanguage(String language) {
38 ui.data().section("General").setString("Language", language);
39 }
40
41 public static float getScaleFactor() {
42 return (float) swing.data().section("General").setIfAbsentDouble("Scale Factor", 1.0);
43 }
44
45 public static void setScaleFactor(float scale) {
46 swing.data().section("General").setDouble("Scale Factor", scale);
47 }
48
49 public static int[] getLayout() {
50 return swing.data().section("Main Window").getIntArray("Layout").orElseGet(() -> new int[] { -1, -1, -1, -1 });
51 }
52
53 public static void setLayout(int leftV, int left, int right, int rightH) {
54 swing.data().section("Main Window").setIntArray("Layout", new int[] { leftV, left, right, rightH });
55 }
56
57 public static LookAndFeel getLookAndFeel() {
58 return swing.data().section("Themes").setIfAbsentEnum(LookAndFeel::valueOf, "Current", LookAndFeel.NONE);
59 }
60
61 public static void setLookAndFeel(LookAndFeel laf) {
62 swing.data().section("Themes").setEnum("Current", laf);
63 }
64
65 public static Decompiler getDecompiler() {
66 return ui.data().section("Decompiler").setIfAbsentEnum(Decompiler::valueOf, "Current", Decompiler.PROCYON);
67 }
68
69 public static void setDecompiler(Decompiler d) {
70 ui.data().section("Decompiler").setEnum("Current", d);
71 }
72
73 private static Color fromComponents(int rgb, double alpha) {
74 int rgba = rgb & 0xFFFFFF | (int) (alpha * 255) << 24;
75 return new Color(rgba, true);
76 }
77
78 private static Color getThemeColorRgba(String colorName) {
79 ConfigSection s = swing.data().section("Themes").section(getLookAndFeel().name()).section("Colors");
80 return fromComponents(s.getRgbColor(colorName).orElse(0), s.getDouble(String.format("%s Alpha", colorName)).orElse(0));
81 }
82
83 private static Color getThemeColorRgb(String colorName) {
84 ConfigSection s = swing.data().section("Themes").section(getLookAndFeel().name()).section("Colors");
85 return new Color(s.getRgbColor(colorName).orElse(0));
86 }
87
88 public static Color getObfuscatedColor() {
89 return getThemeColorRgba("Obfuscated");
90 }
91
92 public static Color getObfuscatedOutlineColor() {
93 return getThemeColorRgba("Obfuscated Outline");
94 }
95
96 public static Color getProposedColor() {
97 return getThemeColorRgba("Proposed");
98 }
99
100 public static Color getProposedOutlineColor() {
101 return getThemeColorRgba("Proposed Outline");
102 }
103
104 public static Color getDeobfuscatedColor() {
105 return getThemeColorRgba("Deobfuscated");
106 }
107
108 public static Color getDeobfuscatedOutlineColor() {
109 return getThemeColorRgba("Deobfuscated Outline");
110 }
111
112 public static Color getEditorBackgroundColor() {
113 return getThemeColorRgb("Editor Background");
114 }
115
116 public static Color getHighlightColor() {
117 return getThemeColorRgb("Highlight");
118 }
119
120 public static Color getCaretColor() {
121 return getThemeColorRgb("Caret");
122 }
123
124 public static Color getSelectionHighlightColor() {
125 return getThemeColorRgb("Selection Highlight");
126 }
127
128 public static Color getStringColor() {
129 return getThemeColorRgb("String");
130 }
131
132 public static Color getNumberColor() {
133 return getThemeColorRgb("Number");
134 }
135
136 public static Color getOperatorColor() {
137 return getThemeColorRgb("Operator");
138 }
139
140 public static Color getDelimiterColor() {
141 return getThemeColorRgb("Delimiter");
142 }
143
144 public static Color getTypeColor() {
145 return getThemeColorRgb("Type");
146 }
147
148 public static Color getIdentifierColor() {
149 return getThemeColorRgb("Identifier");
150 }
151
152 public static Color getTextColor() {
153 return getThemeColorRgb("Text");
154 }
155
156 public static Color getLineNumbersForegroundColor() {
157 return getThemeColorRgb("Line Numbers Foreground");
158 }
159
160 public static Color getLineNumbersBackgroundColor() {
161 return getThemeColorRgb("Line Numbers Background");
162 }
163
164 public static Color getLineNumbersSelectedColor() {
165 return getThemeColorRgb("Line Numbers Selected");
166 }
167
168 public static boolean shouldUseCustomFonts() {
169 return swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setIfAbsentBool("Use Custom", false);
170 }
171
172 public static void setUseCustomFonts(boolean b) {
173 swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setBool("Use Custom", b);
174 }
175
176 public static Optional<Font> getFont(String name) {
177 Optional<String> spec = swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").getString(name);
178 return spec.map(Font::decode);
179 }
180
181 public static void setFont(String name, Font font) {
182 swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setString(name, encodeFont(font));
183 }
184
185 public static Font getDefaultFont() {
186 return getFont("Default").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG).deriveFont(Font.BOLD)));
187 }
188
189 public static void setDefaultFont(Font font) {
190 setFont("Default", font);
191 }
192
193 public static Font getDefault2Font() {
194 return getFont("Default 2").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG)));
195 }
196
197 public static void setDefault2Font(Font font) {
198 setFont("Default 2", font);
199 }
200
201 public static Font getSmallFont() {
202 return getFont("Small").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG)));
203 }
204
205 public static void setSmallFont(Font font) {
206 setFont("Small", font);
207 }
208
209 public static Font getEditorFont() {
210 return getFont("Editor").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.MONOSPACED)));
211 }
212
213 public static void setEditorFont(Font font) {
214 setFont("Editor", font);
215 }
216
217 public static String encodeFont(Font font) {
218 int style = font.getStyle();
219 String s = style == (Font.BOLD | Font.ITALIC) ? "bolditalic" : style == Font.ITALIC ? "italic" : style == Font.BOLD ? "bold" : "plain";
220 return String.format("%s-%s-%s", font.getName(), s, font.getSize());
221 }
222
223 public static Dimension getWindowSize(String window, Dimension fallback) {
224 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
225 ConfigSection section = swing.data().section(window);
226 OptionalInt width = section.getInt(String.format("Width %s", screenSize.width));
227 OptionalInt height = section.getInt(String.format("Height %s", screenSize.height));
228 if (width.isPresent() && height.isPresent()) {
229 return new Dimension(width.getAsInt(), height.getAsInt());
230 } else {
231 return fallback;
232 }
233 }
234
235 public static void setWindowSize(String window, Dimension dim) {
236 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
237 ConfigSection section = swing.data().section(window);
238 section.setInt(String.format("Width %s", screenSize.width), dim.width);
239 section.setInt(String.format("Height %s", screenSize.height), dim.height);
240 }
241
242 public static Point getWindowPos(String window, Point fallback) {
243 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
244 ConfigSection section = swing.data().section(window);
245 OptionalInt x = section.getInt(String.format("X %s", screenSize.width));
246 OptionalInt y = section.getInt(String.format("Y %s", screenSize.height));
247 if (x.isPresent() && y.isPresent()) {
248 return new Point(x.getAsInt(), y.getAsInt());
249 } else {
250 return fallback;
251 }
252 }
253
254 public static void setWindowPos(String window, Point rect) {
255 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
256 ConfigSection section = swing.data().section(window);
257 section.setInt(String.format("X %s", screenSize.width), rect.x);
258 section.setInt(String.format("Y %s", screenSize.height), rect.y);
259 }
260
261 public static String getLastSelectedDir() {
262 return swing.data().section("File Dialog").getString("Selected").orElse("");
263 }
264
265 public static void setLastSelectedDir(String directory) {
266 swing.data().section("File Dialog").setString("Selected", directory);
267 }
268
269 public static void setLookAndFeelDefaults(LookAndFeel laf, boolean isDark) {
270 ConfigSection s = swing.data().section("Themes").section(laf.name()).section("Colors");
271 if (!isDark) {
272 // Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139
273 s.setIfAbsentRgbColor("Line Numbers Foreground", 0x333300);
274 s.setIfAbsentRgbColor("Line Numbers Background", 0xEEEEFF);
275 s.setIfAbsentRgbColor("Line Numbers Selected", 0xCCCCEE);
276 s.setIfAbsentRgbColor("Obfuscated", 0xFFDCDC);
277 s.setIfAbsentDouble("Obfuscated Alpha", 1.0);
278 s.setIfAbsentRgbColor("Obfuscated Outline", 0xA05050);
279 s.setIfAbsentDouble("Obfuscated Outline Alpha", 1.0);
280 s.setIfAbsentRgbColor("Proposed", 0x000000);
281 s.setIfAbsentDouble("Proposed Alpha", 0.15);
282 s.setIfAbsentRgbColor("Proposed Outline", 0x000000);
283 s.setIfAbsentDouble("Proposed Outline Alpha", 0.75);
284 s.setIfAbsentRgbColor("Deobfuscated", 0xDCFFDC);
285 s.setIfAbsentDouble("Deobfuscated Alpha", 1.0);
286 s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50A050);
287 s.setIfAbsentDouble("Deobfuscated Outline Alpha", 1.0);
288 s.setIfAbsentRgbColor("Editor Background", 0xFFFFFF);
289 s.setIfAbsentRgbColor("Highlight", 0x3333EE);
290 s.setIfAbsentRgbColor("Caret", 0x000000);
291 s.setIfAbsentRgbColor("Selection Highlight", 0x000000);
292 s.setIfAbsentRgbColor("String", 0xCC6600);
293 s.setIfAbsentRgbColor("Number", 0x999933);
294 s.setIfAbsentRgbColor("Operator", 0x000000);
295 s.setIfAbsentRgbColor("Delimiter", 0x000000);
296 s.setIfAbsentRgbColor("Type", 0x000000);
297 s.setIfAbsentRgbColor("Identifier", 0x000000);
298 s.setIfAbsentRgbColor("Text", 0x000000);
299 } else {
300 // Based off colors found here: https://github.com/dracula/dracula-theme/
301 s.setIfAbsentRgbColor("Line Numbers Foreground", 0xA4A4A3);
302 s.setIfAbsentRgbColor("Line Numbers Background", 0x313335);
303 s.setIfAbsentRgbColor("Line Numbers Selected", 0x606366);
304 s.setIfAbsentRgbColor("Obfuscated", 0xFF5555);
305 s.setIfAbsentDouble("Obfuscated Alpha", 0.3);
306 s.setIfAbsentRgbColor("Obfuscated Outline", 0xFF5555);
307 s.setIfAbsentDouble("Obfuscated Outline Alpha", 0.5);
308 s.setIfAbsentRgbColor("Proposed", 0x606366);
309 s.setIfAbsentDouble("Proposed Alpha", 0.3);
310 s.setIfAbsentRgbColor("Proposed Outline", 0x606366);
311 s.setIfAbsentDouble("Proposed Outline Alpha", 0.5);
312 s.setIfAbsentRgbColor("Deobfuscated", 0x50FA7B);
313 s.setIfAbsentDouble("Deobfuscated Alpha", 0.3);
314 s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50FA7B);
315 s.setIfAbsentDouble("Deobfuscated Outline Alpha", 0.5);
316 s.setIfAbsentRgbColor("Editor Background", 0x282A36);
317 s.setIfAbsentRgbColor("Highlight", 0xFF79C6);
318 s.setIfAbsentRgbColor("Caret", 0xF8F8F2);
319 s.setIfAbsentRgbColor("Selection Highlight", 0xF8F8F2);
320 s.setIfAbsentRgbColor("String", 0xF1FA8C);
321 s.setIfAbsentRgbColor("Number", 0xBD93F9);
322 s.setIfAbsentRgbColor("Operator", 0xF8F8F2);
323 s.setIfAbsentRgbColor("Delimiter", 0xF8F8F2);
324 s.setIfAbsentRgbColor("Type", 0xF8F8F2);
325 s.setIfAbsentRgbColor("Identifier", 0xF8F8F2);
326 s.setIfAbsentRgbColor("Text", 0xF8F8F2);
327 }
328 }
329
330}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java
new file mode 100644
index 00000000..fdd17d25
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java
@@ -0,0 +1,109 @@
1package cuchaz.enigma.gui.config.legacy;
2
3import java.awt.Color;
4import java.io.File;
5import java.lang.reflect.Type;
6import java.nio.charset.Charset;
7
8import com.google.common.io.Files;
9import com.google.gson.*;
10
11import cuchaz.enigma.gui.config.Decompiler;
12import cuchaz.enigma.utils.I18n;
13
14@Deprecated
15public class Config {
16 public static class AlphaColorEntry {
17 public Integer rgb;
18 public float alpha;
19
20 public AlphaColorEntry(Integer rgb, float alpha) {
21 this.rgb = rgb;
22 this.alpha = alpha;
23 }
24
25 public Color get() {
26 if (rgb == null) {
27 return new Color(0, 0, 0, 0);
28 }
29
30 Color baseColor = new Color(rgb);
31 return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha));
32 }
33 }
34
35 private static final File DIR_HOME = new File(System.getProperty("user.home"));
36 private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma");
37 public static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json");
38
39 private final transient Gson gson; // transient to exclude it from being exposed
40
41 public AlphaColorEntry obfuscatedColor;
42 public AlphaColorEntry obfuscatedColorOutline;
43 public AlphaColorEntry proposedColor;
44 public AlphaColorEntry proposedColorOutline;
45 public AlphaColorEntry deobfuscatedColor;
46 public AlphaColorEntry deobfuscatedColorOutline;
47
48 public String editorFont;
49
50 public Integer editorBackground;
51 public Integer highlightColor;
52 public Integer caretColor;
53 public Integer selectionHighlightColor;
54
55 public Integer stringColor;
56 public Integer numberColor;
57 public Integer operatorColor;
58 public Integer delimiterColor;
59 public Integer typeColor;
60 public Integer identifierColor;
61 public Integer defaultTextColor;
62
63 public Integer lineNumbersBackground;
64 public Integer lineNumbersSelected;
65 public Integer lineNumbersForeground;
66
67 public String language = I18n.DEFAULT_LANGUAGE;
68
69 public cuchaz.enigma.gui.config.LookAndFeel lookAndFeel = cuchaz.enigma.gui.config.LookAndFeel.DEFAULT;
70
71 public float scaleFactor = 1.0f;
72
73 public Decompiler decompiler = Decompiler.PROCYON;
74
75 public Config() {
76 gson = new GsonBuilder()
77 .registerTypeAdapter(Integer.class, new IntSerializer())
78 .registerTypeAdapter(Integer.class, new IntDeserializer())
79 .registerTypeAdapter(Config.class, (InstanceCreator<Config>) type -> this)
80 .setPrettyPrinting()
81 .create();
82 this.loadConfig();
83 }
84
85 public void loadConfig() {
86 if (CONFIG_FILE.exists()) {
87 try {
88 gson.fromJson(Files.asCharSource(CONFIG_FILE, Charset.defaultCharset()).read(), Config.class);
89 } catch (Exception e) {
90 e.printStackTrace();
91 }
92 }
93 }
94
95 private static class IntSerializer implements JsonSerializer<Integer> {
96 @Override
97 public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
98 return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase());
99 }
100 }
101
102 private static class IntDeserializer implements JsonDeserializer<Integer> {
103 @Override
104 public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
105 return (int) Long.parseLong(json.getAsString().replace("#", ""), 16);
106 }
107 }
108
109}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
index 18510602..f8922e64 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
@@ -37,7 +37,7 @@ public class AboutDialog {
37 JLabel title = new JLabel(Enigma.NAME); 37 JLabel title = new JLabel(Enigma.NAME);
38 title.setFont(title.getFont().deriveFont(title.getFont().getSize2D() * 1.5f)); 38 title.setFont(title.getFont().deriveFont(title.getFont().getSize2D() * 1.5f));
39 39
40 JButton okButton = new JButton(I18n.translate("menu.help.about.ok")); 40 JButton okButton = new JButton(I18n.translate("prompt.ok"));
41 okButton.addActionListener(e -> frame.dispose()); 41 okButton.addActionListener(e -> frame.dispose());
42 42
43 pane.add(title, cb.pos(0, 0).build()); 43 pane.add(title, cb.pos(0, 0).build());
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
index 64219ab8..df65473c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
@@ -1,22 +1,23 @@
1package cuchaz.enigma.gui.dialog; 1package cuchaz.enigma.gui.dialog;
2 2
3import java.awt.BorderLayout; 3import java.awt.BorderLayout;
4import java.awt.Dialog;
5import java.awt.Window;
4import java.awt.event.KeyAdapter; 6import java.awt.event.KeyAdapter;
5import java.awt.event.KeyEvent; 7import java.awt.event.KeyEvent;
6 8
7import javax.swing.JButton; 9import javax.swing.JButton;
8import javax.swing.JFrame; 10import javax.swing.JDialog;
9import javax.swing.JLabel; 11import javax.swing.JLabel;
10import javax.swing.JPanel; 12import javax.swing.JPanel;
11 13
12import cuchaz.enigma.gui.Gui;
13import cuchaz.enigma.utils.I18n; 14import cuchaz.enigma.utils.I18n;
14 15
15public class ChangeDialog { 16public class ChangeDialog {
16 17
17 public static void show(Gui gui) { 18 public static void show(Window parent) {
18 // init frame 19 // init frame
19 JFrame frame = new JFrame(I18n.translate("menu.view.change.title")); 20 JDialog frame = new JDialog(parent, I18n.translate("menu.view.change.title"), Dialog.DEFAULT_MODALITY_TYPE);
20 JPanel textPanel = new JPanel(); 21 JPanel textPanel = new JPanel();
21 JPanel buttonPanel = new JPanel(); 22 JPanel buttonPanel = new JPanel();
22 frame.setLayout(new BorderLayout()); 23 frame.setLayout(new BorderLayout());
@@ -29,7 +30,7 @@ public class ChangeDialog {
29 textPanel.add(text); 30 textPanel.add(text);
30 31
31 // show ok button 32 // show ok button
32 JButton okButton = new JButton(I18n.translate("menu.view.change.ok")); 33 JButton okButton = new JButton(I18n.translate("prompt.ok"));
33 buttonPanel.add(okButton); 34 buttonPanel.add(okButton);
34 okButton.addActionListener(event -> frame.dispose()); 35 okButton.addActionListener(event -> frame.dispose());
35 okButton.addKeyListener(new KeyAdapter() { 36 okButton.addKeyListener(new KeyAdapter() {
@@ -43,8 +44,9 @@ public class ChangeDialog {
43 44
44 // show the frame 45 // show the frame
45 frame.pack(); 46 frame.pack();
46 frame.setVisible(true);
47 frame.setResizable(false); 47 frame.setResizable(false);
48 frame.setLocationRelativeTo(gui.getFrame()); 48 frame.setLocationRelativeTo(parent);
49 frame.setVisible(true);
49 } 50 }
51
50} 52}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
index 201f4dcb..2486dfe1 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
@@ -10,6 +10,7 @@ import java.util.Objects;
10import javax.swing.JPasswordField; 10import javax.swing.JPasswordField;
11import javax.swing.JTextField; 11import javax.swing.JTextField;
12 12
13import cuchaz.enigma.gui.config.NetConfig;
13import cuchaz.enigma.gui.elements.ValidatableTextField; 14import cuchaz.enigma.gui.elements.ValidatableTextField;
14import cuchaz.enigma.gui.util.ScaleUtil; 15import cuchaz.enigma.gui.util.ScaleUtil;
15import cuchaz.enigma.network.EnigmaServer; 16import cuchaz.enigma.network.EnigmaServer;
@@ -36,9 +37,9 @@ public class ConnectToServerDialog extends AbstractDialog {
36 37
37 @Override 38 @Override
38 protected List<Pair<String, Component>> createComponents() { 39 protected List<Pair<String, Component>> createComponents() {
39 usernameField = new JTextField(System.getProperty("user.name")); 40 usernameField = new JTextField(NetConfig.getUsername());
40 ipField = new ValidatableTextField(); 41 ipField = new ValidatableTextField(NetConfig.getRemoteAddress());
41 passwordField = new JPasswordField(); 42 passwordField = new JPasswordField(NetConfig.getPassword());
42 43
43 usernameField.addActionListener(event -> confirm()); 44 usernameField.addActionListener(event -> confirm());
44 ipField.addActionListener(event -> confirm()); 45 ipField.addActionListener(event -> confirm());
@@ -51,6 +52,7 @@ public class ConnectToServerDialog extends AbstractDialog {
51 ); 52 );
52 } 53 }
53 54
55 @Override
54 public void validateInputs() { 56 public void validateInputs() {
55 vc.setActiveElement(ipField); 57 vc.setActiveElement(ipField);
56 if (StandardValidation.notBlank(vc, ipField.getText())) { 58 if (StandardValidation.notBlank(vc, ipField.getText())) {
@@ -67,6 +69,7 @@ public class ConnectToServerDialog extends AbstractDialog {
67 if (!vc.canProceed()) return null; 69 if (!vc.canProceed()) return null;
68 return new Result( 70 return new Result(
69 usernameField.getText(), 71 usernameField.getText(),
72 ipField.getText(),
70 Objects.requireNonNull(ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT)), 73 Objects.requireNonNull(ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT)),
71 passwordField.getPassword() 74 passwordField.getPassword()
72 ); 75 );
@@ -84,11 +87,13 @@ public class ConnectToServerDialog extends AbstractDialog {
84 87
85 public static class Result { 88 public static class Result {
86 private final String username; 89 private final String username;
90 private final String addressStr;
87 private final ServerAddress address; 91 private final ServerAddress address;
88 private final char[] password; 92 private final char[] password;
89 93
90 public Result(String username, ServerAddress address, char[] password) { 94 public Result(String username, String addressStr, ServerAddress address, char[] password) {
91 this.username = username; 95 this.username = username;
96 this.addressStr = addressStr;
92 this.address = address; 97 this.address = address;
93 this.password = password; 98 this.password = password;
94 } 99 }
@@ -97,6 +102,10 @@ public class ConnectToServerDialog extends AbstractDialog {
97 return username; 102 return username;
98 } 103 }
99 104
105 public String getAddressStr() {
106 return addressStr;
107 }
108
100 public ServerAddress getAddress() { 109 public ServerAddress getAddress() {
101 return address; 110 return address;
102 } 111 }
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
index ddd3bc37..07daf6dc 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
@@ -6,6 +6,7 @@ import java.awt.Frame;
6import java.util.Arrays; 6import java.util.Arrays;
7import java.util.List; 7import java.util.List;
8 8
9import cuchaz.enigma.gui.config.NetConfig;
9import cuchaz.enigma.gui.elements.ValidatablePasswordField; 10import cuchaz.enigma.gui.elements.ValidatablePasswordField;
10import cuchaz.enigma.gui.elements.ValidatableTextField; 11import cuchaz.enigma.gui.elements.ValidatableTextField;
11import cuchaz.enigma.gui.util.ScaleUtil; 12import cuchaz.enigma.gui.util.ScaleUtil;
@@ -31,8 +32,8 @@ public class CreateServerDialog extends AbstractDialog {
31 32
32 @Override 33 @Override
33 protected List<Pair<String, Component>> createComponents() { 34 protected List<Pair<String, Component>> createComponents() {
34 portField = new ValidatableTextField(Integer.toString(EnigmaServer.DEFAULT_PORT)); 35 portField = new ValidatableTextField(Integer.toString(NetConfig.getServerPort()));
35 passwordField = new ValidatablePasswordField(); 36 passwordField = new ValidatablePasswordField(NetConfig.getServerPassword());
36 37
37 portField.addActionListener(event -> confirm()); 38 portField.addActionListener(event -> confirm());
38 passwordField.addActionListener(event -> confirm()); 39 passwordField.addActionListener(event -> confirm());
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java
new file mode 100644
index 00000000..de019adb
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java
@@ -0,0 +1,126 @@
1package cuchaz.enigma.gui.dialog;
2
3import java.awt.*;
4import java.util.Arrays;
5import java.util.List;
6
7import javax.swing.JButton;
8import javax.swing.JCheckBox;
9import javax.swing.JDialog;
10import javax.swing.JList;
11
12import org.drjekyll.fontchooser.FontChooser;
13
14import cuchaz.enigma.gui.config.UiConfig;
15import cuchaz.enigma.gui.util.GridBagConstraintsBuilder;
16import cuchaz.enigma.gui.util.ScaleUtil;
17import cuchaz.enigma.utils.I18n;
18
19public class FontDialog extends JDialog {
20
21 private static final List<String> CATEGORIES = Arrays.asList(
22 "Default",
23 "Default 2",
24 "Small",
25 "Editor"
26 );
27
28 private static final List<String> CATEGORY_TEXTS = Arrays.asList(
29 "fonts.cat.default",
30 "fonts.cat.default2",
31 "fonts.cat.small",
32 "fonts.cat.editor"
33 );
34
35 private final JList<String> entries = new JList<>(CATEGORY_TEXTS.stream().map(I18n::translate).toArray(String[]::new));
36 private final FontChooser chooser = new FontChooser(Font.decode(Font.DIALOG));
37 private final JCheckBox customCheckBox = new JCheckBox(I18n.translate("fonts.use_custom"));
38 private final JButton okButton = new JButton(I18n.translate("prompt.ok"));
39 private final JButton cancelButton = new JButton(I18n.translate("prompt.cancel"));
40
41 private final Font[] fonts = CATEGORIES.stream().map(name -> UiConfig.getFont(name).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG)))).toArray(Font[]::new);
42
43 public FontDialog(Frame owner) {
44 super(owner, "Fonts", true);
45
46 this.customCheckBox.setSelected(UiConfig.shouldUseCustomFonts());
47
48 this.entries.setPreferredSize(ScaleUtil.getDimension(100, 0));
49
50 this.entries.addListSelectionListener(_e -> this.categoryChanged());
51 this.chooser.addChangeListener(_e -> this.selectedFontChanged());
52 this.customCheckBox.addActionListener(_e -> this.customFontsClicked());
53 this.okButton.addActionListener(_e -> this.apply());
54 this.cancelButton.addActionListener(_e -> this.cancel());
55
56 Container contentPane = this.getContentPane();
57 contentPane.setLayout(new GridBagLayout());
58
59 GridBagConstraintsBuilder cb = GridBagConstraintsBuilder.create()
60 .insets(2);
61
62 contentPane.add(this.entries, cb.pos(0, 0).weight(0.0, 1.0).fill(GridBagConstraints.BOTH).build());
63 contentPane.add(this.chooser, cb.pos(1, 0).weight(1.0, 1.0).fill(GridBagConstraints.BOTH).size(2, 1).build());
64 contentPane.add(this.customCheckBox, cb.pos(0, 1).anchor(GridBagConstraints.WEST).size(2, 1).build());
65 contentPane.add(this.okButton, cb.pos(1, 1).anchor(GridBagConstraints.EAST).weight(1.0, 0.0).build());
66 contentPane.add(this.cancelButton, cb.pos(2, 1).anchor(GridBagConstraints.EAST).weight(0.0, 0.0).build());
67
68 this.updateUiState();
69
70 this.setSize(ScaleUtil.getDimension(640, 360));
71 this.setLocationRelativeTo(owner);
72 }
73
74 private void customFontsClicked() {
75 this.updateUiState();
76 }
77
78 private void categoryChanged() {
79 this.updateUiState();
80 int selectedIndex = this.entries.getSelectedIndex();
81 if (selectedIndex != -1) {
82 this.chooser.setSelectedFont(this.fonts[selectedIndex]);
83 }
84 }
85
86 private void selectedFontChanged() {
87 int selectedIndex = this.entries.getSelectedIndex();
88 if (selectedIndex != -1) {
89 this.fonts[selectedIndex] = this.chooser.getSelectedFont();
90 }
91 }
92
93 private void updateUiState() {
94 recursiveSetEnabled(this.chooser, this.entries.getSelectedIndex() != -1 && this.customCheckBox.isSelected());
95 this.entries.setEnabled(this.customCheckBox.isSelected());
96 }
97
98 private void apply() {
99 for (int i = 0; i < CATEGORIES.size(); i++) {
100 UiConfig.setFont(CATEGORIES.get(i), this.fonts[i]);
101 }
102 UiConfig.setUseCustomFonts(this.customCheckBox.isSelected());
103 UiConfig.save();
104 ChangeDialog.show(this);
105 this.dispose();
106 }
107
108 private void cancel() {
109 this.dispose();
110 }
111
112 public static void display(Frame parent) {
113 FontDialog d = new FontDialog(parent);
114 d.setVisible(true);
115 }
116
117 private static void recursiveSetEnabled(Component self, boolean enabled) {
118 if (self instanceof Container) {
119 for (Component component : ((Container) self).getComponents()) {
120 recursiveSetEnabled(component, enabled);
121 }
122 self.setEnabled(enabled);
123 }
124 }
125
126}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
index 9fbe65af..a934d34c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
@@ -11,23 +11,18 @@
11 11
12package cuchaz.enigma.gui.dialog; 12package cuchaz.enigma.gui.dialog;
13 13
14import cuchaz.enigma.gui.util.GuiUtil;
15import cuchaz.enigma.utils.I18n;
16import cuchaz.enigma.gui.util.ScaleUtil;
17
18import javax.swing.*;
19import javax.swing.text.html.HTML;
20
21import java.awt.*;
22import java.awt.BorderLayout; 14import java.awt.BorderLayout;
23import java.awt.Container; 15import java.awt.Container;
16import java.awt.Dimension;
24import java.awt.FlowLayout; 17import java.awt.FlowLayout;
25import java.awt.event.KeyAdapter; 18import java.awt.event.KeyAdapter;
26import java.awt.event.KeyEvent; 19import java.awt.event.KeyEvent;
27 20
28import javax.swing.*; 21import javax.swing.*;
22import javax.swing.text.html.HTML;
29 23
30import com.google.common.base.Strings; 24import com.google.common.base.Strings;
25
31import cuchaz.enigma.analysis.EntryReference; 26import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.gui.GuiController; 27import cuchaz.enigma.gui.GuiController;
33import cuchaz.enigma.gui.elements.ValidatableTextArea; 28import cuchaz.enigma.gui.elements.ValidatableTextArea;
@@ -88,10 +83,10 @@ public class JavadocDialog {
88 JPanel buttonsPanel = new JPanel(); 83 JPanel buttonsPanel = new JPanel();
89 buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); 84 buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
90 buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); 85 buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction"))));
91 JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); 86 JButton cancelButton = new JButton(I18n.translate("prompt.cancel"));
92 cancelButton.addActionListener(event -> close()); 87 cancelButton.addActionListener(event -> close());
93 buttonsPanel.add(cancelButton); 88 buttonsPanel.add(cancelButton);
94 JButton saveButton = new JButton(I18n.translate("javadocs.save")); 89 JButton saveButton = new JButton(I18n.translate("prompt.save"));
95 saveButton.addActionListener(event -> doSave()); 90 saveButton.addActionListener(event -> doSave());
96 buttonsPanel.add(saveButton); 91 buttonsPanel.add(saveButton);
97 contentPane.add(buttonsPanel, BorderLayout.SOUTH); 92 contentPane.add(buttonsPanel, BorderLayout.SOUTH);
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index d6c60e04..d5d657dc 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -1,12 +1,10 @@
1package cuchaz.enigma.gui.elements; 1package cuchaz.enigma.gui.elements;
2 2
3import java.awt.Desktop; 3import java.awt.FileDialog;
4import java.awt.event.InputEvent; 4import java.awt.event.InputEvent;
5import java.awt.event.KeyEvent; 5import java.awt.event.KeyEvent;
6import java.io.File; 6import java.io.File;
7import java.io.IOException; 7import java.io.IOException;
8import java.net.URISyntaxException;
9import java.net.URL;
10import java.nio.file.Files; 8import java.nio.file.Files;
11import java.nio.file.Path; 9import java.nio.file.Path;
12import java.nio.file.Paths; 10import java.nio.file.Paths;
@@ -20,9 +18,13 @@ import javax.swing.*;
20 18
21import cuchaz.enigma.gui.ConnectionState; 19import cuchaz.enigma.gui.ConnectionState;
22import cuchaz.enigma.gui.Gui; 20import cuchaz.enigma.gui.Gui;
23import cuchaz.enigma.gui.config.Config; 21import cuchaz.enigma.gui.config.Decompiler;
24import cuchaz.enigma.gui.config.Themes; 22import cuchaz.enigma.gui.config.LookAndFeel;
23import cuchaz.enigma.gui.config.NetConfig;
24import cuchaz.enigma.gui.config.UiConfig;
25import cuchaz.enigma.gui.dialog.*; 25import cuchaz.enigma.gui.dialog.*;
26import cuchaz.enigma.gui.util.GuiUtil;
27import cuchaz.enigma.gui.util.LanguageUtil;
26import cuchaz.enigma.gui.util.ScaleUtil; 28import cuchaz.enigma.gui.util.ScaleUtil;
27import cuchaz.enigma.translation.mapping.serde.MappingFormat; 29import cuchaz.enigma.translation.mapping.serde.MappingFormat;
28import cuchaz.enigma.utils.I18n; 30import cuchaz.enigma.utils.I18n;
@@ -32,48 +34,51 @@ public class MenuBar {
32 34
33 private final JMenuBar ui = new JMenuBar(); 35 private final JMenuBar ui = new JMenuBar();
34 36
35 private final JMenu fileMenu = new JMenu(I18n.translate("menu.file")); 37 private final JMenu fileMenu = new JMenu();
36 private final JMenuItem jarOpenItem = new JMenuItem(I18n.translate("menu.file.jar.open")); 38 private final JMenuItem jarOpenItem = new JMenuItem();
37 private final JMenuItem jarCloseItem = new JMenuItem(I18n.translate("menu.file.jar.close")); 39 private final JMenuItem jarCloseItem = new JMenuItem();
38 private final JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open")); 40 private final JMenu openMenu = new JMenu();
39 private final JMenuItem saveMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.save")); 41 private final JMenuItem saveMappingsItem = new JMenuItem();
40 private final JMenu saveMappingsAsMenu = new JMenu(I18n.translate("menu.file.mappings.save_as")); 42 private final JMenu saveMappingsAsMenu = new JMenu();
41 private final JMenuItem closeMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.close")); 43 private final JMenuItem closeMappingsItem = new JMenuItem();
42 private final JMenuItem dropMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.drop")); 44 private final JMenuItem dropMappingsItem = new JMenuItem();
43 private final JMenuItem reloadMappingsItem = new JMenuItem(I18n.translate("menu.file.reload_mappings")); 45 private final JMenuItem reloadMappingsItem = new JMenuItem();
44 private final JMenuItem reloadAllItem = new JMenuItem(I18n.translate("menu.file.reload_all")); 46 private final JMenuItem reloadAllItem = new JMenuItem();
45 private final JMenuItem exportSourceItem = new JMenuItem(I18n.translate("menu.file.export.source")); 47 private final JMenuItem exportSourceItem = new JMenuItem();
46 private final JMenuItem exportJarItem = new JMenuItem(I18n.translate("menu.file.export.jar")); 48 private final JMenuItem exportJarItem = new JMenuItem();
47 private final JMenuItem statsItem = new JMenuItem(I18n.translate("menu.file.stats")); 49 private final JMenuItem statsItem = new JMenuItem();
48 private final JMenuItem exitItem = new JMenuItem(I18n.translate("menu.file.exit")); 50 private final JMenuItem exitItem = new JMenuItem();
49 51
50 private final JMenu decompilerMenu = new JMenu(I18n.translate("menu.decompiler")); 52 private final JMenu decompilerMenu = new JMenu();
51 53
52 private final JMenu viewMenu = new JMenu(I18n.translate("menu.view")); 54 private final JMenu viewMenu = new JMenu();
53 private final JMenu themesMenu = new JMenu(I18n.translate("menu.view.themes")); 55 private final JMenu themesMenu = new JMenu();
54 private final JMenu languagesMenu = new JMenu(I18n.translate("menu.view.languages")); 56 private final JMenu languagesMenu = new JMenu();
55 private final JMenu scaleMenu = new JMenu(I18n.translate("menu.view.scale")); 57 private final JMenu scaleMenu = new JMenu();
56 private final JMenuItem customScaleItem = new JMenuItem(I18n.translate("menu.view.scale.custom")); 58 private final JMenuItem fontItem = new JMenuItem();
57 private final JMenuItem searchItem = new JMenuItem(I18n.translate("menu.view.search")); 59 private final JMenuItem customScaleItem = new JMenuItem();
58 60 private final JMenuItem searchItem = new JMenuItem();
59 private final JMenu collabMenu = new JMenu(I18n.translate("menu.collab")); 61
60 private final JMenuItem connectItem = new JMenuItem(I18n.translate("menu.collab.connect")); 62 private final JMenu collabMenu = new JMenu();
61 private final JMenuItem startServerItem = new JMenuItem(I18n.translate("menu.collab.server.start")); 63 private final JMenuItem connectItem = new JMenuItem();
62 64 private final JMenuItem startServerItem = new JMenuItem();
63 private final JMenu helpMenu = new JMenu(I18n.translate("menu.help")); 65
64 private final JMenuItem aboutItem = new JMenuItem(I18n.translate("menu.help.about")); 66 private final JMenu helpMenu = new JMenu();
65 private final JMenuItem githubItem = new JMenuItem(I18n.translate("menu.help.github")); 67 private final JMenuItem aboutItem = new JMenuItem();
68 private final JMenuItem githubItem = new JMenuItem();
66 69
67 private final Gui gui; 70 private final Gui gui;
68 71
69 public MenuBar(Gui gui) { 72 public MenuBar(Gui gui) {
70 this.gui = gui; 73 this.gui = gui;
71 74
75 this.retranslateUi();
76
72 prepareOpenMenu(this.openMenu, gui); 77 prepareOpenMenu(this.openMenu, gui);
73 prepareSaveMappingsAsMenu(this.saveMappingsAsMenu, this.saveMappingsItem, gui); 78 prepareSaveMappingsAsMenu(this.saveMappingsAsMenu, this.saveMappingsItem, gui);
74 prepareDecompilerMenu(this.decompilerMenu, gui); 79 prepareDecompilerMenu(this.decompilerMenu, gui);
75 prepareThemesMenu(this.themesMenu, gui); 80 prepareThemesMenu(this.themesMenu, gui);
76 prepareLanguagesMenu(this.languagesMenu, gui); 81 prepareLanguagesMenu(this.languagesMenu);
77 prepareScaleMenu(this.scaleMenu, gui); 82 prepareScaleMenu(this.scaleMenu, gui);
78 83
79 this.fileMenu.add(this.jarOpenItem); 84 this.fileMenu.add(this.jarOpenItem);
@@ -102,6 +107,7 @@ public class MenuBar {
102 this.viewMenu.add(this.languagesMenu); 107 this.viewMenu.add(this.languagesMenu);
103 this.scaleMenu.add(this.customScaleItem); 108 this.scaleMenu.add(this.customScaleItem);
104 this.viewMenu.add(this.scaleMenu); 109 this.viewMenu.add(this.scaleMenu);
110 this.viewMenu.add(this.fontItem);
105 this.viewMenu.addSeparator(); 111 this.viewMenu.addSeparator();
106 this.viewMenu.add(this.searchItem); 112 this.viewMenu.add(this.searchItem);
107 this.ui.add(this.viewMenu); 113 this.ui.add(this.viewMenu);
@@ -129,6 +135,7 @@ public class MenuBar {
129 this.statsItem.addActionListener(_e -> StatsDialog.show(this.gui)); 135 this.statsItem.addActionListener(_e -> StatsDialog.show(this.gui));
130 this.exitItem.addActionListener(_e -> this.gui.close()); 136 this.exitItem.addActionListener(_e -> this.gui.close());
131 this.customScaleItem.addActionListener(_e -> this.onCustomScaleClicked()); 137 this.customScaleItem.addActionListener(_e -> this.onCustomScaleClicked());
138 this.fontItem.addActionListener(_e -> this.onFontClicked(this.gui));
132 this.searchItem.addActionListener(_e -> this.onSearchClicked()); 139 this.searchItem.addActionListener(_e -> this.onSearchClicked());
133 this.connectItem.addActionListener(_e -> this.onConnectClicked()); 140 this.connectItem.addActionListener(_e -> this.onConnectClicked());
134 this.startServerItem.addActionListener(_e -> this.onStartServerClicked()); 141 this.startServerItem.addActionListener(_e -> this.onStartServerClicked());
@@ -157,20 +164,58 @@ public class MenuBar {
157 this.statsItem.setEnabled(jarOpen); 164 this.statsItem.setEnabled(jarOpen);
158 } 165 }
159 166
167 public void retranslateUi() {
168 this.fileMenu.setText(I18n.translate("menu.file"));
169 this.jarOpenItem.setText(I18n.translate("menu.file.jar.open"));
170 this.jarCloseItem.setText(I18n.translate("menu.file.jar.close"));
171 this.openMenu.setText(I18n.translate("menu.file.mappings.open"));
172 this.saveMappingsItem.setText(I18n.translate("menu.file.mappings.save"));
173 this.saveMappingsAsMenu.setText(I18n.translate("menu.file.mappings.save_as"));
174 this.closeMappingsItem.setText(I18n.translate("menu.file.mappings.close"));
175 this.dropMappingsItem.setText(I18n.translate("menu.file.mappings.drop"));
176 this.reloadMappingsItem.setText(I18n.translate("menu.file.reload_mappings"));
177 this.reloadAllItem.setText(I18n.translate("menu.file.reload_all"));
178 this.exportSourceItem.setText(I18n.translate("menu.file.export.source"));
179 this.exportJarItem.setText(I18n.translate("menu.file.export.jar"));
180 this.statsItem.setText(I18n.translate("menu.file.stats"));
181 this.exitItem.setText(I18n.translate("menu.file.exit"));
182
183 this.decompilerMenu.setText(I18n.translate("menu.decompiler"));
184
185 this.viewMenu.setText(I18n.translate("menu.view"));
186 this.themesMenu.setText(I18n.translate("menu.view.themes"));
187 this.languagesMenu.setText(I18n.translate("menu.view.languages"));
188 this.scaleMenu.setText(I18n.translate("menu.view.scale"));
189 this.fontItem.setText(I18n.translate("menu.view.font"));
190 this.customScaleItem.setText(I18n.translate("menu.view.scale.custom"));
191 this.searchItem.setText(I18n.translate("menu.view.search"));
192
193 this.collabMenu.setText(I18n.translate("menu.collab"));
194 this.connectItem.setText(I18n.translate("menu.collab.connect"));
195 this.startServerItem.setText(I18n.translate("menu.collab.server.start"));
196
197 this.helpMenu.setText(I18n.translate("menu.help"));
198 this.aboutItem.setText(I18n.translate("menu.help.about"));
199 this.githubItem.setText(I18n.translate("menu.help.github"));
200 }
201
160 public JMenuBar getUi() { 202 public JMenuBar getUi() {
161 return this.ui; 203 return this.ui;
162 } 204 }
163 205
164 private void onOpenJarClicked() { 206 private void onOpenJarClicked() {
165 this.gui.jarFileChooser.setVisible(true); 207 FileDialog d = this.gui.jarFileChooser;
166 String file = this.gui.jarFileChooser.getFile(); 208 d.setDirectory(UiConfig.getLastSelectedDir());
209 d.setVisible(true);
210 String file = d.getFile();
167 // checks if the file name is not empty 211 // checks if the file name is not empty
168 if (file != null) { 212 if (file != null) {
169 Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file); 213 Path path = Paths.get(d.getDirectory()).resolve(file);
170 // checks if the file name corresponds to an existing file 214 // checks if the file name corresponds to an existing file
171 if (Files.exists(path)) { 215 if (Files.exists(path)) {
172 this.gui.getController().openJar(path); 216 this.gui.getController().openJar(path);
173 } 217 }
218 UiConfig.setLastSelectedDir(d.getDirectory());
174 } 219 }
175 } 220 }
176 221
@@ -187,7 +232,7 @@ public class MenuBar {
187 } else if (response == JOptionPane.NO_OPTION) 232 } else if (response == JOptionPane.NO_OPTION)
188 then.run(); 233 then.run();
189 return null; 234 return null;
190 }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); 235 }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.cancel"));
191 } else { 236 } else {
192 then.run(); 237 then.run();
193 } 238 }
@@ -206,16 +251,20 @@ public class MenuBar {
206 } 251 }
207 252
208 private void onExportSourceClicked() { 253 private void onExportSourceClicked() {
254 this.gui.exportSourceFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir()));
209 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 255 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
256 UiConfig.setLastSelectedDir(this.gui.exportSourceFileChooser.getCurrentDirectory().toString());
210 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); 257 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath());
211 } 258 }
212 } 259 }
213 260
214 private void onExportJarClicked() { 261 private void onExportJarClicked() {
262 this.gui.exportJarFileChooser.setDirectory(UiConfig.getLastSelectedDir());
215 this.gui.exportJarFileChooser.setVisible(true); 263 this.gui.exportJarFileChooser.setVisible(true);
216 if (this.gui.exportJarFileChooser.getFile() != null) { 264 if (this.gui.exportJarFileChooser.getFile() != null) {
217 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); 265 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile());
218 this.gui.getController().exportJar(path); 266 this.gui.getController().exportJar(path);
267 UiConfig.setLastSelectedDir(this.gui.exportJarFileChooser.getDirectory());
219 } 268 }
220 } 269 }
221 270
@@ -229,7 +278,21 @@ public class MenuBar {
229 } catch (NumberFormatException ignored) { 278 } catch (NumberFormatException ignored) {
230 } 279 }
231 ScaleUtil.setScaleFactor(newScale); 280 ScaleUtil.setScaleFactor(newScale);
232 ChangeDialog.show(this.gui); 281 ChangeDialog.show(this.gui.getFrame());
282 }
283
284 private void onFontClicked(Gui gui) {
285// FontDialog fd = new FontDialog(gui.getFrame(), "Choose Font", true);
286// fd.setLocationRelativeTo(gui.getFrame());
287// fd.setSelectedFont(UiConfig.getEditorFont());
288// fd.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
289// fd.setVisible(true);
290//
291// if (!fd.isCancelSelected()) {
292// UiConfig.setEditorFont(fd.getSelectedFont());
293// UiConfig.save();
294// }
295 FontDialog.display(gui.getFrame());
233 } 296 }
234 297
235 private void onSearchClicked() { 298 private void onSearchClicked() {
@@ -250,6 +313,10 @@ public class MenuBar {
250 this.gui.getController().disconnectIfConnected(null); 313 this.gui.getController().disconnectIfConnected(null);
251 try { 314 try {
252 this.gui.getController().createClient(result.getUsername(), result.getAddress().address, result.getAddress().port, result.getPassword()); 315 this.gui.getController().createClient(result.getUsername(), result.getAddress().address, result.getAddress().port, result.getPassword());
316 NetConfig.setUsername(result.getUsername());
317 NetConfig.setRemoteAddress(result.getAddressStr());
318 NetConfig.setPassword(String.valueOf(result.getPassword()));
319 NetConfig.save();
253 } catch (IOException e) { 320 } catch (IOException e) {
254 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); 321 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE);
255 this.gui.getController().disconnectIfConnected(null); 322 this.gui.getController().disconnectIfConnected(null);
@@ -269,6 +336,9 @@ public class MenuBar {
269 this.gui.getController().disconnectIfConnected(null); 336 this.gui.getController().disconnectIfConnected(null);
270 try { 337 try {
271 this.gui.getController().createServer(result.getPort(), result.getPassword()); 338 this.gui.getController().createServer(result.getPort(), result.getPassword());
339 NetConfig.setServerPort(result.getPort());
340 NetConfig.setServerPassword(String.valueOf(result.getPassword()));
341 NetConfig.save();
272 } catch (IOException e) { 342 } catch (IOException e) {
273 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); 343 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE);
274 this.gui.getController().disconnectIfConnected(null); 344 this.gui.getController().disconnectIfConnected(null);
@@ -276,10 +346,7 @@ public class MenuBar {
276 } 346 }
277 347
278 private void onGithubClicked() { 348 private void onGithubClicked() {
279 try { 349 GuiUtil.openUrl("https://github.com/FabricMC/Enigma");
280 Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI());
281 } catch (URISyntaxException | IOException ignored) {
282 }
283 } 350 }
284 351
285 private static void prepareOpenMenu(JMenu openMenu, Gui gui) { 352 private static void prepareOpenMenu(JMenu openMenu, Gui gui) {
@@ -287,9 +354,11 @@ public class MenuBar {
287 if (format.getReader() != null) { 354 if (format.getReader() != null) {
288 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); 355 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
289 item.addActionListener(event -> { 356 item.addActionListener(event -> {
357 gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir()));
290 if (gui.enigmaMappingsFileChooser.showOpenDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 358 if (gui.enigmaMappingsFileChooser.showOpenDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
291 File selectedFile = gui.enigmaMappingsFileChooser.getSelectedFile(); 359 File selectedFile = gui.enigmaMappingsFileChooser.getSelectedFile();
292 gui.getController().openMappings(format, selectedFile.toPath()); 360 gui.getController().openMappings(format, selectedFile.toPath());
361 UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString());
293 } 362 }
294 }); 363 });
295 openMenu.add(item); 364 openMenu.add(item);
@@ -303,9 +372,14 @@ public class MenuBar {
303 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); 372 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
304 item.addActionListener(event -> { 373 item.addActionListener(event -> {
305 // TODO: Use a specific file chooser for it 374 // TODO: Use a specific file chooser for it
375 if (gui.enigmaMappingsFileChooser.getCurrentDirectory() == null) {
376 gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir()));
377 }
378
306 if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 379 if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
307 gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); 380 gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format);
308 saveMappingsItem.setEnabled(true); 381 saveMappingsItem.setEnabled(true);
382 UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString());
309 } 383 }
310 }); 384 });
311 saveMappingsAsMenu.add(item); 385 saveMappingsAsMenu.add(item);
@@ -316,21 +390,17 @@ public class MenuBar {
316 private static void prepareDecompilerMenu(JMenu decompilerMenu, Gui gui) { 390 private static void prepareDecompilerMenu(JMenu decompilerMenu, Gui gui) {
317 ButtonGroup decompilerGroup = new ButtonGroup(); 391 ButtonGroup decompilerGroup = new ButtonGroup();
318 392
319 for (Config.Decompiler decompiler : Config.Decompiler.values()) { 393 for (Decompiler decompiler : Decompiler.values()) {
320 JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name); 394 JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name);
321 decompilerGroup.add(decompilerButton); 395 decompilerGroup.add(decompilerButton);
322 if (decompiler.equals(Config.getInstance().decompiler)) { 396 if (decompiler.equals(UiConfig.getDecompiler())) {
323 decompilerButton.setSelected(true); 397 decompilerButton.setSelected(true);
324 } 398 }
325 decompilerButton.addActionListener(event -> { 399 decompilerButton.addActionListener(event -> {
326 gui.getController().setDecompiler(decompiler.service); 400 gui.getController().setDecompiler(decompiler.service);
327 401
328 try { 402 UiConfig.setDecompiler(decompiler);
329 Config.getInstance().decompiler = decompiler; 403 UiConfig.save();
330 Config.getInstance().saveConfig();
331 } catch (IOException e) {
332 throw new RuntimeException(e);
333 }
334 }); 404 });
335 decompilerMenu.add(decompilerButton); 405 decompilerMenu.add(decompilerButton);
336 } 406 }
@@ -338,33 +408,34 @@ public class MenuBar {
338 408
339 private static void prepareThemesMenu(JMenu themesMenu, Gui gui) { 409 private static void prepareThemesMenu(JMenu themesMenu, Gui gui) {
340 ButtonGroup themeGroup = new ButtonGroup(); 410 ButtonGroup themeGroup = new ButtonGroup();
341 for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) { 411 for (LookAndFeel lookAndFeel : LookAndFeel.values()) {
342 JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT))); 412 JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT)));
343 themeGroup.add(themeButton); 413 themeGroup.add(themeButton);
344 if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { 414 if (lookAndFeel.equals(UiConfig.getLookAndFeel())) {
345 themeButton.setSelected(true); 415 themeButton.setSelected(true);
346 } 416 }
347 themeButton.addActionListener(_e -> Themes.setLookAndFeel(lookAndFeel)); 417 themeButton.addActionListener(_e -> {
418 UiConfig.setLookAndFeel(lookAndFeel);
419 UiConfig.save();
420 ChangeDialog.show(gui.getFrame());
421 });
348 themesMenu.add(themeButton); 422 themesMenu.add(themeButton);
349 } 423 }
350 } 424 }
351 425
352 private static void prepareLanguagesMenu(JMenu languagesMenu, Gui gui) { 426 private static void prepareLanguagesMenu(JMenu languagesMenu) {
353 ButtonGroup languageGroup = new ButtonGroup(); 427 ButtonGroup languageGroup = new ButtonGroup();
354 for (String lang : I18n.getAvailableLanguages()) { 428 for (String lang : I18n.getAvailableLanguages()) {
355 JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang)); 429 JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang));
356 languageGroup.add(languageButton); 430 languageGroup.add(languageButton);
357 if (lang.equals(Config.getInstance().language)) { 431 if (lang.equals(UiConfig.getLanguage())) {
358 languageButton.setSelected(true); 432 languageButton.setSelected(true);
359 } 433 }
360 languageButton.addActionListener(event -> { 434 languageButton.addActionListener(event -> {
361 Config.getInstance().language = lang; 435 UiConfig.setLanguage(lang);
362 try { 436 I18n.setLanguage(lang);
363 Config.getInstance().saveConfig(); 437 LanguageUtil.dispatchLanguageChange();
364 } catch (IOException e) { 438 UiConfig.save();
365 e.printStackTrace();
366 }
367 ChangeDialog.show(gui);
368 }); 439 });
369 languagesMenu.add(languageButton); 440 languagesMenu.add(languageButton);
370 } 441 }
@@ -377,7 +448,7 @@ public class MenuBar {
377 float realScaleFactor = scaleFactor / 100f; 448 float realScaleFactor = scaleFactor / 100f;
378 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor)); 449 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor));
379 menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor)); 450 menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor));
380 menuItem.addActionListener(event -> ChangeDialog.show(gui)); 451 menuItem.addActionListener(event -> ChangeDialog.show(gui.getFrame()));
381 scaleGroup.add(menuItem); 452 scaleGroup.add(menuItem);
382 scaleMenu.add(menuItem); 453 scaleMenu.add(menuItem);
383 return new Pair<>(realScaleFactor, menuItem); 454 return new Pair<>(realScaleFactor, menuItem);
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java
index d4962f7b..10d7ce1c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java
@@ -2,7 +2,7 @@ package cuchaz.enigma.gui.events;
2 2
3import java.util.Map; 3import java.util.Map;
4 4
5import cuchaz.enigma.gui.config.Config.LookAndFeel; 5import cuchaz.enigma.gui.config.LookAndFeel;
6import cuchaz.enigma.gui.highlight.BoxHighlightPainter; 6import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
7import cuchaz.enigma.source.RenamableTokenType; 7import cuchaz.enigma.source.RenamableTokenType;
8 8
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
index 3ae4380f..2d8d76a7 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
@@ -11,12 +11,14 @@
11 11
12package cuchaz.enigma.gui.highlight; 12package cuchaz.enigma.gui.highlight;
13 13
14import cuchaz.enigma.gui.config.Config; 14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Rectangle;
17import java.awt.Shape;
15 18
16import javax.swing.text.BadLocationException; 19import javax.swing.text.BadLocationException;
17import javax.swing.text.Highlighter; 20import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent; 21import javax.swing.text.JTextComponent;
19import java.awt.*;
20 22
21public class BoxHighlightPainter implements Highlighter.HighlightPainter { 23public class BoxHighlightPainter implements Highlighter.HighlightPainter {
22 private Color fillColor; 24 private Color fillColor;
@@ -27,8 +29,8 @@ public class BoxHighlightPainter implements Highlighter.HighlightPainter {
27 this.borderColor = borderColor; 29 this.borderColor = borderColor;
28 } 30 }
29 31
30 public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) { 32 public static BoxHighlightPainter create(Color color, Color outline) {
31 return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null); 33 return new BoxHighlightPainter(color, outline);
32 } 34 }
33 35
34 public static Rectangle getBounds(JTextComponent text, int start, int end) { 36 public static Rectangle getBounds(JTextComponent text, int start, int end) {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
index c899e689..22d64201 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
@@ -11,11 +11,12 @@
11 11
12package cuchaz.enigma.gui.highlight; 12package cuchaz.enigma.gui.highlight;
13 13
14import cuchaz.enigma.gui.config.Config; 14import java.awt.*;
15 15
16import javax.swing.text.Highlighter; 16import javax.swing.text.Highlighter;
17import javax.swing.text.JTextComponent; 17import javax.swing.text.JTextComponent;
18import java.awt.*; 18
19import cuchaz.enigma.gui.config.UiConfig;
19 20
20public class SelectionHighlightPainter implements Highlighter.HighlightPainter { 21public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
21 22
@@ -26,7 +27,7 @@ public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
26 // draw a thick border 27 // draw a thick border
27 Graphics2D g2d = (Graphics2D) g; 28 Graphics2D g2d = (Graphics2D) g;
28 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); 29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
29 g2d.setColor(new Color(Config.getInstance().selectionHighlightColor)); 30 g2d.setColor(UiConfig.getSelectionHighlightColor());
30 g2d.setStroke(new BasicStroke(2.0f)); 31 g2d.setStroke(new BasicStroke(2.0f));
31 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); 32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
32 } 33 }
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
index bb8acc8e..3e357cbd 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
@@ -13,6 +13,8 @@ import cuchaz.enigma.utils.I18n;
13public class DeobfPanel extends JPanel { 13public class DeobfPanel extends JPanel {
14 14
15 public final ClassSelector deobfClasses; 15 public final ClassSelector deobfClasses;
16 private final JLabel title = new JLabel();
17
16 private final Gui gui; 18 private final Gui gui;
17 19
18 public DeobfPanel(Gui gui) { 20 public DeobfPanel(Gui gui) {
@@ -23,7 +25,14 @@ public class DeobfPanel extends JPanel {
23 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); 25 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename);
24 26
25 this.setLayout(new BorderLayout()); 27 this.setLayout(new BorderLayout());
26 this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH); 28 this.add(this.title, BorderLayout.NORTH);
27 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); 29 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER);
30
31 this.retranslateUi();
28 } 32 }
33
34 public void retranslateUi() {
35 this.title.setText(I18n.translate("info_panel.classes.deobfuscated"));
36 }
37
29} 38}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
index 3409fc14..ab9de335 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
@@ -24,8 +24,9 @@ import cuchaz.enigma.events.ClassHandleListener;
24import cuchaz.enigma.gui.BrowserCaret; 24import cuchaz.enigma.gui.BrowserCaret;
25import cuchaz.enigma.gui.Gui; 25import cuchaz.enigma.gui.Gui;
26import cuchaz.enigma.gui.GuiController; 26import cuchaz.enigma.gui.GuiController;
27import cuchaz.enigma.gui.config.Config; 27import cuchaz.enigma.gui.config.LookAndFeel;
28import cuchaz.enigma.gui.config.Themes; 28import cuchaz.enigma.gui.config.Themes;
29import cuchaz.enigma.gui.config.UiConfig;
29import cuchaz.enigma.gui.elements.PopupMenuBar; 30import cuchaz.enigma.gui.elements.PopupMenuBar;
30import cuchaz.enigma.gui.events.EditorActionListener; 31import cuchaz.enigma.gui.events.EditorActionListener;
31import cuchaz.enigma.gui.events.ThemeChangeListener; 32import cuchaz.enigma.gui.events.ThemeChangeListener;
@@ -61,7 +62,7 @@ public class EditorPanel {
61 private final JLabel errorLabel = new JLabel(); 62 private final JLabel errorLabel = new JLabel();
62 private final JTextArea errorTextArea = new JTextArea(); 63 private final JTextArea errorTextArea = new JTextArea();
63 private final JScrollPane errorScrollPane = new JScrollPane(this.errorTextArea); 64 private final JScrollPane errorScrollPane = new JScrollPane(this.errorTextArea);
64 private final JButton retryButton = new JButton(I18n.translate("general.retry")); 65 private final JButton retryButton = new JButton(I18n.translate("prompt.retry"));
65 66
66 private DisplayMode mode = DisplayMode.INACTIVE; 67 private DisplayMode mode = DisplayMode.INACTIVE;
67 68
@@ -73,7 +74,7 @@ public class EditorPanel {
73 private boolean mouseIsPressed = false; 74 private boolean mouseIsPressed = false;
74 private boolean shouldNavigateOnClick; 75 private boolean shouldNavigateOnClick;
75 76
76 public Config.LookAndFeel editorLaf; 77 public LookAndFeel editorLaf;
77 private int fontSize = 12; 78 private int fontSize = 12;
78 private Map<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters; 79 private Map<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters;
79 80
@@ -94,9 +95,9 @@ public class EditorPanel {
94 this.editor.setCaret(new BrowserCaret()); 95 this.editor.setCaret(new BrowserCaret());
95 this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); 96 this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize));
96 this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); 97 this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed));
97 this.editor.setCaretColor(new Color(Config.getInstance().caretColor)); 98 this.editor.setCaretColor(UiConfig.getCaretColor());
98 this.editor.setContentType("text/enigma-sources"); 99 this.editor.setContentType("text/enigma-sources");
99 this.editor.setBackground(new Color(Config.getInstance().editorBackground)); 100 this.editor.setBackground(UiConfig.getEditorBackgroundColor());
100 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); 101 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
101 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); 102 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
102 103
@@ -240,7 +241,7 @@ public class EditorPanel {
240 this.themeChangeListener = (laf, boxHighlightPainters) -> { 241 this.themeChangeListener = (laf, boxHighlightPainters) -> {
241 if ((this.editorLaf == null || this.editorLaf != laf)) { 242 if ((this.editorLaf == null || this.editorLaf != laf)) {
242 this.editor.updateUI(); 243 this.editor.updateUI();
243 this.editor.setBackground(new Color(Config.getInstance().editorBackground)); 244 this.editor.setBackground(UiConfig.getEditorBackgroundColor());
244 if (this.editorLaf != null) { 245 if (this.editorLaf != null) {
245 this.classHandle.invalidateMapped(); 246 this.classHandle.invalidateMapped();
246 } 247 }
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
index 248d50d0..4c506404 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
@@ -164,6 +164,11 @@ public class IdentifierPanel {
164 gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true)); 164 gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true));
165 } 165 }
166 166
167 public void retranslateUi() {
168 this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier")));
169 this.refreshReference();
170 }
171
167 public JPanel getUi() { 172 public JPanel getUi() {
168 return ui; 173 return ui;
169 } 174 }
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
index 0ca05837..b384968d 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
@@ -15,6 +15,8 @@ import cuchaz.enigma.utils.I18n;
15public class ObfPanel extends JPanel { 15public class ObfPanel extends JPanel {
16 16
17 public final ClassSelector obfClasses; 17 public final ClassSelector obfClasses;
18 private final JLabel title = new JLabel();
19
18 private final Gui gui; 20 private final Gui gui;
19 21
20 public ObfPanel(Gui gui) { 22 public ObfPanel(Gui gui) {
@@ -34,7 +36,14 @@ public class ObfPanel extends JPanel {
34 this.obfClasses.setRenameSelectionListener(gui::onPanelRename); 36 this.obfClasses.setRenameSelectionListener(gui::onPanelRename);
35 37
36 this.setLayout(new BorderLayout()); 38 this.setLayout(new BorderLayout());
37 this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH); 39 this.add(this.title, BorderLayout.NORTH);
38 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); 40 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER);
41
42 this.retranslateUi();
39 } 43 }
44
45 public void retranslateUi() {
46 this.title.setText(I18n.translate("info_panel.classes.obfuscated"));
47 }
48
40} 49}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
index 631e065c..7fe942d0 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
@@ -16,17 +16,25 @@ import javax.swing.JComponent;
16import javax.swing.JLabel; 16import javax.swing.JLabel;
17import javax.swing.ToolTipManager; 17import javax.swing.ToolTipManager;
18 18
19import cuchaz.enigma.utils.Os;
20
19public class GuiUtil { 21public class GuiUtil {
20 public static void openUrl(String url) { 22 public static void openUrl(String url) {
21 if (Desktop.isDesktopSupported()) { 23 try {
22 Desktop desktop = Desktop.getDesktop(); 24 switch (Os.getOs()) {
23 try { 25 case LINUX:
24 desktop.browse(new URI(url)); 26 new ProcessBuilder("/usr/bin/env", "xdg-open", url).start();
25 } catch (IOException ex) { 27 break;
26 throw new Error(ex); 28 default:
27 } catch (URISyntaxException ex) { 29 if (Desktop.isDesktopSupported()) {
28 throw new IllegalArgumentException(ex); 30 Desktop desktop = Desktop.getDesktop();
31 desktop.browse(new URI(url));
32 }
29 } 33 }
34 } catch (IOException ex) {
35 throw new RuntimeException(ex);
36 } catch (URISyntaxException ex) {
37 throw new IllegalArgumentException(ex);
30 } 38 }
31 } 39 }
32 40
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java
new file mode 100644
index 00000000..69612288
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.gui.util;
2
3public interface LanguageChangeListener {
4
5 void retranslateUi();
6
7}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java
new file mode 100644
index 00000000..d3e63763
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java
@@ -0,0 +1,25 @@
1package cuchaz.enigma.gui.util;
2
3import java.util.ArrayList;
4import java.util.List;
5
6public final class LanguageUtil {
7
8 private static final List<LanguageChangeListener> listeners = new ArrayList<>();
9
10 public LanguageUtil() {
11 }
12
13 public static void addListener(LanguageChangeListener listener) {
14 listeners.add(listener);
15 }
16
17 public static void removeListener(LanguageChangeListener listener) {
18 listeners.remove(listener);
19 }
20
21 public static void dispatchLanguageChange() {
22 listeners.forEach(LanguageChangeListener::retranslateUi);
23 }
24
25}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
index 985615a4..47799fad 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
@@ -3,7 +3,6 @@ package cuchaz.enigma.gui.util;
3import java.awt.Dimension; 3import java.awt.Dimension;
4import java.awt.Font; 4import java.awt.Font;
5import java.awt.Insets; 5import java.awt.Insets;
6import java.io.IOException;
7import java.lang.reflect.Field; 6import java.lang.reflect.Field;
8import java.util.ArrayList; 7import java.util.ArrayList;
9import java.util.List; 8import java.util.List;
@@ -17,26 +16,27 @@ import com.github.swingdpi.plaf.BasicTweaker;
17import com.github.swingdpi.plaf.MetalTweaker; 16import com.github.swingdpi.plaf.MetalTweaker;
18import com.github.swingdpi.plaf.NimbusTweaker; 17import com.github.swingdpi.plaf.NimbusTweaker;
19import com.github.swingdpi.plaf.WindowsTweaker; 18import com.github.swingdpi.plaf.WindowsTweaker;
20import cuchaz.enigma.gui.config.Config;
21import de.sciss.syntaxpane.DefaultSyntaxKit; 19import de.sciss.syntaxpane.DefaultSyntaxKit;
22 20
21import cuchaz.enigma.gui.config.UiConfig;
22
23public class ScaleUtil { 23public class ScaleUtil {
24 24
25 private static List<ScaleChangeListener> listeners = new ArrayList<>(); 25 private static List<ScaleChangeListener> listeners = new ArrayList<>();
26 26
27 public static float getScaleFactor() { 27 public static float getScaleFactor() {
28 return Config.getInstance().scaleFactor; 28 return UiConfig.getScaleFactor();
29 } 29 }
30 30
31 public static void setScaleFactor(float scaleFactor) { 31 public static void setScaleFactor(float scaleFactor) {
32 float oldScale = getScaleFactor(); 32 float oldScale = getScaleFactor();
33 float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f); 33 float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f);
34 Config.getInstance().scaleFactor = clamped; 34 UiConfig.setScaleFactor(clamped);
35 try { 35 rescaleFontInConfig("Default", oldScale);
36 Config.getInstance().saveConfig(); 36 rescaleFontInConfig("Default 2", oldScale);
37 } catch (IOException e) { 37 rescaleFontInConfig("Small", oldScale);
38 e.printStackTrace(); 38 rescaleFontInConfig("Editor", oldScale);
39 } 39 UiConfig.save();
40 listeners.forEach(l -> l.onScaleChanged(clamped, oldScale)); 40 listeners.forEach(l -> l.onScaleChanged(clamped, oldScale));
41 } 41 }
42 42
@@ -64,6 +64,15 @@ public class ScaleUtil {
64 return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font); 64 return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font);
65 } 65 }
66 66
67 private static void rescaleFontInConfig(String name, float oldScale) {
68 UiConfig.getFont(name).ifPresent(font -> UiConfig.setFont(name, rescaleFont(font, oldScale)));
69 }
70
71 public static Font rescaleFont(Font font, float oldScale) {
72 float newSize = Math.round(font.getSize() / oldScale * getScaleFactor());
73 return font.deriveFont(newSize);
74 }
75
67 public static float scale(float f) { 76 public static float scale(float f) {
68 return f * getScaleFactor(); 77 return f * getScaleFactor();
69 } 78 }
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java
new file mode 100644
index 00000000..cb9cbc2e
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigContainer.java
@@ -0,0 +1,97 @@
1package cuchaz.enigma.config;
2
3import java.io.IOException;
4import java.nio.charset.StandardCharsets;
5import java.nio.file.Files;
6import java.nio.file.Path;
7import java.util.Deque;
8import java.util.LinkedList;
9
10public class ConfigContainer {
11
12 private Path configPath;
13 private boolean existsOnDisk;
14
15 private final ConfigSection root = new ConfigSection();
16
17 public ConfigSection data() {
18 return this.root;
19 }
20
21 public void save() {
22 if (this.configPath == null) throw new IllegalStateException("File has no config path set!");
23 try {
24 Files.createDirectories(this.configPath.getParent());
25 Files.write(this.configPath, this.serialize().getBytes(StandardCharsets.UTF_8));
26 this.existsOnDisk = true;
27 } catch (IOException e) {
28 e.printStackTrace();
29 }
30 }
31
32 public void saveAs(Path path) {
33 this.configPath = path;
34 this.save();
35 }
36
37 public boolean existsOnDisk() {
38 return this.existsOnDisk;
39 }
40
41 public String serialize() {
42 return ConfigSerializer.structureToString(this.root);
43 }
44
45 public static ConfigContainer create() {
46 return new ConfigContainer();
47 }
48
49 public static ConfigContainer getOrCreate(String name) {
50 return ConfigContainer.getOrCreate(ConfigPaths.getConfigFilePath(name));
51 }
52
53 public static ConfigContainer getOrCreate(Path path) {
54 ConfigContainer cc = null;
55 try {
56 if (Files.exists(path)) {
57 String s = String.join("\n", Files.readAllLines(path));
58 cc = ConfigContainer.parse(s);
59 cc.existsOnDisk = true;
60 }
61 } catch (IOException e) {
62 e.printStackTrace();
63 }
64
65 if (cc == null) {
66 cc = ConfigContainer.create();
67 }
68
69 cc.configPath = path;
70 return cc;
71 }
72
73 public static ConfigContainer parse(String source) {
74 ConfigContainer cc = ConfigContainer.create();
75 Deque<ConfigSection> stack = new LinkedList<>();
76 stack.push(cc.root);
77 ConfigSerializer.parse(source, new ConfigStructureVisitor() {
78 @Override
79 public void visitKeyValue(String key, String value) {
80 stack.peekLast().setString(key, value);
81 }
82
83 @Override
84 public void visitSection(String section) {
85 stack.add(stack.peekLast().section(section));
86 }
87
88 @Override
89 public void jumpToRootSection() {
90 stack.clear();
91 stack.push(cc.root);
92 }
93 });
94 return cc;
95 }
96
97}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java
new file mode 100644
index 00000000..6e668f86
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigPaths.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.config;
2
3import java.nio.file.Path;
4import java.nio.file.Paths;
5
6import cuchaz.enigma.utils.Os;
7
8public class ConfigPaths {
9
10 public static Path getConfigFilePath(String name) {
11 String fileName = Os.getOs() == Os.LINUX ? String.format("%src", name) : String.format("%s.ini", name);
12 return getConfigPathRoot().resolve(fileName);
13 }
14
15 public static Path getConfigPathRoot() {
16 switch (Os.getOs()) {
17 case LINUX:
18 String configHome = System.getenv("XDG_CONFIG_HOME");
19 if (configHome == null) {
20 return getUserHomeUnix().resolve(".config");
21 }
22 return Paths.get(configHome);
23 case MAC:
24 return getUserHomeUnix().resolve("Library").resolve("Preferences");
25 case WINDOWS:
26 return Paths.get(System.getenv("LOCALAPPDATA"));
27 default:
28 return Paths.get(System.getProperty("user.dir"));
29 }
30 }
31
32 private static Path getUserHomeUnix() {
33 String userHome = System.getenv("HOME");
34 if (userHome == null) {
35 userHome = System.getProperty("user.dir");
36 }
37 return Paths.get(userHome);
38 }
39
40}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java
new file mode 100644
index 00000000..3e7bf6d4
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigSection.java
@@ -0,0 +1,183 @@
1package cuchaz.enigma.config;
2
3import java.util.*;
4import java.util.function.Function;
5
6public class ConfigSection {
7
8 private final Map<String, String> values;
9 private final Map<String, ConfigSection> sections;
10
11 private ConfigSection(Map<String, String> values, Map<String, ConfigSection> sections) {
12 this.values = values;
13 this.sections = sections;
14 }
15
16 public ConfigSection() {
17 this(new HashMap<>(), new HashMap<>());
18 }
19
20 public ConfigSection section(String name) {
21 return this.sections.computeIfAbsent(name, _s -> new ConfigSection());
22 }
23
24 public Map<String, String> values() {
25 return Collections.unmodifiableMap(this.values);
26 }
27
28 public Map<String, ConfigSection> sections() {
29 return Collections.unmodifiableMap(this.sections);
30 }
31
32 public boolean remove(String key) {
33 return this.values.remove(key) != null;
34 }
35
36 public boolean removeSection(String key) {
37 return this.sections.remove(key) != null;
38 }
39
40 public Optional<String> getString(String key) {
41 return Optional.ofNullable(this.values.get(key));
42 }
43
44 public void setString(String key, String value) {
45 this.values.put(key, value);
46 }
47
48 public String setIfAbsentString(String key, String value) {
49 this.values.putIfAbsent(key, value);
50 return this.values.get(key);
51 }
52
53 public Optional<Boolean> getBool(String key) {
54 return ConfigSerializer.parseBool(this.values.get(key));
55 }
56
57 public boolean setIfAbsentBool(String key, boolean value) {
58 return this.getBool(key).orElseGet(() -> {
59 this.setBool(key, value);
60 return value;
61 });
62 }
63
64 public void setBool(String key, boolean value) {
65 this.values.put(key, Boolean.toString(value));
66 }
67
68 public OptionalInt getInt(String key) {
69 return ConfigSerializer.parseInt(this.values.get(key));
70 }
71
72 public void setInt(String key, int value) {
73 this.values.put(key, Integer.toString(value));
74 }
75
76 public int setIfAbsentInt(String key, int value) {
77 return this.getInt(key).orElseGet(() -> {
78 this.setInt(key, value);
79 return value;
80 });
81 }
82
83 public OptionalDouble getDouble(String key) {
84 return ConfigSerializer.parseDouble(this.values.get(key));
85 }
86
87 public void setDouble(String key, double value) {
88 this.values.put(key, Double.toString(value));
89 }
90
91 public double setIfAbsentDouble(String key, double value) {
92 return this.getDouble(key).orElseGet(() -> {
93 this.setDouble(key, value);
94 return value;
95 });
96 }
97
98 public OptionalInt getRgbColor(String key) {
99 return ConfigSerializer.parseRgbColor(this.values.get(key));
100 }
101
102 public void setRgbColor(String key, int value) {
103 this.values.put(key, ConfigSerializer.rgbColorToString(value));
104 }
105
106 public int setIfAbsentRgbColor(String key, int value) {
107 return this.getRgbColor(key).orElseGet(() -> {
108 this.setRgbColor(key, value);
109 return value;
110 });
111 }
112
113 public Optional<String[]> getArray(String key) {
114 return ConfigSerializer.parseArray(this.values.get(key));
115 }
116
117 public void setArray(String key, String[] value) {
118 this.values.put(key, ConfigSerializer.arrayToString(value));
119 }
120
121 public String[] setIfAbsentArray(String key, String[] value) {
122 return this.getArray(key).orElseGet(() -> {
123 this.setArray(key, value);
124 return value;
125 });
126 }
127
128 public Optional<int[]> getIntArray(String key) {
129 return this.getArray(key).map(arr -> Arrays.stream(arr).mapToInt(s -> ConfigSerializer.parseInt(s).orElse(0)).toArray());
130 }
131
132 public void setIntArray(String key, int[] value) {
133 this.setArray(key, Arrays.stream(value).mapToObj(Integer::toString).toArray(String[]::new));
134 }
135
136 public int[] setIfAbsentIntArray(String key, int[] value) {
137 return this.getIntArray(key).orElseGet(() -> {
138 this.setIntArray(key, value);
139 return value;
140 });
141 }
142
143 public <T extends Enum<T>> Optional<T> getEnum(Function<String, T> byName, String key) {
144 return ConfigSerializer.parseEnum(byName, this.values.get(key));
145 }
146
147 public <T extends Enum<T>> void setEnum(String key, T value) {
148 this.values.put(key, value.name());
149 }
150
151 public <T extends Enum<T>> T setIfAbsentEnum(Function<String, T> byName, String key, T value) {
152 return this.getEnum(byName, key).orElseGet(() -> {
153 this.setEnum(key, value);
154 return value;
155 });
156 }
157
158 public ConfigSection copy() {
159 Map<String, ConfigSection> sections = new HashMap<>(this.sections);
160 sections.replaceAll((k, v) -> v.copy());
161 return new ConfigSection(new HashMap<>(this.values), sections);
162 }
163
164 @Override
165 public boolean equals(Object o) {
166 if (this == o) return true;
167 if (!(o instanceof ConfigSection)) return false;
168 ConfigSection that = (ConfigSection) o;
169 return values.equals(that.values) &&
170 sections.equals(that.sections);
171 }
172
173 @Override
174 public int hashCode() {
175 return Objects.hash(values, sections);
176 }
177
178 @Override
179 public String toString() {
180 return String.format("ConfigSection { values: %s, sections: %s }", values, sections);
181 }
182
183}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java
new file mode 100644
index 00000000..dccb5858
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigSerializer.java
@@ -0,0 +1,303 @@
1package cuchaz.enigma.config;
2
3import java.util.*;
4import java.util.Map.Entry;
5import java.util.function.Function;
6import java.util.regex.Pattern;
7import java.util.stream.Collectors;
8
9public final class ConfigSerializer {
10
11 private static final Pattern FULL_RGB_COLOR = Pattern.compile("#[0-9A-Fa-f]{6}");
12 private static final Pattern MIN_RGB_COLOR = Pattern.compile("#[0-9A-Fa-f]{3}");
13
14 private static final int UNEXPECTED_TOKEN = -1;
15 private static final int NO_MATCH = -2;
16
17 public static void parse(String v, ConfigStructureVisitor visitor) {
18 String[] lines = v.split("\n");
19
20 // join escaped newlines
21 int len = lines.length;
22 for (int i = len - 2; i >= 0; i--) {
23 if (lines[i].endsWith("\\")) {
24 lines[i] = String.format("%s\n%s", lines[i], lines[i + 1]);
25 len -= 1;
26 }
27 }
28
29 // parse for real
30 for (int i = 0; i < len; i++) {
31 String line = lines[i];
32
33 // skip empty lines and comment lines
34 if (line.trim().isEmpty() || line.trim().startsWith(";")) continue;
35
36 int r;
37 boolean fail = (r = parseSectionLine(line, 0, visitor)) == NO_MATCH &&
38 (r = parseKeyValue(line, 0, visitor)) == NO_MATCH;
39 }
40 }
41
42 private static int parseSectionLine(String v, int idx, ConfigStructureVisitor visitor) {
43 if (v.startsWith("[")) {
44 List<String> path = new ArrayList<>();
45 while (idx < v.length() && v.charAt(idx) == '[') {
46 idx = parseSection(v, idx, path);
47 if (idx == UNEXPECTED_TOKEN) return UNEXPECTED_TOKEN;
48 }
49
50 if (!path.isEmpty()) {
51 visitor.jumpToRootSection();
52 for (String s : path) {
53 visitor.visitSection(s);
54 }
55 }
56
57 return v.length();
58 } else {
59 return NO_MATCH;
60 }
61 }
62
63 private static int parseSection(String v, int idx, List<String> path) {
64 idx += 1; // skip leading [
65 StringBuilder sb = new StringBuilder();
66 while (idx < v.length()) {
67 int nextCloseBracket = v.indexOf(']', idx);
68 int nextEscape = v.indexOf('\\', idx);
69 int next = optMin(nextCloseBracket, nextEscape);
70 if (next == -1) {
71 // unexpected
72 return UNEXPECTED_TOKEN;
73 } else if (next == nextCloseBracket) {
74 sb.append(v, idx, nextCloseBracket);
75 path.add(sb.toString());
76 return nextCloseBracket + 1;
77 } else if (next == nextEscape) {
78 sb.append(v, idx, nextEscape);
79 idx = parseEscape(v, nextEscape, sb);
80 }
81 }
82 return idx;
83 }
84
85 private static int parseKeyValue(String v, int idx, ConfigStructureVisitor visitor) {
86 StringBuilder sb = new StringBuilder();
87 String k = null;
88 while (idx < v.length()) {
89 int nextEq = v.indexOf('=', idx);
90 int nextEscape = v.indexOf('\\', idx);
91 int next = optMin(nextEq, nextEscape);
92 if (next == -1) {
93 break;
94 } else if (next == nextEq) {
95 sb.append(v, idx, nextEq);
96 k = sb.toString();
97 sb.delete(0, sb.length());
98 idx = nextEq + 1;
99 break;
100 } else if (next == nextEscape) {
101 sb.append(v, idx, nextEscape);
102 idx = parseEscape(v, nextEscape, sb);
103 }
104 }
105 while (idx < v.length()) {
106 int nextEscape = v.indexOf('\\', idx);
107 if (nextEscape != -1) {
108 sb.append(v, idx, nextEscape);
109 idx = parseEscape(v, nextEscape, sb);
110 } else {
111 break;
112 }
113 }
114 sb.append(v, idx, v.length());
115 if (k == null) return NO_MATCH;
116 visitor.visitKeyValue(k, sb.toString());
117 return idx;
118 }
119
120 private static int parseEscape(String v, int idx, StringBuilder sb) {
121 if (idx + 1 < v.length()) {
122 if (v.charAt(idx + 1) == 'u') {
123 if (idx + 5 < v.length()) {
124 String codePoint = v.substring(idx + 2, idx + 6);
125 try {
126 int c = Integer.parseUnsignedInt(codePoint, 16);
127 sb.append((char) c);
128 } catch (NumberFormatException ignored) {
129 }
130 idx = idx + 6;
131 }
132 } else if (v.charAt(idx + 1) == 'n') {
133 sb.append('\n');
134 idx = idx + 2;
135 } else {
136 sb.append(v.charAt(idx + 1));
137 idx = idx + 2;
138 }
139 } else {
140 idx = idx + 1;
141 }
142 return idx;
143 }
144
145 public static String structureToString(ConfigSection section) {
146 StringBuilder sb = new StringBuilder();
147 structureToString(section, sb, new ArrayList<>());
148 return sb.toString();
149 }
150
151 private static void structureToString(ConfigSection section, StringBuilder sb, List<String> pathStack) {
152 if (!section.values().isEmpty()) {
153 if (sb.length() > 0) sb.append('\n');
154 pathStack.forEach(n -> sb.append('[').append(escapeSection(n)).append(']'));
155 if (!pathStack.isEmpty()) sb.append('\n');
156 section.values().entrySet().stream()
157 .sorted(Entry.comparingByKey())
158 .forEach(e -> sb.append(escapeKey(e.getKey())).append('=').append(escapeValue(e.getValue())).append('\n'));
159 }
160
161 section.sections().entrySet().stream().sorted(Entry.comparingByKey()).forEach(e -> {
162 pathStack.add(e.getKey());
163 structureToString(e.getValue(), sb, pathStack);
164 pathStack.remove(pathStack.size() - 1);
165 });
166 }
167
168 private static String escapeSection(String s) {
169 return s
170 .replace("\\", "\\\\")
171 .replace("\n", "\\n")
172 .replace("]", "\\]")
173 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
174 }
175
176 private static String escapeKey(String s) {
177 return s
178 .replace("\\", "\\\\")
179 .replace("[", "\\[")
180 .replace("\n", "\\n")
181 .replace("=", "\\=")
182 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
183 }
184
185 private static String escapeValue(String s) {
186 return s
187 .replace("\\", "\\\\")
188 .replace("\n", "\\n")
189 .chars().mapToObj(c -> c >= 32 && c < 127 ? Character.toString((char) c) : String.format("\\u%04x", c)).collect(Collectors.joining());
190 }
191
192 public static Optional<Boolean> parseBool(String v) {
193 if (v == null) return Optional.empty();
194 switch (v) {
195 case "true":
196 return Optional.of(true);
197 case "false":
198 return Optional.of(false);
199 default:
200 return Optional.empty();
201 }
202 }
203
204 public static OptionalInt parseInt(String v) {
205 if (v == null) return OptionalInt.empty();
206 try {
207 return OptionalInt.of(Integer.parseInt(v));
208 } catch (NumberFormatException e) {
209 return OptionalInt.empty();
210 }
211 }
212
213 public static OptionalDouble parseDouble(String v) {
214 if (v == null) return OptionalDouble.empty();
215 try {
216 return OptionalDouble.of(Double.parseDouble(v));
217 } catch (NumberFormatException e) {
218 return OptionalDouble.empty();
219 }
220 }
221
222 public static OptionalInt parseRgbColor(String v) {
223 if (v == null) return OptionalInt.empty();
224 try {
225 if (FULL_RGB_COLOR.matcher(v).matches()) {
226 return OptionalInt.of(Integer.parseUnsignedInt(v.substring(1), 16));
227 } else if (MIN_RGB_COLOR.matcher(v).matches()) {
228 int result = Integer.parseUnsignedInt(v.substring(1), 16);
229 // change 0xABC to 0xAABBCC
230 result = (result & 0x00F) | (result & 0x0F0) << 4 | (result & 0xF00) << 8;
231 result = result | result << 4;
232 return OptionalInt.of(result);
233 } else {
234 return OptionalInt.empty();
235 }
236 } catch (NumberFormatException e) {
237 return OptionalInt.empty();
238 }
239 }
240
241 public static String rgbColorToString(int color) {
242 color = color & 0xFFFFFF;
243 boolean isShort = ((color & 0xF0F0F0) >> 4 ^ color & 0x0F0F0F) == 0;
244 if (isShort) {
245 int packed = color & 0x0F0F0F;
246 packed = packed & 0xF | packed >> 4;
247 packed = packed & 0xFF | (packed & ~0xFF) >> 4;
248 return String.format("#%03x", packed);
249 } else {
250 return String.format("#%06x", color);
251 }
252 }
253
254 public static Optional<String[]> parseArray(String v) {
255 if (v == null) return Optional.empty();
256 List<String> l = new ArrayList<>();
257 int idx = 0;
258 StringBuilder cur = new StringBuilder();
259 while (true) {
260 int nextSep = v.indexOf(',', idx);
261 int nextEsc = v.indexOf('\\', idx);
262 int next = optMin(nextSep, nextEsc);
263 if (next == -1) {
264 cur.append(v, idx, v.length());
265 l.add(cur.toString());
266 return Optional.of(l.toArray(new String[0]));
267 } else if (next == nextSep) {
268 cur.append(v, idx, nextSep);
269 l.add(cur.toString());
270 cur.delete(0, cur.length());
271 idx = nextSep + 1;
272 } else if (next == nextEsc) {
273 cur.append(v, idx, nextEsc);
274 if (nextEsc + 1 < v.length()) {
275 cur.append(v.charAt(nextEsc + 1));
276 }
277 idx = nextEsc + 2;
278 }
279 }
280 }
281
282 public static String arrayToString(String[] values) {
283 return Arrays.stream(values)
284 .map(s -> s.replace("\\", "\\\\").replace(",", "\\,"))
285 .collect(Collectors.joining(","));
286 }
287
288 public static <T extends Enum<T>> Optional<T> parseEnum(Function<String, T> byName, String v) {
289 if (v == null) return Optional.empty();
290 try {
291 return Optional.of(byName.apply(v));
292 } catch (IllegalArgumentException e) {
293 return Optional.empty();
294 }
295 }
296
297 private static int optMin(int v1, int v2) {
298 if (v1 == -1) return v2;
299 if (v2 == -1) return v1;
300 return Math.min(v1, v2);
301 }
302
303}
diff --git a/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java b/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java
new file mode 100644
index 00000000..12d7ec4e
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/config/ConfigStructureVisitor.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.config;
2
3public interface ConfigStructureVisitor {
4
5 void visitKeyValue(String key, String value);
6
7 void visitSection(String section);
8
9 void jumpToRootSection();
10
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Os.java b/enigma/src/main/java/cuchaz/enigma/utils/Os.java
new file mode 100644
index 00000000..b493c041
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/Os.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.utils;
2
3import java.util.Locale;
4
5public enum Os {
6 LINUX,
7 MAC,
8 SOLARIS,
9 WINDOWS,
10 OTHER;
11
12 private static Os os = null;
13
14 public static Os getOs() {
15 if (os == null) {
16 String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
17 if (osName.contains("mac") || osName.contains("darwin")) {
18 os = MAC;
19 } else if (osName.contains("win")) {
20 os = WINDOWS;
21 } else if (osName.contains("nix") || osName.contains("nux")
22 || osName.contains("aix")) {
23 os = LINUX;
24 } else if (osName.contains("sunos")) {
25 os = SOLARIS;
26 } else {
27 os = OTHER;
28 }
29 }
30 return os;
31 }
32
33} \ No newline at end of file
diff --git a/enigma/src/main/resources/lang/de_de.json b/enigma/src/main/resources/lang/de_de.json
index 31acb5c2..2c69bcd6 100644
--- a/enigma/src/main/resources/lang/de_de.json
+++ b/enigma/src/main/resources/lang/de_de.json
@@ -1,10 +1,9 @@
1{ 1{
2 "language": "German", 2 "language": "German",
3 3
4 "general.retry": "Wiederholen",
5
6 "menu.file.stats.title": "Mapping-Statistiken", 4 "menu.file.stats.title": "Mapping-Statistiken",
7 "menu.file.stats.generate": "Diagramm generieren", 5 "menu.file.stats.generate": "Diagramm generieren",
6 "menu.view.font": "Schriftarten...",
8 "menu.help.about.description": "Ein Tool zur Dekompilierung von Java-Code", 7 "menu.help.about.description": "Ein Tool zur Dekompilierung von Java-Code",
9 "menu.help.about.version": "Version: %s", 8 "menu.help.about.version": "Version: %s",
10 9
@@ -18,6 +17,18 @@
18 "editor.decompile_error": "Ein Fehler ist während des Dekompilierens aufgetreten.", 17 "editor.decompile_error": "Ein Fehler ist während des Dekompilierens aufgetreten.",
19 "editor.remap_error": "Ein Fehler ist während des Remappens aufgetreten.", 18 "editor.remap_error": "Ein Fehler ist während des Remappens aufgetreten.",
20 19
20 "fonts.cat.default": "Standard",
21 "fonts.cat.default2": "Standard 2",
22 "fonts.cat.small": "Klein",
23 "fonts.cat.editor": "Editor",
24 "fonts.use_custom": "Benutzerdefinierte Schriftarten verwenden",
25
26 "prompt.ok": "OK",
27 "prompt.cancel": "Abbrechen",
28 "prompt.retry": "Wiederholen",
29 "prompt.save": "Speichern",
30 "prompt.open": "Öffnen",
31
21 "validation.message.empty_field": "Dieses Feld muss ausgefüllt werden.", 32 "validation.message.empty_field": "Dieses Feld muss ausgefüllt werden.",
22 "validation.message.not_int": "Wert muss eine ganze Zahl sein.", 33 "validation.message.not_int": "Wert muss eine ganze Zahl sein.",
23 "validation.message.field_out_of_range_int": "Wert muss eine ganze Zahl zwischen %d und %d sein.", 34 "validation.message.field_out_of_range_int": "Wert muss eine ganze Zahl zwischen %d und %d sein.",
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json
index ca0b007c..b08962d5 100644
--- a/enigma/src/main/resources/lang/en_us.json
+++ b/enigma/src/main/resources/lang/en_us.json
@@ -1,8 +1,6 @@
1{ 1{
2 "language": "English", 2 "language": "English",
3 3
4 "general.retry": "Retry",
5
6 "mapping_format.enigma_file": "Enigma File", 4 "mapping_format.enigma_file": "Enigma File",
7 "mapping_format.enigma_directory": "Enigma Directory", 5 "mapping_format.enigma_directory": "Enigma Directory",
8 "mapping_format.enigma_zip": "Enigma ZIP", 6 "mapping_format.enigma_zip": "Enigma ZIP",
@@ -43,9 +41,9 @@
43 "menu.view.scale": "Scale", 41 "menu.view.scale": "Scale",
44 "menu.view.scale.custom": "Custom...", 42 "menu.view.scale.custom": "Custom...",
45 "menu.view.scale.custom.title": "Custom Scale", 43 "menu.view.scale.custom.title": "Custom Scale",
44 "menu.view.font": "Fonts...",
46 "menu.view.change.title": "Changes", 45 "menu.view.change.title": "Changes",
47 "menu.view.change.summary": "Changes will be applied after the next restart.", 46 "menu.view.change.summary": "Changes will be applied after the next restart.",
48 "menu.view.change.ok": "Ok",
49 "menu.view.search": "Search", 47 "menu.view.search": "Search",
50 "menu.collab": "Collab", 48 "menu.collab": "Collab",
51 "menu.collab.connect": "Connect to server", 49 "menu.collab.connect": "Connect to server",
@@ -57,7 +55,6 @@
57 "menu.help": "Help", 55 "menu.help": "Help",
58 "menu.help.about": "About", 56 "menu.help.about": "About",
59 "menu.help.about.title": "%s - About", 57 "menu.help.about.title": "%s - About",
60 "menu.help.about.ok": "Ok",
61 "menu.help.about.description": "A tool for deobfuscation of Java code", 58 "menu.help.about.description": "A tool for deobfuscation of Java code",
62 "menu.help.about.version": "Version: %s", 59 "menu.help.about.version": "Version: %s",
63 "menu.help.github": "Github Page", 60 "menu.help.github": "Github Page",
@@ -132,16 +129,23 @@
132 129
133 "javadocs.edit": "Edit Javadocs", 130 "javadocs.edit": "Edit Javadocs",
134 "javadocs.instruction": "Edit javadocs here.", 131 "javadocs.instruction": "Edit javadocs here.",
135 "javadocs.cancel": "Cancel", 132
136 "javadocs.save": "Save", 133 "fonts.cat.default": "Default",
134 "fonts.cat.default2": "Default 2",
135 "fonts.cat.small": "Small",
136 "fonts.cat.editor": "Editor",
137 "fonts.use_custom": "Use Custom Fonts",
138
139 "prompt.ok": "OK",
140 "prompt.cancel": "Cancel",
141 "prompt.retry": "Retry",
142 "prompt.save": "Save",
143 "prompt.open": "Open",
137 144
138 "prompt.close.title": "Save your changes?", 145 "prompt.close.title": "Save your changes?",
139 "prompt.close.summary": "Your mappings have not been saved yet. Do you want to save?", 146 "prompt.close.summary": "Your mappings have not been saved yet. Do you want to save?",
140 "prompt.close.save": "Save and close", 147 "prompt.close.save": "Save and close",
141 "prompt.close.discard": "Discard changes", 148 "prompt.close.discard": "Discard changes",
142 "prompt.close.cancel": "Cancel",
143 "prompt.open": "Open",
144 "prompt.cancel": "Cancel",
145 "prompt.connect.title": "Connect to Server", 149 "prompt.connect.title": "Connect to Server",
146 "prompt.connect.username": "Username:", 150 "prompt.connect.username": "Username:",
147 "prompt.connect.address": "Address:", 151 "prompt.connect.address": "Address:",
diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json
index 567ad2b4..aad8f29e 100644
--- a/enigma/src/main/resources/lang/fr_fr.json
+++ b/enigma/src/main/resources/lang/fr_fr.json
@@ -1,8 +1,6 @@
1{ 1{
2 "language": "Français", 2 "language": "Français",
3 3
4 "general.retry": "Réessayer",
5
6 "mapping_format.enigma_file": "Fichier Enigma", 4 "mapping_format.enigma_file": "Fichier Enigma",
7 "mapping_format.enigma_directory": "Répertoire Enigma", 5 "mapping_format.enigma_directory": "Répertoire Enigma",
8 "mapping_format.enigma_zip": "ZIP Enigma", 6 "mapping_format.enigma_zip": "ZIP Enigma",
@@ -43,7 +41,6 @@
43 "menu.view.scale.custom.title": "Échelle personnalisée", 41 "menu.view.scale.custom.title": "Échelle personnalisée",
44 "menu.view.change.title": "Modifications", 42 "menu.view.change.title": "Modifications",
45 "menu.view.change.summary": "Les modifications seront appliquées lors du prochain redémarrage.", 43 "menu.view.change.summary": "Les modifications seront appliquées lors du prochain redémarrage.",
46 "menu.view.change.ok": "Ok",
47 "menu.view.search": "Rechercher", 44 "menu.view.search": "Rechercher",
48 "menu.collab": "Collab", 45 "menu.collab": "Collab",
49 "menu.collab.connect": "Se connecter à un serveur", 46 "menu.collab.connect": "Se connecter à un serveur",
@@ -55,7 +52,6 @@
55 "menu.help": "Aide", 52 "menu.help": "Aide",
56 "menu.help.about": "À propos", 53 "menu.help.about": "À propos",
57 "menu.help.about.title": "%s - À propos", 54 "menu.help.about.title": "%s - À propos",
58 "menu.help.about.ok": "Ok",
59 "menu.help.github": "Page Github", 55 "menu.help.github": "Page Github",
60 56
61 "popup_menu.rename": "Renommer", 57 "popup_menu.rename": "Renommer",
@@ -128,16 +124,17 @@
128 124
129 "javadocs.edit": "Éditer les Javadocs", 125 "javadocs.edit": "Éditer les Javadocs",
130 "javadocs.instruction": "Éditer les Javadocs ici.", 126 "javadocs.instruction": "Éditer les Javadocs ici.",
131 "javadocs.cancel": "Annuler", 127
132 "javadocs.save": "Enregistrer", 128 "prompt.ok": "OK",
129 "prompt.cancel": "Annuler",
130 "prompt.retry": "Réessayer",
131 "prompt.open": "Ouvrir",
132 "prompt.save": "Enregistrer",
133 133
134 "prompt.close.title": "Enregistrer les modifications ?", 134 "prompt.close.title": "Enregistrer les modifications ?",
135 "prompt.close.summary": "Vos mappings n'ont pas encore été enregistrés. Souhaitez-vous enregistrer ?", 135 "prompt.close.summary": "Vos mappings n'ont pas encore été enregistrés. Souhaitez-vous enregistrer ?",
136 "prompt.close.save": "Enregistrer et fermer", 136 "prompt.close.save": "Enregistrer et fermer",
137 "prompt.close.discard": "Annuler les modifications", 137 "prompt.close.discard": "Annuler les modifications",
138 "prompt.close.cancel": "Annuler",
139 "prompt.open": "Ouvrir",
140 "prompt.cancel": "Annuler",
141 "prompt.connect.title": "Se connecter à un serveur", 138 "prompt.connect.title": "Se connecter à un serveur",
142 "prompt.connect.username": "Nom d'utilisateur :", 139 "prompt.connect.username": "Nom d'utilisateur :",
143 "prompt.connect.address": "Adresse :", 140 "prompt.connect.address": "Adresse :",
diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json
index f3f503aa..82f0c641 100644
--- a/enigma/src/main/resources/lang/zh_cn.json
+++ b/enigma/src/main/resources/lang/zh_cn.json
@@ -37,12 +37,10 @@
37 "menu.view.languages": "语言", 37 "menu.view.languages": "语言",
38 "menu.view.languages.title": "更改语言", 38 "menu.view.languages.title": "更改语言",
39 "menu.view.languages.summary": "新语言将在下次重新启动后应用.", 39 "menu.view.languages.summary": "新语言将在下次重新启动后应用.",
40 "menu.view.languages.ok": "确定",
41 "menu.view.search": "搜索", 40 "menu.view.search": "搜索",
42 "menu.help": "帮助", 41 "menu.help": "帮助",
43 "menu.help.about": "关于", 42 "menu.help.about": "关于",
44 "menu.help.about.title": "%s - 关于", 43 "menu.help.about.title": "%s - 关于",
45 "menu.help.about.ok": "确定",
46 "menu.help.github": "GitHub 页面", 44 "menu.help.github": "GitHub 页面",
47 45
48 "popup_menu.rename": "改名", 46 "popup_menu.rename": "改名",
@@ -100,14 +98,16 @@
100 98
101 "javadocs.edit": "编辑注释", 99 "javadocs.edit": "编辑注释",
102 "javadocs.instruction": "在此处编辑编辑注释.", 100 "javadocs.instruction": "在此处编辑编辑注释.",
103 "javadocs.cancel": "取消", 101
104 "javadocs.save": "保存", 102 "prompt.ok": "确定",
103 "prompt.cancel": "取消",
104 "prompt.save": "保存",
105 "prompt.open": "打开",
105 106
106 "prompt.close.title": "保存更改?", 107 "prompt.close.title": "保存更改?",
107 "prompt.close.summary": "您的映射尚未保存。你想保存吗?", 108 "prompt.close.summary": "您的映射尚未保存。你想保存吗?",
108 "prompt.close.save": "保存并关闭", 109 "prompt.close.save": "保存并关闭",
109 "prompt.close.discard": "放弃更改", 110 "prompt.close.discard": "放弃更改",
110 "prompt.close.cancel": "取消",
111 111
112 "crash.title": "%s - 崩溃报告", 112 "crash.title": "%s - 崩溃报告",
113 "crash.summary": "%s 已经崩溃! =(", 113 "crash.summary": "%s 已经崩溃! =(",
diff --git a/enigma/src/test/java/cuchaz/enigma/ConfigTest.java b/enigma/src/test/java/cuchaz/enigma/ConfigTest.java
new file mode 100644
index 00000000..a44f0375
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/ConfigTest.java
@@ -0,0 +1,86 @@
1package cuchaz.enigma;
2
3import org.junit.Test;
4
5import cuchaz.enigma.config.ConfigContainer;
6
7import static org.junit.Assert.assertEquals;
8
9public class ConfigTest {
10
11 @Test
12 public void serialize() {
13 ConfigContainer cc = new ConfigContainer();
14 cc.data().setString("a", "a");
15 cc.data().section("a").section("b").section("c").setString("a", "abcd");
16 cc.data().section("a").section("b").section("c").setBool("b", true);
17 cc.data().section("a").section("b").section("c").setInt("c", 5);
18 cc.data().section("a").section("b").section("c").setDouble("d", 3.5);
19 cc.data().section("a").section("b").section("c").setRgbColor("e", 0x123456);
20 assertEquals("a=a\n" +
21 "\n" +
22 "[a][b][c]\n" +
23 "a=abcd\n" +
24 "b=true\n" +
25 "c=5\n" +
26 "d=3.5\n" +
27 "e=#123456\n",
28 cc.serialize());
29 }
30
31 @Test
32 public void deserialize() {
33 ConfigContainer cc = new ConfigContainer();
34 cc.data().setString("a", "a");
35 cc.data().section("a").section("b").section("c").setString("a", "abcd");
36 cc.data().section("a").section("b").section("c").setBool("b", true);
37 cc.data().section("a").section("b").section("c").setInt("c", 5);
38 cc.data().section("a").section("b").section("c").setDouble("d", 3.5);
39 cc.data().section("a").section("b").section("c").setRgbColor("e", 0x123456);
40 assertEquals(ConfigContainer.parse("a=a\n" +
41 "\n" +
42 "[a][b][c]\n" +
43 "a=abcd\n" +
44 "b=true\n" +
45 "c=5\n" +
46 "d=3.5\n" +
47 "e=#123456\n").data(), cc.data());
48 }
49
50 @Test
51 public void weirdChars() {
52 ConfigContainer cc = new ConfigContainer();
53 String thing = "\\[],\\,./'\"`~!@#$%^&*()_+-=|}{\n\\\\\r\b\u0000\uffff\u1234";
54 cc.data().section(thing).setString(thing, thing);
55 cc.data().section(thing).setArray("arr", new String[] { thing, thing, thing, thing });
56
57 assertEquals(
58 "[\\\\[\\],\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234]\n" +
59 "\\\\\\[],\\\\,./'\"`~!@#$%^&*()_+-\\=|}{\\n\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234=\\\\[],\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234\n" +
60 "arr=\\\\\\\\[]\\\\,\\\\\\\\\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234,\\\\\\\\[]\\\\,\\\\\\\\\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234,\\\\\\\\[]\\\\,\\\\\\\\\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234,\\\\\\\\[]\\\\,\\\\\\\\\\\\,./'\"`~!@#$%^&*()_+-=|}{\\n\\\\\\\\\\\\\\\\\\u000d\\u0008\\u0000\\uffff\\u1234\n",
61 cc.serialize());
62
63 ConfigContainer cc1 = ConfigContainer.parse(cc.serialize());
64 assertEquals(cc.data(), cc1.data());
65
66 cc1 = ConfigContainer.parse(cc1.serialize());
67 assertEquals(cc.data(), cc1.data());
68 }
69
70 @Test
71 public void syntaxErrors() {
72 assertEquals("", ConfigContainer.parse("abcde").serialize());
73 assertEquals("", ConfigContainer.parse("what\\=?").serialize());
74
75 assertEquals("[a]\nb=c\n", ConfigContainer.parse("[a] what is this\nb=c").serialize());
76 assertEquals("b=c\n", ConfigContainer.parse("[a][ what is this\nb=c").serialize());
77 assertEquals("", ConfigContainer.parse("[").serialize());
78 assertEquals("[a]\na=b\nc=d\n", ConfigContainer.parse("[a]\na=b\n[\nc=d").serialize());
79
80
81 // not technically syntax errors but never something that gets generated
82 assertEquals("", ConfigContainer.parse("[a]").serialize());
83 assertEquals("", ConfigContainer.parse("[a]\n[b]").serialize());
84 }
85
86}