/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 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
*/
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", 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", 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, "textbox", style);
if (element != null) {
// Set position, style and content:
applyAbsolutePosition(element, position);
VmlStyleUtil.applyStyle(element, style);
// Set width, because this may change otherwise...
int textWidth = width - (int) position.getX();
if (textWidth <= 0) {
textWidth = 10;
}
DOM.setStyleAttribute(element, "width", textWidth + "px");
element.setInnerText(text);
}
}
}
/**
* Return the (enclosing) group for the specified element id.
*
* @param 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
* @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 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");
}
}
/**
* Hide 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");
}
}
}
/**
* 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);
}
// ------------------------------------------------------------------------
// 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;
}
}
}