<@doc hierarchy="GMLDOM"> Base aspect for all graphic lines (c) SAP AG 2003-2006. All rights reserved. Aspect Line for GmlDrawing; inherit Graphic; constructor(diagram) this.id = base.id; this.diagram = diagram; this.board = diagram.board; this.srcpin = diagram.allPins[base.source.id]; this.trgpin = diagram.allPins[base.target.id]; if (!this.srcpin) throw new Error('Source pin '+base.source.id+' not found'); if (!this.trgpin) throw new Error('Target pin '+base.target.id+' not found'); this.srcpin.lines[this.id] = this; this.trgpin.lines[this.id] = this; this.srcpin.shape.lines[this.id] = this; this.trgpin.shape.lines[this.id] = this; this.diagram.allLines[this.id] = this; this.paint(); end destructor this.unselect(); if (this.srcpin) { delete this.srcpin.lines[this.id]; delete this.srcpin.shape.lines[this.id]; } if (this.trgpin) { delete this.trgpin.lines[this.id]; delete this.trgpin.shape.lines[this.id]; } this.lineNode.peer = null; this.labelNode.peer = null; SVG.removeElement(this.lineNode); SVG.removeElement(this.labelNode); this.lineNode = null; this.labelNode = null; delete this.diagram.allLines[this.id]; // Silent update: (see Line.reroute) this.diagram.silentUpdate(this, '@path', null); end ////////////////////////////////////////////////////////////////////////////////////// // BASE PROPERTIES <@doc default="SVG_BLOCK_ARROW|SVG_FILLED"> Gets or sets the line's arrow head The ~arrowHead property is a bitwise combination of the following groups of flags: Name | Description SVG_BLOCK_ARROW | Block arrow SVG_CLASSIC_ARROW | Classic arrow SVG_DIAMOND_ARROW | Diamond arrow SVG_CIRCLE_ARROW | Circle arrow SVG_NOTCH_ARROW | Block arrow with a notch SVG_DOUBLE_ARROW | Double arrow | SVG_HOLLOW | The arrow head is hollow, i.e. filled with white color. SVG_FILLED | The arrow head is filled with the primary color. property arrowHead = 0; <@doc scope="private"> Gets or sets the line's controls The value of this property depends on the specific type of line that uses it. property controls = ''; <@doc> Gets or sets the line's dash pattern The ~dash property controls the pattern of dashes and gaps used to stroke the line. The ~dash property value can be either 'none' (indicating that the line is to be drawn solid), or a list of whitespace-separated numbers that specify the lengths of alternating dashes and gaps. property lineDash = 'none'; <@doc> Gets or sets the line's width property lineWidth = 1.25; <@doc scope="private"> Gets or sets the line's path The ~path property contains a whitespace-separated of path drawing commands relative to the the line's source pin. property path = ''; <@doc type="RGB">Gets or sets the line's stroke color property strokeColor = '#718398'; <@doc type="RGB">Gets or sets the line's hilight color property hilightColor = '#FF9933'; <@doc type="RGB">Gets or sets the line's text color property textColor = '#718398'; ////////////////////////////////////////////////////////////////////////////////////// // ASPECT PROPERTIES <@doc scope="private">Indicates whether the line is animated virtual readonly property animated = false; <@doc scope="private">Indicates whether the line is hilighted virtual readonly property hilighted = false; <@doc type="SVGNode" scope="private">Gets the line's label node virtual readonly property labelNode = null; <@doc type="SVGNode" scope="private">Gets the line's svg node virtual readonly property lineNode = null; <@doc scope="private">Indicates whether the line is locked (cannot be rerouted) virtual readonly property lock = false; <@doc type="@Pin!" scope="private">Gets the line's source pin virtual readonly property srcpin = null; <@doc type="@Pin!" scope="private">Gets the line's target pin virtual readonly property trgpin = null; ////////////////////////////////////////////////////////////////////////////////////// // ASPECT METHODS <@doc scope="private"> Tests whether the line is enclosed by a given rectangle virtual method enclosedIn(rect) var b=this.lineNode.getBBox(), x1=b.x, x2=x1+b.width, y1=b.y, y2=y1+b.height; return (rect.x <= x1 && rect.x+rect.w >= x2 && rect.y <= y1 && rect.y+rect.h >= y2); end <@doc scope="private"> Gets the line's bounding box virtual method getBBox() return this.lineNode.getBBox(); end <@doc scope="private"> Gets the group that should be selected when the line is selected (the innermost group that contains both line ends) virtual method getSelGroup() var diag = this.diagram; var g1 = this.srcpin.shape.group || diag; var g2 = this.trgpin.shape.group || diag; if (g1 === g2) return g1; if (g1==diag || g2==diag) return diag; var G = {}; for (var g=g1; g; g=g.group) G[g.id] = g; for (var g=g2; g; g=g.group) if (g.id in G) return G[g.id]; return diag; end <@doc scope="private"> Tests whether the line is in focus virtual method inFocus() return (this.diagram.focus == this); end <@doc scope="private"> Tests whether the line intersects a given rectangle virtual method intersects(rect) if (this.enclosedIn(rect)) return true; return false; end <@doc scope="private"> Converts an array of path drawing commands to string virtual method path2str() var A=arguments, len=A.length; var B=new Array(len); for (var i=0; i Handles a mutation event on the base object of this line virtual method onObjectMutate(evt) if (evt.type == 'onupdateobject') { this.repaint(evt.name); } end <@doc scope="private"> Handles a mutation event on a sub-object of this line virtual method onSubObjectMutate(evt) // override end <@doc scope="private"> Selects the line virtual method select() if (this.selected) return true; SVG.raiseElement(this.lineNode); var color=base.@hilightColor; this.hilight(color); this.srcpin.hilight(color); this.trgpin.hilight(color); this.lineNode.setAttribute('stroke-dasharray', base.@lineDash); this.selected = true; return true; end <@doc scope="private"> Starts a line rename operation virtual method startRename() var bbox=this.labelNode.getBBox(); if (bbox.width < 0) { SVG.setText(this.labelNode, '.'); bbox=this.labelNode.getBBox(); SVG.setText(this.labelNode, base.name); } if (bbox.width < 0) { bbox = this.lineNode.getBBox(); bbox = {x:bbox.x+bbox.width/2, y:bbox.y+bbox.height/2, width:75, height:16}; } var diag = this.diagram var cbox = diag.canvas.canvasToClient(bbox.x, bbox.y, bbox.width, bbox.height); var cpos = diag.board.clientToBrowser({x:cbox.x, y:cbox.y}); var data = { object: base, graphic: this, attr: 'name', callback: diag.endRename, delegate: diag, x: cpos.x+cbox.w/2, centerX:true, y: cpos.y+cbox.h/2, centerY:true, w: cbox.w, h: cbox.h }; diag.board.textedit.show(data); end <@doc scope="private"> Unselects the shape virtual method unselect() if (!this.selected) return; this.unlight(); this.srcpin.unlight(); this.trgpin.unlight(); this.selected = false; end ////////////////////////////////////////////////////////////////////////////////////// // EVENT HANDLERS virtual method setupPointer(evt) var diag=this.diagram; diag.selectById(this.id, evt.ctrlKey); if (evt.detail>1) { var evt2 = SVG.createModelEvent('onDrawingAction'); evt2.object = this.base; evt2.detail = #[SVG_ACTIVATE]; $ENV.fireModelEvent(evt2); } else if (evt.button == 2) { var evt2 = SVG.createModelEvent('onShapeMenu'); evt2.object = this.base; evt2.selection = diag.getSelectedObjects(); evt2.pos = diag.canvas.clientToCanvas(evt); evt2.menu = $ENV.defineMenubar(); evt2.callback = null; evt2.cancel = false; $ENV.fireModelEvent(evt2); this.diagram.openMenu(evt2.cancel ? null : evt2.menu, evt2.pos, evt2.callback); } return null; end virtual method mouseover(evt, ptr) if (!ptr) this.diagram.showTooltip(this); if (this.selected || ptr || !this.diagram.isEditable) return; var color=base.@hilightColor; this.hilight(color); this.srcpin.hilight(color); this.trgpin.hilight(color); end virtual method mouseout(evt, ptr) if (!ptr) this.diagram.hideTooltip(); if (this.selected || ptr || !this.diagram.isEditable) return; this.unlight(); this.srcpin.unlight(); this.trgpin.unlight(); end <@doc scope="private"> Gets the line's control handles data virtual method getHandlesData() return null; end <@doc scope="private"> Gets the line's tooltip positioning data virtual method getTooltipData() var b=this.lineNode.getBBox(); return {x:b.x+b.width/2, y:b.y+b.height, align:'center below', offset:4}; end virtual method onHandleMove(ptr) end <@doc scope="private"> Prepares the line for an ongoing shapes attachment interaction virtual method setAttachMode(flag) this.lineNode.setAttribute('pointer-events', flag ? 'none' : 'visibleStroke'); end ////////////////////////////////////////////////////////////////////////////////////// // PAINTING METHODS <@doc scope="private"> Paints the shape virtual method paint() var head = base.@arrowHead; var props = {fill:'none', stroke:'currentColor', color:base.@strokeColor, 'stroke-width':base.@lineWidth, 'stroke-dasharray':base.@lineDash, 'pointer-events':'visibleStroke'}; if (head) props['marker-end'] = 'url(#'+this.id+'_head)'; this.lineNode = SVG.createElement(this.diagram.linesLayer, 'path', props); this.labelNode = SVG.createElement(this.diagram.linesLayer, 'text', {'font-size':'8.5', fill:base.@textColor, stroke:'none', 'text-anchor':'middle', 'pointer-events':'none'}); SVG.setText(this.labelNode, base.name); if (head) { var C = ((head & #[SVG_FILLED]) ? 'currentColor' : 'white'); var D = SVG_ARROWHEADS[(head & 0x0F)-1]; SVG.createFragment(this.lineNode, '<'+D[0]+' style="fill:'+C+';stroke:currentColor;stroke-width:1"/>'); } this.lineNode.peer = this; if (!base.@path) this.reroute(); end <@doc scope="private"> Repaints the line after a property has changed virtual method repaint(prop) switch (prop) { case 'name': SVG.setText(this.labelNode, base.name); break; } end <@doc scope="private"> Reroutes the line after either of its endpoints was repositioned virtual method reroute() if (this.diagram.bufferedReroute(this)) return; var p1=this.srcpin.getCP(this.trgpin); var p2=this.trgpin.getCP(this.srcpin); // SILENT UPDATE: // As an optimization the line path/controls are saved silently and the line is repainted immediately. // This is allowed since rerouting is always invoked in response to some other shape change and is never // triggered by the onObjectUpdate event handler. this.diagram.silentUpdate(this, '@path', 'l '+(p2.x-p1.x)+' '+(p2.y-p1.y)); this.lineNode.setAttribute ('d', 'M'+p1.x+','+p1.y+base.@path); // calculate label position var lx = (p1.x+p2.x)/2; var ly = (p1.y+p2.y)/2; var la = Math.atan2(p2.y-p1.y, p2.x-p1.x)*SVG.R2D; if (Math.abs(la) > 90) la += 180; this.labelNode.setAttribute('x', lx); this.labelNode.setAttribute('y', ly-3); this.labelNode.setAttribute('transform', 'rotate('+la+' '+lx+' '+ly+')'); end <@doc scope="private"> Hilights the line using the given color virtual method hilight(color, animate) this.hilighted = true; if (!this.lineNode) return; this.lineNode.setAttribute('stroke-dasharray', '4 1'); this.lineNode.setAttribute('stroke-width', Math.max(base.@lineWidth+0.5,2)); this.lineNode.setAttribute('color', color); if (this.animated) { SVG.clearElement(this.lineNode); this.animated = false; } if (animate) { SVG.createFragment(this.lineNode, ''); this.animated = true; } end <@doc scope="private"> Unlights the line virtual method unlight() this.hilighted = false; if (!this.lineNode) return; this.lineNode.setAttribute('stroke-dasharray', base.@lineDash); this.lineNode.setAttribute('stroke-width', base.@lineWidth); this.lineNode.setAttribute('color', base.@strokeColor); if (this.animated) { SVG.clearElement(this.lineNode); this.animated = false; } end