/*
	Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
	Available via Academic Free License >= 2.1 OR the modified BSD license.
	see: http://dojotoolkit.org/license for details
*/


if(!dojo._hasResource["dojox.drawing.stencil._Base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.drawing.stencil._Base"] = true;
dojo.provide("dojox.drawing.stencil._Base");
dojo.require("dojo.fx.easing");

/*=====
StencilArgs = {
//	container: [readonly] dojo.gfx.group
//		The parent shape that contains all
//		shapes used in a Stencil
container:null,
//
//	anchorType: String
//		Optionally blank or 'group'. 'group' tells
//		an anchor point that it must constrain
//		itself to other anchor points.
anchorType:"",
//
// 	isText: Boolean
//		Whether this is a text object or not
//		(either stencil.text or tools.TextBlock)
isText:false,
//
// 	shortType: String
//		The type of stencil that corresponds with the types and
//		constructors used in Drawing.registerTool
shortType:"",
//
//	annotation: Boolean
//		A Stencil used within a Stencil. An annotation
//		is not selectable or clickable. A Label would
//		be one example.
annotation:false,
//
//	subShape: Boolean
//		A Stencil used within a Stencil. A subShape
//		is clickable. An arrow head would be an example.
subShape:false,
//
//	style: Object
//		An instance of the styles and defaults used within
//		the Stencil.
style:null,
//
//	util: Object
//		Pointer to util.common
util:null,
//
//	mouse: Object
//		Pointer to the mouse instance
mouse:null,
//
//	keys: Object
//		Pointer to the keys class
keys:null,
//
//	points: StencilPoints
//		Points is an array of objects that make up the
//		description of a Stencil. The points to a Rect
//		that is 100x100 and at x:10 and y:10 would look like:
//		[{x:10,y:10}, {x:110, y:10}, {x:110, y:110}, {x:10, y:110}]
//		Points go clockwise from the top left. In the case of Paths,
//		they would go in the order that the Stencil would be drawn.
//		Always when the points Array is set, a data Object is created
//		as well. So never set points directly, always use setPoints().
//	See:
//		setPoints()
points:[],
//
//	data: StencilData
//		A data object typically (but not always) resembles the data
//		that is used to create the dojox.gfx Shape. The same Rect
//		example shown in points above would look like:
//		{x:10, y:10, width:100, height:100}
//		And an Ellipse with the same coordinates:
//		{cx:55, cy:55, rx:50, ry:50}
//		The only Stencil that does not support data (at this time)
//		is the Path. While x1,x2,x3... culd be used in a data object
//		it doesn't provide much benefit.
//		Always when a data object is set, a set of points is created
//		as well. So never set data directly, always use setData().
//	See:
//		setData()
data:null,
//
// 	marginZero [readonly] Number
// 		How closely shape can get to y:0 or x:0. Less than zero has
//		bugs in VML. This is set with defaults, and should be equal
//		to half the size of an anchor point (5 px)
marginZero:0,
//
//	created [readonly] Boolean
//		Whether the Stencil has been rendered for the first time or
//		not.
created: false,
//
//	highlighted [readonly] Boolean
//		Whether the Stencil is highlighted or not.
highlighted:false,
//
//	selected [readonly] Boolean
//		Whether the Stencil is selected or not.
selected:false,
//
//	draws [readonly] Boolean
//		Whether the Stencil can draw with a mouse drag or can just
//		be created programmtically. If the Stencil comes from the
//		stencil package, it should be draw:false. If it comes from
//		the tools package it should be draw:true.
draws:false
}

StencilPoint = {
// summary:
//	One point Object in the points Array
//	x: Number
//		x position of point
//	y: Number
//		y position of point
}

ToolsSetup = {
// summary:
//	An object attached to a Tool's constructor
//	used to inform the toolbar of its information
//	and properties.
// description:
//	This object is inserted into the *function* of
//	a tool (not a stencil). Like: function.ToolsSetup
//	It must be attached after constructr creation, so
//	this object is found at the botton of the file.
//
//	name:String
//		Fully qualified name of constructor
//	tooltip: Stirng
//		Text to display on toolbar button hover
//	iconClass: String
//		CSS class with icon information to attach
//		to toolbar button.
}
=====*/

dojox.drawing.stencil._Base = dojox.drawing.util.oo.declare(
	// summary:
	//		The base class used for all Stencils.
	// description:
	//		All stencils extend this base class.
	//		Most methods and events can be found here.
	//
	function(options){
		//console.log("______Base", this.type, options)
		// clone style so changes are reflected in future shapes
		dojo.mixin(this, options);
		this.style = options.style || dojox.drawing.defaults.copy();
		if(options.stencil){
			this.stencil = options.stencil;
			this.util = options.stencil.util;
			this.mouse = options.stencil.mouse;
			this.container = options.stencil.container;
			this.style = options.stencil.style;
		}

		// don't use the 'g' on these, it affects
		// the global RegExp
		var lineTypes = /Line|Vector|Axes|Arrow/;
		var textTypes = /Text/;

		this.shortType = this.util.abbr(this.type);
		this.isText = textTypes.test(this.type);
		this.isLine = lineTypes.test(this.type);

		this.renderHit = this.style.renderHitLayer;
		if(!this.renderHit && this.style.renderHitLines && this.isLine){
			this.renderHit = true;
		}
		if(!this.renderHit && this.style.useSelectedStyle){
			this.useSelectedStyle = true;
			this.selCopy = dojo.clone(this.style.selected);
			for(var nm in this.style.norm){
				if(this.style.selected[nm]===undefined){
					this.style.selected[nm] = this.style.norm[nm];
				}
			}
			this.textSelected = dojo.clone(this.style.text);
			this.textSelected.color = this.style.selected.fill;

		}


		this.angleSnap = this.style.angleSnap || 1;

		this.marginZero = options.marginZero || this.style.anchors.marginZero;
		this.id = options.id || this.util.uid(this.type);
		this._cons = [];

		if(!this.annotation && !this.subShape){
			this.util.attr(this.container, "id", this.id);
		}

		this.connect(this, "onBeforeRender", "preventNegativePos");

		this._offX = this.mouse.origin.x;
		this._offY = this.mouse.origin.y;

		if(this.isText){
			this.align = options.align || this.align;
			this.valign = options.valign || this.valign;
			if(options.data && options.data.makeFit){
				var textObj = this.makeFit(options.data.text, options.data.width);
				this.textSize = this.style.text.size = textObj.size;
				this._lineHeight = textObj.box.h;
			}else{
				this.textSize = parseInt(this.style.text.size, 10);
				this._lineHeight = this.textSize * 1.4;
			}


			// TODO: thinner text selection
			//this.style.hitSelected.width *= 0.5;
			//
			// ouch. how verbose. My mixin is weak....
			this.deleteEmptyCreate = options.deleteEmptyCreate!==undefined ? options.deleteEmptyCreate : this.style.text.deleteEmptyCreate;
			this.deleteEmptyModify = options.deleteEmptyModify!==undefined ? options.deleteEmptyModify : this.style.text.deleteEmptyModify;
		}

		//this.drawingType

		this.attr(options.data);

		// make truthy
		// add to renders below
		// this.baseRender && render()
		//if(this.type == "dojox.drawing.tools.TextBlock"){
		if(this.noBaseRender){
			// TextBlock will handle rendering itself
			return;
		}

		//console.log("BASE OPTS:", options)
		if(options.points){
			//console.log("__________Base.constr >>>> ", this.type, "points", options.points)
			if(options.data && options.data.closePath===false){
				this.closePath = false;
			}
			this.setPoints(options.points);
			this.connect(this, "render", this, "onRender", true);
			this.baseRender && this.enabled && this.render();
			options.label && this.setLabel(options.label);
			options.shadow && this.addShadow(options.shadow);

		}else if(options.data){
			//console.log("___________Base.constr", this.type, "options data", options.data)
			options.data.width = options.data.width ? options.data.width : this.style.text.minWidth;
			options.data.height = options.data.height ? options.data.height : this._lineHeight;
			this.setData(options.data);
			this.connect(this, "render", this, "onRender", true);
			this.baseRender && this.enabled && this.render(options.data.text);
			this.baseRender && options.label && this.setLabel(options.label);
			this.baseRender && options.shadow && this.addShadow(options.shadow);

		}else if(this.draws){
			//console.log("_____________Base.constr", this.type, "draws")
			this.points = [];
			this.data = {};
			this.connectMouse();
			this._postRenderCon = dojo.connect(this, "render", this, "_onPostRender");
		}
		if(this.showAngle){
			this.angleLabel = new dojox.drawing.annotations.Angle({stencil:this});
		}

		if(!this.enabled){
			this.disable();
			this.moveToBack();
			// some things render some don't...
			this.render(options.data.text);
		}

	},
	{

		// type: String
		//		The type of Stencil this is. Should be overridden
		//		by extending classes.
		//	FIXME: should this be declaredClass?
		type:"dojox.drawing.stencil",
		//
		//	minimumSize: Number
		//		The minimum size allowed for a render. If the size
		//		is less, the shape is destroyed.
		minimumSize:10,
		//
		//	enabled [readonly] Boolean
		//		Whether the Stencil is enabled or not.
		enabled:true,


		drawingType:"stencil",

		//points:[],

		setData: function(/*StencilData*/data){
			// summary:
			//		Setter for Stencil data; also converts
			//		data to points. See individual Stencils
			//		for specific data properties.
			this.data = data;
			this.points = this.dataToPoints();
		},

		setPoints: function(/*StencilPoints*/points){
			// summary:
			//		Setter for Stencil points; also converts
			//		points to data. See individual Stencils
			//		for specific points properties.
			this.points = points;
			// Path doesn't do data
			if(this.pointsToData){
				this.data = this.pointsToData();
			}
		},

		onDelete: function(/* Stencil */ stencil){
			// summary:
			//		Stub - fires before this is destroyed
			console.info("onDelete", this.id);
		},

		onBeforeRender: function(/*Object*/ stencil){
			// summary:
			//		Stub - Fires before render occurs.
		},

		onModify: function(/*Object*/stencil){
			// summary:
			//		Stub - fires on change of any property,
			// including style properties

		},

		onChangeData: function(/*Object*/ stencil){
			// summary:
			//		Stub - fires on change of dimensional
			//	properties or a text change
		},

		onChangeText: function(value){ // value or 'this' ?
			// summary:
			//		Stub - fires on change of text in a
			//	TextBlock tool only
		},

		onRender: function(/*Object*/ stencil){
			// summary:
			//		Stub - Fires on creation.
			// 		Drawing connects to this (once!) to be
			// 		notified of drag completion. But only if it
			//		was registered as a Tool. Creating Stencil in and of
			// 		itself does not register it.
			//
			// 		This should fire
			// 		at the *end* of creation (not during drag)
			//
			//	FIXME:
			//		This should probably be onCreate. It should
			//		only fire once. But the mechanism for determining
			//		this is more complicated than it sounds.
			//
			this._postRenderCon = dojo.connect(this, "render", this, "_onPostRender");
			this.created = true;
			this.disconnectMouse();

			// for Silverlight
			if(this.shape){
				this.shape.superClass = this;
			}else{
				this.container.superClass = this;
			}
			this._setNodeAtts(this);
			//console.warn("ONRENDER", this.id, this)
		},

		onChangeStyle: function(/*Object*/stencil){
			// summary:
			//		Fires when styles of shape has changed
			//
			this._isBeingModified = true; // need this to prevent onRender
			if(!this.enabled){
				this.style.current = this.style.disabled;
				this.style.currentText = this.style.textDisabled;
				this.style.currentHit = this.style.hitNorm;

			}else{
				this.style.current = this.style.norm;
				this.style.currentHit = this.style.hitNorm;
				this.style.currentText = this.style.text;
			}

			if(this.selected){
				if(this.useSelectedStyle){
					this.style.current = this.style.selected;
					this.style.currentText = this.textSelected;
				}
				this.style.currentHit = this.style.hitSelected;

			}else if(this.highlighted){
				//this.style.current = this.style.highlighted;
				this.style.currentHit = this.style.hitHighlighted;
				//this.style.currentText = this.style.textHighlighted;
			}

			// NOTE: Can't just change props like setStroke
			//	because Silverlight throws error
			this.render();
		},

		animate: function(options, create){
			console.warn("ANIMATE..........................")
			var d = 	options.d || options.duration || 1000;
			var ms = 	options.ms || 20;
			var ease = 	options.ease || dojo.fx.easing.linear;
			var steps = options.steps;
			var ts = 	new Date().getTime();
			var w = 	100;
			var cnt = 	0;
			var isArray = true;
			var sp, ep;

			if(dojo.isArray(options.start)){
				sp =	options.start;
				ep = 	options.end;

			}else if(dojo.isObject(options.start)){
				sp =	options.start;
				ep = 	options.end;
				isArray = 	false;
			}else{

				console.warn("No data provided to animate")
			}

			var v = setInterval(dojo.hitch(this, function(){
				var t = new Date().getTime() - ts;
				var p = ease(1-t/d);
				if(t > d || cnt++ > 100){
					clearInterval(v);
					return;
				}

				if(isArray){
					var pnts = [];
					dojo.forEach(sp, function(pt, i){

						var o = {
							x: (ep[i].x-sp[i].x)*p + sp[i].x,
							y: (ep[i].y-sp[i].y)*p + sp[i].y
						};
						pnts.push(o);
					});
					this.setPoints(pnts);
					this.render();

				}else{

					var o = {};
					for(var nm in sp){
						o[nm] = (ep[nm] - sp[nm]) * p + sp[nm];
					}

					this.attr(o);

				}
				//console.dir(pnts)


				//this.attr("height", w);
				////console.log("W:", w)
				//w += 5;

			}), ms);
		},

		attr: function(/*String | Object*/key, /* ? String | Number */value){
			// summary
			//		Changes properties in the style or disabled styles,
			//		depending on whether the object is enabled.
			//		Also can be used to change most position and size props.

			// NOTE: JUST A SETTTER!! TODO!

			// WARNING:
			//	Not doing any Stencil-type checking here. Setting a height
			//	on a line or an angle on a rectangle will just not render.

			// FIXME
			// 'width' attr is used for line width. How to change the width of a stencil?
			var n = this.enabled?this.style.norm:this.style.disabled;
			var t = this.enabled?this.style.text:this.style.textDisabled;
			var ts = this.textSelected || {},
				o,
				nm,
				width,
				styleWas = dojo.toJson(n),
				textWas = dojo.toJson(t);

			var coords = {
				x:true,
				y:true,
				r:true,
				height:true,
				width:true,
				radius:true,
				angle:true
			};
			var propChange = false;
			if(typeof(key)!="object"){
				o = {};
				o[key] = value;
			}else{
				// prevent changing actual data
				o = dojo.clone(key);
			}

			if(o.width){
				// using width for size,
				// borderWidth should be used
				// for line thickness
				width = o.width;
				delete o.width;
			}

			for(nm in o){
				if(nm in n){ n[nm] = o[nm]; }
				if(nm in t){ t[nm] = o[nm]; }
				if(nm in ts){ ts[nm] = o[nm]; }

				if(nm in coords){
					coords[nm] = o[nm];
					propChange = true;
					if(nm == "radius" && o.angle===undefined){
						o.angle = coords.angle = this.getAngle();
					}else if(nm == "angle" && o.radius===undefined){
						o.radius = coords.radius = this.getRadius();
					}

				}
				if(nm == "text"){
					this.setText(o.text);
				}
				if(nm == "label"){
					this.setLabel(o.label);
				}
			}
			if(o.borderWidth!==undefined){
				n.width = o.borderWidth;
			}
			
			if(this.useSelectedStyle){
				// using the orginal selected style copy as
				// a reference map of what props to copy
				for(nm in this.style.norm){
					if(this.selCopy[nm]===undefined){
						this.style.selected[nm] = this.style.norm[nm];
					}
				}
				this.textSelected.color = this.style.selected.color;

			}

			if(!this.created){
				return;
			}

			// basic transform
			if(o.x!==undefined || o.y!==undefined){
				var box = this.getBounds(true);
				var mx = { dx:0, dy:0 };
				for(nm in o){
					if(nm=="x" || nm =="y" || nm =="r"){
						mx["d"+nm] = o[nm] - box[nm];
					}
				}
				this.transformPoints(mx);
			}


			var p = this.points;
			if(o.angle!==undefined){
				this.dataToPoints({
					x:this.data.x1,
					y:this.data.y1,
					angle:o.angle,
					radius:o.radius
				});

			} else if(width!==undefined){
				p[1].x = p[2].x = p[0].x + width;
				this.pointsToData(p);
			}

			if(o.height!==undefined && o.angle===undefined){
			console.log("Doing P2D-2");
				p[2].y = p[3].y = p[0].y + o.height;
				this.pointsToData(p);
			}

			if(o.r!==undefined){
				this.data.r = Math.max(0, o.r);
			}

			//console.dir(this.data);
			if(propChange || textWas!=dojo.toJson(t) || styleWas != dojo.toJson(n)){
				// to trigger the render
				// other events will be called post render
				this.onChangeStyle(this);
			}
			o.width = width;
			
			if(o.cosphi!=undefined){
				!this.data? this.data = {cosphi:o.cosphi} : this.data.cosphi = o.cosphi;
				this.style.zAxis = o.cosphi!=0 ? true : false;
			}
		},

		exporter: function(){
			// summary:
			//		Exports Stencil data
			//
			var type = this.type.substring(this.type.lastIndexOf(".")+1).charAt(0).toLowerCase()
				+ this.type.substring(this.type.lastIndexOf(".")+2);
			var o = dojo.clone(this.style.norm);
			o.borderWidth = o.width;
			delete o.width;
			if(type=="path"){
				o.points = this.points;
			}else{
				o = dojo.mixin(o, this.data);
			}
			o.type = type;
			if(this.isText){
				o.text = this.getText();
				o = dojo.mixin(o, this.style.text);
				delete o.minWidth;
				delete o.deleteEmptyCreate;
				delete o.deleteEmptyModify;
			}
			var lbl = this.getLabel();
			if(lbl){
				o.label = lbl;
			}
			return o;
		},


		//	TODO:
		// 		Makes these all called by att()
		//		Should points and data be?
		//
		disable: function(){
			// summary:
			//		Disables Stencil so it is not selectable.
			//		Changes the color to the disabled style.
			this.enabled = false;
			this.renderHit = false;
			this.onChangeStyle(this);
		},

		enable: function(){
			// summary:
			//		Enables Stencil so it is not selectable (if
			//		it was selectable to begin with). Changes the
			//		color to the current style.
			this.enabled = true;
			this.renderHit = true;
			this.onChangeStyle(this);
		},

		select: function(){
			// summary:
			//		Called when the Stencil is selected.
			//		NOTE: Calling this will not select the Stencil
			//		calling this just sets the style to the 'selected'
			//		theme. 'manager.Stencil' should be used for selecting
			//		Stencils.
			//
			this.selected = true;
			this.onChangeStyle(this);
		},

		deselect: function(/*Boolean*/useDelay){
			// summary:
			//		Called when the Stencil is deselected.
			//		NOTE: Calling this will not deselect the Stencil
			//		calling this just sets the style to the current
			//		theme. 'manager.Stencil' should be used for selecting
			//		and deselecting Stencils.
			//
			//	arguments:
			//		useDelay: Boolean
			//			Adds  slight delay before the style is set.
			//
			// should not have to render here because the deselection
			// re-renders after the transform
			// but... oh well.
			if(useDelay){
				setTimeout(dojo.hitch(this, function(){
					this.selected = false;
					this.onChangeStyle(this);
				}),200);
			}else{
				this.selected = false;
				this.onChangeStyle(this);
			}
		},
		_toggleSelected: function(){
			if(!this.selected){ return; }
			this.deselect();
			setTimeout(dojo.hitch(this, "select"), 0);
		},

		highlight: function(){
			// summary:
			//		Changes style to the highlight theme.
			this.highlighted = true;
			this.onChangeStyle(this);
		},

		unhighlight: function(){
			// summary:
			//		Changes style to the current theme.
			this.highlighted = false;
			this.onChangeStyle(this);
		},

		moveToFront: function(){
			// summary:
			//		Moves Stencil to the front of all other items
			//		on the canvas.
			this.container && this.container.moveToFront();
		},

		moveToBack: function(){
			// summary:
			//		Moves Stencil to the back of all other items
			//		on the canvas.
			this.container && this.container.moveToBack();
		},

		onTransformBegin: function(/* ? manager.Anchor */anchor){
			// summary:
			//		Fired at the start of a transform. This would be
			//		an anchor drag or a selection.
			//
			this._isBeingModified = true;
		},

		onTransformEnd: function(/* manager.Anchor */anchor){
			// summary:
			// 		Called from anchor point up mouse up
			this._isBeingModified = false;
			this.onModify(this);
		},

		onTransform: function(/* ? manager.Anchor */anchor){
			// summary:
			// 		Called from anchor point mouse drag
			// 		also called from plugins.Pan.checkBounds
			if(!this._isBeingModified){
				this.onTransformBegin();
			}
			// this is not needed for anchor moves, but it
			// is for stencil move:
			this.setPoints(this.points);
			this.render();
		},

		transformPoints: function(mx){
			// summary:
			//		Moves object to a new X Y location
			//		mx is additive. So mx.dx=1 will move the stencil
			//		1 pixel to the right from wherever it was.
			//
			// An attempt is made to prevent < 0 errors, but
			// this won't work on all shapes (like Axes)
			//
			if(!mx.dx && !mx.dy){
				// no change
				return;
			}
			var backup = dojo.clone(this.points), abort = false;
			dojo.forEach(this.points, function(o){
				o.x += mx.dx;
				o.y += mx.dy;
				if(o.x<this.marginZero || o.y<this.marginZero){
					abort = true;
				}
			});
			if(abort){
				this.points = backup;
				console.error("Attempt to set object '"+this.id+"' to less than zero.");
				return;
			}
			this.onTransform();
			this.onTransformEnd();
		},

		applyTransform: function(mx){
			// summary:
			//		Applies the transform to the stencil
			//		NOTE: PARTIALLY IMPLEMENTED
			//			Only applies x y coords
			this.transformPoints(mx);
		},

		setTransform: function(/*Object*/mx){
			// summary:
			//		Sets the transform to the stencil
			//		NOTE: PARTIALLY IMPLEMENTED
			//			Only applies x y coords
			this.attr({
				x:mx.dx,
				y:mx.dy
			});
		},

		getTransform: function(){
			// summary:
			//		Returns the current transform (position) of the Stencil's
			//		container
			return this.selected ? this.container.getParent().getTransform() : {dx:0, dy:0}; // Object
		},

		addShadow: function(/*Object*/args){
			args = args===true ? {} : args;
			args.stencil = this;
			this.shadow = new dojox.drawing.annotations.BoxShadow(args);
		},

		removeShadow: function(){
			this.shadow.destroy();
		},

		setLabel: function(/*String*/text){
			// summary:
			//		Creates and sets a label annotation for the Stencil.
			//		If Stencil contains a labelPosition method, that will
			//		be used for positioning. Otherwise
			//		dojox.drawing.util.positioning.label is used.
			// arguments:
			//		text: String
			//			The text to set as the label.
			//
			if(!this._label){
				this._label = new dojox.drawing.annotations.Label({
					text:text,
					util:this.util,
					mouse:this.mouse,
					stencil:this,
					annotation:true,
					container:this.container,
					labelPosition:this.labelPosition
				});
			}else if(text!=undefined){
				this._label.setLabel(text);
			}
		},
		
		getLabel: function(){
			// summary:
			//		Get the text of the label.
			//
			if(this._label){
				return this._label.getText(); // String
			}
			return null; //null
		},

		getAngle: function(){
			// summary:
			//		Gets angle of Stencil
			// NOTE: Only works for Lines, Arrows, Vectors and Axes
			//		(works on points, not transforms)
			var d = this.pointsToData();
			var obj = {
				start:{
					x:d.x1,
					y:d.y1
				},
				x:d.x2,
				y:d.y2
			};
			var angle = this.util.angle(obj, this.angleSnap);
			// converting the angle for display: -180 -> 180, -90 -> 270
			angle<0 ? angle = 360 + angle : angle;
			return angle;
		},
		getRadius: function(){
			// summary:
			//		Gets radius (length) of Stencil
			// NOTE: Only works for Lines, Arrows and Vectors
			//	(not for Ellipse, Axes has its own version)
			//
			var box = this.getBounds(true);
			var line = {start:{x:box.x1, y:box.y1}, x:box.x2, y:box.y2};
			return this.util.length(line);
		},
		getBounds: function(/* ? Boolean*/absolute){
			// summary:
			//		Returns the coordinates of the Stencil. This is often
			//		different than the data or the points.
			//		arguments:
			//			absolute: Boolean
			//				Keeps lines from flipping (see note).
			//
			// NOTE: Won't work for paths or annotations (labels, Axes, arrow tips)
			//		They should overwrite.
			// NOTE: Primarily used for checking for if shape is off
			//		canvas. Therefore Lines could get flipped. Use absolute
			//		to prevent this.
			//
			var p = this.points, x1, y1, x2, y2;
			if(p.length==2){
				if(absolute){
					x1 = p[0].x;
					y1 = p[0].y;
					x2 = p[1].x;
					y2 = p[1].y
				}else{
					x1 = p[0].x < p[1].x ? p[0].x : p[1].x;
					y1 = p[0].y < p[1].y ? p[0].y : p[1].y;
					x2 = p[0].x < p[1].x ? p[1].x : p[0].x;
					y2 = p[0].y < p[1].y ? p[1].y : p[0].y;
				}
				return {
					x1:x1,
					y1:y1,
					x2:x2,
					y2:y2,
					x:x1,
					y:y1,
					w:x2-x1,
					h:y2-y1
				}; // Object
			}else{
				return {
					x1:p[0].x,
					y1:p[0].y,
					x2:p[2].x,
					y2:p[2].y,
					x:p[0].x,
					y:p[0].y,
					w:p[2].x - p[0].x,
					h:p[2].y - p[0].y
				}; // Object
			}
		},


		preventNegativePos: function(){
			// summary:
			//		Internal. Prevent item from being drawn/rendered less
			//		than zero on the X or Y.
			//
			// if being modified anchors will prevent less than zero.
			if(this._isBeingModified){ return; }
			// FIXME: why is this sometimes empty?
			if(!this.points || !this.points.length){ return; }

			if(this.type=="dojox.drawing.tools.custom.Axes"){
				// this scenario moves all points if < 0
				var minY = this.marginZero, minX = this.marginZero;
				dojo.forEach(this.points, function(p){ minY = Math.min(p.y, minY); });
				dojo.forEach(this.points, function(p){ minX = Math.min(p.x, minX); });

				if(minY<this.marginZero){
					dojo.forEach(this.points, function(p, i){
						p.y = p.y + (this.marginZero-minY)
					}, this);
				}
				if(minX<this.marginZero){
					dojo.forEach(this.points, function(p){
						p.x += (this.marginZero-minX)
					}, this);
				}

			}else{
				// this scenario moves just the one point that is < 0
				dojo.forEach(this.points, function(p){
					p.x = p.x < 0 ? this.marginZero : p.x;
					p.y = p.y < 0 ? this.marginZero : p.y;
				});
			}
			this.setPoints(this.points);
		},

		_onPostRender: function(/*Object*/data){
			// summary:
			//		Drag-create or programmatic create calls onRender
			//		and afterwards, _onPostRender is called and
			//		manages further events.
			//
			// TODO: can this be onModify? Is that more clear?
			//
			//console.info("...........post render.....");

			if(this._isBeingModified){
				this.onModify(this);
				this._isBeingModified = false;
			}else if(!this.created){
				//this.onCreate(this);
				//this.onRender(this);
			}

			if(!this.editMode && !this.selected && this._prevData && dojo.toJson(this._prevData) != dojo.toJson(this.data)){
				//console.info("_Base data changed ----> : this.editMode:", this.editMode)
				this.onChangeData(this);
				this._prevData = dojo.clone(this.data);

			}else if(!this._prevData && (!this.isText || this.getText())){
				//console.info("_Base no prevData..........................");
				this._prevData = dojo.clone(this.data);

			}

		},

		_setNodeAtts: function(shape){
			// summary:
			//		Internal. Sets the rawNode attribute. (Or in Silverlight
			//		an "object attribute". "stencil" is
			// 		used by the application to determine if
			//		something is selectable or not. This also
			//		sets the mouse custom events like:
			//		"onStencilUp". To disable the selectability,
			//		make the att "", which causes a standard
			//		mouse event.
			//		Labels are special and used to select master stencils.
			var att = this.enabled && (!this.annotation || this.drawingType=="label") ? this.drawingType : "";
			this.util.attr(shape, "drawingType", att);
		},


		destroy: function(){
			// summary:
			//		Destroys this Stencil
			// Note:
			//		Can connect to this, but it's better to
			//		connect to onDelete
			//
			// prevent loops:
			if(this.destroyed){ return; }
			if(this.data || this.points && this.points.length){
				this.onDelete(this);
			}

			this.disconnectMouse();
			this.disconnect(this._cons);
			dojo.disconnect(this._postRenderCon);
			this.remove(this.shape, this.hit);
			this.destroyed = true;
		},

		remove: function(/*Shape...*/){
			// summary:
			//		Removes shape(s), typically before a re-render
			// 		No args defaults to this.shape
			//		Pass in multiple args to remove multiple shapes
			//
			// FIXME: Use an Array of all shapes
			//
			var a = arguments;
			if(!a.length){
				if(!this.shape){ return; }
				a = [this.shape];
			}
			for(var i=0;i<a.length;i++){
				if(a[i]){
					a[i].removeShape();
				}
			}
		},

		connectMult: function(/*dojo.connect args */){
			// summary:
			//		Convenience method for batches of quick connects
			// 		Handles are not returned and therefore cannot be
			//		disconnected until Shape destroy time
			//
			if(arguments.length>1){
				// arguments are the connect params
				this._cons.push(this.connect.apply(this, arguments));
			}else if(dojo.isArray(arguments[0][0])){
				// an array of arrays of params
				dojo.forEach(arguments[0], function(ar){
					this._cons.push(this.connect.apply(this, ar));
				}, this);
			}else{
				//one array of params
				this._cons.push(this.connect.apply(this, arguments[0]));
			}

		},

		// TODO: connect to a Shape event from outside class
		connect: function(o, e, s, m, /* Boolean*/once){
			// summary:
			//		Convenience method for quick connects
			//		See comments below for possiblities
			//		functions can be strings
			// once:
			//		If true, the connection happens only
			//		once then disconnects. Five args are required
			//		for this functionality.
			//
			var c;
			if(typeof(o)!="object"){
				if(s){
					// ** function object function **
					m = s; s = e; e=o; o = this;
				}else{
					// ** function function **
					m = e; e = o; o = s = this;
				}
			}else if(!m){
				// ** object function function **
				m = s; s = this;
			}else if(once){
				// ** object function object function Boolean **
				c = dojo.connect(o, e, function(evt){
					dojo.hitch(s, m)(evt);
					dojo.disconnect(c);
				});
				this._cons.push(c);
				return c;
			}else{
				// ** object function object function **
			}
			c = dojo.connect(o, e, s, m);
			this._cons.push(c);
			return c;
		},

		disconnect: function(/*handle | Array*/handles){
			// summary:
			//		Removes connections based on passed
			//		handles arguments
			if(!handles){ return }
			if(!dojo.isArray(handles)){ handles=[handles]; }
			dojo.forEach(handles, dojo.disconnect, dojo);
		},

		connectMouse: function(){
			// summary:
			//		Internal. Registers this Stencil to receive
			//		mouse events.
			this._mouseHandle = this.mouse.register(this);
		},
		disconnectMouse: function(){
			// summary:
			//		Internal. Unregisters this Stencil from receiving
			//		mouse events.
			this.mouse.unregister(this._mouseHandle);
		},

		// Should be overwritten by sub class:
		render: function(){
			// summary:
			// 	This Stencil's render description. Often
			//	calls 'sub render' methods.
		},
		//renderOutline: function(){},
		dataToPoints: function(/*Object*/data){
			// summary:
			//		Converts data to points.
		},
		pointsToData: function(/*Array*/points){
			// summary:
			//		Converts points to data
		},
		onDown: function(/*EventObject*/obj){
			// summary:
			//		Mouse event, fired on mousedown on canvas
			//
			// by default, object is ready to accept data
			// turn this off for dragging or onRender will
			// keep firing and register the shape
			// NOTE: Not needed for all stencils. Axes needs it.
			this._downOnCanvas = true;
			dojo.disconnect(this._postRenderCon);
			this._postRenderCon = null;
		},
		onMove: function(/*EventObject*/obj){
			// summary:
			//		Mouse event, fired on mousemove while mouse
			//		is not down.
			//		NOTE: Not currently implemented
		},
		onDrag: function(/*EventObject*/obj){
			// summary:
			//		Mouse event, fired on mousemove while mouse
			// 		is down on canvas
		},
		onUp: function(/*EventObject*/obj){
			// summary:
			//		Mouse event, fired on mouseup
		}
	}
);


}
