blob: e172e0daadb4b9b658a13a97fdea13c73da8d7ff [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Topology Select Module.
*/
(function () {
'use strict';
var t2zs, t2ddp;
// internal state
var instance,
consumeClick,
zoomer,
previousNearestLink; // previous link to mouse position
function mouseClickHandler() {
if (d3.event.defaultPrevented) return;
if (!d3.event.shiftKey) {
this.clearSelection();
}
if (!this.clickConsumed()) {
if (previousNearestLink) {
this.selectObject(previousNearestLink, true);
}
}
}
// Select Links
function mouseMoveHandler(ev) {
var mp = getLogicalMousePosition(this),
link = computeNearestLink(mp);
if (link) {
if (previousNearestLink && previousNearestLink !== link) {
previousNearestLink.unenhance();
}
link.enhance();
} else if (previousNearestLink) {
previousNearestLink.unenhance();
}
previousNearestLink = link;
}
function getLogicalMousePosition(container) {
var m = d3.mouse(container),
sc = zoomer.scale(),
tr = zoomer.translate(),
mx = (m[0] - tr[0]) / sc,
my = (m[1] - tr[1]) / sc;
return { x: mx, y: my };
}
function sq(x) {
return x * x;
}
function mdist(p, m) {
return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
}
function prox(dist) {
return dist / zoomer.scale();
}
function computeNearestLink(mouse) {
var proximity = prox(30),
nearest = null,
minDist;
function pdrop(line, mouse) {
var x1 = line.x1,
y1 = line.y1,
x2 = line.x2,
y2 = line.y2,
x3 = mouse.x,
y3 = mouse.y,
k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) /
(sq(y2 - y1) + sq(x2 - x1)),
x4 = x3 - k * (y2 - y1),
y4 = y3 + k * (x2 - x1);
return { x: x4, y: y4 };
}
function lineHit(line, p, m) {
if (p.x < line.x1 && p.x < line.x2) return false;
if (p.x > line.x1 && p.x > line.x2) return false;
if (p.y < line.y1 && p.y < line.y2) return false;
if (p.y > line.y1 && p.y > line.y2) return false;
// line intersects, but are we close enough?
return mdist(p, m) <= proximity;
}
var links = [];
if (instance.region.model.get('links')) {
links = instance.region.regionLinks();
}
if (links.length) {
minDist = proximity * 2;
links.forEach(function (d) {
var line = d.get('position'),
point,
hit,
dist;
// TODO: Reinstate when showHost() is implemented
// if (!api.showHosts() && d.type() === 'hostLink') {
// return; // skip hidden host links
// }
if (line) {
point = pdrop(line, mouse);
hit = lineHit(line, point, mouse);
if (hit) {
dist = mdist(point, mouse);
if (dist < minDist) {
minDist = dist;
nearest = d;
}
}
}
});
}
return nearest;
}
var SelectionService = function () {
instance = this;
this.selectedNodes = [];
};
SelectionService.prototype = {
init: function () {
zoomer = t2zs.getZoomer();
var svg = d3.select('#topo2');
svg.on('mousemove', mouseMoveHandler);
svg.on('click', mouseClickHandler.bind(this));
},
updateDetails: function () {
var nodeCount = this.selectedNodes.length;
if (nodeCount === 1) {
this.selectedNodes[0].showDetails();
} else if (nodeCount > 1) {
t2ddp.showMulti(this.selectedNodes);
} else {
t2ddp.hide();
}
},
selectObject: function (node, multiSelectEnabled) {
var event = d3.event;
if (multiSelectEnabled && !event.shiftKey || !multiSelectEnabled) {
this.clearSelection();
}
var nodeIndex = _.indexOf(this.selectedNodes, node);
if (nodeIndex < 0) {
this.selectedNodes.push(node);
node.select();
} else {
this.removeNode(node, nodeIndex);
}
this.updateDetails();
},
removeNode: function (node, index) {
this.selectedNodes.splice(index, 1);
node.deselect();
},
clearSelection: function () {
_.each(this.selectedNodes, function (node) {
node.deselect();
});
this.selectedNodes = [];
this.updateDetails();
},
clickConsumed: function (x) {
var cc = consumeClick;
consumeClick = Boolean(x);
return cc;
}
};
angular.module('ovTopo2')
.factory('Topo2SelectService', [
'Topo2ZoomService', 'Topo2DeviceDetailsPanel',
function (_t2zs_, _t2ddp_) {
t2zs = _t2zs_;
t2ddp = _t2ddp_;
return instance || new SelectionService();
}
]);
})();