Java tutorial
/* Xholon Runtime Framework - executes event-driven & dynamic applications * Copyright (C) 2013 Ken Webb * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.primordion.xholon.service.svg; //import com.google.gwt.canvas.dom.client.CssColor; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Text; //import com.google.gwt.event.dom.client.ContextMenuEvent; //import com.google.gwt.event.dom.client.DragOverHandler; //import com.google.gwt.event.dom.client.DragOverEvent; //import com.google.gwt.event.dom.client.DropHandler; //import com.google.gwt.event.dom.client.DropEvent; //import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; //import com.google.gwt.user.client.ui.MenuBar; //import com.google.gwt.user.client.ui.MenuItem; //import com.google.gwt.user.client.ui.PopupPanel; //import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; //import com.google.gwt.http.client.RequestBuilder.Method; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.Response; //import java.util.ArrayList; import java.util.Date; import java.util.HashMap; //import java.util.Iterator; //import java.util.List; import org.primordion.xholon.app.Application; import org.primordion.xholon.app.IApplication; import org.primordion.xholon.base.IMessage; import org.primordion.xholon.base.ISignal; import org.primordion.xholon.base.IXholon; import org.primordion.xholon.base.Message; import org.primordion.xholon.base.Xholon; import org.primordion.xholon.io.xml.IXml2Xholon; import org.primordion.xholon.io.XholonGuiForHtml5; import org.primordion.xholon.io.xml.Xml2Xholon; import org.primordion.xholon.service.IXholonService; import org.primordion.xholon.service.NodeSelectionService; import org.primordion.xholon.util.Misc; /** * Provides access to an existing Scalable Vector Graphics (SVG) image. * This class does NOT use any third-party SVG Java or JavaScript libraries. * @author <a href="mailto:ken@primordion.com">Ken Webb</a> * @see <a href="http://www.primordion.com/Xholon">Xholon Project website</a> * @since 0.8.1 (Created on August 14, 2013) */ public class SvgViewBrowser extends Xholon implements ISvgView, EventListener { //, DragOverHandler, DropHandler { private static final String DEFAULT_FONTSIZE = "8px"; private static final String SVG_NAMESPACE = "http://www.w3.org/2000/svg"; /** Maximum RGB color value, used to invert a color. */ private static final int RGBMAX = 0xffffff; // I've retained these constants from SVG Salamander public static final int AT_CSS = 0; public static final int AT_XML = 1; public static final int AT_AUTO = 2; //Check CSS first, then XML /** XPath to the root viewables node. */ private IXholon viewablesRoot = null; /** * Model viewables URI. * Correspondence between nodes in the model (the viewables), * and nodes in the SVG view (.xml) (optional) */ private String viewablesUri = null; /** * The SVG image. */ private JavaScriptObject diagram = null; /** * Textual representation of the currently selected node. */ //private JTextField currentSelectionField = null; /** * Whether or not the SVG image has clickable nodes. * If it has clickable nodes, then the JTextField will be displayed. */ private boolean isClickable = true; /** * The width of the Swing JFrame needs to be large enough to contain the SVG diagram, * and the border of the JFrame. */ private int frameWidthInc = 11; /** * The height of the Swing JFrame needs to be large enough to contain the SVG diagram, * and the border of the JFrame. */ private int frameHeightInc = 42; /** * The background color of the Swing JPanel. * This is useful when the SVG image has a transparent background. */ private int backgroundColor = 0xFFFFFF; /** * It's not clear yet whether this will be useful or not. */ private IXholon nodeSelectionService = null; /** * A local or remote SVG image. ex: * <p>"/org/primordion/user/app/climatechange/model04/Sun_climate_system_alternative_(German)_2008.svg"</p> * <p>"file:///home/ken/Documents/ClimateChange/images/Sun_climate_system_alternative_(German)_2008.svg"</p> * <p>"http://upload.wikimedia.org/wikipedia/commons/d/d2/Sun_climate_system_alternative_(German)_2008.svg"</p> */ private String svgUri = null; /** * An optional fallback local or remote SVG image. */ private String svgUriFallback = null; /** * Used as the title of the JFrame that contains the SVG image. */ private String modelName = null; /** * An optional internationalization XML URI. */ private String i18nUri = null; /** * An optional attributes XML URI. ex: * <p>"/org/primordion/user/app/Risk/SvgAttributes.xml"</p> */ protected String svgAttributesUri = null; /** * The default String.format to use when writing text to the SVG image. */ private String format = "%.0f"; /** * Should all numeric text be set to 0 at the start? */ private boolean shouldInitNumericText = false; /** * A service that provides additional methods for IXholon instances. */ protected IXholon xholonHelperService = null; // indexes into the idScheme array protected static final int IX_RECT = 0; protected static final int IX_CIRCLE = 1; protected static final int IX_ELLIPSE = 2; protected static final int IX_LINE = 3; protected static final int IX_POLYGON = 4; protected static final int IX_POLYLINE = 5; protected static final int IX_PATH = 6; protected static final int IX_TEXT = 7; protected static final int IX_TSPAN = 8; protected static final int IX_G = 9; /** * Each SVG tool creates id's for new SVG shapes using it's own naming scheme. * For example, svg-edit id names all start with "svg", while Inkscape uses "rect", "text", "path". * Rect Circle Ellipse Line Polygon Polyline Path Text Tspan */ protected String[] idScheme = { "rect", "path", "path", "path", "path", "path", "path", "text", "tspan", "g" }; // Inkscape //protected String[] idScheme = {"svg","svg","svg","svg","svg","svg","svg","svg","svg"}; // svg-edit /** Indicates whether or not this object has changed. */ protected boolean hasChanged = false; /** Mapping between SVGSalamander classes and SVG tag names. */ final static HashMap class2TagMapping = new HashMap(); /** An instance of SvgClient, or other IXholon node. */ private IXholon client = null; /* * @see org.primordion.xholon.base.Xholon#setVal(boolean) */ public void setVal(boolean val) { hasChanged = val; } public boolean getVal_boolean() { return hasChanged; } public Object getVal_Object() { return diagram; } /* * @see org.primordion.xholon.base.Xholon#act() */ public void act() { super.act(); if (hasChanged) { //svgPanel.repaint(); // GWT hasChanged = false; } } /* * @see org.primordion.xholon.base.Xholon#processReceivedMessage(org.primordion.xholon.base.IMessage) */ public void processReceivedMessage(IMessage msg) { switch (msg.getSignal()) { default: super.processReceivedMessage(msg); } } /* * @see org.primordion.xholon.base.Xholon#processReceivedSyncMessage(org.primordion.xholon.base.IMessage) */ public IMessage processReceivedSyncMessage(IMessage msg) { //System.out.println(msg); Object response = null; switch (msg.getSignal()) { case SIG_SETUP_SVGURI_REQ: setSvgUri((String) msg.getData()); break; case SIG_SETUP_REQ: client = msg.getSender(); //System.out.println("SvgViewBrowser processReceivedSyncMessage( client: " + client); response = setup(msg); break; case SIG_SETUP_SVGATTRIBUTESURI_REQ: setSvgAttributesUri((String) msg.getData()); break; case SIG_DISPLAY_REQ: //display(); break; case SIG_ACT_REQ: act(); break; case SIG_MAKETEXT_REQ: final String[] args = ((String) msg.getData()).split(","); if (args.length > 3) { makeText(args[0], Double.parseDouble(args[1]), Double.parseDouble(args[2]), args[3]); } else if (args.length > 2) { makeText(args[0], Double.parseDouble(args[1]), Double.parseDouble(args[2]), DEFAULT_FONTSIZE); } break; case SIG_STYLE_REQ: style(msg, AT_CSS); break; case SIG_XMLATTR_REQ: style(msg, AT_XML); break; case SIG_ADD_VIEWBEHAVIOR_REQ: addViewBehavior((String) msg.getData()); break; default: return super.processReceivedSyncMessage(msg); } return new Message(SIG_PROCESS_RSP, response, this, msg.getSender()); } /** * Setup the SVG image, by parsing the SVG parameters, loading the image, and initializing it. * @param msg * @return null */ public Object setup(IMessage msg) { String setupStr = (String) msg.getData(); // A comma-separated list of parameters. if (setupStr == null || setupStr.length() == 0) { return null; } final String[] args = setupStr.split(","); if (args.length < 2) { return null; } if (MODELNAME_DEFAULT.equals(args[0])) { setModelName(getApp().getModelName()); } else { setModelName(args[0]); } if (getSvgUri() == null) { if (SVGURI_DEFAULT.equals(args[1])) { String path = getApp().getJavaXhClassName(); path = path.substring(0, path.lastIndexOf('.')); path = path.replaceAll("\\.", "/"); setSvgUri("/" + path + "/default.svg"); } else { setSvgUri(args[1]); } } if (args.length > 2 && args[2].length() > 0) { setSvgUriFallback(args[2]); } if (args.length > 3) { setI18nUri(args[3]); } if (args.length > 4) { setViewablesRoot(getXPath().evaluate(args[4], getApp().getRoot())); } else { setViewablesRoot(getApp().getRoot()); } if (args.length > 5) { setViewablesUri(args[5]); } if (args.length > 6) { setFormat(args[6]); } if (args.length > 7) { setShouldInitNumericText(Boolean.parseBoolean(args[7])); } // if the svgUri starts with SVG_DATA_URI then it's an SVG String if (getSvgUri().startsWith(SVG_DATA_URI)) { String docName = "doc" + new Date().getTime(); diagram = loadSvg(getSvgUri().substring(SVG_DATA_URI.length()), docName); finishSetup(); client.sendSyncMessage(SIG_STATUS_OK_REQ, null, this); } else { loadSvg(svgUri); } return null; } /** * Finish the setup after the SVG diagram has loaded. */ public void finishSetup() { // Note: this Element class is NOT the same Element class declared above as an import // That Element (com.google.gwt.dom.client.Element) is the superclass of this Element com.google.gwt.user.client.Element svg = (com.google.gwt.user.client.Element) diagram.cast(); if (svg == null) { return; } // Fix attributes in the SVG image. initFromXml(this.getSvgAttributesUri()); // Internationalize the text in the SVG image. initFromXml(this.getI18nUri()); if (VIEWABLES_CREATE.equals(this.getViewablesUri())) { // Create SvgViewable nodes by scanning the SVG diagram content. createSvgViewables(svg); //diagram.getRoot()); } else { // Initialize the correspondence between viewable nodes in the model, and SVG nodes. initFromXml(this.getViewablesUri()); } // enable events DOM.sinkEvents(svg, Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONCONTEXTMENU); DOM.sinkBitlessEvent(svg, "dragover"); DOM.sinkBitlessEvent(svg, "drop"); DOM.setEventListener(svg, this); } /** * Load an SVG image from a URI. */ protected void loadSvg(final String uri) { final IXholon this_ = this; final IXholon client_ = client; try { new RequestBuilder(RequestBuilder.GET, uri).sendRequest("", new RequestCallback() { @Override public void onResponseReceived(Request req, Response resp) { if (resp.getStatusCode() == resp.SC_OK) { diagram = loadSvg(resp.getText(), uri); finishSetup(); client_.sendSyncMessage(SIG_STATUS_OK_REQ, null, this_); } else { if (uri.equals(svgUri) && (svgUriFallback != null)) { loadSvg(svgUriFallback); } else { String xmlString = xmlGwtresource2String(uri); if (xmlString == null) { client_.sendSyncMessage(SIG_STATUS_NOT_OK_REQ, resp.getText(), this_); this_.removeChild(); // remove self from the Xholon tree } else { diagram = loadSvg(xmlString, uri); finishSetup(); client_.sendSyncMessage(SIG_STATUS_OK_REQ, null, this_); } } } } @Override public void onError(Request req, Throwable e) { client_.sendSyncMessage(SIG_STATUS_NOT_OK_REQ, e.getMessage(), this_); this_.removeChild(); // remove self from the Xholon tree } }); } catch (RequestException e) { client_.sendSyncMessage(SIG_STATUS_NOT_OK_REQ, e.getMessage(), this_); this_.removeChild(); // remove self from the Xholon tree } } /** * Load an SVG image from a String. * @param svgString A string containing valid SVG content. * @param docName A name that uniquely identifies the document/image. * The id of the HTML element where the SVG content should be inserted. */ protected native JavaScriptObject loadSvg(String svgString, String docName) /*-{ // create a new div element as a child of "xhsvg" div. var div = $doc.getElementById("xhsvg"); if (div) { var newDiv = $doc.createElement("div"); if (newDiv) { newDiv.id = docName; div.appendChild(newDiv); newDiv.innerHTML = svgString; var node = newDiv.firstChild; while (node) { if (node.nodeName == "svg") {break;} node = node.nextSibling; } return node; } } return null; }-*/; /** * Display an already-loaded SVG image. */ //protected void display() { /* GWT if (diagram == null) {return;} createSwingGui(diagram); svgPanel.addMouseListener(this); //svgPanel.addMouseMotionListener(this); if (shouldInitNumericText) { initNumericText(diagram.getRoot()); } */ //} protected String xmlGwtresource2String(String resourceName) { IXml2Xholon xml2Xholon = new Xml2Xholon(); xml2Xholon.setApp(getApp()); return xml2Xholon.xmlGwtresource2String(resourceName); } /** * Create and initialize a node subtree by reading XML. * @param uri The URI of the XML. */ protected void initFromXml(String uri) { if (uri == null || uri.length() == 0) { return; } if (!uri.startsWith("http")) { uri = GWT.getHostPageBaseURL() + uri; } //System.out.println("SvgViewBrowser initFromXml() " + uri); IXml2Xholon xml2Xholon = new Xml2Xholon(); IApplication app = getApp(); xml2Xholon.setApp(getApp()); xml2Xholon.setInheritanceHierarchy(((Application) app).getInherHier()); xml2Xholon.setTreeNodeFactory(((Application) app).getFactory()); xml2Xholon.xmlUri2Xholon(uri, this); IXholon myLastChild = this.getLastChild(); //System.out.println(" myLastChild: " + myLastChild); if (myLastChild == null) { return; } IXholon firstNode = this.findFirstChildWithXhClass(myLastChild.getXhcId()); //System.out.println(" firstNode: " + firstNode); firstNode.postConfigure(); } /** * Create SvgViewable nodes by scanning the SVG content for tspan and text nodes * whose id references a node in the Xholon model. * @param svgElement An instance of an SvgElement subclass. */ protected void createSvgViewables(Object svgElement) { if (svgElement == null) { return; } if (("text".equals(((Element) svgElement).getNodeName())) || ("tspan".equals(((Element) svgElement).getNodeName()))) { String svgId = ((Element) svgElement).getId(); if (svgId != null) { int beginIndex = svgId.indexOf("[@val"); // ex: [@val] or [@val_char] if (beginIndex != -1) { int endIndex = svgId.indexOf("]", beginIndex); String attrName = svgId.substring(beginIndex + 2, endIndex); String formatStr = this.getFormat(); if ("val".equals(attrName)) { //this is a double formatStr = "%.1f"; } else if ("val_char".equals(attrName)) { formatStr = "%c"; } else if ("val_int".equals(attrName)) { formatStr = "%d"; } else if ("val_String".equals(attrName)) { formatStr = "%s"; } SvgViewable svgViewable = (SvgViewable) this.appendChild("SvgViewable", null); svgViewable.setSvgId(((Element) svgElement).getId()); svgViewable.setSvgNode((Element) svgElement); String xhcName = ((Element) svgElement).getId().substring(0, beginIndex); // remove the first location in the String; this is the viewables root beginIndex = xhcName.indexOf('/'); if (beginIndex == -1) { xhcName = "./"; // this is the viewables root } else { xhcName = xhcName.substring(beginIndex + 1); } svgViewable.setXhcName(xhcName); IXholon xhNode = getXPath().evaluate(xhcName, this.getViewablesRoot()); svgViewable.setXhNode(xhNode); svgViewable.setFormat(formatStr); svgViewable.postConfigure(); } } } //System.out.println(((Element)svgElement).getNodeName() + " " + svgElement.getClass().getName()); Element ele = ((Element) svgElement).getFirstChildElement(); while (ele != null) { createSvgViewables(ele); ele = ele.getNextSiblingElement(); } } /** * Add a new view behavior. * @param viewBehavior An XML string, containing a JavaScript behavior. * This is typically an "SvgViewBrowserbehavior". */ protected void addViewBehavior(String viewBehavior) { if (viewBehavior.startsWith("<")) { // this is XML content sendXholonHelperService(ISignal.ACTION_PASTE_LASTCHILD_FROMSTRING, viewBehavior, this); } else { // this is a URI (ex: "config/_commonsvg/stabilityBalls.js") initFromXml(viewBehavior); } } /** * Style a CSS attribute, or set an XML attribute, of an SVG node. * @param msg data: anId,styleName,styleValue * @param attributeType One of the possible AnimationElement.AT_ values. */ protected void style(IMessage msg, int attributeType) { String styleStr = (String) msg.getData(); // A comma-separated list of parameters. if (styleStr == null || styleStr.length() == 0) { return; } final String[] args = styleStr.split(","); if (args.length < 3) { return; } int argIx = 0; while (args.length >= argIx + 3) { style(args[argIx++], args[argIx++], args[argIx++], attributeType); } } /** * * @param anId either a Xholon id or an SVG id * @param styleName * @param styleValue * @param attributeType */ protected void style(String anId, String styleName, String styleValue, int attributeType) { // first look for anId amongst the SvgViewable nodes IXholon node = this.getFirstChild(); while (node != null) { if (anId == ((SvgViewable) node).getXhcName()) { anId = ((SvgViewable) node).getSvgId(); break; } node = node.getNextSibling(); } Element svgElement = DOM.getElementById(anId); if (svgElement != null) { style(svgElement, styleName, styleValue, attributeType); } } /** * Style an SVG node. * @param svgElement The SVG node that should be restyled. * @param styleName The name of the style attribute that will be changed. * ex: "fill" * @param styleValue The new value for the style attribute. * ex: "#ff0000" * @param attributeType One of the possible AnimationElement.AT_ values. */ protected void style(Element svgElement, String styleName, String styleValue, int attributeType) { String val = doFunctions(svgElement, styleName, styleValue, attributeType); if (attributeType == AT_CSS) { svgElement.getStyle().setProperty(styleName, val); } else if (attributeType == AT_XML) { setAttribute(svgElement, styleName, val); } else { // TODO AT_AUTO ? } } /** * A styleValue may include a function. * @param styleValue * ex: "inc(32)" "dec(32)" "32" "inc(123.45)" "inc(-17.48)" */ protected String doFunctions(Element svgElement, String styleName, String styleValue, int attributeType) { if (styleValue.endsWith(")") && styleValue.length() > 5) { String oldVal = ""; if (attributeType == AT_CSS) { oldVal = svgElement.getStyle().getProperty(styleName); } else if (attributeType == AT_XML) { oldVal = getAttribute(svgElement, styleName); } if (styleValue.startsWith("inc(")) { String newValue = sum(oldVal, styleValue.substring(4, styleValue.length() - 1)); if (newValue == null) { return styleValue; } return newValue; } else if (styleValue.startsWith("dec(")) { String newValue = sum(oldVal, "-" + styleValue.substring(4, styleValue.length() - 1)); if (newValue == null) { return styleValue; } return newValue; } else { // the styleValue is an unknown function return styleValue; } } else { // the styleValue is not a function return styleValue; } } /** * Sum two doubles or ints * @param val1 ex: "123.45" "123" "-123.45" "-123" */ protected String sum(String val1, String val2) { try { double d1 = Double.parseDouble(val1); double d2 = Double.parseDouble(val2); double d3 = d1 + d2; return Double.toString(d3); } catch (NumberFormatException e) { return null; } } /** * */ private native void setAttribute(Element ele, String attr, String val) /*-{ if (ele) { ele.setAttributeNS(null, attr, val); } }-*/; /** * */ private native String getAttribute(Element ele, String attr) /*-{ if (ele) { return ele.getAttributeNS(null, attr); } }-*/; /** * Scale the diagram. * @param scale */ protected native void scale(float scale) /*-{ // SVG Salamander code //svgPanel.setScale(scale); //refresh(); // // plain old JavaScript code TODO }-*/; /** * Scale the diagram. * Note: This doesn't seem to have any effect on what is actually displayed. * TODO An image will scale if I manually add a scale() attribute to the top level g. * <pre><g id="layer1" transform="translate(-120.16872,-87.928404) scale(2.0)"></pre> */ protected void scale(double scaleX, double scaleY) { /* SVG Salamander code Group top = (Group)diagram.getElement("g169"); if (top == null) {return;} //println("g169:" + top); try { //println("hasAttribute transform:" + top.hasAttribute("transform", AnimationElement.AT_XML)); Iterator it = top.getPresentationAttributes().iterator(); while (it.hasNext()) { @SuppressWarnings("unused") Object attr = it.next(); //println(attr + " " + attr.getClass()); } //println("hasAttribute scale:" + top.hasAttribute("scale", AnimationElement.AT_XML)); @SuppressWarnings("unused") StyleAttribute styleAttr = top.getPresAbsolute("transform"); //println(styleAttr.getName() + " " + styleAttr.getStringValue()); top.setAttribute("transform", AnimationElement.AT_XML, "scale(" + scaleX + "," + scaleY + ")"); styleAttr = top.getPresAbsolute("transform"); //println(styleAttr.getName() + " " + styleAttr.getStringValue()); } catch (SVGElementException e) { logger.error("", e); } */ } /** * Blank out the values of Tspan elements in a subtree. * @param svgElement The root element in the subtree. */ protected void initNumericText(Object svgElement) { Element ele = (Element) svgElement; if ("tspan".equals(ele.getNodeName())) { String text = ele.getNodeValue(); if (!text.isEmpty() && Misc.isdigit(text.charAt(0) - '0')) { setText(ele, ""); } } Node node = ele.getFirstChild(); while (node != null) { initNumericText(node); node = node.getNextSibling(); } } /** * Set the text of a Tspan node. * @param svgNode A Tspan node. * @param text Some text. */ protected void setText(Element svgNode, String text) { svgNode.getFirstChild().setNodeValue(text); } /** * Make a new Text Tspan subtree, and add it to the root of the SVG tree. * @param str The contents of the new text. * @param x X position of the new text. * @param y Y position of the new text. * @param fontSize Font size of the new text. */ protected void makeText(String str, double x, double y, String fontSize) { // GWT code (innerHTML fails with SVG) if (diagram == null) { return; } Element svg = (Element) diagram.cast(); Element text = createElementNS(SVG_NAMESPACE, "text"); text.setAttribute("x", "" + x); text.setAttribute("y", "" + y); text.setAttribute("font-size", fontSize); Text textNode = createTextNode(str); text.appendChild(textNode); //System.out.println("SvgViewBrowser makeText: " + text); svg.appendChild(text); } private native Element createElementNS(final String ns, final String name)/*-{ return $doc.createElementNS(ns, name); }-*/; private native Text createTextNode(String val)/*-{ return $doc.createTextNode(val); }-*/; /* * @see org.primordion.xholon.base.Xholon#toString() */ public String toString() { return super.toString() + " " + this.getClass().getName() + " " + diagram; } @Override public void onBrowserEvent(Event event) { System.out.println("Received event: " + event.getType()); if (event.getTypeInt() == Event.ONMOUSEDOWN) { mousePressed(event); } else if (event.getTypeInt() == Event.ONMOUSEUP) { mouseReleased(event); } else if (event.getTypeInt() == Event.ONCONTEXTMENU) { //println("Event.ONCONTEXTMENU" + event); handleContextMenuEvent(event); } else if ("dragover".equals(event.getType())) { handleDragOverEvent(event); } else if ("drop".equals(event.getType())) { handleDropEvent(event); } else { //consoleLog(event.getType()); //consoleLog(event.getEventTarget()); //consoleLog(event.getDataTransfer()); } } /*public void onDragOver(DragOverEvent doe) { // this is never called consoleLog("onDragOver SvgViewBrowser"); //handleDragOverEvent(doe); }*/ /** * this is necessary; drop fails to do anything without this ??? */ public void handleDragOverEvent(Event doe) { //consoleLog("DragOverEvent SvgViewBrowser"); //consoleLog(doe); // a MouseEvent, type: "dragover" doe.preventDefault(); } // drag and drop /*public void onDrop(DropEvent de) { // this is never called consoleLog("onDrop SvgViewBrowser"); //handleDropEvent(de); }*/ /** * This works with Google Chrome. */ public void handleDropEvent(Event de) { // Event is a wrapper for (subclass of) NativeEvent consoleLog("DropEvent SvgViewBrowser"); consoleLog(de); // a MouseEvent, type: "drop" consoleLog(de.getDataTransfer()); String data = de.getDataTransfer().getData("text"); consoleLog(data); consoleLog(de.getEventTarget()); Element ele = Element.as(de.getEventTarget()); consoleLog(ele); de.stopPropagation(); de.preventDefault(); if (data == null) { return; } // handleDrop; paste data into node String svgNodeName = ele.getNodeName(); String svgElementId = ele.getId(); consoleLog(svgElementId); if (svgElementId == null) { return; } if (!svgElementId.startsWith(findIdScheme(svgNodeName))) { // the svgElementId is an XPath expression; use it to find the IXholon IXholon node = findXholonNode(svgElementId); if (node != null) { consoleLog(node); // assume this is XML content sendXholonHelperService(ISignal.ACTION_PASTE_LASTCHILD_FROMDROP, data, node); // do not refresh the SVG content } } } protected void handleContextMenuEvent(Event cme) { // allow the browser's default context menu if (cme.getShiftKey()) { return; } cme.preventDefault(); cme.stopPropagation(); Element ele = Element.as(((NativeEvent) cme).getEventTarget()); String nodeName = ele.getNodeName(); String svgElementId = ele.getId(); if ((svgElementId == null) || ("".equals(svgElementId))) { Element parentEle = ele.getParentElement(); if ("g".equals(parentEle.getNodeName())) { // Graphviz creates the id on the SVG "g" parent element svgElementId = parentEle.getId(); if ((svgElementId == null) || ("".equals(svgElementId))) { return; } } else { return; } } if (!svgElementId.startsWith(findIdScheme(nodeName))) { IXholon node = findXholonNode(svgElementId); if (node != null) { int y = ele.getAbsoluteTop(); new XholonGuiForHtml5().makeContextMenu(node, 0, y); } } } /** * Show a popup menu for a specified node. * @param node The IXholon that the popup menu is for. */ /*protected void showPopupMenu(final IXholon node, int x, int y) { PopupPanel popup = new PopupPanel(true); popup.hide(); MenuBar menu = new MenuBar(); // Special String[] specialItems = node.getActionList(); //System.out.println(specialItems); if (specialItems != null) { MenuBar specialSubMenu = new MenuBar(true); for (int i = 0; i < specialItems.length; i++) { final String actionName = specialItems[i]; specialSubMenu.addItem(specialItems[i], new Command() { @Override public void execute() { node.doAction(actionName); } }); } menu.addItem("Special", specialSubMenu); } // Edit MenuBar editSubMenu = new MenuBar(true); editSubMenu.addItem("Copy", new Command() { public void execute() { sendXholonHelperService(ISignal.ACTION_COPY_TOCLIPBOARD, node, null); } }); editSubMenu.addItem("Cut", new Command() { public void execute() { sendXholonHelperService(ISignal.ACTION_CUT_TOCLIPBOARD, node, null); } }).setEnabled(false); editSubMenu.addItem("Paste Last Child", new Command() { public void execute() { sendXholonHelperService(ISignal.ACTION_PASTE_LASTCHILD_FROMCLIPBOARD, node, null); } }); menu.addItem("Edit", editSubMenu); popup.add(menu); popup.setPopupPosition(x, y); popup.show(); }*/ protected void mousePressed(Event event) { //consoleLog("mousePressed"); Element ele = Element.as(((NativeEvent) event).getEventTarget()); //System.out.println(" ONMOUSEDOWN:" + ele.getNodeName() + " " + ele.getId()); //println(" ONMOUSEDOWN:" + ele.getNodeName() + " " + ele.getId()); String nodeName = ele.getNodeName(); String svgElementId = ele.getId(); String invertAttr = "stroke"; if (("text".equals(nodeName)) || ("tspan".equals(nodeName))) { invertAttr = "fill"; } if (svgElementId == null) { return; } if (!svgElementId.startsWith(findIdScheme(nodeName))) { invertStyle(ele, invertAttr); return; } } protected void mouseReleased(Event event) { //consoleLog("mouseReleased"); Element ele = Element.as(((NativeEvent) event).getEventTarget()); //consoleLog(ele); String nodeName = ele.getNodeName(); String svgElementId = ele.getId(); String invertAttr = "stroke"; if (("text".equals(nodeName)) || ("tspan".equals(nodeName))) { invertAttr = "fill"; } //consoleLog("|" + svgElementId + "|"); if ((svgElementId == null) || ("".equals(svgElementId))) { Element parentEle = ele.getParentElement(); //consoleLog("svgElementId == null 1"); //consoleLog(parentEle); //consoleLog(parentEle.getNodeName()); if ("g".equals(parentEle.getNodeName())) { // Graphviz creates the id on the SVG "g" parent element svgElementId = parentEle.getId(); //consoleLog(svgElementId); if ((svgElementId == null) || ("".equals(svgElementId))) { return; } } else { return; } } if (!svgElementId.startsWith(findIdScheme(nodeName))) { //consoleLog("!svgElementId.startsWith(findIdScheme(nodeName))"); invertStyle(ele, invertAttr); IXholon node = findXholonNode(svgElementId); if (node != null) { if ((event.getButton() & Event.BUTTON_RIGHT) == Event.BUTTON_RIGHT) { //sendXholonHelperService(ISignal.ACTION_START_XHOLON_CONSOLE, node, null); } else { rememberNodeSelection(node, event.getCtrlKey()); //println(node.handleNodeSelection()); println(this.handleNodeSelection(node, node.getName())); } } return; } } public String handleNodeSelection(IXholon node, String nodeName) { return getApp().handleNodeSelection(node, nodeName); } protected String findIdScheme(String nodeName) { if ("rect".equals(nodeName)) { return idScheme[IX_RECT]; } if ("path".equals(nodeName)) { return idScheme[IX_PATH]; } if ("text".equals(nodeName)) { return idScheme[IX_TEXT]; } if ("tspan".equals(nodeName)) { return idScheme[IX_TSPAN]; } if ("circle".equals(nodeName)) { return idScheme[IX_CIRCLE]; } if ("ellipse".equals(nodeName)) { return idScheme[IX_ELLIPSE]; } if ("line".equals(nodeName)) { return idScheme[IX_LINE]; } if ("polygon".equals(nodeName)) { return idScheme[IX_POLYGON]; } if ("polyline".equals(nodeName)) { return idScheme[IX_POLYLINE]; } if ("g".equals(nodeName)) { return idScheme[IX_G]; } return "svg"; } /** * Remember which IXholon node(s) are currently selected. * This will allow services or other xholons to act on those nodes. * @param svgElementId ex: World/Europe/Iceland * @param isControlDown Whether or not the Control key is pressed down. */ protected void rememberNodeSelection(IXholon node, boolean isControlDown) { if (node != null) { IXholon[] nodeArray = { node }; if (nodeSelectionService == null) { nodeSelectionService = getService(IXholonService.XHSRV_NODE_SELECTION); } if (nodeSelectionService != null) { if (isControlDown) { // mouse Ctrl-click // append the new selection to what has already been remembered nodeSelectionService.sendSystemMessage(NodeSelectionService.SIG_APPEND_SELECTED_NODES_REQ, nodeArray, this); } else { // mouse click // the new selection should replace what was previously remembered nodeSelectionService.sendSystemMessage(NodeSelectionService.SIG_REMEMBER_SELECTED_NODES_REQ, nodeArray, this); } } } } /** * Find an IXholon node. * TODO just use getXPath().evaluate("descendant::" + svgElementId, getApp().getRoot().getParentNode()); * @param svgElementId * @return */ protected IXholon findXholonNode(String svgElementId) { /*IXholon node = getXPath().evaluate("/" + svgElementId, viewablesRoot); if (node == null) { // svgElementId may already include the root node node = getXPath().evaluate(svgElementId, viewablesRoot.getParentNode()); } if (node == null) { // svgElementId may be a descendant that's not directly a child node = getXPath().evaluate("descendant::" + svgElementId, getApp().getRoot()); } return node;*/ IXholon theRootNode = getApp().getRoot(); IXholon node = getXPath().evaluate("descendant::" + svgElementId, theRootNode); if (node == null) { if (theRootNode.getXhcName().equals(svgElementId)) { node = theRootNode; } } if (node == null) { // svgElementId may already include the root node node = getXPath().evaluate(svgElementId, viewablesRoot.getParentNode()); } return node; } /** * Invert the color of the fill or stroke attribute of a style. * @param element * @param attrName "fill" or "stroke" */ protected void invertStyle(Element element, String attrName) { String str = element.getStyle().getProperty(attrName); System.out.println("invertStyle: " + attrName + " str:" + str); if ((str.length() > 1)) { style(element, attrName, invertColor(str), AT_CSS); } else { str = getAttribute(element, attrName); if ((str != null) && (str.length() > 1)) { style(element, attrName, invertColor(str), AT_XML); } } } /** * Invert a color. * There are 256*256*256 = 16777216 (1000000 hex) possible color values. * There are 3 steps to this process: * (1) separate the 3 rgb values from rgb or hex format * (2) invert the 3 values * (3) reassemble into a single hex string * @param color An RGB hex color value (ex: #7f7f7f #000000 #ffffff). * @return An RGB hex color value (ex: #808080 #ffffff #000000). */ protected String invertColor(String color) { if (color.charAt(0) == '#') { return reassemble(invert(separateValuesHex(color))); } else if (color.startsWith("rgb(") && color.endsWith(")")) { return reassemble(invert(separateValuesRgb(color))); } return color; } /* * Invert a color, step 1 rgb (ex: "rgb(161, 33, 241)") */ protected int[] separateValuesRgb(String rgb) { int[] arr = new int[3]; String[] vals = rgb.substring(4, rgb.length() - 1).split(","); arr[0] = Integer.parseInt(vals[0].trim()); arr[1] = Integer.parseInt(vals[1].trim()); arr[2] = Integer.parseInt(vals[2].trim()); return arr; } /* * Invert a color, step 1 hex (ex: "#059AF3") */ protected int[] separateValuesHex(String hex) { int[] arr = new int[3]; arr[0] = Integer.parseInt(hex.substring(1, 3), 16); arr[1] = Integer.parseInt(hex.substring(3, 5), 16); arr[2] = Integer.parseInt(hex.substring(5), 16); return arr; } /* * Invert a color, step 2 */ protected int[] invert(int[] arr) { arr[0] = 255 - arr[0]; arr[1] = 255 - arr[1]; arr[2] = 255 - arr[2]; return arr; } /* * Invert a color, step 3 */ protected String reassemble(int[] arr) { String hex = "#" + (arr[0] < 16 ? "0" : "") + Integer.toHexString(arr[0]) + (arr[1] < 16 ? "0" : "") + Integer.toHexString(arr[1]) + (arr[2] < 16 ? "0" : "") + Integer.toHexString(arr[2]); return hex; } /** * Send a synchronous message to the XholonHelperService. * @param signal * @param data * @param sender * @return */ protected IMessage sendXholonHelperService(int signal, Object data, IXholon sender) { // send the request to the XholonHelperService by sending it a sync message if (xholonHelperService == null) { xholonHelperService = getApp().getService(IXholonService.XHSRV_XHOLON_HELPER); } if (xholonHelperService == null) { return null; } else { if (sender == null) { sender = getApp(); } return xholonHelperService.sendSyncMessage(signal, data, sender); } } /* * @see org.primordion.xholon.base.Xholon#println(java.lang.Object) */ //public void println(Object obj) { // GWT //if (currentSelectionField != null) { // currentSelectionField.setText((String)obj); //} //super.println(obj); //} /** An action. Create and display an XML structure. */ private static final String makeSvgViewables = "Make Svg viewables"; /** An action. This can be useful while editing the image using Inkscape, svg-edit, etc. */ private static final String reloadSvgImage = "Reload SVG image"; /** An action. Show the current content in SVG format. */ private static final String showSvg = "Show SVG"; /** Action list. */ private String[] actions = { makeSvgViewables, reloadSvgImage, showSvg }; /* * @see org.primordion.xholon.base.IXholon#getActionList() */ public String[] getActionList() { return actions; } /* * @see org.primordion.xholon.base.Xholon#setActionList(java.lang.String[]) */ public void setActionList(String[] actionList) { actions = actionList; } /* * @see org.primordion.xholon.base.IXholon#doAction(java.lang.String) */ public void doAction(String action) { System.out.println("ACTION: " + action); if (action == null) { return; } if (action.equals(makeSvgViewables)) { StringBuilder sb = new StringBuilder(); writeSvgViewables(sb); System.out.println(sb.toString()); println(sb.toString()); } else if (action.equals(reloadSvgImage)) { System.out.println(action + " not implemented"); //reloadSvgImage(); } else if (action.equals(showSvg)) { showSvg(); } } /** * Show the current content in SVG format. * @param out The stream to write the XML to. */ /* SVG Salamander code protected void showSvg(PrintStream out) { out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); showSvgRecurse(diagram.getRoot(), out); }*/ protected void showSvg() { System.out.println("showSvg"); System.out.println(diagram); println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); println(diagram.toString()); } /** * Show the SVG content, by recursively working through the internal structure. * @param svgElement An element that may be a Tspan or Text. * @param out The stream to write the XML to. */ /* GWT @SuppressWarnings("unchecked") protected void showSvgRecurse(SVGElement svgElement, PrintStream out) { String tagName = getSvgTagName(svgElement); if (tagName == null) {return;} StringBuilder sb = new StringBuilder().append("<").append(tagName); // Non-style attributes (ex: cx="30" cy="80" rx="10" ry="20" style="..."). // These are also called "presentation attributes", and "AT_XML". // Iterator attrIt = svgElement.getPresentationAttributes().iterator(); while (attrIt.hasNext()) { String attrKey = (String)attrIt.next(); StyleAttribute attr = svgElement.getPresAbsolute(attrKey); sb.append(" ").append(attr.getName()).append("=\"").append(attr.getStringValue()).append("\""); } if ("svg".equals(tagName)) { sb.append(" xmlns=\"").append(SVGElement.SVG_NS).append("\""); // xmlns="http://www.w3.org/2000/svg" sb.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\""); sb.append(" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\""); sb.append(" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\""); } out.print(sb.append(">")); if ("text".equals(tagName)) { // content consists of Tspan and String List textList = ((Text)svgElement).getContent(); for (int i = 0; i < textList.size(); i++) { Object obj = textList.get(i); if (obj instanceof String) { out.print(normalizeXmlContent((String)obj)); out.print("\n"); } else { out.print("\n"); showSvgRecurse((SVGElement)obj, out); } // TODO does Text have children that are not part of the content ? Text children ? } out.print(new StringBuilder().append("</").append(tagName).append(">\n")); return; } else if ("tspan".equals(tagName)) { out.print(normalizeXmlContent(((Tspan)svgElement).getText())); } out.print("\n"); Iterator childIt = svgElement.getChildren(null).iterator(); // handle children while (childIt.hasNext()) { showSvgRecurse((SVGElement)childIt.next(), out); } out.print(new StringBuilder().append("</").append(tagName).append(">\n")); }*/ /** * Reload the SVG image. * It's assumed that each child SvgViewable node still has a valid svgId. * The only editing that should be done to an image that will be reloaded, * is moving existing SVG nodes, changing their attributes and styles, * adding new elements that are unrelated to the Xholon model. * Do Not: * - ungroup and regroup clickable/viewable elements (go inside existing groups instead) * - */ /* GWT protected void reloadSvgImage() { try { internalDocUri = SVGCache.getSVGUniverse().loadSVG(internalDocUri.toURL(), true); } catch (MalformedURLException e) { e.printStackTrace(); } diagram = SVGCache.getSVGUniverse().getDiagram(internalDocUri, false); svgPanel.setDiagram(diagram); // invoke all the SvgViewables so they can recreate their svgNode attributes IXholon node = this.getFirstChild(); if (node != null) { node.reconfigure(); } svgPanel.repaint(); // is this required ? }*/ /** * Write potential SVG viewables to a StringBuilder, in XML format. * This can be edited, saved in an XML file (SvgViewables.xml), * and used as the input to the SvgViewable class. * It only writes Tspan nodes, and Text nodes that directly contain some text. * @param sb The StringBuilder to write the XML to. */ protected void writeSvgViewables(StringBuilder sb) { if (diagram == null) { return; } Element svg = (Element) diagram.cast(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").append("<_-.SvgViewables>\n"); writeSvgViewablesRecurse(svg, sb); sb.append("").append("</_-.SvgViewables>\n"); } /** * Recursively write SVG viewables to a StringBuilder. ex: * <p> <SvgViewable xhcName="some text" svgId="tspan3463"/></p> * @param svgElement An element that may be a Tspan or Text. * @param out The StringBuilder to write the XML to. */ protected void writeSvgViewablesRecurse(Element ele, StringBuilder sb) { if ("tspan".equals(ele.getNodeName())) { sb.append(" <SvgViewable xhcName=\"" + ele.getFirstChild().getNodeValue().trim() + "\" svgId=\"" + ele.getId().trim() + "\"/>\n"); } else if ("text".equals(ele.getNodeName())) { // look for TextNode nodes that are direct children of the SVG text node String text = ""; Node node = ele.getFirstChild(); while (node != null) { if (node.getNodeType() == Node.TEXT_NODE) { text += node.getNodeValue(); } node = node.getNextSibling(); } text = text.trim(); if (text.length() > 0) { sb.append(" <SvgViewable xhcName=\"" + text + "\" svgId=\"" + ele.getId().trim() + "\"/>\n"); } } Element node = (Element) ele.getFirstChildElement(); while (node != null) { writeSvgViewablesRecurse(node, sb); node = (Element) node.getNextSiblingElement(); } } /** * Normalize the content of the input String. * XML content may not contain the * < or & characters. * @param content The content that will appear between an XML start and end tag. * @return Valid XML content. */ protected String normalizeXmlContent(String content) { String newContent; newContent = content.replaceAll("&", "&"); newContent = newContent.replaceAll("<", "<"); newContent = newContent.replaceAll(">", ">"); return newContent; } public IXholon getViewablesRoot() { return viewablesRoot; } public void setViewablesRoot(IXholon viewablesRoot) { this.viewablesRoot = viewablesRoot; } public String getViewablesUri() { return viewablesUri; } public void setViewablesUri(String viewablesUri) { this.viewablesUri = viewablesUri; } public String getSvgUriFallback() { return svgUriFallback; } public void setSvgUriFallback(String svgUri) { if (svgUri == null || svgUri.length() == 0) { return; } if (svgUri.charAt(0) == '/') { //this.svgUriFallback = "file://" + new java.io.File("bin" + svgUri).getAbsolutePath(); // GWT } else { this.svgUriFallback = svgUri; } } public String getSvgUri() { return svgUri; } public void setSvgUri(String svgUri) { if (svgUri == null || svgUri.length() == 0) { return; } if (svgUri.charAt(0) == '/') { //this.svgUri = "file://" + new java.io.File("bin" + svgUri).getAbsolutePath(); } else { this.svgUri = svgUri; } } public String getSvgAttributesUri() { return svgAttributesUri; } public void setSvgAttributesUri(String svgAttributesUri) { this.svgAttributesUri = svgAttributesUri; } public String getModelName() { return modelName; } public void setModelName(String modelName) { this.modelName = modelName; } public String getI18nUri() { return i18nUri; } public void setI18nUri(String i18nUri) { if (i18nUri == null || i18nUri.length() == 0) { return; } this.i18nUri = i18nUri; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public boolean isShouldInitNumericText() { return shouldInitNumericText; } public void setShouldInitNumericText(boolean shouldInitNumericText) { this.shouldInitNumericText = shouldInitNumericText; } public JavaScriptObject getDiagram() { return diagram; } public void setDiagram(JavaScriptObject diagram) { this.diagram = diagram; } }