@doc hierarchy="GMLDOM">
The base aspect for all graphic groups.
Group aspect assumes several things on its base object:
The inner members are stored in a property called members
The methods for adding and removing inner members in the base object are attachElement and detachElement
The event raised upon these operations is onReattachElement
(c) SAP AG 2003-2006. All rights reserved.
Aspect Group for GmlDrawing;
inherit Polyshape;
constructor(diagram, group)
this.subShapes = {};
this.subGroups = {};
this.supercall();
this.diagram.allGroups[this.id] = this;
this.group.subGroups[this.id] = this;
end
destructor
delete this.group.subGroups[this.id];
this.supercall();
if (this.diagram.allGroups[this.id]) delete this.diagram.allGroups[this.id];
this.svgNode = null;
end
//////////////////////////////////////////////////////////////////////////////////////
// BASE PROPERTIES
<@doc>
Gets or sets the group's scale
The ~scale is a positive floating point number between 0 (exclusive) and 1 (inclusive),
indicating the scale of the group's inner coordinates system (the group's contents)
relative to the group's outer coordinates system (containing group or diagram).
property scale = 0.8;
//////////////////////////////////////////////////////////////////////////////////////
// ASPECT PROPERTIES
<@doc scope="private">Indicates whether the group is animated
virtual readonly property animated = false;
<@doc scope="private">Indicates whether shapes can be attached to this group
virtual readonly property attachMode = false;
<@doc type="SVGNode" scope="private">Gets the group's inner border SVG node
virtual property borderNode = null;
<@doc scope="private">Indicates whether the group is hilighted
virtual readonly property hilighted = false;
<@doc scope="private">Gets the group's inner coordinates system x-origin (relative to the canvas)
virtual readonly property qx = 0;
<@doc scope="private">Gets the group's inner coordinates system y-origin (relative to the canvas)
virtual readonly property qy = 0;
<@doc scope="private">Gets the group's inner coordinates system scale (relative to the canvas)
virtual readonly property qs = 1;
<@doc type="SVGNode" scope="private">Gets the group's sub-shapes SVG layer
virtual property shapesLayer = null;
<@doc type="SVGNode" scope="private">Gets the group's contents SVG node
virtual property svgNode = null;
<@doc type="@Shape![id]" scope="private">Gets the sub-groups collection (i.e., the groups directly contained in this group)
virtual property subGroups = null;
<@doc type="@Shape![id]" scope="private">Gets the sub-shapes collection (i.e., all shapes directly contained in this group, including sub-groups)
virtual property subShapes = null;
//////////////////////////////////////////////////////////////////////////////////////
// ATTACH/DETACH METHODS
<@doc scope="private">
Handles shape attachment event
virtual method onAttachShape(shape)
delete shape.group.subShapes[shape.id];
shape.group.shapesLayer.removeChild(shape.shapeNode);
if (shape.isa('core.svg:Group')) {
delete shape.group.subGroups[shape.id];
this.subGroups[shape.id] = shape;
}
shape.group = this;
this.shapesLayer.appendChild(shape.shapeNode);
this.subShapes[shape.id] = shape;
shape.ox = this.qx;
shape.oy = this.qy;
shape.os = this.qs;
shape.repaint('@pos');
end
<@doc scope="private">
Handles shape detachment event
virtual method onDetachShape(shape)
if (shape.group != this) return;
this.shapesLayer.removeChild(shape.shapeNode);
delete this.subShapes[shape.id];
if (shape.isa('core.svg:Group')) {
delete this.subGroups[shape.id];
this.diagram.subGroups[shape.id] = shape;
}
this.diagram.subShapes[shape.id] = shape;
this.diagram.shapesLayer.appendChild(shape.shapeNode);
shape.group = this.diagram;
shape.ox = 0;
shape.oy = 0;
shape.os = 1;
shape.repaint('@pos');
end
<@doc scope="private">
Prepares the group for an ongoing shapes attachment interaction
virtual method setAttachMode(flag)
if (this.attachMode === flag) return;
this.shapeNode.setAttribute('pointer-events', flag ? 'visible' : 'visiblePainted');
this.attachMode = flag;
end
//////////////////////////////////////////////////////////////////////////////////////
// EVENT HANDLERS
<@doc scope="private">
Handles the group's mouseover event
override virtual method mouseover(evt, ptr)
if (!ptr) {
this.diagram.showTooltip(this);
} else if (ptr.method == 'moveHandler') {
var diag=this.diagram;
if (ptr.phase != #[PTR_MOVE]) return;
if (ptr.targetGroup && ptr.targetGroup != diag) ptr.targetGroup.unlight();
for (var g=this; g && g != diag && !g.attachMode; g=g.group);
ptr.targetGroup = g || diag;
if (ptr.targetGroup && ptr.targetGroup != diag) {
ptr.targetGroup.hilight();
this.diagram.showTooltip(ptr.targetGroup);
}
return;
}
if (this.selected || !this.diagram.isEditable) return;
this.frameNode.setAttribute('color', base.@hilightColor);
end
<@doc scope="private">
Handles the group's mouseout event
override virtual method mouseout(evt, ptr)
if (!ptr) {
this.diagram.hideTooltip();
} else if (ptr.method == 'moveHandler') {
var diag=this.diagram;
if (ptr.targetGroup && ptr.targetGroup != diag) {
ptr.targetGroup.unlight();
this.diagram.hideTooltip();
}
ptr.targetGroup = diag;
return;
}
if (this.selected || !this.diagram.isEditable) return;
this.frameNode.setAttribute('color', base.@strokeColor);
end
<@doc scope="private">
Returns the group's control handles data
override virtual method getHandlesData()
var ox=this.ox, oy=this.oy, os=this.os;
var L=ox+this.x1*os, R=ox+this.x2*os, C=ox+this.cx*os;
var T=oy+this.y1*os, B=oy+this.y2*os, M=oy+this.cy*os;
return {
NN:{cx:C,cy:T}, SS:{cx:C,cy:B}, WW:{cx:L,cy:M}, EE:{cx:R,cy:M},
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 group's tooltip positioning data
override 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.y1*os, align:'center above', offset:2};
end
//////////////////////////////////////////////////////////////////////////////////////
// PAINTING
<@doc scope="private">
Paints the group
override virtual method paint()
this.supercall();
this.shapeNode.setAttribute('pointer-events', 'visiblePainted');
this.svgNode = SVG.createElement(this.shapeNode, 'svg');
this.adjustViewbox();
this.borderNode = SVG.createElement(this.svgNode, 'rect', {x:0, y:0, width:'100%', height:'100%', 'fill':(this.diagram.isEditable ? base.@fillColor : '#F7F7F7'), 'stroke':'none', 'stroke-width':6, 'stroke-opacity':0.6, 'pointer-events':'none'});
this.shapesLayer = SVG.createElement(this.svgNode, 'g');
// BORKA: 28-Aug-2005. This fixes the bug when group was sometimes exploding on drop of element inside it.
// The same call exists in Shape.paint, but it is called there with 'firstTime=true' and as a result this.updateCoordSys()
// is not being called, resulting in desynchronization of coordinate systems.
// YG: need to optimize this
this.reposition('@shape', false);
end
<@doc scope="private">
Repositions the group after a property has changed
override virtual method reposition(prop, firstTime)
if (!this.supercall(prop, firstTime)) return false;
if (!firstTime) {
this.adjustViewbox();
this.updateCoordSys();
}
end
<@doc scope="private">
Repaints the group after its size has changed
override virtual method reshape()
this.supercall();
// move the group contents into view (assumes the group size is large enough to fit all contents into view.
var bbox=this.shapesLayer.getBBox(), pad=base.@geometry.padding, scale=base.@scale, sp0=#[SVG_SPACING];
if (bbox.width<0 || bbox.height<0) return;
var x1=bbox.x-sp0, y1=bbox.y-sp0, x2=bbox.x+bbox.width+sp0, y2=bbox.y+bbox.height+sp0;
var w=(this.w-pad[1]-pad[3])/scale, h=(this.h-pad[0]-pad[2])/scale, dx=0, dy=0;
if (x2 > w) dx = w-x2;
if (y2 > h) dy = h-y2;
if (x1+dx < 0) dx -= x1+dx;
if (y1+dy < 0) dy -= y1+dy;
if (dx != 0 || dy != 0) {
var S=this.subShapes;
for (var k in S) S[k].moveby(dx,dy);
}
end
<@doc scope="private">
Ensures the group size fits its contents (enlarging it if needed, but not contracting)
virtual method repack(recursive)
// get the group contents bounding box
var bbox=this.shapesLayer.getBBox(), pad=base.@geometry.padding, scale=base.@scale, sp0=#[SVG_SPACING];
if (bbox.width<0 || bbox.height<0) return false;
var x1=bbox.x-sp0, y1=bbox.y-sp0, x2=bbox.x+bbox.width+sp0, y2=bbox.y+bbox.height+sp0;
// align the group contents on the origin
var dx=Math.max(0,-x1), dy=Math.max(0,-y1);
if (dx != 0 || dy != 0) {
var S=this.subShapes;
for (var k in S) S[k].moveby(dx,dy);
}
// enlarge group, if needed
var nw=(x2+dx)*scale+pad[1]+pad[3], nh=(y2+dy)*scale+pad[0]+pad[2];
if (nw <= this.w && nh <= this.h) return false;
var nx = (nw > this.w ? (nw-this.w)/2-dx*scale : 0);
var ny = (nh > this.h ? (nh-this.h)/2-dy*scale : 0);
// Buffered update: part of "move shapes", "resize shape", "rotate/flip shapes", or "create shape" transactions
this.diagram.bufferedUpdate(this, '@size', Math.max(nw,this.w)+' '+Math.max(nh,this.h));
if (nx != 0 || ny != 0) this.moveby(nx,ny);
// recursively repack upward
if (recursive) {
this.diagram.plowShape(this);
if (this.group != this.diagram) this.group.repack(recursive);
}
return true;
end
<@doc scope="private">
Adjusts the group's viewbox after the group's size, position, or scale have changed
virtual method adjustViewbox()
var pad=base.@geometry.padding, scale=base.@scale;
var x=-this.w/2+pad[1], y=-this.h/2+pad[0], w=this.w-pad[1]-pad[3], h=this.h-pad[0]-pad[2];
SVG.setRect(this.svgNode, x, y, w, h);
this.svgNode.setAttribute('viewBox', '0 0 '+(w/scale)+' '+(h/scale));
end
<@doc scope="private">
Gets the group's size limits
override virtual method getSizeLimits()
var bbox=this.shapesLayer.getBBox(), w=bbox.width, h=bbox.height;
var pad=base.@geometry.padding, scale=base.@scale, sp0=#[SVG_SPACING];
this.minWidth = (Math.max(w,0)+2*sp0)*scale + pad[1] + pad[3];
this.minHeight = (Math.max(h,0)+2*sp0)*scale + pad[0] + pad[2];
return this.supercall();
end
<@doc scope="private">
Updates the coordinates system of all group's members
override virtual method updateCoordSys(ox,oy,os)
if (arguments.length == 3) {
this.ox = ox;
this.oy = oy;
this.os = os;
}
this.qx = this.ox+(this.x1+base.@geometry.padding[3])*this.os;
this.qy = this.oy+(this.y1+base.@geometry.padding[0])*this.os;
this.qs = this.os*base.@scale;
for (var k in this.subShapes) {
this.subShapes[k].updateCoordSys(this.qx, this.qy, this.qs);
}
end
<@doc scope="private">
Hilights the group using the given color
virtual method hilight(color, animate)
if (!this.borderNode) return;
this.hilighted = true;
if (!this.borderNode) return;
this.borderNode.setAttribute('stroke', color || base.@hilightColor);
if (this.animated) {
SVG.clearElement(this.borderNode);
this.animated = false;
}
if (animate) {
SVG.createFragment(this.borderNode, '');
this.animated = true;
}
end
<@doc scope="private">
Unlights the group
virtual method unlight()
if (!this.hilighted || !this.borderNode) return;
this.hilighted = false;
this.borderNode.setAttribute('stroke', 'none');
if (this.animated) {
SVG.clearElement(this.borderNode);
this.animated = false;
}
end
<@doc scope="private">
Get all sub-shapes.
Set this flag to ~true get all nested subsapes (that belong to a subgroup)
Set this flag to ~true get all lines that conenct 2 shapes inside the group.
Only set this to ~true if also ~deep is set to ~true.
a collection of shape elements
virtual method getNestedShapes(deep, with_lines, C)
//XXX: Group handling
var subshapes;
subshapes = (C == undefined ? new Object() : C);
for (var s in this.subShapes) {
var sub = this.subShapes[s];
subshapes[s] = sub;
if (sub.isa('core.svg:Group') && deep) {
sub.getNestedShapes(deep, false, subshapes);
}
}
if (deep && with_lines) {
var elements = {}
for (var x in subshapes) {
elements[x] = subshapes[x].base;
}
for (var k in this.diagram.allLines) {
//go over all links and test if their source and target are in the selected elements, and inside a state??
var line = this.diagram.allLines[k];
//find the elements what the link (base of the line) connects
var source = line.base.source && line.base.source.parent; //also can access through link
var target = line.base.target && line.base.target.parent;
if (!source || !target) continue;
//if both source and target are part of this state
if ((source.id in elements) && source.state && (target.id in elements) && target.state) {
subshapes[k] = line;
}
}
}
return subshapes;
end