@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