org.geomajas.gwt.client.gfx.context.VmlGraphicsContext.java Source code

Java tutorial

Introduction

Here is the source code for org.geomajas.gwt.client.gfx.context.VmlGraphicsContext.java

Source

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2013 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.gwt.client.gfx.context;

import java.util.HashMap;
import java.util.Map;

import org.geomajas.configuration.ImageInfo;
import org.geomajas.configuration.SymbolInfo;
import org.geomajas.geometry.Coordinate;
import org.geomajas.gwt.client.controller.GraphicsController;
import org.geomajas.gwt.client.gfx.GraphicsContext;
import org.geomajas.gwt.client.gfx.context.DomHelper.Namespace;
import org.geomajas.gwt.client.gfx.style.FontStyle;
import org.geomajas.gwt.client.gfx.style.PictureStyle;
import org.geomajas.gwt.client.gfx.style.ShapeStyle;
import org.geomajas.gwt.client.gfx.style.Style;
import org.geomajas.gwt.client.spatial.Bbox;
import org.geomajas.gwt.client.spatial.Matrix;
import org.geomajas.gwt.client.spatial.geometry.LineString;
import org.geomajas.gwt.client.spatial.geometry.Polygon;
import org.geomajas.gwt.client.util.Dom;

import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.util.SC;

/**
 * Implementation of the GraphicsContext interface using the VML language for Internet Explorer.
 * 
 * @author Pieter De Graef
 */
public class VmlGraphicsContext implements GraphicsContext {

    private int width;

    private int height;

    private String id;

    private Map<String, SymbolDefinition> symbolDefs = new HashMap<String, SymbolDefinition>();

    private DomHelper helper;

    private Widget parent;

    // -------------------------------------------------------------------------
    // Constructor:
    // -------------------------------------------------------------------------

    /**
     * Constructs an image context, appending the root element to the specified parent widget.
     * 
     * @param parent
     *            parent widget
     */
    public VmlGraphicsContext(Widget parent) {
        this.parent = parent;

        // Initialize the VML namespace:
        Dom.initVMLNamespace();

        // the root VML node
        Element rootNode = Dom.createElementNS(Dom.NS_HTML, "div");
        id = Dom.createUniqueId();
        rootNode.setId(id);
        Dom.setStyleAttribute(rootNode, "position", "absolute");
        Dom.setStyleAttribute(rootNode, "width", "100%");
        Dom.setStyleAttribute(rootNode, "height", "100%");
        Dom.setStyleAttribute(rootNode, "clip", "rect(0 " + width + "px " + height + "px 0)");
        Dom.setStyleAttribute(rootNode, "overflow", "hidden");
        helper = new DomHelper(rootNode, Namespace.VML);

        // Append to parent: we need a top div or the vml is blocked by any peer div !!!
        parent.getElement().appendChild(rootNode);
    }

    /**
     * Delete this element from the graphics DOM structure.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The element's name.
     */
    public void deleteElement(Object parent, String name) {
        if (isAttached()) {
            helper.deleteElement(parent, name);
        }
    }

    /**
     * Delete this group from the graphics DOM structure.
     * 
     * @param object
     *            The group's object.
     */
    public void deleteGroup(Object object) {
        if (isAttached()) {
            helper.deleteGroup(object);
        }
    }

    /**
     * Draw a circle on the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The circle's name.
     * @param position
     *            The center position as a coordinate.
     * @param radius
     *            The circle's radius.
     * @param style
     *            The styling object by which the circle should be drawn.
     */
    public void drawCircle(Object parent, String name, Coordinate position, double radius, ShapeStyle style) {
        if (isAttached()) {
            Element circle = helper.createOrUpdateElement(parent, name, "oval", style);

            // Real position is the upper left corner of the circle:
            applyAbsolutePosition(circle, new Coordinate(position.getX() - radius, position.getY() - radius));

            // width and height are both radius*2
            int size = (int) (2 * radius);
            applyElementSize(circle, size, size, false);
        }
    }

    /**
     * Draw inner group data directly (implementation-specific shortcut). This method can only be called once, creating
     * the group. Delete the group first to redraw with different data.
     * 
     * @param parent
     *            The parent group's object
     * @param object
     *            The group's object
     * @param data
     *            VML fragment
     * @param transformation
     *            transformation to apply to the group
     */
    public void drawData(Object parent, Object object, String data, Matrix transformation) {
        if (isAttached()) {
            Element group = helper.getGroup(object);
            if (group == null) {
                group = helper.createOrUpdateGroup(parent, object, transformation, null);
                Dom.setInnerHTML(group, data);
            }
        }
    }

    /**
     * Creates a group element in the technology (SVG/VML/...) of this context. A group is meant to group other elements
     * together.
     * 
     * @param parent
     *            parent group object
     * @param object
     *            group object
     */
    public void drawGroup(Object parent, Object object) {
        if (isAttached()) {
            helper.createOrUpdateGroup(parent, object, null, null);
        }
    }

    /**
     * Creates a group element in the technology (SVG/VML/...) of this context with the specified tag name.
     * 
     * @param parent
     *            parent group object
     * @param object
     *            group object
     * @param tagName
     *            the tag name
     * @return HTML element which was drawn
     */
    public Element drawGroup(Object parent, Object object, String tagName) {
        if (isAttached()) {
            return helper.drawGroup(parent, object, tagName);
        } else {
            return null;
        }
    }

    /**
     * Creates a group element in the technology (SVG/VML/...) of this context. A group is meant to group other elements
     * together, possibly applying a transformation upon them.
     * 
     * @param parent
     *            parent group object
     * @param object
     *            group object
     * @param transformation
     *            On each group, it is possible to apply a matrix transformation (currently translation only). This is
     *            the real strength of a group element.
     */
    public void drawGroup(Object parent, Object object, Matrix transformation) {
        if (isAttached()) {
            helper.drawGroup(parent, object, transformation);
        }
    }

    /**
     * Creates a group element in the technology (SVG/VML/...) of this context. A group is meant to group other elements
     * together, and in this case applying a style on them.
     * 
     * @param parent
     *            parent group object
     * @param object
     *            group object
     * @param style
     *            Add a style to a group.
     */
    public void drawGroup(Object parent, Object object, Style style) {
        if (isAttached()) {
            helper.drawGroup(parent, object, style);
        }
    }

    /**
     * Creates a group element in the technology (SVG/VML/...) of this context. A group is meant to group other elements
     * together, possibly applying a transformation upon them.
     * 
     * @param parent
     *            parent group object
     * @param object
     *            group object
     * @param transformation
     *            On each group, it is possible to apply a matrix transformation (currently translation only). This is
     *            the real strength of a group element.
     * @param style
     *            Add a style to a group.
     */
    public void drawGroup(Object parent, Object object, Matrix transformation, Style style) {
        if (isAttached()) {
            helper.createOrUpdateGroup(parent, object, transformation, style);
        }
    }

    /**
     * Draw an image onto the the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The image's name.
     * @param href
     *            The image's location (URL).
     * @param bounds
     *            The bounding box that sets the image's origin (x and y), it's width and it's height.
     * @param style
     *            A styling object to be passed along with the image. Can be null.
     */
    public void drawImage(Object parent, String name, String href, Bbox bounds, PictureStyle style) {
        if (isAttached()) {
            Element image = helper.createOrUpdateElement(parent, name, "image", style);
            applyAbsolutePosition(image, bounds.getOrigin());
            applyElementSize(image, (int) bounds.getWidth(), (int) bounds.getHeight(), true);
            Dom.setElementAttribute(image, "src", Dom.makeUrlAbsolute(href));
        }
    }

    /**
     * Draw a {@link LineString} geometry onto the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The LineString's name.
     * @param line
     *            The LineString to be drawn.
     * @param style
     *            The styling object for the LineString. Watch out for fill colors! If the fill opacity is not 0, then
     *            the LineString will have a fill surface.
     */
    public void drawLine(Object parent, String name, LineString line, ShapeStyle style) {
        if (isAttached()) {
            Element element = helper.createOrUpdateElement(parent, name, "shape", style);
            if (line != null) {
                Dom.setElementAttribute(element, "path", VmlPathDecoder.decode(line));
                Dom.setStyleAttribute(element, "position", "absolute");
                applyElementSize(element, width, height, false);
            }
        }
    }

    /**
     * Draw a {@link Polygon} geometry onto the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The Polygon's name.
     * @param polygon
     *            The Polygon to be drawn.
     * @param style
     *            The styling object for the Polygon.
     */
    public void drawPolygon(Object parent, String name, Polygon polygon, ShapeStyle style) {
        if (isAttached()) {
            Element element = helper.createOrUpdateElement(parent, name, "shape", style);
            if (polygon != null) {
                Dom.setStyleAttribute(element, "position", "absolute");
                Dom.setElementAttribute(element, "fill-rule", "evenodd");
                Dom.setElementAttribute(element, "path", VmlPathDecoder.decode(polygon));
                applyElementSize(element, getWidth(), getHeight(), false);
            }
        }
    }

    /**
     * Draw a rectangle onto the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The rectangle's name.
     * @param rectangle
     *            The rectangle to be drawn. The bounding box's origin, is the rectangle's upper left corner on the
     *            screen.
     * @param style
     *            The styling object for the rectangle.
     */
    public void drawRectangle(Object parent, String name, Bbox rectangle, ShapeStyle style) {
        if (isAttached()) {
            Element element = helper.createOrUpdateElement(parent, name, "rect", style);
            applyAbsolutePosition(element, rectangle.getOrigin());
            applyElementSize(element, (int) rectangle.getWidth(), (int) rectangle.getHeight(), false);
        }
    }

    /**
     * Draw a type (shapetype for vml).
     * 
     * @param parent
     *            the parent of the shapetype
     * @param id
     *            the types's unique identifier
     * @param symbol
     *            the symbol information
     * @param style
     *            The default style to apply on the shape-type. Can be overridden when a shape uses this shape-type.
     * @param transformation
     *            the transformation to apply on the symbol
     */
    public void drawSymbolDefinition(Object parent, String id, SymbolInfo symbol, ShapeStyle style,
            Matrix transformation) {
        if (isAttached()) {
            if (symbol == null) {
                return;
            }
            symbolDefs.put(id, new SymbolDefinition(symbol, style));
            if (symbol.getImage() != null) {
                // When it's an image symbol, add an extra definition for it's selection:
                SymbolInfo selected = new SymbolInfo();
                ImageInfo selectedImage = new ImageInfo();
                selectedImage.setHref(symbol.getImage().getSelectionHref());
                selectedImage.setWidth(symbol.getImage().getWidth());
                selectedImage.setHeight(symbol.getImage().getHeight());
                selected.setImage(selectedImage);
                symbolDefs.put(id + "-selection", new SymbolDefinition(selected, null));
            }
        }
    }

    /**
     * Draw a symbol, using some predefined ShapeType.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The symbol's name.
     * @param position
     *            The symbol's (X,Y) location on the graphics.
     * @param style
     *            The style to apply on the symbol. When the symbol is an image, the style will be ignored!
     * @param shapeTypeId
     *            The name of the predefined ShapeType. This symbol will create a reference to this predefined type and
     *            take on it's characteristics.
     */
    public void drawSymbol(Object parent, String name, Coordinate position, ShapeStyle style, String shapeTypeId) {
        if (isAttached()) {
            SymbolDefinition definition = symbolDefs.get(shapeTypeId);
            if (position == null) {
                return;
            }
            if (style == null) {
                style = definition.getStyle();
            }
            SymbolInfo symbol = definition.getSymbol();
            if (symbol.getRect() != null) {
                Element rect = helper.createOrUpdateElement(parent, name, "rect", style);

                // Real position is the upper left corner of the rectangle:
                float w = symbol.getRect().getW();
                float h = symbol.getRect().getH();
                applyAbsolutePosition(rect, new Coordinate(position.getX() - 0.5 * w, position.getY() - 0.5 * h));

                // width and height
                applyElementSize(rect, (int) w, (int) h, false);

            } else if (symbol.getCircle() != null) {
                Element circle = helper.createOrUpdateElement(parent, name, "oval", style);

                // Real position is the upper left corner of the circle:
                float radius = symbol.getCircle().getR();
                applyAbsolutePosition(circle, new Coordinate(position.getX() - radius, position.getY() - radius));

                // width and height are both radius*2
                int size = (int) (2 * radius);
                applyElementSize(circle, size, size, false);
            } else if (symbol.getImage() != null) {
                // Creating an image; ignoring style....
                Element image = helper.createOrUpdateElement(parent, name, "image", null);
                Dom.setElementAttribute(image, "src", Dom.makeUrlAbsolute(symbol.getImage().getHref()));
                int width = symbol.getImage().getWidth();
                int height = symbol.getImage().getHeight();
                applyElementSize(image, width, height, false);
                applyAbsolutePosition(image, new Coordinate(position.getX() - Math.round(width / 2),
                        position.getY() - Math.round(height / 2)));
            }
        }
    }

    /**
     * Draw a string of text onto the <code>GraphicsContext</code>.
     * 
     * @param parent
     *            parent group object
     * @param name
     *            The text's name.
     * @param text
     *            The actual string content.
     * @param position
     *            The upper left corner for the text to originate.
     * @param style
     *            The styling object for the text.
     */
    public void drawText(Object parent, String name, String text, Coordinate position, FontStyle style) {
        if (isAttached()) {
            Element element = helper.createOrUpdateElement(parent, name, "shape", null);
            if (element != null) {
                // Set position, style and content:
                applyAbsolutePosition(element, position);
                Element textbox = helper.createOrUpdateSingleChild(element, "textbox");
                VmlStyleUtil.applyStyle(textbox, style);
                // hook to upper-left corner
                textbox.setPropertyString("inset", "0px, 0px, 0px, 0px");
                textbox.setInnerHTML(text);
                // Set width, because this may change otherwise...
                applyElementSize(element, getWidth(), getHeight(), false);
            }
        }
    }

    /**
     * Return the (enclosing) group for the specified element id.
     * 
     * @param id group id
     * @return the group object
     */
    public Object getGroupById(String id) {
        if (isAttached()) {
            return helper.getGroupById(id);
        } else {
            return null;
        }
    }

    /**
     * Return the id of the specified group.
     * 
     * @param group
     *            the group object
     * @return the corresponding element id or null if the group has not been drawn.
     */
    public String getId(Object group) {
        return helper.getId(group);
    }

    /**
     * Return the unique id of the container div of this context.
     * 
     * @return the unique id of the container div.
     */
    public String getId() {
        return id;
    }

    /**
     * Return the element name for the specified id.
     * 
     * @param id element id
     * @return the name of the element
     */
    public String getNameById(String id) {
        if (isAttached()) {
            return helper.getNameById(id);
        } else {
            return null;
        }
    }

    /**
     * Return the current graphics height.
     */
    public int getHeight() {
        return height;
    }

    /**
     * Return the current graphics width.
     */
    public int getWidth() {
        return width;
    }

    /**
     * Hide the specified element in the specified group. If the element does not exist, nothing will happen.
     * 
     * @param group
     *            The group object.
     * @param name
     *            The element name.
     */
    public void hide(Object group, String name) {
        if (isAttached()) {
            Element element = helper.getElement(group, name);
            if (element != null) {
                Dom.setStyleAttribute(element, "visibility", "hidden");
            }
        }
    }

    /**
     * Hide the specified group. If the group does not exist, nothing will happen.
     * 
     * @param group
     *            The group object.
     */
    public void hide(Object group) {
        if (isAttached()) {
            Element element = helper.getGroup(group);
            if (element != null) {
                Dom.setStyleAttribute(element, "visibility", "hidden");
            }
        }
    }

    /**
     * Set the controller on an element of this <code>GraphicsContext</code> so it can react to events.
     * 
     * @param object
     *            the element on which the controller should be set.
     * @param controller
     *            The new <code>GraphicsController</code>
     */
    public void setController(Object object, GraphicsController controller) {
        if (isAttached()) {
            helper.setController(object, controller);
        }
    }

    /**
     * Set the controller on an element of this <code>GraphicsContext</code> so it can react to events.
     * 
     * @param parent
     *            the parent of the element on which the controller should be set.
     * @param name
     *            the name of the child element on which the controller should be set
     * @param controller
     *            The new <code>GraphicsController</code>
     */
    public void setController(Object parent, String name, GraphicsController controller) {
        if (isAttached()) {
            helper.setController(parent, name, controller);
        }
    }

    /**
     * Set the controller on an element of this <code>GraphicsContext</code> so it can react to events.
     * 
     * @param object
     *            the element on which the controller should be set.
     * @param controller
     *            The new <code>GraphicsController</code>
     * @param eventMask
     *            a bitmask to specify which events to listen for {@link com.google.gwt.user.client.Event}
     */
    public void setController(Object object, GraphicsController controller, int eventMask) {
        if (isAttached()) {
            helper.setController(object, controller, eventMask);
        }
    }

    /**
     * Set the controller on an element of this <code>GraphicsContext</code> so it can react to events.
     * 
     * @param parent
     *            the parent of the element on which the controller should be set.
     * @param name
     *            the name of the child element on which the controller should be set
     * @param controller
     *            The new <code>GraphicsController</code>
     * @param eventMask
     *            a bitmask to specify which events to listen for {@link com.google.gwt.user.client.Event}
     */
    public void setController(Object parent, String name, GraphicsController controller, int eventMask) {
        if (isAttached()) {
            helper.setController(parent, name, controller, eventMask);
        }
    }

    /**
     * Set a specific cursor on an element of this <code>GraphicsContext</code>.
     * 
     * @param object
     *            the element on which the controller should be set.
     * @param cursor
     *            The string representation of the cursor to use.
     */
    public void setCursor(Object object, String cursor) {
        if (isAttached()) {
            helper.setCursor(object, cursor);
        }
    }

    /**
     * Set a specific cursor on an element of this <code>GraphicsContext</code>.
     * 
     * @param parent
     *            the parent of the element on which the cursor should be set.
     * @param name
     *            the name of the child element on which the cursor should be set
     * @param cursor
     *            The string representation of the cursor to use.
     */
    public void setCursor(Object parent, String name, String cursor) {
        if (isAttached()) {
            helper.setCursor(parent, name, cursor);
        }
    }

    /**
     * Apply a new size on the graphics context.
     * 
     * @param newWidth
     *            The new newWidth in pixels for this graphics context.
     * @param newHeight
     *            The new newHeight in pixels for this graphics context.
     */
    public void setSize(int newWidth, int newHeight) {
        this.width = newWidth;
        this.height = newHeight;

        if (helper.getRootElement() != null) {
            applyElementSize(helper.getRootElement(), newWidth, newHeight, false);
            Dom.setStyleAttribute(helper.getRootElement(), "clip",
                    "rect(0 " + newWidth + "px " + newHeight + "px 0)");
        } else {
            SC.logWarn("problems");
        }
    }

    /**
     * Show the specified group. If the group does not exist, nothing will happen.
     * 
     * @param group
     *            The group object.
     */
    public void unhide(Object group) {
        if (isAttached()) {
            Element element = helper.getGroup(group);
            if (element != null) {
                Dom.setStyleAttribute(element, "visibility", "inherit");
            }
        }
    }

    /**
     * Show the specified element in the specified group. If the element does not exist, nothing will happen.
     * 
     * @param group
     *            The group object.
     * @param name
     *            The element name.
     */
    public void unhide(Object group, String name) {
        if (isAttached()) {
            Element element = helper.getElement(group, name);
            if (element != null) {
                Dom.setStyleAttribute(element, "visibility", "inherit");
            }
        }
    }

    /**
     * Move an element from on group to another. The elements name will remain the same.
     * 
     * @param name
     *            The name of the element within the sourceParent group.
     * @param sourceParent
     *            The original parent object associated with the element.
     * @param targetParent
     *            The target parent object to be associated with the element.
     * @since 1.8.0
     */
    public void moveElement(String name, Object sourceParent, Object targetParent) {
        helper.moveElement(name, sourceParent, targetParent);
    }

    @Override
    public void bringToFront(Object object, String name) {
        helper.bringToFront(object, name);
    }

    @Override
    public void moveToBack(Object object, String name) {
        helper.moveToBack(object, name);
    }

    // ------------------------------------------------------------------------
    // Private methods:
    // ------------------------------------------------------------------------

    /**
     * Apply an absolute position on an element.
     * 
     * @param element
     *            The element that needs an absolute position.
     * @param position
     *            The position as a Coordinate.
     */
    private void applyAbsolutePosition(Element element, Coordinate position) {
        Dom.setStyleAttribute(element, "position", "absolute");
        Dom.setStyleAttribute(element, "left", (int) position.getX() + "px");
        Dom.setStyleAttribute(element, "top", (int) position.getY() + "px");
    }

    /**
     * Apply a size on an element.
     * 
     * @param element
     *            The element that needs sizing.
     * @param width
     *            The new width to apply on the element.
     * @param height
     *            The new height to apply on the element.
     * @param addCoordSize
     *            Should a coordsize attribute be added as well?
     */
    private void applyElementSize(Element element, int width, int height, boolean addCoordSize) {
        if (width >= 0 && height >= 0) {
            if (addCoordSize) {
                Dom.setElementAttribute(element, "coordsize", width + " " + height);
            }
            Dom.setStyleAttribute(element, "width", width + "px");
            Dom.setStyleAttribute(element, "height", height + "px");
        }
    }

    private boolean isAttached() {
        return parent != null && parent.isAttached();
    }

    /**
     * Symbol definition data.
     * 
     * @author Jan De Moerloose
     * 
     */
    public class SymbolDefinition extends SymbolInfo {

        private static final long serialVersionUID = 154L;

        private SymbolInfo symbol;

        private ShapeStyle style;

        public SymbolDefinition(SymbolInfo symbol, ShapeStyle style) {
            this.symbol = symbol;
            this.style = style;
        }

        public SymbolInfo getSymbol() {
            return symbol;
        }

        public ShapeStyle getStyle() {
            return style;
        }
    }
}