GUI -- TopoView - Initial work for implementing link selection.
- a step in the direction for showing port numbers.

Change-Id: I313782374c82b87b6d426e88519a5ab7c072a622
diff --git a/web/gui/src/main/webapp/app/view/topo/topoLink.js b/web/gui/src/main/webapp/app/view/topo/topoLink.js
new file mode 100644
index 0000000..6dd4338
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoLink.js
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2015 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 Link Module.
+ Functions for highlighting/selecting links
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, fs, sus, ts;
+
+    var api,
+        network,
+        enhancedLink = null;    // the link which the mouse is hovering over
+
+    // SVG elements;
+    var svg, mouseG;
+
+
+    // ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
+
+    function setupMouse(forceG, zoomer) {
+        $log.debug('set up mouse handlers for mouse move');
+        mouseG = forceG.append('g').attr('id', 'topo-mouse');
+        //mouseG.append('circle')
+        //    .attr({
+        //        r: 5,
+        //        opacity: 0
+        //    })
+        //    .style('fill', 'red');
+
+        svg.on('mouseenter', function () {
+            //$log.log('M--ENTER');
+            //mouseG.selectAll('circle').attr('opacity', 1);
+        })
+            .on('mouseleave', function () {
+                //$log.log('M--LEAVE');
+                //mouseG.selectAll('circle').attr('opacity', 0);
+            })
+            .on('mousemove', function () {
+                var m = d3.mouse(this),
+                    sc = zoomer.scale(),
+                    tr = zoomer.translate(),
+                    mx = (m[0] - tr[0]) / sc,
+                    my = (m[1] - tr[1]) / sc;
+
+                //$log.log('M--MOVE', m);
+
+                //mouseG.selectAll('circle')
+                //    .attr({
+                //        cx: mx,
+                //        cy: my
+                //    });
+                updatePerps({x: mx, y: my}, zoomer);
+            });
+    }
+
+    function updatePerps(mouse, zoomer) {
+        var proximity = 30 / zoomer.scale(),
+            perpData, perps, nearest, minDist;
+
+        function sq(x) { return x * x; }
+
+        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 mdist(p, m) {
+            return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
+        }
+
+        function lineSeg(d) {
+            return {
+                x1: d.source.x,
+                y1: d.source.y,
+                x2: d.target.x,
+                y2: d.target.y
+            };
+        }
+
+        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 bisects, but are we close enough?
+            return mdist(p, m) <= proximity;
+        }
+
+        if (network.links.length) {
+            perpData = [];
+            nearest = null;
+            minDist = proximity * 2;
+
+            network.links.forEach(function (d) {
+                if (!api.showHosts() && d.type() === 'hostLink') {
+                    return; // skip hidden host links
+                }
+
+                var line = lineSeg(d),
+                    point = pdrop(line, mouse),
+                    hit = lineHit(line, point, mouse),
+                    dist;
+
+                if (hit) {
+                    dist = mdist(point, mouse);
+                    if (dist < minDist) {
+                        minDist = dist;
+                        nearest = d;
+                    }
+                    /*
+                     perpData.push({
+                     key: d.key,
+                     x1: mouse.x,
+                     y1: mouse.y,
+                     x2: point.x,
+                     y2: point.y
+                     });
+                     */
+                }
+            });
+
+            /*
+             perps = mouseG.selectAll('line')
+             .data(perpData, function (d) { return d.key; })
+             .attr({
+             x1: function (d) { return d.x1; },
+             y1: function (d) { return d.y1; },
+             x2: function (d) { return d.x2; },
+             y2: function (d) { return d.y2; }
+             });
+
+             perps.enter().append('line')
+             .attr({
+             x1: function (d) { return d.x1; },
+             y1: function (d) { return d.y1; },
+             x2: function (d) { return d.x2; },
+             y2: function (d) { return d.y2; }
+             })
+             .style('stroke-width', 2)
+             .style('stroke', 'limegreen');
+
+             perps.exit().remove();
+             */
+
+            enhanceNearestLink(nearest);
+        }
+    }
+
+
+    function enhanceNearestLink(ldata) {
+        // if the new link is same as old link, do nothing
+        if (enhancedLink && ldata && enhancedLink.key === ldata.key) return;
+
+        // first, unenhance the currently enhanced link
+        if (enhancedLink) {
+            unenhance(enhancedLink);
+        }
+        enhancedLink = ldata;
+        if (enhancedLink) {
+            enhance(enhancedLink);
+        }
+    }
+
+    function unenhance(d) {
+        d.el.style('stroke', '#666');
+        $log.debug('UN-enhancing link: ', d.key);
+    }
+
+    function enhance(d) {
+        d.el.style('stroke', 'gold');
+        $log.debug('enhancing link: ', d.key);
+    }
+
+
+
+
+    // ==========================
+    // Module definition
+
+    angular.module('ovTopo')
+        .factory('TopoLinkService',
+        ['$log', 'FnService', 'SvgUtilService', 'ThemeService',
+
+            function (_$log_, _fs_, _sus_, _ts_) {
+                $log = _$log_;
+                fs = _fs_;
+                sus = _sus_;
+                ts = _ts_;
+
+                function initLink(_api_) {
+                    api = _api_;
+                    svg = api.svg;
+                    network = api.network;
+                    setupMouse(api.forceG, api.zoomer);
+                }
+
+                function destroyLink() {
+                    svg.on('mouseenter', null)
+                        .on('mouseleave', null)
+                        .on('mousemove', null);
+                }
+
+                return {
+                    initLink: initLink,
+                    destroyLink: destroyLink
+                };
+            }]);
+}());