From 73c8245eff51883f0f4a478607d190ad5516b051 Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Sat, 1 Aug 2020 18:28:47 -0500 Subject: [PATCH] Introducing the main window, yet more tweaks to localization support --- Main.class | Bin 337 -> 0 bytes app/App.class | Bin 3059 -> 0 bytes images/app_icon.png | Bin 0 -> 220 bytes locale/en-US.txt | 10 ++ makefile | 2 +- src/desktop/app/App.java | 42 ++++-- src/desktop/app/Window.java | 218 ++++++++++++++++++++++++++++++++ src/desktop/util/Localizer.java | 181 +++++++++++++++----------- src/desktop/util/Util.java | 12 ++ util/Localizer$1.class | Bin 185 -> 0 bytes util/Localizer$Locale.class | Bin 1813 -> 0 bytes util/Localizer.class | Bin 8162 -> 0 bytes util/Util$1.class | Bin 981 -> 0 bytes util/Util$2.class | Bin 1066 -> 0 bytes util/Util$3.class | Bin 785 -> 0 bytes util/Util$4.class | Bin 811 -> 0 bytes util/Util$5.class | Bin 802 -> 0 bytes util/Util$6.class | Bin 948 -> 0 bytes util/Util$7.class | Bin 829 -> 0 bytes util/Util$8$1.class | Bin 616 -> 0 bytes util/Util$8.class | Bin 552 -> 0 bytes util/Util$BOM.class | Bin 370 -> 0 bytes util/Util$OnClose.class | Bin 207 -> 0 bytes util/Util$OnClose2.class | Bin 219 -> 0 bytes util/Util$OnFocus.class | Bin 206 -> 0 bytes util/Util$OnKey.class | Bin 200 -> 0 bytes util/Util$OnMouse.class | Bin 206 -> 0 bytes util/Util$OnResize.class | Bin 212 -> 0 bytes util/Util.class | Bin 7477 -> 0 bytes 29 files changed, 382 insertions(+), 83 deletions(-) delete mode 100644 Main.class delete mode 100644 app/App.class create mode 100644 images/app_icon.png create mode 100644 src/desktop/app/Window.java delete mode 100644 util/Localizer$1.class delete mode 100644 util/Localizer$Locale.class delete mode 100644 util/Localizer.class delete mode 100644 util/Util$1.class delete mode 100644 util/Util$2.class delete mode 100644 util/Util$3.class delete mode 100644 util/Util$4.class delete mode 100644 util/Util$5.class delete mode 100644 util/Util$6.class delete mode 100644 util/Util$7.class delete mode 100644 util/Util$8$1.class delete mode 100644 util/Util$8.class delete mode 100644 util/Util$BOM.class delete mode 100644 util/Util$OnClose.class delete mode 100644 util/Util$OnClose2.class delete mode 100644 util/Util$OnFocus.class delete mode 100644 util/Util$OnKey.class delete mode 100644 util/Util$OnMouse.class delete mode 100644 util/Util$OnResize.class delete mode 100644 util/Util.class diff --git a/Main.class b/Main.class deleted file mode 100644 index 6fcc6764e670e6357cbf421ba959d0f3da539917..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/app/App.class b/app/App.class deleted file mode 100644 index e7b5490c4596dd92f870bf779f141923df5d767e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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-> diff --git a/images/app_icon.png b/images/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ad6e8b1a6d90105424d3e5499117c3def704bc5 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPV2cwv<=-rT|+CZU9PZ!4!i_=>tISMr>@Gx(etv~dC z{*&lON1K%{T)TLup=9~wgfmOu_H+J0+Z(kc&z{lrNU7TM`?Qp)cUbq8sX6P#9x+;E%BnB4X*>?Jl)=;0 K&t;ucLK6U~h)T2o literal 0 HcmV?d00001 diff --git a/locale/en-US.txt b/locale/en-US.txt index d24483f..4d11e2d 100644 --- a/locale/en-US.txt +++ b/locale/en-US.txt @@ -6,6 +6,7 @@ locale { # Main window app { + title { default PVB Emulator mixed {ctrl.number} {ctrl.filename} - {app.title.default} @@ -16,6 +17,7 @@ app { file { (menu) File load_rom Load ROM... + new_window New window exit Exit } @@ -30,3 +32,11 @@ core { windows-x86 Windows (32-bit) windows-x86_64 Windows (64-bit) } + +# File dialog +dialog { + load Load + load_rom Load ROM + load_rom_error Unable to load the selected ROM file. + load_rom_notvb The selected file does not appear to be a Virtual Boy ROM. +} diff --git a/makefile b/makefile index 3ecbb74..afbe3c4 100644 --- a/makefile +++ b/makefile @@ -84,7 +84,7 @@ pack: $(eval jarname = "pvbemu_`date +%Y%m%d`.jar") @echo " Bundling into $(jarname)" @jar -cfe $(jarname) Main *.class \ - app locale native src util vue makefile license.txt + app images locale native src util vue makefile license.txt # Delete only Java .class files .PHONY: clean_desktop diff --git a/src/desktop/app/App.java b/src/desktop/app/App.java index b4dfa1a..97ed65f 100644 --- a/src/desktop/app/App.java +++ b/src/desktop/app/App.java @@ -12,6 +12,7 @@ public class App { // Instance fields private Localizer.Locale[] locales; // Language translations private Localizer localizer; // UI localization manager + private ArrayList windows; // Application windows @@ -24,41 +25,55 @@ public class App { // Instance fields localizer = new Localizer(); + windows = new ArrayList(); // Additional processing initLocales(); + addWindow(); } /////////////////////////////////////////////////////////////////////////// - // Public Methods // + // Package Methods // /////////////////////////////////////////////////////////////////////////// - // Associate a control with the localizer - public boolean addControl(Object control, Object key) { - return localizer.add(control, key); + // Add a new program window + void addWindow() { + windows.add(new Window(this)); + windowsChanged(); } // Retrieve the currently active locale - public Localizer.Locale getLocale() { + Localizer.Locale getLocale() { return localizer.getLocale(); } + // Retrieve the localization manager + Localizer getLocalizer() { + return localizer; + } + // Retrieve a list of registered locales - public Localizer.Locale[] listLocales() { + 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); + // Determine the number of a window in the collection + int numberOf(Window window) { + return windows.indexOf(window) + 1; + } + + // Remove a program window + void removeWindow(Window window) { + windows.remove(window); + windowsChanged(); } // Specify a new locale - public void setLocale(Localizer.Locale locale) { + void setLocale(Localizer.Locale locale) { localizer.setLocale(locale); } @@ -110,4 +125,11 @@ public class App { localizer.setLocale(locale != null ? locale : this.locales[0]); } + // A window has been added to or removed from the program state + private void windowsChanged() { + int count = windows.size(); + for (int x = 0; x < count; x++) + windows.get(x).windowsChanged(x + 1, count == 1); + } + } diff --git a/src/desktop/app/Window.java b/src/desktop/app/Window.java new file mode 100644 index 0000000..fbfece7 --- /dev/null +++ b/src/desktop/app/Window.java @@ -0,0 +1,218 @@ +package app; + +// Java imports +import java.awt.*; +import java.awt.image.*; +import java.io.*; +import javax.swing.*; +import javax.swing.filechooser.*; + +// Project imports +import util.*; + +// Main application window +class Window extends JFrame { + + // Instance fields + private App app; // Containing application + private int number; // Window number within application + private boolean only; // This is the only application window + private File romFile; // Currently loaded ROM file + + // UI components + private File pwd; // Most recent working directory + private JPanel video; // Video output + + + + /////////////////////////////////////////////////////////////////////////// + // Constants // + /////////////////////////////////////////////////////////////////////////// + + // Application icon + private static final BufferedImage APPICON; + + // Static initializer + static { + APPICON = Util.imageRead("images/app_icon.png"); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + Window(App app) { + super(); + + // Configure instance fields + this.app = app; + + // Configure video pane + video = new JPanel() { + public void paintComponent(Graphics g) { + super.paintComponent(g); + onPaintVideo((Graphics2D) g, getWidth(), getHeight()); + } + }; + video.setPreferredSize(new Dimension(384, 224)); + + // Configure window + addWindowListener(Util.onClose(e->onClose())); + setContentPane(video); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + setIconImage(APPICON); + setJMenuBar(initMenus()); + app.getLocalizer().add(this, "app.title.default"); + + // Display window + pack(); + setLocationRelativeTo(null); + setVisible(true); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Menu Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Produce the window's menu bar + private JMenuBar initMenus() { + var bar = new JMenuBar(); + var loc = app.getLocalizer(); + bar.setBorder(null); + bar.add(initMenuFile(loc)); + return bar; + } + + // Initialize the File menu + private JMenu initMenuFile(Localizer loc) { + var mnuFile = new JMenu(); + loc.add(mnuFile, "app.file.(menu)"); + + var mnuFileLoadRom = new JMenuItem(); + loc.add(mnuFileLoadRom, "app.file.load_rom"); + mnuFileLoadRom.addActionListener(e->onLoadROM()); + mnuFile.add(mnuFileLoadRom); + + mnuFile.addSeparator(); + + var mnuFileNewWindow = new JMenuItem(); + loc.add(mnuFileNewWindow, "app.file.new_window"); + mnuFileNewWindow.addActionListener(e->onNewWindow()); + mnuFile.add(mnuFileNewWindow); + + var mnuFileExit = new JMenuItem(); + loc.add(mnuFileExit, "app.file.exit"); + mnuFileExit.addActionListener(e->onClose()); + mnuFile.add(mnuFileExit); + + return mnuFile; + } + + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // A window has been added to or removed from the program state + void windowsChanged(int number, boolean only) { + this.number = number; + this.only = only; + app.getLocalizer().put(this, "ctrl.number", "" + number); + updateTitle(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Event Handlers // + /////////////////////////////////////////////////////////////////////////// + + // Window close, File -> Exit + private void onClose() { + app.removeWindow(this); + dispose(); + } + + // File -> Load ROM + private void onLoadROM() { + var loc = app.getLocalizer(); + + // Prompt the user to select a file + var dlgFile = new JFileChooser(pwd); + dlgFile.addChoosableFileFilter(new FileNameExtensionFilter( + "Virtual Boy ROMs (*.vb)", "vb")); + dlgFile.addChoosableFileFilter(new FileNameExtensionFilter( + "ISX modules (*.isx)", "isx")); + dlgFile.setAcceptAllFileFilterUsed(true); + dlgFile.setDialogTitle(loc.get("dialog.load_rom")); + int option = dlgFile.showDialog(this, loc.get("dialog.load")); + + // The user did not select a file + var file = dlgFile.getSelectedFile(); + if (option != JFileChooser.APPROVE_OPTION || file == null) + return; + + // Update the current directory + pwd = file.getParentFile(); + + // Attempt to load the ROM file + var data = Util.fileRead(file); + if (data == null) { + JOptionPane.showMessageDialog(this, + loc.get("dialog.load_rom_error"), + loc.get("dialog.load_rom"), + JOptionPane.ERROR_MESSAGE + ); + return; + } + + // Update the emulation state + romFile = file; + loc.put(this, "ctrl.filename", file.getName()); + updateTitle(); + } + + // File -> New window + private void onNewWindow() { + app.addWindow(); + } + + // Video paint + private void onPaintVideo(Graphics2D g, int width, int height) { + int scale = Math.max(1, Math.min(width / 384, height / 224)); + + g.translate( + Math.max(0, (width - scale * 384) / 2), + Math.max(0, (height - scale * 224) / 2) + ); + g.scale(scale, scale); + + g.setColor(Color.black); + g.fillRect(0, 0, 384, 224); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Private Methods // + /////////////////////////////////////////////////////////////////////////// + + // Update the window title + private void updateTitle() { + app.getLocalizer().add(this, + only ? romFile != null ? + "app.title.rom" : + "app.title.default" + : romFile != null ? + "app.title.mixed" : + "app.title.number" + ); + } + +} diff --git a/src/desktop/util/Localizer.java b/src/desktop/util/Localizer.java index d34f9d6..3a40faf 100644 --- a/src/desktop/util/Localizer.java +++ b/src/desktop/util/Localizer.java @@ -10,9 +10,8 @@ import javax.swing.text.*; public class Localizer { // Instance fields - private HashMap controls; // Control mapping - private Locale locale; // Current message store - private HashMap> tags; // Control messages + private HashMap controls; // Control mapping + private Locale locale; // Current message store @@ -29,6 +28,18 @@ public class Localizer { // Classes // /////////////////////////////////////////////////////////////////////////// + // Control settings + private static class Control { + String key; // Single-string dictionary key + String[] keys; // Multiple-string dictionary key + String tipKey; // Tooltip dictionary key + HashMap tags; // Message overrides + Control(String tipKey) { + tags = new HashMap(); + this.tipKey = tipKey; + } + } + // Locale container public static class Locale implements Comparable { @@ -265,8 +276,7 @@ public class Localizer { // Default constructor public Localizer() { - controls = new HashMap(); - tags = new HashMap>(); + controls = new HashMap(); } // Parsing constructor @@ -283,6 +293,14 @@ public class Localizer { // Add a control to the collection public boolean add(Object control, Object key) { + return add(control, key, null); + } + + // Add a control with a tooltip to the collection + public boolean add(Object control, Object key, String tipKey) { + var ctrl = controls.get(control); + if (ctrl == null) + ctrl = new Control(tipKey); // Error checking if (control == null || key == null) @@ -290,6 +308,8 @@ public class Localizer { // Control takes a single string if (key instanceof String) { + + // Type validation if (!( control instanceof AbstractButton || control instanceof JFrame || @@ -297,22 +317,31 @@ public class Localizer { control instanceof JPanel || // TitledBorder control instanceof JTextComponent )) return false; + + // Configure key + ctrl.key = (String) key; } // Control takes an array of strings else if (key instanceof String[]) { + + // Type validation if (!( JCOMBOBOX.getClass().isAssignableFrom(control.getClass()) )) return false; + + // Configure keys + String[] keys = (String[]) key; + ctrl.keys = new String[keys.length]; + System.arraycopy(keys, 0, ctrl.keys, 0, keys.length); } // Invalid control type else return false; // Add the control to the collection - controls.put(control, key); - tags .put(control, new HashMap()); - update(); + controls.put(control, ctrl); + update(control); return true; } @@ -321,6 +350,11 @@ public class Localizer { controls.clear(); } + // Evaluate the message for a given key + public String get(String key) { + return key == null ? null : evaluate(null, key); + } + // Retrieve the currently loaded locale public Locale getLocale() { return locale; @@ -328,21 +362,26 @@ public class Localizer { // Configure a control tag public String put(Object control, String key, String value) { - if (controls.get(control) == null || key == null) + var ctrl = controls.get(control); + + // Error checking + if (ctrl == null || key == null) return null; - var tags = this.tags.get(control); + + // Update the control's tags key = key.toLowerCase(); String ret = value == null ? - tags.remove(key) : - tags.put(key, value) + ctrl.tags.remove(key) : + ctrl.tags.put(key, value) ; - update(); + + // Refresh the text on the control + update(control); return ret; } // Remove a control from the collection public boolean remove(Object control) { - tags.remove(control); return controls.remove(control) != null; } @@ -358,7 +397,7 @@ public class Localizer { /////////////////////////////////////////////////////////////////////////// // Process substitutions and escapes on a message for a given key - private String evaluate(Object control, String key) { + private String evaluate(Control control, String key) { // No locale is loaded if (locale == null) @@ -395,7 +434,7 @@ public class Localizer { // Determine the substitution key = new String(chars, start, x - start); String lkey = key.toLowerCase(); - String value = tags.get(control).get(lkey); + String value = control == null ? null : control.tags.get(lkey); if (value == null) value = locale.messages.get(lkey); if (value == null) @@ -430,68 +469,66 @@ public class Localizer { // Update the text for all controls private void update() { + for (var control : controls.keySet()) + update(control); + } - // Process all controls - for (var control : controls.keySet()) { - Object key = controls.get(control); - String[] values = null; + // Update the text for a control + private void update(Object control) { + var ctrl = controls.get(control); + String[] keys = ctrl.key==null ? ctrl.keys : new String[]{ctrl.key}; + String[] values = new String[keys.length]; - // One string - if (key instanceof String) - values = new String[] { evaluate(control, (String) key) }; + // Evaluate all messages + for (int x = 0; x < keys.length; x++) + values[x] = evaluate(ctrl, keys[x]); - // Multiple strings - else { - String[] keys = (String[]) key; - values = new String[keys.length]; - for (int x = 0; x < keys.length; x++) - values[x] = evaluate(control, keys[x]); - } - - // Update the control's text - if (control instanceof AbstractButton) - ((AbstractButton) control).setText (values[0]); - if (control instanceof JFrame) - ((JFrame ) control).setTitle(values[0]); - if (control instanceof JInternalFrame) - ((JInternalFrame) control).setTitle(values[0]); - if (control instanceof JTextComponent) - ((JTextComponent) control).setText (values[0]); - - // JPanel must be wrapped in a TitledBorder - if (control instanceof JPanel) { - var border = ((JPanel) control).getBorder(); - if (border instanceof TitledBorder) - ((TitledBorder) border).setTitle(values[0]); - } - - // Replace the contents of a JComboBox without firing events - if (JCOMBOBOX.getClass().isAssignableFrom(control.getClass())) { - - // The type is explicitly verified above - @SuppressWarnings("unchecked") - var box = (JComboBox) control; - - // Configure working variables - var action = box.getActionListeners(); - int index = box.getSelectedIndex(); - var item = box.getItemListeners(); - - // Remove event listeners - for (var lst : action) box.removeActionListener(lst); - for (var lst : item ) box.removeItemListener (lst); - - // Update contents - box.setModel(new DefaultComboBoxModel(values)); - box.setSelectedIndex(index); - - // Restore event listeners - for (var lst : action) box.addActionListener(lst); - for (var lst : item ) box.addItemListener (lst); - } + // Update the control's text + if (control instanceof AbstractButton) + ((AbstractButton) control).setText (values[0]); + if (control instanceof JFrame) + ((JFrame ) control).setTitle(values[0]); + if (control instanceof JInternalFrame) + ((JInternalFrame) control).setTitle(values[0]); + if (control instanceof JTextComponent) + ((JTextComponent) control).setText (values[0]); + // JPanel must be wrapped in a TitledBorder + if (control instanceof JPanel) { + var border = ((JPanel) control).getBorder(); + if (border instanceof TitledBorder) + ((TitledBorder) border).setTitle(values[0]); } + // Replace the contents of a JComboBox without firing events + if (JCOMBOBOX.getClass().isAssignableFrom(control.getClass())) { + + // The type is explicitly verified above + @SuppressWarnings("unchecked") + var box = (JComboBox) control; + + // Configure working variables + var action = box.getActionListeners(); + int index = box.getSelectedIndex(); + var item = box.getItemListeners(); + + // Remove event listeners + for (var lst : action) box.removeActionListener(lst); + for (var lst : item ) box.removeItemListener (lst); + + // Update contents + box.setModel(new DefaultComboBoxModel(values)); + box.setSelectedIndex(index); + + // Restore event listeners + for (var lst : action) box.addActionListener(lst); + for (var lst : item ) box.addItemListener (lst); + } + + // Update the control's tooltip text + if (control instanceof JComponent) + ((JComponent) control).setToolTipText( + ctrl.tipKey == null ? null : evaluate(ctrl, ctrl.tipKey)); } } diff --git a/src/desktop/util/Util.java b/src/desktop/util/Util.java index c002a18..ce2c894 100644 --- a/src/desktop/util/Util.java +++ b/src/desktop/util/Util.java @@ -117,6 +117,18 @@ public final class Util { return new Color(bits, true); } + // Read a file from disk + public static byte[] fileRead(File file) { + FileInputStream stream = null; + byte[] data = null; + try { + stream = new FileInputStream(file); + data = stream.readAllBytes(); + } catch (Exception e) { } + try { stream.close(); } catch (Exception e) { } + return data; + } + // Read a file, first from disk, then from .jar public static byte[] fileRead(String filename) { InputStream stream = null; diff --git a/util/Localizer$1.class b/util/Localizer$1.class deleted file mode 100644 index 8e91022ef5cfd351c9b088576a60d885e4c0f57e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Localizer$Locale.class b/util/Localizer$Locale.class deleted file mode 100644 index 888a611a6cfda3e97fc7c6135e1ffcb42fd313cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Localizer.class b/util/Localizer.class deleted file mode 100644 index 87c3c79f2e702029c418a1017e94cead85291681..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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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 diff --git a/util/Util$3.class b/util/Util$3.class deleted file mode 100644 index a8f09cab2aeb7b128a3e37afc484850b151b3144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Util$6.class b/util/Util$6.class deleted file mode 100644 index ba35529f6c3c68646c6b9104ccca2045b8ef6a63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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+ diff --git a/util/Util$8$1.class b/util/Util$8$1.class deleted file mode 100644 index f60f0e1b8149a8ec2993a2f289c89c0edce338c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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+ diff --git a/util/Util$8.class b/util/Util$8.class deleted file mode 100644 index 00781fedeb42d1202cf2e4ee3569f03f0cc30367..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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& diff --git a/util/Util$OnClose.class b/util/Util$OnClose.class deleted file mode 100644 index a4cf0b23872fd6e666927c7f4b51148d15541dd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Util$OnKey.class b/util/Util$OnKey.class deleted file mode 100644 index 64a2c7014871b16ab7c26d777b52c2157b1a9312..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Util$OnMouse.class b/util/Util$OnMouse.class deleted file mode 100644 index 697ce28edb5f2029dc5fe7f873e605b14157cc7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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)! diff --git a/util/Util$OnResize.class b/util/Util$OnResize.class deleted file mode 100644 index d9a1345965a7da6dc5a6b56822ce9a97b32dc6b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/util/Util.class b/util/Util.class deleted file mode 100644 index 530cf9d0995d1a35e95cd7f97a2410a368065ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7477 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