@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