/** * JSItem.java * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.expeditee.items; import java.awt.Graphics2D; import java.awt.Polygon; import java.util.LinkedList; import java.util.List; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import org.expeditee.gui.DisplayIO; import org.expeditee.items.widgets.InteractiveWidget; public class JSItem extends XRayable implements JSThreadable { private static ScriptEngineManager scriptEngineManager = null; private static ScriptEngine globalScriptEngine = null; private static Object global = null; // a method to run that will set up and return the root JComponent for this Widget private final String init; // a method to run to populate a List with our state private final String save; // a method to run that will load state from a String[] private final String load; // a method to run that will paint our item using a Graphics2D object private final String paint; // we have our own script context since it needs to have some global variables which are specific to each widget private final ScriptEngine scriptEngine; private final Invocable invocable; private int _width, _height; static { try { scriptEngineManager = new ScriptEngineManager(); globalScriptEngine = scriptEngineManager.getEngineByMimeType("application/javascript"); global = globalScriptEngine.eval("new Object()"); } catch (ScriptException e) { e.printStackTrace(); } } private static Text getSauce(int width, int height) { Text source = new Text(DisplayIO.getCurrentFrame().getNextItemID(), "@js: " + width + " " + height); source.setParent(DisplayIO.getCurrentFrame()); return source; } private JSItem(Text source, String init, String save, String load, String paint) throws Exception { super(source); this.init = init; this.save = save; this.load = load; this.paint = paint; this.scriptEngine = scriptEngineManager.getEngineByMimeType("application/javascript"); this.invocable = (Invocable) this.scriptEngine; this.scriptEngine.put("global", global); this.scriptEngine.put("invocable", this.invocable); this.scriptEngine.put("item", this); this.scriptEngine.put("source", this._source); this.parseSource(); System.out.println(this.init); this.scriptEngine.eval("var init = " + this.init + "\ninit()"); this.scriptEngine.eval("save = " + this.save); this.scriptEngine.eval("paint = " + this.paint); this.updateSource(); } public JSItem(Text source) throws Exception { this(source, source.getData().get(0).replaceAll("\\\\n", "\n"), source.getData().get(1).replaceAll("\\\\n", "\n"), source.getData().get(2).replaceAll("\\\\n", "\n"), source.getData().get(3).replaceAll("\\\\n", "\n")); } public JSItem(int width, int height, String init, String save, String load, String paint) throws Exception { this(getSauce(width, height), init, save, load, paint); } public JSItem(String init, String save, String load, String paint) throws Exception { this(100, 100, init, save, load, paint); } @Override public Item copy() { try { return new JSItem(_source.copy()); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void paint(Graphics2D g) { try { this.invocable.invokeFunction("paint", (Object)g); } catch (Exception e) { e.printStackTrace(); } } @Override public void setAnnotation(boolean val) { // TODO Auto-generated method stub } @Override public void updatePolygon() { _poly = new Polygon(); _poly.addPoint(getX(), getY()); _poly.addPoint(getX() + _width, getY()); _poly.addPoint(getX() + _width, getY() + _height); _poly.addPoint(getX(), getY() + _height); } private void parseSource() { String text = _source.getText(); text = text.replaceFirst("@js:", ""); text = text.replaceAll("\n", ""); text = text.trim(); int index = text.indexOf(':'); String[] values; if(index != -1) { values = text.substring(0, index).split("\\s+"); } else { values = text.split("\\s+"); } if(values.length >= 2) { _width = Integer.parseInt(values[0]); _height = Integer.parseInt(values[1]); } if(index == -1) { return; } String[] args = InteractiveWidget.parseArgs(text.substring(index)); try { this.scriptEngine.eval("load = " + this.load); this.invocable.invokeFunction("load", (Object) args); } catch (Exception e) { e.printStackTrace(); } } private String[] saveArgs() { try { List args = new LinkedList(); this.invocable.invokeFunction("save", (Object)args); return args.toArray(new String[0]); } catch (Exception e) { e.printStackTrace(); } return null; } private void updateSource() { List data = new LinkedList(); data.add(this.init.replaceAll("[\n\r]", "\\\\n")); data.add(this.save.replaceAll("[\n\r]", "\\\\n")); data.add(this.load.replaceAll("[\n\r]", "\\\\n")); data.add(this.paint.replaceAll("[\n\r]", "\\\\n")); _source.setData(data); StringBuffer newText = new StringBuffer("@js: "); newText.append(_width).append(" ").append(_height); String stateArgs = InteractiveWidget.formatArgs(saveArgs()); if (stateArgs != null) { newText.append(':'); newText.append(stateArgs); } _source.setText(newText.toString()); } @Override public Integer getWidth() { return this._width; } @Override public int getHeight() { return this._height; } private List threads = new LinkedList(); public JSThread addThread(String code) { JSThread t = new JSThread(scriptEngine, code); this.threads.add(t); return t; } @Override public void onParentStateChanged(ItemParentStateChangedEvent e) { switch (e.getEventType()) { case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED: case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY: case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN: for(JSThread t : this.threads) { t.kill(); } break; case ItemParentStateChangedEvent.EVENT_TYPE_ADDED: case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY: case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN: case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY: for(JSThread t : this.threads) { t.resume(); } break; } } }