Topo2: Compute nearest link by mouse position
Topo2: Deselect Nodes and Links on ESC command
Topo2: Added deselect methods to nodes
Topo2: Updated to new icon

Change-Id: Ia0aaa24e887d645123787f42bb1f847ef1de11b0
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 7274761..6e5df6c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -25,7 +25,7 @@
     var $log, wss, sus, t2rs, t2d3, t2vs, t2ss;
 
     var linkG, linkLabelG, nodeG;
-    var link, node;
+    var link, node, zoomer;
 
     // default settings for force layout
     var defaultSettings = {
@@ -73,12 +73,14 @@
     };
 
     // internal state
-    var settings,   // merged default settings and options
-        force,      // force layout object
-        drag,       // drag behavior handler
+    var settings,               // merged default settings and options
+        force,                  // force layout object
+        drag,                   // drag behavior handler
+        previousNearestLink,    // previous link to mouse position
         nodeLock = false;       // whether nodes can be dragged or not (locked)
 
-    function init(_svg_, forceG, _uplink_, _dim_, opts) {
+
+    function init(_svg_, forceG, _uplink_, _dim_, _zoomer_, opts) {
 
         $log.debug("Initialising Topology Layout");
         settings = angular.extend({}, defaultSettings, opts);
@@ -92,6 +94,10 @@
         link = linkG.selectAll('.link');
         linkLabelG.selectAll('.linkLabel');
         node = nodeG.selectAll('.node');
+
+        zoomer = _zoomer_;
+        _svg_.on('mousemove', mouseMoveHandler);
+        _svg_.on('click', mouseClickHandler);
     }
 
     function getDeviceChargeForType(node) {
@@ -325,6 +331,121 @@
         force.start();
     }
 
+    function mouseClickHandler() {
+
+        if (!d3.event.shiftKey) {
+            t2rs.deselectLink();
+        }
+
+        if (!t2ss.clickConsumed()) {
+            if (previousNearestLink) {
+                previousNearestLink.select();
+            }
+        }
+
+    }
+
+    // Select Links
+    function mouseMoveHandler() {
+        var mp = getLogicalMousePosition(this),
+            link = computeNearestLink(mp);
+
+        // link.enhance();
+        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 = t2rs.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;
+    }
+
     angular.module('ovTopo2')
     .factory('Topo2LayoutService',
         [