From 75a3442f9ff38222606a1e24753d4a57da1e8c0a Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Tue, 4 Aug 2020 20:42:39 +0200 Subject: Configuration stuff (#301) * Begin writing new config system * Make config work * Save window size and position * Add editor font chooser * Use *.ini for windows and mac instead of *rc * Allow for changing language without having to restart the program * Save selected directory in file dialogs * Make dialog visible after moving it to the correct position * Don't change theme on the fly since it's broken * Remove unused gui parameter * Use xdg-open to open URLs on Linux since Desktop.browse doesn't work, at least not on my PC * Fix default proposed highlight color * Multi font selection dialog thingy * Remember network options * Make font selection dialog actually work * Collapse general actions ("OK", "Cancel", ..) into one translation * Localize font dialog * Use enum name when saving colors for consistency with currently selected theme * Save size of split panes * Import old config * Add test & fix some parts of the config serializer * TranslationChangeListener/TranslationUtil -> LanguageChangeListener/LanguageUtil--- .../java/cuchaz/enigma/gui/EnigmaSyntaxKit.java | 74 +++-- .../src/main/java/cuchaz/enigma/gui/Gui.java | 66 ++++- .../main/java/cuchaz/enigma/gui/GuiController.java | 9 +- .../src/main/java/cuchaz/enigma/gui/Main.java | 15 +- .../cuchaz/enigma/gui/MethodTreeCellRenderer.java | 15 +- .../main/java/cuchaz/enigma/gui/config/Config.java | 263 ---------------- .../java/cuchaz/enigma/gui/config/Decompiler.java | 17 ++ .../java/cuchaz/enigma/gui/config/LookAndFeel.java | 67 +++++ .../java/cuchaz/enigma/gui/config/NetConfig.java | 57 ++++ .../enigma/gui/config/OldConfigImporter.java | 26 ++ .../main/java/cuchaz/enigma/gui/config/Themes.java | 83 ++++-- .../java/cuchaz/enigma/gui/config/UiConfig.java | 330 +++++++++++++++++++++ .../cuchaz/enigma/gui/config/legacy/Config.java | 109 +++++++ .../java/cuchaz/enigma/gui/dialog/AboutDialog.java | 2 +- .../cuchaz/enigma/gui/dialog/ChangeDialog.java | 16 +- .../enigma/gui/dialog/ConnectToServerDialog.java | 17 +- .../enigma/gui/dialog/CreateServerDialog.java | 5 +- .../java/cuchaz/enigma/gui/dialog/FontDialog.java | 126 ++++++++ .../cuchaz/enigma/gui/dialog/JavadocDialog.java | 15 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 205 ++++++++----- .../enigma/gui/events/ThemeChangeListener.java | 2 +- .../enigma/gui/highlight/BoxHighlightPainter.java | 10 +- .../gui/highlight/SelectionHighlightPainter.java | 7 +- .../java/cuchaz/enigma/gui/panels/DeobfPanel.java | 11 +- .../java/cuchaz/enigma/gui/panels/EditorPanel.java | 13 +- .../cuchaz/enigma/gui/panels/IdentifierPanel.java | 5 + .../java/cuchaz/enigma/gui/panels/ObfPanel.java | 11 +- .../main/java/cuchaz/enigma/gui/util/GuiUtil.java | 24 +- .../enigma/gui/util/LanguageChangeListener.java | 7 + .../java/cuchaz/enigma/gui/util/LanguageUtil.java | 25 ++ .../java/cuchaz/enigma/gui/util/ScaleUtil.java | 27 +- 31 files changed, 1187 insertions(+), 472 deletions(-) delete mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java (limited to 'enigma-swing/src/main/java') 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 45f87bb..27c866c 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; import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; import de.sciss.syntaxpane.util.Configuration; -import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.config.UiConfig; public class EnigmaSyntaxKit extends JavaSyntaxKit { - private static Configuration configuration = null; + private static Configuration configuration = null; - @Override - public Configuration getConfig() { - if (configuration == null) { - initConfig(DefaultSyntaxKit.getConfig(JavaSyntaxKit.class)); - } - return configuration; - } + @Override + public Configuration getConfig() { + if (configuration == null) { + initConfig(DefaultSyntaxKit.getConfig(JavaSyntaxKit.class)); + } + return configuration; + } - public void initConfig(Configuration baseConfig) { - configuration = flattenConfiguration(baseConfig, EnigmaSyntaxKit.class); + public void initConfig(Configuration baseConfig) { + configuration = flattenConfiguration(baseConfig, EnigmaSyntaxKit.class); // Remove all actions except a select few because they disregard the // editable state of the editor, or at least are useless anyway because @@ -34,38 +34,36 @@ public class EnigmaSyntaxKit extends JavaSyntaxKit { s.startsWith("Action.jump-to-pair") || s.startsWith("Action.quick-find"))); - // See de.sciss.syntaxpane.TokenType - configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); - configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3"); - configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0"); - configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1"); - configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1"); - configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0"); - configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1"); - configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2"); - configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1"); - configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0"); - configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0"); - configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + ""); - configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + ""); - configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + ""); - configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config + // See de.sciss.syntaxpane.TokenType + configuration.put("Style.KEYWORD", String.format("%d, 0", UiConfig.getHighlightColor().getRGB())); + configuration.put("Style.KEYWORD2", String.format("%d, 3", UiConfig.getHighlightColor().getRGB())); + configuration.put("Style.STRING", String.format("%d, 0", UiConfig.getStringColor().getRGB())); + configuration.put("Style.STRING2", String.format("%d, 1", UiConfig.getStringColor().getRGB())); + configuration.put("Style.NUMBER", String.format("%d, 1", UiConfig.getNumberColor().getRGB())); + configuration.put("Style.OPERATOR", String.format("%d, 0", UiConfig.getOperatorColor().getRGB())); + configuration.put("Style.DELIMITER", String.format("%d, 1", UiConfig.getDelimiterColor().getRGB())); + configuration.put("Style.TYPE", String.format("%d, 2", UiConfig.getTypeColor().getRGB())); + configuration.put("Style.TYPE2", String.format("%d, 1", UiConfig.getTypeColor().getRGB())); + configuration.put("Style.IDENTIFIER", String.format("%d, 0", UiConfig.getIdentifierColor().getRGB())); + configuration.put("Style.DEFAULT", String.format("%d, 0", UiConfig.getTextColor().getRGB())); + configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, String.format("%d", UiConfig.getLineNumbersBackgroundColor().getRGB())); + configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, String.format("%d", UiConfig.getLineNumbersForegroundColor().getRGB())); + configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, String.format("%d", UiConfig.getLineNumbersSelectedColor().getRGB())); + configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config - configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); + configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); - if (Config.getInstance().editorFont != null) { - configuration.put("DefaultFont", Config.getInstance().editorFont); - } + configuration.put("DefaultFont", UiConfig.encodeFont(UiConfig.getEditorFont())); } /** * Creates a new configuration from the passed configuration so that it has - * no parents and all its values are on the same level. This is needed since + * no parents and all its values are on the same level. This is needed since * there is no way to remove map entries from parent configurations. * * @param source the configuration to flatten - * @param configClass the class for the new configuration - * @return a new configuration + * @param configClass the class for the new configuration + * @return a new configuration */ private static Configuration flattenConfiguration(Configuration source, Class configClass) { Configuration config = new Configuration(configClass, null); @@ -73,10 +71,10 @@ public class EnigmaSyntaxKit extends JavaSyntaxKit { config.put(p, source.getString(p)); } return config; - } + } - public static void invalidate() { - configuration = null; - } + public static void invalidate() { + configuration = null; + } } 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 3aec6b8..92b6e52 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; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FileDialog; +import java.awt.Point; import java.awt.event.*; import java.nio.file.Path; import java.util.*; @@ -31,8 +32,8 @@ import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProfile; import cuchaz.enigma.analysis.*; import cuchaz.enigma.classhandle.ClassHandle; -import cuchaz.enigma.gui.config.Config; import cuchaz.enigma.gui.config.Themes; +import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.CrashDialog; import cuchaz.enigma.gui.dialog.JavadocDialog; import cuchaz.enigma.gui.dialog.SearchDialog; @@ -43,6 +44,8 @@ import cuchaz.enigma.gui.elements.ValidatableUi; import cuchaz.enigma.gui.events.EditorActionListener; import cuchaz.enigma.gui.panels.*; import cuchaz.enigma.gui.util.History; +import cuchaz.enigma.gui.util.LanguageChangeListener; +import cuchaz.enigma.gui.util.LanguageUtil; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.network.Message; import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket; @@ -59,7 +62,7 @@ import cuchaz.enigma.utils.I18n; import cuchaz.enigma.utils.validation.ParameterizedMessage; import cuchaz.enigma.utils.validation.ValidationContext; -public class Gui { +public class Gui implements LanguageChangeListener { private final ObfPanel obfPanel; private final DeobfPanel deobfPanel; @@ -73,10 +76,10 @@ public class Gui { public FileDialog jarFileChooser; public FileDialog tinyMappingsFileChooser; - public SearchDialog searchDialog; public JFileChooser enigmaMappingsFileChooser; public JFileChooser exportSourceFileChooser; public FileDialog exportJarFileChooser; + public SearchDialog searchDialog; private GuiController controller; private JFrame frame; private JPanel classesPanel; @@ -88,6 +91,7 @@ public class Gui { private JList tokens; private JTabbedPane tabs; + private JSplitPane splitCenter; private JSplitPane splitRight; private JSplitPane logSplit; private CollapsibleTabbedPane logTabs; @@ -107,7 +111,7 @@ public class Gui { private final HashBiMap editors = HashBiMap.create(); public Gui(EnigmaProfile profile) { - Config.getInstance().lookAndFeel.setGlobalLAF(); + UiConfig.getLookAndFeel().setGlobalLAF(); // init frame this.frame = new JFrame(Enigma.NAME); @@ -328,10 +332,19 @@ public class Gui { splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); splitRight.setResizeWeight(1); // let the left side take all the slack splitRight.resetToPreferredSizes(); - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); + splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); splitCenter.setResizeWeight(0); // let the right side take all the slack pane.add(splitCenter, BorderLayout.CENTER); + // restore state + int[] layout = UiConfig.getLayout(); + if (layout.length >= 4) { + this.splitClasses.setDividerLocation(layout[0]); + this.splitCenter.setDividerLocation(layout[1]); + this.splitRight.setDividerLocation(layout[2]); + this.logSplit.setDividerLocation(layout[3]); + } + // init menus this.menuBar = new MenuBar(this); this.frame.setJMenuBar(this.menuBar.getUi()); @@ -358,11 +371,20 @@ public class Gui { // show the frame pane.doLayout(); - this.frame.setSize(ScaleUtil.getDimension(1024, 576)); + this.frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576))); this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - this.frame.setLocationRelativeTo(null); + + Point windowPos = UiConfig.getWindowPos("Main Window", null); + if (windowPos != null) { + this.frame.setLocation(windowPos); + } else { + this.frame.setLocationRelativeTo(null); + } + this.frame.setVisible(true); + + LanguageUtil.addListener(this); } public JFrame getFrame() { @@ -692,11 +714,20 @@ public class Gui { } return null; - }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); + }, I18n.translate("prompt.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.cancel")); } } private void exit() { + UiConfig.setWindowPos("Main Window", this.frame.getLocationOnScreen()); + UiConfig.setWindowSize("Main Window", this.frame.getSize()); + UiConfig.setLayout( + this.splitClasses.getDividerLocation(), + this.splitCenter.getDividerLocation(), + this.splitRight.getDividerLocation(), + this.logSplit.getDividerLocation()); + UiConfig.save(); + if (searchDialog != null) { searchDialog.dispose(); } @@ -848,6 +879,25 @@ public class Gui { } } + @Override + public void retranslateUi() { + this.jarFileChooser.setTitle(I18n.translate("menu.file.jar.open")); + this.exportJarFileChooser.setTitle(I18n.translate("menu.file.export.jar")); + this.tabs.setTitleAt(0, I18n.translate("info_panel.tree.inheritance")); + this.tabs.setTitleAt(1, I18n.translate("info_panel.tree.implementations")); + this.tabs.setTitleAt(2, I18n.translate("info_panel.tree.calls")); + this.logTabs.setTitleAt(0, I18n.translate("log_panel.users")); + this.logTabs.setTitleAt(1, I18n.translate("log_panel.messages")); + this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); + + this.updateUiState(); + + this.menuBar.retranslateUi(); + this.obfPanel.retranslateUi(); + this.deobfPanel.retranslateUi(); + this.infoPanel.retranslateUi(); + } + public void setConnectionState(ConnectionState state) { connectionState = state; 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 2f5e5e1..88ebd64 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; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.analysis.*; import cuchaz.enigma.api.service.ObfuscationTestService; -import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.classhandle.ClassHandle; import cuchaz.enigma.classhandle.ClassHandleProvider; -import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.classprovider.ClasspathClassProvider; +import cuchaz.enigma.gui.config.NetConfig; +import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.gui.stats.StatsGenerator; import cuchaz.enigma.gui.stats.StatsMember; @@ -98,7 +99,7 @@ public class GuiController implements ClientPacketHandler { return ProgressDialog.runOffThread(gui.getFrame(), progress -> { project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress); indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); - chp = new ClassHandleProvider(project, Config.getInstance().decompiler.service); + chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service); gui.onFinishOpenJar(jarPath.getFileName().toString()); refreshClasses(); }); @@ -563,7 +564,7 @@ public class GuiController implements ClientPacketHandler { server.start(); client = new EnigmaClient(this, "127.0.0.1", port); client.connect(); - client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); + client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, NetConfig.getUsername())); gui.setConnectionState(ConnectionState.HOSTING); } 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 1f3aa2c..0589f36 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 @@ package cuchaz.enigma.gui; -import cuchaz.enigma.EnigmaProfile; -import cuchaz.enigma.gui.config.Config; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; - -import cuchaz.enigma.utils.I18n; -import joptsimple.*; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.google.common.io.MoreFiles; +import joptsimple.*; + +import cuchaz.enigma.EnigmaProfile; +import cuchaz.enigma.gui.config.UiConfig; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.utils.I18n; public class Main { @@ -54,7 +53,7 @@ public class Main { EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile)); - I18n.setLanguage(Config.getInstance().language); + I18n.setLanguage(UiConfig.getLanguage()); Gui gui = new Gui(parsedProfile); GuiController controller = gui.getController(); 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 1eead6e..3f38f4f 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 @@ package cuchaz.enigma.gui; -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; -import cuchaz.enigma.gui.config.Config; +import java.awt.Component; +import java.awt.Font; -import javax.swing.*; +import javax.swing.JTree; import javax.swing.tree.TreeCellRenderer; -import java.awt.*; + +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.gui.config.UiConfig; class MethodTreeCellRenderer implements TreeCellRenderer { @@ -29,12 +31,11 @@ class MethodTreeCellRenderer implements TreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); - Config config = Config.getInstance(); if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) { - ret.setForeground(new Color(config.defaultTextColor)); + ret.setForeground(UiConfig.getTextColor()); ret.setFont(ret.getFont().deriveFont(Font.PLAIN)); } else { - ret.setForeground(new Color(config.numberColor)); + ret.setForeground(UiConfig.getNumberColor()); ret.setFont(ret.getFont().deriveFont(Font.ITALIC)); } 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 a389196..0000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java +++ /dev/null @@ -1,263 +0,0 @@ -package cuchaz.enigma.gui.config; - -import com.bulenkov.darcula.DarculaLaf; -import com.google.common.io.Files; -import com.google.gson.*; -import cuchaz.enigma.source.DecompilerService; -import cuchaz.enigma.source.Decompilers; - -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import javax.swing.plaf.metal.MetalLookAndFeel; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.Charset; - -public class Config { - public static class AlphaColorEntry { - public Integer rgb; - public float alpha = 1.0f; - - public AlphaColorEntry(Integer rgb, float alpha) { - this.rgb = rgb; - this.alpha = alpha; - } - - public Color get() { - if (rgb == null) { - return new Color(0, 0, 0, 0); - } - - Color baseColor = new Color(rgb); - return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha)); - } - } - - public enum LookAndFeel { - DEFAULT("Default"), - DARCULA("Darcula"), - SYSTEM("System"), - NONE("None (JVM default)"); - - // the "JVM default" look and feel, get it at the beginning and store it so we can set it later - private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); - private final String name; - - LookAndFeel(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setGlobalLAF() { - try { - switch (this) { - case NONE: - UIManager.setLookAndFeel(NONE_LAF); - break; - case DEFAULT: - UIManager.setLookAndFeel(new MetalLookAndFeel()); - break; - case DARCULA: - UIManager.setLookAndFeel(new DarculaLaf()); - break; - case SYSTEM: - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - } catch (Exception e){ - throw new Error("Failed to set global look and feel", e); - } - } - - public static boolean isDarkLaf() { - // 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 - JPanel panel = new JPanel(); - panel.setSize(new Dimension(10, 10)); - panel.doLayout(); - - BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB); - panel.printAll(image.getGraphics()); - - Color c = new Color(image.getRGB(0, 0)); - - // convert the color we got to grayscale - int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); - return b < 85; - } - - public void apply(Config config) { - boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf(); - if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 - config.lineNumbersForeground = 0x333300; - config.lineNumbersBackground = 0xEEEEFF; - config.lineNumbersSelected = 0xCCCCEE; - config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f); - config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f); - config.proposedColor = new AlphaColorEntry(0x000000, 0.075f); - config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f); - config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f); - config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f); - config.editorBackground = 0xFFFFFF; - config.highlightColor = 0x3333EE; - config.caretColor = 0x000000; - config.selectionHighlightColor = 0x000000; - config.stringColor = 0xCC6600; - config.numberColor = 0x999933; - config.operatorColor = 0x000000; - config.delimiterColor = 0x000000; - config.typeColor = 0x000000; - config.identifierColor = 0x000000; - config.defaultTextColor = 0x000000; - } else {//Based off colors found here: https://github.com/dracula/dracula-theme/ - config.lineNumbersForeground = 0xA4A4A3; - config.lineNumbersBackground = 0x313335; - config.lineNumbersSelected = 0x606366; - config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f); - config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f); - config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f); - config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f); - config.proposedColor = new AlphaColorEntry(0x606366, 0.3f); - config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f); - config.editorBackground = 0x282A36; - config.highlightColor = 0xFF79C6; - config.caretColor = 0xF8F8F2; - config.selectionHighlightColor = 0xF8F8F2; - config.stringColor = 0xF1FA8C; - config.numberColor = 0xBD93F9; - config.operatorColor = 0xF8F8F2; - config.delimiterColor = 0xF8F8F2; - config.typeColor = 0xF8F8F2; - config.identifierColor = 0xF8F8F2; - config.defaultTextColor = 0xF8F8F2; - } - } - } - - public enum Decompiler { - PROCYON("Procyon", Decompilers.PROCYON), - CFR("CFR", Decompilers.CFR); - - public final DecompilerService service; - public final String name; - - Decompiler(String name, DecompilerService service) { - this.name = name; - this.service = service; - } - } - - private static final File DIR_HOME = new File(System.getProperty("user.home")); - private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); - private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); - private static final Config INSTANCE = new Config(); - - private final transient Gson gson; // transient to exclude it from being exposed - - public AlphaColorEntry obfuscatedColor; - public AlphaColorEntry obfuscatedColorOutline; - public AlphaColorEntry proposedColor; - public AlphaColorEntry proposedColorOutline; - public AlphaColorEntry deobfuscatedColor; - public AlphaColorEntry deobfuscatedColorOutline; - - public String editorFont; - - public Integer editorBackground; - public Integer highlightColor; - public Integer caretColor; - public Integer selectionHighlightColor; - - public Integer stringColor; - public Integer numberColor; - public Integer operatorColor; - public Integer delimiterColor; - public Integer typeColor; - public Integer identifierColor; - public Integer defaultTextColor; - - public Integer lineNumbersBackground; - public Integer lineNumbersSelected; - public Integer lineNumbersForeground; - - public String language = I18n.DEFAULT_LANGUAGE; - - public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT; - - public float scaleFactor = 1.0f; - - public Decompiler decompiler = Decompiler.PROCYON; - - private Config() { - gson = new GsonBuilder() - .registerTypeAdapter(Integer.class, new IntSerializer()) - .registerTypeAdapter(Integer.class, new IntDeserializer()) - .registerTypeAdapter(Config.class, (InstanceCreator) type -> this) - .setPrettyPrinting() - .create(); - try { - this.loadConfig(); - } catch (IOException ignored) { - try { - this.reset(); - } catch (IOException ignored1) { - } - } - } - - public void loadConfig() throws IOException { - if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs(); - File configFile = new File(ENIGMA_DIR, "config.json"); - boolean loaded = false; - - if (configFile.exists()) { - try { - gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class); - loaded = true; - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (!loaded) { - this.reset(); - Files.touch(configFile); - } - saveConfig(); - } - - public void saveConfig() throws IOException { - Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this)); - } - - public void reset() throws IOException { - this.lookAndFeel = LookAndFeel.DEFAULT; - this.lookAndFeel.apply(this); - this.decompiler = Decompiler.PROCYON; - this.language = I18n.DEFAULT_LANGUAGE; - this.saveConfig(); - } - - private static class IntSerializer implements JsonSerializer { - @Override - public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase()); - } - } - - private static class IntDeserializer implements JsonDeserializer { - @Override - public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { - return (int) Long.parseLong(json.getAsString().replace("#", ""), 16); - } - } - - public static Config getInstance() { - return INSTANCE; - } -} 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 0000000..0f9e41c --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java @@ -0,0 +1,17 @@ +package cuchaz.enigma.gui.config; + +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; + +public enum Decompiler { + PROCYON("Procyon", Decompilers.PROCYON), + CFR("CFR", Decompilers.CFR); + + public final DecompilerService service; + public final String name; + + Decompiler(String name, DecompilerService service) { + this.name = name; + this.service = service; + } +} 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 0000000..1c70d43 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java @@ -0,0 +1,67 @@ +package cuchaz.enigma.gui.config; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.image.BufferedImage; + +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.plaf.metal.MetalLookAndFeel; + +import com.bulenkov.darcula.DarculaLaf; + +public enum LookAndFeel { + DEFAULT("Default"), + DARCULA("Darcula"), + SYSTEM("System"), + NONE("None (JVM default)"); + + // the "JVM default" look and feel, get it at the beginning and store it so we can set it later + private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); + private final String name; + + LookAndFeel(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setGlobalLAF() { + try { + switch (this) { + case NONE: + UIManager.setLookAndFeel(NONE_LAF); + break; + case DEFAULT: + UIManager.setLookAndFeel(new MetalLookAndFeel()); + break; + case DARCULA: + UIManager.setLookAndFeel(new DarculaLaf()); + break; + case SYSTEM: + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + } catch (Exception e) { + throw new Error("Failed to set global look and feel", e); + } + } + + public static boolean isDarkLaf() { + // 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 + JPanel panel = new JPanel(); + panel.setSize(new Dimension(10, 10)); + panel.doLayout(); + + BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB); + panel.printAll(image.getGraphics()); + + Color c = new Color(image.getRGB(0, 0)); + + // convert the color we got to grayscale + int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); + return b < 85; + } + +} 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 0000000..4439cb8 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/NetConfig.java @@ -0,0 +1,57 @@ +package cuchaz.enigma.gui.config; + +import cuchaz.enigma.config.ConfigContainer; +import cuchaz.enigma.network.EnigmaServer; + +public final class NetConfig { + + private NetConfig() { + } + + private static final ConfigContainer cfg = ConfigContainer.getOrCreate("enigma/net"); + + public static void save() { + cfg.save(); + } + + public static String getUsername() { + return cfg.data().section("User").setIfAbsentString("Username", System.getProperty("user.name", "user")); + } + + public static void setUsername(String username) { + cfg.data().section("User").setString("Username", username); + } + + public static String getPassword() { + return cfg.data().section("Remote").getString("Password").orElse(""); + } + + public static void setPassword(String password) { + cfg.data().section("Remote").setString("Password", password); + } + + public static String getRemoteAddress() { + return cfg.data().section("Remote").getString("Address").orElse(""); + } + + public static void setRemoteAddress(String address) { + cfg.data().section("Remote").setString("Address", address); + } + + public static String getServerPassword() { + return cfg.data().section("Server").getString("Password").orElse(""); + } + + public static void setServerPassword(String password) { + cfg.data().section("Server").setString("Password", password); + } + + public static int getServerPort() { + return cfg.data().section("Server").setIfAbsentInt("Port", EnigmaServer.DEFAULT_PORT); + } + + public static void setServerPort(int port) { + cfg.data().section("Server").setInt("Port", port); + } + +} 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 0000000..660d231 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/OldConfigImporter.java @@ -0,0 +1,26 @@ +package cuchaz.enigma.gui.config; + +import java.awt.Font; + +import cuchaz.enigma.gui.config.legacy.Config; + +public final class OldConfigImporter { + + private OldConfigImporter() { + } + + @SuppressWarnings("deprecation") + public static void doImport() { + if (Config.CONFIG_FILE.exists()) { + Config config = new Config(); + if (config.editorFont != null) { + UiConfig.setEditorFont(Font.decode(config.editorFont)); + } + UiConfig.setLanguage(config.language); + UiConfig.setLookAndFeel(config.lookAndFeel); + UiConfig.setScaleFactor(config.scaleFactor); + UiConfig.setDecompiler(config.decompiler); + } + } + +} 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 fd40cb7..4905b1c 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 @@ package cuchaz.enigma.gui.config; -import java.io.IOException; +import java.awt.Font; import java.util.HashSet; import java.util.Set; +import javax.swing.UIManager; + import com.google.common.collect.ImmutableMap; +import de.sciss.syntaxpane.DefaultSyntaxKit; + import cuchaz.enigma.gui.EnigmaSyntaxKit; import cuchaz.enigma.gui.events.ThemeChangeListener; import cuchaz.enigma.gui.highlight.BoxHighlightPainter; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.source.RenamableTokenType; -import de.sciss.syntaxpane.DefaultSyntaxKit; public class Themes { private static final Set listeners = new HashSet<>(); - public static void setLookAndFeel(Config.LookAndFeel lookAndFeel) { - Config.getInstance().lookAndFeel = lookAndFeel; - updateTheme(); - } - public static void updateTheme() { - Config config = Config.getInstance(); - config.lookAndFeel.setGlobalLAF(); - config.lookAndFeel.apply(config); - try { - config.saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } + LookAndFeel laf = UiConfig.getLookAndFeel(); + laf.setGlobalLAF(); + setFonts(); + UiConfig.setLookAndFeelDefaults(laf, LookAndFeel.isDarkLaf()); EnigmaSyntaxKit.invalidate(); DefaultSyntaxKit.initKit(); DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); ImmutableMap boxHighlightPainters = getBoxHighlightPainters(); - listeners.forEach(l -> l.onThemeChanged(config.lookAndFeel, boxHighlightPainters)); + listeners.forEach(l -> l.onThemeChanged(laf, boxHighlightPainters)); ScaleUtil.applyScaling(); + UiConfig.save(); + } + + private static void setFonts() { + if (UiConfig.shouldUseCustomFonts()) { + Font small = UiConfig.getSmallFont(); + Font bold = UiConfig.getDefaultFont(); + Font normal = UiConfig.getDefault2Font(); + + UIManager.put("CheckBox.font", bold); + UIManager.put("CheckBoxMenuItem.font", bold); + UIManager.put("CheckBoxMenuItem.acceleratorFont", small); + UIManager.put("ColorChooser.font", normal); + UIManager.put("ComboBox.font", bold); + UIManager.put("DesktopIcon.font", bold); + UIManager.put("EditorPane.font", normal); + UIManager.put("InternalFrame.titleFont", bold); + UIManager.put("FormattedTextField.font", normal); + UIManager.put("Label.font", bold); + UIManager.put("List.font", bold); + UIManager.put("Menu.acceleratorFont", small); + UIManager.put("Menu.font", bold); + UIManager.put("MenuBar.font", bold); + UIManager.put("MenuItem.acceleratorFont", small); + UIManager.put("MenuItem.font", bold); + UIManager.put("OptionPane.font", normal); + UIManager.put("Panel.font", normal); + UIManager.put("PasswordField.font", normal); + UIManager.put("PopupMenu.font", bold); + UIManager.put("ProgressBar.font", bold); + UIManager.put("RadioButton.font", bold); + UIManager.put("RadioButtonMenuItem.acceleratorFont", small); + UIManager.put("RadioButtonMenuItem.font", bold); + UIManager.put("ScrollPane.font", normal); + UIManager.put("Slider.font", bold); + UIManager.put("Spinner.font", bold); + UIManager.put("TabbedPane.font", bold); + UIManager.put("Table.font", normal); + UIManager.put("TableHeader.font", normal); + UIManager.put("TextArea.font", normal); + UIManager.put("TextField.font", normal); + UIManager.put("TextPane.font", normal); + UIManager.put("TitledBorder.font", bold); + UIManager.put("ToggleButton.font", bold); + UIManager.put("ToolBar.font", bold); + UIManager.put("ToolTip.font", normal); + UIManager.put("Tree.font", normal); + UIManager.put("Viewport.font", normal); + UIManager.put("Button.font", bold); + } } public static ImmutableMap getBoxHighlightPainters() { - Config config = Config.getInstance(); return ImmutableMap.of( - RenamableTokenType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), - RenamableTokenType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), - RenamableTokenType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) + RenamableTokenType.OBFUSCATED, BoxHighlightPainter.create(UiConfig.getObfuscatedColor(), UiConfig.getObfuscatedOutlineColor()), + RenamableTokenType.PROPOSED, BoxHighlightPainter.create(UiConfig.getProposedColor(), UiConfig.getProposedOutlineColor()), + RenamableTokenType.DEOBFUSCATED, BoxHighlightPainter.create(UiConfig.getDeobfuscatedColor(), UiConfig.getDeobfuscatedOutlineColor()) ); } 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 0000000..fa2c4e3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java @@ -0,0 +1,330 @@ +package cuchaz.enigma.gui.config; + +import java.awt.*; +import java.util.Optional; +import java.util.OptionalInt; + +import cuchaz.enigma.config.ConfigContainer; +import cuchaz.enigma.config.ConfigSection; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.utils.I18n; + +public final class UiConfig { + + private UiConfig() { + } + + // General UI configuration such as localization + private static final ConfigContainer ui = ConfigContainer.getOrCreate("enigma/enigmaui"); + // Swing specific configuration such as theming + private static final ConfigContainer swing = ConfigContainer.getOrCreate("enigma/enigmaswing"); + + static { + if (!swing.existsOnDisk() && !ui.existsOnDisk()) { + OldConfigImporter.doImport(); + } + } + + public static void save() { + ui.save(); + swing.save(); + } + + public static String getLanguage() { + return ui.data().section("General").setIfAbsentString("Language", I18n.DEFAULT_LANGUAGE); + } + + public static void setLanguage(String language) { + ui.data().section("General").setString("Language", language); + } + + public static float getScaleFactor() { + return (float) swing.data().section("General").setIfAbsentDouble("Scale Factor", 1.0); + } + + public static void setScaleFactor(float scale) { + swing.data().section("General").setDouble("Scale Factor", scale); + } + + public static int[] getLayout() { + return swing.data().section("Main Window").getIntArray("Layout").orElseGet(() -> new int[] { -1, -1, -1, -1 }); + } + + public static void setLayout(int leftV, int left, int right, int rightH) { + swing.data().section("Main Window").setIntArray("Layout", new int[] { leftV, left, right, rightH }); + } + + public static LookAndFeel getLookAndFeel() { + return swing.data().section("Themes").setIfAbsentEnum(LookAndFeel::valueOf, "Current", LookAndFeel.NONE); + } + + public static void setLookAndFeel(LookAndFeel laf) { + swing.data().section("Themes").setEnum("Current", laf); + } + + public static Decompiler getDecompiler() { + return ui.data().section("Decompiler").setIfAbsentEnum(Decompiler::valueOf, "Current", Decompiler.PROCYON); + } + + public static void setDecompiler(Decompiler d) { + ui.data().section("Decompiler").setEnum("Current", d); + } + + private static Color fromComponents(int rgb, double alpha) { + int rgba = rgb & 0xFFFFFF | (int) (alpha * 255) << 24; + return new Color(rgba, true); + } + + private static Color getThemeColorRgba(String colorName) { + ConfigSection s = swing.data().section("Themes").section(getLookAndFeel().name()).section("Colors"); + return fromComponents(s.getRgbColor(colorName).orElse(0), s.getDouble(String.format("%s Alpha", colorName)).orElse(0)); + } + + private static Color getThemeColorRgb(String colorName) { + ConfigSection s = swing.data().section("Themes").section(getLookAndFeel().name()).section("Colors"); + return new Color(s.getRgbColor(colorName).orElse(0)); + } + + public static Color getObfuscatedColor() { + return getThemeColorRgba("Obfuscated"); + } + + public static Color getObfuscatedOutlineColor() { + return getThemeColorRgba("Obfuscated Outline"); + } + + public static Color getProposedColor() { + return getThemeColorRgba("Proposed"); + } + + public static Color getProposedOutlineColor() { + return getThemeColorRgba("Proposed Outline"); + } + + public static Color getDeobfuscatedColor() { + return getThemeColorRgba("Deobfuscated"); + } + + public static Color getDeobfuscatedOutlineColor() { + return getThemeColorRgba("Deobfuscated Outline"); + } + + public static Color getEditorBackgroundColor() { + return getThemeColorRgb("Editor Background"); + } + + public static Color getHighlightColor() { + return getThemeColorRgb("Highlight"); + } + + public static Color getCaretColor() { + return getThemeColorRgb("Caret"); + } + + public static Color getSelectionHighlightColor() { + return getThemeColorRgb("Selection Highlight"); + } + + public static Color getStringColor() { + return getThemeColorRgb("String"); + } + + public static Color getNumberColor() { + return getThemeColorRgb("Number"); + } + + public static Color getOperatorColor() { + return getThemeColorRgb("Operator"); + } + + public static Color getDelimiterColor() { + return getThemeColorRgb("Delimiter"); + } + + public static Color getTypeColor() { + return getThemeColorRgb("Type"); + } + + public static Color getIdentifierColor() { + return getThemeColorRgb("Identifier"); + } + + public static Color getTextColor() { + return getThemeColorRgb("Text"); + } + + public static Color getLineNumbersForegroundColor() { + return getThemeColorRgb("Line Numbers Foreground"); + } + + public static Color getLineNumbersBackgroundColor() { + return getThemeColorRgb("Line Numbers Background"); + } + + public static Color getLineNumbersSelectedColor() { + return getThemeColorRgb("Line Numbers Selected"); + } + + public static boolean shouldUseCustomFonts() { + return swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setIfAbsentBool("Use Custom", false); + } + + public static void setUseCustomFonts(boolean b) { + swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setBool("Use Custom", b); + } + + public static Optional getFont(String name) { + Optional spec = swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").getString(name); + return spec.map(Font::decode); + } + + public static void setFont(String name, Font font) { + swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setString(name, encodeFont(font)); + } + + public static Font getDefaultFont() { + return getFont("Default").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG).deriveFont(Font.BOLD))); + } + + public static void setDefaultFont(Font font) { + setFont("Default", font); + } + + public static Font getDefault2Font() { + return getFont("Default 2").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); + } + + public static void setDefault2Font(Font font) { + setFont("Default 2", font); + } + + public static Font getSmallFont() { + return getFont("Small").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); + } + + public static void setSmallFont(Font font) { + setFont("Small", font); + } + + public static Font getEditorFont() { + return getFont("Editor").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.MONOSPACED))); + } + + public static void setEditorFont(Font font) { + setFont("Editor", font); + } + + public static String encodeFont(Font font) { + int style = font.getStyle(); + String s = style == (Font.BOLD | Font.ITALIC) ? "bolditalic" : style == Font.ITALIC ? "italic" : style == Font.BOLD ? "bold" : "plain"; + return String.format("%s-%s-%s", font.getName(), s, font.getSize()); + } + + public static Dimension getWindowSize(String window, Dimension fallback) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + ConfigSection section = swing.data().section(window); + OptionalInt width = section.getInt(String.format("Width %s", screenSize.width)); + OptionalInt height = section.getInt(String.format("Height %s", screenSize.height)); + if (width.isPresent() && height.isPresent()) { + return new Dimension(width.getAsInt(), height.getAsInt()); + } else { + return fallback; + } + } + + public static void setWindowSize(String window, Dimension dim) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + ConfigSection section = swing.data().section(window); + section.setInt(String.format("Width %s", screenSize.width), dim.width); + section.setInt(String.format("Height %s", screenSize.height), dim.height); + } + + public static Point getWindowPos(String window, Point fallback) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + ConfigSection section = swing.data().section(window); + OptionalInt x = section.getInt(String.format("X %s", screenSize.width)); + OptionalInt y = section.getInt(String.format("Y %s", screenSize.height)); + if (x.isPresent() && y.isPresent()) { + return new Point(x.getAsInt(), y.getAsInt()); + } else { + return fallback; + } + } + + public static void setWindowPos(String window, Point rect) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + ConfigSection section = swing.data().section(window); + section.setInt(String.format("X %s", screenSize.width), rect.x); + section.setInt(String.format("Y %s", screenSize.height), rect.y); + } + + public static String getLastSelectedDir() { + return swing.data().section("File Dialog").getString("Selected").orElse(""); + } + + public static void setLastSelectedDir(String directory) { + swing.data().section("File Dialog").setString("Selected", directory); + } + + public static void setLookAndFeelDefaults(LookAndFeel laf, boolean isDark) { + ConfigSection s = swing.data().section("Themes").section(laf.name()).section("Colors"); + if (!isDark) { + // Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 + s.setIfAbsentRgbColor("Line Numbers Foreground", 0x333300); + s.setIfAbsentRgbColor("Line Numbers Background", 0xEEEEFF); + s.setIfAbsentRgbColor("Line Numbers Selected", 0xCCCCEE); + s.setIfAbsentRgbColor("Obfuscated", 0xFFDCDC); + s.setIfAbsentDouble("Obfuscated Alpha", 1.0); + s.setIfAbsentRgbColor("Obfuscated Outline", 0xA05050); + s.setIfAbsentDouble("Obfuscated Outline Alpha", 1.0); + s.setIfAbsentRgbColor("Proposed", 0x000000); + s.setIfAbsentDouble("Proposed Alpha", 0.15); + s.setIfAbsentRgbColor("Proposed Outline", 0x000000); + s.setIfAbsentDouble("Proposed Outline Alpha", 0.75); + s.setIfAbsentRgbColor("Deobfuscated", 0xDCFFDC); + s.setIfAbsentDouble("Deobfuscated Alpha", 1.0); + s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50A050); + s.setIfAbsentDouble("Deobfuscated Outline Alpha", 1.0); + s.setIfAbsentRgbColor("Editor Background", 0xFFFFFF); + s.setIfAbsentRgbColor("Highlight", 0x3333EE); + s.setIfAbsentRgbColor("Caret", 0x000000); + s.setIfAbsentRgbColor("Selection Highlight", 0x000000); + s.setIfAbsentRgbColor("String", 0xCC6600); + s.setIfAbsentRgbColor("Number", 0x999933); + s.setIfAbsentRgbColor("Operator", 0x000000); + s.setIfAbsentRgbColor("Delimiter", 0x000000); + s.setIfAbsentRgbColor("Type", 0x000000); + s.setIfAbsentRgbColor("Identifier", 0x000000); + s.setIfAbsentRgbColor("Text", 0x000000); + } else { + // Based off colors found here: https://github.com/dracula/dracula-theme/ + s.setIfAbsentRgbColor("Line Numbers Foreground", 0xA4A4A3); + s.setIfAbsentRgbColor("Line Numbers Background", 0x313335); + s.setIfAbsentRgbColor("Line Numbers Selected", 0x606366); + s.setIfAbsentRgbColor("Obfuscated", 0xFF5555); + s.setIfAbsentDouble("Obfuscated Alpha", 0.3); + s.setIfAbsentRgbColor("Obfuscated Outline", 0xFF5555); + s.setIfAbsentDouble("Obfuscated Outline Alpha", 0.5); + s.setIfAbsentRgbColor("Proposed", 0x606366); + s.setIfAbsentDouble("Proposed Alpha", 0.3); + s.setIfAbsentRgbColor("Proposed Outline", 0x606366); + s.setIfAbsentDouble("Proposed Outline Alpha", 0.5); + s.setIfAbsentRgbColor("Deobfuscated", 0x50FA7B); + s.setIfAbsentDouble("Deobfuscated Alpha", 0.3); + s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50FA7B); + s.setIfAbsentDouble("Deobfuscated Outline Alpha", 0.5); + s.setIfAbsentRgbColor("Editor Background", 0x282A36); + s.setIfAbsentRgbColor("Highlight", 0xFF79C6); + s.setIfAbsentRgbColor("Caret", 0xF8F8F2); + s.setIfAbsentRgbColor("Selection Highlight", 0xF8F8F2); + s.setIfAbsentRgbColor("String", 0xF1FA8C); + s.setIfAbsentRgbColor("Number", 0xBD93F9); + s.setIfAbsentRgbColor("Operator", 0xF8F8F2); + s.setIfAbsentRgbColor("Delimiter", 0xF8F8F2); + s.setIfAbsentRgbColor("Type", 0xF8F8F2); + s.setIfAbsentRgbColor("Identifier", 0xF8F8F2); + s.setIfAbsentRgbColor("Text", 0xF8F8F2); + } + } + +} 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 0000000..fdd17d2 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/legacy/Config.java @@ -0,0 +1,109 @@ +package cuchaz.enigma.gui.config.legacy; + +import java.awt.Color; +import java.io.File; +import java.lang.reflect.Type; +import java.nio.charset.Charset; + +import com.google.common.io.Files; +import com.google.gson.*; + +import cuchaz.enigma.gui.config.Decompiler; +import cuchaz.enigma.utils.I18n; + +@Deprecated +public class Config { + public static class AlphaColorEntry { + public Integer rgb; + public float alpha; + + public AlphaColorEntry(Integer rgb, float alpha) { + this.rgb = rgb; + this.alpha = alpha; + } + + public Color get() { + if (rgb == null) { + return new Color(0, 0, 0, 0); + } + + Color baseColor = new Color(rgb); + return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha)); + } + } + + private static final File DIR_HOME = new File(System.getProperty("user.home")); + private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); + public static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); + + private final transient Gson gson; // transient to exclude it from being exposed + + public AlphaColorEntry obfuscatedColor; + public AlphaColorEntry obfuscatedColorOutline; + public AlphaColorEntry proposedColor; + public AlphaColorEntry proposedColorOutline; + public AlphaColorEntry deobfuscatedColor; + public AlphaColorEntry deobfuscatedColorOutline; + + public String editorFont; + + public Integer editorBackground; + public Integer highlightColor; + public Integer caretColor; + public Integer selectionHighlightColor; + + public Integer stringColor; + public Integer numberColor; + public Integer operatorColor; + public Integer delimiterColor; + public Integer typeColor; + public Integer identifierColor; + public Integer defaultTextColor; + + public Integer lineNumbersBackground; + public Integer lineNumbersSelected; + public Integer lineNumbersForeground; + + public String language = I18n.DEFAULT_LANGUAGE; + + public cuchaz.enigma.gui.config.LookAndFeel lookAndFeel = cuchaz.enigma.gui.config.LookAndFeel.DEFAULT; + + public float scaleFactor = 1.0f; + + public Decompiler decompiler = Decompiler.PROCYON; + + public Config() { + gson = new GsonBuilder() + .registerTypeAdapter(Integer.class, new IntSerializer()) + .registerTypeAdapter(Integer.class, new IntDeserializer()) + .registerTypeAdapter(Config.class, (InstanceCreator) type -> this) + .setPrettyPrinting() + .create(); + this.loadConfig(); + } + + public void loadConfig() { + if (CONFIG_FILE.exists()) { + try { + gson.fromJson(Files.asCharSource(CONFIG_FILE, Charset.defaultCharset()).read(), Config.class); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static class IntSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase()); + } + } + + private static class IntDeserializer implements JsonDeserializer { + @Override + public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + return (int) Long.parseLong(json.getAsString().replace("#", ""), 16); + } + } + +} 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 1851060..f8922e6 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 { JLabel title = new JLabel(Enigma.NAME); title.setFont(title.getFont().deriveFont(title.getFont().getSize2D() * 1.5f)); - JButton okButton = new JButton(I18n.translate("menu.help.about.ok")); + JButton okButton = new JButton(I18n.translate("prompt.ok")); okButton.addActionListener(e -> frame.dispose()); 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 64219ab..df65473 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 @@ package cuchaz.enigma.gui.dialog; import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.Window; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JButton; -import javax.swing.JFrame; +import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; -import cuchaz.enigma.gui.Gui; import cuchaz.enigma.utils.I18n; public class ChangeDialog { - public static void show(Gui gui) { + public static void show(Window parent) { // init frame - JFrame frame = new JFrame(I18n.translate("menu.view.change.title")); + JDialog frame = new JDialog(parent, I18n.translate("menu.view.change.title"), Dialog.DEFAULT_MODALITY_TYPE); JPanel textPanel = new JPanel(); JPanel buttonPanel = new JPanel(); frame.setLayout(new BorderLayout()); @@ -29,7 +30,7 @@ public class ChangeDialog { textPanel.add(text); // show ok button - JButton okButton = new JButton(I18n.translate("menu.view.change.ok")); + JButton okButton = new JButton(I18n.translate("prompt.ok")); buttonPanel.add(okButton); okButton.addActionListener(event -> frame.dispose()); okButton.addKeyListener(new KeyAdapter() { @@ -43,8 +44,9 @@ public class ChangeDialog { // show the frame frame.pack(); - frame.setVisible(true); frame.setResizable(false); - frame.setLocationRelativeTo(gui.getFrame()); + frame.setLocationRelativeTo(parent); + frame.setVisible(true); } + } 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 201f4dc..2486dfe 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; import javax.swing.JPasswordField; import javax.swing.JTextField; +import cuchaz.enigma.gui.config.NetConfig; import cuchaz.enigma.gui.elements.ValidatableTextField; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.network.EnigmaServer; @@ -36,9 +37,9 @@ public class ConnectToServerDialog extends AbstractDialog { @Override protected List> createComponents() { - usernameField = new JTextField(System.getProperty("user.name")); - ipField = new ValidatableTextField(); - passwordField = new JPasswordField(); + usernameField = new JTextField(NetConfig.getUsername()); + ipField = new ValidatableTextField(NetConfig.getRemoteAddress()); + passwordField = new JPasswordField(NetConfig.getPassword()); usernameField.addActionListener(event -> confirm()); ipField.addActionListener(event -> confirm()); @@ -51,6 +52,7 @@ public class ConnectToServerDialog extends AbstractDialog { ); } + @Override public void validateInputs() { vc.setActiveElement(ipField); if (StandardValidation.notBlank(vc, ipField.getText())) { @@ -67,6 +69,7 @@ public class ConnectToServerDialog extends AbstractDialog { if (!vc.canProceed()) return null; return new Result( usernameField.getText(), + ipField.getText(), Objects.requireNonNull(ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT)), passwordField.getPassword() ); @@ -84,11 +87,13 @@ public class ConnectToServerDialog extends AbstractDialog { public static class Result { private final String username; + private final String addressStr; private final ServerAddress address; private final char[] password; - public Result(String username, ServerAddress address, char[] password) { + public Result(String username, String addressStr, ServerAddress address, char[] password) { this.username = username; + this.addressStr = addressStr; this.address = address; this.password = password; } @@ -97,6 +102,10 @@ public class ConnectToServerDialog extends AbstractDialog { return username; } + public String getAddressStr() { + return addressStr; + } + public ServerAddress getAddress() { return address; } 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 ddd3bc3..07daf6d 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; import java.util.Arrays; import java.util.List; +import cuchaz.enigma.gui.config.NetConfig; import cuchaz.enigma.gui.elements.ValidatablePasswordField; import cuchaz.enigma.gui.elements.ValidatableTextField; import cuchaz.enigma.gui.util.ScaleUtil; @@ -31,8 +32,8 @@ public class CreateServerDialog extends AbstractDialog { @Override protected List> createComponents() { - portField = new ValidatableTextField(Integer.toString(EnigmaServer.DEFAULT_PORT)); - passwordField = new ValidatablePasswordField(); + portField = new ValidatableTextField(Integer.toString(NetConfig.getServerPort())); + passwordField = new ValidatablePasswordField(NetConfig.getServerPassword()); portField.addActionListener(event -> confirm()); 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 0000000..de019ad --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/FontDialog.java @@ -0,0 +1,126 @@ +package cuchaz.enigma.gui.dialog; + +import java.awt.*; +import java.util.Arrays; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JList; + +import org.drjekyll.fontchooser.FontChooser; + +import cuchaz.enigma.gui.config.UiConfig; +import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.utils.I18n; + +public class FontDialog extends JDialog { + + private static final List CATEGORIES = Arrays.asList( + "Default", + "Default 2", + "Small", + "Editor" + ); + + private static final List CATEGORY_TEXTS = Arrays.asList( + "fonts.cat.default", + "fonts.cat.default2", + "fonts.cat.small", + "fonts.cat.editor" + ); + + private final JList entries = new JList<>(CATEGORY_TEXTS.stream().map(I18n::translate).toArray(String[]::new)); + private final FontChooser chooser = new FontChooser(Font.decode(Font.DIALOG)); + private final JCheckBox customCheckBox = new JCheckBox(I18n.translate("fonts.use_custom")); + private final JButton okButton = new JButton(I18n.translate("prompt.ok")); + private final JButton cancelButton = new JButton(I18n.translate("prompt.cancel")); + + private final Font[] fonts = CATEGORIES.stream().map(name -> UiConfig.getFont(name).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG)))).toArray(Font[]::new); + + public FontDialog(Frame owner) { + super(owner, "Fonts", true); + + this.customCheckBox.setSelected(UiConfig.shouldUseCustomFonts()); + + this.entries.setPreferredSize(ScaleUtil.getDimension(100, 0)); + + this.entries.addListSelectionListener(_e -> this.categoryChanged()); + this.chooser.addChangeListener(_e -> this.selectedFontChanged()); + this.customCheckBox.addActionListener(_e -> this.customFontsClicked()); + this.okButton.addActionListener(_e -> this.apply()); + this.cancelButton.addActionListener(_e -> this.cancel()); + + Container contentPane = this.getContentPane(); + contentPane.setLayout(new GridBagLayout()); + + GridBagConstraintsBuilder cb = GridBagConstraintsBuilder.create() + .insets(2); + + contentPane.add(this.entries, cb.pos(0, 0).weight(0.0, 1.0).fill(GridBagConstraints.BOTH).build()); + contentPane.add(this.chooser, cb.pos(1, 0).weight(1.0, 1.0).fill(GridBagConstraints.BOTH).size(2, 1).build()); + contentPane.add(this.customCheckBox, cb.pos(0, 1).anchor(GridBagConstraints.WEST).size(2, 1).build()); + contentPane.add(this.okButton, cb.pos(1, 1).anchor(GridBagConstraints.EAST).weight(1.0, 0.0).build()); + contentPane.add(this.cancelButton, cb.pos(2, 1).anchor(GridBagConstraints.EAST).weight(0.0, 0.0).build()); + + this.updateUiState(); + + this.setSize(ScaleUtil.getDimension(640, 360)); + this.setLocationRelativeTo(owner); + } + + private void customFontsClicked() { + this.updateUiState(); + } + + private void categoryChanged() { + this.updateUiState(); + int selectedIndex = this.entries.getSelectedIndex(); + if (selectedIndex != -1) { + this.chooser.setSelectedFont(this.fonts[selectedIndex]); + } + } + + private void selectedFontChanged() { + int selectedIndex = this.entries.getSelectedIndex(); + if (selectedIndex != -1) { + this.fonts[selectedIndex] = this.chooser.getSelectedFont(); + } + } + + private void updateUiState() { + recursiveSetEnabled(this.chooser, this.entries.getSelectedIndex() != -1 && this.customCheckBox.isSelected()); + this.entries.setEnabled(this.customCheckBox.isSelected()); + } + + private void apply() { + for (int i = 0; i < CATEGORIES.size(); i++) { + UiConfig.setFont(CATEGORIES.get(i), this.fonts[i]); + } + UiConfig.setUseCustomFonts(this.customCheckBox.isSelected()); + UiConfig.save(); + ChangeDialog.show(this); + this.dispose(); + } + + private void cancel() { + this.dispose(); + } + + public static void display(Frame parent) { + FontDialog d = new FontDialog(parent); + d.setVisible(true); + } + + private static void recursiveSetEnabled(Component self, boolean enabled) { + if (self instanceof Container) { + for (Component component : ((Container) self).getComponents()) { + recursiveSetEnabled(component, enabled); + } + self.setEnabled(enabled); + } + } + +} 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 9fbe65a..a934d34 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 @@ package cuchaz.enigma.gui.dialog; -import cuchaz.enigma.gui.util.GuiUtil; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; - -import javax.swing.*; -import javax.swing.text.html.HTML; - -import java.awt.*; import java.awt.BorderLayout; import java.awt.Container; +import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.*; +import javax.swing.text.html.HTML; import com.google.common.base.Strings; + import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.gui.GuiController; import cuchaz.enigma.gui.elements.ValidatableTextArea; @@ -88,10 +83,10 @@ public class JavadocDialog { JPanel buttonsPanel = new JPanel(); buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); - JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); + JButton cancelButton = new JButton(I18n.translate("prompt.cancel")); cancelButton.addActionListener(event -> close()); buttonsPanel.add(cancelButton); - JButton saveButton = new JButton(I18n.translate("javadocs.save")); + JButton saveButton = new JButton(I18n.translate("prompt.save")); saveButton.addActionListener(event -> doSave()); buttonsPanel.add(saveButton); 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 d6c60e0..d5d657d 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 @@ package cuchaz.enigma.gui.elements; -import java.awt.Desktop; +import java.awt.FileDialog; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -20,9 +18,13 @@ import javax.swing.*; import cuchaz.enigma.gui.ConnectionState; import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.config.Config; -import cuchaz.enigma.gui.config.Themes; +import cuchaz.enigma.gui.config.Decompiler; +import cuchaz.enigma.gui.config.LookAndFeel; +import cuchaz.enigma.gui.config.NetConfig; +import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.*; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.gui.util.LanguageUtil; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.translation.mapping.serde.MappingFormat; import cuchaz.enigma.utils.I18n; @@ -32,48 +34,51 @@ public class MenuBar { private final JMenuBar ui = new JMenuBar(); - private final JMenu fileMenu = new JMenu(I18n.translate("menu.file")); - private final JMenuItem jarOpenItem = new JMenuItem(I18n.translate("menu.file.jar.open")); - private final JMenuItem jarCloseItem = new JMenuItem(I18n.translate("menu.file.jar.close")); - private final JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open")); - private final JMenuItem saveMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.save")); - private final JMenu saveMappingsAsMenu = new JMenu(I18n.translate("menu.file.mappings.save_as")); - private final JMenuItem closeMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.close")); - private final JMenuItem dropMappingsItem = new JMenuItem(I18n.translate("menu.file.mappings.drop")); - private final JMenuItem reloadMappingsItem = new JMenuItem(I18n.translate("menu.file.reload_mappings")); - private final JMenuItem reloadAllItem = new JMenuItem(I18n.translate("menu.file.reload_all")); - private final JMenuItem exportSourceItem = new JMenuItem(I18n.translate("menu.file.export.source")); - private final JMenuItem exportJarItem = new JMenuItem(I18n.translate("menu.file.export.jar")); - private final JMenuItem statsItem = new JMenuItem(I18n.translate("menu.file.stats")); - private final JMenuItem exitItem = new JMenuItem(I18n.translate("menu.file.exit")); - - private final JMenu decompilerMenu = new JMenu(I18n.translate("menu.decompiler")); - - private final JMenu viewMenu = new JMenu(I18n.translate("menu.view")); - private final JMenu themesMenu = new JMenu(I18n.translate("menu.view.themes")); - private final JMenu languagesMenu = new JMenu(I18n.translate("menu.view.languages")); - private final JMenu scaleMenu = new JMenu(I18n.translate("menu.view.scale")); - private final JMenuItem customScaleItem = new JMenuItem(I18n.translate("menu.view.scale.custom")); - private final JMenuItem searchItem = new JMenuItem(I18n.translate("menu.view.search")); - - private final JMenu collabMenu = new JMenu(I18n.translate("menu.collab")); - private final JMenuItem connectItem = new JMenuItem(I18n.translate("menu.collab.connect")); - private final JMenuItem startServerItem = new JMenuItem(I18n.translate("menu.collab.server.start")); - - private final JMenu helpMenu = new JMenu(I18n.translate("menu.help")); - private final JMenuItem aboutItem = new JMenuItem(I18n.translate("menu.help.about")); - private final JMenuItem githubItem = new JMenuItem(I18n.translate("menu.help.github")); + private final JMenu fileMenu = new JMenu(); + private final JMenuItem jarOpenItem = new JMenuItem(); + private final JMenuItem jarCloseItem = new JMenuItem(); + private final JMenu openMenu = new JMenu(); + private final JMenuItem saveMappingsItem = new JMenuItem(); + private final JMenu saveMappingsAsMenu = new JMenu(); + private final JMenuItem closeMappingsItem = new JMenuItem(); + private final JMenuItem dropMappingsItem = new JMenuItem(); + private final JMenuItem reloadMappingsItem = new JMenuItem(); + private final JMenuItem reloadAllItem = new JMenuItem(); + private final JMenuItem exportSourceItem = new JMenuItem(); + private final JMenuItem exportJarItem = new JMenuItem(); + private final JMenuItem statsItem = new JMenuItem(); + private final JMenuItem exitItem = new JMenuItem(); + + private final JMenu decompilerMenu = new JMenu(); + + private final JMenu viewMenu = new JMenu(); + private final JMenu themesMenu = new JMenu(); + private final JMenu languagesMenu = new JMenu(); + private final JMenu scaleMenu = new JMenu(); + private final JMenuItem fontItem = new JMenuItem(); + private final JMenuItem customScaleItem = new JMenuItem(); + private final JMenuItem searchItem = new JMenuItem(); + + private final JMenu collabMenu = new JMenu(); + private final JMenuItem connectItem = new JMenuItem(); + private final JMenuItem startServerItem = new JMenuItem(); + + private final JMenu helpMenu = new JMenu(); + private final JMenuItem aboutItem = new JMenuItem(); + private final JMenuItem githubItem = new JMenuItem(); private final Gui gui; public MenuBar(Gui gui) { this.gui = gui; + this.retranslateUi(); + prepareOpenMenu(this.openMenu, gui); prepareSaveMappingsAsMenu(this.saveMappingsAsMenu, this.saveMappingsItem, gui); prepareDecompilerMenu(this.decompilerMenu, gui); prepareThemesMenu(this.themesMenu, gui); - prepareLanguagesMenu(this.languagesMenu, gui); + prepareLanguagesMenu(this.languagesMenu); prepareScaleMenu(this.scaleMenu, gui); this.fileMenu.add(this.jarOpenItem); @@ -102,6 +107,7 @@ public class MenuBar { this.viewMenu.add(this.languagesMenu); this.scaleMenu.add(this.customScaleItem); this.viewMenu.add(this.scaleMenu); + this.viewMenu.add(this.fontItem); this.viewMenu.addSeparator(); this.viewMenu.add(this.searchItem); this.ui.add(this.viewMenu); @@ -129,6 +135,7 @@ public class MenuBar { this.statsItem.addActionListener(_e -> StatsDialog.show(this.gui)); this.exitItem.addActionListener(_e -> this.gui.close()); this.customScaleItem.addActionListener(_e -> this.onCustomScaleClicked()); + this.fontItem.addActionListener(_e -> this.onFontClicked(this.gui)); this.searchItem.addActionListener(_e -> this.onSearchClicked()); this.connectItem.addActionListener(_e -> this.onConnectClicked()); this.startServerItem.addActionListener(_e -> this.onStartServerClicked()); @@ -157,20 +164,58 @@ public class MenuBar { this.statsItem.setEnabled(jarOpen); } + public void retranslateUi() { + this.fileMenu.setText(I18n.translate("menu.file")); + this.jarOpenItem.setText(I18n.translate("menu.file.jar.open")); + this.jarCloseItem.setText(I18n.translate("menu.file.jar.close")); + this.openMenu.setText(I18n.translate("menu.file.mappings.open")); + this.saveMappingsItem.setText(I18n.translate("menu.file.mappings.save")); + this.saveMappingsAsMenu.setText(I18n.translate("menu.file.mappings.save_as")); + this.closeMappingsItem.setText(I18n.translate("menu.file.mappings.close")); + this.dropMappingsItem.setText(I18n.translate("menu.file.mappings.drop")); + this.reloadMappingsItem.setText(I18n.translate("menu.file.reload_mappings")); + this.reloadAllItem.setText(I18n.translate("menu.file.reload_all")); + this.exportSourceItem.setText(I18n.translate("menu.file.export.source")); + this.exportJarItem.setText(I18n.translate("menu.file.export.jar")); + this.statsItem.setText(I18n.translate("menu.file.stats")); + this.exitItem.setText(I18n.translate("menu.file.exit")); + + this.decompilerMenu.setText(I18n.translate("menu.decompiler")); + + this.viewMenu.setText(I18n.translate("menu.view")); + this.themesMenu.setText(I18n.translate("menu.view.themes")); + this.languagesMenu.setText(I18n.translate("menu.view.languages")); + this.scaleMenu.setText(I18n.translate("menu.view.scale")); + this.fontItem.setText(I18n.translate("menu.view.font")); + this.customScaleItem.setText(I18n.translate("menu.view.scale.custom")); + this.searchItem.setText(I18n.translate("menu.view.search")); + + this.collabMenu.setText(I18n.translate("menu.collab")); + this.connectItem.setText(I18n.translate("menu.collab.connect")); + this.startServerItem.setText(I18n.translate("menu.collab.server.start")); + + this.helpMenu.setText(I18n.translate("menu.help")); + this.aboutItem.setText(I18n.translate("menu.help.about")); + this.githubItem.setText(I18n.translate("menu.help.github")); + } + public JMenuBar getUi() { return this.ui; } private void onOpenJarClicked() { - this.gui.jarFileChooser.setVisible(true); - String file = this.gui.jarFileChooser.getFile(); + FileDialog d = this.gui.jarFileChooser; + d.setDirectory(UiConfig.getLastSelectedDir()); + d.setVisible(true); + String file = d.getFile(); // checks if the file name is not empty if (file != null) { - Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file); + Path path = Paths.get(d.getDirectory()).resolve(file); // checks if the file name corresponds to an existing file if (Files.exists(path)) { this.gui.getController().openJar(path); } + UiConfig.setLastSelectedDir(d.getDirectory()); } } @@ -187,7 +232,7 @@ public class MenuBar { } else if (response == JOptionPane.NO_OPTION) then.run(); return null; - }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); + }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.cancel")); } else { then.run(); } @@ -206,16 +251,20 @@ public class MenuBar { } private void onExportSourceClicked() { + this.gui.exportSourceFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + UiConfig.setLastSelectedDir(this.gui.exportSourceFileChooser.getCurrentDirectory().toString()); this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); } } private void onExportJarClicked() { + this.gui.exportJarFileChooser.setDirectory(UiConfig.getLastSelectedDir()); this.gui.exportJarFileChooser.setVisible(true); if (this.gui.exportJarFileChooser.getFile() != null) { Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); this.gui.getController().exportJar(path); + UiConfig.setLastSelectedDir(this.gui.exportJarFileChooser.getDirectory()); } } @@ -229,7 +278,21 @@ public class MenuBar { } catch (NumberFormatException ignored) { } ScaleUtil.setScaleFactor(newScale); - ChangeDialog.show(this.gui); + ChangeDialog.show(this.gui.getFrame()); + } + + private void onFontClicked(Gui gui) { +// FontDialog fd = new FontDialog(gui.getFrame(), "Choose Font", true); +// fd.setLocationRelativeTo(gui.getFrame()); +// fd.setSelectedFont(UiConfig.getEditorFont()); +// fd.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); +// fd.setVisible(true); +// +// if (!fd.isCancelSelected()) { +// UiConfig.setEditorFont(fd.getSelectedFont()); +// UiConfig.save(); +// } + FontDialog.display(gui.getFrame()); } private void onSearchClicked() { @@ -250,6 +313,10 @@ public class MenuBar { this.gui.getController().disconnectIfConnected(null); try { this.gui.getController().createClient(result.getUsername(), result.getAddress().address, result.getAddress().port, result.getPassword()); + NetConfig.setUsername(result.getUsername()); + NetConfig.setRemoteAddress(result.getAddressStr()); + NetConfig.setPassword(String.valueOf(result.getPassword())); + NetConfig.save(); } catch (IOException e) { JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); this.gui.getController().disconnectIfConnected(null); @@ -269,6 +336,9 @@ public class MenuBar { this.gui.getController().disconnectIfConnected(null); try { this.gui.getController().createServer(result.getPort(), result.getPassword()); + NetConfig.setServerPort(result.getPort()); + NetConfig.setServerPassword(String.valueOf(result.getPassword())); + NetConfig.save(); } catch (IOException e) { JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); this.gui.getController().disconnectIfConnected(null); @@ -276,10 +346,7 @@ public class MenuBar { } private void onGithubClicked() { - try { - Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI()); - } catch (URISyntaxException | IOException ignored) { - } + GuiUtil.openUrl("https://github.com/FabricMC/Enigma"); } private static void prepareOpenMenu(JMenu openMenu, Gui gui) { @@ -287,9 +354,11 @@ public class MenuBar { if (format.getReader() != null) { JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); item.addActionListener(event -> { + gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); if (gui.enigmaMappingsFileChooser.showOpenDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { File selectedFile = gui.enigmaMappingsFileChooser.getSelectedFile(); gui.getController().openMappings(format, selectedFile.toPath()); + UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString()); } }); openMenu.add(item); @@ -303,9 +372,14 @@ public class MenuBar { JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); item.addActionListener(event -> { // TODO: Use a specific file chooser for it + if (gui.enigmaMappingsFileChooser.getCurrentDirectory() == null) { + gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); + } + if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); saveMappingsItem.setEnabled(true); + UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString()); } }); saveMappingsAsMenu.add(item); @@ -316,21 +390,17 @@ public class MenuBar { private static void prepareDecompilerMenu(JMenu decompilerMenu, Gui gui) { ButtonGroup decompilerGroup = new ButtonGroup(); - for (Config.Decompiler decompiler : Config.Decompiler.values()) { + for (Decompiler decompiler : Decompiler.values()) { JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name); decompilerGroup.add(decompilerButton); - if (decompiler.equals(Config.getInstance().decompiler)) { + if (decompiler.equals(UiConfig.getDecompiler())) { decompilerButton.setSelected(true); } decompilerButton.addActionListener(event -> { gui.getController().setDecompiler(decompiler.service); - try { - Config.getInstance().decompiler = decompiler; - Config.getInstance().saveConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } + UiConfig.setDecompiler(decompiler); + UiConfig.save(); }); decompilerMenu.add(decompilerButton); } @@ -338,33 +408,34 @@ public class MenuBar { private static void prepareThemesMenu(JMenu themesMenu, Gui gui) { ButtonGroup themeGroup = new ButtonGroup(); - for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) { + for (LookAndFeel lookAndFeel : LookAndFeel.values()) { JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT))); themeGroup.add(themeButton); - if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { + if (lookAndFeel.equals(UiConfig.getLookAndFeel())) { themeButton.setSelected(true); } - themeButton.addActionListener(_e -> Themes.setLookAndFeel(lookAndFeel)); + themeButton.addActionListener(_e -> { + UiConfig.setLookAndFeel(lookAndFeel); + UiConfig.save(); + ChangeDialog.show(gui.getFrame()); + }); themesMenu.add(themeButton); } } - private static void prepareLanguagesMenu(JMenu languagesMenu, Gui gui) { + private static void prepareLanguagesMenu(JMenu languagesMenu) { ButtonGroup languageGroup = new ButtonGroup(); for (String lang : I18n.getAvailableLanguages()) { JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang)); languageGroup.add(languageButton); - if (lang.equals(Config.getInstance().language)) { + if (lang.equals(UiConfig.getLanguage())) { languageButton.setSelected(true); } languageButton.addActionListener(event -> { - Config.getInstance().language = lang; - try { - Config.getInstance().saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } - ChangeDialog.show(gui); + UiConfig.setLanguage(lang); + I18n.setLanguage(lang); + LanguageUtil.dispatchLanguageChange(); + UiConfig.save(); }); languagesMenu.add(languageButton); } @@ -377,7 +448,7 @@ public class MenuBar { float realScaleFactor = scaleFactor / 100f; JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor)); menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor)); - menuItem.addActionListener(event -> ChangeDialog.show(gui)); + menuItem.addActionListener(event -> ChangeDialog.show(gui.getFrame())); scaleGroup.add(menuItem); scaleMenu.add(menuItem); 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 d4962f7..10d7ce1 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; import java.util.Map; -import cuchaz.enigma.gui.config.Config.LookAndFeel; +import cuchaz.enigma.gui.config.LookAndFeel; import cuchaz.enigma.gui.highlight.BoxHighlightPainter; import cuchaz.enigma.source.RenamableTokenType; 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 3ae4380..2d8d76a 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 @@ package cuchaz.enigma.gui.highlight; -import cuchaz.enigma.gui.config.Config; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; -import java.awt.*; public class BoxHighlightPainter implements Highlighter.HighlightPainter { private Color fillColor; @@ -27,8 +29,8 @@ public class BoxHighlightPainter implements Highlighter.HighlightPainter { this.borderColor = borderColor; } - public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) { - return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null); + public static BoxHighlightPainter create(Color color, Color outline) { + return new BoxHighlightPainter(color, outline); } 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 c899e68..22d6420 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 @@ package cuchaz.enigma.gui.highlight; -import cuchaz.enigma.gui.config.Config; +import java.awt.*; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; -import java.awt.*; + +import cuchaz.enigma.gui.config.UiConfig; public class SelectionHighlightPainter implements Highlighter.HighlightPainter { @@ -26,7 +27,7 @@ public class SelectionHighlightPainter implements Highlighter.HighlightPainter { // draw a thick border Graphics2D g2d = (Graphics2D) g; Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); - g2d.setColor(new Color(Config.getInstance().selectionHighlightColor)); + g2d.setColor(UiConfig.getSelectionHighlightColor()); g2d.setStroke(new BasicStroke(2.0f)); g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); } 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 bb8acc8..3e357cb 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; public class DeobfPanel extends JPanel { public final ClassSelector deobfClasses; + private final JLabel title = new JLabel(); + private final Gui gui; public DeobfPanel(Gui gui) { @@ -23,7 +25,14 @@ public class DeobfPanel extends JPanel { this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); this.setLayout(new BorderLayout()); - this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH); + this.add(this.title, BorderLayout.NORTH); this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); + + this.retranslateUi(); } + + public void retranslateUi() { + this.title.setText(I18n.translate("info_panel.classes.deobfuscated")); + } + } 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 3409fc1..ab9de33 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; import cuchaz.enigma.gui.BrowserCaret; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.config.LookAndFeel; import cuchaz.enigma.gui.config.Themes; +import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.elements.PopupMenuBar; import cuchaz.enigma.gui.events.EditorActionListener; import cuchaz.enigma.gui.events.ThemeChangeListener; @@ -61,7 +62,7 @@ public class EditorPanel { private final JLabel errorLabel = new JLabel(); private final JTextArea errorTextArea = new JTextArea(); private final JScrollPane errorScrollPane = new JScrollPane(this.errorTextArea); - private final JButton retryButton = new JButton(I18n.translate("general.retry")); + private final JButton retryButton = new JButton(I18n.translate("prompt.retry")); private DisplayMode mode = DisplayMode.INACTIVE; @@ -73,7 +74,7 @@ public class EditorPanel { private boolean mouseIsPressed = false; private boolean shouldNavigateOnClick; - public Config.LookAndFeel editorLaf; + public LookAndFeel editorLaf; private int fontSize = 12; private Map boxHighlightPainters; @@ -94,9 +95,9 @@ public class EditorPanel { this.editor.setCaret(new BrowserCaret()); this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); - this.editor.setCaretColor(new Color(Config.getInstance().caretColor)); + this.editor.setCaretColor(UiConfig.getCaretColor()); this.editor.setContentType("text/enigma-sources"); - this.editor.setBackground(new Color(Config.getInstance().editorBackground)); + this.editor.setBackground(UiConfig.getEditorBackgroundColor()); DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); @@ -240,7 +241,7 @@ public class EditorPanel { this.themeChangeListener = (laf, boxHighlightPainters) -> { if ((this.editorLaf == null || this.editorLaf != laf)) { this.editor.updateUI(); - this.editor.setBackground(new Color(Config.getInstance().editorBackground)); + this.editor.setBackground(UiConfig.getEditorBackgroundColor()); if (this.editorLaf != null) { this.classHandle.invalidateMapped(); } 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 248d50d..4c50640 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 { gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true)); } + public void retranslateUi() { + this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); + this.refreshReference(); + } + public JPanel getUi() { return ui; } 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 0ca0583..b384968 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; public class ObfPanel extends JPanel { public final ClassSelector obfClasses; + private final JLabel title = new JLabel(); + private final Gui gui; public ObfPanel(Gui gui) { @@ -34,7 +36,14 @@ public class ObfPanel extends JPanel { this.obfClasses.setRenameSelectionListener(gui::onPanelRename); this.setLayout(new BorderLayout()); - this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH); + this.add(this.title, BorderLayout.NORTH); this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); + + this.retranslateUi(); } + + public void retranslateUi() { + this.title.setText(I18n.translate("info_panel.classes.obfuscated")); + } + } 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 631e065..7fe942d 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; import javax.swing.JLabel; import javax.swing.ToolTipManager; +import cuchaz.enigma.utils.Os; + public class GuiUtil { public static void openUrl(String url) { - if (Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - try { - desktop.browse(new URI(url)); - } catch (IOException ex) { - throw new Error(ex); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException(ex); + try { + switch (Os.getOs()) { + case LINUX: + new ProcessBuilder("/usr/bin/env", "xdg-open", url).start(); + break; + default: + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + desktop.browse(new URI(url)); + } } + } catch (IOException ex) { + throw new RuntimeException(ex); + } catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); } } 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 0000000..6961228 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui.util; + +public interface LanguageChangeListener { + + void retranslateUi(); + +} 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 0000000..d3e6376 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageUtil.java @@ -0,0 +1,25 @@ +package cuchaz.enigma.gui.util; + +import java.util.ArrayList; +import java.util.List; + +public final class LanguageUtil { + + private static final List listeners = new ArrayList<>(); + + public LanguageUtil() { + } + + public static void addListener(LanguageChangeListener listener) { + listeners.add(listener); + } + + public static void removeListener(LanguageChangeListener listener) { + listeners.remove(listener); + } + + public static void dispatchLanguageChange() { + listeners.forEach(LanguageChangeListener::retranslateUi); + } + +} 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 985615a..47799fa 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; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; -import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -17,26 +16,27 @@ import com.github.swingdpi.plaf.BasicTweaker; import com.github.swingdpi.plaf.MetalTweaker; import com.github.swingdpi.plaf.NimbusTweaker; import com.github.swingdpi.plaf.WindowsTweaker; -import cuchaz.enigma.gui.config.Config; import de.sciss.syntaxpane.DefaultSyntaxKit; +import cuchaz.enigma.gui.config.UiConfig; + public class ScaleUtil { private static List listeners = new ArrayList<>(); public static float getScaleFactor() { - return Config.getInstance().scaleFactor; + return UiConfig.getScaleFactor(); } public static void setScaleFactor(float scaleFactor) { float oldScale = getScaleFactor(); float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f); - Config.getInstance().scaleFactor = clamped; - try { - Config.getInstance().saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } + UiConfig.setScaleFactor(clamped); + rescaleFontInConfig("Default", oldScale); + rescaleFontInConfig("Default 2", oldScale); + rescaleFontInConfig("Small", oldScale); + rescaleFontInConfig("Editor", oldScale); + UiConfig.save(); listeners.forEach(l -> l.onScaleChanged(clamped, oldScale)); } @@ -64,6 +64,15 @@ public class ScaleUtil { return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font); } + private static void rescaleFontInConfig(String name, float oldScale) { + UiConfig.getFont(name).ifPresent(font -> UiConfig.setFont(name, rescaleFont(font, oldScale))); + } + + public static Font rescaleFont(Font font, float oldScale) { + float newSize = Math.round(font.getSize() / oldScale * getScaleFactor()); + return font.deriveFont(newSize); + } + public static float scale(float f) { return f * getScaleFactor(); } -- cgit v1.2.3