--- /dev/null
+package javajs.async;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import swingjs.api.JSUtilI;
+
+/**
+ * The Assets class allows assets such as images and property files to be
+ * combined into zip files rather than delivered individually. The Assets
+ * instance is a singleton served by a set of static methods. In particular, the
+ * three add(...) methods are used to create asset references, which include an
+ * arbitrary name, a path to a zip file asset, and one or more class paths that
+ * are covered by this zip file asset.
+ *
+ * For example:
+ *
+ * <code>
+ static {
+ try {
+ Assets.add(new Assets.Asset("osp", "osp-assets.zip", "org/opensourcephysics/resources"));
+ Assets.add(new Assets.Asset("tracker", "tracker-assets.zip",
+ "org/opensourcephysics/cabrillo/tracker/resources"));
+ Assets.add(new Assets.Asset("physlets", "physlet-assets.zip", new String[] { "opticsimages", "images" }));
+ // add the Info.assets last so that it can override these defaults
+ if (OSPRuntime.isJS) {
+ Assets.add(OSPRuntime.jsutil.getAppletInfo("assets"));
+ }
+ } catch (Exception e) {
+ OSPLog.warning("Error reading assets path. ");
+ }
+
+ }
+ * </code>
+ *
+ * It is not clear that Java is well-served by this zip-file loading, but
+ * certainly JavaScript is. What could be 100 downloads is just one, and SwingJS
+ * (but not Java) can cache individual ZipEntry instances in order to unzip them
+ * independently only when needed. This is potentially a huge savings.
+ *
+ * Several static methods can be used to retrieve assets. Principal among those
+ * are:
+ *
+ * <code>
+ * getAssetBytes(String fullPath)
+ * getAssetString(String fullPath)
+ * getAssetStream(String fullPath)
+ * </code>
+ *
+ * If an asset is not found in a zip file, then it will be loaded from its fullPath.
+ *
+ *
+ *
+ * @author hansonr
+ *
+ */
+
+public class Assets {
+
+ public static boolean isJS = /** @j2sNative true || */
+ false;
+
+ public static JSUtilI jsutil;
+
+ static {
+ try {
+ if (isJS) {
+ jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
+ }
+
+ } catch (Exception e) {
+ System.err.println("Assets could not create swinjs.JSUtil instance");
+ }
+ }
+
+ private Map<String, Map<String, ZipEntry>> htZipContents = new HashMap<>();
+
+ private static boolean doCacheZipContents = true;
+
+ private static Assets instance = new Assets();
+
+ private Assets() {
+ }
+
+ private Map<String, Asset> assetsByPath = new HashMap<>();
+
+ private String[] sortedList = new String[0];
+
+ /**
+ * If this object has been cached by SwingJS, add its bytes to the URL, URI, or
+ * File
+ *
+ * @param URLorURIorFile
+ * @return
+ */
+ public static byte[] addJSCachedBytes(Object URLorURIorFile) {
+ return (isJS ? jsutil.addJSCachedBytes(URLorURIorFile) : null);
+ }
+
+ public static class Asset {
+ String name;
+ URI uri;
+ String classPath;
+ String zipPath;
+ String[] classPaths;
+
+ public Asset(String name, String zipPath, String[] classPaths) {
+ this.name = name;
+ this.zipPath = zipPath;
+ this.classPaths = classPaths;
+ }
+
+ public Asset(String name, String zipPath, String classPath) {
+ this.name = name;
+ this.zipPath = zipPath;
+ uri = getAbsoluteURI(zipPath); // no spaces expected here.
+ this.classPath = classPath.endsWith("/") ? classPath : classPath + "/";
+ }
+
+ public URL getURL(String fullPath) throws MalformedURLException {
+ return (fullPath.indexOf(classPath) < 0 ? null
+ : new URL("jar", null, uri + "!/" + fullPath));//.replaceAll(" ", "%20")));
+ }
+
+ @Override
+ public String toString() {
+ return "{" + "\"name\":" + "\"" + name + "\"," + "\"zipPath\":" + "\"" + zipPath + "\"," + "\"classPath\":"
+ + "\"" + classPath + "\"" + "}";
+ }
+
+ }
+
+ public static Assets getInstance() {
+ return instance;
+ }
+
+ /**
+ * The difference here is that URL will not insert the %20 for space that URI will.
+ *
+ * @param path
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static URL getAbsoluteURL(String path) {
+ URL url = null;
+ try {
+ url = (path.indexOf(":/") < 0 ? new File(new File(path).getAbsolutePath()).toURL() : new URL(path));
+ if (path.indexOf("!/")>=0)
+ url = new URL("jar", null, url.toString());
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ return url;
+ }
+
+ public static URI getAbsoluteURI(String path) {
+ URI uri = null;
+ try {
+ uri = (path.indexOf(":/") < 0 ? new File(new File(path).getAbsolutePath()).toURI() : new URI(path));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ return uri;
+ }
+
+ /**
+ * Allows passing a Java Asset or array of Assets or a JavaScript Object or
+ * Object array that contains name, zipPath, and classPath keys; in JavaScript,
+ * the keys can have multiple .
+ *
+ * @param o
+ */
+ public static void add(Object o) {
+ if (o == null)
+ return;
+ try {
+ if (o instanceof Object[]) {
+ Object[] a = (Object[]) o;
+ for (int i = 0; i < a.length; i++)
+ add(a[i]);
+ return;
+ }
+ // In JavaScript this may not actually be an Asset, only a proxy for that.
+ // Just testing for keys. Only one of classPath and classPaths is allowed.
+ Asset a = (Asset) o;
+ if (a.name == null || a.zipPath == null || a.classPath == null && a.classPaths == null
+ || a.classPath != null && a.classPaths != null) {
+ throw new NullPointerException("Assets could not parse " + o);
+ }
+ if (a.classPaths == null) {
+ // not possible in Java, but JavaScript may be passing an array of class paths
+ add(a.name, a.zipPath, a.classPath);
+ } else {
+ add(a.name, a.zipPath, a.classPaths);
+ }
+ } catch (Throwable t) {
+ throw new IllegalArgumentException(t.getMessage());
+ }
+ }
+
+ public static void add(String name, String zipFile, String path) {
+ add(name, zipFile, new String[] { path });
+ }
+
+ private static HashSet<String> loadedAssets = new HashSet<>();
+
+ public static boolean hasLoaded(String name) {
+ return loadedAssets.contains(name);
+ }
+
+ public static void reset() {
+ getInstance().htZipContents.clear();
+ getInstance().assetsByPath.clear();
+ getInstance().sortedList = new String[0];
+ }
+
+ public static void add(String name, String zipFile, String[] paths) {
+ getInstance()._add(name, zipFile, paths);
+ }
+
+ private void _add(String name, String zipFile, String[] paths) {
+ if (hasLoaded(name)) {
+ System.err.println("Assets warning: Asset " + name + " already exists");
+ }
+ loadedAssets.add(name);
+ for (int i = paths.length; --i >= 0;) {
+ assetsByPath.put(paths[i], new Asset(name, zipFile, paths[i]));
+ }
+ resort();
+ }
+
+
+ /**
+ * Gets the asset, preferably from a zip file asset, but not necessarily.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static byte[] getAssetBytes(String assetPath) {
+ return getAssetBytes(assetPath, false);
+ }
+
+ /**
+ * Gets the asset, preferably from a zip file asset, but not necessarily.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static String getAssetString(String assetPath) {
+ return getAssetString(assetPath, false);
+ }
+
+ /**
+ * Gets the asset, preferably from a zip file asset, but not necessarily.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static InputStream getAssetStream(String assetPath) {
+ return getAssetStream(assetPath, false);
+ }
+
+ /**
+ * Gets the asset from a zip file.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static byte[] getAssetBytesFromZip(String assetPath) {
+ return getAssetBytes(assetPath, true);
+ }
+
+ /**
+ * Gets the asset from a zip file.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static String getAssetStringFromZip(String assetPath) {
+ return getAssetString(assetPath, true);
+ }
+
+ /**
+ * Gets the asset from a zip file.
+ *
+ * @param assetPath
+ * @return
+ */
+ public static InputStream getAssetStreamFromZip(String assetPath) {
+ return getAssetStream(assetPath, true);
+ }
+
+
+ /**
+ * Get the contents of a path from a zip file asset as byte[], optionally loading
+ * the resource directly using a class loader.
+ *
+ * @param path
+ * @param zipOnly
+ * @return
+ */
+ private static byte[] getAssetBytes(String path, boolean zipOnly) {
+ byte[] bytes = null;
+ try {
+ URL url = getInstance()._getURLFromPath(path, true);
+ if (url == null && !zipOnly) {
+ url = getAbsoluteURL(path);
+ //url = Assets.class.getResource(path);
+ }
+ if (url == null)
+ return null;
+ if (isJS) {
+ bytes = jsutil.getURLBytes(url);
+ if (bytes == null) {
+ url.openStream();
+ bytes = jsutil.getURLBytes(url);
+ }
+ } else {
+ bytes = getLimitedStreamBytes(url.openStream(), -1, null);
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return bytes;
+ }
+
+ /**
+ * Get the contents of a path from a zip file asset as a String, optionally
+ * loading the resource directly using a class loader.
+ *
+ * @param path
+ * @param zipOnly
+ * @return
+ */
+ private static String getAssetString(String path, boolean zipOnly) {
+ byte[] bytes = getAssetBytes(path, zipOnly);
+ return (bytes == null ? null : new String(bytes));
+ }
+
+ /**
+ * Get the contents of a path from a zip file asset as an InputStream, optionally
+ * loading the resource directly using a class loader.
+ *
+ * @param path
+ * @param zipOnly
+ * @return
+ */
+ private static InputStream getAssetStream(String path, boolean zipOnly) {
+ try {
+ URL url = getInstance()._getURLFromPath(path, true);
+ if (url == null && !zipOnly) {
+ url = Assets.class.getResource(path);
+ }
+ if (url != null)
+ return url.openStream();
+ } catch (Throwable t) {
+ }
+ return null;
+ }
+ /**
+ * Determine the path to an asset. If not found in a zip file asset, return the
+ * absolute path to this resource.
+ *
+ * @param fullPath
+ * @return
+ */
+ public static URL getURLFromPath(String fullPath) {
+ return getInstance()._getURLFromPath(fullPath, false);
+ }
+
+ /**
+ * Determine the path to an asset. If not found in a zip file asset, optionally
+ * return null or the absolute path to this resource.
+ *
+ * @param fullPath
+ * @param zipOnly
+ * @return the URL to this asset, or null if not found.
+ */
+ public static URL getURLFromPath(String fullPath, boolean zipOnly) {
+ return getInstance()._getURLFromPath(fullPath, zipOnly);
+ }
+
+ private URL _getURLFromPath(String fullPath, boolean zipOnly) {
+ URL url = null;
+ try {
+ if (fullPath.startsWith("/"))
+ fullPath = fullPath.substring(1);
+ for (int i = sortedList.length; --i >= 0;) {
+ if (fullPath.startsWith(sortedList[i])) {
+ url = assetsByPath.get(sortedList[i]).getURL(fullPath);
+ ZipEntry ze = findZipEntry(url);
+ if (ze == null)
+ break;
+ if (isJS) {
+ jsutil.setURLBytes(url, jsutil.getZipBytes(ze));
+ }
+ return url;
+ }
+ }
+ if (!zipOnly)
+ return getAbsoluteURL(fullPath);
+ } catch (MalformedURLException e) {
+ }
+ return null;
+ }
+
+ public static ZipEntry findZipEntry(URL url) {
+ String[] parts = getJarURLParts(url.toString());
+ if (parts == null || parts[0] == null || parts[1].length() == 0)
+ return null;
+ return findZipEntry(parts[0], parts[1]);
+ }
+
+ public static ZipEntry findZipEntry(String zipFile, String fileName) {
+ return getZipContents(zipFile).get(fileName);
+ }
+
+ /**
+ * Gets the contents of a zip file.
+ *
+ * @param zipPath the path to the zip file
+ * @return a set of file names in alphabetical order
+ */
+ public static Map<String, ZipEntry> getZipContents(String zipPath) {
+ return getInstance()._getZipContents(zipPath);
+ }
+
+ private Map<String, ZipEntry> _getZipContents(String zipPath) {
+ URL url = getURLWithCachedBytes(zipPath); // BH carry over bytes if we have them already
+ Map<String, ZipEntry> fileNames = htZipContents.get(url.toString());
+ if (fileNames != null)
+ return fileNames;
+ try {
+ // Scan URL zip stream for files.
+ return readZipContents(url.openStream(), url);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Deconstruct a jar URL into two parts, before and after "!/".
+ *
+ * @param source
+ * @return
+ */
+ public static String[] getJarURLParts(String source) {
+ int n = source.indexOf("!/");
+ if (n < 0)
+ return null;
+ String jarfile = source.substring(0, n).replace("jar:", "");
+ while (jarfile.startsWith("//"))
+ jarfile = jarfile.substring(1);
+ return new String[] { jarfile, (n == source.length() - 2 ? null : source.substring(n + 2)) };
+ }
+
+ /**
+ * Get the contents of any URL as a byte array. This method does not do any asset check. It just gets the url data as a byte array.
+ *
+ * @param url
+ * @return byte[]
+ *
+ * @author hansonr
+ */
+ public static byte[] getURLContents(URL url) {
+ if (url == null)
+ return null;
+ try {
+ if (isJS) {
+ // Java 9! return new String(url.openStream().readAllBytes());
+ return jsutil.readAllBytes(url.openStream());
+ }
+ return getLimitedStreamBytes(url.openStream(), -1, null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ *
+ * Convert a file path to a URL, retrieving any cached file data, as from DnD.
+ * Do not do any actual data transfer. This is a swingjs.JSUtil service.
+ *
+ * @param path
+ * @return
+ */
+ private static URL getURLWithCachedBytes(String path) {
+ URL url = getAbsoluteURL(path);
+ if (url != null)
+ addJSCachedBytes(url);
+ return url;
+ }
+
+ private Map<String, ZipEntry> readZipContents(InputStream is, URL url) throws IOException {
+ HashMap<String, ZipEntry> fileNames = new HashMap<String, ZipEntry>();
+ if (doCacheZipContents)
+ htZipContents.put(url.toString(), fileNames);
+ ZipInputStream input = new ZipInputStream(is);
+ ZipEntry zipEntry = null;
+ int n = 0;
+ while ((zipEntry = input.getNextEntry()) != null) {
+ if (zipEntry.isDirectory() || zipEntry.getSize() == 0)
+ continue;
+ n++;
+ String fileName = zipEntry.getName();
+ fileNames.put(fileName, zipEntry); // Java has no use for the ZipEntry, but JavaScript can read it.
+ }
+ input.close();
+ System.out.println("Assets: " + n + " zip entries found in " + url); //$NON-NLS-1$
+ return fileNames;
+ }
+
+ private void resort() {
+ sortedList = new String[assetsByPath.size()];
+ int i = 0;
+ for (String path : assetsByPath.keySet()) {
+ sortedList[i++] = path;
+ }
+ Arrays.sort(sortedList);
+ }
+
+
+ /**
+ * Only needed for Java
+ *
+ * @param is
+ * @param n
+ * @param out
+ * @return
+ * @throws IOException
+ */
+ private static byte[] getLimitedStreamBytes(InputStream is, long n, OutputStream out) throws IOException {
+
+ // Note: You cannot use InputStream.available() to reliably read
+ // zip data from the web.
+
+ boolean toOut = (out != null);
+ int buflen = (n > 0 && n < 1024 ? (int) n : 1024);
+ byte[] buf = new byte[buflen];
+ byte[] bytes = (out == null ? new byte[n < 0 ? 4096 : (int) n] : null);
+ int len = 0;
+ int totalLen = 0;
+ if (n < 0)
+ n = Integer.MAX_VALUE;
+ while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {
+ totalLen += len;
+ if (toOut) {
+ out.write(buf, 0, len);
+ } else {
+ if (totalLen > bytes.length)
+ bytes = Arrays.copyOf(bytes, totalLen * 2);
+ System.arraycopy(buf, 0, bytes, totalLen - len, len);
+ if (n != Integer.MAX_VALUE && totalLen + buflen > bytes.length)
+ buflen = bytes.length - totalLen;
+ }
+ }
+ if (toOut)
+ return null;
+ if (totalLen == bytes.length)
+ return bytes;
+ buf = new byte[totalLen];
+ System.arraycopy(bytes, 0, buf, 0, totalLen);
+ return buf;
+ }
+
+ /**
+ * Return all assets in the form that is appropriate for the Info.assets value in SwingJS.
+ *
+ */
+ @Override
+ public String toString() {
+ String s = "[";
+ for (int i = 0; i < sortedList.length; i++) {
+ Asset a = assetsByPath.get(sortedList[i]);
+ s += (i == 0 ? "" : ",") + a;
+ }
+ return s + "]";
+ }
+
+}
--- /dev/null
+package javajs.async;
+
+/**
+ * A package to manage asynchronous aspects of SwingJS
+ *
+ * The javajs.async package simplifies the production of methods that can be
+ * used equally well in Java and in JavaScript for handling "pseudo-modal"
+ * blocking in JavaScript, meaning the user is locked out of other interactions,
+ * as in Java, but the code is not actually blocking the thread.
+ *
+ * Included in this package are
+ *
+ * Async
+ *
+ * Provides few simple generic static methods.
+ *
+ * Async.isJS() -- true if we are running this in JavaScript
+ *
+ * Async.javaSleep() -- bypassing Thread.sleep() for JavaScript; allowing it for
+ * Java
+ *
+ *
+ * AsyncDialog
+ *
+ * Provides several very useful methods that act as a replacement for direct
+ * JOptionPane or JDialog calls, including both a synchronous callback
+ * (manifested in Java) and an asynchronous callback (JavaScript), both
+ * resulting in the same effect, except for the fact that the JavaScript code
+ * has returned immediately from the call with an "ignore me" reference, while
+ * Java is waiting at that call to return a value from JOptionPane (which is
+ * saved by AsyncDialog and not delivered as a return value).
+ *
+ * AsyncDialog does not extend JOptionPane, but it mirrors that classes public
+ * methods. There are a LOT of public methods in JOPtionPane. I suppose we
+ * should implement them all. In practice, AsyncDialog calls standard
+ * JOptionPane static classes for the dialogs.
+ *
+ * Initially, the following methods are implemented:
+ *
+ * public void showConfirmDialog(Component frame, Object message, String title,
+ * ActionListener a)
+ *
+ * public void showConfirmDialog(Component frame, Object message, String title,
+ * int optionType, ActionListener a)
+ *
+ * public void showConfirmDialog(Component frame, Object message, String title,
+ * int optionType, int messageType, ActionListener a)
+ *
+ * public void showInputDialog(Component frame, Object message, ActionListener
+ * a)
+ *
+ * public void showInputDialog(Component frame, Object message, String title,
+ * int messageType, Icon icon, Object[] selectionValues, Object
+ * initialSelectionValue, ActionListener a)
+ *
+ * public void showMessageDialog(Component frame, Object message, ActionListener
+ * a)
+ *
+ * public void showOptionDialog(Component frame, Object message, String title,
+ * int optionType, int messageType, Icon icon, Object[] options, Object
+ * initialValue, ActionListener a)
+ *
+ *
+ * All nonstatic methods, requiring new AsyncDialog(), also require an
+ * ActionListener. This listener will get a call to actionPerformed(ActionEvent)
+ * where:
+ *
+ * event.getSource() is a reference to the originating AsyncDialog (super
+ * JOptionPane) for all information that a standard JOptionPane can provide,
+ * along with the two methods int getOption() and Object getChoice().
+ *
+ * event.getID() is a reference to the standard JOptionPane int return code.
+ *
+ * event.getActionCommand() also holds a value, but it may or may not be of
+ * value.
+ *
+ *
+ * A few especially useful methods are static, allowing just one or two expected
+ * callbacks of interest:
+ *
+ * AsyncDialog.showOKAsync(Component parent, Object message, String title,
+ * Runnable ok)
+ *
+ * AsyncDialog.showYesAsync (Component parent, Object message, String title,
+ * Runnable yes)
+ *
+ * AsyncDialog.showYesNoAsync (Component parent, Object message, String title,
+ * Runnable yes, Runnable no)
+ *
+ * These methods provide a fast way to adjust JOptionPane calls to be
+ * asynchronous.
+ *
+ *
+ *
+ * AsyncFileChooser extends javax.swing.JFileChooser
+ *
+ * Accepted constructors include:
+ *
+ * public AsyncFileChooser()
+ *
+ * public AsyncFileChooser(File file)
+ *
+ * public AsyncFileChooser(File file, FileSystemView view)
+ *
+ * (Note, however, that FileSystemView has no equivalent in JavaScript.)
+ *
+ * It's three public methods include:
+ *
+ * public void showDialog(Component frame, String btnLabel, Runnable ok,
+ * Runnable cancel)
+ *
+ * public void showOpenDialog(Component frame, Runnable ok, Runnable cancel)
+ *
+ * public void showSaveDialog(Component frame, Runnable ok, Runnable cancel)
+ *
+ *
+ * ActionListener is not needed here, as the instance of new AsyncFileChooser()
+ * already has direct access to all the JFileChooser public methods such as
+ * getSelectedFile() and getSelectedFiles().
+ *
+ * As a subclass of JFileChooser, it accepts all three public showXXXX methods
+ * of JFileChooser, namely:
+ *
+ * public void showDialog(Component frame, String btnLabel)
+ *
+ * public void showOpenDialog(Component frame)
+ *
+ * public void showSaveDialog(Component frame)
+ *
+ *
+ * None of these are recommended. AsyncFileChooser will indicate errors if the
+ * first of these two are called. (showSaveDialog is fine, as it is modal even
+ * in JavaScript. However it is not recommended that showSaveDialog(Component
+ * frame) be used, as in the future browsers may implement some sort of file
+ * saver in HTML5.
+ *
+ *
+ *
+ * AsyncColorChooser
+ *
+ *
+ * AsyncColorChooser accesses JColorChooser asynchronously, using a private
+ * SwingJS setting that tells JColorChooser to report back to it with property
+ * changes. It is constructed using new AsyncColorChooser() and implements just
+ * two methods:
+ *
+ * public void showDialog(Component component, String title, Color initialColor,
+ * ActionListener listener)
+ *
+ * public Color getSelectedColor()
+ *
+ *
+ * The listener will get an actionPerformed(ActionEvent) callback with
+ * event.getID() equal to the color value or 0 if canceled. The
+ * getSelectedColor() method may also be called from this callback to retrieve
+ * the associated java.awt.Color object, using
+ *
+ * ((AsyncColorChooser)e.getSource()).getSelectedColor()
+ *
+ * As in Java, a null value for the selected color indicates that the
+ * JColorChooser was closed.
+ *
+ * Bob Hanson 2019.11.07
+ *
+ *
+ * @author Bob Hanson hansonr_at_stolaf.edu
+ *
+ */
+public class Async {
+
+ public static boolean isJS() {
+ return (/** @j2sNative 1 ? true : */false);
+ }
+
+ /**
+ * No sleep in JavaScript
+ * @param ms
+ */
+ public static void javaSleep(int ms) {
+ if (!isJS()) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ }
+
+}
--- /dev/null
+package javajs.async;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JColorChooser;
+import javax.swing.plaf.UIResource;
+
+/**
+ * A simple Asynchronous file chooser for JavaScript; synchronous with Java.
+ *
+ * Allows two modes -- using an ActionListener (setAction(ActionListener) or constructor(ActionListener))
+ *
+ * @author Bob Hanson
+ */
+
+public class AsyncColorChooser implements PropertyChangeListener {
+
+ private ActionListener listener;
+ private Color selectedColor;
+
+ public void showDialog(Component component, String title, Color initialColor, ActionListener listener) {
+ setListener(listener);
+ process(JColorChooser.showDialog(component, title, initialColor));
+ unsetListener();
+ }
+
+ public Color getSelectedColor() {
+ return selectedColor;
+ }
+
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ // JavaScript only
+ Color c = (Color) evt.getNewValue();
+
+ switch (evt.getPropertyName()) {
+ case "SelectedColor":
+ process(c);
+ break;
+ }
+ }
+
+ private void setListener(ActionListener a) {
+ listener = a;
+ /** @j2sNative Clazz.load("javax.swing.JColorChooser");javax.swing.JColorChooser.listener = this */
+ }
+
+ private void unsetListener() {
+ /** @j2sNative javax.swing.JColorChooser.listener = null */
+ }
+
+
+
+ private void process(Color c) {
+ if (c instanceof UIResource)
+ return;
+ selectedColor = c;
+ listener.actionPerformed(new ActionEvent(this, c == null ? 0 : c.getRGB(), c == null ? null : c.toString()));
+ }
+
+}
--- /dev/null
+package javajs.async;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import javax.swing.plaf.UIResource;
+
+/**
+ * A class to manage asynchronous input, option, and confirmation dialogs.
+ *
+ * @author Bob Hanson hansonr_at_stolaf.edu
+ *
+ */
+public class AsyncDialog implements PropertyChangeListener {
+
+// see discussion in net.sf.j2s.core/doc/Differences.txt
+//
+// Confirmation dialog example. Note moving the parent component into the constructor.
+// Original:
+//
+// private void promptQuit() {
+// int sel = JOptionPane.showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+// switch (sel) {
+// case JOptionPane.YES_OPTION:
+// resultsTab.clean();
+// seqs.dispose();
+// if (fromMain) {
+// System.exit(0);
+// }
+// break;
+// }
+// }
+//
+// revised:
+//
+// private void promptQuitAsync() {
+// new AsyncDialog().showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION, new ActionListener() {
+//
+// @Override
+// public void actionPerformed(ActionEvent e) {
+// int sel = ((AsyncDialog)e.getSource()).getOption();
+// switch (sel) {
+// case JOptionPane.YES_OPTION:
+// resultsTab.clean();
+// seqs.dispose();
+// if (fromMain) {
+// System.exit(0);
+// }
+// break;
+// }
+// }}));
+// }
+
+
+ public AsyncDialog() {
+ }
+
+ private ActionListener actionListener;
+ private Object choice;
+ private Object[] options;
+ private Object value;
+ private boolean wantsInput;
+
+ // These options can be supplemented as desired.
+
+
+ /**
+ * Synchronous call; OK in JavaScript as long as we are using a JavaScript prompt() call
+ *
+ * @param frame
+ * @param msg
+ * @return
+ */
+ @Deprecated
+ public static String showInputDialog(Component frame, String msg) {
+ return JOptionPane.showInputDialog(frame, msg);
+ }
+
+ public void showInputDialog(Component frame, Object message, ActionListener a) {
+ setListener(a);
+ wantsInput = true;
+ process(JOptionPane.showInputDialog(frame, message));
+ unsetListener();
+ }
+
+ public void showInputDialog(Component frame, Object message, String title, int messageType, Icon icon,
+ Object[] selectionValues, Object initialSelectionValue, ActionListener a) {
+ setListener(a);
+ wantsInput = true;
+ process(JOptionPane.showInputDialog(frame, message, title, messageType, icon, selectionValues,
+ initialSelectionValue));
+ unsetListener();
+ }
+
+ public void showMessageDialog(Component frame, Object message, ActionListener a) {
+ setListener(a);
+ JOptionPane.showMessageDialog(frame, message);
+ unsetListener();
+ if (/** @j2sNative false || */true)
+ process("" + message);
+ }
+
+ public void showOptionDialog(Component frame, Object message, String title, int optionType, int messageType,
+ Icon icon, Object[] options, Object initialValue, ActionListener a) {
+ actionListener = a;
+ this.options = options;
+ setListener(a);
+ process(JOptionPane.showOptionDialog(frame, message, title, optionType, messageType, icon, options,
+ initialValue));
+ unsetListener();
+ }
+
+ public void showConfirmDialog(Component frame, Object message, String title, ActionListener a) {
+ showConfirmDialog(frame, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, a);
+ }
+
+ public void showConfirmDialog(Component frame, Object message, String title, int optionType, ActionListener a) {
+ showConfirmDialog(frame, message, title, optionType, JOptionPane.QUESTION_MESSAGE, a);
+ }
+
+ public void showConfirmDialog(Component frame, Object message, String title, int optionType, int messageType,
+ ActionListener a) {
+ setListener(a);
+ process(JOptionPane.showConfirmDialog(frame, message, title, optionType, messageType));
+ unsetListener();
+ }
+
+ /**
+ * retrieve selection from the ActionEvent, for which "this" is getSource()
+ *
+ * @return
+ */
+ public Object getChoice() {
+ return choice;
+ }
+
+ public int getOption() {
+ if (!(choice instanceof Integer)) {
+ throw new java.lang.IllegalArgumentException("AsyncDialog.getOption called for non-Integer choice");
+ }
+ return ((Integer) choice).intValue();
+ }
+
+ /**
+ * A dialog option that allows for YES, NO, and CLOSE options via
+ * ActionListener. ActionEvent.getID() contains the reply.
+ *
+ * @param parent The parent component for the dialog
+ * @param message The text of the message to display
+ * @param title Optional title defaults to "Question"
+ * @param listener Handle options based on an ActionEvent
+ */
+ public static void showYesNoAsync(Component parent, Object message, String title, ActionListener listener) {
+ new AsyncDialog().showConfirmDialog(parent, message, (title == null ? "Question" : title),
+ JOptionPane.YES_NO_OPTION, listener);
+ }
+
+ /**
+ * A dialog option that involves just a YES follower.
+ * @param parent
+ * @param message
+ * @param title TODO
+ * @param yes
+ */
+ public static void showYesAsync(Component parent, Object message, String title, Runnable yes) {
+ AsyncDialog.showYesNoAsync(parent, message, title, new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getID() == JOptionPane.YES_OPTION) {
+ yes.run();
+ }
+ }
+
+ });
+ }
+
+ /**
+ * A dialog option that involves just an OK follower.
+ * @param parent
+ * @param message
+ * @param title
+ * @param ok
+ */
+ public static void showOKAsync(Component parent, Object message, String title, Runnable ok) {
+ new AsyncDialog().showConfirmDialog(parent, message, title, JOptionPane.OK_CANCEL_OPTION, new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getID() == JOptionPane.OK_OPTION) {
+ ok.run();
+ }
+ }
+
+ });
+ }
+
+
+ private void setListener(ActionListener a) {
+ actionListener = a;
+ @SuppressWarnings("unused")
+ Class c = JOptionPane.class; // loads the class
+ /** @j2sNative c.$clazz$.listener = this */
+ }
+
+ private void unsetListener() {
+ /** @j2sNative javax.swing.JOptionPane.listener = null */
+ }
+
+ /**
+ * Switch from property change to action.
+ *
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ value = evt.getNewValue();
+ switch (evt.getPropertyName()) {
+ case "inputValue":
+ process(value);
+ break;
+ case "value":
+ if (value != null && options == null && !(value instanceof Integer)) {
+ process(getOptionIndex(((JOptionPane) evt.getSource()).getOptions(), value));
+ return;
+ }
+ if (options != null) {
+ int i = getOptionIndex(options, value);
+ value = Integer.valueOf(i >= 0 ? i : JOptionPane.CLOSED_OPTION);
+ }
+ process(value);
+ break;
+ }
+ }
+
+ private int getOptionIndex(Object[] options, Object val) {
+ if (options != null)
+ for (int i = 0; i < options.length; i++) {
+ if (options[i] == val)
+ return i;
+ }
+ return -1;
+ }
+
+ public Object getValue() {
+ if (wantsInput || options == null)
+ return value;
+ int val = ((Integer) value).intValue();
+ return (val < 0 ? null : options[val]);
+ }
+
+ private boolean processed;
+
+ /**
+ * Return for confirm dialog.
+ *
+ * @param ret may be JavaScript NaN, testable as ret != ret or ret != - -ret
+ */
+ private void process(int ret) {
+ if (ret != -(-ret) || processed)
+ return;
+ processed = true;
+ choice = ret;
+ actionListener.actionPerformed(new ActionEvent(this, ret, "SelectedOption"));
+ }
+
+ private void process(Object ret) {
+ if (ret instanceof UIResource || processed)
+ return;
+ processed = true;
+ choice = ret;
+ actionListener.actionPerformed(new ActionEvent(this,
+ ret == null ? JOptionPane.CANCEL_OPTION : JOptionPane.OK_OPTION,
+ (ret == null ? null : ret.toString())));
+ }
+
+
+}
--- /dev/null
+package javajs.async;
+
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.util.function.Function;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileSystemView;
+
+/**
+ * A simple Asynchronous file chooser for JavaScript and Java.
+ *
+ * Requires an OK runnable; JavaScript can return notification of cancel for
+ * file reading only, not saving.
+ *
+ * @author Bob Hanson
+ */
+
+public class AsyncFileChooser extends JFileChooser implements PropertyChangeListener {
+
+ private int optionSelected;
+ private Runnable ok, cancel; // sorry, no CANCEL in JavaScript for file open
+ private boolean isAsyncSave = true;
+ private static boolean notified;
+
+ public AsyncFileChooser() {
+ super();
+ }
+
+ public AsyncFileChooser(File file) {
+ super(file);
+ }
+
+ public AsyncFileChooser(File file, FileSystemView view) {
+ super(file, view);
+ }
+
+ @Deprecated
+ @Override
+ public int showDialog(Component frame, String btnText) {
+ // This one can come from JFileChooser - default is OPEN
+ return super.showDialog(frame, btnText);
+ }
+
+ private int err() {
+ try {
+ throw new java.lang.IllegalAccessException("Warning! AsyncFileChooser interface bypassed!");
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return JFileChooser.ERROR_OPTION;
+ }
+
+ @Deprecated
+ @Override
+ public int showOpenDialog(Component frame) {
+ return err();
+ }
+
+ @Override
+ public int showSaveDialog(Component frame) {
+ isAsyncSave = false;
+ return super.showSaveDialog(frame);
+ }
+
+ /**
+ *
+ * @param frame
+ * @param btnLabel "open" or "save"
+ * @param ok
+ * @param cancel must be null; JavaScript cannot capture a cancel from a file dialog
+ */
+ public void showDialog(Component frame, String btnLabel, Runnable ok, Runnable cancel) {
+ this.ok = ok;
+ if (getDialogType() != JFileChooser.SAVE_DIALOG && cancel != null)
+ notifyCancel();
+ process(super.showDialog(frame, btnLabel));
+ }
+
+ /**
+ *
+ * @param frame
+ * @param ok
+ * @param cancel must be null; JavaScript cannot capture a cancel from a file dialog
+ */
+ public void showOpenDialog(Component frame, Runnable ok, Runnable cancel) {
+ this.ok = ok;
+ if (cancel != null)
+ notifyCancel();
+ process(super.showOpenDialog(frame));
+ }
+
+ /**
+ *
+ * This just completes the set. It is not necessary for JavaScript, because JavaScript
+ * will just throw up a simple modal OK/Cancel message anyway.
+ *
+ * @param frame
+ * @param ok
+ * @param cancel must be null
+ */
+ public void showSaveDialog(Component frame, Runnable ok, Runnable cancel) {
+ this.ok = ok;
+ this.cancel = cancel;
+ process(super.showSaveDialog(frame));
+ }
+
+
+ /**
+ * Locate a file for input or output. Note that JavaScript will not return on cancel for OPEN_DIALOG.
+ *
+ * @param title The title for the dialog
+ * @param mode OPEN_DIALOG or SAVE_DIALOG
+ * @param processFile function to use when complete
+ */
+ public static void getFileAsync(Component parent, String title, int mode, Function<File, Void> processFile) {
+ // BH no references to this method. So changing its signature for asynchonous use
+ // And it didn't do as advertised - ran System.exit(0) if canceled
+ // create and display a file dialog
+ AsyncFileChooser fc = new AsyncFileChooser();
+ fc.setDialogTitle(title);
+ Runnable after = new Runnable() {
+
+ @Override
+ public void run() {
+ processFile.apply(fc.getSelectedFile());
+ }
+
+ };
+ if (mode == JFileChooser.OPEN_DIALOG) {
+ fc.showOpenDialog(parent, after, after);
+ } else {
+ fc.showSaveDialog(parent, after, after);
+ }
+
+ }
+
+ /**
+ * Run yes.run() if a file doesn't exist or if the user allows it, else run no.run()
+ * @param parent
+ * @param filename
+ * @param title
+ * @param yes (approved)
+ * @param no (optional)
+ */
+ public static void checkReplaceFileAsync(Component parent, File outfile, String title, Runnable yes, Runnable no) {
+ if (outfile.exists()) {
+ AsyncDialog.showYesNoAsync(parent,
+ outfile + " exists. Replace it?", null, new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ switch (e.getID()) {
+ case JOptionPane.YES_OPTION:
+ yes.run();
+ break;
+ default:
+ if (no != null)
+ no.run();
+ break;
+ }
+ }
+
+ });
+
+ } else {
+ yes.run();
+ }
+
+ }
+
+ private void notifyCancel() {
+ if (!notified) {
+ System.err.println("developer note: JavaScript cannot fire a FileChooser CANCEL action");
+ }
+ notified = true;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ switch (evt.getPropertyName()) {
+ case "SelectedFile":
+ case "SelectedFiles":
+ process(optionSelected = (evt.getNewValue() == null ? CANCEL_OPTION : APPROVE_OPTION));
+ break;
+ }
+ }
+
+ private void process(int ret) {
+ if (ret != -(-ret))
+ return; // initial JavaScript return is NaN
+ optionSelected = ret;
+ File f = getSelectedFile();
+ if (f == null) {
+ if (cancel != null)
+ cancel.run();
+ } else {
+ if (ok != null)
+ ok.run();
+ }
+ }
+
+ public int getSelectedOption() {
+ return optionSelected;
+ }
+
+ public static byte[] getFileBytes(File f) {
+ return /** @j2sNative f.秘bytes || */null;
+ }
+
+}
--- /dev/null
+package javajs.async;
+
+import java.awt.Component;
+
+import javax.swing.ProgressMonitor;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+
+import javajs.async.SwingJSUtils.StateHelper;
+import javajs.async.SwingJSUtils.StateMachine;
+
+/**
+ * Executes synchronous or asynchronous tasks using a SwingWorker in Java or
+ * JavaScript, equivalently.
+ *
+ * Unlike a standard SwingWorker, AsyncSwingWorker may itself be asynchronous.
+ * For example, it might load a file asynchronously, or carry out a background
+ * process in JavaScript much like one might be done in Java, but with only a
+ * single thread.
+ *
+ * Whereas a standard SwingWorker would execute done() long before the
+ * asynchronous task completed, this class will wait until progress has been
+ * asynchronously set greater or equal to its max value or the task is canceled
+ * before executing that method.
+ *
+ * Three methods must be supplied by the subclass:
+ *
+ * void initAsync()
+ *
+ * int doInBackgroundAsync(int progress)
+ *
+ * void doneAsync()
+ *
+ * Both initAsync() and doneAsync() are technically optional - they may be
+ * empty. doInBackgroundAsync(), however, is the key method where, like
+ * SwingWorker's doInBackground, the main work is done. The supplied progress
+ * parameter reminds the subclass of where it is at, and the return value allows
+ * the subclass to update the progress field in both the SwingWorker and the
+ * ProgressMonitor.
+ *
+ * If it is desired to run the AsyncSwingWorker synchonously, call the
+ * executeSynchronously() method rather than execute(). Never call
+ * SwingWorker.run().
+ *
+ *
+ * @author hansonr
+ *
+ */
+public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
+
+ public static final String DONE_ASYNC = "DONE_ASYNC";
+ public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
+
+ protected int progressAsync;
+
+ /**
+ * Override to provide initial tasks.
+ */
+ abstract public void initAsync();
+
+ /**
+ * Given the last progress, do some portion of the task that the SwingWorker
+ * would do in the background, and return the new progress. returning max or
+ * above will complete the task.
+ *
+ * @param progress
+ * @return new progress
+ */
+ abstract public int doInBackgroundAsync(int progress);
+
+ /**
+ * Do something when the task is finished or canceled.
+ *
+ */
+ abstract public void doneAsync();
+
+ protected ProgressMonitor progressMonitor;
+
+ protected int delayMillis;
+ protected String note;
+ protected int min;
+ protected int max;
+ protected int progressPercent;
+
+ protected boolean isAsync;
+ private Exception exception;
+
+ /**
+ * Construct an asynchronous SwingWorker task that optionally will display a
+ * ProgressMonitor. Progress also can be monitored by adding a
+ * PropertyChangeListener to the AsyncSwingWorker and looking for the "progress"
+ * event, just the same as for a standard SwingWorker.
+ *
+ * @param owner optional owner for the ProgressMonitor, typically a JFrame
+ * or JDialog.
+ *
+ * @param title A non-null title indicates we want to use a
+ * ProgressMonitor with that title line.
+ *
+ * @param delayMillis A positive number indicating the delay we want before
+ * executions, during which progress will be reported.
+ *
+ * @param min The first progress value. No range limit.
+ *
+ * @param max The last progress value. No range limit; may be greater
+ * than min.
+ *
+ */
+ public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) {
+ if (title != null && delayMillis > 0) {
+ progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max));
+ progressMonitor.setProgress(Math.min(min, max)); // displays monitor
+ }
+ this.delayMillis = Math.max(0, delayMillis);
+ this.isAsync = (delayMillis > 0);
+
+ this.min = min;
+ this.max = max;
+ }
+
+ public void executeAsync() {
+ super.execute();
+ }
+
+ public void executeSynchronously() {
+ isAsync = false;
+ delayMillis = 0;
+ try {
+ doInBackground();
+ } catch (Exception e) {
+ exception = e;
+ e.printStackTrace();
+ cancelAsync();
+ }
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ public int getMinimum() {
+ return min;
+ }
+
+ public void setMinimum(int min) {
+ this.min = min;
+ if (progressMonitor != null) {
+ progressMonitor.setMinimum(min);
+ }
+ }
+
+ public int getMaximum() {
+ return max;
+ }
+
+ public void setMaximum(int max) {
+ if (progressMonitor != null) {
+ progressMonitor.setMaximum(max);
+ }
+ this.max = max;
+ }
+
+ public int getProgressPercent() {
+ return progressPercent;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ if (progressMonitor != null) {
+ progressMonitor.setNote(note);
+ }
+ }
+
+ /**
+ * Cancel the asynchronous process.
+ *
+ */
+ public void cancelAsync() {
+ helper.interrupt();
+ }
+
+ /**
+ * Check to see if the asynchronous process has been canceled.
+ *
+ * @return true if StateHelper is not alive anymore
+ *
+ */
+ public boolean isCanceledAsync() {
+ return !helper.isAlive();
+ }
+
+ /**
+ * Check to see if the asynchronous process is completely done.
+ *
+ * @return true only if the StateMachine is at STATE_DONE
+ *
+ */
+ public boolean isDoneAsync() {
+ return helper.getState() == STATE_DONE;
+ }
+
+ /**
+ * Override to set a more informed note for the ProcessMonitor.
+ *
+ * @param progress
+ * @return
+ */
+ public String getNote(int progress) {
+ return String.format("Completed %d%%.\n", progress);
+ }
+
+ /**
+ * Retrieve the last note delivered by the ProcessMonitor.
+ *
+ * @return
+ */
+ public String getNote() {
+ return note;
+ }
+
+ public int getProgressAsync() {
+ return progressAsync;
+ }
+
+ /**
+ * Set the [min,max] progress safely.
+ *
+ * SwingWorker only allows progress between 0 and 100. This method safely
+ * translates [min,max] to [0,100].
+ *
+ * @param n
+ */
+ public void setProgressAsync(int n) {
+ n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min)));
+ progressAsync = n;
+ n = (n - min) * 100 / (max - min);
+ n = (n < 0 ? 0 : n > 100 ? 100 : n);
+ progressPercent = n;
+ }
+
+ ///// the StateMachine /////
+
+ private final static int STATE_INIT = 0;
+ private final static int STATE_LOOP = 1;
+ private final static int STATE_WAIT = 2;
+ private final static int STATE_DONE = 99;
+
+ private StateHelper helper;
+
+ protected StateHelper getHelper() {
+ return helper;
+ }
+
+ private boolean isPaused;
+
+ protected void setPaused(boolean tf) {
+ isPaused = tf;
+ }
+
+ protected boolean isPaused() {
+ return isPaused;
+ }
+
+ /**
+ * The StateMachine's main loop.
+ *
+ * Note that a return from this method will exit doInBackground, trigger the
+ * isDone() state on the underying worker, and scheduling its done() for
+ * execution on the AWTEventQueue.
+ *
+ * Since this happens essentially immediately, it is unlikely that
+ * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task
+ * itself won't be cancelable in Java or in JavaScript, since its
+ * doInBackground() method is officially complete, and isDone() is true well
+ * before we are "really" done. FutureTask will not set isCancelled() true once
+ * the task has run.
+ *
+ * We are using an asynchronous task specifically because we want to have the
+ * opportunity for the ProgressMonitor to report in JavaScript. We will have to
+ * cancel our task and report progress explicitly using our own methods.
+ *
+ */
+ @Override
+ public boolean stateLoop() {
+ while (helper.isAlive() && !isPaused) {
+ switch (helper.getState()) {
+ case STATE_INIT:
+ setProgressAsync(min);
+ initAsync();
+ helper.setState(STATE_WAIT);
+ continue;
+ case STATE_LOOP:
+ if (checkCanceled()) {
+ helper.setState(STATE_DONE);
+ firePropertyChange("state", null, CANCELED_ASYNC);
+ } else {
+ int ret = doInBackgroundAsync(progressAsync);
+ if (!helper.isAlive() || isPaused) {
+ continue;
+ }
+ progressAsync = ret;
+ setProgressAsync(progressAsync);
+ setNote(getNote(progressAsync));
+ setProgress(progressPercent);
+ if (progressMonitor != null) {
+ progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync);
+ }
+ helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT);
+ }
+ continue;
+ case STATE_WAIT:
+ helper.setState(STATE_LOOP);
+ helper.sleep(delayMillis);
+ return true;
+ default:
+ case STATE_DONE:
+ stopProgressMonitor();
+ // Put the doneAsync() method on the AWTEventQueue
+ // just as for SwingWorker.done().
+ if (isAsync) {
+ SwingUtilities.invokeLater(doneRunnable);
+ } else {
+ doneRunnable.run();
+ }
+
+ return false;
+ }
+ }
+ if (!helper.isAlive()) {
+ stopProgressMonitor();
+ }
+ return false;
+ }
+
+ private void stopProgressMonitor() {
+ if (progressMonitor != null) {
+ progressMonitor.close();
+ progressMonitor = null;
+ }
+ }
+
+ private Runnable doneRunnable = new Runnable() {
+ @Override
+ public void run() {
+ doneAsync();
+ firePropertyChange("state", null, DONE_ASYNC);
+ }
+
+ };
+
+ private boolean checkCanceled() {
+ if (isMonitorCanceled() || isCancelled()) {
+ helper.interrupt();
+ return true;
+ }
+ return false;
+ }
+
+ //// final SwingWorker methods not to be used by subclasses ////
+
+ private boolean isMonitorCanceled() {
+ return (progressMonitor != null && progressMonitor.isCanceled());
+ }
+
+ /**
+ * see SwingWorker, made final here.
+ *
+ */
+ @Override
+ final protected Void doInBackground() throws Exception {
+ helper = new StateHelper(this);
+ setProgressAsync(min);
+ helper.next(STATE_INIT);
+ return null;
+ }
+
+ /**
+ * see SwingWorker, made final here. Nothing to do.
+ *
+ */
+ @Override
+ final public void done() {
+ }
+
+}
--- /dev/null
+package javajs.async;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.stream.Collectors;
+
+import javax.imageio.ImageIO;
+import javax.swing.Timer;
+
+/**
+ * A set of generally useful SwingJS-related methods. Includes:
+ *
+ * alternatives to using getCodeBase() for loading resources, due to issues in
+ * Eclipse setting that incorrectly (but no problem in JavaScript)
+ *
+ *
+ *
+ * @author hansonr
+ *
+ */
+public class SwingJSUtils {
+ /**
+ * Set the dimension for the applet prior to j2sApplet's call to
+ * run the applet. Must be used to create a static field:
+ *
+ * <code>
+ * private static Dimension dim =
+ * </code>
+ *
+ *
+ * Then, if it is desired also to have Java also set this, add
+ *
+ * if (dim != null) setSize(dim);
+ *
+ * to the applet's init() method.
+ *
+ * @param w
+ * @param h
+ * @return the Dimension
+ *
+ * @author hansonr
+ */
+ public static Dimension setDim(int w, int h) {
+ String baseURI = (/** @j2sNative document.body.baseURI || */
+ null);
+ boolean isTest = (baseURI == null || baseURI.indexOf("_applet.html") >= 0);
+ if (!isTest)
+ return null;
+ /**
+ * @j2sNative
+ *
+ * J2S.thisApplet.__Info.width = w; J2S.thisApplet.__Info.height = h;
+ */
+ return new Dimension(w, h);
+ }
+
+ /**
+ * Reliably load a resource of a specific type from the code directory
+ *
+ * adaptable - here we are returning an image or a string
+ *
+ * @param cl the classname of the object to return (Image.class,
+ * String.class) null for InputStream
+ * @param filename
+ * @return
+ *
+ * @author hansonr
+ */
+ public static Object getResource(Class<?> baseClass, String filename, Class<?> cl) {
+ System.out.println("mpUtils.SwingJSUtils.getResource " + baseClass.getCanonicalName() + " " + filename);
+ InputStream is = baseClass.getResourceAsStream(filename);
+ if (cl == Image.class) {
+ try {
+ return ImageIO.read(is);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else if (cl == String.class) {
+ return new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
+ }
+ return is;
+ }
+
+ /**
+ * Pre-fetch images during the static entry of the class. This should provide
+ * plenty of clock ticks, since the file transfer is synchronous, and all we are
+ * waiting for is the DOM image object to initialize.
+ *
+ * @param cl
+ * @param images
+ * @param root
+ * @param nImages
+ * @param ext
+ */
+ public static void loadImagesStatic(Class<?> cl, Image[] images, String root, String ext, int nImages) {
+ for (int i = nImages; --i >= 0;) {
+
+ // Bild laden und beim MediaTracker registrieren
+ // MediaTracker ladekontrolle = new MediaTracker(this);
+
+ // BH SwingJS -- adding generally useful method for loading data
+ // avoiding the use of getCodeBase(), which for some reason does not work in
+ // Eclipse.
+
+ images[i] = (Image) getResource(cl, root + i + "." + ext, Image.class);
+// /**
+// * @j2sNative
+// * $("body").append(images[i]._imgNode);
+// *
+// */
+// ladekontrolle.addImage(scharf[i],i);
+ // Warten , bis Bild ganz geladen ist
+
+// try {ladekontrolle.waitForID(i);}
+// catch (InterruptedException e)
+// {}
+ }
+ }
+
+ /**
+ * Fill an array with images based on a String[] listing
+ * @param cl reference class
+ * @param root optional root path, ending in "/"
+ * @param names source file names
+ * @param images array to fill
+ */
+ public static void loadImagesStatic(Class<?> cl, String root, String[] names, Image[] images) {
+ for (int i = names.length; --i >= 0;) {
+ images[i] = (Image) getResource(cl, root + names[i], Image.class);
+ }
+ }
+
+ /**
+ * Eclipse-friendly image getting
+ *
+ * @param c
+ * @param fileName
+ * @return
+ */
+ public static Image getImage(Component c, String fileName) {
+ return getImage(c.getClass(), fileName);
+ }
+
+ /**
+ * Eclipse-friendly image getting
+ *
+ * @param c
+ * @param fileName
+ * @return
+ */
+ public static Image getImage(Class<?> c, String fileName) {
+ return (Image) getResource(c, fileName, Image.class);
+ }
+
+ /**
+ * Clear the component graphic. BH added this for JavaScript because changing
+ * the browser zoom can change the size of the canvas for unknown reasons.
+ *
+ * @param c
+ */
+ public static void clearComponent(Component c) {
+ Graphics gc = c.getGraphics();
+ gc.clearRect(0, 0, c.getWidth(), c.getHeight());
+ gc.dispose();
+ }
+
+
+ /**
+ * A simple interface to the machine loop, generally of the form
+ * <code>
+ * public boolean stateLoop() {
+ * while (stateHepler.isAlive()) {
+ * switch (stateHelper.getState()) {
+ * case STATE_XXX:
+ * ...
+ * return stateHelper.delayState(100,STATE_YYY);
+ * case STATE_YYY:
+ * ...
+ * stateHelper.setState(STATE_ZZZ);
+ * continue;
+ * case STATE_ZZZ:
+ * ...
+ * return stateHelper.delayAction(100, MY_ID, "myCommand", myListener, STATE_XXX); *
+ * case STATE_DONE:
+ * ...
+ * stateHelper.interrupt();
+ * return false;
+ * }
+ * return true;
+ * }
+ * return false;
+ * }
+ * </code>
+ * @author hansonr
+ *
+ */
+ public interface StateMachine {
+
+ public boolean stateLoop();
+
+ }
+ /**
+ * StateHelper is a class that facilitates moving from an asychronous multithreaded model to a state-oriented model of programming
+ * for SwingJS animations and other asynchronous business.
+ *
+ * @author hansonr
+ *
+ */
+ public static class StateHelper {
+
+ public static final int UNCHANGED = Integer.MIN_VALUE;
+
+ private StateMachine machine;
+ private int state;
+ private int level;
+
+ private boolean interrupted;
+
+
+ public StateHelper(StateMachine machine) {
+ this.machine = machine;
+ }
+
+ public void interrupt() {
+ interrupted = true;
+ }
+
+ public boolean isInterrupted() {
+ return interrupted;
+ }
+
+ public boolean isAlive() {
+ return !interrupted;
+ }
+
+ public void restart() {
+ interrupted = false;
+ }
+
+ public void setState(int state) {
+ this.state = this.stateNext = state;
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ public void setLevel(int level) {
+ this.level = this.levelNext = level;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setNextState(int next) {
+ stateNext = next;
+ }
+
+ public int getNextState() {
+ return stateNext;
+ }
+
+ public int getNextLevel() {
+ return levelNext;
+ }
+
+ public void setNextLevel(int next) {
+ levelNext = next;
+ }
+
+ /**
+ *
+ * NOTE: this method must remain private; it is accessed via p$1
+ *
+ * @return
+ */
+ private boolean nextState() {
+ return next(stateNext, levelNext);
+ }
+ /**
+ * Set the state and run machine.stateLoop().
+ *
+ * @param state something meaningful to the machine
+ *
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean next(int state) {
+ return next(state, 0);
+ }
+
+ /**
+ * Set the state and level, and then run machine.stateLoop(). Driven directly or via delayedState or delayedAction
+ *
+ * @param state something meaningful to the machine
+ * @param level something meaningful to the machine
+ *
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean next(int state, int level) {
+ return nextStatePriv(this, state, level);
+ }
+
+ private static boolean nextStatePriv(Object oThis, int state, int level) {
+ StateHelper me = (StateHelper) oThis;
+ if (me.interrupted)
+ return false;
+ if (level != UNCHANGED)
+ me.level = level;
+ if (state != UNCHANGED)
+ me.state = state;
+ return me.machine.stateLoop();
+ }
+
+ /**
+ * After the given number of milliseseconds, set the new state and run the machines stateLoop with unchanged level
+ *
+ * @param ms the number of milliseconds to delay; 0 to execute synchronously *
+ * @param stateNext the next state to run
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean delayedState(int ms, int stateNext) {
+ return delayedState(ms, stateNext, level);
+ }
+
+ private Timer stateTimer;
+
+ private int stateNext;
+ private int levelNext;
+
+ /**
+ * After the given number of milliseseconds, set the new state and level, and
+ * run the machines stateLoop
+ *
+ * @param ms the number of milliseconds to delay; 0 to execute
+ * synchronously *
+ * @param stateNext the next state
+ * @param levelNext the next level
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+
+ public boolean delayedState(int ms, int stateNext, int levelNext) {
+ if (interrupted)
+ return false;
+ if (ms == 0)
+ return next(stateNext, levelNext);
+ if (stateNext != UNCHANGED)
+ this.stateNext = stateNext;
+ if (levelNext != UNCHANGED)
+ this.levelNext = levelNext;
+
+ /**
+ * @j2sNative
+ * var me = this;
+ * setTimeout(function(){
+ * p$1.nextState.apply(me, []);
+ * },ms);
+ */
+ {
+ // Java only
+
+ if (stateTimer == null) {
+ stateTimer = new Timer(ms, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ nextState();
+ }
+
+ });
+ stateTimer.setRepeats(false);
+ stateTimer.start();
+ } else {
+ stateTimer.restart();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Fire an actionPerformed event after a given number of milliseconds
+ *
+ * @param ms delay milliseconds. if 0, then this action will be called
+ * synchronously
+ * @param id id for this event, possibly ACTION_PERFORMED (1001), but not
+ * necessarily
+ * @param command key for ActionEvent.getCommand()
+ * @param listener ActionListener to be called.
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean delayedAction(int ms, int id, String command, ActionListener listener) {
+ return delayedAction(ms, id, command, listener, UNCHANGED, UNCHANGED);
+ }
+
+ /**
+ * Fire an actionPerformed event after a given number of milliseconds
+ *
+ * @param ms delay milliseconds. if 0, then this action will be called
+ * synchronously
+ * @param id id for this event, possibly ACTION_PERFORMED (1001), but not
+ * necessarily
+ * @param command key for ActionEvent.getCommand()
+ * @param listener ActionListener to be called.
+ *
+ * @param state the next state to go to after this listener is called; UNCHANGED to let the listener take care of this.
+ *
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean delayedAction(int ms, int id, String command, ActionListener listener, int state) {
+ return delayedAction(ms, id, command, listener, state, UNCHANGED);
+ }
+
+ /**
+ * Fire an actionPerformed event after a given number of milliseconds. Setting BOTH stateNext and levelNext to UNCHANGED (Integer.MIN_VALUE)
+ * allows the listener to handle continuing the loop.
+ *
+ * @param ms delay milliseconds. if 0, then this action will be called
+ * synchronously
+ * @param id id for this event, possibly ACTION_PERFORMED (1001), but not
+ * necessarily
+ * @param command key for ActionEvent.getCommand()
+ * @param listener ActionListener to be called.
+ * @param stateNext state to run after the event is processed by the listener, or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle this.
+ * @param levelNext level to run after the event is processed by the listener, or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle this.
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean delayedAction(int ms, int id, String command, ActionListener listener, int stateNext, int levelNext) {
+ if (interrupted)
+ return false;
+ ActionEvent event = new ActionEvent(this, id, command);
+ if (ms == 0) {
+ listener.actionPerformed(event);
+ return (stateNext == UNCHANGED && levelNext == UNCHANGED || nextStatePriv(this, stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext));
+ }
+
+ StateHelper me = this;
+
+ Timer timer = new Timer(ms, id == ActionEvent.ACTION_PERFORMED ? listener : new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (!interrupted)
+ listener.actionPerformed(event);
+ if (!interrupted && (stateNext != UNCHANGED || levelNext != UNCHANGED))
+ nextStatePriv(me, stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext);
+ }
+
+ });
+ timer.setRepeats(false);
+ timer.start();
+ return true;
+ }
+
+ public static void delayedRun(int ms, Runnable runnable) {
+ new StateHelper(null).delayedRun(ms, runnable, UNCHANGED, UNCHANGED);
+ }
+
+
+ /**
+ * Fire an actionPerformed event after a given number of milliseconds. Setting
+ * BOTH stateNext and levelNext to UNCHANGED (Integer.MIN_VALUE) allows the
+ * listener to handle continuing the loop.
+ *
+ * @param ms delay milliseconds. if 0, then this action will be called
+ * synchronously
+ * @param id id for this event, possibly ACTION_PERFORMED (1001), but not
+ * necessarily
+ * @param command key for ActionEvent.getCommand()
+ * @param listener ActionListener to be called.
+ * @param stateNext state to run after the event is processed by the listener,
+ * or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle
+ * this.
+ * @param levelNext level to run after the event is processed by the listener,
+ * or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle
+ * this.
+ * @return not interrupted
+ *
+ * @author Bob Hanson hansonr@stolaf.edu
+ */
+ public boolean delayedRun(int ms, Runnable runnable, int stateNext, int levelNext) {
+ if (interrupted)
+ return false;
+ if (ms == 0) {
+ return (stateNext == UNCHANGED && levelNext == UNCHANGED || nextStateIfUnchanged(this, runnable,
+ stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext));
+ }
+ StateHelper me = this;
+ /**
+ * @j2sNative
+ *
+ * setTimeout(function() {
+ *
+ * me.nextStateIfUnchanged$O$O$I$I.apply(me, [me, runnable, stateNext, levelNext]);
+ *
+ * },ms);
+ */
+ {
+ Timer timer = new Timer(ms, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ nextStateIfUnchanged(me, runnable, stateNext, levelNext);
+ }
+
+ });
+ timer.setRepeats(false);
+ timer.start();
+ }
+ return true;
+ }
+
+ protected boolean nextStateIfUnchanged(Object oThis, Object runnable, int stateNext, int levelNext) {
+ StateHelper me = (StateHelper)(oThis);
+ if (!me.interrupted)
+ ((Runnable) runnable).run();
+ if (!me.interrupted && (stateNext != UNCHANGED || levelNext != UNCHANGED))
+ nextStatePriv(oThis, stateNext == UNCHANGED ? me.state : stateNext,
+ levelNext == UNCHANGED ? me.level : levelNext);
+ return true;
+ }
+
+ /**
+ * sleep and then execute the next state
+ * @param ms
+ */
+ public void sleep(int ms) {
+ int next = stateNext;
+ delayedState(ms, next);
+ }
+ }
+
+ /**
+ * open a "url-like" input stream
+ * @param base
+ * @param fileName
+ * @return
+ */
+ public static BufferedInputStream openStream(Class<?> base, String fileName) {
+ String s = (String) getResource(base, fileName, String.class);
+ return new BufferedInputStream(new ByteArrayInputStream(s.getBytes()));
+ }
+
+
+ public static class Performance {
+
+ public final static int TIME_RESET = 0;
+
+ public final static int TIME_MARK = 1;
+
+ public static final int TIME_SET = 2;
+
+ public static final int TIME_GET = 3;
+
+ public static long time, mark, set, duration;
+
+ /**
+ * typical usage:
+ *
+ * Performance.timeCheck(null, Platform.TIME_MARK);
+ *
+ * ...
+ *
+ * Performance.timeCheck("some message", Platform.TIME_MARK);
+ *
+ * reset...[set/mark]n...get (total time) (time spent between set and mark)
+ *
+ * set...get (total time) (time spent between set and get)
+ *
+ * long t0 = now(0); ........ ; dt = now(t0); (time since t0)e
+ *
+ * @param msg
+ * @param mode
+ */
+ public static void timeCheck(String msg, int mode) {
+ msg = timeCheckStr(msg, mode);
+ if (msg != null)
+ System.err.println(msg);
+ }
+
+ public static long now(long t) {
+ return System.currentTimeMillis() - t;
+ }
+
+ public static String timeCheckStr(String msg, int mode) {
+ long t = System.currentTimeMillis();
+ switch (mode) {
+ case TIME_RESET:
+ time = mark = t;
+ duration = set = 0;
+ if (msg != null) {
+ return ("Platform: timer reset\t\t\t" + msg);
+ }
+ break;
+ case TIME_SET:
+ if (time == 0)
+ time = t;
+ set = t;
+ break;
+ case TIME_MARK:
+ if (set > 0) {
+ // total time between set/mark points
+ duration += (t - set);
+ } else {
+ if (time == 0) {
+ time = mark = t;
+ }
+ if (msg != null) {
+ long m0 = mark;
+ mark = t;
+ return ("Platform: timer mark\t" + ((t - time) / 1000f) + "\t" + ((t - m0) / 1000f) + "\t"
+ + msg);
+ }
+ mark = t;
+ }
+ break;
+ case TIME_GET:
+ if (msg != null) {
+ if (mark < set)
+ duration = t - set;
+ return ("Platform: timer get\t" + ((t - time) / 1000f) + "\t" + ((duration) / 1000f) + "\t" + msg);
+ }
+ set = 0;
+ break;
+ }
+ return null;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/* $RCSfile$
+ * $Author$
+ * $Date$
+ * $Revision$
+ *
+ * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
+ * for use in SwingJS via transpilation into JavaScript using Java2Script.
+ *
+ * Copyright (C) 2006 The Jmol Development Team
+ *
+ * Contact: jmol-developers@lists.sf.net
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+package swingjs.api;
+
+public class Interface {
+
+ private static String instances="";
+
+ public static Object getInstanceWithParams(String name, Class<?>[] classes, Object... params) {
+ try {
+ Class<?> cl = Class.forName(name);
+ return cl.getConstructor(classes).newInstance(params);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ public static Object getInstance(String name, boolean isQuiet) {
+ Object x = null;
+ /**
+ * @j2sNative
+ *
+ * Clazz._isQuietLoad = isQuiet;
+ */
+ {}
+ try {
+ if (!isQuiet && instances.indexOf(name + ";") <= 0) {
+ System.out.println("swingjs.api.Interface creating instance of " + name);
+ instances += name + ";";
+ }
+ Class<?> y = Class.forName(name);
+ if (y != null)
+ x = y.newInstance();
+ } catch (Throwable e) {
+ System.out.println("Swingjs.api.Interface Error creating instance for " + name + ": \n" + e);
+ /**
+ * @j2sNative
+ *
+ * if (e.stack)System.out.println(e.stack);
+ */
+ {}
+ } finally {
+ /**
+ * @j2sNative
+ *
+ * Clazz._isQuietLoad = false;
+ */
+ {}
+ }
+ return x;
+ }
+
+}
--- /dev/null
+package swingjs.api;
+
+public interface JSFileHandler {
+
+ void handleFileLoaded(Object data, String fileName);
+
+}
import java.awt.Component;
import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Properties;
+import java.util.function.Function;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.swing.JComponent;
import swingjs.api.js.HTML5Applet;
public interface JSUtilI {
- /**
- * Indicate to SwingJS that the given file type is binary.
- *
- * @param ext
- */
- void addBinaryFileType(String ext);
-
- /**
- * Indicate to SwingJS that we can load files using AJAX from the given
- * domain, such as "www.stolaf.edu", because we know that CORS access has been
- * provided.
- *
- * @param domain
- */
- void addDirectDatabaseCall(String domain);
-
- /**
- * Cache or uncache data under the given path name.
- *
- * @param path
- * @param data
- * null to remove from the cache
- */
- void cachePathData(String path, Object data);
-
- /**
- * Get the HTML5 object corresponding to the specified Component, or the
- * current thread if null.
- *
- * @param c
- * the associated component, or null for the current thread
- * @return HTML5 applet object
- */
- HTML5Applet getAppletForComponent(Component c);
-
- /**
- * Get an attribute applet.foo for the applet found using getApplet(null).
- *
- * @param key
- * @return
- */
- Object getAppletAttribute(String key);
-
- /**
- * Get the code base (swingjs/j2s, probably) for the applet found using
- * getApplet(null).
- *
- * @return
- */
- URL getCodeBase();
-
- /**
- * Get the document base (wherever the page is) for the applet found using
- * getApplet(null).
- *
- * @return
- */
-
- URL getDocumentBase();
-
- /**
- * Get an attribute from the div on the page that is associated with this
- * frame, i.e. with id frame.getName() + "-div".
- *
- * @param frame
- * @param type
- * "node" or "dim"
- * @return
- */
- Object getEmbeddedAttribute(Component frame, String type);
-
- /**
- * Get a file synchronously.
- *
- * @param path
- * @param asString
- * true for String; false for byte[]
- * @return byte[] or String
- */
- Object getFile(String path, boolean asString);
-
- /**
- * Get the 秘bytes field associated with a file, but only if the File object
- * itself has them attached, not downloading them.
- *
- * @param f
- * @return
- */
- byte[] getBytes(File f);
-
- /**
- * Retrieve a HashMap consisting of whatever the application wants, but
- * guaranteed to be unique to this app context, that is, for the applet found
- * using getApplet(null).
- *
- * @param contextKey
- * @return
- */
- HashMap<?, ?> getJSContext(Object contextKey);
-
- /**
- * Load a resource -- probably a core file -- if and only if a particular
- * class has not been instantialized. We use a String here because if we used
- * a .class object, that reference itself would simply load the class, and we
- * want the core package to include that as well.
- *
- * @param resourcePath
- * @param className
- */
- void loadResourceIfClassUnknown(String resource, String className);
-
- /**
- * Read all applet.__Info properties for the applet found using
- * getApplet(null) that start with the given prefix, such as "jalview_". A
- * null prefix retrieves all properties. Note that non-string properties will
- * be stringified.
- *
- * @param prefix
- * an application prefix, or null for all properties
- * @param p
- * properties to be appended to
- */
- void readInfoProperties(String prefix, Properties p);
-
- /**
- * Set an attribute for the applet found using getApplet(null). That is,
- * applet[key] = val.
- *
- * @param key
- * @param val
- */
- void setAppletAttribute(String key, Object val);
-
- /**
- * Set an attribute of applet's Info map for the applet found using
- * getApplet(null). That is, applet.__Info[key] = val.
- *
- * @param infoKey
- * @param val
- */
- void setAppletInfo(String infoKey, Object val);
-
- /**
- * Set the given File object's 秘bytes field from an InputStream or a byte[]
- * array. If the file is a JSTempFile, then also cache those bytes.
- *
- * @param f
- * @param isOrBytes
- * BufferedInputStream, ByteArrayInputStream, FileInputStream, or
- * byte[]
- * @return
- */
- boolean setFileBytes(File f, Object isOrBytes);
-
- /**
- * Same as setFileBytes, but also caches the data if it is a JSTempFile.
- *
- * @param is
- * @param outFile
- * @return
- */
- boolean streamToFile(InputStream is, File outFile);
-
- /**
- * Switch the flag in SwingJS to use or not use the JavaScript Map object in
- * Hashtable, HashMap, and HashSet. Default is enabled.
- *
- */
-
- void setJavaScriptMapObjectEnabled(boolean enabled);
+ /**
+ * The HTML5 canvas delivers [r g b a r g b a ...] which is not a Java option.
+ * The closest Java option is TYPE_4BYTE_ABGR, but that is not quite what we
+ * need. SwingJS decodes TYPE_4BYTE_HTML5 as TYPE_4BYTE_RGBA"
+ *
+ * ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ *
+ * int[] nBits = { 8, 8, 8, 8 };
+ *
+ * int[] bOffs = { 0, 1, 2, 3 };
+ *
+ * colorModel = new ComponentColorModel(cs, nBits, true, false,
+ * Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
+ *
+ * raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height,
+ * width * 4, 4, bOffs, null);
+ *
+ * Note, however, that this buffer type should only be used for direct buffer access
+ * using
+ *
+ *
+ *
+ */
+ public static final int TYPE_4BYTE_HTML5 = -6;
+
+ /**
+ * The HTML5 VIDEO element wrapped in a BufferedImage.
+ *
+ * To be extended to allow video capture?
+ */
+ public static final int TYPE_HTML5_VIDEO = Integer.MIN_VALUE;
+
+ /**
+ * Indicate to SwingJS that the given file type is binary.
+ *
+ * @param ext
+ */
+ void addBinaryFileType(String ext);
+
+ /**
+ * Indicate to SwingJS that we can load files using AJAX from the given domain,
+ * such as "www.stolaf.edu", because we know that CORS access has been provided.
+ *
+ * @param domain
+ */
+ void addDirectDatabaseCall(String domain);
+
+ /**
+ * Cache or uncache data under the given path name.
+ *
+ * @param path
+ * @param data null to remove from the cache
+ */
+ void cachePathData(String path, Object data);
+
+ /**
+ * Get the HTML5 object corresponding to the specified Component, or the current thread if null.
+ *
+ * @param c the associated component, or null for the current thread
+ * @return HTML5 applet object
+ */
+ HTML5Applet getAppletForComponent(Component c);
+
+ /**
+ * Get an attribute applet.foo for the applet found using getApplet(null).
+ *
+ * @param key
+ * @return
+ */
+ Object getAppletAttribute(String key);
+
+
+ /**
+ * Get an attribute of applet's Info map for the applet found using
+ * getApplet(null). That is, applet.__Info[InfoKey].
+ *
+ * @param infoKey
+ */
+ Object getAppletInfo(String infoKey);
+
+ /**
+ * Get the code base (swingjs/j2s, probably) for the applet found using
+ * getApplet(null).
+ *
+ * @return
+ */
+ URL getCodeBase();
+
+ /**
+ * Get the document base (wherever the page is) for the applet found using
+ * getApplet(null).
+ *
+ * @return
+ */
+
+ URL getDocumentBase();
+
+ /**
+ * Get an attribute from the div on the page that is associated with this frame,
+ * i.e. with id frame.getName() + "-div".
+ *
+ * @param frame
+ * @param type "node" or "dim"
+ * @return
+ */
+ Object getEmbeddedAttribute(Component frame, String type);
+
+ /**
+ * Get a file synchronously.
+ *
+ * @param path
+ * @param asString true for String; false for byte[]
+ * @return byte[] or String
+ */
+ Object getFile(String path, boolean asString);
+
+ /**
+ * Get the 秘bytes field associated with a file, but only if the File object itself has
+ * them attached, not downloading them.
+ *
+ * @param f
+ * @return
+ */
+ byte[] getBytes(File f);
+
+ /**
+ * Retrieve a HashMap consisting of whatever the application wants, but
+ * guaranteed to be unique to this app context, that is, for the applet found using
+ * getApplet(null).
+ *
+ * @param contextKey
+ * @return
+ */
+ HashMap<?, ?> getJSContext(Object contextKey);
+
+ /**
+ * Load a resource -- probably a core file -- if and only if a particular class
+ * has not been instantialized. We use a String here because if we used a .class
+ * object, that reference itself would simply load the class, and we want the
+ * core package to include that as well.
+ *
+ * @param resourcePath
+ * @param className
+ */
+ void loadResourceIfClassUnknown(String resource, String className);
+
+ /**
+ * Read all applet.__Info properties for the applet found using
+ * getApplet(null) that start with the given prefix, such as "jalview_".
+ * A null prefix retrieves all properties. Note that non-string properties will be
+ * stringified.
+ *
+ * @param prefix an application prefix, or null for all properties
+ * @param p properties to be appended to
+ */
+ void readInfoProperties(String prefix, Properties p);
+
+ /**
+ * Set an attribute for the applet found using
+ * getApplet(null). That is, applet[key] = val.
+ *
+ * @param key
+ * @param val
+ */
+ void setAppletAttribute(String key, Object val);
+
+ /**
+ * Set an attribute of applet's Info map for the applet found using
+ * getApplet(null). That is, applet.__Info[key] = val.
+ *
+ * @param infoKey
+ * @param val
+ */
+ void setAppletInfo(String infoKey, Object val);
+
+ /**
+ * Set the given File object's 秘bytes field from an InputStream or a byte[] array.
+ * If the file is a JSTempFile, then also cache those bytes.
+ *
+ * @param f
+ * @param isOrBytes BufferedInputStream, ByteArrayInputStream, FileInputStream, or byte[]
+ * @return
+ */
+ boolean setFileBytes(File f, Object isOrBytes);
+
+ /**
+ * Set the given URL object's _streamData field from an InputStream or a byte[] array.
+ *
+ * @param f
+ * @param isOrBytes BufferedInputStream, ByteArrayInputStream, FileInputStream, or byte[]
+ * @return
+ */
+ boolean setURLBytes(URL url, Object isOrBytes);
+
+ /**
+ * Same as setFileBytes.
+ *
+ * @param is
+ * @param outFile
+ * @return
+ */
+ boolean streamToFile(InputStream is, File outFile);
+
+ /**
+ * Switch the flag in SwingJS to use or not use the JavaScript Map object in
+ * Hashtable, HashMap, and HashSet. Default is enabled.
+ * *
+ */
+ void setJavaScriptMapObjectEnabled(boolean enabled);
+
+
+ /**
+ * Open a URL in a browser tab.
+ *
+ * @param url
+ * @param target null or specific tab, such as "_blank"
+ */
+ void displayURL(String url, String target);
+
+ /**
+ * Retrieve cached bytes for a path (with unnormalized name)
+ * from J2S._javaFileCache.
+ *
+ * @param path
+ *
+ * @return byte[] or null
+ */
+ byte[] getCachedBytes(String path);
+
+ /**
+ * Attach cached bytes to a file-like object, including URL,
+ * or anything having a 秘bytes field (File, URI, Path)
+ * from J2S._javaFileCache. That is, allow two such objects
+ * to share the same underlying byte[ ] array.
+ *
+ *
+ * @param URLorURIorFile
+ * @return byte[] or null
+ */
+ byte[] addJSCachedBytes(Object URLorURIorFile);
+
+ /**
+ * Seek an open ZipInputStream to the supplied ZipEntry, if possible.
+ *
+ * @param zis the ZipInputStream
+ * @param ze the ZipEntry
+ * @return the length of this entry, or -1 if, for whatever reason, this was not possible
+ */
+ long seekZipEntry(ZipInputStream zis, ZipEntry ze);
+
+ /**
+ * Retrieve the byte array associated with a ZipEntry.
+ *
+ * @param ze
+ * @return
+ */
+ byte[] getZipBytes(ZipEntry ze);
+
+ /**
+ * Java 9 method to read all (remaining) bytes from an InputStream. In SwingJS,
+ * this may just create a new reference to an underlying Int8Array without
+ * copying it.
+ *
+ * @param zis
+ * @return
+ * @throws IOException
+ */
+ byte[] readAllBytes(InputStream zis) throws IOException;
+
+ /**
+ * Java 9 method to transfer all (remaining) bytes from an InputStream to an OutputStream.
+ *
+ * @param is
+ * @param out
+ * @return
+ * @throws IOException
+ */
+ long transferTo(InputStream is, OutputStream out) throws IOException;
+
+ /**
+ * Retrieve any bytes already attached to this URL.
+ *
+ * @param url
+ * @return
+ */
+ byte[] getURLBytes(URL url);
+
+ /**
+ * Set a message in the lower-left-hand corner SwingJS status block.
+ *
+ * @param msg
+ * @param doFadeOut
+ */
+ void showStatus(String msg, boolean doFadeOut);
+
+ /**
+ * Asynchronously retrieve the byte[] for a URL.
+ *
+ * @param url
+ * @param whenDone
+ */
+ void getURLBytesAsync(URL url, Function<byte[], Void> whenDone);
+
+ /**
+ * Experimental method to completely disable a Swing Component's user interface.
+ *
+ * @param jc
+ * @param enabled
+ */
+ void setUIEnabled(JComponent jc, boolean enabled);
}
--- /dev/null
+package swingjs.api.js;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+/**
+ * A mix of direct DOM calls on DOM nodes and convenience methods to do that.
+ *
+ * NOTE: DO NOT OVERLOAD THESE METHODS, as this package will not be qualified.
+ *
+ * @author hansonr
+ *
+ */
+public interface DOMNode {
+
+ public static JQuery jQuery = /** @j2sNative jQuery.$ || (jQuery.$ = jQuery) || */null;
+
+ // "abstract" in the sense that these are the exact calls to JavaScript
+
+
+ public void addEventListener(String event, Object listener);
+ public void removeEventListener(String event);
+ public void removeEventListener(String event, Object listener);
+
+
+
+ public String[] getAttributeNames();
+
+ public String getAttribute(String name);
+
+ public void setAttribute(String attr, String val);
+
+ public void appendChild(DOMNode node);
+
+ public void prepend(DOMNode node);
+
+ public void insertBefore(DOMNode node, DOMNode refNode);
+
+ public DOMNode removeChild(DOMNode node);
+
+ public void focus();
+ public boolean hasFocus();
+ public void blur();
+
+ public DOMNode removeAttribute(String attr);
+
+ public void setSelectionRange(int start, int end, String direction);
+
+ public Rectangle getBoundingClientRect();
+
+ // static convenience methods
+
+ public static DOMNode createElement(String key, String id) {
+ DOMNode node = null;
+ /**
+ * @j2sNative
+ * node = document.createElement(key);
+ * id && (node.id = id);
+ */
+ return node;
+ }
+
+ public static DOMNode getElement(String id) {
+ return (/** @j2sNative document.getElementById(id) ||*/ null);
+ }
+
+ public static DOMNode createTextNode(String text) {
+ return (/** @j2sNative document.createTextNode(text) || */ null);
+ }
+
+ public static DOMNode getParent(DOMNode node) {
+ return (/** @j2sNative node.parentNode ||*/ null);
+ }
+
+ public static DOMNode getPreviousSibling(DOMNode node) {
+ return (/** @j2sNative node.previousSibling ||*/ null);
+ }
+
+ public static DOMNode firstChild(DOMNode node) {
+ return (/** @j2sNative node.firstChild ||*/ null);
+ }
+
+ public static DOMNode lastChild(DOMNode node) {
+ return (/** @j2sNative node.lastChild ||*/ null);
+ }
+
+ public static DOMNode setZ(DOMNode node, int z) {
+ return setStyles(node, "z-index", "" + z);
+ }
+
+ public static Object getAttr(Object node, String attr) {
+ /**
+ * @j2sNative
+ *
+ * if (!node)
+ * return null;
+ * var a = node[attr];
+ * return (typeof a == "undefined" ? null : a);
+ */
+ {
+ return null;
+ }
+ }
+
+ public static int getAttrInt(DOMNode node, String attr) {
+ return (/** @j2sNative node && node[attr] ||*/ 0);
+ }
+
+ public static String getStyle(DOMNode node, String style) {
+ return (/** @j2sNative node && node.style[style] ||*/ null);
+ }
+
+ public static void getCSSRectangle(DOMNode node, Rectangle r) {
+ /**
+ * @j2sNative
+ *
+ * r.x = parseInt(node.style.left.split("p")[0]);
+ * r.y = parseInt(node.style.top.split("p")[0]);
+ * r.width = parseInt(node.style.width.split("p")[0]);
+ * r.height = parseInt(node.style.height.split("p")[0]);
+ *
+ */
+ }
+
+ public static DOMNode setAttr(DOMNode node, String attr, Object val) {
+ /**
+ * @j2sNative
+ *
+ * attr && (node[attr] = (val == "秘TRUE" ? true : val == "秘FALSE" ? false : val));
+ *
+ */
+ return node;
+ }
+
+
+ public static void setAttrInt(DOMNode node, String attr, int val) {
+ /**
+ * @j2sNative
+ *
+ * node[attr] = val;
+ *
+ */
+ }
+
+
+ /**
+ * allows for null key to be skipped (used in audio)
+ *
+ * @param node
+ * @param attr
+ * @return
+ */
+ public static DOMNode setAttrs(DOMNode node, Object... attr) {
+ /**
+ * @j2sNative
+ *
+ * for (var i = 0; i < attr.length;) {
+ * C$.setAttr(node, attr[i++],attr[i++]);
+ * }
+ */
+ return node;
+ }
+
+ public static DOMNode setStyles(DOMNode node, String... attr) {
+ /**
+ * @j2sNative
+ *
+ * if (node) for (var i = 0; i < attr.length;) {
+ * node.style[attr[i++]] = attr[i++];
+ * }
+ *
+ */
+ return node;
+ }
+
+ public static DOMNode setSize(DOMNode node, int width, int height) {
+ return setStyles(node, "width", width + "px", "height", height + "px");
+ }
+
+ public static DOMNode setPositionAbsolute(DOMNode node) {
+ return DOMNode.setStyles(node, "position", "absolute");
+ }
+
+ public static void setVisible(DOMNode node, boolean visible) {
+ setStyles(node, "display", visible ? "block" : "none");
+ }
+
+ public static DOMNode setTopLeftAbsolute(DOMNode node, int top, int left) {
+ DOMNode.setStyles(node, "top", top + "px");
+ DOMNode.setStyles(node, "left", left + "px");
+ return DOMNode.setStyles(node, "position", "absolute");
+ }
+
+ public static void addHorizontalGap(DOMNode domNode, int gap) {
+ DOMNode label = DOMNode.setStyles(DOMNode.createElement("label", null),
+ "letter-spacing", gap + "px", "font-size", "0pt");
+ label.appendChild(DOMNode.createTextNode("."));
+ domNode.appendChild(label);
+ }
+
+ public static void appendChildSafely(DOMNode parent, DOMNode node) {
+ /**
+ * @j2sNative
+ * if (!parent || node.parentElement == parent)
+ * return;
+ */
+ parent.appendChild(node);
+ }
+
+ // static jQuery calls
+
+ /**
+ * jQuery height()
+ *
+ * @param node
+ * @return height
+ */
+ public static int getHeight(DOMNode node) {
+ return jQuery.$(node).height();
+ }
+
+ /**
+ * jQuery width()
+ *
+ * @param node
+ * @return width
+ */
+ public static int getWidth(DOMNode node) {
+ return jQuery.$(node).width();
+ }
+
+ /**
+ * jQuery remove()
+ *
+ * Remove this node and return its parent. Automatically removing all events
+ * attached to it.
+ *
+ * @param node
+ * @return parent or null
+ */
+ public static void dispose(DOMNode node) {
+ if (node != null)
+ jQuery.$(node).remove();
+ }
+
+ /**
+ * Just remove the node, keeping its events and data
+ * @param node
+ */
+ public static void remove(DOMNode node) {
+
+ // NOTE: IE does not have node.remove()
+
+ DOMNode p = getParent(node);
+ if (p != null)
+ p.removeChild(node);
+ }
+
+ /**
+ * just detaches all the nodes; doesn't remove their listeners
+ * @param node
+ */
+ public static void detachAll(DOMNode node) {
+ /**
+ * @j2sNative
+ * if(node)
+ * while(node.lastChild)
+ * node.removeChild(node.lastChild);
+ */
+ }
+
+ /**
+ * jQuery detach() + append()
+ *
+ * @param node
+ * @param container
+ * @return parent if container is null, or container if it is not null
+ */
+ public static DOMNode transferTo(DOMNode node, DOMNode container) {
+ if (node == null)
+ return null;
+ DOMNode p = getParent(node);
+ try {
+ if (p != null)
+ jQuery.$(node).detach();
+ } catch (Throwable e) {
+ // ignore
+ }
+ if (container == null)
+ return p;
+ jQuery.$(container).append(node);
+ return container;
+ }
+
+ public static Object getEmbedded(String name, String type) {
+ DOMNode node = DOMNode.getElement(name + "-div");
+ if (node == null)
+ return null;
+ switch (type) {
+ case "node":
+ return node;
+ case "dim":
+ return new Dimension(DOMNode.getWidth(node), DOMNode.getHeight(node));
+ default:
+ return DOMNode.getAttr(node, type);
+ }
+ }
+
+}
--- /dev/null
+package swingjs.api.js;
+
+public interface HTML5AudioContext {
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
+ // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API
+
+ void close();
+
+ float[] createBuffer(int nChannels, int frameCount, int sampleRate);
+
+ void createBufferSource();
+
+ void createMediaElementSource();
+
+ void createMediaStreamSource();
+
+ void createMediaStreamDestination();
+
+ void createScriptProcessor();
+
+ //void createStereoPanner();
+
+ void createAnalyser();
+
+ void createBiquadFilter();
+
+ void createChannelMerger();
+
+ void createChannelSplitter();
+
+ void createConvolver();
+
+ void createDelay();
+
+ void createDynamicsCompressor();
+
+ void createGain();
+
+ void createIIRFilter();
+
+ void createOscillator();
+
+ void createPanner();
+
+ void createPeriodicWave();
+
+ void createWaveShaper();
+
+ void createAudioWorker();
+
+ void decodeAudioData();
+
+ void resume();
+
+ void suspend();
+
+}
--- /dev/null
+package swingjs.api.js;
+
+import java.awt.image.BufferedImage;
+
+public interface HTML5Canvas extends DOMNode {
+
+ HTML5CanvasContext2D getContext(String str2d);
+
+ /*
+ * Retrieves the byte[] data buffer from an HTML5 CANVAS element, optionally
+ * first setting its contents to a source IMG, CANVAS, or VIDEO element.
+ *
+ */
+ static byte[] getDataBufferBytes(HTML5Canvas canvas, DOMNode sourceNode, int w, int h) {
+ if (sourceNode != null) {
+ DOMNode.setAttrInt(canvas, "width", w);
+ DOMNode.setAttrInt(canvas, "height", h);
+ }
+ HTML5CanvasContext2D ctx = canvas.getContext("2d");
+ if (sourceNode != null) {
+ ctx.drawImage(sourceNode, 0, 0, w, h);
+ }
+ // Coerse int[] to byte[]
+ return (byte[]) (Object) ctx.getImageData(0, 0, w, h).data;
+ }
+
+ /**
+ * Install a source image (img, video, or canvas) into a matching BufferedImage
+ *
+ * @param sourceNode
+ * @param image
+ */
+ static void setImageNode(DOMNode sourceNode, BufferedImage image) {
+ /**
+ * @j2sNative
+ *
+ * image._setImageNode$O$Z(sourceNode, false);
+ *
+ */ {
+ // image._setImageNode(sourceNode, false);
+ }
+ }
+
+
+
+ static HTML5Canvas createCanvas(int width, int height, String id) {
+ HTML5Canvas canvas = (HTML5Canvas) DOMNode.createElement("canvas", (id == null ? "img" + Math.random() : id + ""));
+ DOMNode.setStyles(canvas, "width", width + "px", "height", height + "px");
+ /**
+ * @j2sNative
+ *
+ * canvas.width = width;
+ * canvas.height = height;
+ *
+ */
+ return canvas;
+ }
+
+}
--- /dev/null
+package swingjs.api.js;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D.Float;
+
+public abstract class HTML5CanvasContext2D {
+
+ public class ImageData {
+ public int[] data;
+ }
+
+ public ImageData imageData;
+
+ public Object[][] _aSaved;
+
+ public double lineWidth;
+
+ public String font, fillStyle, strokeStyle;
+
+ public float globalAlpha;
+
+ public abstract void drawImage(DOMNode img, double sx,
+ double sy, double swidth, double sheight, double dx, double dy, double width, double height);
+
+ public abstract ImageData getImageData(int x, int y, int width, int height);
+
+ public abstract void beginPath();
+
+ public abstract void moveTo(double x0, double y0);
+
+ public abstract void lineTo(double x1, double y1);
+
+ public abstract void stroke();
+
+ public abstract void save();
+
+ public abstract void scale(double f, double g);
+
+ public abstract void arc(double centerX, double centerY, double radius, double startAngle, double endAngle, boolean counterclockwise);
+
+ public abstract void closePath();
+
+ public abstract void restore();
+
+ public abstract void translate(double x, double y);
+
+ public abstract void rotate(double radians);
+
+ public abstract void fill();
+
+
+ public abstract void fill(String winding);
+
+ public abstract void rect(double x, double y, double width, double height);
+
+ public abstract void fillText(String s, double x, double y);
+
+ public abstract void fillRect(double x, double y, double width, double height);
+
+ public abstract void clearRect(double i, double j, double windowWidth, double windowHeight);
+
+ public abstract void setLineDash(int[] dash);
+
+ public abstract void clip();
+
+ public abstract void quadraticCurveTo(double d, double e, double f, double g);
+
+ public abstract void bezierCurveTo(double d, double e, double f, double g, double h, double i);
+
+ public abstract void drawImage(DOMNode img, double x, double y, double width, double height);
+
+ public abstract void putImageData(Object imageData, double x, double y);
+
+ public abstract void transform(double d, double shx, double e, double shy, double f, double g);
+
+
+ /**
+ * pull one save structure onto the stack array ctx._aSaved
+ *
+ * @param ctx
+ * @return the length of the stack array after the push
+ */
+ public static int push(HTML5CanvasContext2D ctx, Object[] map) {
+ /**
+ * @j2sNative
+ *
+ * (ctx._aSaved || (ctx._aSaved = [])).push(map);
+ * return ctx._aSaved.length;
+ */
+ {
+ return 0;
+ }
+ }
+
+ /**
+ * pull one save structure off the stack array ctx._aSaved
+ *
+ * @param ctx
+ * @return
+ */
+ public static Object[] pop(HTML5CanvasContext2D ctx) {
+ /**
+ * @j2sNative
+ *
+ * return (ctx._aSaved && ctx._aSaved.length > 0 ? ctx._aSaved.pop() : null);
+ */
+ {
+ return null;
+ }
+ }
+
+ public static int getSavedLevel(HTML5CanvasContext2D ctx) {
+ /**
+ * @j2sNative
+ *
+ * return (ctx._aSaved ? ctx._aSaved.length : 0);
+ */
+ {
+ return 0;
+ }
+ }
+
+ public static Object[][] getSavedStack(HTML5CanvasContext2D ctx) {
+ /**
+ * @j2sNative
+ *
+ * return (ctx._aSaved || []);
+ */
+ {
+ return null;
+ }
+
+ }
+
+ public static double[] setMatrix(HTML5CanvasContext2D ctx, AffineTransform transform) {
+ double[] m = /** @j2sNative ctx._m || */ null;
+ if (transform == null) {
+ /** @j2sNative ctx._m = null; */
+ return null;
+ }
+ if (m == null) {
+ /**
+ * @j2sNative
+ * ctx._m = m = new Array(6);
+ */
+ transform.getMatrix(m);
+ }
+ return m;
+ }
+
+ public static void createLinearGradient(HTML5CanvasContext2D ctx, Float p1, Float p2, String css1, String css2) {
+ /**
+ * @j2sNative
+ *
+ * var grd = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
+ * grd.addColorStop(0,css1);
+ * grd.addColorStop(1,css2);
+ * ctx.fillStyle = grd;
+ */
+ }
+
+ abstract public void drawImage(DOMNode domNode, int x, int y);
+
+}
--- /dev/null
+package swingjs.api.js;
+
+public interface HTML5DataTransfer {
+
+ Object getData(String type);
+
+}
--- /dev/null
+package swingjs.api.js;
+
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.function.Function;
+
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import swingjs.api.JSUtilI;
+
+/**
+ * A full-service interface for HTML5 video element interaction. Allows setting
+ * and getting HTML5 video element properties. ActionListeners can be set to
+ * listen for JavaScript events associated with a video element.
+ *
+ * Video is added using a JavaScript-only two-parameter constructor for
+ * ImageIcon with "jsvideo" as the description, allowing for video construction
+ * from byte[], File, or URL.
+ *
+ * After adding the ImageIcon to a JLabel, calling
+ * jlabel.getClientProperty("jsvideo") returns an HTML5 object of type
+ * HTML5Video (the <video> tag), which has the full suite of HTML5 video
+ * element properties, methods, and events.
+ *
+ * Access to event listeners is via the method addActionListener, below, which
+ * return an ActionEvent that has as its source both the video element source as
+ * well as the original JavaScript event as an Object[] { jsvideo, event }. The
+ * id of this ActionEvent is 12345, and its command is the name of the event,
+ * for example, "canplay" or "canplaythrough".
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement for
+ * details.
+ *
+ * @author hansonr
+ *
+ */
+public interface HTML5Video extends DOMNode {
+
+ public interface Promise {
+
+ }
+
+ final static String[] eventTypes = new String[] { "audioprocess", // The input buffer of a ScriptProcessorNode is
+ // ready to be processed.
+ "canplay", // The browser can play the media, but estimates that not enough data has been
+ // loaded to play the media up to its end without having to stop for further
+ // buffering of content.
+ "canplaythrough", // The browser estimates it can play the media up to its end without stopping
+ // for content buffering.
+ "complete", // The rendering of an OfflineAudioContext is terminated.
+ "durationchange", // The duration attribute has been updated.
+ "emptied", // The media has become empty; for example, this event is sent if the media has
+ // already been loaded (or partially loaded), and the load() method is called to
+ // reload it.
+ "ended", // Playback has stopped because the end of the media was reached.
+ "loadeddata", // The first frame of the media has finished loading.
+ "loadedmetadata", // The metadata has been loaded.
+ "pause", // Playback has been paused.
+ "play", // Playback has begun.
+ "playing", // Playback is ready to start after having been paused or delayed due to lack of
+ // data.
+ "progress", // Fired periodically as the browser loads a resource.
+ "ratechange", // The playback rate has changed.
+ "seeked", // A seek operation completed.
+ "seeking", // A seek operation began.
+ "stalled", // The user agent is trying to fetch media data, but data is unexpectedly not
+ // forthcoming.
+ "suspend", // Media data loading has been suspended.
+ "timeupdate", // The time indicated by the currentTimeattribute has been updated.
+ "volumechange", // The volume has changed.
+ "waiting", // Playback has stopped because of a temporary lack of data
+ };
+
+ // direct methods
+
+ public void addTextTrack() throws Throwable;
+
+ public Object captureStream() throws Throwable;
+
+ public String canPlayType(String mediaType) throws Throwable;
+
+ public void fastSeek(double time) throws Throwable;
+
+ public void load() throws Throwable;
+
+ public void mozCaptureStream() throws Throwable;
+
+ public void mozCaptureStreamUntilEnded() throws Throwable;
+
+ public void mozGetMetadata() throws Throwable;
+
+ public void pause() throws Throwable;
+
+ public Promise play() throws Throwable;
+
+ public Promise seekToNextFrame() throws Throwable;
+
+ public Promise setMediaKeys(Object mediaKeys) throws Throwable;
+
+ public Promise setSinkId(String id) throws Throwable;
+
+ // convenience methods
+
+ public static double getDuration(HTML5Video v) {
+ return /** @j2sNative v.duration || */
+ 0;
+ }
+
+ public static double setCurrentTime(HTML5Video v, double time) {
+ return /** @j2sNative v.currentTime = time|| */
+ 0;
+ }
+
+ public static double getCurrentTime(HTML5Video v) {
+ return /** @j2sNative v.currentTime|| */
+ 0;
+ }
+
+ public static Dimension getSize(HTML5Video v) {
+ return new Dimension(/** @j2sNative v.videoWidth || */
+ 0, /** @j2sNative v.videoHeight|| */
+ 0);
+ }
+
+ /**
+ *
+ * Create a BufferedIfmage from the current frame. The image will be of type
+ * swingjs.api.JSUtilI.TYPE_4BYTE_HTML5, matching the data buffer of HTML5
+ * images.
+ *
+ * @param v
+ * @param imageType if Integer.MIN_VALUE, swingjs.api.JSUtilI.TYPE_4BYTE_HTML5
+ * @return
+ */
+ public static BufferedImage getImage(HTML5Video v, int imageType) {
+ Dimension d = HTML5Video.getSize(v);
+ BufferedImage image = (BufferedImage) HTML5Video.getProperty(v, "_image");
+ if (image == null || image.getWidth() != d.width || image.getHeight() != d.height) {
+ image = new BufferedImage(d.width, d.height, imageType == Integer.MIN_VALUE ? JSUtilI.TYPE_4BYTE_HTML5 : imageType);
+ HTML5Video.setProperty(v, "_image", image);
+ }
+ HTML5Canvas.setImageNode(v, image);
+ return image;
+ }
+
+ // property setting and getting
+
+ /**
+ * Set a property of the the HTML5 video element using jsvideo[key] = value.
+ * Numbers and Booleans will be unboxed.
+ *
+ * @param jsvideo the HTML5 video element
+ * @param key
+ * @param value
+ */
+ public static void setProperty(HTML5Video jsvideo, String key, Object value) {
+ if (value instanceof Number) {
+ /** @j2sNative jsvideo[key] = +value; */
+ } else if (value instanceof Boolean) {
+ /** @j2sNative jsvideo[key] = !!+value */
+ } else {
+ /** @j2sNative jsvideo[key] = value; */
+ }
+ }
+
+ /**
+ * Get a property using jsvideo[key], boxing number as Double and boolean as
+ * Boolean.
+ *
+ * @param jsvideo the HTML5 video element
+ *
+ * @param key
+ * @return value or value boxed as Double or Boolean
+ */
+ @SuppressWarnings("unused")
+ public static Object getProperty(HTML5Video jsvideo, String key) {
+ Object val = (/** @j2sNative 1? jsvideo[key] : */
+ null);
+ if (val == null)
+ return null;
+ switch (/** @j2sNative typeof val || */
+ "") {
+ case "number":
+ return Double.valueOf(/** @j2sNative val || */
+ 0);
+ case "boolean":
+ return Boolean.valueOf(/** @j2sNative val || */
+ false);
+ default:
+ return val;
+ }
+ }
+
+ // event action
+
+ /**
+ * Add an ActionListener for the designated events. When an event is fired,
+ *
+ * @param jsvideo the HTML5 video element
+ * @param listener
+ * @param events array of events to listen to or null to listen on all video
+ * element event types
+ * @return an array of event/listener pairs that can be used for removal.
+ */
+ public static Object[] addActionListener(HTML5Video jsvideo, ActionListener listener, String... events) {
+ if (events == null || events.length == 0)
+ events = eventTypes;
+ @SuppressWarnings("unused")
+ Function<Object, Void> f = new Function<Object, Void>() {
+
+ @Override
+ public Void apply(Object jsevent) {
+ String name = (/** @j2sNative jsevent.type || */
+ "?");
+ System.out.println("HTML5Video " + name);
+ ActionEvent e = new ActionEvent(new Object[] { jsvideo, jsevent }, 12345, name,
+ System.currentTimeMillis(), 0);
+ listener.actionPerformed(e);
+ return null;
+ }
+ };
+ ArrayList<Object> listeners = new ArrayList<>();
+ for (int i = 0; i < events.length; i++) {
+ Object func = /**
+ * @j2sNative function(event){f.apply$O.apply(f, [event])} ||
+ */
+ null;
+ listeners.add(events[i]);
+ listeners.add(func);
+ if (jsvideo != null)
+ jsvideo.addEventListener(events[i], func);
+
+ }
+ return listeners.toArray(new Object[listeners.size()]);
+ }
+
+ /**
+ * Remove action listener
+ *
+ * @param jsvideo the HTML5 video element
+ * @param listeners an array of event/listener pairs created by
+ * addActionListener
+ */
+ public static void removeActionListener(HTML5Video jsvideo, Object[] listeners) {
+ if (listeners == null) {
+ for (int i = 0; i < eventTypes.length; i++) {
+ jsvideo.removeEventListener(eventTypes[i]);
+ }
+ }
+
+ for (int i = 0; i < listeners.length; i += 2) {
+ String event = (String) listeners[i];
+ Object listener = listeners[i + 1];
+ jsvideo.removeEventListener(event, listener);
+ }
+ }
+
+ /**
+ * Create an ImageIcon which, when placed in a JLabel, displays the video.
+ *
+ * @param source
+ * @return
+ */
+ public static ImageIcon createIcon(Object source) {
+ try {
+ if (source instanceof URL) {
+ return new ImageIcon((URL) source, "jsvideo");
+ } else if (source instanceof byte[]) {
+ return new ImageIcon((byte[]) source, "jsvideo");
+ } else if (source instanceof File) {
+ return new ImageIcon(Files.readAllBytes(((File) source).toPath()));
+ } else {
+ return new ImageIcon(Files.readAllBytes(new File(source.toString()).toPath()));
+ }
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ /**
+ * Create a label that, when shown, displays the video.
+ *
+ * @param source
+ * @return
+ */
+ public static JLabel createLabel(Object source) {
+ ImageIcon icon = (source instanceof ImageIcon ? (ImageIcon) source : createIcon(source));
+ return (icon == null ? null : new JLabel(icon));
+ }
+
+ /**
+ * Create a dialog that includes rudimentary controls. Optional maxWidth allows image downscaling by factors of two.
+ *
+ * @param parent
+ * @param source
+ * @param maxWidth
+ * @return
+ */
+ public static JDialog createDialog(Frame parent, Object source, int maxWidth, Function<HTML5Video, Void> whenReady) {
+ JDialog dialog = new JDialog(parent);
+ Container p = dialog.getContentPane();
+ p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+ JLabel label = (source instanceof JLabel ? (JLabel) source : createLabel(source));
+ label.setAlignmentX(0.5f);
+ // not in Java! dialog.putClientProperty("jsvideo", label);
+ p.add(label);
+ label.setVisible(false);
+ p.add(getControls(label));
+ dialog.setModal(false);
+ dialog.pack();
+ dialog.setVisible(true);
+ dialog.setVisible(false);
+ HTML5Video jsvideo = (HTML5Video) label.getClientProperty("jsvideo");
+ HTML5Video.addActionListener(jsvideo, new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (label.getClientProperty("jsvideo.size") != null)
+ return;
+ Dimension dim = HTML5Video.getSize(jsvideo);
+ while (dim.width > maxWidth) {
+ dim.width /= 2;
+ dim.height /= 2;
+ }
+ label.putClientProperty("jsvideo.size", dim);
+ label.setPreferredSize(dim);
+ label.setVisible(true);
+// label.invalidate();
+ dialog.pack();
+// dialog.setVisible(false);
+ if (whenReady != null)
+ whenReady.apply(jsvideo);
+ }
+
+ }, "canplaythrough");
+ HTML5Video.setCurrentTime(jsvideo, 0);
+ return dialog;
+ }
+
+ static JPanel getControls(JLabel label) {
+
+ JPanel controls = new JPanel();
+ controls.setAlignmentX(0.5f);
+ JButton btn = new JButton("play");
+ btn.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ ((HTML5Video) label.getClientProperty("jsvideo")).play();
+ } catch (Throwable e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ });
+ controls.add(btn);
+
+ btn = new JButton("pause");
+ btn.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ ((HTML5Video) label.getClientProperty("jsvideo")).pause();
+ } catch (Throwable e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ });
+ controls.add(btn);
+
+ btn = new JButton("reset");
+ btn.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ HTML5Video.setCurrentTime((HTML5Video) label.getClientProperty("jsvideo"), 0);
+ }
+
+ });
+ controls.add(btn);
+
+ return controls;
+ }
+
+ /**
+ * Advance to the next frame, using seekToNextFrame() if available, or using the time difference supplied.
+ *
+ * @param jsvideo
+ * @param dt seconds to advance if seekToNextFrame() is not available
+ * @return true if can use seekToNextFrame()
+ *
+ */
+ public static boolean nextFrame(HTML5Video jsvideo, double dt) {
+ Boolean canSeek = (Boolean) getProperty(jsvideo,"_canseek");
+ if (canSeek == null) {
+ setProperty(jsvideo, "_canseek", canSeek = Boolean.valueOf(getProperty(jsvideo, "seekToNextFrame") != null));
+ }
+ try {
+ if (canSeek) {
+ jsvideo.seekToNextFrame();
+ } else {
+ HTML5Video.setCurrentTime(jsvideo, HTML5Video.getCurrentTime(jsvideo) + dt);
+ }
+ } catch (Throwable e1) {
+ }
+ return canSeek.booleanValue();
+ }
+
+ public static int getFrameCount(HTML5Video jsvideo) {
+ return (int) (getDuration(jsvideo) / 0.033334);
+ }
+
+// HTMLMediaElement properties
+
+// audioTracks
+// autoplay
+// buffered Read only
+// controller
+// controls
+// controlsList Read only
+// crossOrigin
+// currentSrc Read only
+// currentTime
+// defaultMuted
+// defaultPlaybackRate
+// disableRemotePlayback
+// duration Read only
+// ended Read only
+// error Read only
+// loop
+// mediaGroup
+// mediaKeys Read only
+// mozAudioCaptured Read only
+// mozFragmentEnd
+// mozFrameBufferLength
+// mozSampleRate Read only
+// muted
+// networkState Read only
+// paused Read only
+// playbackRate
+// played Read only
+// preload
+// preservesPitch
+// readyState Read only
+// seekable Read only
+// seeking Read only
+// sinkId Read only
+// src
+// srcObject
+// textTracks Read only
+// videoTracks Read only
+// volume
+// initialTime Read only
+// mozChannels Read only
+
+}
--- /dev/null
+package swingjs.api.js;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.util.Hashtable;
+
+
+/**
+ * An interface to J2S.xxx() functions.
+ *
+ * @author hansonr
+ *
+ */
+
+public interface J2SInterface {
+
+ void addBinaryFileType(String ext);
+
+ void addDirectDatabaseCall(String domain);
+
+ boolean debugClip();
+
+
+
+ HTML5Applet findApplet(String htmlName);
+
+ Object getCachedJavaFile(String key);
+
+ /**
+ *
+ * @param isAll true for check of navigator; otherwise just J2S._lang from j2sLang=xx_XX in URI
+ * @return
+ */
+ String getDefaultLanguage(boolean isAll);
+
+ Object getFileData(String fileName, Object fWhenDone, boolean doProcess, boolean isBinary);
+
+ void getFileFromDialog(Object fWhenDone, String type);
+
+ Object getJavaResource(String resourceName, boolean isJavaPath);
+
+ String getJavaVersion();
+
+ int getKeyModifiers(Object jQueryEvent);
+
+ Point getMousePosition(Point p);
+
+ String getResourcePath(String resourceName, boolean isJavaPath);
+
+ Hashtable<String, Object> getSetJavaFileCache(Object object);
+
+ Object getSwing(); // JSSwingMenu
+
+ int getZ(HTML5Applet applet, String frameType);
+
+ boolean isBinaryUrl(String filename);
+
+ boolean isResourceLoaded(String file, boolean done);
+
+ void readyCallback(String appId, String fullId, boolean isReady,
+ Object javaApplet, Object javaAppletPanel);
+
+ void saveFile(String fileName, Object data, String mimeType, String encoding);
+
+ void setDragDropTarget(Component target, DOMNode node, boolean adding);
+
+ void setDraggable(DOMNode tagNode, Object targetNodeOrFDown);
+
+ void setKeyListener(DOMNode node);
+
+ void setMouse(DOMNode frameNode, boolean isSwingJS);
+
+ int setWindowZIndex(DOMNode domNode, int pos);
+
+ void unsetMouse(DOMNode frameNode);
+
+ String fixCachePath(String uri);
+
+ void showStatus(String msg, boolean doFadeOut);
+
+
+}
+
--- /dev/null
+package swingjs.api.js;
+
+public interface JQuery {
+
+ JQueryObject $(Object selector);
+
+ DOMNode parseXML(String xmlData);
+
+ boolean contains(Object outer, Object inner);
+
+ Object parseJSON(String json);
+
+ Object data(Object node, String attr);
+
+}
--- /dev/null
+package swingjs.api.js;
+
+public interface JQueryObject {
+
+ public interface JQEvent {
+
+ }
+
+ public abstract void appendTo(Object obj);
+ public abstract JQueryObject append(Object span);
+
+ public abstract void bind(String actions, Object f);
+ public abstract void unbind(String actions);
+
+ public abstract void on(String eventName, Object f);
+
+ public abstract JQueryObject focus();
+ public abstract JQueryObject select();
+
+ public abstract int width();
+ public abstract int height();
+ public abstract Object offset();
+
+
+ public abstract void html(String html);
+
+ public abstract DOMNode get(int i);
+
+ public abstract String attr(String key);
+ public abstract JQueryObject attr(String key, String value);
+ public abstract JQueryObject css(String key, String value);
+
+ public abstract JQueryObject addClass(String name);
+ public abstract JQueryObject removeClass(String name);
+
+ public abstract JQueryObject show();
+ public abstract JQueryObject hide();
+
+ public abstract void resize(Object fHandleResize);
+
+
+ /**
+ * closest ancestor
+ *
+ * @param selector
+ * @return
+ */
+ public abstract JQueryObject closest(String selector);
+
+ /**
+ * find all descendants
+ *
+ * @param selector
+ * @return
+ */
+ public abstract JQueryObject find(String selector);
+
+ public abstract JQueryObject parent();
+ public abstract void before(Object obj);
+ public abstract void after(Object div);
+
+
+ /**
+ * remove from tree, but do not clear events
+ */
+ public abstract void detach(); // like remove(), but does not change event settings
+
+ /**
+ * remove from tree and clear all events -- for disposal only
+ */
+ public abstract void remove();
+
+ /**
+ * fully remove all children, clearing all events
+ */
+ public abstract void empty();
+
+ public abstract DOMNode getElement();
+
+ public static DOMNode getDOMNode(JQueryObject jnode) {
+ return (jnode == null ? null : ((DOMNode[]) (Object) jnode)[0]);
+ }
+
+
+}
--- /dev/null
+package swingjs.api.js;
+
+/**
+ * A flag that this object is really a JavaScript function that, for example,
+ * might be called from setTimeout().
+ *
+ * @author Bob Hanson
+ *
+ */
+public interface JSFunction {
+
+}
--- /dev/null
+package swingjs.api.js;
+
+/**
+ * called by SwingJS JavaScript methods
+ *
+ */
+public interface JSInterface {
+
+ int cacheFileByName(String fileName, boolean isAdd); // $S$Z
+
+ void cachePut(String key, Object data); // $S$O
+
+ void destroy();
+
+ String getFullName();
+
+ void openFileAsyncSpecial(String fileName, int flags); // $S$I
+
+ boolean processMouseEvent(int id, int x, int y, int modifiers, long time, Object jqevent, int scroll); // $I$I$I$I$J$O$I
+
+ void processTwoPointGesture(float[][][] touches); // AFFF
+
+ void setDisplay(HTML5Canvas canvas);
+
+ void setScreenDimension(int width, int height);
+
+ boolean setStatusDragDropped(int mode, int x, int y, String fileName);
+
+ void startHoverWatcher(boolean enable);
+
+ static void setCursor(String c) {
+ /**
+ * @j2sNative
+ *
+ * try {
+ *
+ * document.body.style.cursor = c;
+ *
+ * } catch (e) {}
+ */
+ }
+
+}