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:
*
*
* private static Dimension dim =
*
*
*
* 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
*
* 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;
* }
*
* @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;
}
}
}