<@doc hierarchy="GMLDOM"> Aspect for drawing zoomable blocks. A block is a shape that can contain any other shape, including other blocks. (c) SAP AG 2003-2006. All rights reserved. /////////////////////////////////////////////////////////////////////// // ASPECT HEADER Aspect ZBlock for ZDrawing; inherit ZShape; constructor(diagram, parent) this.isRoot = (parent == diagram); this.supercall(); end destructor // remove nested graphics var diag=this.getDiagram(); var lines = base[base.@linesKey]; for (var k in lines) diag.removeGraphic(k); var shapes = base[base.@shapesKey]; for (var k in shapes) diag.removeGraphic(k); this.supercall(); this.contentNode = null; this.canvas.removeWidget(this.id); end ////////////////////////////////////////////////////////////////////////////////////// // BASE PROPERTIES <@doc group="Instance Properties"> Gets or sets the block's scale The ~scale is a positive floating point number between 0 (exclusive) and 1 (inclusive), indicating the scale of the block's inner coordinates system (the block's contents) relative to the block's outer coordinates system (containing block or diagram). property scale = 1; ////////////////////////////////////////////////////////////////////////////////////// // STATIC PROPERTIES <@doc group="Aspect Properties"> Defines the block's lines collection key The ~linesKey is the name of the collection on the base object in which the block lines are stored static readonly property linesKey = null; <@doc> Defines the block's shapes collection key The ~shapesKey is the name of the collection on the base object in which the block shapes are stored static readonly property shapesKey = null; <@doc type="@BLOCK_LAYOUT" default="SVG_LAYOUT_NONE">Defines the block's layout constraints static readonly property layoutMode = #[SVG_LAYOUT_NONE]; <@doc type="n">Defines the spacing between adjacent shapes in the block layout static readonly property layoutSpacing = 10; <@doc type="b">Defines whether the child button should be added into this shape static readonly property childButton = false; <@struct name="BLOCK_LAYOUT" group="Structures"> A bitwise structure for defining block layout constraints A block layout can be any one of the following bitwise flags: Name | Description SVG_LAYOUT_NONE | No layout. The block shapes can be freely arranged by the user, and overlapping shapes are allowed. SVG_LAYOUT_PLOW | The block shapes can be arranged by the user, but without overlaps. Overlapping shapes are automatically plowed (moved away from each other). SVG_LAYOUT_HFLOW | The block shapes are arranged in horizontal flow. The user can control the width and relative order of the block shapes. SVG_LAYOUT_VFLOW | The block shapes are arranged in vertical flow. The user can control the height and relative order of the block shapes. SVG_LAYOUT_HSPLIT | Same as horizontal flow, but with a draggable splitter between adjacent block shapes. SVG_LAYOUT_VSPLIT | Same as vertical flow, but with a draggable splitter between adjacent block shapes. ////////////////////////////////////////////////////////////////////////////////////// // ASPECT PROPERTIES // Status flags <@doc type="b" scope="private">Indicates that this shape is a block (for quick tests) virtual property isBlock = true; <@doc type="b" scope="private">Indicates that this block is the root block virtual property isRoot = false; <@doc type="b" scope="private">Indicates whether the block is expanded virtual property isExpanded = true; <@doc type="b" scope="private">Indicates whether the block is a currently active block virtual property isActive = false; <@doc type="b" scope="private">Indicates whether the block has already been painted virtual property isPainted = false; // SVG painting objects <@doc type="SVGNode" scope="private">Gets the block's inner border SVG node virtual property borderNode = null; <@doc type="SVGNode" scope="private">Gets the block's contents SVG node virtual property contentNode = null; <@doc type="SVGNode" scope="private">Gets the block's lines SVG layer virtual property linesLayer = null; <@doc type="SVGNode" scope="private">Gets the block's shapes SVG layer virtual property shapesLayer = null; // Geometry properties <@doc scope="private">Gets the block's inner coordinates system x-origin (relative to the canvas) virtual property qx = 0; <@doc scope="private">Gets the block's inner coordinates system y-origin (relative to the canvas) virtual property qy = 0; <@doc scope="private">Gets the block's inner coordinates system scale (relative to the canvas) virtual property qs = 1; ////////////////////////////////////////////////////////////////////////////////////// // MODEL EVENTS override virtual method onModelInsert(evt) var diag = this.getDiagram(); var aspect = $DOM.getAspectOf(evt.child, '#NS[ZDrawing]') || null; if (!aspect) return; if (aspect.prototype.isShape) { // insert or move shape var shape = evt.oldParent && diag.getGraphic(evt.child.id) || null; if (shape) { // move shape from old block var oldParent=shape.getParent(); oldParent.shapesLayer.removeChild(shape.graphicNode); this.shapesLayer.appendChild(shape.graphicNode); shape.parentID = this.id; oldParent.updateToggleBtn(); } else { // create new shape shape = diag.createGraphic(evt.child, this); // resize-to-content on drop, if requested var BB = shape.base.@boundingBox; if (BB && BOOL(BB.defAuto)) { diag.begin('Auto-resize shape on create'); shape.fitToBody(true); var w = MAX(BB.defWidth||0, shape.w), h = MAX(BB.defHeight||0, shape.h); shape.invalidate('@size', w+' '+h); diag.commit(); } } if (shape) { shape.updateCoordSys(this.qx, this.qy, this.qs); var curBoard = $ENV.contextBoard || null; // When entring the below IF, it means that current shape's parent is changed via Layout board if (curBoard && curBoard.name == 'Layout') { this.getDiagram().applyGlobalLayout(); // apply global layouting ONLY in LYT board var shapeBase = shape.base; var canvas=this.canvas; if (shapeBase.@pinsKey) { var pins = shapeBase[shapeBase.@pinsKey]; for (var i in pins) { var pin = canvas.getWidget(pins[i].id); if (!pin) continue; var lines = pin.lines; for (var j in lines) { var line=canvas.getWidget(j); if (!line) continue; line.reroute(true); } } } } } this.updateToggleBtn(); if (diag.isZoomable && shape.isBlock) diag.setActiveBlock(shape); // TODO: BlockZoom work } else if (aspect.prototype.isLine) { // insert or move line var line = evt.oldParent && diag.getGraphic(evt.child.id) || null; if (line) { // move line from old block line.getParent().linesLayer.removeChild(line.graphicNode); this.linesLayer.appendChild(line.graphicNode); line.parentID = this.id; } else { // create new line line = diag.createGraphic(evt.child, this); } } else if (aspect.prototype.isPin) { // insert pin this.supercall(); } end override virtual method onModelRemove(evt) var diag=this.getDiagram(), id=evt.child.id, g=diag.getGraphic(id); if (!g) { // remove pin this.supercall(); } else if (!evt.newParent) { // remove shape or line diag.removeGraphic(id); this.updateToggleBtn(); } end ////////////////////////////////////////////////////////////////////////////////////// // PAINTING METHODS override virtual method paint() this.supercall(); // create block content area this.contentNode = SVG.createElement(this.graphicNode, 'svg', {overflow: 'visible'}); var borderProps = {x:0, y:0, width:'100%', height:'100%', fill:'none', stroke:'none', tooltip:'hide', 'pointer-events':'none'}; if (!this.isRoot) { borderProps['stroke-width'] = 4; borderProps['stroke-opacity'] = 0.6; borderProps['pointer-events'] = 'visible'; borderProps['stroke-linejoin'] = 'miter'; } this.borderNode = SVG.createElement(this.contentNode, 'rect', borderProps); this.shapesLayer = SVG.createElement(this.contentNode, 'g'); this.linesLayer = SVG.createElement(this.contentNode, 'g'); this.repaintBackground(); this.paintContents(); this.updateToggleBtn(); this.updateChildBtn(); // create shape remark if (!ISEMPTY(base.comment)) this.createRemarkNode(); end virtual method paintContents() if (this.isPainted) return; // create block shapes var diag = this.getDiagram(); var C = base[base.@shapesKey]; for (var k in C) { var aspect = $DOM.getAspectOf(C[k], '#NS[ZDrawing]'); if (aspect && aspect.prototype.isShape) { diag.createGraphic(C[k], this); } } // create block lines var L = base[base.@linesKey]; for (var k in L) { var aspect = $DOM.getAspectOf(L[k], '#NS[ZDrawing]'); if (aspect && aspect.prototype.isLine) { diag.createGraphic(L[k], this); } } this.isPainted = true; end override virtual method repaint(prop) var flags = this.supercall(); // adjusts the block's viewbox after size or scale have changed if (flags & (#[SVG_REPAINT_SIZE|SVG_REPAINT_SCALE])) { var FP=base.@framePadding, SC=base.@scale; var x=-this.w/2+FP.left, y=-this.h/2+FP.top; var w=this.w-FP.left-FP.right, h=this.h-FP.top-FP.bottom; SVG.setRect(this.contentNode, x, y, w, h); this.contentNode.setAttribute('viewBox', '0 0 '+(w/SC)+' '+(h/SC)); } // adjusts the block's inner coordinates system if any of the positioning properties has changed if (flags) { this.updateCoordSys(); } end override virtual method repaintBackground() if (!base) return; var color=base.@fillColor, opacity=1; // var color='#FFFFFF', opacity=1; if (this.isRoot) { color='none'; } else if (GETVAR('SVG_COLOR_NESTING')) { if (this.isSelected) { color=this.hilightColor, opacity=0.2; } else { color=base.@fillColor, opacity=0.05; } } SVG.setProperty(this.borderNode, '$fill', color); SVG.setProperty(this.borderNode, '$fill-opacity', opacity); end ////////////////////////////////////////////////////////////////////////////////////// // BLOCK EXPAND/COLLAPSE virtual method expand() if (this.isExpanded) return; if (!this.isPainted) { this.paintContents(); this.repaint(); } else { SVG.show(this.shapesLayer); SVG.show(this.linesLayer); } this.isExpanded = true; this.updateToggleBtn(); end virtual method collapse() if (!this.isExpanded || !this.isPainted) return; SVG.hide(this.shapesLayer); SVG.hide(this.linesLayer); this.isExpanded = false; this.updateToggleBtn(); end virtual method setHorizon(horizon, depth) if (!depth) depth=0; if (depth >= horizon) this.collapse(); else this.expand(); var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var g=canvas.getWidget(k); if (g && g.isBlock) g.setHorizon(horizon, depth+1); } end virtual method updateToggleBtn() // TODO: BlockZoom: in zoomable diagram, any zoom-able block should always have toggle button, therefore // simplify the code here and remove updateToggleBtn() calls from everywhere, except expand/collapse if (!this.getDiagram().isZoomable || this.isRoot) return; var btn=this.btnToggle; // TODO: BlockZoom: uncomment and fix this when block zoomability becomes controllable //if (ISEMPTY(this.shapes)) { // if (btn) btn.style.setProperty('display', 'none'); // return; //} var state=(this.isExpanded ? 1 : 0) if (!btn) { this.btnToggle = SVG.createFlipper(this.contentNode, ['blockExpandBtn', 'blockCollapseBtn'], {target:this, onClick:'onToggleBtn', width:11, state:state, x:15} ); SVG.setTransform(this.btnToggle, 3, 3); } else { btn.style.setProperty('display', 'block'); btn.setState(state); } end virtual method onToggleBtn() var diag = this.getDiagram(); if (diag.isZoomable) diag.setActiveBlock(this.isExpanded ? this.getParent() : this); else if (this.isExpanded) this.collapse(); else this.expand(); this.updateToggleBtn(); end virtual method updateChildBtn() if(!base.@childButton || this.isRoot) return; var btn=this.childBtn; if (!btn) { // TODO: get this via defineShape and not via DOM dependency var ns = base.Class.metadata['layerNamestem']||null; var tooltip = ns ? ('#TEXT[XTOL_DOM_TCONTAINER_ADD_PREFIX]' + ' ' + ns) : null; this.childBtn = SVG.createButton(this.contentNode, 'blockChildBtn', {glyph: 'childBtn', target:this, onClick:'onClickChildBtn', tooltip: tooltip, width:11, x:0}, SvgClickable); SVG.setTransform(this.childBtn, 3, 3); } end virtual method onClickChildBtn() RULE('createChildShape', $ENV.contextUnit, this.getDiagram().boardAspect, base); end ////////////////////////////////////////////////////////////////////////////////////// // INTERACTIVE EFFECTS override virtual method showSelectionEffect() this.supercall(); this.repaintBackground(); end override virtual method hideSelectionEffect() this.supercall(); this.repaintBackground(); end override virtual method showDropEffect(animate) if (this.isDropTarget) return; if (this.isRoot && !GETVAR('SVG_SHOW_BORDER')) return; this.isDropTarget = true; this.borderNode.setAttribute('stroke', this.hilightColor); if (this.isAnimated) { SVG.clearElement(this.borderNode); this.isAnimated = false; } if (animate) { SVG.createFragment(this.borderNode, ''); this.isAnimated = true; } end override virtual method hideDropEffect() if (!this.isDropTarget) return; if (this.isRoot && !GETVAR('SVG_SHOW_BORDER')) return; this.isDropTarget = false; this.borderNode.setAttribute('stroke', 'none'); if (this.isAnimated) { SVG.clearElement(this.borderNode); this.isAnimated = false; } end <@doc scope="private"> Shows the block's wiring effect virtual method showWiringEffect(ptr) var diag = this.getDiagram(); var canvas = this.canvas; if (diag.isZoomable) this.showZoomableEffect(); // TODO: BlockZoom: temp // if this block has already been visited during this wiring interaction, hilight previously tested pins if (this.id in ptr.visitedBlocks) { var pins=ptr.visitedBlocks[this.id]; for (var k in pins) { var pin = canvas.getWidget(k); if (pin) pin.showWiringEffect(ptr); } return; } // otherwise, scan and test all pins in this block var pins={}, checked = { target:null, list:{} }; var srcpin = ptr.getSrcpin && ptr.getSrcpin() || canvas.getWidget(ptr.srcpinID); var shapes = base[base.@shapesKey]; for (var k in shapes) { var shape=canvas.getWidget(k); if (!shape) continue; var shapeBase = shape.base; if (!shapeBase.@pinsKey) continue; var P = shapeBase[shapeBase.@pinsKey]; for (var k2 in P) { var pin=canvas.getWidget(P[k2].id); if (!pin || pin == srcpin) continue; if (pin.isInward) { if (srcpin.isInward) continue; var src=srcpin.base, trg=pin.base; } else if (pin.isOutward) { if (srcpin.isOutward) continue; var src=pin.base, trg=srcpin.base; } else { // pin.isInout if (srcpin.isOutward) var src=srcpin.base, trg=pin.base; else var src=pin.base, trg=srcpin.base; } if (!RULE('canConnectTo', diag.base, diag.boardAspect, src, trg)) continue; if (RULE('isCyclicConnection', diag.base, diag.boardAspect, src, trg, checked)) continue; pin.showWiringEffect(ptr); pins[pin.id] = true; } } ptr.visitedBlocks[this.id] = pins; end <@doc scope="private"> Hides the block's wiring effect virtual method hideWiringEffect(ptr) if (this.getDiagram().isZoomable) this.hideZoomableEffect(); // TODO: BlockZoom: temp var canvas = this.canvas; var pins=ptr.visitedBlocks[this.id]; if (!pins) return; for (var k in pins) { var pin = canvas.getWidget(k); if (pin) pin.hideWiringEffect(ptr); } end override virtual method flyIntoView() this.expand(); return this.supercall(); end ////////////////////////////////////////////////////////////////////////////////////// // GEOMETRIC CALCULATIONS <@doc scope="private"> Updates the coordinates system of all block's members override virtual method updateCoordSys(ox,oy,os) if (arguments.length == 3) { this.ox = ox; this.oy = oy; this.os = os; } var FP = base.@framePadding; this.qx = this.ox+(this.x1+FP.left)*this.os; this.qy = this.oy+(this.y1+FP.top)*this.os; this.qs = this.os*base.@scale; var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape=canvas.getWidget(k); if (shape) shape.updateCoordSys(this.qx, this.qy, this.qs); } end <@doc scope="private"> Gets the block's size limits override virtual method getSizeLimits(sx,sy) // TODO: TEMP for BlockZoom var bbox = this.getDiagram().isZoomable ? this.getBBox() : this.shapesLayer.getBBox(); var x1=bbox.x, y1=bbox.y, x2=x1+bbox.width, y2=y1+bbox.height; var FP = base.@framePadding var BP = base.@bodyPadding; var BB = base.@boundingBox; var SC = base.@scale; if (base.@layoutMode & #[SVG_LAYOUT_FLOW]) { this.minWidth = BP.left+BP.right; this.minHeight = BP.top+BP.bottom; } else { if (sx < 0) { this.minWidth = POS(this.w-(x1-BP.left)*SC); } else if (sx > 0) { this.minWidth = POS((x2+BP.right)*SC) + FP.right + FP.left; } else { this.minWidth = 0; } this.minWidth = MAX(this.minWidth, BB.minWidth||0); if (sy < 0) { this.minHeight = POS(this.h-(y1-BP.top)*SC); } else if (sy > 0) { this.minHeight = POS((y2+BP.bottom)*SC) + FP.top + FP.bottom; } else { this.minHeight = 0; } this.minHeight = MAX(this.minHeight, BB.minHeight||0); } return this.supercall(); end <@doc scope="private"> Returns the block'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 block's tooltip positioning data override virtual method getTooltipPos() var ox=this.ox, oy=this.oy, os=this.os; return {x:ox+(this.x1+this.x2)*os/2, y:oy+this.y1*os, dy:1, color:base.@strokeColor}; end ////////////////////////////////////////////////////////////////////////////////////// // LAYOUT GENERAL <@doc scope="private"> Adjusts the block layout after its contents have been rearranged to ensure all layout constraints are satisfied virtual method adjustLayout(anchors) var func='adjustLayout'+base.@layoutMode; if (func in this) this[func](anchors); end <@doc scope="private"> Resizes the contents to exactly fit the block virtual method resizeContents() var func='resizeContents'+base.@layoutMode; if (func in this) this[func](); end <@doc scope="private"> Enlarges the block to encompass all its contents override virtual method enlargeToContents() // get the block contents bounding box // TODO: temp for BlockZoom work var bbox = this.getDiagram().isZoomable ? this.getBBox() : this.shapesLayer.getBBox(); if (bbox.width<0 || bbox.height<0) return false; // calculate new bounding box var FP = base.@framePadding; var BP = base.@bodyPadding; var SC = base.@scale, u=#[SVG_SNAPUNIT]; var dx1 = Math.min((bbox.x-BP.left)/SC, 0); var dy1 = Math.min((bbox.y-BP.top)/SC, 0); var dx2 = Math.max((bbox.x+bbox.width+BP.right)/SC+FP.left+FP.right-this.w, -0); var dy2 = Math.max((bbox.y+bbox.height+BP.bottom)/SC+FP.top+FP.bottom-this.h, -0); if (dx1 > -u && dy1 > -u && dx2 < u && dy2 < u) return; // snap new bounding box to grid units var x1=this.x1+dx1, x2=this.x2+dx2, sx=(x2-x1) % u; var y1=this.y1+dy1, y2=this.y2+dy2, sy=(y2-y1) % u; if (sx != 0) { if (dx1 < 0) x1-=(u-sx); else x2+=(u-sx); } if (sy != 0) { if (dy1 < 0) y1-=(u-sy); else y2+=(u-sy); } // update block bounding box this.shiftContents(-dx1,-dy1); this.moveto((x1+x2)/2, (y1+y2)/2); this.sizeto((x2-x1),(y2-y1)); end <@doc scope="private"> Shifts the block contents by the given offset virtual method shiftContents(dx,dy) if (dx==0 && dy==0) return; var S= base[base.@shapesKey], SC=base.@scale, canvas=this.canvas; for (var k in S) { var shape = canvas.getWidget(k); if (shape) shape.moveby(dx*SC,dy*SC); } end override virtual method fitToBody(force) var BB = base.@boundingBox; var FP = base.@framePadding; var BP = base.@bodyPadding; var SM = force ? #[SVG_AUTO_SIZE] : base.@resizeMode||0; if (!(SM & #[SVG_AUTO_SIZE])) return; var bbox=this.getBBox(), u=#[SVG_SNAPUNIT]; var w = Math.ceil(Math.max(bbox.width+FP.left+FP.right+BP.left+BP.right, this.minWidth||BB.minWidth||u)/u)*u; var h = Math.ceil(Math.max(bbox.height+FP.top+FP.bottom+BP.top+BP.bottom, this.minHeight||BB.minHeight||u)/u)*u; this.shiftContents(-(bbox.x-BP.left), -(bbox.y-BP.top)); if (w != this.w || h != this.h) this.invalidate('@size', w+' '+h); end ////////////////////////////////////////////////////////////////////////////////////// // NO LAYOUT virtual method adjustLayout#[SVG_LAYOUT_NONE](anchors) this.enlargeToContents(); end ////////////////////////////////////////////////////////////////////////////////////// // PLOW LAYOUT virtual method adjustLayout#[SVG_LAYOUT_PLOW](anchors) var diag = this.getDiagram(), canvas = this.canvas; if (diag.plowMode) { var sp0 = base.@layoutSpacing; // Minimum space between shapes (0 <= SP0 <= SP1) var sp1 = base.@layoutSpacing; // Plow space for move on drop // scan and plow block shapes, depending on number of given anchors var block=this, shapes=base[base.@shapesKey]; if (!anchors || typeof anchors != 'object') { for (var k in shapes) { var shape = canvas.getWidget(k); if (shape) plow(shape, null); } } else if (anchors.isa && anchors.isa('#NS[ZShape]')) { plow(anchors, null); } else { for (var k in anchors) { if (!anchors[k].isShape) continue; plow(anchors[k], anchors); delete anchors[k]; } } } // TODO: BlockZoom: find out why enlargeToContents moves the block and start using either 'fit' or 'enlarge' but not both if (diag.isZoomable) this.fitToBody(true); else this.enlargeToContents(); function plow(shape, anchors) { // scan all placeable child elements into: P=neighbor elements, Q=remaining elements if (shape.base.@protect & #[SVG_PROTECT_LAYOUT]) return; var P={}, Q={}, B=block.base[block.base.@shapesKey]; var x1=shape.x1-sp0, y1=shape.y1-sp0, x2=shape.x2+sp0, y2=shape.y2+sp0; var A = {}; for (var k in B) { var e = ISA(B[k], 'gml2:PAttachedElement') && B[k].attachedElement || null; if (e) { if ((k + e.id in A) || (e.id + k in A)) continue; A[k + e.id] = mergeAttachedElements(k, e.id) } else { A[k] = canvas.getWidget(k); } } for (var k in A) { var b = A[k]; if (!b || b == shape || (anchors && ((k in anchors) || b.inAncors)) || (b.base.@protect & #[SVG_PROTECT_LAYOUT])) continue; if (Math.max(b.x1,x1) < Math.min(b.x2,x2) && Math.max(b.y1,y1) < Math.min(b.y2,y2)) P[k]=b; else Q[k]=b; } // for each neighbor element, calculate the dx,dy shift required to plow it away and then recursively // apply the same shift on any remaining elements that become touched as a result for (var k in P) { // find the line and angle connecting the centers of the shape and its neighbor var p=P[k], d=calcDelta(p, shape); // do the actual plow p.moveby(d.dx,d.dy); qplow(p); } function calcDelta(p, q) { var w0=(q.x2-q.x1)/2+sp1, h0=(q.y2-q.y1)/2+sp1; // find the line and angle connecting the centers of the shape and its neighbor var dx=p.cx-q.cx, dy=p.cy-q.cy, a=Math.atan2(dy, dx); // compute the new center of the neighbor var w1=(p.x2-p.x1)/2, h1=(p.y2-p.y1)/2; if (Math.abs(dy) <= Math.abs(dx)) { var nx = (dx < 0 ? -1 : 1) * (w0 + w1); var ny = nx * Math.tan(a); } else { var ny = (dy < 0 ? -1 : 1) * (h0 + h1); var nx = ny / Math.tan(a); } return {dx:nx-dx, dy:ny-dy}; } // recursively detect and shift any remaining shapes function qplow(p) { var R={}, x1=p.x1-sp0, y1=p.y1-sp0, x2=p.x2+sp0, y2=p.y2+sp0; for (var k in Q) { var q=Q[k]; if (Math.max(q.x1,x1) >= Math.min(q.x2,x2) || Math.max(q.y1,y1) >= Math.min(q.y2,y2)) continue; var d = calcDelta(q, p); q.moveby(d.dx,d.dy); delete Q[k]; R[k] = q; } Q[p.id] = p; for (var k in R) qplow(R[k]); } function mergeAttachedElements(id1, id2) { var e1 = canvas.getWidget(id1) var e2 = canvas.getWidget(id2) var elem = { id: id1 + id2, base: {}, inAncors: anchors && ((e1.id in anchors) || (e2.id in anchors)), updateData: function() { var x1 = Math.min(e1.x1, e2.x1); var x2 = Math.max(e1.x2, e2.x2); var y1 = Math.min(e1.y1, e2.y1); var y2 = Math.max(e1.y2, e2.y2); this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; this.cx = (x1 + x2) / 2; this.cy = (y1 + y2) / 2; }, moveby: function(dx, dy) { e1.moveby(dx, dy); e2.moveby(dx, dy); this.updateData(); } } elem.updateData(); return elem; } } end ////////////////////////////////////////////////////////////////////////////////////// // VFLOW LAYOUT virtual method resizeContents#[SVG_LAYOUT_VFLOW]() return var BP = base.@bodyPadding; var W = this.w-BP.left+BP.right; var H = 0; // collect shape dimensions var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape = canvas.getWidget(k); if (shape) H += shape.h; } if (H == 0) return; var dH = (this.h-BP.top-BP.bottom)/H; // resize shapes proportionally for (var k in shapes) { var shape=canvas.getWidget(k); if (shape) shape.sizeto(W, shape.h*dH); } this.adjustLayout(); end virtual method adjustLayout#[SVG_LAYOUT_VFLOW](anchors) var SP = base.@layoutSpacing; var BP = base.@bodyPadding; var W = BP.left+BP.right; var H = BP.top+BP.bottom; var Y = BP.top; var L = []; // collect shape dimensions var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape = canvas.getWidget(k); if (!shape) continue; W = Math.max(shape.w, W); H += shape.h; L.push({y:shape.cy, shape:shape}); } var len=L.length; if (len == 0) return; H += (len-1)*SP; if (anchors && anchors.isa && anchors.isa('#NS[ZShape]')) W=anchors.w; // sort and reposition shapes SORTN(L,'y') for (var i=0; i Handles the block's mouseover event override virtual method mouseover(evt, ptr) if (!ptr) { this.supercall(); return; } var canvas = this.canvas; switch (ptr.method) { case 'moveHandler': var diag=this.getDiagram(), trg=this.canvas.getWidget(ptr.dropTargetID); if (!ptr.canRecompose) break; if (this == trg || (ptr.phase != #[PTR_MOVE])) break; for (var trg2=this; trg2 && !trg2.isDragSource; trg2=trg2.getParent&&trg2.getParent()); if (trg2) break; //TODO: canInsert check is temp var shapes=ptr.shapes, canInsert=true; for (var k in shapes) { canInsert = RULE('canInsert', diag.base, diag.boardAspect, base, shapes[k]); if (!canInsert) break; } if (!canInsert) break; if (ptr.visualCues) { if (trg && trg != diag) trg.hideDropEffect(); if (canInsert) { //TODO: temp this.showDropEffect(); diag.showTooltip(this); } } ptr.dropTargetID = this.id; break; case 'wiringHandler': var diag=this.getDiagram(), trg=ptr.wireTargetID; if (diag.isZoomable && !this.isExpanded) { var node = evt.target && evt.target.parentNode; if (node == this.btnToggle) { // TODO: BlockZoom work if (this.canvas.zoomTimer) diag.canvas.zoomTimer.stop(); var timer = new SvgTimer({target: delayedZoom, delay: 400}); timer.blockID = this.id; timer.wire = ptr.wire; timer.srcpin = ptr.getSrcpin && ptr.getSrcpin() || canvas.getWidget(ptr.srcpinID); this.canvas.zoomTimer = timer; timer.start(); } } if (this.id == trg) break; trg = this.canvas.getWidget(trg); if (trg) trg.hideWiringEffect(ptr); this.showWiringEffect(ptr); ptr.wireTargetID = this.id; break; } function delayedZoom(timer) { var canvas = BOARD.canvas; var t = canvas.zoomTimer; if (!t || (t.id != timer.id)) return; canvas.zoomTimer = null; var block = canvas.getWidget(t.blockID); if (!block || block.isExpanded) return; block.hideZoomableEffect(); // stop toggleBtn highliting block.getDiagram().zoomBlock(block); // update wiring link source after its shape could have been moved by zoom operation var wire = t.wire, srcpin = t.getSrcpin && t.getSrcpin() || canvas.getWidget(t.srcpinID), shape = srcpin.getShape(); wire.setAttribute('x1', shape.ox+(shape.cx+srcpin.x)*shape.os); wire.setAttribute('y1', shape.oy+(shape.cy+srcpin.y)*shape.os); } end <@doc scope="private"> Handles the block's mouseout event override virtual method mouseout(evt, ptr) if (!ptr) { this.supercall(); return; } end ////////////////////////////////////////////////////////////////////////////////////// // ZOOM HANDLING virtual method showZoomableEffect() if (this.isExpanded) { var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape = canvas.getWidget(k); if (shape && shape.isBlock) shape.showZoomableEffect(); } } else { var btn = this.btnToggle; if (!btn) return; SVG.setProperty(btn, 'color', this.hilightColor); SVG.createFragment(btn, ''); } end virtual method hideZoomableEffect() if (this.isExpanded) { var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape = canvas.getWidget(k); if (shape && shape.isBlock) shape.hideZoomableEffect(); } } var btn = this.btnToggle; if (!btn) return; SVG.clearElement(btn); this.btnToggle = null; this.updateToggleBtn(); end override virtual method showMovableEffect() if (this.getDiagram().alphaMode) SVG.setProperty(this.frameNode, 'opacity', 1); end override virtual method hideMovableEffect() if (this.getDiagram().alphaMode) SVG.setProperty(this.frameNode, 'opacity', 0.2); end override virtual method showActiveEffect() this.showMovableEffect() if (!this.isExpanded) return; var canvas = this.canvas; var shapes = base[base.@shapesKey]; for (var k in shapes) { var g = canvas.getWidget(k); if (g) g.showMovableEffect(); } var lines = base[base.@linesKey]; for (var k in lines) { var g=canvas.getWidget(lines[k].id); if (g) g.showMovableEffect(); } end override virtual method hideActiveEffect() // TODO: end override virtual method showZoomedEffect() this.hideMovableEffect(); if (!this.isExpanded) return; var canvas = this.canvas; var shapes = base[base.@shapesKey]; for (var k in shapes) { var g = canvas.getWidget(k); if (g) g.hideMovableEffect(); } var lines = base[base.@linesKey]; for (var k in lines) { var g=canvas.getWidget(lines[k].id); if (g) g.hideMovableEffect(); } end virtual method Zoom(activate) var diag = this.getDiagram(); var path = []; for (var b = this; b && b.isBlock; b = b.getParent()) path.push(b); for (var i = path.length-1; i >= 0; i--) { var b = path[i]; b.expand(); b.restorePositions(); b.fitToBody(true); // TODO: see if fitToBody is needed (adjustBlockLayout[PLOW] does this anyway) diag.adjustBlockLayout(b.getParent(), b); if ((i > 0) || !activate) b.showZoomedEffect(); } if (activate) { this.showActiveEffect(); this.isActive = true; } end virtual method Unzoom(deactivate) if (deactivate) { this.isActive = false; this.hideActiveEffect(); this.savePositions(); } for (var b = this; b && b.isBlock && !b.isRoot; b = b.getParent()) { b.collapse(); b.sizeto(80, 60); } end virtual method ZoomAll() var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var g = canvas.getWidget(k); if (g && g.isBlock) g.ZoomAll(); } this.Zoom(false); end virtual method UnzoomAll() var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var g = canvas.getWidget(k); if (g && g.isBlock) g.UnzoomAll(); } this.collapse(); this.sizeto(80, 60); end virtual method savePositions() var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var g = canvas.getWidget(k); if (g) g.base.setProperty('@xpos', g.cx+' '+g.cy, true); } end virtual method restorePositions() var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var g = canvas.getWidget(k); if (g && g.base.hasProperty('@xpos')) { var p = SPLITN(g.base.@xpos); g.moveto(p[0]||0, p[1]||0); } } end virtual method getBBox() var boxes = []; var shapes = base[base.@shapesKey], canvas = this.canvas; for (var k in shapes) { var shape = canvas.getWidget(k); if (!shape) continue; var os = shape.os; boxes.push({x: shape.x1*os, y: shape.y1*os, w: shape.w*os, h: shape.h*os}); } if (boxes.length == 0) return this.frameNode.getBBox(); var m=Number.MAX_VALUE, x1=m, y1=m, x2=-m, y2=-m; for (var i=0, len = boxes.length; i < len; i++) { var box = boxes[i]; x1 = Math.min(x1, box.x); y1 = Math.min(y1, box.y); x2 = Math.max(x2, box.x + box.w); y2 = Math.max(y2, box.y + box.h); } return {x: x1, y: y1, width: x2-x1, height: y2-y1}; end