You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1204 lines
32 KiB
1204 lines
32 KiB
define(["dojo", "dojo/fx/easing", "../util/oo", "../annotations/BoxShadow", |
|
"../annotations/Angle", "../annotations/Label", "../defaults"], |
|
function(dojo, easing, oo, BoxShadow, Angle, Label, defaults){ |
|
|
|
/*===== |
|
var 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 |
|
}; |
|
|
|
var StencilPoint = { |
|
// summary: |
|
// One point Object in the points Array |
|
// x: Number |
|
// x position of point |
|
// y: Number |
|
// y position of point |
|
}; |
|
|
|
var 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 bottom of the file. |
|
|
|
// name: String |
|
// Fully qualified name of constructor |
|
// tooltip: String |
|
// Text to display on toolbar button hover |
|
// iconClass: String |
|
// CSS class with icon information to attach |
|
// to toolbar button. |
|
}; |
|
=====*/ |
|
|
|
var Base = oo.declare( |
|
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 || 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 Angle({stencil:this}); |
|
} |
|
|
|
if(!this.enabled){ |
|
this.disable(); |
|
this.moveToBack(); |
|
// some things render some don't... |
|
this.render(options.data.text); |
|
} |
|
|
|
}, |
|
{ |
|
// summary: |
|
// The base class used for all Stencils. |
|
// description: |
|
// All stencils extend this base class. |
|
// Most methods and events can be found here. |
|
|
|
// 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 || 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 SETTER!! 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 original 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. |
|
// 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 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. |
|
// text: String |
|
// The text to set as the label. |
|
if(!this._label){ |
|
this._label = new Label.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. |
|
// |
|
// 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. |
|
// absolute: Boolean |
|
// Keeps lines from flipping (see note). |
|
|
|
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 |
|
} |
|
} |
|
); |
|
dojo.setObject("dojox.drawing.stencil._Base", Base); |
|
return Base; |
|
});
|
|
|