<@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