<@doc hierarchy="GMLDOM"> Aspect for drawing zoomable shapes. A shape is graphic that occupies a bound region, has multiple frame and body parts, and has optional connection pins. A zoomable shape is a shape that can display different degrees of detail at different zoom levels. (c) SAP AG 2003-2006. All rights reserved. /////////////////////////////////////////////////////////////////////// // ASPECT HEADER Aspect ZShape for ZDrawing; inherit ZGraphic; constructor(diagram, parent) this.supercall(); this.hilightColor = diagram.base.@hilightColor; this.hilightFill = diagram.base.@hilightFill; this.paint(); this.repaint(); end destructor var diag=this.getDiagram(); var pins = base[base.@pinsKey]; for (var k in pins) diag.removeGraphic(k); SVG.removeElement(this.graphicNode); SVG.removeElement(this.wireNode); this.graphicNode = null; this.frameNode = null; this.bodyNode = null; this.wireNode = null; this.isSelected = false; this.parentID = ''; this.canvas.removeWidget(this.id); //TODO: delete this['base']; end ////////////////////////////////////////////////////////////////////////////////////// // BASE PROPERTIES <@doc group="Instance Properties"> 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 type="RGB">Gets or sets the shape's fill color property fillColor = '#F5F5F5'; <@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 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 containing block (the coordinates system of the innermost block that contains the shape, or the diagram's coordinate system if the shape is not contained in any block). 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 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 SVG_PROTECT_LAYOUT | Protect the shape from layout property protect = 0; <@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 containing block (the coordinates system of the innermost block that contains the shape, or the diagram's coordinate system if the shape is not contained in any block). 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 = ''; ////////////////////////////////////////////////////////////////////////////////////// // STATIC PROPERTIES <@doc type="Object" group="Aspect Properties"> Defines the shape's body padding Use this property to specify the body padding sizes (the distances to keep between the body and the body contents), according to: Name | Description top | Top padding right | Right padding bottom | Bottom padding left | Left padding static readonly property bodyPadding = {top:0, right:0, bottom:0, left:0}; <@doc type="@SHAPE_PARTS[]"> Defines the shape's body parts Use this property to define the graphical parts that draw the shape's contents. The bounding box of the body parts defines the shape's size during auto-size calculations. Therefore, body parts cannot be defined using dynamic formulas that depend on the shape's size (since this would result in cyclic dependencies). static readonly property bodyParts = null; <@doc type="Object">Defines the shape's bounding box constraints Use this property to constrain the shape's bounding box, according to: Name | Description defAuto | Auto-size the shape when created (if defAuto is ~true) defWidth | Default shape width defHeight | Default shape height | minWidth | Minimum shape width minHeight | Minimum shape height | maxWidth | Maximum shape width maxHeight | Maximum shape height When ~defAuto is set to ~true, the initial shape size will be MAX('default size', 'content size') static readonly property boundingBox = {defWidth:100, defHeight:70}; <@doc type="Object">Defines the shape's frame padding Use this property to specify the frame padding sizes (the distances to keep between the shape's frame and the shape's body), according to: Name | Description top | Top padding right | Right padding bottom | Bottom padding left | Left padding static readonly property framePadding = {top:0, right:0, bottom:0, left:0}; <@doc type="@SHAPE_PARTS[]"> Defines the shape's frame parts Use this property to define the graphical parts that draw the shape's frame. The frame parts do not contribute to the shape's size during auto-size calculations. Therefore, frame parts can be defined using dynamic formulas that depend on the shape's size (frame parts defined in this way will be adjusted whenever the shape's size changes). static readonly property frameParts = null; <@doc> Defines the shape's pins collection key The ~pinsKey is the name of the collection on the base object in which the shape pins are stored static readonly property pinsKey = null; <@doc type="@PIN_SYMBOLS[]"> Defines a collection of custom symbols to use for custom shape pins static readonly property pinSymbols = null; <@doc type="n">Defines the shape's resizing constraints The resizing constraints are 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 body size | SVG_FIXED_WIDTH | Width is fixed to ~defWidth SVG_AUTO_WIDTH | Width is automatically set equal to the body width SVG_KEEP_WIDTH | Width is kept greater or equal to the body width | SVG_FIXED_HEIGHT | Height is fixed to ~defHeight SVG_AUTO_HEIGHT | Height is automatically set equal to the body height SVG_KEEP_HEIGHT | Height is kept greater or equal to the body height static readonly property resizeMode = #[SVG_FREE_SIZE]; <@doc type="n">Defines the shape's rotation constraints The rotation constraints are 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 static readonly property rotateMode = #[SVG_ROTATE_FULL]; <@doc type="b"> Indicates whether a text sensor should be automatically added to the shape Set this property to enable pointer events on text parts that are positioned outside the shape's frame. By default, text parts do not receive pointer events. This property does not need to be set if the text parts are inside the shape frame's, since the frame itself receives all pointer events. static readonly property textSensor = false; <@struct name="SHAPE_PARTS" group="Structures"> A structure containing an array of shape part definitions

The SHAPE_PARTS structure is an array of objects, each representing a single shape part. The shape part's ~type attribute defines the type of SVG element that will be created. All the basic SVG element types can be used, including ~rect, ~circle, ~ellipse, ~line, ~polyline, ~polygon, ~path, ~text, and ~image. The remaining attributes are visual attributes specific to the shape part's type. See the SVG 1.1 Specification (www.w3.org/TR/2003/REC-SVG11-20030114) for more details.

All shape part attributes (except for type) can be expressed using dynamic formulas. A dynamic formula is string containing a valid JavaScript expression that involves one or more GML property references. A GML aspect property reference is written by adding the \@ prefix (e.g., \@fontSize). A GML object property reference is written by adding both the \@ prefix and ! suffix (e.g., \@name!). The ! suffix can be omitted from an object property reference, provided there is no aspect property with the same name.

NOTES:

Dynamic formulas that depend on shape size cannot be used for the body parts of auto-sized shapes. Otherwise, cyclic dependencies will be created, resulting in inconsistent shape resize behavior.

If the part is part of object's wireframe you can only use the following properties: ~x1, ~x2, ~y1, ~y2, ~w, ~h.

<@struct name="PIN_SYMBOLS"> A structure containing an array of pin symbol definitions The PIN_SYMBOLS structure is an array of symbol definitions, each representing a single custom pin. A symbol definitions should be written as standard SVG symbol element, using the following guidelines: The symbol's id must be a number between 1-255. This number is used by the pin that references the symbol definition (see @ZPin!symbol property). The symbol ~viewBox attribute is mandatory and must be centered exactly on the pin's origin. A ~radius property can be defined to indicate the distance of the pin's connection point from the pin's origin. If omitted, then the radius is taken to be equal to half the width of the pin's viewbox. ////////////////////////////////////////////////////////////////////////////////////// // ASPECT PROPERTIES // Status Flags <@doc scope="private">Indicates whether the shape is currently a drag source virtual property isDragSource = false; <@doc scope="private">Indicates whether the shape is currently a drop target virtual property isDropTarget = false; <@doc type="b" scope="private">Indicates that this graphic is a shape (for quick tests) virtual property isShape = true; <@doc type="b" scope="private">Indicates that this graphic has a selection filter effect virtual property hasSelFilter = 0; <@doc type="b" scope="private">Indicates that this graphic has invisible pins virtual property hasInvisiblePins = false; // SVG painting objects <@doc type="SVGNode" scope="private">Gets the shape body's svg node virtual property bodyNode = null; <@doc type="SVGNode" scope="private">Gets the shape frame's svg node virtual property frameNode = null; <@doc type="SVGNode" scope="private">Gets the mouse sensor svg node virtual property sensorNode = null; <@doc type="SVGNode" scope="private">Gets the shape wireframe's svg node virtual property wireNode = null; <@doc type="SVGNode[]" scope="private">Gets the collection of svg nodes of all shape parts virtual property partNodes = null; <@doc type="SVGNode" scope="private">Gets the shape's pins SVG layer virtual property pinsLayer = null; <@doc type="SVGNode" scope="private">Gets the shape's pin labels SVG layer virtual property pinsLabels = null; <@doc type="SVGNode" scope="private">Gets the shape's remark node virtual property remark = null; // Geometry properties // - The shape coordinates system origin (ox,oy) and scale (os) are defined by the parent block/diagram and are relative to the canvas. // - All other geometry properties are defined relative to this coordinates system. // - The shape center position (cx,cy) and size (w,h) do not depend on the rotation angle. // - The shape bounding box (x1,y1,x2,y2) always reflect the rotation angle // - The shape rotation angle (rot) is normalized to: 0x00=0, 0x01=90, 0x02=180, 0x03=270 <@doc scope="private">Gets the coordinates system x-origin (relative to the canvas) virtual property ox = 0; <@doc scope="private">Gets the coordinates system y-origin (relative to the canvas) virtual property oy = 0; <@doc scope="private">Gets the coordinates system scale (relative to the canvas) virtual property os = 1; <@doc scope="private">Gets the x-center (relative to the parent coordinates system) virtual property cx = 0; <@doc scope="private">Gets the y-center (relative to the parent coordinates system) virtual property cy = 0; <@doc scope="private">Gets the left x-coordinate (relative to the parent coordinates system) virtual property x1 = 0; <@doc scope="private">Gets the top y-coordinate (relative to the parent coordinates system) virtual property y1 = 0; <@doc scope="private">Gets the right x-coordinate (relative to the parent coordinates system) virtual property x2 = 0; <@doc scope="private">Gets the bottom y-coordinate (relative to the parent coordinates system) virtual property y2 = 0; <@doc scope="private">Gets the width (relative to the parent coordinates system) virtual property w = 0; <@doc scope="private">Gets the height (relative to the parent coordinates system) virtual property h = 0; <@doc scope="private">Gets the rotation angle (in multiples of 90 degrees) virtual property rot = 0; <@doc type="Object" scope="private">Gets the collection of all dynamic shape formulas virtual property formulas = null; ////////////////////////////////////////////////////////////////////////////////////// // MODEL EVENTS override virtual method onModelUpdate(evt) this.repaint(evt.name); //TODO: repaint also: strokeColor, fillColor end override virtual method onModelInsert(evt) var aspect = $DOM.getAspectOf(evt.child, '#NS[ZDrawing]') || null; if (aspect && aspect.prototype.isPin) { var pin = $DOM.createAspect('#NS[ZDrawing]', evt.child, this); if (pin) this.repaint(); } end override virtual method onModelRemove(evt) var pin = this.canvas.getWidget(evt.child.id); if (pin) { pin.Destructor(); this.repaint(); } else if (evt.key == "targetComponent"){ this.repaint(); } end ////////////////////////////////////////////////////////////////////////////////////// // PAINTING METHODS <@doc scope="private"> Paints the shape virtual method paint() // create shape this.graphicNode = SVG.createElement(this.getParent().shapesLayer, 'g', { fill: base.@fillColor, stroke: base.@strokeColor, color: base.@currentColor || base.@strokeColor }); this.frameNode = SVG.createElement(this.graphicNode, 'g', { 'stroke-width': 1.5, 'stroke-linejoin': 'round' }); this.bodyNode = SVG.createElement(this.graphicNode, 'g'); if (base.@textSensor) { this.sensorNode = SVG.createElement(this.graphicNode, 'rect', {'class':'textSensor'}); } this.graphicNode.peerID = this.id; // create shape parts var data = this.parseParts(this.Class, base.Class); this.formulas = data.formulas; this.partNodes = new Array(data.shapeParts.length); for (var i=0, len=data.shapeParts.length; i'); // Base shape prts.push(''); for (var k in prts) SVG.createFragment(this.remark, prts[k]); end virtual method removeRemarkNode() if (!this.remark) return; this.graphicNode.removeChild(this.remark); base.setProperty('comment', undefined, true); this.remark = undefined; end virtual method setRemarkEffect() if (!this.remark) return; var bbox = this.remark.getBBox; var diag = this.getDiagram(), ox=this.ox, oy=this.oy, os=this.os; var cbox = this.canvas.canvasToClient(ox+(this.cx+bbox.x)*os, oy+(this.cy+bbox.y)*os, bbox.width*os, bbox.height*os); var cpos = this.board.clientToBrowser({x:cbox.x, y:cbox.y}); var hlpr = diag.board.textedit; var comment = JOIN(base.comment,''); var size = hlpr.getTextWidthHeight(comment.split('\n')); var data = { attr: 'comment', object: base, readOnly: true, showAnimation: true, select: false, multi: true, isArray: true, bgcolor: '#FFFFE1', fgcolor: '#[BLK]', callback: this.endComment, delegate: this, x: cpos.x + cbox.w + 10, y: cpos.y + cbox.h + 10, w: size.w, h: size.h }; this.board.textedit.show(data); end <@doc scope="private"> Setting the remark node position virtual method setRemarkPos() { var dxdy = this.remark.getBBox().height/2; var x = this.w/2 - dxdy, y = this.h/2 - dxdy; this.remark.setAttribute('transform', 'translate('+x+','+y+')'); } <@doc scope="private"> Paints the shape pins virtual method paintPins() this.pinsLayer = SVG.createElement(this.graphicNode, 'a'); this.pinsLabels = SVG.createElement(this.graphicNode, 'g'); var P = base[base.@pinsKey]; for (var k in P) { var aspect = $DOM.getAspectOf(P[k], '#NS[ZDrawing]'); if (aspect && aspect.prototype.isPin) { $DOM.createAspect('#NS[ZDrawing]', P[k], this); } } end <@doc scope="private"> Repaints the shape after a property has changed virtual method repaint(prop) // set repaint flags var flags; switch (prop) { case '@size': flags = #[SVG_REPAINT_SIZE]; break; case '@pos': flags = #[SVG_REPAINT_POS]; break; case '@angle': flags = #[SVG_REPAINT_ANGLE]; break; case '@flip': flags = #[SVG_REPAINT_FLIP]; break; case '@scale': flags = #[SVG_REPAINT_SCALE]; break; default: flags = (prop ? #[SVG_REPAINT_NONE] : #[SVG_REPAINT_ALL]); break; } // not a positioning property, so repaint relevant shape parts and return if (!flags) { var formula = this.formulas[prop]; if (formula) formula(this.partNodes, this, base); return; } // recompute shape's local coordinates if (flags & #[SVG_REPAINT_SIZE]) { var size = (base.@size+''), k=size.indexOf(' '); var BB = base.@boundingBox, u=#[SVG_SNAPUNIT]; this.w = Math.round((parseInt(size.slice(0,k))||BB.defWidth)/u)*u; this.h = Math.round((parseInt(size.slice(k+1))||BB.defHeight)/u)*u; } if (flags & #[SVG_REPAINT_ANGLE]) { this.rot = Math.round((parseInt(base.@angle+'')||0)/90) % 4; if (this.rot<0) this.rot+=4; } if (flags & #[SVG_REPAINT_POS]) { var pos=(base.@pos+''), k=pos.indexOf(' '), u2=#[SVG_SNAPUNIT]/2; this.cx = Math.round((parseInt(pos.slice(0,k))||0)/u2)*u2; this.cy = Math.round((parseInt(pos.slice(k+1))||0)/u2)*u2; } 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; // transform shape based on pos and/or angle if (flags & (#[SVG_REPAINT_POS|SVG_REPAINT_ANGLE])) { this.graphicNode.setAttribute('transform', 'translate('+this.cx+','+this.cy+')'+(this.rot ? ' rotate('+(this.rot*90)+')' : '')); } // transform shape's parts based on angle and/or flip if (flags & (#[SVG_REPAINT_ANGLE|SVG_REPAINT_FLIP])) { var flip=base.@flip, RM=base.@rotateMode, FP=base.@framePadding; var fx = (flip & #[SVG_FLIPX] ? -1 : 1); var fy = (flip & #[SVG_FLIPY] ? -1 : 1); var bx = fx*(FP.left-FP.right)/2; var by = fy*(FP.top-FP.bottom)/2; var tr1='', tr2='', tr3=''; if (flip) tr1 = 'matrix('+fx+' 0 0 '+fy+' 0 0)'; if (bx || by) tr2 = 'translate('+bx+' '+by+')'; if (RM & #[SVG_FLIP_FLAGS]) { if (RM & #[SVG_FLIP_VBODY]) { if ((this.rot & 1) && (flip & #[SVG_FLIPY])) tr3 = 'rotate(180)'; } else if (RM & #[SVG_FLIP_BODY]) { if (flip & #[SVG_FLIPY]) tr3 = 'rotate(180)'; } else if (RM & #[SVG_FLIP_UPSIDE]) { if (this.rot) tr3 = 'rotate('+(360-this.rot*90)+')'; } if (tr3) tr2 += ' '+tr3; } this.frameNode.setAttribute('transform', tr1); this.bodyNode.setAttribute('transform', tr2); } // adjust the shape's pins var P = base.@pinsKey ? base[base.@pinsKey] : null; if (P && (flags & (#[SVG_REPAINT_SIZE|SVG_REPAINT_ANGLE]))) { var canvas = this.canvas; for (var k in P) { var pin = canvas.getWidget(P[k].id); if (pin) pin.repaint('@anchor'); } } // adjust the shape's parts if (prop) { var formula = this.formulas[prop]; if (formula) formula(this.partNodes, this, base); } else { for (var k in this.formulas) { this.formulas[k](this.partNodes, this, base); } } if (this.sensorNode) { var fb=this.frameNode.getBBox(); var bb=this.bodyNode.getBBox(); var x = MIN(fb.x, bb.x), y = MIN(fb.y, bb.y); var b = {x: x, y: y, width: MAX(fb.x+fb.width, bb.x+bb.width) - x, height: MAX(fb.y+fb.height, bb.y+bb.height) - y}; if (b.width > 0) SVG.setRect(this.sensorNode, b.x-5, b.y-5, b.width+10, b.height+10); } // adjust the shape's wireframe and handles, if any if (prop && this.isSelected) { this.adjustWireframe(); if (this.inFocus) this.getDiagram().adjustHandles(); } // adjust the shape's remark @ any flag value if (this.remark) { this.setRemarkPos(); } return flags; end ////////////////////////////////////////////////////////////////////////////////////// // INTERACTIVE EFFECTS override virtual method showHoverEffect(); if (this.isSelected || this.marked) return; SVG.setProperty(this.frameNode, 'stroke', this.hilightColor); if (this.sensorNode) { SVG.setProperty(this.bodyNode, 'color', this.hilightColor); } end override virtual method hideHoverEffect(); if (this.isSelected || this.marked) return; SVG.setProperty(this.frameNode, 'stroke', base.@strokeColor); if (this.sensorNode) { SVG.setProperty(this.bodyNode, 'color', base.@currentColor || base.@strokeColor); } end override virtual method showSelectionEffect() if (base.@protect & #[SVG_PROTECT_SELECT]) return; if (this.isSelected) return; SVG.raiseElement(this.graphicNode); this.isSelected = true; var currentColor = this.getDiagram().base.@hilightText; SVG.setProperty(this.frameNode, 'fill', this.hilightFill); if (this.marked != 'frameNode') SVG.setProperty(this.frameNode, 'stroke', this.hilightColor); SVG.setProperty(this.frameNode, 'color', currentColor); if (this.marked != 'bodyNode') SVG.setProperty(this.bodyNode, 'color', currentColor); if (this.sensorNode) { SVG.setProperty(this.sensorNode, '$stroke', this.hilightColor); } if (base.@pinsKey) { var pins = base[base.@pinsKey], canvas = this.canvas; for (var k in pins) { var pin = canvas.getWidget(pins[k].id); if (pin) pin.showSelectionEffect(); } } if (this.hasSelFilter) { var part=this.partNodes[this.hasSelFilter]; SVG.setProperty(part, 'filter', 'url(#'+SVG.getProperty(part,'selFilter')+')'); } end override virtual method hideSelectionEffect() if (!this.isSelected) return; this.isSelected = false; var currentColor = base.@currentColor || base.@strokeColor; SVG.setProperty(this.frameNode, 'fill', base.@fillColor); if (this.marked != 'frameNode') SVG.setProperty(this.frameNode, 'stroke', base.@strokeColor); SVG.setProperty(this.frameNode, 'color', currentColor); if (this.marked != 'bodyNode') SVG.setProperty(this.bodyNode, 'color', currentColor); if (this.sensorNode) { SVG.resetProperty(this.sensorNode, '$stroke'); } if (base.@pinsKey) { var pins = base[base.@pinsKey], canvas = this.canvas; for (var k in pins) { var pin=canvas.getWidget(pins[k].id); if (pin) pin.hideSelectionEffect(); } } if (this.hasSelFilter) { var part=this.partNodes[this.hasSelFilter]; SVG.setProperty(part, 'filter', 'none'); } end override virtual method showFocusEffect() this.inFocus = true; if (!this.hasInvisiblePins) return; var pins = base[base.@pinsKey], canvas = this.canvas; for (var k in pins) { var pin = canvas.getWidget(pins[k].id); if (pin && pin.isAutohide) pin.showFocusEffect(); } end override virtual method hideFocusEffect() this.inFocus = false; if (!this.hasInvisiblePins) return; var pins = base[base.@pinsKey], canvas = this.canvas; for (var k in pins) { var pin = canvas.getWidget(pins[k].id); if (pin && pin.isAutohide) pin.hideFocusEffect(); } end <@doc scope="private"> Shows the shape's drag effect virtual method showDragEffect() if (this.isDragSource) return; this.isDragSource = true; this.graphicNode.setAttribute('pointer-events', 'none'); var diag = this.getDiagram(); if (diag.alphaMode) this.graphicNode.setAttribute('opacity', 0.5); this.hideSelectionEffect(); if (!this.wireNode) this.createWireframe(); SVG.show(this.wireNode); SVG.setProperty(this.wireNode, '$fill', diag.alphaMode ? this.hilightColor : 'none'); SVG.setProperty(this.wireNode, '$stroke', this.hilightColor); this.adjustWireframe(); end <@doc scope="private"> Hides the shape's drag effect virtual method hideDragEffect() if (!this.isDragSource) return; this.isDragSource = false; this.graphicNode.removeAttribute('pointer-events'); if (this.getDiagram().alphaMode) this.graphicNode.removeAttribute('opacity'); this.showSelectionEffect(); SVG.hide(this.wireNode); end override virtual method showDropEffect(animate) if (this.isDropTarget) return; if (this.isRoot && !GETVAR('SVG_SHOW_BORDER')) return; this.isDropTarget = true; this.frameNode.setAttribute('stroke', this.hilightColor); this.frameNode.setAttribute('opacity', 0.5); this.frameNode.setAttribute('stroke-width', 4); end override virtual method hideDropEffect() if (!this.isDropTarget) return; if (this.isRoot && !GETVAR('SVG_SHOW_BORDER')) return; this.isDropTarget = false; this.frameNode.setAttribute('stroke', base.@strokeColor); this.frameNode.setAttribute('opacity', 1); this.frameNode.setAttribute('stroke-width', 1.5); // TODO - get the default width instead of hardcoded value end <@doc scope="private"> Creates the shape's wireframe virtual method createWireframe() this.wireNode = SVG.createElement(this.getDiagram().wiresLayer, 'g', { 'class':'wireframe', $stroke:'none', $fill:'none', '$fill-opacity':0.2}); var data = this.parseParts(this.Class, base.Class); for (var i=0, len=data.wireParts.length; i Adjusts the shape's wireframe to fit the shape bounding box virtual method adjustWireframe(cx, cy, w, h) if (!this.wireNode || this.wireNode.isHidden) 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 (base.@flip) tr += ' matrix('+(base.@flip & #[SVG_FLIPX] ? -1 : 1)+' 0 0 '+(base.@flip & #[SVG_FLIPY] ? -1 : 1)+' 0 0)'; if (!this.isRoot) tr = 'translate('+this.ox+','+this.oy+') scale('+this.os+') '+tr; this.wireNode.setAttribute('transform', tr); if (arguments.length == 2) return; var data = this.parseParts(this.Class, base.Class); if (data.wireFormula) data.wireFormula(this.wireNode, w, h); end <@doc scope="private"> Creates the shape's sprite object virtual method createSprite(parentNode, aspectClass, baseClass, w, h) var color = baseClass.prototype.@strokeColor; var fcolor = baseClass.prototype.@fillColor; var sprite = SVG.createElement(parentNode, 'g', {'class':'spriteframe', $stroke:color, $fill:fcolor||color}); var data = aspectClass.prototype.parseParts(aspectClass, baseClass); for (var i=0, len=data.wireParts.length; i 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"> Gets the shape's size limits virtual method getSizeLimits(sx,sy) var data = {}; var bbox = null; var FP = base.@framePadding; var SM = base.@resizeMode||0; var BB = base.@boundingBox, u=#[SVG_SNAPUNIT]; if (SM & #[SVG_FIXED_WIDTH]) { data.minWidth = data.maxWidth = this.minWidth||BB.defWidth; } else if (SM & #[SVG_AUTO_WIDTH]) { if (!bbox) bbox=this.bodyNode.getBBox(); data.minWidth = Math.ceil(Math.max(bbox.width+FP.left+FP.right, this.minWidth||BB.minWidth||u)/u)*u; data.maxWidth = ((SM & #[SVG_KEEP_WIDTH]) == #[SVG_AUTO_WIDTH]) ? data.minWidth : (BB.maxWidth||Number.MAX_VALUE); } else { data.minWidth = this.minWidth||BB.minWidth||u; data.maxWidth = BB.maxWidth||Number.MAX_VALUE; } if (SM & #[SVG_FIXED_HEIGHT]) { data.minHeight = data.maxHeight = this.minHeight||BB.defHeight; } else if (SM & #[SVG_AUTO_HEIGHT]) { if (!bbox) bbox=this.bodyNode.getBBox(); data.minHeight = Math.ceil(Math.max(bbox.height+FP.top+FP.bottom, this.minHeight||BB.minHeight||u)/u)*u; data.maxHeight = ((SM & #[SVG_KEEP_HEIGHT]) == #[SVG_AUTO_HEIGHT]) ? data.minHeight : (BB.maxHeight||Number.MAX_VALUE); } else { data.minHeight = this.minHeight||BB.minHeight||u; data.maxHeight = BB.maxHeight||Number.MAX_VALUE; } return data; end <@doc scope="private"> Gets the shape's control handles data virtual method getHandlesData() if (base.@protect & #[SVG_PROTECT_RESIZE]) return null; if (base.@resizeMode == #[SVG_AUTO_SIZE]) 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 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, color:base.@strokeColor}; 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"> 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"> Gets the position of shape's connection point relative to the given line's parent coordinates system virtual method getLineCP(line, otherShape) var s=this, p=line.getParent(); return { x: s.ox+s.cx*s.os-p.qx, y: s.oy+s.y1*s.os-p.qy, // TODO: define connection point dx/dy using defineShape RULE rot: this.rot, scale: s.os/p.qs, shapeID: s.id }; end ////////////////////////////////////////////////////////////////////////////////////// // LAYOUT METHODS <@doc scope="private"> Adjusts the shape's frame to best fit its body virtual method fitToBody(force) var BB = base.@boundingBox; var FP = base.@framePadding; var SM = force ? #[SVG_AUTO_SIZE] : base.@resizeMode||0; if (!(SM & #[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 (SM & #[SVG_AUTO_WIDTH]) { w = Math.ceil(Math.max(bbox.width+FP.left+FP.right, this.minWidth||BB.minWidth||u)/u)*u; if ((SM & #[SVG_KEEP_WIDTH]) != #[SVG_AUTO_WIDTH]) w = Math.max(w, this.w); } if (SM & #[SVG_AUTO_HEIGHT]) { h = Math.ceil(Math.max(bbox.height+FP.top+FP.bottom, this.minHeight||BB.minHeight||u)/u)*u; if ((SM & #[SVG_KEEP_HEIGHT]) != #[SVG_AUTO_HEIGHT]) h = Math.max(h, this.h); } if (w != this.w || h != this.h) this.invalidate('@size', w+' '+h); end <@doc scope="private"> Flips the shape horizontally virtual method flipX() var mask = (this.rot & 1 ? #[SVG_FLIPY] : #[SVG_FLIPX]); if (!(base.@rotateMode & mask) || (base.@protect & #[SVG_PROTECT_FLIP])) return; this.invalidate('@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.@rotateMode & mask) || (base.@protect & #[SVG_PROTECT_FLIP])) return; this.invalidate('@flip', (base.@flip ^ mask)); end <@doc scope="private"> Moves the shape by a specified delta virtual method moveby(dx,dy) if (base.@protect & #[SVG_PROTECT_MOVE]) return; var u2 = #[SVG_SNAPUNIT]/2; var cx = Math.round((this.cx+dx)/u2)*u2; var cy = Math.round((this.cy+dy)/u2)*u2; if (cx != this.cx || cy != this.cy) this.invalidate('@pos', cx+' '+cy); end <@doc scope="private"> Moves the shape to a specified position virtual method moveto(cx,cy) if (base.@protect & #[SVG_PROTECT_MOVE]) return; var u2 = #[SVG_SNAPUNIT]/2; var cx = Math.round(cx/u2)*u2; var cy = Math.round(cy/u2)*u2; if (cx != this.cx || cy != this.cy) this.invalidate('@pos', cx+' '+cy); 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; do { rot = (rot+dir) % 4; if (rot<0) rot += 4; if (Math.pow(2,rot) & base.@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; // update angle and center point this.invalidate('@angle', rot*90); this.moveto(cx,cy); end <@doc scope="private"> Resizes the shape by a specified delta virtual method sizeby(dw,dh) if (base.@protect & #[SVG_PROTECT_RESIZE]) return; var u = #[SVG_SNAPUNIT]; var w = Math.round((this.w+dw)/u)*u; var h = Math.round((this.h+dh)/u)*u; if (w != this.w || h != this.h) this.invalidate('@size', w+' '+h); end <@doc scope="private"> Resizes the shape to a specified size virtual method sizeto(w,h) if (base.@protect & #[SVG_PROTECT_RESIZE]) return; var u = #[SVG_SNAPUNIT]; var w = Math.round(w/u)*u; var h = Math.round(h/u)*u; if (w != this.w || h != this.h) this.invalidate('@size', w+' '+h); end ////////////////////////////////////////////////////////////////////////////////////// // EDITING METHODS <@doc scope="private"> Invalidates a shape positioning property in the scope of the current transaction The name of the property to update (pos, size, angle, or flip) The new property value Indicates whether the update was actually recorded

The invalidated property is repainted without actually committing it. As a result, all dependant aspect properties will be recomputed accordingly. This is required to ensure correctness of subsequent geometric calculations within the transaction.

Therefore, it follows that during the transaction the properties on the base object may not reflect the actual values on the aspect and should not be used in calculations. Only the aspect properties can be used for calculations during this period.

Once the transaction is committed, the base object properties will be set to their valid values.

virtual method invalidate(prop, value) var diag=this.getDiagram(), oldval=base[prop]; if (!diag.inTransaction) return; // repaint the invalidated value without committing it base[prop] = value; this.repaint(prop); if (oldval === undefined) delete base[prop]; else base[prop]=oldval; // store the invalidated value for later commit if (!diag.invalidShapes) diag.invalidShapes = {}; var buf=diag.invalidShapes, id=this.id; if (!(id in buf)) buf[id] = {}; buf[id][prop] = value; end <@doc scope="private"> Starts a shape rename operation virtual method startRename() for (var i=0, len=this.partNodes.length; i Setup the mouse pointer for shape move/click/double-click interactions virtual method setupPointer(evt) var diag=this.getDiagram(); diag.selectById(this.id, evt.ctrlKey); if (evt.detail>1) { if (this.isBlock && evt.shiftKey) { // TODO: Temporary for BlockZoom work diag.setActiveBlock(this); return null; } else { RULE('drillIntoShape', diag.base, base); } } else if (evt.button == 2) { return new SvgPointer('menusHandler', evt, diag.id, this.canvas); } else { var parent = this.getParent(); if (diag.isZoomable && parent.isBlock && !parent.isActive) return null; // TODO: BlockZoom: Make this a generic mouse handling thing, not a local IF return new SvgPointer('moveHandler', evt, diag.id, this.canvas); } return null; end <@doc scope="private"> Handles the shapes's mouseover event virtual method mouseover(evt, ptr) if (!ptr) { var diag=this.getDiagram(); var c = evt.target.getAttribute('class'); if (c && this.remark && c in this.remark.classes) this.setRemarkEffect(); if (evt.target.getAttribute('tooltip') !== 'hide') diag.showTooltip(this); if (diag.hilightMode) this.showHoverEffect(); return; } if (ptr.method == 'moveHandler' && !this.isRoot) { this.getParent().mouseover(evt, ptr); } end <@doc scope="private"> Handles the shapes's mouseout event virtual method mouseout(evt, ptr) if (this.remark) this.getDiagram().board.textedit.hide(); if (!ptr) { if (evt.target.getAttribute('tooltip') !== 'hide') { this.getDiagram().hideTooltip(); } this.hideHoverEffect(); return; } if (ptr.method == 'moveHandler' && !this.isRoot) { this.getParent().mouseout(evt, ptr); } 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('SVG_SMOOTH_DRAG') ? 1 : #[SVG_SNAPUNIT]; ptr.snapshot = this.board.captureSnapshot(); ptr.handles = {}; var H=this.getDiagram().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(ptr.dx, ptr.dy); 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_FIRSTMOVE]: this.getDiagram().adjustWireframes(); this.showDragEffect(); 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.getDiagram(), 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_LASTMOVE]: this.hideDragEffect(); 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.getDiagram().resizeSelection(size, pos, ptr); break; case #[PTR_CANCEL]: if (!ptr.isOverSource) SVG.setProperty(ptr.source, 'class', 'handleNormal'); this.repaint(); break; } end ////////////////////////////////////////////////////////////////////////////////////// // PARTS DEFINITIONS PARSER // Moved to ZGraphic ////////////////////////////////////////////////////////////////////////////////////// // ZOOM HANDLING property xpos = ''; <@doc scope="private"> Shows the shape's movable effect virtual method showMovableEffect() if (this.getDiagram().alphaMode) SVG.setProperty(this.graphicNode, 'opacity', 1); end <@doc scope="private"> Hides the shape's movable effect virtual method hideMovableEffect() if (this.getDiagram().alphaMode) SVG.setProperty(this.graphicNode, 'opacity', 0.2); end