From 7c7a52c1132e3d753c8d3de8d888dc80831db23d Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Sat, 1 Aug 2020 15:42:28 -0500 Subject: [PATCH] Establishing app module, adjustments to localization support --- Main.class | Bin 0 -> 337 bytes app/App.class | Bin 0 -> 3059 bytes locale/en-US.txt | 32 ++++ locale/en_US.txt | 9 -- makefile | 9 +- src/desktop/Main.java | 13 +- src/desktop/app/App.java | 113 ++++++++++++++ src/desktop/util/Localizer.java | 267 ++++++++++++++++++-------------- src/desktop/util/Util.java | 53 ++++++- util/Localizer$1.class | Bin 0 -> 185 bytes util/Localizer$Locale.class | Bin 0 -> 1813 bytes util/Localizer.class | Bin 0 -> 8162 bytes util/Util$1.class | Bin 0 -> 981 bytes util/Util$2.class | Bin 0 -> 1066 bytes util/Util$3.class | Bin 0 -> 785 bytes util/Util$4.class | Bin 0 -> 811 bytes util/Util$5.class | Bin 0 -> 802 bytes util/Util$6.class | Bin 0 -> 948 bytes util/Util$7.class | Bin 0 -> 829 bytes util/Util$8$1.class | Bin 0 -> 616 bytes util/Util$8.class | Bin 0 -> 552 bytes util/Util$BOM.class | Bin 0 -> 370 bytes util/Util$OnClose.class | Bin 0 -> 207 bytes util/Util$OnClose2.class | Bin 0 -> 219 bytes util/Util$OnFocus.class | Bin 0 -> 206 bytes util/Util$OnKey.class | Bin 0 -> 200 bytes util/Util$OnMouse.class | Bin 0 -> 206 bytes util/Util$OnResize.class | Bin 0 -> 212 bytes util/Util.class | Bin 0 -> 7477 bytes 29 files changed, 359 insertions(+), 137 deletions(-) create mode 100644 Main.class create mode 100644 app/App.class create mode 100644 locale/en-US.txt delete mode 100644 locale/en_US.txt create mode 100644 src/desktop/app/App.java create mode 100644 util/Localizer$1.class create mode 100644 util/Localizer$Locale.class create mode 100644 util/Localizer.class create mode 100644 util/Util$1.class create mode 100644 util/Util$2.class create mode 100644 util/Util$3.class create mode 100644 util/Util$4.class create mode 100644 util/Util$5.class create mode 100644 util/Util$6.class create mode 100644 util/Util$7.class create mode 100644 util/Util$8$1.class create mode 100644 util/Util$8.class create mode 100644 util/Util$BOM.class create mode 100644 util/Util$OnClose.class create mode 100644 util/Util$OnClose2.class create mode 100644 util/Util$OnFocus.class create mode 100644 util/Util$OnKey.class create mode 100644 util/Util$OnMouse.class create mode 100644 util/Util$OnResize.class create mode 100644 util/Util.class diff --git a/Main.class b/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..6fcc6764e670e6357cbf421ba959d0f3da539917 GIT binary patch literal 337 zcmYLEO-sW-5Pg%RjfqKHzp57x-daI(^P&hv=qc(U+M=E|btzktG^88BpXI5d;1BRe ziL<$M7v7uMd9yR`_wVZmzy*$cSg>6<0a|DidJFL;qD&O?=ssP@nI^QaRH5`W!5$1J zKDOYw@B?fkAb7P_S@g(22v$-j?<+0ycrCh4hVaUrfkr|J`OlG(zB)&2tVOYzY#vI(VcEuIx68fB5 GIQ$1S6*;&7 literal 0 HcmV?d00001 diff --git a/app/App.class b/app/App.class new file mode 100644 index 0000000000000000000000000000000000000000..e7b5490c4596dd92f870bf779f141923df5d767e GIT binary patch literal 3059 zcmaJ@X?qh@6n<}$_NM8ul@>@58WbdLicwUARzavBg;bCth$uQFV>@&@6K5ucx-Yol zE^fGs3vM682TO&=>L>N_Kj`mJKZx(0Oi9v4pFFuUbLQOlob#UL&Ts#mn*^{1U&c{~ zh(uIFJsJe&9@0nkjHTOynH>iY8985|;d;|H{q+KoRL35Pn856!Z(5nGlhZBpe#4DJ zBGQN^XcDtD%*Gr6sTT^FjfH|hqcx>R^lbIco;Y{S)6lF;Hz_j#sNpHd77eXfK(l&2 zFR(I|o$gxCOi9NbYUd&i3AEAlpb-KJBvKvIj4vQSCwqG1V^((S-m!xLDv|3B0v zk^-&OafDEDT#CyimTOpn%d5B?81sB%gdXXxtB>WJ!k9p3jfnbYhSb;Bu}1}!($IlU zro%NxoKb^5)YxB&ze~d^TtRDIIsUfPf5fA5dZoa;fZZUht-3eVuNUITAQHpXG+AOL zj%%?-;yMj$v5x40@@*^y-dm>UZ!s;!Tvet9eAl!Gdph=41QyDgS%5tnuEz}m3rF-} zqt~%>y1&=-hj{1ty6qF~^2#Wu&}M=u-!^EtQGvyLvTZmG_B5IXDHce2i5!|m0T{T9|?xsbzv z6xBO5+^X{1lyhufH*IgbF(xq}&{kI8RkVF`#Mpc!XB2$Xv6~^l9@>RHs(trPtBEPL zSUwr-({Km&vzrP<_Vn6H7D|m?tD7o34V7ZJi(DG^sv>)I8}8O{Pf(P`eMQ~!Bn}G9 zFZ+AZ(MEPgUW0*q>4D+8^t6O+I+>lU5TCvp`UqpuF{Hs%RL0r8{f6i1gGMul)UYZQ zmRgnTVTcueP6a(v6o(B*qM+eEMODeoYWo}j1?7!pE6O|#K8h^=QQayAGhr%sLAmrg zmc;?dG|=q~4r@39wsq9wObmu)UlSg{gAxyEco>f`_2m{T@1;rK*{G(1Kq^()CL!Zh ztrN;5md5coo{%`I;YmC-Rd<130T??c>Nze`y`Uy&P!rE+coxU#E190wX;a24XbeRJ z?sf8va8A~=ja!N%2Mu?(e$b*M&PmPI^<9T~YEmO19Oo?J6yvDfWdgHGBkkhWSp&YF z8|DZKa3zj2Tb0a&Ii|q@r|9MkHF((eJZ`Q~6qAijj^lg2s~7qWf5^#ua~kkg94GL$ z#5<~v-W6D0E>qJUb%u>h36@@P3T@GIzT+|{Z&h|ms8|Bss@r)EJkEY+xLBxcpU}Sw z?y5HK9xE6%)GH&bL4OF*t6SE9=^H&Uyw8|RlQ|<|_)uU)iSFcKj?iS^P05vFWm31; zSoyJ}YYZ|UhHKcDY}zFK?AD(*68Z)`b7OXbORGuKY@+i4T**`*gTHD zZkbNVaqKvO*7Ml4FA$%@Zs06#`;o5_4db|dZ$dWTIgSG-ps7HVEPVW)@ z2hZUUFpiOqy&?1BYHZm@Spw( z?fvRy*Uyk$rvla%M^@9=X-1nw0Upnlx}ZJ|ur`40j6KUYJ2|*-#X9=E8+&jU_F<66 zWP#cq4xos;@gNVlqr5&%{TFaAj#J|Vub*HDUtt(Oz``U(@GDQ3-{IgDqTi1A1+<`E zqDdkn@hY1N|6qf}Hi>qwe_`<^3H@)({}c5YT;PXpWJ^0)o n;dQ*hYYU&>WQ6Yp>j%O55kBS{bzru$e?DP#eTq}~0*QYCp2q-> literal 0 HcmV?d00001 diff --git a/locale/en-US.txt b/locale/en-US.txt new file mode 100644 index 0000000..d24483f --- /dev/null +++ b/locale/en-US.txt @@ -0,0 +1,32 @@ +# Locale +locale { + id en-US + name English (United States) +} + +# Main window +app { + title { + default PVB Emulator + mixed {ctrl.number} {ctrl.filename} - {app.title.default} + number {ctrl.number} {app.title.default} + rom {ctrl.filename} - {app.title.default} + } + + file { + (menu) File + load_rom Load ROM... + exit Exit + } + +} + +# Emulation core +core { + java Java + linux-x86 Linux (32-bit) + linux-x86_64 Linux (64-bit) + native Native + windows-x86 Windows (32-bit) + windows-x86_64 Windows (64-bit) +} diff --git a/locale/en_US.txt b/locale/en_US.txt deleted file mode 100644 index 85c6689..0000000 --- a/locale/en_US.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Main window -app { - title { - default PVB Emulator - mixed {ctrl.number} {ctrl.filename} - {app.title.default} - number {ctrl.number} {app.title.default} - rom {ctrl.filename} - {app.title.default} - } -} diff --git a/makefile b/makefile index f6467e9..3ecbb74 100644 --- a/makefile +++ b/makefile @@ -67,7 +67,8 @@ core: .PHONY: desktop desktop: clean_desktop @echo " Compiling Java desktop application" - @javac -sourcepath src/desktop -d . -Xlint:unchecked src/desktop/Main.java + @javac -sourcepath src/desktop --release 10 -Xlint:unchecked \ + -d . src/desktop/Main.java # Build all native modules .PHONY: native @@ -83,17 +84,17 @@ pack: $(eval jarname = "pvbemu_`date +%Y%m%d`.jar") @echo " Bundling into $(jarname)" @jar -cfe $(jarname) Main *.class \ - locale native src util vue makefile license.txt + app locale native src util vue makefile license.txt # Delete only Java .class files .PHONY: clean_desktop clean_desktop: - @rm -r -f *.class util vue + @rm -r -f *.class app util vue # Delete everything but the .jar .PHONY: clean_most clean_most: clean_desktop - @rm -f src/desktop/native/vue_NativeVUE.h native/* + @rm -f src/desktop/native/vue_NativeVUE.h native/*.dll native/*.so diff --git a/src/desktop/Main.java b/src/desktop/Main.java index 1ea43b8..5b50c55 100644 --- a/src/desktop/Main.java +++ b/src/desktop/Main.java @@ -1,5 +1,5 @@ -import java.util.*; -import javax.swing.*; +// Project imports +import app.*; import util.*; // Desktop application primary class @@ -7,13 +7,8 @@ public class Main { // Program entry point public static void main(String[] args) { - var loc = new Localizer(); - loc.set(Util.textRead("locale/en_US.txt")); - var window = new JFrame(); - loc.add(window, "app.title.mixed"); - loc.put(window, "ctrl.filename", "wario.vb"); - loc.put(window, "ctrl.number", "1"); - System.out.println(window.getTitle()); + Util.setSystemLAF(); + new App(); } } diff --git a/src/desktop/app/App.java b/src/desktop/app/App.java new file mode 100644 index 0000000..b4dfa1a --- /dev/null +++ b/src/desktop/app/App.java @@ -0,0 +1,113 @@ +package app; + +// Java imports +import java.util.*; + +// Project imports +import util.*; + +// Top-level software state manager +public class App { + + // Instance fields + private Localizer.Locale[] locales; // Language translations + private Localizer localizer; // UI localization manager + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + public App() { + + // Instance fields + localizer = new Localizer(); + + // Additional processing + initLocales(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Public Methods // + /////////////////////////////////////////////////////////////////////////// + + // Associate a control with the localizer + public boolean addControl(Object control, Object key) { + return localizer.add(control, key); + } + + // Retrieve the currently active locale + public Localizer.Locale getLocale() { + return localizer.getLocale(); + } + + // Retrieve a list of registered locales + public Localizer.Locale[] listLocales() { + var ret = new Localizer.Locale[locales.length]; + System.arraycopy(locales, 0, ret, 0, locales.length); + return ret; + } + + // Remove a control from the localizer + public boolean removeControl(Object control) { + return localizer.remove(control); + } + + // Specify a new locale + public void setLocale(Localizer.Locale locale) { + localizer.setLocale(locale); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Private Methods // + /////////////////////////////////////////////////////////////////////////// + + // Load and parse all locale translations + private void initLocales() { + + // Process all locale files + var locales = new HashMap(); + for (String file : Util.listFiles("locale")) { + + // Process the file + try { + var locale = Localizer.parse(Util.textRead("locale/" + file)); + String key = locale.id.toLowerCase(); + + // Register the locale + if (locales.containsKey(key)) throw new RuntimeException( + "Locale with ID '" + locale.id + "' already registered"); + locales.put(key, locale); + + // The locale matches the one in the config + if (key.equals("en-us")) + localizer.setLocale(locale); + } + + // Could not process the file + catch (Exception e) { + System.err.println("Error parsing locale " + + file + ": " + e.getMessage()); + } + + } + + // Produce the list of registered locales + this.locales = locales.values().toArray( + new Localizer.Locale[locales.size()]); + Arrays.sort(this.locales); + + // Select a default locale + if (localizer.getLocale() != null || this.locales.length == 0) + return; + var locale = locales.get("en-us"); + localizer.setLocale(locale != null ? locale : this.locales[0]); + } + +} diff --git a/src/desktop/util/Localizer.java b/src/desktop/util/Localizer.java index bbdf995..d34f9d6 100644 --- a/src/desktop/util/Localizer.java +++ b/src/desktop/util/Localizer.java @@ -11,8 +11,8 @@ public class Localizer { // Instance fields private HashMap controls; // Control mapping - private HashMap messages; // Message dictionary - private HashMap> tags; // Control overrides + private Locale locale; // Current message store + private HashMap> tags; // Control messages @@ -26,124 +26,69 @@ public class Localizer { /////////////////////////////////////////////////////////////////////////// - // Constructors // + // Classes // /////////////////////////////////////////////////////////////////////////// - // Default constructor - public Localizer() { - controls = new HashMap(); - messages = new HashMap(); - tags = new HashMap>(); + // Locale container + public static class Locale implements Comparable { + + // Public fields + public final String id; // Unique identifier + public final String name; // Display name + + // Private fields + private HashMap messages; // Message dictionary + + // Constructor + private Locale(HashMap messages) { + id = messages.get("locale.id"); + this.messages = messages; + name = messages.get("locale.name"); + } + + // Comparator + public int compareTo(Locale o) { + return id.compareTo(o.id); + } + + // Represent this object as a string + public String toString() { + return id + " - " + name; + } + } /////////////////////////////////////////////////////////////////////////// - // Public Methods // + // Static Methods // /////////////////////////////////////////////////////////////////////////// - // Add a control to the collection - public boolean add(Object control, Object key) { - - // Error checking - if (control == null || key == null) - return false; - - // Control takes a single string - if (key instanceof String) { - if (!( - control instanceof AbstractButton || - control instanceof JFrame || - control instanceof JInternalFrame || - control instanceof JPanel || // TitledBorder - control instanceof JTextComponent - )) return false; - } - - // Control takes an array of strings - else if (key instanceof String[]) { - if (!( - JCOMBOBOX.getClass().isAssignableFrom(control.getClass()) - )) return false; - } - - // Invalid control type - else return false; - - // Add the control to the collection - controls.put(control, key); - tags .put(control, new HashMap()); - update(); - return true; - } - - // Remove all controls from being managed - public void clearControls() { - controls.clear(); - } - - // Configure a control tag - public String put(Object control, String key, String value) { - if (controls.get(control) == null || key == null) - return null; - var tags = this.tags.get(control); - key = key.toLowerCase(); - String ret = value == null ? - tags.remove(key) : - tags.put(key, value) - ; - update(); - return ret; - } - - // Configure a dictionary entry - public String put(String key, String value) { - String ret = value == null ? - messages.remove(key) : - messages.put(key, value) - ; - update(); - return ret; - } - - // Remove a control from the collection - public boolean remove(Object control) { - tags.remove(control); - return controls.remove(control) != null; - } - - // Specify a message dictionary - public void set(String text) { - - // Pre-processing - messages.clear(); - if (text == null) { - update(); - return; - } - - // Configure working variables + // Parse a text file to produce a Locale object + public static Locale parse(String text) { var chars = text.replaceAll("\\r\\n?", "\n").toCharArray(); - int col = 1; // Current character on the current line - int line = 1; // Current line in the file + var ret = new HashMap(); // Output dictionary int start = 0; // Position of first character in key or value var stack = new Stack(); // Nested key chain int state = 0; // Current parsing context // Process all characters - for (int x = 0; x < chars.length; x++, col++) { + for (int x = 0, line = 1, col = 1; x < chars.length; x++, col++) { char c = chars[x]; boolean end = x == chars.length - 1; String pos = line + ":" + col + ": "; boolean white = c == ' ' || c == '\t'; + // Processing for newline + if (c == '\n') { + col = 0; + line++; + } + // Comment if (state == -1) { - if (c != '\n' && !end) - continue; - col = 1; - state = 0; - line++; + if (c == '\n' || end) + state = 0; } // Pre-key @@ -164,11 +109,8 @@ public class Localizer { break; // The line is empty - if (c == '\n') { - col = 1; - line++; + if (c == '\n') continue; - } // Proceed to key start = x; @@ -263,9 +205,7 @@ public class Localizer { if (end) x++; String value = new String(chars, start, x - start).trim(); - col = 1; state = 0; - line++; // Open a key group if (value.equals("{")) { @@ -281,8 +221,7 @@ public class Localizer { case '{' : depth++; continue; case '}' : if (--depth == -1) throw new RuntimeException( - line + ":" + (x - start + y + 1) + - ": Unexpected '}'" + (line - 1) + ": Unexpected '}'" ); continue; } @@ -298,18 +237,118 @@ public class Localizer { // Check for duplicate keys String lkey = key.toLowerCase(); - if (messages.get(lkey) != null) throw new RuntimeException( + if (ret.get(lkey) != null) throw new RuntimeException( (line-1)+ ": Key '" + key + "' has already been defined."); // Add the pair to the dictionary - messages.put(lkey, value); + ret.put(lkey, value); stack.pop(); } } - // Update all controls + // Perform post-processing error checks + if (state != 0 || !stack.empty()) throw new RuntimeException( + "Unexpected end of input"); + if (!ret.containsKey("locale.id")) throw new RuntimeException( + "Required key not found: 'locale.id'"); + if (!ret.containsKey("locale.name")) throw new RuntimeException( + "Required key not found: 'locale.name'"); + return new Locale(ret); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + public Localizer() { + controls = new HashMap(); + tags = new HashMap>(); + } + + // Parsing constructor + public Localizer(String text) { + this(); + setLocale(parse(text)); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Public Methods // + /////////////////////////////////////////////////////////////////////////// + + // Add a control to the collection + public boolean add(Object control, Object key) { + + // Error checking + if (control == null || key == null) + return false; + + // Control takes a single string + if (key instanceof String) { + if (!( + control instanceof AbstractButton || + control instanceof JFrame || + control instanceof JInternalFrame || + control instanceof JPanel || // TitledBorder + control instanceof JTextComponent + )) return false; + } + + // Control takes an array of strings + else if (key instanceof String[]) { + if (!( + JCOMBOBOX.getClass().isAssignableFrom(control.getClass()) + )) return false; + } + + // Invalid control type + else return false; + + // Add the control to the collection + controls.put(control, key); + tags .put(control, new HashMap()); update(); + return true; + } + + // Remove all controls from being managed + public void clearControls() { + controls.clear(); + } + + // Retrieve the currently loaded locale + public Locale getLocale() { + return locale; + } + + // Configure a control tag + public String put(Object control, String key, String value) { + if (controls.get(control) == null || key == null) + return null; + var tags = this.tags.get(control); + key = key.toLowerCase(); + String ret = value == null ? + tags.remove(key) : + tags.put(key, value) + ; + update(); + return ret; + } + + // Remove a control from the collection + public boolean remove(Object control) { + tags.remove(control); + return controls.remove(control) != null; + } + + // Specify a message dictionary + public void setLocale(Locale locale) { + this.locale = locale; } @@ -320,9 +359,13 @@ public class Localizer { // Process substitutions and escapes on a message for a given key private String evaluate(Object control, String key) { - String ret = messages.get(key.toLowerCase()); - // The topmost key does not exist in the dictionary + // No locale is loaded + if (locale == null) + return key; + + // Check that the key exists + String ret = locale.messages.get(key.toLowerCase()); if (ret == null) return key; @@ -354,7 +397,7 @@ public class Localizer { String lkey = key.toLowerCase(); String value = tags.get(control).get(lkey); if (value == null) - value = messages.get(lkey); + value = locale.messages.get(lkey); if (value == null) value = "\\{" + key + "\\}"; diff --git a/src/desktop/util/Util.java b/src/desktop/util/Util.java index 0471f19..c002a18 100644 --- a/src/desktop/util/Util.java +++ b/src/desktop/util/Util.java @@ -5,7 +5,9 @@ import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; +import java.net.*; import java.nio.charset.*; +import java.nio.file.*; import java.util.*; import javax.imageio.*; import javax.swing.*; @@ -53,6 +55,23 @@ public final class Util { new BOM(new byte[] { - 1, - 2 }, StandardCharsets.UTF_16LE), }; + // Filesystem state manager for the current .jar (if any) + private static final FileSystem JARFS; + + // Static initializer + static { + FileSystem fs = null; + try { + fs = FileSystems.newFileSystem( + new URI("jar:" + Util.class.getProtectionDomain() + .getCodeSource().getLocation().toString()), + new HashMap(), + Util.class.getClassLoader() + ); + } catch (Exception e) { } + JARFS = fs; + } + /////////////////////////////////////////////////////////////////////////// @@ -138,6 +157,34 @@ public final class Util { catch (Exception e) { return null; } } + // Produce a list of files contained in a directory + // If the directory is not found on disk, looks for it in the .jar + public static String[] listFiles(String path) { + + // Check for the directory on disk + var file = new File(path); + if (file.exists() && file.isDirectory()) + return file.list(); + + // Not executing out of a .jar + if (JARFS == null) + return null; + + // Check for the directory in the .jar + try { + var list = Files.list(JARFS.getPath("/" + path)).toArray(); + var ret = new String[list.length]; + for (int x = 0; x < list.length; x++) { + path = "/" + ((Path) list[x]).toString(); + ret[x] = path.substring(path.lastIndexOf("/") + 1); + } + return ret; + } catch (Exception e) { } + + // The directory was not found + return null; + } + // Produce a WindowListener with a functional interface public static WindowListener onClose(OnClose close) { return new WindowListener() { @@ -246,7 +293,7 @@ public final class Util { return split; } - // Read a text file, accounting for BOMs and guaranteeing Unix line endings + // Read a text file, accounting for BOMs public static String textRead(String filename) { // Read the file @@ -277,8 +324,8 @@ public final class Util { data = temp; } - // Produce a string with LF-style (Unix) line endings - return new String(data, set).replaceAll("\\r\\n?", "\n"); + // Produce a string + return new String(data, set); } } diff --git a/util/Localizer$1.class b/util/Localizer$1.class new file mode 100644 index 0000000000000000000000000000000000000000..8e91022ef5cfd351c9b088576a60d885e4c0f57e GIT binary patch literal 185 zcmX^0Z`VEs1_m<*ZgvJHMh1b>lFS@^pZw&+oXo1!A{9e+1{R=bR$^JAeokUuy1su> zR%&tyBLi1(erZv1s#|7GDkB3QT!S7+B_jjBYhH3resN}Ax^HSpMt%xA11C@ws@aST zJf3-ZsYT8?iN(dK#R3Wpj10^`r+@%611peZVqjxn2huDI96*u_j2VHdfIxr)0JSPA AH~;_u literal 0 HcmV?d00001 diff --git a/util/Localizer$Locale.class b/util/Localizer$Locale.class new file mode 100644 index 0000000000000000000000000000000000000000..888a611a6cfda3e97fc7c6135e1ffcb42fd313cf GIT binary patch literal 1813 zcma)7TT|0e5dKcNq^+?O4Dn7hgPV=w%bh(@a~TFQ+3he!86t*EL=^OT6z4FmU_!+trWi(hQXsE} zAm3S^G!RhBdU*s*3ND%07#WHK$eXF~>v~pZ- zx+MByAK%{Gf`$47vNLlsnR$jprrNi?T6RrAkzxF#6_rlfe8-^PM>h3_$=$Y1>wCQ| zE%)ZpYgy7roQhSa5F=IH6dUd4j~s+l3v zhgj>@sH91fD6iFM3#3a63k-9A6|z?zDttMhhtD3coJd{nruKH9CdG?xT5Y=~R&?2s zSm%S>pat17n&iipNpa=4x_q))o5oA0fCQS?;v=52-7 zT+9Bz@F_THKF|xg%FVhV96A%M{dP+Wl;F==z;eoQ>qATQfxipjkDC%O`5`LYFgA5p z(8gO~7-Gr!Bx6w5OjFnugFB9J6i_c?r$?L4Ax}L8Rnq75$8<@-2E$B`BK-oH7{I7JNoaeI%EN)4A$i8qw}sBSMC*_|GUzi-iCq3W7+I!|Ey=HhW4KB8Ns0;NF^DuyvBt;*Qmcc literal 0 HcmV?d00001 diff --git a/util/Localizer.class b/util/Localizer.class new file mode 100644 index 0000000000000000000000000000000000000000..87c3c79f2e702029c418a1017e94cead85291681 GIT binary patch literal 8162 zcmb7J3w%`NmH(f4-O1$UH89Aa=pccFJP060ogfmTXdnq_5X!^qkh#LhWG2o`c!*ec z8y|gWDPnE1t5B+@7A#tI(4ahAQIW0Mhr8?B*43@rR(D&wO2O>^yLXaI9#oef_s+fN zp6`6;{LlH$Ip3ETzUbKxpjs|+k=Sa1Ct42_6iX!qV@m7mPom{`HQ(t1t#m~lnU*Nc z13D()OzKKZS_Pv^M;PlyF#+AI*eB~KK`B)>u5AxRIh`*XwuP&%FkQMjdJR(r;|5^9tUZ>Bv>F#}XffJSk$B7t!8B#zbiw3o!IsBrM$pZJN}Q$a znmI(5z9CDzuwj}~=o}sA;yh~77H=DBX-0E5YG~;uW$F1kX5#{ZyCoh=g(5N9dTO>x z%FoS2!HBgOwAc$fYL)79l^!E|z=sQUT!eYVq^&)6}LVrzeRPl zDk7I+LwvoFs0$?xwbk_ zwHj{RBAE1LBNb;QgOJUD!HBsyJ~)2ymeK4ZhV6WTj0&XHr%P<2RiP;4+Z&yr4zCF zbqgEkE}XmYYlPPH%P=`LN=BECf5Ja!7J4GClG-TCjf1pTtoP{Hu2>zZ(CVaz1TL~| z*s0^kxR_1L$29EDtlz%x^XDb#I}JY@GH!l-jLSb3ie|n)G<4jeP|S#G__^SW zY(J#9*sJC*=A8~{i^q&uO2easZADi2XI8Lsa9YP+JWhnU^y;Faq(Zna3z-GkBod1r z?9=e1j;FA{uLcf$XNW|SwaH{;bu6?hYRpT-&3ctJr6opHpKUm(0yh@gOAiCTkx@tPw?N2e9EtFQC&zwB$+Z|MuK5_CObF_>(KgC zm9dVIUNxABQM(0y*6|lr^$IDY*@&7cygn8-%tZB99si5ZtdFdvty$s&BZ$vtF@+z~ z@i!c2!(zqV;OUi$U!tH&vRm+lj$W2xcr7W}vj_siWMqF=IY(^d%FY278drzfqbdQI z8M!eYHliL8aj*i2Q?(h{$)K+mDpf?&#Vt9O!_9Gm(=7%1{5eRfStYhet}eRd5k`i| zAdH!g*>9PW3v?+|P!)y3;lVhmdA+)fQVa((z z+ez#EY<7-U{XfQ@Y=V=n@YywLigI&`KA`$5Vz!DI35wfMN59bGhDgk4YHwX-Bo?dO z$)#>?E4U9a-!(Z+U|Uhg&W6~ip6IJsJY_lMn7NeOkCat8)hg|)VScVs(@K+&)hST1 zL1}I{rY8kqguJ3jF=O+~mZ>I_8D_({vg3eid79(xi56pCL}8TQulH0H`3$|e@pvl9 zQrp&Oq}Ig4$pV*@d!#}tHJQr%EmeYIGl!a0D-v55Uu$G`n}mpawRxcyHsRbF{xBQi z45KCVl29xhHIi%);%nR6YO)+L9F#!=7jJAcMqtmFY--fE46}r0MAU_%(dJ0XsBsJT zUQP*0FyNN67&?;Gm6mKSam(3UU(#f(k20nLeGRKy&SBVaP+fZ>x-qbd?TC>Kgc63? zgHkwK@i5z2)s`mR!Y#w7Y{4;0oLlIFvDqI6^}%w!nmQO5su6c-c|zT?w5er03)2ik zx|k!_9tTRKB)|;Xr{PQ^w_zU&mhDAR zqkZ;1`1o{6&|$B3+N)iTYRysY_PNUU@3a?|`&=`tbJU9?yaSq}8|3o%G@rW%6dla< z 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 literal 0 HcmV?d00001 diff --git a/util/Util$3.class b/util/Util$3.class new file mode 100644 index 0000000000000000000000000000000000000000..a8f09cab2aeb7b128a3e37afc484850b151b3144 GIT binary patch literal 785 zcma)4%Wl&^6g}4tnL2iwM=6g|3T*>TQiK=2xp zj626kn5TrL;4*ZehXx!N@`4A)Fc-|*ahy+UaN!BmJuIR@Xr9TL^kb<{{DbkC3Nu3W zX{4j<8Nu%MMlM=t3oLoKj%7mY$}faUDC3y0)(r|R`7ZO-OzF%ove+~3pf?h@Nmwm= z5kzUGw3-rn-I6Lwpx%go-AyKn&h2}==#xhM64^_-3iTgZB7la$InoY9`09zg`Z;8!k5w|#co4qZwFl-zB z?Q>B7(K$r_BWmv%BCPOTWea@g`wo`pq#Y(%jJEq9KwnWeX8RMG77Wk&hxY@kjM$(S pQ}31vhzkKxF7WeGz{XmkeviuoDpfgc5Pj>UaS}UCQYfDVN=YF}5k7#p1RUi=ga_Y6$;Tqf0+jR&tUp?#9DGsGdfaqF_gQ&#GjDEfeZEiOl}CO5emi7qfgc5Pf5ZY?C<6M=9SFS^`bd3Lg+$g1D3-A=&g$$bI9if{U|8@h1GPO06n5 zaOA+BL5MqI){Q6#A2{sJXlCD=**Ej`=cmsAo}gQS16QEr;S$P(+T6s6|Aw#5W5UWp zCY@=zP=vMMB6Fa_be^e!#jbd$SYZBiJd=|uYVZW=9#+vHG>_y$`iayN|6p{aB10%Y zjdg6E5!`NXSVIeKfi(}8u}*0HQ-x59WRegzxP2jU6)WyT+b7fR>nKUHSWiM_-lk)LEq?G@0uLSB=b5DE zdfQ@z;D5U>RO@do1_bY=W>NMMnPn;?h~;c?muFi8;R>8))9wbqmPgHNHO^jV zZ`*DV+eUxq6x826h3KE5ddwlh4Zh24fp2`@#QGBSh(QiV+x-*J7u2oU`G}?ii*x?K xea9`1xS$q8Zx<4Xa{^IJ@Z*BO#YWD4hrb7uu!(zkz+QXV4ZFOKo7gT_egSVfv9$mI literal 0 HcmV?d00001 diff --git a/util/Util$6.class b/util/Util$6.class new file mode 100644 index 0000000000000000000000000000000000000000..ba35529f6c3c68646c6b9104ccca2045b8ef6a63 GIT binary patch literal 948 zcma)5OK;Oa5dPK)aT7buqm*YUg$5cs2p^Do3E~o^5>i4DExB);RqNJWSN1yPZy`}3 zIB?{^KjNW^SvO4)SSkna%+7rC&11*k&OUzuu!&9?1_~yMHm;$>V5KSuy|;9Q37fl31Z4gNDDm@yDc$beuve-wu^Gz2QQSWq^5Yp*H3Pp9q2QUnvJ<7TFSY`mr>x)Lscwj>=u1D0l0fba zh1!oMCLR%BJ}Vm=Xfv46`Li%={coeWBfS_{m%4i?X>z@gQv(TQm_0n!=;19e+<;N9 z>%{>`(jL%G>2DFY$a|Z#4ZU6@Yi{Qh%zbhS)BT9bdonTHp|eC1_)h0tEYE^;prH|e z!+sE8fgc5Qg7L(m4J|>$>Hme1?!hl1li11V}+#q9TwIDv^@=#$L5<-E|c^snmZ} zsR+S=BL{vIVm3ITf>u3vW_I?`%nq4xZp(SZyyLiu&1qU;T!*lZ1L zEMvuh>tGcg!TY}=p%jQXCe)j~Sut^v`En{%=692$<3w?^t&g47(7-yOb`{l&(o8Bj zCfsUXHhOEwy}C)rPh06lN`9CejpTS&jAES$=FQ$kVJH=0d6DTSQyE@N4EB=~Cfo(_ zM;vCVgG>a6eQ}(ZwFb##9LTqk?qO<~pXu?r-?j?kB#qQwUuOGBXy6e83wazoMuSjE za@`59E`QUNn$6Yd5S;glW$DHuO=ZeO=RXrQ-h~PXYarLv`@k!*#Gj2DoW03WorwZ_ ztG)FZ)cykFj8CjE+cJBQwzuKs{08SWPyb>D0~^<4dYU`eVB&OH9RSszX8(rtM32+ literal 0 HcmV?d00001 diff --git a/util/Util$8$1.class b/util/Util$8$1.class new file mode 100644 index 0000000000000000000000000000000000000000..f60f0e1b8149a8ec2993a2f289c89c0edce338c3 GIT binary patch literal 616 zcmb7BT}whi5It+==e5kVvY>=~P?N5`L}B!x76=xB+528ybK`ce+z;z-^$A+j_VtFc-LWaX8*Yh~oXCN2sOXurq<2od zQfDD%uU#+SF`Sm>MEr{zZrTIEu-cK9xb`0n;oNe=q)gluUc+_*VY~Bsj3BN5pn-x! z*Y=&hxRe1?jpI_=jvV_w#x$6oS1zKZ_Hp6s&9zC(36Cd rjgSZ1Bb&zBl#r*qD#8AKD^Q(G0InlTK8zeTu}x8i2s!fVC@P(Ap`eo+ literal 0 HcmV?d00001 diff --git a/util/Util$8.class b/util/Util$8.class new file mode 100644 index 0000000000000000000000000000000000000000..00781fedeb42d1202cf2e4ee3569f03f0cc30367 GIT binary patch literal 552 zcma)3%SyvQ6g|_%#-^!{TAxJ}DVTO)bR$Z|g{?xRicl-KoTO77iAl+$`dPXX6#M`` zO1y(ws;kc8+{?L-bLZZV&)0VVyC@k*A+19*F@X%j<{cmKM>~3u{*B%Dc+YltBwhPR zVY~0i_=5YQ(`3jTNMFW>3~8%!rDKvIpTyF$I|Svu@{R!=hU%JQ*tc3Ev07_Pk(z3p zXUL5dGiddoD;TC*(ii8+y(7X)?s&u$-B9pYG(?XlUfhra*%cwfwpAHV@Ta22V6=lI zbj68OZL{h}TWVwyJ@#EMh-f)yBEAi}IuJRd9Y37x7`Y52f!7>#cT&4tKc2-Y+*#8n?%^mwq4yr(~^cWae}$+O707RJB+ zoUlKbM*sJR6J8x>kCMz2F@b953MnCrfnRYT^c?T@M6I T+vLo`7Pi@YTy0|qd+>e$L|{k& literal 0 HcmV?d00001 diff --git a/util/Util$OnClose.class b/util/Util$OnClose.class new file mode 100644 index 0000000000000000000000000000000000000000..a4cf0b23872fd6e666927c7f4b51148d15541dd2 GIT binary patch literal 207 zcmX|*u?oUK5JYEV%$XQLzre;q8?Upm3<`pfLe%<C!r&2RwapbjJ6WVUQKgX#K4DsjMsOwcgQvGbW|rV*LMg(0dF(UMS}q%@ zEx+daUbef=Z&w#WbSmpAllxprLfE}tcNc{CptYcXhK$Rx9=DIL#4_=M3}U$rvYo>X=`b6yktTpL43rrS2VD|ag- z1Rv b0-+ z-@HH17l19M0t3Rl@wt^39?Q(7rEY40fG{gmtE5%#F0)%G;RNx+hXlfq8v?{dMhICA QAo@8*#5?8-<{$~9FH{aM%>V!Z literal 0 HcmV?d00001 diff --git a/util/Util$OnKey.class b/util/Util$OnKey.class new file mode 100644 index 0000000000000000000000000000000000000000..64a2c7014871b16ab7c26d777b52c2157b1a9312 GIT binary patch literal 200 zcmX|5y9&ZU5S)#9m>AIyu(Z&|>ufAVK@==R`!g((aGa1_BL13%AK*ucn-n%P12elb zpYQ7pV2NP_k1(oTZq!*|dbE3fM(_#aLN{6&ZExy$EjV)oKhwq#CbL85>4#I?uysmq zHmz9AFNEZDugi?LxnV-weqFS2Lb|h-%eB!}#Z`oXxc_28&{c;7LQl2`Yl2L literal 0 HcmV?d00001 diff --git a/util/Util$OnMouse.class b/util/Util$OnMouse.class new file mode 100644 index 0000000000000000000000000000000000000000..697ce28edb5f2029dc5fe7f873e605b14157cc7e GIT binary patch literal 206 zcmX|5F$%&!5S)!MXJQ2XfR%+dUT0$|3W8!G+MnT&gyRCaOZ=OK5AadqCWy`Kz|8E- z`}2GOSYad(5GJ+Hjl3|-PWD*VP78#DaiJO|jk0%nx)nO}gfLUa5T^4(r>TdRy3y9l zF12mxdT}Kr=d!Lcy~~Xz#O>#08z7{6Yjw3T$~o->hOGX}3F22D5ePkA2oMkHBVran PbT~lFH(>|&AdTWLV8t)! literal 0 HcmV?d00001 diff --git a/util/Util$OnResize.class b/util/Util$OnResize.class new file mode 100644 index 0000000000000000000000000000000000000000..d9a1345965a7da6dc5a6b56822ce9a97b32dc6b4 GIT binary patch literal 212 zcmX|*Jqp4=5QX1F&7Tp(BZ!4IZf9dD3W5+&v|qy@3F}5oqTtaiJb;H1Cjo8Vyy4By z_x?Oz0A}cUa0r8{OpV%$jAFaxB7LxjHld&Cn^s2KlZuZSCncesXk!S&@utc2y;OW- zTdGBVzT{TivTo;-10mey)ivQ&YM2nz=cn}&AzE9@*M-qV!G(veq<;c}|6_0ogqCmu WBs3kk;v9I5`Us?kG9ix;xxp7L$uepH literal 0 HcmV?d00001 diff --git a/util/Util.class b/util/Util.class new file mode 100644 index 0000000000000000000000000000000000000000..530cf9d0995d1a35e95cd7f97a2410a368065ad4 GIT binary patch literal 7477 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 literal 0 HcmV?d00001