1213 lines
32 KiB
JavaScript
1213 lines
32 KiB
JavaScript
//>>built
|
|
// wrapped by build app
|
|
define("dojox/drawing/stencil/_Base", ["dijit","dojo","dojox","dojo/require!dojo/fx/easing"], function(dijit,dojo,dojox){
|
|
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
|
|
}
|
|
}
|
|
);
|
|
|
|
|
|
});
|