@doc hierarchy="GMLDOM">
Base aspect for all graphic shapes
(c) SAP AG 2003-2006. All rights reserved.
Aspect Shape for GmlDrawing;
inherit Graphic;
constructor(diagram, group)
this.id = base.id;
this.diagram = diagram;
this.board = diagram.board;
this.group = group || diagram;
this.diagram.allShapes[this.id] = this;
this.group.subShapes[this.id] = this;
this.paint();
end
destructor
this.shapeNode.peer = null;
SVG.removeElement(this.shapeNode);
SVG.removeElement(this.wireNode);
this.shapeNode = null;
this.frameNode = null;
this.bodyNode = null;
this.wireNode = null;
delete this.diagram.allShapes[this.id];
delete this.group.subShapes[this.id];
this.group = this.diagram;
end
//////////////////////////////////////////////////////////////////////////////////////
// BASE PROPERTIES
<@doc>
Gets or sets the shape's rotation angle
The ~angle should be normalized to an even multiple of 90 degrees, according to:
Angle | Description
0 | Left-to-right orientation (default)
90 | Top-to-bottom orientation
180 | Right-to-left orientation
270 | Bottom-to-top orientation
property angle = 0;
<@doc>Gets or sets the shape's flip state
The ~flip state is a bitwise combination of the following flags:
Flag | Description
SVG_FLIPX | The shape is flipped horizontally
SVG_FLIPY | The shape is flipped vertically
property flip = 0;
<@doc>
Gets or sets the shape's center position (x y)
The ~pos property defines the shape's center point (around which the shape is rotated).
The ~pos property is relative to the shape's coordinates system (the coordinates system of the innermost group that contains the shape, or the diagram's coordinate system if the shape is not contained in any group).
The property value is a string containing the center position's x and y coordinates concatenated with a space.
The x and y coordinate values should be rounded to the nearest half of a grid unit so that the shape itself is maintained aligned to whole grid units.
property pos = '';
<@doc>
Gets or sets the shape's size (w h)
The ~size property defines the shape's width and height (prior to any rotation that may be applied on the shape).
The ~size property is relative to the shape's coordinates system (the coordinates system of the innermost group that contains the shape, or the diagram's coordinate system if the shape is not contained in any group).
The property value is a string containing the width and height values concatenated with a space.
The width and height values should be rounded to the nearest grid unit.
property size = '';
<@doc type="Object">Gets the shape's geometry constraints
The ~geometry property defines the shape's geometrical constraints using the following structure:
Name | Description
resizeMode | Shape resizing mode (see @RESIZE_MODES)
rotateMode | Shape rotating mode (see @ROTATE_MODES)
|
defWidth | Default shape width
minWidth | Minimum shape width
maxWidth | Maximum shape width
|
defHeight | Default shape height
minHeight | Minimum shape height
maxHeight | Maximum shape height
|
padding | An array of four numbers [top,right,bottom,left]
specifying the distances to use for padding between the top, right, bottom, and left frame edges and the corresponding body content boundaries. Padding is ignored on fixed-size shapes.
Aspects derived from the Shape aspect may extend the ~geometry property by adding their specific constraints.
static readonly property geometry = {
resizeMode: #[SVG_FREE_SIZE],
rotateMode: #[SVG_ROTATE_FULL],
defWidth: 100,
defHeight: 70,
padding: [0,0,0,0]
};
<@doc type="0">
Gets or sets the shape's protection mode
The shape protection mode is a bitwise combination of the following flags:
Name | Description
SVG_NONE | Do not protect the shape from any user interaction (default)
SVG_PROTECT_MOVE | Protect the shape from moving
SVG_PROTECT_RESIZE | Protect the shape from resizing
SVG_PROTECT_ROTATE | Protect the shape from rotating
SVG_PROTECT_FLIP | Protect the shape from flipping
SVG_PROTECT_TRANSFORM | Protect the shape from all transformations (move, resize, rotate, or flip)
SVG_PROTECT_SELECT | Protect the shape from selection
property protect = 0;
<@doc type="RGB">Gets or sets the shape's fill color
property fillColor = '#F5F5F5';
metadata for fillColor = {
editor: 'color',
group: 'Drawing',
label: '#TEXT[XFLD_FILL_COLOR]',
priority:100
}
<@doc type="RGB">Gets or sets the shape's stroke color
property strokeColor = '#718398';
metadata for strokeColor = {
editor: 'color',
group: 'Drawing',
label: '#TEXT[XFLD_FRAME_COLOR]',
priority:100
}
<@doc type="RGB">Gets or sets the shape's hilight color
property hilightColor = '#FF9933';
<@doc type="RGB">Gets or sets the shape's text color
property textColor = '#546374';
metadata for textColor = {
editor: 'color',
group: 'Drawing',
label: '#TEXT[XFLD_TEXT_COLOR]',
priority:100
}
<@enum name="RESIZE_MODES">
An enumeration of the available shape resize modes
The resize mode is a bitwise combination of the following groups of flags:
Name | Description
SVG_FREE_SIZE | Shape's size can be freely changed (while still honoring the other constraints)
SVG_FIXED_SIZE | Both the width and the height are fixed
SVG_AUTO_SIZE | Both the width and the height are set automatically
SVG_KEEP_SIZE | Both the width and the height are kept greater or equal to the shape's body size
|
SVG_FIXED_WIDTH | Width is fixed to ~defWidth
SVG_AUTO_WIDTH | Width is automatically set equal to the shape's body width
SVG_KEEP_WIDTH | Width is kept greater or equal to the shape's body width
|
SVG_FIXED_HEIGHT | Height is fixed to ~defHeight
SVG_AUTO_HEIGHT | Height is automatically set equal to the shape's body height
SVG_KEEP_HEIGHT | Height is kept greater or equal to the shape's body height
<@enum name="ROTATE_MODES">
An enumeration of the available shape rotating modes
The rotate mode is a bitwise combination of the following groups of flags:
Name | Description
SVG_ROTATE_FULL | The shape can be freely rotated and flipped in all directions
|
SVG_ROTATE_0 | The shape can be rotated to 0 degrees (left-to-right)
SVG_ROTATE_90 | The shape can be rotated to 90 degrees (top-to-bottom)
SVG_ROTATE_180 | The shape can be rotated to 180 degrees (right-to-left)
SVG_ROTATE_270 | The shape can be rotated to 270 degrees (bottom-to-top)
|
SVG_FLIPX | The shape can be flipped horizontally
SVG_FLIPY | The shape can be flipped vertically
|
SVG_FLIP_BODY | Flips the body contents together with the shape
SVG_FLIP_VBODY | Flips the body contents together with the shape, but only if the shape is oriented vertically (angle equals 90 or 270)
SVG_FLIP_UPSIDE | Flips or rotates the body contents to ensure they are always oriented upside, regardless of the shape's orientation
//////////////////////////////////////////////////////////////////////////////////////
// ASPECT PROPERTIES
<@doc type="@Group!" scope="private">Gets the innermost group that contains this shape, or the diagram if this is a top-level shape
virtual readonly property group = null;
<@doc type="SVGNode" scope="private">Gets the shape body's svg node
virtual readonly property bodyNode = null;
<@doc type="SVGNode" scope="private">Gets the shape frame's svg node
virtual readonly property frameNode = null;
<@doc type="SVGNode" scope="private">Gets the shape's svg node
virtual readonly property shapeNode = null;
<@doc type="SVGNode" scope="private">Gets the shape wireframe's svg node
virtual readonly property wireNode = null;
// Aspect geometry properties:
// - The shape's coordinates system origin (ox,oy) and scale (os) are defined by the containing group/diagram. All other geometry properties are defined relative to this coordinates system.
// - The shape's center position (cx,cy) and size (w,h) do not depend on the rotation angle.
// - The shape's bounding box (x1,y1,x2,y2) always reflect the rotation angle
// - The shape's rotation angle (rot) is normalized to: 0x00=0, 0x01=90, 0x02=180, 0x03=270
<@doc scope="private">Gets the shape's coordinates system x-origin (relative to the canvas)
virtual readonly property ox = 0;
<@doc scope="private">Gets the shape's coordinates system y-origin (relative to the canvas)
virtual readonly property oy = 0;
<@doc scope="private">Gets the shape's coordinates system scale (relative to the canvas)
virtual readonly property os = 1;
<@doc scope="private">Gets the shape's center x-coordinate (relative to the shape's coordinates system)
virtual readonly property cx = 0;
<@doc scope="private">Gets the shape's left x-coordinate (relative to the shape's coordinates system)
virtual readonly property x1 = 0;
<@doc scope="private">Gets the shape's right x-coordinate (relative to the shape's coordinates system)
virtual readonly property x2 = 0;
<@doc scope="private">Gets the shape's center y-coordinate (relative to the shape's coordinates system)
virtual readonly property cy = 0;
<@doc scope="private">Gets the shape's top y-coordinate (relative to the shape's coordinates system)
virtual readonly property y1 = 0;
<@doc scope="private">Gets the shape's bottom y-coordinate (relative to the shape's coordinates system)
virtual readonly property y2 = 0;
<@doc scope="private">Gets the shape's width (relative to the shape's coordinates system)
virtual readonly property w = 0;
<@doc scope="private">Gets the shape's height (relative to the shape's coordinates system)
virtual readonly property h = 0;
<@doc scope="private">Gets the shape's rotation angle (in multiples of 90 degrees)
virtual readonly property rot = 0;
//////////////////////////////////////////////////////////////////////////////////////
// ASPECT METHODS
<@doc scope="private">
Recalculates the shape's size to best fit its contents
virtual method autoFit()
var geo=base.@geometry, szmode=geo.resizeMode||0;
if (!(szmode & #[SVG_AUTO_SIZE])) return;
var bbox=this.bodyNode.getBBox(), u=#[SVG_SNAPUNIT];
var w=Math.round(this.w/u)*u, h=Math.round(this.h/u)*u;
if (szmode & #[SVG_AUTO_WIDTH]) {
var padding = (geo.padding ? geo.padding[1]+geo.padding[3] : 0);
w = Math.ceil(Math.max(bbox.width+padding, this.minWidth||geo.minWidth||u)/u)*u;
if ((szmode & #[SVG_KEEP_WIDTH]) != #[SVG_AUTO_WIDTH]) w = Math.max(w, this.w);
}
if (szmode & #[SVG_AUTO_HEIGHT]) {
var padding = (geo.padding ? geo.padding[0]+geo.padding[2] : 0);
h = Math.ceil(Math.max(bbox.height+padding, this.minHeight||geo.minHeight||u)/u)*u;
if ((szmode & #[SVG_KEEP_HEIGHT]) != #[SVG_AUTO_HEIGHT]) h = Math.max(h, this.h);
}
if (w != this.w || h != this.h) {
this.diagram.bufferedUpdate(this, '@size', Math.round(w)+' '+Math.round(h));
}
end
<@doc scope="private">
Tests whether the shape is enclosed by a given rectangle
virtual method enclosedIn(rect)
var ox=this.ox, oy=this.oy, os=this.os;
return (rect.x <= ox+this.x1*os && rect.x+rect.w >= ox+this.x2*os &&
rect.y <= oy+this.y1*os && rect.y+rect.h >= oy+this.y2*os);
end
<@doc scope="private">
Flips the shape horizontally
virtual method flipX()
var mask = (this.rot & 1 ? #[SVG_FLIPY] : #[SVG_FLIPX]);
if (!(base.@geometry.rotateMode & mask) || (base.@protect & #[SVG_PROTECT_FLIP])) return;
// Buffered update: part of "rotate/flip shapes" transaction
this.diagram.bufferedUpdate(this, '@flip', (base.@flip ^ mask));
end
<@doc scope="private">
Flips the shape vertically
virtual method flipY()
var mask = (this.rot & 1 ? #[SVG_FLIPX] : #[SVG_FLIPY]);
if (!(base.@geometry.rotateMode & mask) || (base.@protect & #[SVG_PROTECT_FLIP])) return;
// Buffered update: part of "rotate/flip shapes" transaction
this.diagram.bufferedUpdate(this, '@flip', (base.@flip ^ mask));
end
<@doc scope="private">
Gets the group that should be selected when the shape is selected
virtual method getSelGroup()
return this.group;
end
<@doc scope="private">
Gets the shape's size limits
virtual method getSizeLimits()
var geo=base.@geometry, u=#[SVG_SNAPUNIT];
var szmode=geo.resizeMode||0, data={}, bbox=null;
if (szmode & #[SVG_FIXED_WIDTH]) {
data.minWidth = data.maxWidth = this.minWidth||geo.defWidth;
} else if (szmode & #[SVG_AUTO_WIDTH]) {
if (!bbox) bbox=this.bodyNode.getBBox();
var padding = (geo.padding ? geo.padding[1]+geo.padding[3] : 0);
data.minWidth = Math.ceil(Math.max(bbox.width+padding, this.minWidth||geo.minWidth||u)/u)*u;
data.maxWidth = ((szmode & #[SVG_KEEP_WIDTH]) == #[SVG_AUTO_WIDTH]) ? data.minWidth : (geo.maxWidth||10000);
} else {
data.minWidth = this.minWidth||geo.minWidth||u;
data.maxWidth = geo.maxWidth||10000;
}
if (szmode & #[SVG_FIXED_HEIGHT]) {
data.minHeight = data.maxHeight = this.minHeight||geo.defHeight;
} else if (szmode & #[SVG_AUTO_HEIGHT]) {
if (!bbox) bbox=this.bodyNode.getBBox();
var padding = (geo.padding ? geo.padding[0]+geo.padding[2] : 0);
data.minHeight = Math.ceil(Math.max(bbox.height+padding, this.minHeight||geo.minHeight||u)/u)*u;
data.maxHeight = ((szmode & #[SVG_KEEP_HEIGHT]) == #[SVG_AUTO_HEIGHT]) ? data.minHeight : (geo.maxHeight||10000);
} else {
data.minHeight = this.minHeight||geo.minHeight||u;
data.maxHeight = geo.maxHeight||10000;
}
return data;
end
<@doc scope="private">
Tests whether the shape is in focus
virtual method inFocus()
return (this.diagram.focus == this);
end
<@doc scope="private">
Tests whether the shape intersects a given rectangle
virtual method intersects(rect)
var ox=this.ox, oy=this.oy, os=this.os;
return (Math.max(ox+this.x1*os, rect.x) <= Math.min(ox+this.x2*os, rect.x+rect.w) &&
Math.max(oy+this.y1*os, rect.y) <= Math.min(oy+this.y2*os, rect.y+rect.h));
end
<@doc scope="private">
Moves the shape by a specified offset
virtual method moveby(offx, offy)
this.moveto(this.cx+offx, this.cy+offy);
end
<@doc scope="private">
Moves the shape to a specified position
virtual method moveto(cx, cy)
if (base.@protect & #[SVG_PROTECT_MOVE]) return;
var u = #[SVG_SNAPUNIT];
var dx = (((this.rot & 1) ? this.h : this.w) % (2*u))/2;
var dy = (((this.rot & 1) ? this.w : this.h) % (2*u))/2;
var cx = Math.round((cx-dx)/u)*u+dx;
var cy = Math.round((cy-dy)/u)*u+dy;
// Buffered update: part of "move shapes", "resize shape", "rotate/flip shapes", or "create shape" transactions
this.diagram.bufferedUpdate(this, '@pos', Math.round(cx)+' '+Math.round(cy));
end
<@doc scope="private">
Handles a mutation event on the base object of this shape
virtual method onObjectMutate(evt)
switch (evt.type) {
case 'onupdateobject':
this.repaint(evt.name);
break;
case 'onreattachelement':
var diag = this.diagram;
var shape = diag.allGraphics[evt.object.id];
var newgroup = evt.newstate && diag.allGroups[evt.newstate.id] || null;
var oldgroup = evt.oldstate && diag.allGroups[evt.oldstate.id] || null;
if (newgroup) {
//XXX: Group handling
newgroup.onAttachShape(shape);
} else if (oldgroup) {
//XXX: Group handling
oldgroup.onDetachShape(shape);
}
break;
}
end
<@doc scope="private">
Handles a mutation event on a sub-object of this shape
virtual method onSubObjectMutate(evt)
// override
end
<@doc scope="private">
Rotates the shape clockwise (dir=+1) or counter-clockwise (dir=-1)
virtual method rotate(dir)
if (base.@protect & #[SVG_PROTECT_ROTATE]) return;
// calculate new rotation angle
var dir = (dir < 0 ? -1 : 1), rot=this.rot, geo=base.@geometry;
do {
rot = (rot+dir) % 4;
if (rot<0) rot += 4;
if (Math.pow(2,rot) & geo.rotateMode) break;
} while (rot != this.rot);
if (rot == this.rot) return;
// calculate new center point (adjusted to keep shape snapped to grid)
var u = #[SVG_SNAPUNIT], vert=(rot & 1);
var fn = (vert ? Math.ceil : Math.floor);
var dx = ((vert ? this.h : this.w) % (2*u))/2;
var dy = ((vert ? this.w : this.h) % (2*u))/2;
var cx = fn((this.cx-dx)/u)*u+dx;
var cy = fn((this.cy-dy)/u)*u+dy;
// Buffered update: part of "rotate/flip shapes" transaction
if (cx == this.cx && cy == this.cy) {
this.diagram.bufferedUpdate(this, '@angle', rot*90);
} else {
this.diagram.bufferedUpdate(this, '@angle', rot*90);
this.diagram.bufferedUpdate(this, '@pos', Math.round(cx)+' '+Math.round(cy));
}
end
<@doc scope="private">
Selects the shape
virtual method select()
if (base.@protect & #[SVG_PROTECT_SELECT]) return false;
if (!this.wireNode) this.createWireframe();
if (!this.selected) {
SVG.display(this.wireNode, true);
if (!GETVAR('DndPlow')) SVG.raiseElement(this.shapeNode);
this.frameNode.setAttribute('color', base.@strokeColor);
this.selected = true;
}
this.adjustWireframe();
return true;
end
<@doc scope="private">
Starts a shape rename operation
virtual method startRename()
// override
end
<@doc scope="private">
Unselects the shape
virtual method unselect()
if (!this.selected) return;
if (this.wireNode) SVG.display(this.wireNode, false);
this.selected = false;
end
//////////////////////////////////////////////////////////////////////////////////////
// EVENT HANDLERS
<@doc scope="private">
Setup the mouse pointer for shape move/click/double-click interactions
virtual method setupPointer(evt)
this.diagram.selectById(this.id, evt.ctrlKey);
if (evt.detail>1) {
var evt = SVG.createModelEvent('onDrawingAction');
evt.object = this.base;
evt.detail = #[SVG_ACTIVATE];
$ENV.fireModelEvent(evt);
} else {
return new SvgPointer('moveHandler', evt, this.diagram, this.diagram.canvas);
}
return null;
end
<@doc scope="private">
Handles the shapes's mouseover event
virtual method mouseover(evt, ptr)
if (ptr && ptr.method == 'moveHandler' && this.group != this.diagram) {
this.group.mouseover(evt, ptr);
}
if (!ptr) this.diagram.showTooltip(this);
if (this.selected || ptr || !this.diagram.isEditable) return;
this.frameNode.setAttribute('color', base.@hilightColor);
end
<@doc scope="private">
Handles the shapes's mouseout event
virtual method mouseout(evt, ptr)
if (ptr && ptr.method == 'moveHandler' && this.group != this.diagram) {
this.group.mouseout(evt, ptr);
}
if (!ptr) this.diagram.hideTooltip();
if (this.selected || ptr || !this.diagram.isEditable) return;
this.frameNode.setAttribute('color', base.@strokeColor);
end
<@doc scope="private">
Prepares the shape for an ongoing pointer interaction
virtual method setPointerMode(mode)
// override
end
<@doc scope="private">
Gets the shape's control handles data
virtual method getHandlesData()
if (base.@protect & #[SVG_PROTECT_RESIZE]) return null;
var ox=this.ox, oy=this.oy, os=this.os;
var L=ox+this.x1*os, R=ox+this.x2*os;
var T=oy+this.y1*os, B=oy+this.y2*os;
return { NW:{cx:L,cy:T}, NE:{cx:R,cy:T}, SW:{cx:L,cy:B}, SE:{cx:R,cy:B} }
end
<@doc scope="private">
Gets the shape's tooltip positioning data
virtual method getTooltipData()
var ox=this.ox, oy=this.oy, os=this.os;
return {x:ox+(this.x1+this.x2)*os/2, y:oy+this.y2*os, align:'center below', offset:4};
end
<@doc scope="private">
Resizes the shape when its control handles are dragged
virtual method onHandleMove(ptr)
switch (ptr.phase) {
case #[PTR_START]:
ptr.scrollMode = #[PTR_AUTOSCROLL];
ptr.snapUnit = GETVAR('DndSmooth') ? 1 : #[SVG_SNAPUNIT];
ptr.snapshot = this.board.captureSnapshot();
ptr.handles = {};
var H=this.diagram.handlesList, hndl=ptr.source.getAttribute('handle');
var dw=(this.rot & 1 ? this.h : this.w), dh=(this.rot & 1 ? this.w : this.h);
var ox=this.ox, oy=this.oy, os=this.os;
for (var k in H) {
if(H[k].style.getPropertyValue('display') != 'block') continue;
var dx = 2*((FLOAT(H[k].getAttribute('cx'))-ox)/os-this.cx)/dw;
var dy = 2*((FLOAT(H[k].getAttribute('cy'))-oy)/os-this.cy)/dh;
ptr.handles[k] = {dx:dx, dy:dy};
if (k == hndl) { ptr.dx=dx; ptr.dy=dy; }
}
ptr.oldbox = {x1:ox+this.x1*os, y1:oy+this.y1*os, x2:ox+this.x2*os, y2:oy+this.y2*os};
ptr.newbox = {x1:ox+this.x1*os, y1:oy+this.y1*os, x2:ox+this.x2*os, y2:oy+this.y2*os};
ptr.limits = this.getSizeLimits();
if (this.rot & 1) {
var L=ptr.limits, M;
M=L.minWidth; L.minWidth=L.minHeight; L.minHeight=M;
M=L.maxWidth; L.maxWidth=L.maxHeight; L.maxHeight=M;
}
for (var k in ptr.limits) ptr.limits[k] *= os;
break;
case #[PTR_MOVE]:
var ob=ptr.oldbox, nb=ptr.newbox, L=ptr.limits, off=ptr.offset, org=ptr.origin, u=ptr.snapUnit;
if (ptr.dy < 0) nb.y1 = Math.min(nb.y2-L.minHeight, Math.max(nb.y2-L.maxHeight, org.y-off.y/ptr.dy));
if (ptr.dy > 0) nb.y2 = Math.max(nb.y1+L.minHeight, Math.min(nb.y1+L.maxHeight, org.y+off.y/ptr.dy));
if (ptr.dx < 0) nb.x1 = Math.min(nb.x2-L.minWidth, Math.max(nb.x2-L.maxWidth, org.x-off.x/ptr.dx));
if (ptr.dx > 0) nb.x2 = Math.max(nb.x1+L.minWidth, Math.min(nb.x1+L.maxWidth, org.x+off.x/ptr.dx));
if (u != 1) nb.x1=Math.round(nb.x1/u)*u, nb.y1=Math.round(nb.y1/u)*u, nb.x2=Math.round(nb.x2/u)*u, nb.y2=Math.round(nb.y2/u)*u;
var w=nb.x2-nb.x1, h=nb.y2-nb.y1, cx=nb.x1+w/2, cy=nb.y1+h/2;
var diag=this.diagram, H=ptr.handles;
for (var k in H) {
diag.moveHandle(k, cx+H[k].dx*w/2, cy+H[k].dy*h/2);
}
if (this.rot & 1) { var t=w; w=h; h=t; }
var ox=this.ox, oy=this.oy, os=this.os;
this.adjustWireframe((cx-ox)/os, (cy-oy)/os, w/os, h/os);
break;
case #[PTR_FINISH]:
if (!ptr.isOverSource) SVG.setProperty(ptr.source, 'class', 'handleNormal');
var nb=ptr.newbox, ox=this.ox, oy=this.oy, os=this.os;
var w=nb.x2-nb.x1, h=nb.y2-nb.y1, cx=nb.x1+w/2, cy=nb.y1+h/2, t;
if (this.rot & 1) { var t=w; w=h; h=t; }
var size = {w:Math.round(w/os), h:Math.round(h/os)};
var pos = {x:Math.round((cx-ox)/os), y:Math.round((cy-oy)/os)};
this.diagram.resizeSelection(size, pos, ptr);
break;
case #[PTR_CANCEL]:
if (!ptr.isOverSource) SVG.setProperty(ptr.source, 'class', 'handleNormal');
this.repaint('@shape');
break;
}
end
//////////////////////////////////////////////////////////////////////////////////////
// PAINTING METHODS
<@doc scope="private">
Paints the shape
virtual method paint()
this.shapeNode = SVG.createElement(this.group.shapesLayer, 'g');
this.frameNode = SVG.createElement(this.shapeNode, 'g', {color:base.@strokeColor});
this.bodyNode = SVG.createElement(this.shapeNode, 'g');
this.shapeNode.peer = this;
this.reposition('@shape', true);
// Calculate tolerance pos - u/2 < base.@pos < pos + u/2
var pos=(base.@pos+''), k=pos.indexOf(' '), u=#[SVG_SNAPUNIT];
var basex = parseInt(pos.slice(0,k)), basey = parseInt(pos.slice(k+1));
var minx = this.cx - u/2, maxx = minx + u, miny = this.cy - u/2, maxy = miny + u;
if (basex > maxx || basex < minx || basey > maxy || basey < miny)
// SILENT UPDATE:
// In case the coordinates were rounded off, the position needs to be updated,
// but the shape does not need repositioning.
this.diagram.silentUpdate(this, '@pos', pos);
end
<@doc scope="private">
Repaints the shape after a property has changed
virtual method repaint(prop)
this.reposition(prop, false);
if (prop == '@shape' || prop == '@size') {
this.reshape();
}
this.board.adjustCanvas();
if (this.selected) {
this.adjustWireframe();
if (this == this.diagram.focus) this.diagram.adjustHandles();
}
end
<@doc scope="private">
Repositions the shape after its size, position, or orientation have changed
virtual method reposition(prop, firstTime)
var fullDraw=false;
switch (prop) {
case '@shape':
fullDraw=true;
// fall-through
case '@size':
var size=(base.@size+''), k=size.indexOf(' '), u=#[SVG_SNAPUNIT];
this.w = Math.round((parseInt(size.slice(0,k))||base.@geometry.defWidth)/u)*u;
this.h = Math.round((parseInt(size.slice(k+1))||base.@geometry.defHeight)/u)*u;
if (!fullDraw) break;
// fall-through
case '@angle':
this.rot = Math.round((parseInt(base.@angle+'')||0)/90) % 4;
if (this.rot<0) this.rot+=4;
if (!fullDraw) break;
// fall-through
case '@flip':
if (!fullDraw) break;
// fall-through
case '@pos':
var pos=(base.@pos+''), k=pos.indexOf(' '), u=#[SVG_SNAPUNIT];
var dw=((this.rot & 1) ? this.h : this.w), dx=(dw % (2*u))/2;
var dh=((this.rot & 1) ? this.w : this.h), dy=(dh % (2*u))/2;
this.cx = Math.round(((parseInt(pos.slice(0,k))||0)-dx)/u)*u+dx;
this.cy = Math.round(((parseInt(pos.slice(k+1))||0)-dy)/u)*u+dy;
break;
default:
return false;
}
var dx = ((this.rot & 1) ? this.h : this.w)/2;
var dy = ((this.rot & 1) ? this.w : this.h)/2;
this.x1 = this.cx-dx;
this.x2 = this.cx+dx;
this.y1 = this.cy-dy;
this.y2 = this.cy+dy;
if (fullDraw || prop == '@angle' || prop == '@pos') {
this.shapeNode.setAttribute('transform', 'translate('+this.cx+','+this.cy+')'+(this.rot ? ' rotate('+(this.rot*90)+')' : ''));
}
if (fullDraw || prop == '@angle' || prop == '@flip') {
var flip=base.@flip, geo=base.@geometry, tr1='', tr2='', tr3='';
var fx = (flip & #[SVG_FLIPX] ? -1 : 1);
var fy = (flip & #[SVG_FLIPY] ? -1 : 1);
var bx = geo.padding ? fx*(geo.padding[3]-geo.padding[1])/2 : 0;
var by = geo.padding ? fy*(geo.padding[0]-geo.padding[2])/2 : 0;
if (flip) tr1 = 'matrix('+fx+' 0 0 '+fy+' 0 0)';
if (bx || by) tr2 = 'translate('+bx+' '+by+')';
if (geo.rotateMode & #[SVG_FLIP_FLAGS]) {
if (geo.rotateMode & #[SVG_FLIP_VBODY]) {
if ((this.rot & 1) && (flip & #[SVG_FLIPY])) tr3 = 'rotate(180)';
} else if (geo.rotateMode & #[SVG_FLIP_BODY]) {
if (flip & #[SVG_FLIPY]) tr3 = 'rotate(180)';
} else if (geo.rotateMode & #[SVG_FLIP_UPSIDE]) {
if (this.rot) tr3 = 'rotate('+(360-this.rot*90)+')';
}
if (tr3) tr2 += ' '+tr3;
}
if (tr1 || !firstTime) this.frameNode.setAttribute('transform', tr1);
if (tr2 || !firstTime) this.bodyNode.setAttribute('transform', tr2);
}
return true;
end
<@doc scope="private">
Repaints the shape after its size has changed
virtual method reshape()
// override
end
<@doc scope="private">
Updates the shape's coordinates system
virtual method updateCoordSys(ox,oy,os)
if (arguments.length != 3) return;
this.ox = ox;
this.oy = oy;
this.os = os;
end
<@doc scope="private">
Creates the shape's wireframe
virtual method createWireframe()
this.wireNode = SVG.createElement(this.diagram.wireframesLayer, 'rect', {'class':'wireframe'});
end
<@doc scope="private">
Adjusts the shape's wireframe to fit the shape bounding box
virtual method adjustWireframe(cx, cy, w, h)
if (!this.wireNode) return;
if (arguments.length < 4) w=this.w, h=this.h;
if (arguments.length < 2) cx=this.cx, cy=this.cy;
var tr = 'translate('+cx+','+cy+')';
if (this.rot) tr += ' rotate('+(this.rot*90)+')';
if (this.group != this.diagram) tr = 'translate('+this.ox+','+this.oy+') scale('+this.os+') '+tr;
this.wireNode.setAttribute('transform', tr);
if (arguments == 2) return;
this.wireNode.setAttribute('x', -w/2);
this.wireNode.setAttribute('y', -h/2);
this.wireNode.setAttribute('width', w);
this.wireNode.setAttribute('height', h);
end
<@doc scope="private">
Creates the shape's sprite object
virtual method createSprite(parentNode, aspectClass, baseClass, w, h)
return SVG.createElement(parentNode, 'rect', {x:0, y:0, width:w, height:h, 'class':'spriteframe', $stroke:baseClass.prototype.@strokeColor});
end