/**
* @fileoverview A class representing a drawing canvas.
*/
goog.provide('xrx.drawing.Drawing');
goog.require('goog.array');
goog.require('goog.dom.DomHelper');
goog.require('goog.dom.ViewportSizeMonitor');
goog.require('xrx.engine.Engine');
goog.require('xrx.engine.Engines');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.net.ImageLoader');
goog.require('goog.style');
goog.require('goog.userAgent');
goog.require('xrx.drawing.EventHandler');
goog.require('xrx.drawing.Hoverable');
goog.require('xrx.drawing.LayerBackground');
goog.require('xrx.drawing.LayerShape');
goog.require('xrx.drawing.LayerShapeCreate');
goog.require('xrx.drawing.LayerShapeModify');
goog.require('xrx.drawing.LayerTool');
goog.require('xrx.drawing.Mode');
goog.require('xrx.drawing.Modifiable');
goog.require('xrx.drawing.Selectable');
goog.require('xrx.drawing.State');
goog.require('xrx.engine.Engine');
goog.require('xrx.viewbox.Viewbox');
goog.require('xrx.shape');
goog.require('xrx.shape.Shapes');
/**
* A class representing a drawing canvas. The drawing canvas can have a background
* image and thereby can serve as an image annotation tool.
* @param {DOMElement} element The HTML element used to install the canvas.
* @param {(xrx.engine.Canvas|xrx.engine.SVG|xrx.engine.VML)} opt_engine The
* rendering engine type.
* @constructor
*/
xrx.drawing.Drawing = function(element, opt_engine) {
goog.base(this);
/**
* The DOM element used to install the drawing canvas.
* @type {DOMElement}
* @private
*/
this.element_ = element;
/**
* The graphics rendering engine.
* @type {xrx.engine.Engine}
* @private
*/
this.engine_;
/**
* The graphics canvas.
* @type {xrx.shape.Canvas}
* @private
*/
this.canvas_;
/**
* The layers of the drawing canvas.
* @type {Array}
* @private
*/
this.layer_ = [];
/**
* A shield in front of the canvas needed by the SVG and the
* VML rendering engine for smooth dragging of elements.
* @type {xrx.shape.Rect}
* @private
*/
this.shield_;
/**
* @type {number}
* @private
*/
this.mode_ = xrx.drawing.Mode.NONE;
/**
* The shape currently modified by the user.
* @type {xrx.drawing.Modifiable}
* @private
*/
this.modifiable_ = new xrx.drawing.Modifiable(this);
/**
* The shape currently selected by the user.
* @type {xrx.drawing.Selectable}
* @private
*/
this.selectable_ = new xrx.drawing.Selectable(this);
/**
* The shape currently hovered by the user.
* @type {xrx.drawing.Hoverable}
* @private
*/
this.hoverable_ = new xrx.drawing.Hoverable(this);
/**
* The shape currently created by the user.
* @type {Object}
* @private
*/
this.creatable_;
/**
* The view-box of the drawing canvas.
* @type {xrx.viewbox.Viewbox}
* @private
*/
this.viewbox_;
/**
* The width of this drawing canvas.
* @type {number}
* @private
*/
this.width_;
/**
* The height of this drawing canvas.
* @type {number}
* @private
*/
this.height_;
/**
* Just for debugging.
* @private
*/
this.eventBeforeRendering;
/**
* Viewport size monitor. Fires events whenever the size of
* the browser window changes.
* @type {goog.dom.VieportSizeMonitor}
* @private
*/
this.vsm_;
/**
* Timeout function for function draw() to improve performance.
* @type {function}
* @private
*/
this.timeoutDraw_;
/**
* Timeout function for function handleResize() to improve performance.
* @type {function}
* @private
*/
this.timeoutResize_;
// install the canvas
this.install_(opt_engine);
};
goog.inherits(xrx.drawing.Drawing, xrx.drawing.EventHandler);
/**
* Event that is thrown whenever a shape is modified.
* @param {xrx.shape.Shape} shape The shape that was modified.
* @event
*/
xrx.drawing.Drawing.prototype.eventShapeModify = function(shape) {};
/**
* Returns the wrapper element of this drawing canvas.
* @return {DOMElement} The wrapper.
*/
xrx.drawing.Drawing.prototype.getElement = function() {
return this.element_;
};
/**
* Returns the engine used for rendering.
* @return {xrx.engine.Engine} The engine.
*/
xrx.drawing.Drawing.prototype.getEngine = function() {
return this.engine_;
};
/**
* Returns the canvas object of this drawing canvas.
* @return {xrx.shape.Canvas} The canvas object.
*/
xrx.drawing.Drawing.prototype.getCanvas = function() {
return this.canvas_;
};
/**
* Returns the background layer of this drawing canvas.
* @return {xrx.drawing.LayerBackground} The background layer object.
*/
xrx.drawing.Drawing.prototype.getLayerBackground = function() {
return this.layer_[0];
};
/**
* Returns the shape layer of this drawing canvas.
* @return {xrx.drawing.LayerShape} The shape layer object.
*/
xrx.drawing.Drawing.prototype.getLayerShape = function() {
return this.layer_[1];
};
/**
* Returns the layer where shapes can be modified.
* @return {xrx.drawing.LayerShapeModify} The shape modify layer object.
*/
xrx.drawing.Drawing.prototype.getLayerShapeModify = function() {
return this.layer_[2];
};
/**
* Returns the layer where new shapes can be drawn.
* @return {xrx.drawing.LayerShapeCreate} The create layer object.
*/
xrx.drawing.Drawing.prototype.getLayerShapeCreate = function() {
return this.layer_[3];
};
/**
* Returns the layer where tools can be plugged in.
* @return {xrx.drawing.LayerTool} The tool layer object.
*/
xrx.drawing.Drawing.prototype.getLayerTool = function() {
return this.layer_[4];
};
/**
* Returns the view-box of this drawing canvas.
* @return {Object} The view-box.
*/
xrx.drawing.Drawing.prototype.getViewbox = function() {
return this.viewbox_;
};
/**
* Sets the size of this drawing canvas.
* @param {number} width The width in pixel.
* @param {number} height The height in pixel.
*/
xrx.drawing.Drawing.prototype.setSize = function(width, height) {
goog.style.setSize(this.element_, width + 'px', height + 'px');
this.handleResize();
};
/**
* Returns the width of this drawing canvas.
* @param {number} width The width in pixel.
*/
xrx.drawing.Drawing.prototype.getWidth = function() {
return this.width_;
};
/**
* Sets the width of this drawing canvas.
* @param {number} width The width in pixel.
*/
xrx.drawing.Drawing.prototype.setWidth = function(width) {
this.setSize(width, this.height_);
};
/**
* Returns the height of this drawing canvas.
* @param {number} height The height in pixel.
*/
xrx.drawing.Drawing.prototype.getHeight = function() {
return this.height_;
};
/**
* Sets the height of this drawing canvas.
* @param {number} height The height in pixel.
*/
xrx.drawing.Drawing.prototype.setHeight = function(height) {
this.setSize(this.width_, height);
};
/**
* Sets a CSS style for this drawing canvas.
* @param {string} The style name such as 'padding'.
* @param {string} The style value such as '20px'.
*/
xrx.drawing.Drawing.prototype.setStyle = function(name, value) {
goog.style.setStyle(this.element_, name, value);
this.handleResize();
};
/**
* Returns the current background image of this drawing canvas.
* @return {HTMLImage} The background image.
*/
xrx.drawing.Drawing.prototype.getBackgroundImage = function() {
return this.layer_[0].getImage().getImage();
};
/**
* Sets a background image to this drawing canvas.
* @param {string} url The URL of the image.
* @param {function} opt_callback A callback function that is evaluated after
* the image is loaded.
*/
xrx.drawing.Drawing.prototype.setBackgroundImage = function(url, opt_callback) {
var img = this.layer_[0].getImage();
if (img && img.src === url) return;
var self = this;
var imageLoader = new goog.net.ImageLoader();
var tmpImage = goog.dom.createElement('img');
tmpImage.id = '_tmp';
tmpImage.src = url;
goog.events.listen(imageLoader, goog.events.EventType.LOAD, function(e) {
self.layer_[0].setImage(e.target);
if (opt_callback) opt_callback();
self.draw();
});
imageLoader.addImage('_tmp', tmpImage);
imageLoader.start();
};
/**
* Adds one or more shapes to this drawing canvas.
* @param {xrx.shape.Shape} var_args The shapes.
*/
xrx.drawing.Drawing.prototype.addShapes = function(var_args) {
this.layer_[1].addShapes(goog.array.toArray(arguments));
this.draw();
};
/**
* Returns the shapes currently rendered by this drawing canvas.
* @return {Array<xrx.shape.Shape>} The shapes.
*/
xrx.drawing.Drawing.prototype.getShapes = function() {
return this.layer_[1].getShapes() || [];
};
xrx.drawing.Drawing.prototype.removeShape = function(shape) {
this.layer_[1].removeShape(shape);
this.layer_[2].removeShapes();
this.draw();
};
xrx.drawing.Drawing.prototype.getSelectedShape = function() {
if (this.mode_ === xrx.drawing.Mode.SELECT) {
return this.selectable_.getShape();
} else if (this.mode_ === xrx.drawing.Mode.MODIFY) {
return this.modifiable_.getShape();
}
return null;
};
/**
* Draws this canvas and all its layers, tools and shapes contained.
*/
xrx.drawing.Drawing.prototype.draw = function() {
var self = this;
var viewbox = this.getViewbox();
this.eventBeforeRendering = function(element) {
// apply the current view-box matrix to the view-box group
if (element === viewbox.getGroup()) {
element.setCTM(viewbox.getCTM());
}
// tell each stylable shape the current scale of the viewbox
else if (element instanceof xrx.shape.Style) {
element.setZoomFactor(viewbox.getZoomValue());
}
else {};
};
goog.dom.getWindow().clearTimeout(this.timeoutDraw_);
this.timeoutDraw_ = goog.dom.getWindow().setTimeout(function() {
if (self.eventBeforeDraw) self.eventBeforeDraw();
self.canvas_.draw();
});
};
/**
* Returns the current mode of this drawing canvas.
* @return {number} The mode.
*/
xrx.drawing.Drawing.prototype.getMode = function() {
return this.mode_;
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.setMode_ = function(mode) {
if (mode !== this.mode_ || mode === xrx.drawing.Mode.CREATE) {
this.mode_ = mode;
this.registerEvents(mode);
}
};
/**
* Switch the drawing canvas over into mode <i>disabled</i>.
*/
xrx.drawing.Drawing.prototype.setModeDisabled = function() {
this.getLayerBackground().setLocked(true);
this.getLayerShape().setLocked(true);
this.getLayerShapeModify().setLocked(true);
this.getLayerShapeCreate().setLocked(true);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.setMode_(xrx.drawing.Mode.DISABLED);
};
/**
* Switch the drawing canvas over into mode <i>view</i> to allow view-box panning
* zooming and rotating.
*/
xrx.drawing.Drawing.prototype.setModeView = function() {
this.getLayerBackground().setLocked(false);
this.getLayerShape().setLocked(true);
this.getLayerShapeModify().setLocked(true);
this.getLayerShapeCreate().setLocked(true);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.setMode_(xrx.drawing.Mode.VIEW);
};
/**
* Switch the drawing canvas over into mode <i>hover</i> to allow hovering
* of shapes.
* @param {boolean} opt_overlapping Whether to highlight all hovered shapes that
* lie on top of each other or just the most forward. opt_overlapping defaults
* to false.
*/
xrx.drawing.Drawing.prototype.setModeHover = function(opt_overlapping) {
this.getLayerBackground().setLocked(true);
this.getLayerShape().setLocked(false);
this.getLayerShapeModify().setLocked(true);
this.getLayerShapeCreate().setLocked(true);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.hoverable_.setMultiple(opt_overlapping);
if (opt_overlapping === true) {
this.setMode_(xrx.drawing.Mode.HOVERMULTIPLE);
} else {
this.setMode_(xrx.drawing.Mode.HOVER);
}
};
/**
* Switch the drawing canvas over into mode <i>select</i> to allow selecting
* of shapes.
*/
xrx.drawing.Drawing.prototype.setModeSelect = function() {
this.getLayerBackground().setLocked(true);
this.getLayerShape().setLocked(false);
this.getLayerShapeModify().setLocked(true);
this.getLayerShapeCreate().setLocked(true);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.setMode_(xrx.drawing.Mode.SELECT);
};
/**
* Sets a shape as the selected.
* @param {xrx.shape.Shape} shape The shape to be selected.
*/
xrx.drawing.Drawing.prototype.setSelected = function(shape) {
this.selectable_.setSelected(shape);
this.draw();
};
/**
* Switch the drawing canvas over into mode <i>modify</i> to allow modification of shapes.
* @see xrx.drawing.Mode
*/
xrx.drawing.Drawing.prototype.setModeModify = function() {
this.getLayerBackground().setLocked(true);
this.getLayerShape().setLocked(false);
this.getLayerShapeModify().setLocked(false);
this.getLayerShapeCreate().setLocked(true);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.setMode_(xrx.drawing.Mode.MODIFY);
};
/**
* Switch the drawing canvas over into mode <i>create</i> to allow drawing of new shapes.
* @see xrx.drawing.Mode
* @param {xrx.shape.Creatable} creatable The shape to be created.
*/
xrx.drawing.Drawing.prototype.setModeCreate = function(creatable) {
if (!creatable) return;
if (!(creatable instanceof xrx.shape.Creatable)) {
throw Error('Instance of xrx.shape.Creatable expected.');
}
this.creatable_ = creatable;
this.getLayerBackground().setLocked(true);
this.getLayerShape().setLocked(true);
this.getLayerShapeModify().setLocked(true);
this.getLayerShapeCreate().setLocked(false);
this.getLayerShapeModify().removeShapes();
this.getLayerShapeCreate().removeShapes();
this.setMode_(xrx.drawing.Mode.CREATE);
};
/**
* Handles resizing of this drawing canvas. This function is
* automatically called whenever the size of the browser window
* changes. It can be also called by an application that manually
* changes the size of this drawing canvas during live time.
*/
xrx.drawing.Drawing.prototype.handleResize = function() {
var self = this;
goog.dom.getWindow().clearTimeout(this.timeoutResize_);
this.timeoutResize_ = goog.dom.getWindow().setTimeout(function() {
self.calculateSize_();
self.canvas_.setHeight(self.height_);
self.canvas_.setWidth(self.width_);
self.shield_.setHeight(self.height_);
self.shield_.setWidth(self.width_);
});
this.draw();
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.calculateSize_ = function() {
var size = goog.style.getSize(this.element_);
var paddingBox = goog.style.getPaddingBox(this.element_);
var borderBox = goog.style.getBorderBox(this.element_);
var height = size.height - paddingBox.top - paddingBox.bottom -
borderBox.top - borderBox.bottom;
var width = size.width - paddingBox.left - paddingBox.right -
borderBox.left - borderBox.right;
this.width_ = width;
this.height_ = height;
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installCanvas_ = function() {
var self = this;
this.vsm_ = new goog.dom.ViewportSizeMonitor();
goog.events.listen(this.vsm_, goog.events.EventType.RESIZE, function(e) {
self.handleResize();
}, false, self);
this.canvas_ = new xrx.shape.Canvas(this);
this.canvas_.setHeight(this.height_);
this.canvas_.setWidth(this.width_);
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installViewbox_ = function() {
this.viewbox_ = new xrx.viewbox.Viewbox(this);
this.canvas_.addChildren(this.viewbox_.getGroup());
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installLayerBackground_ = function() {
this.layer_[0] = new xrx.drawing.LayerBackground(this);
this.viewbox_.getGroup().addChildren(this.layer_[0].getGroup());
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installLayerShape_ = function() {
this.layer_[1] = new xrx.drawing.LayerShape(this);
this.viewbox_.getGroup().addChildren(this.layer_[1].getGroup());
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installLayerShapeModify_ = function() {
this.layer_[2] = new xrx.drawing.LayerShapeModify(this);
this.viewbox_.getGroup().addChildren(this.layer_[2].getGroup());
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installLayerShapecreatable_ = function() {
this.layer_[3] = new xrx.drawing.LayerShapeCreate(this);
this.viewbox_.getGroup().addChildren(this.layer_[3].getGroup());
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installShield_ = function() {
this.shield_ = new xrx.shape.Rect(this);
this.shield_.setX(0);
this.shield_.setY(0);
this.shield_.setWidth(this.width_);
this.shield_.setHeight(this.height_);
this.shield_.setFillOpacity(0);
this.shield_.setStrokeWidth(0);
this.canvas_.addChildren(this.shield_);
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.installLayerTool_ = function() {
this.layer_[4] = new xrx.drawing.LayerTool(this);
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.initEngine_ = function(opt_engine) {
this.engine_ = new xrx.engine.Engine(opt_engine, this.element_);
};
/**
* @private
*/
xrx.drawing.Drawing.prototype.install_ = function(opt_engine) {
// initialize the rendering engine
this.initEngine_(opt_engine);
if (this.engine_.isAvailable()) {
// remove the fall-back message if some exists
goog.dom.removeChildren(this.element_);
// calculate the size of the canvas
this.calculateSize_();
// install the drawing canvas
this.installCanvas_();
// install the drawing view-box
this.installViewbox_();
// install the drawing layers
this.installLayerBackground_();
this.installLayerShape_();
this.installLayerShapeModify_();
this.installLayerShapecreatable_();
// install a shield
this.installShield_();
// install the tool layer
this.installLayerTool_();
/*
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9)) {
// IE 7 and IE 8 z-index fix
var divs = goog.dom.getElementsByTagNameAndClass('div', undefined, this.element_);
var zIndex = 1000;
goog.array.forEach(divs, function(e, i, a) {
goog.style.setStyle(e, 'z-index', zIndex);
zIndex -= 10;
})
};
*/
}
};
xrx.drawing.Drawing.prototype.disposeInternal = function() {
goog.dom.removeNode(this.element_);
this.element_ = null;
this.engine_.dispose();
this.engine_ = null;
this.canvas_.dispose();
this.canvas_ = null;
var layer;
while(layer = this.layer_.pop()) {
layer.dispose();
layer = null;
}
this.layer_ = null;
this.shield_.dispose();
this.shield_ = null;
this.modifiable_.dispose();
this.modifiable_ = null;
this.selectable_.dispose();
this.selectable_ = null;
this.hoverable_.dispose();
this.hoverable_ = null;
goog.dispose(this.creatable_);
this.creatable_ = null;
this.viewbox_.dispose();
this.viewbox_ = null;
this.eventBeforeRendering = null;
this.vsm_.removeAllListeners();
this.vsm_.dispose();
this.vsm_ = null;
this.timeoutDraw_ = null;
this.timeoutResize_ = null;
goog.base(this, 'disposeInternal');
};