/* * Copyright 2008-2009 Oliver Zoran * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.iplantc.phyloviewer.client.tree.viewer.canvas.impl.ie; import java.util.Stack; import org.iplantc.phyloviewer.client.tree.viewer.canvas.Canvas; import org.iplantc.phyloviewer.client.tree.viewer.canvas.Gradient; import org.iplantc.phyloviewer.client.tree.viewer.canvas.impl.CanvasImpl; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; /** * The Internet Explorer implementation of the canvas widget. * * @see http://msdn2.microsoft.com/en-us/library/bb250524(VS.85).aspx * @see http://en.wikipedia.org/wiki/Transformation_matrix * @see http://code.google.com/p/explorercanvas/ */ public class CanvasImplIE extends CanvasImpl { ///////////////////////////////////////////////////////////////// // STATIC CONSTANTS AND METHODS ///////////////////////////////////////////////////////////////// public final static String SOURCE_OVER = "beforeEnd"; public final static String DESTINATION_OVER = "afterBegin"; public final static String BUTT = "flat"; public static native int floor(double value) /*-{ return (value | 0); }-*/; ///////////////////////////////////////////////////////////////// // PRIVATE/PROTECTED MEMBERS/METHODS ///////////////////////////////////////////////////////////////// private Stack<VMLContext> contextStack = new Stack<VMLContext>(); private JSOStack<String> pathStack = JSOStack.create(); private JSOStack<String> scratchStack = JSOStack.create(); private VMLContext context; private double[] matrix; private double currentX = 0.0; private double currentY = 0.0; private int getCoordX(double x, double y) { int coordX = floor(10 * (matrix[0] * x + matrix[3] * y + matrix[6]) - 4.5); // TODO update min-max x-values return coordX; } private int getCoordY(double x, double y) { int coordY = floor(10 * (matrix[1] * x + matrix[4] * y + matrix[7]) - 4.5); // TODO update min-max y-values return coordY; } private native void insert(String where, String html) /*-{ this.@org.iplantc.phyloviewer.client.tree.viewer.canvas.impl.CanvasImpl::element.insertAdjacentHTML(where, html); }-*/; // TODO investigate further // protected native void cancelSelections() /*-{ // try { // $doc.selection.empty(); // } catch (e) { // // do nothing // } // }-*/; protected native void init() /*-{ if (!$doc.namespaces["v"]) { $doc.namespaces.add("v", "urn:schemas-microsoft-com:vml"); $doc.createStyleSheet().cssText = "v\\:*{behavior:url(#default#VML);}"; } }-*/; ///////////////////////////////////////////////////////////////// // CONSTRUCTORS AND PUBLIC METHODS ///////////////////////////////////////////////////////////////// public void init(Element element) { this.element = element; DOM.setStyleAttribute(element, "position", "relative"); DOM.setStyleAttribute(element, "display", "inline-block"); DOM.setStyleAttribute(element, "overflow", "hidden"); DOM.setStyleAttribute(element, "textAlign", "left"); DOM.setStyleAttribute(element, "cursor", "default"); init(); context = new VMLContext(); matrix = context.matrix; } public void setWidth(String width) { DOM.setInnerHTML(element, ""); DOM.setStyleAttribute(element, "width", width); } public void setHeight(String height) { DOM.setInnerHTML(element, ""); DOM.setStyleAttribute(element, "height", height); } // public void onMouseDown(Event event) { // cancelSelections(); // DOM.setCapture(element); // } // public void onMouseUp() { // DOM.releaseCapture(element); // } ///////////////////////////////////////////////////////////////// // CANVAS STATE METHODS ///////////////////////////////////////////////////////////////// public void restore() { if (!contextStack.isEmpty()) { context = contextStack.pop(); matrix = context.matrix; } } public void save() { contextStack.push(context); context = new VMLContext(context); matrix = context.matrix; } public void rotate(double angle) { double s = Math.sin(angle); double c = Math.cos(angle); double a = matrix[0]; double b = matrix[3]; matrix[0] = a * c + b * s; matrix[3] = -a * s + b * c; a = matrix[1]; b = matrix[4]; matrix[1] = a * c + b * s; matrix[4] = -a * s + b * c; } public void scale(double x, double y) { context.arcScaleX *= x; context.arcScaleY *= y; matrix[0] *= x; matrix[1] *= x; matrix[3] *= y; matrix[4] *= y; } public void translate(double x, double y) { matrix[6] += matrix[0] * x + matrix[3] * y; matrix[7] += matrix[1] * x + matrix[4] * y; } public void transform(double m11, double m12, double m21, double m22, double dx, double dy) { double a = matrix[0]; double b = matrix[3]; matrix[0] = a * m11 + b * m12; matrix[3] = a * m21 + b * m22; matrix[6] += a * dx + b * dy; a = matrix[1]; b = matrix[4]; matrix[1] = a * m11 + b * m12; matrix[4] = a * m21 + b * m22; matrix[7] += a * dx + b * dy; } public void setTransform(double m11, double m12, double m21, double m22, double dx, double dy) { matrix[0] = m11; matrix[1] = m12; matrix[3] = m21; matrix[4] = m22; matrix[6] = dx; matrix[7] = dy; } ///////////////////////////////////////////////////////////////// // WORKING WITH PATHS ///////////////////////////////////////////////////////////////// public void arc(double x, double y, double radius, double startAngle, double endAngle, boolean antiClockwise) { double ar = radius * 10; double startX = x + Math.cos(startAngle) * ar - 5; double startY = y + Math.sin(startAngle) * ar - 5; double endX = x + Math.cos(endAngle) * ar - 5; double endY = y + Math.sin(endAngle) * ar - 5; if (startX == endX && !antiClockwise) { startX += 0.125; } int cx = getCoordX(x, y); int cy = getCoordY(x, y); double arcX = context.arcScaleX * ar; double arcY = context.arcScaleY * ar; if (antiClockwise) { pathStack.push(" at " + floor(cx - arcX + 0.5)); } else { pathStack.push(" wa " + floor(cx - arcX + 0.5)); } pathStack.push("," + floor(cy - arcY + 0.5)); pathStack.push(" " + floor(cx + arcX + 0.5)); pathStack.push("," + floor(cy + arcY + 0.5)); pathStack.push(" " + getCoordX(startX, startY)); pathStack.push("," + getCoordY(startX, startY)); pathStack.push(" " + getCoordX(endX, endY)); pathStack.push("," + getCoordY(endX, endY)); } public void cubicCurveTo(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y) { pathStack.push(" c " + getCoordX(cp1x, cp1y)); pathStack.push("," + getCoordY(cp1x, cp1y)); pathStack.push("," + getCoordX(cp2x, cp2y)); pathStack.push("," + getCoordY(cp2x, cp2y)); pathStack.push("," + getCoordX(x, y)); pathStack.push("," + getCoordY(x, y)); currentX = x; currentY = y; } public void quadraticCurveTo(double cpx, double cpy, double x, double y) { double cp1x = currentX + 2.0 / 3.0 * (cpx - currentX); double cp1y = currentY + 2.0 / 3.0 * (cpy - currentY); double cp2x = cp1x + (x - currentX) / 3.0; double cp2y = cp1y + (y - currentY) / 3.0; cubicCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); } public void beginPath() { pathStack.clear(); } public void closePath() { pathStack.push(" x"); } public void moveTo(double x, double y) { pathStack.push(" m " + getCoordX(x, y) + "," + getCoordY(x, y)); currentX = x; currentY = y; } public void lineTo(double x, double y) { pathStack.push(" l " + getCoordX(x, y) + "," + getCoordY(x, y)); currentX = x; currentY = y; } public void rect(double x, double y, double w, double h) { w += x; h += y; pathStack.push(" m " + getCoordX(x, y)); pathStack.push("," + getCoordY(x, y)); pathStack.push(" l " + getCoordX(w, y)); pathStack.push("," + getCoordY(x, y)); pathStack.push(" l " + getCoordX(w, y)); pathStack.push("," + getCoordY(x, h)); pathStack.push(" l " + getCoordX(x, y)); pathStack.push("," + getCoordY(x, h)); pathStack.push(" x"); currentX = x; currentY = h; } ///////////////////////////////////////////////////////////////// // STROKING AND FILLING ///////////////////////////////////////////////////////////////// public void clear() { // TODO reset min-max values pathStack.clear(); DOM.setInnerHTML(element, ""); } public void fill() { if (pathStack.isEmpty()) { return; } scratchStack.clear(); scratchStack.push("<v:shape style=\"position:absolute;width:10;height:10;\" coordsize=\"100,100\" fillcolor=\""); scratchStack.push(context.fillStyle); scratchStack.push("\" stroked=\"f\" path=\""); scratchStack.push(pathStack.join()); scratchStack.push(" e\"><v:fill opacity=\"" + context.globalAlpha * context.fillAlpha); // TODO add gradient code here scratchStack.push("\"></v:fill></v:shape>"); insert(context.globalCompositeOperation, scratchStack.join()); } public void stroke() { if (pathStack.isEmpty()) { return; } scratchStack.clear(); scratchStack.push("<v:shape style=\"position:absolute;width:10;height:10;\" coordsize=\"100,100\" filled=\"f\" strokecolor=\""); scratchStack.push(context.strokeStyle); scratchStack.push("\" strokeweight=\"" + context.lineWidth); scratchStack.push("px\" path=\""); scratchStack.push(pathStack.join()); scratchStack.push(" e\"><v:stroke opacity=\"" + context.globalAlpha * context.strokeAlpha); scratchStack.push("\" miterlimit=\"" + context.miterLimit); scratchStack.push("\" joinstyle=\""); scratchStack.push(context.lineJoin); scratchStack.push("\" endcap=\""); scratchStack.push(context.lineCap); scratchStack.push("\"></v:stroke></v:shape>"); insert(context.globalCompositeOperation, scratchStack.join()); } public void fillRect(double x, double y, double w, double h) { beginPath(); rect(x, y, w, h); fill(); pathStack.clear(); } public void strokeRect(double x, double y, double w, double h) { beginPath(); rect(x, y, w, h); stroke(); pathStack.clear(); } ///////////////////////////////////////////////////////////////// // GRADIENT STYLES ///////////////////////////////////////////////////////////////// public Gradient createLinearGradient(double x0, double y0, double x1, double y1) { return new LinearGradientImplIE(x0, y0, x1, y1); } public Gradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1) { return new RadialGradientImplIE(x0, y0, r0, x1, y1, r1); } ///////////////////////////////////////////////////////////////// // DRAWING IMAGES ///////////////////////////////////////////////////////////////// public void drawImage(ImageElement image, double sx, double sy, double sWidth, double sHeight, double dx, double dy, double dWidth, double dHeight) { int iWidth = image.getWidth(); int iHeight = image.getHeight(); int dX = (int)(getCoordX(dx, dy) / 10.0); int dY = (int)(getCoordY(dx, dy) / 10.0); scratchStack.clear(); scratchStack.push("<v:group style=\"position:absolute;width:10;height:10;"); // If the transformation matrix has a rotation/scale we apply a filter // Create a padding bounding box to prevent clipping if (context.matrix[0] != 1 || context.matrix[3] != 0) { scratchStack.push("padding-right:"); // TODO verify px suffix scratchStack.push(DOM.getStyleAttribute(element, "width")); scratchStack.push(";padding-bottom:"); // TODO verify px suffix scratchStack.push(DOM.getStyleAttribute(element, "height")); scratchStack.push(";filter:progid:DXImageTransform.Microsoft.Matrix(M11='"); scratchStack.push(matrix[0] + "',M12='"); scratchStack.push(matrix[3] + "',M21='"); scratchStack.push(matrix[1] + "',M22='"); scratchStack.push(matrix[4] + "',Dx='"); scratchStack.push(floor(dX) + "',Dy='"); scratchStack.push(floor(dY) + "', SizingMethod='clip');"); } else { scratchStack.push("left:"); scratchStack.push(dX + "px;"); scratchStack.push("top:"); scratchStack.push(dY + "px"); } scratchStack.push("\" coordsize=\"100,100\" coordorigin=\"0,0\"><v:image src=\""); scratchStack.push(image.getSrc()); scratchStack.push("\" style=\"width:" + (int)(dWidth * 10)); scratchStack.push(";height:" + (int)(dHeight * 10)); scratchStack.push(";\" cropleft=\"" + (sx / iWidth)); scratchStack.push("\" croptop=\"" + (sy / iHeight)); scratchStack.push("\" cropright=\"" + ((iWidth - sx - sWidth) / iWidth)); scratchStack.push("\" cropbottom=\"" + ((iHeight - sy - sHeight) / iHeight)); scratchStack.push("\"/></v:group>"); insert("BeforeEnd", scratchStack.join()); } ///////////////////////////////////////////////////////////////// // SETTERS AND GETTERS ///////////////////////////////////////////////////////////////// public void setGlobalAlpha(double globalAlpha) { context.globalAlpha = globalAlpha; } public double getGlobalAlpha() { return context.globalAlpha; } public void setGlobalCompositeOperation(String gcop) { gcop = gcop.trim(); if (gcop.equalsIgnoreCase(Canvas.SOURCE_OVER)) { context.globalCompositeOperation = CanvasImplIE.SOURCE_OVER; } else if (gcop.equalsIgnoreCase(Canvas.DESTINATION_OVER)) { context.globalCompositeOperation = CanvasImplIE.DESTINATION_OVER; } } public String getGlobalCompositeOperation() { if (context.globalCompositeOperation == CanvasImplIE.DESTINATION_OVER) { return Canvas.DESTINATION_OVER; } else { return Canvas.SOURCE_OVER; } } public void setStrokeStyle(String strokeStyle) { strokeStyle = strokeStyle.trim(); if (strokeStyle.startsWith("rgba(")) { int end = strokeStyle.indexOf(")", 12); if (end > -1) { String[] guts = strokeStyle.substring(5, end).split(","); if (guts.length == 4) { context.strokeAlpha = Double.parseDouble(guts[3]); context.strokeStyle = "rgb(" + guts[0] + "," + guts[1] + "," + guts[2] + ")"; context.strokeStyle_ = strokeStyle; } } } else { context.strokeAlpha = 1.0; context.strokeStyle = strokeStyle; context.strokeStyle_ = null; } } public String getStrokeStyle() { if (context.strokeStyle_ != null) { return context.strokeStyle_; } return context.strokeStyle; } public void setFillStyle(Gradient fillStyle) { context.fillGradient = (GradientImplIE) fillStyle; } public void setFillStyle(String fillStyle) { fillStyle = fillStyle.trim(); if (fillStyle.startsWith("rgba(")) { int end = fillStyle.indexOf(")", 12); if (end > -1) { String[] guts = fillStyle.substring(5, end).split(","); if (guts.length == 4) { context.fillAlpha = Double.parseDouble(guts[3]); context.fillStyle = "rgb(" + guts[0] + "," + guts[1] + "," + guts[2] + ")"; context.fillStyle_ = fillStyle; } } } else { context.fillAlpha = 1.0; context.fillStyle = fillStyle; context.fillStyle_ = null; } context.fillGradient = null; } public String getFillStyle() { if (context.fillStyle_ != null) { return context.fillStyle_; } return context.fillStyle; } public void setLineWidth(double lineWidth) { context.lineWidth = lineWidth; } public double getLineWidth() { return context.lineWidth; } public void setLineCap(String lineCap) { if (lineCap.trim().equalsIgnoreCase(Canvas.BUTT)) { context.lineCap = CanvasImplIE.BUTT; } else { context.lineCap = lineCap; } } public String getLineCap() { if (context.lineCap == CanvasImplIE.BUTT) { return Canvas.BUTT; } return context.lineCap; } public void setLineJoin(String lineJoin) { context.lineJoin = lineJoin; } public String getLineJoin() { return context.lineJoin; } public void setMiterLimit(double miterLimit) { context.miterLimit = miterLimit; } public double getMiterLimit() { return context.miterLimit; } public void fillText(String text, double x, double y) { } }