<@doc hierarchy="GMLDOM"> Aspect for drawing connection pins (c) SAP AG 2003-2006. All rights reserved. #INCLUDE[gml:defs.inc] Aspect Pin for GmlDrawing; constructor(shape) this.id = base.id; this.dir = base.dir; this.name = base.getName(); this.shape = shape; this.lines = {}; shape.pins[this.id] = this; shape.diagram.allPins[this.id] = this; this.paint(); end destructor delete this.shape.pins[this.id]; delete this.shape.diagram.allPins[this.id]; this.erase(); end ////////////////////////////////////////////////////////////////////////////////////// // BASE PROPERTIES <@doc type="RGB">Gets or sets the pin's color If the pin color is not specified, the stroke color of the containing shape will be used. property color = ''; <@doc type="Object">Gets or sets the pin's graphical symbol The ~symbol property is a bitwise combination of the following groups of flags: Name | Description SVG_CLASSIC_PIN | Classic pin symbol (default) SVG_DIAMOND_PIN | Diamond symbol SVG_CIRCLE_PIN | Circle symbol SVG_SQUARE_PIN | Square symbol SVG_TRIANGLE_PIN | Triangle symbol | SVG_HOLLOW | The symbol is hollow, i.e. filled with white color (default for input pins). SVG_FILLED | The symbol is filled with the primary color (default for output pins). | SVG_INWARD | The symbol is facing inward (default for input pins). SVG_OUTWARD | The symbol is facing outward (default for output pins). | SVG_NOTEXT | The pin text label is hidden property symbol = 0; ////////////////////////////////////////////////////////////////////////////////////// // ASPECT PROPERTIES <@doc scope="private">Indicates whether the pin is animated virtual readonly property animated = false; <@doc type="@gml:Plug!DIRECTIONS" scope="private">Gets the pin's direction virtual readonly property dir = 0; <@doc scope="private">Gets or sets the pin's display flags virtual property flags = 0; <@doc scope="private">Indicates whether the pin is hilighted virtual readonly property hilighted = false; <@doc type="SVGNode" scope="private">Gets the pin's symbol svg node virtual readonly property symbNode = null; <@doc scope="private">Gets the pin's Id virtual readonly property id = ''; <@doc type="@Lines![id]" scope="private">Gets the collection of lines connected to this pin virtual readonly property lines = null; <@doc scope="private">Gets the pin's name virtual readonly property name = ''; <@doc scope="private">Gets or sets the pin's offset virtual property offset = 0; <@doc type="SVGNode" scope="private">Gets the pin's rectangle svg node virtual readonly property rectNode = null; <@doc scope="private">Gets the pin's rotation angle (in multiples of 90 degrees) The pin's rotation angle is defined as the rotation angle of the imaginary line that emanates from the pin's connection point outward, and is perpendicular to the side of the shape on which the pin is placed (after accounting for the rotation angle and flip state of the containing shape). virtual readonly property rot = 0; <@doc type="ENUM" scope="private">Gets or sets the pin's side (0=left, 1=top, 2=right, 3=bottom) virtual property side = 0; <@doc type="@Shape!" scope="private">Gets the owner shape object virtual readonly property shape = null; <@doc type="SVGNode" scope="private">Gets the pin's text svg node virtual readonly property textNode = null; <@doc scope="private">Gets the x-coordinate of the pin's connection point The pin's connection point is the precise point to which the pin's lines should be connected. The coordinate of the connection point is relative to the containing shape's center point. virtual readonly property x = 0; <@doc scope="private">Gets the y-coordinate of the pin's connection point The pin's connection point is the precise point to which the pin's lines should be connected. The coordinate of the connection point is relative to the containing shape's center point. virtual readonly property y = 0; ////////////////////////////////////////////////////////////////////////////////////// // ASPECT METHODS <@doc scope="private"> Tests whether this pin is connected to a given other pin virtual method isConnectedTo(other) for (var k in this.lines) { var ln=this.lines[k]; if (ln.srcpin == this && ln.trgpin == other) return true; if (ln.srcpin == other && ln.trgpin == this) return true; } return false; end <@doc scope="private"> Gets the absolute position of the pin's connection point virtual method getCP(otherpin) var shape=this.shape; return { x: shape.ox+(shape.cx+this.x)*shape.os, y: shape.oy+(shape.cy+this.y)*shape.os, rot: this.rot, scale: shape.os }; end ////////////////////////////////////////////////////////////////////////////////////// // EVENT HANDLERS virtual method setupPointer(evt) var diagram=this.shape.diagram; if (!diagram.isEditable) return; return new SvgPointer('wiringHandler', evt, this, diagram.canvas); end virtual method mouseover(evt, ptr) var diagram=this.shape.diagram; if (ptr) { if (ptr.method == 'wiringHandler' && this.hilighted) { ptr.currpin = this; diagram.showTooltip(this); } if (ptr.method == 'moveHandler') this.shape.mouseover(evt, ptr); return; } diagram.showTooltip(this); if (!diagram.isEditable) return; var color=this.shape.base.@hilightColor this.hilight(color); for (var k in this.lines) { var line = this.lines[k]; var pin2 = (line.srcpin == this ? line.trgpin : line.srcpin); if (!line.selected) line.hilight(color); if (!line.inFocus()) pin2.hilight(color); } end virtual method mouseout(evt, ptr) var diagram=this.shape.diagram; if (ptr) { if (ptr.method == 'wiringHandler' && this.hilighted) { ptr.currpin = null; diagram.hideTooltip(); } if (ptr.method == 'moveHandler') this.shape.mouseout(evt, ptr); return; } diagram.hideTooltip(); if (!diagram.isEditable) return; this.unlight(); for (var k in this.lines) { var line = this.lines[k]; var pin2 = (line.srcpin == this ? line.trgpin : line.srcpin); if (!line.selected) line.unlight(); if (!line.inFocus()) pin2.unlight(); } end virtual method wiringHandler(ptr) var shape=this.shape, sbase=shape.base, diag=shape.diagram; // Right clicking the direct plug is equivalent to right clicking the shape itself. if(ptr.button & #[PTR_RIGHT]) { diag.moveHandler(ptr); return; } // Unselect when left clicking the direct plug diag.clearSelection(); switch (ptr.phase) { case #[PTR_START]: var P=diag.allPins, Q=diag.base.getConnectablePlugs(this.base), n=0; if (!Q) { ptr.cancel=true; break; } diag.clearSelection(); this.mouseout(); var color=sbase.@hilightColor, animate=GETVAR('AnimDrawing'); for (var k in Q) { if (!(k in P)) continue; if (!Q[k].canMultipleConnect() && this.isConnectedTo(P[k])) continue; P[k].hilight(color, animate); n++; } var x=shape.ox+(shape.cx+this.x)*shape.os; var y=shape.oy+(shape.cy+this.y)*shape.os; ptr.scrollMode = #[PTR_AUTOSCROLL]; ptr.wire = SVG.createElement(diag.linesLayer, 'line', {x1:x, y1:y, x2:x, y2:y, fill:'none', 'stroke':sbase.@strokeColor, 'stroke-dasharray':'4 3', 'pointer-events':'none'}); ptr.trgpin = null; ptr.currpin = null; if (diag.rubberwireObj) { SVG.removeElement(diag.rubberwireObj); diag.rubberwireObj = null; } break; case #[PTR_MOVE]: if (ptr.currpin !== ptr.trgpin) { if (!ptr.currpin) { ptr.trgpin = null; ptr.wire.setAttribute('stroke-width', '1'); ptr.wire.setAttribute('stroke-dasharray', '4 3'); ptr.wire.setAttribute('stroke', sbase.@strokeColor); } else { ptr.trgpin = ptr.currpin; var P2=ptr.trgpin, S2=P2.shape; ptr.wire.setAttribute('x2', S2.ox+(S2.cx+P2.x)*S2.os); ptr.wire.setAttribute('y2', S2.oy+(S2.cy+P2.y)*S2.os); ptr.wire.setAttribute('stroke-width', '2'); ptr.wire.setAttribute('stroke-dasharray', 'none'); ptr.wire.setAttribute('stroke', sbase.@hilightColor); } } if (!ptr.trgpin) { ptr.wire.setAttribute('x2', ptr.pos.x); ptr.wire.setAttribute('y2', ptr.pos.y); } break; case #[PTR_FINISH]: var P=diag.allPins, srcplug, trgplug; for (var k in P) P[k].unlight(); if (!ptr.moved) { SVG.removeElement(ptr.wire); break; } // if the wire is dangling, get the quick connect menu if (!ptr.trgpin) { var evt = SVG.createModelEvent('onQuickConnectMenu'); evt.object = this.base.parent; evt.source = this.base; } // else get the connect menu else { var this_in = (this.dir == #[DIR_IN] || ptr.trgpin.dir == #[DIR_OUT]); //note: in case both pins are DIR_IO: we decide that "this" is source. srcplug = (this_in ? ptr.trgpin : this).base; trgplug = (this_in ? this : ptr.trgpin).base; var evt = SVG.createModelEvent('onConnectMenu'); evt.object = trgplug.parent; evt.source = srcplug; evt.target = trgplug; } evt.pos = ptr.pos; evt.menu = $ENV.defineMenubar(); evt.callback = null; evt.cancel = false; $ENV.fireModelEvent(evt); // open the menu, if any if (evt.cancel) { SVG.removeElement(ptr.wire); break; } if (evt.menu.length > 0) { diag.rubberwireObj = ptr.wire; diag.openMenu(evt.menu, ptr.pos, evt.callback); break; } if (!ptr.trgpin) { SVG.removeElement(ptr.wire); break; } // otherwise, create new link based on candidate link type(s) var linktypes = SPLIT(trgplug.parent.canConnect(srcplug, trgplug)); switch (linktypes.length) { case 0: // no valid link type SVG.removeElement(ptr.wire); break; case 1: // exactly one valid link type, so create it immediately diag.rubberwireObj = ptr.wire; var linkclass = CLASS(linktypes[0]); if (linkclass) createLink(linkclass.fullname); break; default: // more than one valid link type, so open a selection menu diag.rubberwireObj = ptr.wire; for (var i=0, len=linktypes.length, menu=[]; i Paints the pin virtual method paint() var sbase=this.shape.base; this.rectNode = SVG.createElement(this.shape.pinsLayer, 'rect', {x:-5, y:-5, width:10, height:10, 'class':'pinRect'}); this.rectNode.peer = this; var flags = base.@symbol; if (!(flags & 0x0F)) flags |= #[SVG_CLASSIC_PIN]; if (!(flags & (#[SVG_HOLLOW|SVG_FILLED]))) flags |= (this.dir == #[DIR_IN] ? #[SVG_HOLLOW] : #[SVG_FILLED]); if (!(flags & (#[SVG_INWARD|SVG_OUTWARD]))) flags |= (this.dir == #[DIR_IN] ? #[SVG_INWARD] : #[SVG_OUTWARD]); var data = SVG_PINSYMBOLS[(flags & 0x0F)-1]; if (!data) data = SVG_PINSYMBOLS[0]; if (data[2]) flags &= ~(#[SVG_INWARD|SVG_OUTWARD]); data[1].$fill = ((flags & #[SVG_FILLED]) ? 'currentColor' : 'white'); data[1].color = base.@color || sbase.@strokeColor; this.symbNode = SVG.createElement(this.shape.pinsLayer, data[0], data[1]); this.flags = flags; if (!(base.@symbol & #[SVG_NOTEXT])) { this.textNode = SVG.createElement(this.shape.pinsLayer, 'text', {fill:sbase.@textColor, 'class':'pinText'}); } end <@doc scope="private"> Erases the pin virtual method erase() this.rectNode.peer = null; if (this.animated) SVG.clearElement(this.symbNode); SVG.removeElement(this.rectNode); SVG.removeElement(this.symbNode); if (this.textNode) SVG.removeElement(this.textNode); this.rectNode = null; this.symbNode = null; this.textNode = null; end <@doc scope="private"> Repaints the pin after a property has changed virtual method repaint(prop) this.erase(); this.paint(); this.reposition(false); end <@doc scope="private"> Repositions the pin after its side or offset have changed virtual method reposition(reroute) var side=this.side, offset=this.offset, flags=this.flags, sign=(offset<0 ? -1 : 1); var dw=this.shape.w/2, dh=this.shape.h/2; var px, py, dx=0, dy=0; switch (side) { case 0: px=-dw; py=-sign*dh+offset; dx=-1; break; case 2: px=+dw; py=-sign*dh+offset; dx=+1; break; case 1: px=-sign*dw+offset; py=-dh; dy=-1; break; case 3: px=-sign*dw+offset; py=+dh; dy=+1; break; default: return; } var pa=side*90; if (flags & #[SVG_OUTWARD]) pa = 180-pa; else if (!(flags & #[SVG_INWARD])) pa=0; this.rectNode.setAttribute('transform', 'translate('+px+' '+py+')'); this.symbNode.setAttribute('transform', 'translate('+px+' '+py+')'+(pa ? ' rotate('+pa+') ' : '')); if (this.textNode) { this.textNode.setAttribute('transform', 'translate('+(px-(dx*6||+3))+' '+(py-(dy*6||-3))+')'+((side & 0x01) ? ' rotate(90) ' : '')); this.textNode.setAttribute('text-anchor', (side & 0x02) ? 'end' : 'start'); // Shortening long labels to prevent situations that labels override other labels var maxLabelLength = this.shape.w/20; if(this.name.length > maxLabelLength) { var shortName = this.name.substring(0,maxLabelLength)+"..."; } else { shortName = this.name; } SVG.setText(this.textNode, shortName); } px+=dx*3.5, py+=dy*3.5; switch (this.shape.rot) { case 0: this.x=+px; this.y=+py; break; case 1: this.x=-py; this.y=+px; break; case 2: this.x=-px; this.y=-py; break; case 3: this.x=+py; this.y=-px; break; } this.rot = (side + (2-this.shape.rot)) % 4; if (this.rot < 0) this.rot += 4; if (reroute) { var L=this.lines; for (var k in L) L[k].reroute(); } end <@doc scope="private"> Exposes the pin virtual method expose() end <@doc scope="private"> Conceals the pin virtual method conceal() end <@doc scope="private"> Hilights the pin using the given color virtual method hilight(color, animate) this.hilighted = true; if (!this.symbNode) return; this.symbNode.style.setProperty('fill', color); if (this.animated) { SVG.clearElement(this.symbNode); this.animated = false; } if (animate) { this.expose(); SVG.createFragment(this.symbNode, ''); this.animated = true; } end <@doc scope="private"> Unlights the pin virtual method unlight() this.hilighted = false; if (!this.symbNode) return; this.symbNode.style.setProperty('fill', (this.flags & #[SVG_FILLED]) ? 'currentColor' : 'white'); if (this.animated) { this.conceal(); SVG.clearElement(this.symbNode); this.animated = false; } end <@doc scope="private"> Gets the pin's tooltip positioning data virtual method getTooltipData() var cp = this.getCP(); var align = ['middle after', 'center above', 'middle before', 'center below'][cp.rot]; return {x:cp.x, y:cp.y, align:align}; end