static final int NOTPACKED_FLAG = 0x00000004;
static final int HSHRINK_FLAG = 0x00000008;
static final int VSHRINK_FLAG = 0x00000010;
- static final int TILE_FLAG = 0x00000020;
static final int FONT_CHANGED_FLAG = 0x00000040; // set when font changes, cleared during repack
static final int ISROOT_FLAG = 0x00000080;
static final int NOCLIP_FLAG = 0x00000100;
int globaly = parenty + (parent == null ? 0 : y);
// intersect the x,y,w,h rendering window with ourselves; quit if it's empty
+
if ((flags & NOCLIP_FLAG) == 0) {
clipw = min(max(clipx, parent == null ? 0 : globalx) + clipw, (parent == null ? 0 : globalx) + width) - globalx;
cliph = min(max(clipy, parent == null ? 0 : globaly) + cliph, (parent == null ? 0 : globaly) + height) - globaly;
}
if (image != null)
- if ((flags & TILE_FLAG) != 0) renderTiledImage(globalx, globaly, clipx, clipy, clipw, cliph, buf);
- else buf.drawPicture(image, globalx, globaly, clipx, clipy, clipx + clipw, clipy + cliph);
+ for(int x = globalx; x < clipx + clipw; x += image.getWidth())
+ for(int y = globaly; y < clipy + cliph; y += image.getHeight())
+ buf.drawPicture(image, x, y, clipx, clipy, clipx + clipw, clipy + cliph);
if (text != null && !text.equals(""))
- renderText(globalx, globaly, clipx, clipy, clipw, cliph, buf);
+ Glyph.rasterizeGlyphs(font, fontsize, text, buf, textcolor, globalx, globaly, clipx, clipy, clipw + clipx, clipy + cliph,
+ new Callback() { public Object call(Object arg) {
+ Box.this.dirty();
+ Box b = Box.this;
+ MARK_FOR_REFLOW_b;
+ b.dirty();
+ return null;
+ }});
if (path != null) {
if (rtransform == null) rpath = null;
}
}
- void renderTiledImage(int globalx, int globaly, int x, int y, int w, int h, PixelBuffer buf) {
- /*
- FIXME
- int iw = image.getWidth();
- int ih = image.getHeight();
- for(int i=(x - x)/iw; i <= (x + w - x)/iw; i++) {
- for(int j=(y - y)/ih; j<= (y + h - y)/ih; j++) {
-
- int dx1 = max(i * iw + x, x);
- int dy1 = max(j * ih + y, y);
- int dx2 = min((i+1) * iw + x, x + w);
- int dy2 = min((j+1) * ih + y, y + h);
-
- int sx1 = dx1 - (i*iw) - x;
- int sy1 = dy1 - (j*ih) - y;
- int sx2 = dx2 - (i*iw) - x;
- int sy2 = dy2 - (j*ih) - y;
-
- if (dx2 - dx1 > 0 && dy2 - dy1 > 0 && sx2 - sx1 > 0 && sy2 - sy1 > 0)
- buf.drawPicture(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
- }
- }
- */
- }
-
- void renderText(int x, int y, int clipx, int clipy, int clipw, int cliph, PixelBuffer buf) {
- for(int i=0; i<text.length(); i++) {
- final char c = text.charAt(i);
- Glyph g = Glyph.getCachedGlyph(font, fontsize, c);
- if (g != null) {
- int top = y + g.max_ascent - g.baseline + g.max_descent;
- if (g.p != null)
- buf.drawPictureAlphaOnly(g.p, x, top,
- clipx, clipy, clipx + clipw, clipy + cliph, textcolor);
- x += g.advance;
- } else {
- final int fontsize_final = fontsize;
- final Res font_final = font;
- ThreadMessage.newthread(new JS.Callable() {
- public Object call(JS.Array args) {
- Glyph.renderGlyph(font_final, fontsize_final, c);
- recompute_font();
- Box b = Box.this; MARK_FOR_REFLOW_b;
- dirty();
- return null;
- } });
- return;
- }
- }
- }
-
// Methods to implement org.xwt.js.JS //////////////////////////////////////
}
public void recompute_font() {
- try {
- MARK_FOR_REFLOW_this;
- textwidth = 0;
- textheight = 0;
- if (text == null) return;
- for(int i=0; i<text.length(); i++) {
- Glyph g = Glyph.getCachedGlyph(font, fontsize, text.charAt(i));
- if (g == null) {
- final int fontsize_final = fontsize;
- final Res font_final = font;
- final char c = text.charAt(i);
- ThreadMessage.newthread(new JS.Callable() {
- public Object call(JS.Array args) {
- Glyph.renderGlyph(font_final, fontsize_final, c);
- recompute_font();
- Box b = Box.this; MARK_FOR_REFLOW_b;
- dirty();
- return null;
- } });
- } else {
- textwidth += g.advance;
- textheight = max(textheight, g.max_ascent + g.max_descent);
- }
- }
- } catch (Exception e) {
- Log.log(this, e);
- }
+ if (text == null) { textwidth = textheight = 0; return; }
+ if (font == null) { /* FIXME */ }
+ long widthheight = Glyph.rasterizeGlyphs(font, fontsize, text, null, textcolor, 0, 0, 0, 0, 0, 0,
+ new Callback() { public Object call(Object arg) {
+ Box b = Box.this;
+ recompute_font();
+ MARK_FOR_REFLOW_b;
+ b.dirty();
+ return null;
+ } });
+ if (widthheight == -1) return;
+ textwidth = (int)((widthheight & 0xffff0000) >> 16);
+ textheight = (int)(widthheight & 0x0000ffff);
+ MARK_FOR_REFLOW_this;
}
- // Trivial Helper Methods (should be inlined) /////////////////////////////////////////
+ // Trivial Helper Method s(should be inlined) /////////////////////////////////////////
static final int min(int a, int b) { if (a<b) return a; else return b; }
static final float min(float a, float b) { if (a<b) return a; else return b; }
static {
specialBoxProperties.put("fill", new ColorBoxProperty() {
- public void put(Box b, Object value) {
- if (value != null && value instanceof Res) {
- b.image = Picture.fromRes((Res)value);
+ public void put(final Box b, final Object value) {
+ if (value == null || !(value instanceof Res)) super.put(b, value);
+ else Picture.fromRes((Res)value, new Callback() { public Object call(Object pic) {
+ if (pic == b.image) return null;
+ b.image = (Picture)pic;
b.minwidth = b.image.getWidth();
b.minheight = b.image.getHeight();
+ MARK_FOR_REFLOW_b;
b.dirty();
- } else {
- super.put(b, value);
- }
+ return null;
+ } });
}
public int getColor(Box b) { return b.fillcolor; }
public void putColor(Box b, int argb) { b.fillcolor = argb; }
String t = value == null ? "null" : value.toString();
if (t.equals(b.text)) return;
b.text = t;
- if (t == null) {
- if (b.textwidth != 0 || b.textheight != 0) MARK_FOR_REFLOW_b;
- b.textwidth = b.textheight = 0;
- } else {
- b.recompute_font();
- }
b.dirty();
+ b.recompute_font();
} });
specialBoxProperties.put("path", new SpecialBoxProperty() {
});
//#end
- specialBoxProperties.put("tile", new SpecialBoxProperty() {
- public Object get(Box b) { return ((b.flags & TILE_FLAG) != 0) ? Boolean.TRUE : Boolean.FALSE; }
- public void put(Box b, Object value) {
- if (((b.flags & TILE_FLAG) != 0) == stob(value)) return;
- if (stob(value)) b.flags |= TILE_FLAG; else b.flags &= ~TILE_FLAG;
- b.dirty();
- } });
-
specialBoxProperties.put("noclip", new SpecialBoxProperty() {
public Object get(Box b) { return ((b.flags & NOCLIP_FLAG) != 0) ? Boolean.TRUE : Boolean.FALSE; }
public void put(Box b, Object value) {
if (b.parent != null) MARK_FOR_REFLOW_b_parent;
} });
- specialBoxProperties.put("image", new SpecialBoxProperty() {
- public Object get(Box b) { return b.image == null ? null : b.image.res; }
- public void put(Box b, Object value) {
- if (value == null) {
- b.image = null;
- } else if (value instanceof Res) {
- b.image = Picture.fromRes((Res)value);
- } else {
- // FIXME
- }
- b.minwidth = min(b.maxwidth, max(b.minwidth, b.image == null ? 0 : b.image.getWidth()));
- b.minheight = min(b.maxheight, max(b.minheight, b.image == null ? 0 : b.image.getHeight()));
- MARK_FOR_REFLOW_b;
- b.dirty();
- }
- });
-
//#repeat globalx/globaly x/y
specialBoxProperties.put("globalx", new SpecialBoxProperty() {
public Object get(Box b) { return new Integer(b.parent == null || b.surface == null ? 0 : b.x); }
import org.xwt.translators.*;
import org.xwt.util.*;
import org.xwt.js.*;
+import java.util.*;
public class Glyph {
public char c;
public int max_descent; // same value for every glyph in font; the max descent of all chars
public int baseline; // within the picture, this is the y-coordinate of the baseline
public int advance; // amount to increment the x-coordinate
+ public int pointsize;
public Picture p;
+ public Res res;
+ public boolean rendered = false;
// k1=font.res k2=(c << 16 | pointsize)
private static Cache glyphCache = new Cache();
- public static Glyph getCachedGlyph(Res res, int pointsize, char c) {
- return (Glyph)glyphCache.get(res, new Integer((((int)c) << 16) | pointsize));
- }
- public static void renderGlyph(Res res, int pointsize, char c) {
- if (!ThreadMessage.suspendThread())
- throw new RuntimeException("attempt to perform background-only operation in a foreground thread");
- synchronized(res) {
- // FEATURE: be smarter here
- if ((Glyph)glyphCache.get(res, new Integer((((int)c) << 16) | pointsize)) != null) return;
- Log.log(Freetype.class, "rendering glyphs for font " + res.getDescriptiveName());
- if (c >= 32 && c < 127) Freetype.renderGlyphs(res, pointsize, 32, 126, glyphCache);
- else Freetype.renderGlyphs(res, pointsize, (int)c, (int)c, glyphCache);
- if ((Glyph)glyphCache.get(res, new Integer((((int)c) << 16) | pointsize)) == null)
- throw new JS.Exn("error rendering glyph " + c + "; glyph is null");
- Log.log(Freetype.class, " done rendering glyphs for font " + res.getDescriptiveName());
- }
- ThreadMessage.resumeThread();
+ private static Queue glyphsToBeRendered = new Queue(255);
+ private static Freetype freetype = new Freetype();
+
+ private static Scheduler.Task glyphRenderingTask = new Scheduler.Task() {
+ public Object call(Object arg) {
+ Glyph g = (Glyph)glyphsToBeRendered.remove(false);
+ if (g == null) return null;
+ if (g.p != null) return null;
+ Log.log(Glyph.class, "rendering glyph " + g.c);
+ freetype.renderGlyph(g.res, g);
+ g.rendered = true;
+ Scheduler.add(this);
+ return null;
+ }
+ };
+
+ /**
+
+ * If the glyphs of <code>text</code> are not yet loaded, spawn a
+ * Task to load them and invoke callback.
+ *
+
+ * returns the width (in the high-order int) and height (in the
+ * low-order int) of the string's rasterization, or -1 if some
+ * glyphs are not loaded.
+
+ */
+ public static long rasterizeGlyphs(final Res res, final int pointsize, final String text, PixelBuffer pb, int textcolor,
+ int x, int y, int cx1, int cy1, int cx2, int cy2, final Callback callback) {
+ boolean ret = true;
+ int width = 0;
+ int height = 0;
+ for(int i=0; i<text.length(); i++) {
+ final char c = text.charAt(i);
+ Glyph g = (Glyph)glyphCache.get(res, new Integer((((int)c) << 16) | pointsize));
+ if (g == null) {
+ g = new Glyph();
+ g.c = c;
+ g.pointsize = pointsize;
+ g.res = res;
+ glyphCache.put(res, new Integer((((int)c) << 16) | pointsize), g);
+ glyphsToBeRendered.prepend(g);
+ }
+ if (g.rendered && ret) {
+ if (pb != null && g.p != null)
+ pb.drawPictureAlphaOnly(g.p, x, y + g.max_ascent - g.baseline, cx1, cy1, cx2, cy2, textcolor);
+ x += g.advance;
+ width += g.advance;
+ height = java.lang.Math.max(height, g.max_ascent + g.max_descent);
+ } else {
+ glyphsToBeRendered.prepend(g);
+ Scheduler.add(glyphRenderingTask);
+ ret = false;
+ }
+ }
+ if (!ret) Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+ for(int i=0; i<text.length(); i++) {
+ Glyph g = (Glyph)glyphCache.get(res, new Integer((((int)text.charAt(i)) << 16) | pointsize));
+ if (g == null || !g.rendered) {
+ Scheduler.add(this);
+ return null;
+ }
+ }
+ callback.call(null);
+ return null;
+ }});
+
+ if (ret) return ((long)width << 16) | (long)height;
+ for(int i=32; i<128; i++) {
+ Glyph g = (Glyph)glyphCache.get(res, new Integer((i << 16) | pointsize));
+ if (g == null) {
+ g = new Glyph();
+ g.c = (char)i;
+ g.pointsize = pointsize;
+ g.res = res;
+ glyphCache.put(res, new Integer((i << 16) | pointsize), g);
+ glyphsToBeRendered.append(g);
+ }
+ }
+ return -1;
}
}
return;
} catch (UnknownHostException uhe) { }
- if (Platform.detectProxy() == null) throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured");
- if (Log.on) Log.log(this, " could not resolve host " + host + "; using xmlrpc.xwt.org to ensure security");
- try {
- JS.Array args = new JS.Array();
- args.addElement(host);
- Object ret = new XMLRPC("http://xmlrpc.xwt.org/RPC2/", "dns.resolve").call(args);
- if (ret == null || !(ret instanceof String)) throw new Exception(" xmlrpc.xwt.org returned non-String: " + ret);
- resolvedHosts.put(host, ret);
- return;
- } catch (Throwable e) {
- throw new HTTPException("exception while attempting to use xmlrpc.xwt.org to resolve " + host + ": " + e);
- }
+ if (Platform.detectProxy() == null)
+ throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured");
}
org.xwt.js.JS.Array args = new org.xwt.js.JS.Array();
args.addElement(url.toString());
args.addElement(url.getHost());
+ /* FIXME
Object obj = pacFunc.call(args);
if (Log.verbose) Log.log(this, " PAC script returned \"" + obj + "\"");
pac = obj.toString();
+ */
} catch (Throwable e) {
if (Log.on) Log.log(this, "PAC script threw exception " + e);
return null;
}
JS.CompiledFunction scr = JS.parse("PAC script at " + url, 0, new StringReader(script));
+ // FIXME
+ /*
scr.call(new JS.Array(), proxyAutoConfigRootScope);
+ */
return (JS.Callable)proxyAutoConfigRootScope.get("FindProxyForURL");
} catch (Exception e) {
if (Log.on) {
if (authorization != oldAuth) return;
if (Log.on) Log.log(Authorization.class, "displaying proxy authorization dialog");
+ /*
+ FIXME
Message.Q.add(new Message() {
public void perform() {
Box b = new Box();
b.put("proxyIP", proxyIP);
}
});
-
+ */
waitingForUser.block();
if (Log.on) Log.log(Authorization.class, "got proxy authorization info; re-attempting connection");
final XWT xwt = new XWT(rr);
final Res final_rr = rr;
- new Thread(new Runnable() {
- public void run() {
- Message.Q.startQ();
- ThreadMessage.newthread(new JS.Callable() {
- public Object call(JS.Array args) {
- scarImage = Picture.fromRes((Res)Main.builtin.get("org/xwt/builtin/scar.png"));
- Template.getTemplate(((Res)final_rr.get(initialTemplate))).apply(new Box(), null, xwt);
- return null;
- }
- });
- }
- }).start();
-
+ Picture.fromRes((Res)Main.builtin.get("org/xwt/builtin/scar.png"), new Callback() {
+ public Object call(Object arg) {
+ scarImage = (Picture)arg;
+ Scheduler.add(new Scheduler.Task() {
+ public Object call(Object args) {
+ Template.getTemplate(((Res)final_rr.get(initialTemplate))).apply(new Box(), null, xwt);
+ return null;
+ }
+ });
+ return null;
+ } });
+
+ new Thread() { public void run() { Scheduler.run(); } }.start();
Platform.running();
}
+++ /dev/null
-// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
-package org.xwt;
-
-import java.util.*;
-import org.xwt.util.*;
-
-
-/** A simple interface that must be supported by any object inserted into the MessageQueue. */
-public interface Message {
-
- /** Invoked when the Message is dequeued. */
- public void perform();
-
-
- /**
- * A singleton class (one instance per JVM) that implements a queue
- * for XWT events and threads; this is the main action-scheduling
- * loop for the engine.
- *
- * This <i>is</i> the foreground thread -- it
- * dequeues Messages when they arrive in the queue. Using this
- * thread ensures that the messages are executed in a synchronous,
- * in-order fashion.
- */
- public static class Q extends Thread {
-
- /** a do-nothing message enqueued to trigger Surfaces to refresh themselves */
- private static Message refreshMessage = new Message() { public void perform() { } };
-
- /** enqueues a do-nothing message to get the Surfaces to refresh themselves */
- public static void refresh() { add(refreshMessage); }
-
- /** true iff latency-sensitive UI work is being done; signals the networking code to yield */
- public static volatile boolean working = false;
-
- private Q() { }
-
- /** invoked by Main */
- public static void startQ() { singleton.start(); }
-
- /** pending events */
- private static Queue events = new Queue(50);
-
- /** the number of objects in the queue that are not subclasses of ThreadMessage */
- public static volatile int nonThreadEventsInQueue = 0;
-
- /** the message currently being performed */
- static Message currentlyPerforming = null;
-
- private static Q singleton = new Q();
- private static Watcher watcher = new Watcher();
-
- /**
- * The message loop. Note that non-ThreadMessage Messages get
- * priority, and that the queue is emptied in passes -- first we
- * look at how many events are in the queue, then perform that
- * many events, then render. This has the effect of throttling
- * render requests to the appropriate frequency -- when many
- * messages are in the queue, refreshes happen less frequently.
- */
- public void run() {
- Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
- while(true) {
- try {
- int size = events.size();
- for(int i=0; i<Math.max(1, size); i++) {
- Message e = (Message)events.remove();
-
- // if there are non-ThreadMessage events, perform them first
- // we check against Thread instead of ThreadMessage so we don't get link failures in the Shoehorn
- if (!(e instanceof Thread)) nonThreadEventsInQueue--;
- else if (nonThreadEventsInQueue > 0) {
- add(e);
- i--;
- continue;
- }
- if (!(e instanceof Thread)) working = true;
- currentlyPerforming = e;
- e.perform();
- currentlyPerforming = null;
- working = false;
- }
- working = true;
- for(int i=0; i<Surface.allSurfaces.size(); i++)
- ((Surface)Surface.allSurfaces.elementAt(i)).render();
- working = false;
-
- } catch (Throwable t) {
- if (Log.on) Log.log(this, "caught throwable in Q.run(); this should never happen");
- if (Log.on) Log.log(this, " currentlyPerforming == " + currentlyPerforming);
- if (Log.on) Log.log(this, " working == " + working);
- if (Log.on) Log.log(this, t);
- if (Log.on) Log.log(this, "resuming Q loop");
- }
- }
- }
-
- /** Adds an event to the queue */
- public static void add(Message e) {
- // Even though we don't synchronize around these two statements, it is not a race condition. In the worst case,
- // nonThreadEventsInQueue undercounts -- it never overcounts.
- events.append(e);
- if (!(e instanceof Thread)) nonThreadEventsInQueue++;
- }
-
- /** a simple thread that logs a warning if too much time is spent in any given message -- helpful for debugging infinite loops */
- private static class Watcher extends Thread {
- long t = System.currentTimeMillis();
- Message m = null;
- public Watcher() { start(); }
- public void run() {
- while(true) {
- if ((m != null && m == Q.currentlyPerforming) || Q.working) {
- String what, where;
- if (m != null && m instanceof ThreadMessage) {
- where = org.xwt.js.JS.Thread.fromJavaThread((ThreadMessage)m).getSourceName();
- what = "background thread";
- } else if (m != null) {
- where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
- what = "event trap";
- } else {
- where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
- what = "script";
- }
- long howlong = (System.currentTimeMillis() - t) / 1000;
- if (howlong >= 5)
- if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where);
- } else {
- m = Q.currentlyPerforming;
- t = System.currentTimeMillis();
- }
- try { Thread.sleep(1000); } catch (Exception e) { }
- }
- }
- }
-
- }
-
-}
-
*/
public abstract class Picture {
- /** Pictures, cached by Res */
- private static Cache cache = new Cache();
-
- private static GIF gif = new GIF();
-
- /** turns a resource into a Picture.Source */
- public static Picture fromRes(Res r) {
- Picture ret = (Picture)cache.get(r);
- if (ret == null) {
- try {
- PushbackInputStream pbis = new PushbackInputStream(r.getInputStream());
- int c = pbis.read();
- pbis.unread(c);
- if (c == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
- else if (c == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
- else if (c == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
- else throw new JS.Exn("couldn't figure out image type from first byte");
- cache.put(r, ret);
- ret.res = r;
- } catch (IOException e) {
- Log.logJS(Picture.class, e);
- return null;
- }
- }
- return ret;
- }
-
/** the resource that created this Picture */
public Res res = null;
/** the width of the picture */
public abstract int getWidth();
+ /** Pictures, cache keyed by Res instance */
+ private static Cache cache = new Cache();
+ private static GIF gif = new GIF();
+
+ /** turns a resource into a Picture.Source and passes it to the callback */
+ public static void fromRes(final Res r, final Callback callback) {
+ Picture ret = (Picture)cache.get(r);
+ if (ret != null) {
+ callback.call(ret);
+ return;
+ }
+
+ try {
+ // FIXME: put self in background
+ PushbackInputStream pbis = new PushbackInputStream(r.getInputStream());
+ int c = pbis.read();
+ pbis.unread(c);
+ if (c == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
+ else if (c == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
+ else if (c == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
+ else throw new JS.Exn("couldn't figure out image type from first byte");
+ cache.put(r, ret);
+ ret.res = r;
+ callback.call(ret);
+ } catch (Exception e) {
+ Log.log(Picture.class, e);
+ }
+ }
}
/** displays a platform-specific "open file" dialog and returns the chosen filename, or null if the user hit cancel */
protected String _fileDialog(String suggestedFileName, boolean write) { return null; }
public static String fileDialog(String suggestedFileName, boolean write) {
- if (!ThreadMessage.suspendThread()) return null;
- try {
- return platform._fileDialog(suggestedFileName, write);
- } finally {
- ThreadMessage.resumeThread();
- }
+ // FIXME: put self in background
+ return platform._fileDialog(suggestedFileName, write);
}
/** default implementation is Eric Albert's BrowserLauncher.java */
Object icon = b.get("icon", true);
if (icon != null && icon instanceof Res) {
+ /*
+ FIXME
Picture pic = Picture.fromRes((Res)icon);
if (pic != null) ret.setIcon(pic);
else if (Log.on) Log.log(Platform.class, "unable to load icon " + icon);
+ */
}
ret.setLimits(b.minwidth, b.minheight, b.maxwidth, b.maxheight);
public Res getParent() { return null; }
/** an InputStream that makes sure it is not in the MessageQueue when blocked on a read */
+ // FIXME
private static class BackgroundInputStream extends FilterInputStream {
BackgroundInputStream(InputStream i) { super(i); }
+ /*
private void suspend() throws IOException {
if (!ThreadMessage.suspendThread())
throw new IOException("attempt to perform background-only operation in a foreground thread");
try { return super.read(b, off, len); }
finally { resume(); }
}
+ */
}
/** returns an InputStream containing the Resource's contents */
/** shadow resource which replaces the graft */
public static class ProgressWatcher extends Res {
final Res watchee;
- JS.Callable callback;
- ProgressWatcher(Res watchee, JS.Callable callback) { this.watchee = watchee; this.callback = callback; }
+ JS.CompiledFunction callback;
+ ProgressWatcher(Res watchee, JS.CompiledFunction callback) { this.watchee = watchee; this.callback = callback; }
public String getDescriptiveName() { return watchee.getDescriptiveName(); }
public InputStream getInputStream(String s) throws IOException {
final InputStream is = watchee.getInputStream(s);
public int read(byte[] b, int off, int len) throws IOException {
int ret = super.read(b, off, len);
if (ret != 1) bytesDownloaded += ret;
- ThreadMessage.newthread(new JS.Callable() { public Object call(JS.Array a) {
+ Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
JS.Array args = new JS.Array();
args.addElement(new Integer(bytesDownloaded));
args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
- callback.call(args);
+ // FIXME
+ // new JS.Thread(callback, callbackScope).resume();
return null;
} });
return ret;
--- /dev/null
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt;
+
+import java.util.*;
+import org.xwt.js.*;
+import org.xwt.util.*;
+
+// FEATURE: reimplement Watcher
+/** Implements cooperative multitasking */
+public class Scheduler {
+
+ private static Scheduler singleton = new Scheduler();
+ public static void run() { singleton.do_run(); }
+ protected Scheduler() { }
+
+ public static abstract class Task implements Callback { public abstract Object call(Object o); }
+
+ private static Queue runnable = new Queue(50);
+
+ public static void add(Task t) { singleton.runnable.append(t); }
+ public void do_run() {
+ while(true) {
+ Task t = (Task)runnable.remove(true);
+ try {
+ t.call(null);
+ for(int i=0; i<Surface.allSurfaces.size(); i++)
+ ((Surface)Surface.allSurfaces.elementAt(i)).render();
+ } catch (Exception e) {
+ Log.log(Scheduler.class, "Task threw an exception: " + e);
+ Log.log(Scheduler.class, e);
+ }
+ }
+ }
+}
*
* Note that the members in the section 'state variables' are either
* in real-time (the actual size/position/state), or in
- * MessageQueue-time (the size/position/state at the time that the
+ * Scheduler-time (the size/position/state at the time that the
* now-executing message was enqueued). This distinction is important.
*/
// FIXME: put the scar box back in
// Static Data ////////////////////////////////////////////////////////////////////////////////
+ // FIXME
+ private abstract static class Message extends Scheduler.Task {
+ public abstract void perform();
+ public Object call(Object arg) { perform(); return null; }
+ }
+
/**< the most recently enqueued Move message; used to throttle the message rate */
private static Message lastMoveMessage = null;
- /** all instances of Surface which need to be refreshed by the MessageQueue */
+ /** all instances of Surface which need to be refreshed by the Scheduler */
public static Vec allSurfaces = new Vec();
/** When set to true, render() should abort as soon as possible and restart the rendering process */
public static boolean alt = false; ///< true iff the alt button is pressed down, in real time
public static boolean control = false; ///< true iff the control button is pressed down, in real time
public static boolean shift = false; ///< true iff the shift button is pressed down, in real time
- public static boolean button1 = false; ///< true iff button 1 is depressed, in MessageQueue-time
- public static boolean button2 = false; ///< true iff button 2 is depressed, in MessageQueue-time
- public static boolean button3 = false; ///< true iff button 3 is depressed, in MessageQueue-time
+ public static boolean button1 = false; ///< true iff button 1 is depressed, in Scheduler-time
+ public static boolean button2 = false; ///< true iff button 2 is depressed, in Scheduler-time
+ public static boolean button3 = false; ///< true iff button 3 is depressed, in Scheduler-time
public Box root; /**< The Box at the root of this surface */
public String cursor = "default";
- public int mousex; ///< the x position of the mouse, relative to this Surface, in MessageQueue-time
- public int mousey; ///< the y position of the mouse, relative to this Surface, in MessageQueue-time
+ public int mousex; ///< the x position of the mouse, relative to this Surface, in Scheduler-time
+ public int mousey; ///< the y position of the mouse, relative to this Surface, in Scheduler-time
public boolean minimized = false; ///< True iff this surface is minimized, in real time
public boolean maximized = false; ///< True iff this surface is maximized, in real time
else if (button == 2) new SimpleMessage("Press2", Boolean.TRUE, Box.whoIs(root, mousex, mousey));
else if (button == 3) {
final Box who = Box.whoIs(root, mousex, mousey);
- Message.Q.add(new Message() { public void perform() {
+ Scheduler.add(new Message() { public void perform() {
Platform.clipboardReadEnabled = true;
root.put("Press3", Boolean.TRUE);
Platform.clipboardReadEnabled = false;
else if (control) key = "C-" + key;
final String fkey = key;
- Message.Q.add(new KMessage(key));
+ Scheduler.add(new KMessage(key));
}
// This is implemented as a private static class instead of an anonymous class to work around a GCJ bug
- private class KMessage implements Message {
+ private class KMessage extends Message {
String key = null;
public KMessage(String k) { key = k; }
public void perform() {
if (key.toLowerCase().equals("alt")) alt = false;
else if (key.toLowerCase().equals("control")) control = false;
else if (key.toLowerCase().equals("shift")) shift = false;
- Message.Q.add(new Message() { public void perform() {
+ Scheduler.add(new Message() { public void perform() {
/* FIXME
outer: for(int i=0; i<keywatchers.size(); i++) {
Box b = (Box)keywatchers.elementAt(i);
* message), the subclass should use (-1,-1).
*/
protected final void Move(final int newmousex, final int newmousey) {
- Message.Q.add(lastMoveMessage = new Message() { public void perform() {
+ Scheduler.add(lastMoveMessage = new Message() { public void perform() {
synchronized(Surface.this) {
// if move messages are arriving faster than we can process them, we just start ignoring them
}
protected final void SizeChange(final int width, final int height) {
- Message.Q.add(new Message() { public void perform() {
+ Scheduler.add(new Message() { public void perform() {
if (width == root.width && height == root.height) return;
root.needs_reflow = true;
do { abort = false; root.reflow(width, height); } while(abort);
}
protected final void PosChange(final int x, final int y) {
- Message.Q.add(new Message() { public void perform() {
+ Scheduler.add(new Message() { public void perform() {
root.x = x;
root.y = y;
root.put("PosChange", Boolean.TRUE);
protected final void Minimized(boolean b) { minimized = b; new SimpleMessage("Minimized", b ? Boolean.TRUE : Boolean.FALSE, root); }
protected final void Maximized(boolean b) { maximized = b; new SimpleMessage("Maximized", b ? Boolean.TRUE : Boolean.FALSE, root); }
protected final void Focused(boolean b) { new SimpleMessage("Focused", b ? Boolean.TRUE : Boolean.FALSE, root); }
- public static void Refresh() { Message.Q.refresh(); }
+ public static void Refresh() {
+ Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+ for(int i=0; i<allSurfaces.size(); i++)
+ ((Surface)allSurfaces.elementAt(i)).render();
+ return null;
+ }}); }
public final void setMaximized(boolean b) { if (b != maximized) _setMaximized(maximized = b); }
public final void setMinimized(boolean b) { if (b != minimized) _setMinimized(minimized = b); }
}
// FEATURE: reinstate recycler
- public class SimpleMessage implements Message {
+ public class SimpleMessage extends Message {
private Box boxContainingMouse;
private Object value;
this.boxContainingMouse = boxContainingMouse;
this.name = name;
this.value = value;
- Message.Q.add(this);
+ Scheduler.add(this);
}
public void perform() { boxContainingMouse.put(name, value); }
if (staticscript == null) return staticScope;
JS.CompiledFunction temp = staticscript;
staticscript = null;
- temp.call(new JS.Array(), staticScope);
+ new JS.Thread(temp, staticScope).resume();
return staticScope;
}
b.put(b.numChildren(), kid);
}
- if (script != null) script.call(new JS.Array(), pis);
+ if (script != null) new JS.Thread(script, pis).resume();
for(int i=0; keys != null && i<keys.length; i++)
if (vals[i] instanceof String && ((String)vals[i]).charAt(0) == '$') b.put(keys[i], pis.get(vals[i]));
if (super.has(key)) return super.get(key);
if (key.equals("xwt")) return xwt;
if (key.equals("static")) return myStatic;
- if (Box.SpecialBoxProperty.specialBoxProperties.get(key.toString()) != null) return getParentScope().get(key);
- throw new JS.Exn("must declare " + key + " before using it!");
+ return super.get(key);
}
public void put(Object key, Object val) {
if (super.has(key)) super.put(key, val);
- else if (Box.SpecialBoxProperty.specialBoxProperties.get(key.toString()) != null) getParentScope().put(key, val);
- else throw new JS.Exn("must declare " + key + " before using it!");
+ super.put(key, val);
}
}
+++ /dev/null
-// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
-package org.xwt;
-
-import java.util.*;
-import org.xwt.util.*;
-import org.xwt.js.*;
-
-/**
- * A background thread. All threads created with <tt>xwt.thread</tt>
- * are instances of this class. ThreadMessage objects can be enqueued
- * in MessageQueue.
- *
- * INVARIANT: only one thread can be executing JavaScript scripts or
- * accessing Box instances at any given time. For
- * performance reasons, no locks are employed to enforce
- * this invariant.
- */
-public class ThreadMessage extends Thread implements Message {
-
- private volatile static int threadcount = 0;
-
- /** the JavaScript function that we are executing */
- volatile JS.Callable f;
-
- /** the ThreadMessage thread blocks on this before executing any JavaScript */
- Semaphore go = new Semaphore();
-
- /** The Message.Q (main) thread blocks on this while the ThreadMessage thread is running JavaScript code */
- Semaphore done = new Semaphore();
-
- /** used to pool ThreadMessages that are not in use */
- private static Queue spare = new Queue(50);
-
- /** queue of functions waiting to be spawned; used if threadcount == Platform.maxThreads() */
- private static Queue waiting = new Queue(50);
-
- private ThreadMessage() { start(); }
- private static Object[] emptyobj = new Object[] { };
-
- /** creates a new thread to execute function <tt>f</tt> */
- public static synchronized void newthread(JS.Callable f) {
- ThreadMessage ret = (ThreadMessage)spare.remove(false);
- if (ret == null) {
- if (threadcount < Platform.maxThreads()) ret = new ThreadMessage();
- else {
- waiting.append(f);
- return;
- }
- }
- ret.f = f;
- Message.Q.add(ret);
- }
-
- /** attempts to put this thread into the background to perform a blocking operation; returns false if unable to do so */
- public static boolean suspendThread() {
- // put ourselves in the background
- Thread thread = Thread.currentThread();
- if (!(thread instanceof ThreadMessage)) {
- if (Log.on) Log.logJS(ThreadMessage.class, "attempt to perform background-only operation in a foreground thread");
- return false;
- }
- ThreadMessage mythread = (ThreadMessage)thread;
- mythread.setPriority(Thread.MIN_PRIORITY);
- mythread.done.release();
- return true;
- }
-
- /** re-enqueues this thread */
- public static void resumeThread() {
- ThreadMessage mythread = (ThreadMessage)Thread.currentThread();
- Message.Q.add(mythread);
- mythread.setPriority(Thread.NORM_PRIORITY);
- mythread.go.block();
- }
-
- public void run() {
- try {
- threadcount++;
- while (true) {
- try {
- go.block();
- f.call(new JS.Array());
- } catch (JS.Exn e) {
- if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e);
- }
- done.release();
- synchronized(waiting) {
- if (waiting.size() > 0) {
- f = (JS.Callable)waiting.remove(false);
- Message.Q.add(this);
- } else if (spare.size() < 10) {
- spare.append(this);
- } else {
- threadcount--;
- return;
- }
- }
- }
- } catch (Throwable t) {
- if (Log.on) Log.log(this, "caught throwable at thread entry point; this should never happen");
- if (Log.on) Log.log(this, t);
- if (Log.on) Log.log(this, "reaping thread");
- return;
- }
- }
-
- /** this is invoked in the Message.Q thread */
- public void perform() {
- go.release();
- done.block();
- }
-
-}
private Trap() { }
- public Object perform() throws JS.Exn {
+ public Object perform() {
try {
if (f.getNumFormalArgs() > 0) return cascade();
- return f.call(new TrapArgs(this));
+ JS.Thread.current().setTailCall(f, new TrapArgs(this));
+ return null;
} catch (Exception e) {
Log.log(this, "Exception thrown from within trap: " + e);
return null;
}
}
- public void perform(Object val) throws JS.Exn {
+ public void perform(Object val) {
try {
if (f.getNumFormalArgs() == 0) cascade(val);
TrapArgs ta = new TrapArgs(this, val);
- Object ret = f.call(ta);
- if (ret != Boolean.FALSE && !ta.cascadeHappened) cascade(val);
+ JS.Thread.current().setTailCall(f, ta);
+ // FIXME: re-add autocascades
+ //if (ret != Boolean.FALSE && !ta.cascadeHappened) cascade(val);
} catch (Exception e) {
Log.log(this, "Exception thrown from within trap: " + e);
}
}
public Object cascade() {
- if (next != null) return next.perform();
+ if (next != null) { next.perform(); return null; }
return trapee.get(name, true);
}
InputStream is = http.POST("text/xml", content);
try {
BufferedReader br = !Log.verbose ?
- new BufferedReader(new InputStreamReader(new Filter(is))) :
- new BufferedReader(new FilterReader(new InputStreamReader(new Filter(is))) {
+ new BufferedReader(new InputStreamReader(is)) :
+ new BufferedReader(new FilterReader(new InputStreamReader(is)) {
public int read() throws IOException {
int i = super.read();
if (Log.on) Log.log(this, "recv: " + ((char)i));
}
public final Object call(JS.Array args) throws JS.Exn {
-
- if (!ThreadMessage.suspendThread()) return null;
-
try {
return call2(args);
} catch (IOException se) {
} catch (JS.Exn jse) {
if (Log.on) Log.log(this, jse.toString());
throw jse;
- } finally {
- ThreadMessage.resumeThread();
}
-
}
/** When you get a property from an XMLRPC, it just returns another XMLRPC with the property name tacked onto methodname. */
public AccessibleCharArrayWriter(int i) { super(i); }
}
- /** private filter class to make sure that network transfers don't interfere with UI responsiveness */
- private static class Filter extends FilterInputStream {
- public Filter(InputStream is) { super(is); }
- public int read() throws IOException {
- java.lang.Thread.yield();
- while(Message.Q.nonThreadEventsInQueue > 0) try { java.lang.Thread.sleep(100); } catch (Exception e) { };
- return super.read();
- }
- public int read(byte[] b) throws IOException {
- java.lang.Thread.yield();
- while(Message.Q.nonThreadEventsInQueue > 0) try { java.lang.Thread.sleep(100); } catch (Exception e) { };
- return super.read(b);
- }
- public int read(byte[] b, int i, int j) throws IOException {
- java.lang.Thread.yield();
- while(Message.Q.nonThreadEventsInQueue > 0) try { java.lang.Thread.sleep(100); } catch (Exception e) { };
- return super.read(b, i, j);
- }
- }
-
}
else return rr.get(name);
}
- public void put(Object name, Object value) {
- if (name.equals("thread") && value != null && value instanceof JS.Callable) ThreadMessage.newthread((JS.Callable)value);
- else if (name.equals("clipboard")) Platform.setClipBoard(value.toString());
+ public void put(Object name, final Object value) {
+ if (name.equals("thread") && value != null && (value instanceof JS.Callable || value instanceof JS.CompiledFunction)) {
+ Scheduler.add(new Scheduler.Task() { public Object call(Object arg) {
+ new JS.Thread((CompiledFunction)value).resume();
+ return null;
+ } });
+ } else if (name.equals("clipboard")) Platform.setClipBoard(value.toString());
else if (name.equals("frame")) Platform.createSurface((Box)value, true, true);
else if (name.equals("window")) Platform.createSurface((Box)value, false, true);
else if (name.equals("proxyAuthorization")) {
} else if (method.equals("watchProgress")) {
if (checkOnly) return Boolean.TRUE;
- return new Res.ProgressWatcher((Res)args.elementAt(0), (JS.Callable)args.elementAt(1));
+ return new Res.ProgressWatcher((Res)args.elementAt(0), (JS.CompiledFunction)args.elementAt(1));
} else if (method.equals("yield")) {
if (checkOnly) return Boolean.TRUE;
}
}
- public static void sleep(int i) {
- java.lang.Thread thread = java.lang.Thread.currentThread();
- if (!(thread instanceof ThreadMessage)) {
- if (Log.on) Log.log(XWT.class, "cannot sleep() or yield() in the foreground thread");
- } else {
- ThreadMessage mythread = (ThreadMessage)thread;
- mythread.done.release();
- if (i > 0) try { java.lang.Thread.sleep(i); } catch (Exception e) { }
- Message.Q.add(mythread);
- mythread.go.block();
- }
+ public static void sleep(final int i) {
+ final JS.Thread jsthread = JS.Thread.current();
+ final long currentTime = System.currentTimeMillis();
+ final Scheduler.Task task = new Scheduler.Task() { public Object call(Object arg) {
+ if (System.currentTimeMillis() - currentTime < i) {
+ Scheduler.add(this);
+ } else {
+ jsthread.resume();
+ }
+ return null;
+ } };
+ jsthread.pause();
+ Scheduler.add(task);
}
- private static class XWTMath extends JS.Obj {
+ private static class XWTMath extends org.xwt.js.Math {
public XWTMath() {
JS gs = new JS.GlobalScope();
put("isNaN",gs.get("isNaN"));
var new_xwt = xwt.clone(new_rr);
xwt.thread = function() {
-for(var i=0; 100>i; i++) { progress(i, 100); xwt.sleep(100); }
- xwt.yield();
+for(var i=0; 100>i; i++) {
+progress(i, 100);
+xwt.yield();
+}
new_xwt.apply(xwt.box, new_xwt["main.xwt"]);
thisbox = null;
}
}
- <box height="236" align="bottomleft" packed="false" x="20" y="0">
+ <box height="233" align="bottomleft" packed="false" x="20" y="0">
<box textcolor="white" id="text" shrink="true"/>
</box>
<box packed="false" id="bar" x="20" y="236" width="354" height="20" align="left">