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/resources/core/js.html b/web/gui/src/main/resources/core/js.html
index 581b190..dde2271 100644
--- a/web/gui/src/main/resources/core/js.html
+++ b/web/gui/src/main/resources/core/js.html
@@ -5,6 +5,7 @@
 <script src="app/view/topo/topoFilter.js"></script>
 <script src="app/view/topo/topoForce.js"></script>
 <script src="app/view/topo/topoInst.js"></script>
+<script src="app/view/topo/topoLink.js"></script>
 <script src="app/view/topo/topoModel.js"></script>
 <script src="app/view/topo/topoOblique.js"></script>
 <script src="app/view/topo/topoPanel.js"></script>
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 998f442..2aebee8 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -232,6 +232,7 @@
                     showNoDevs: showNoDevs,
                     projection: function () { return projection; },
                     zoomLayer: function () { return zoomLayer; },
+                    zoomer: function () { return zoomer; },
                     opacifyMap: opacifyMap,
                     sendEvent: _tes_.sendEvent
                 };
@@ -287,7 +288,7 @@
             );
 
             forceG = zoomLayer.append('g').attr('id', 'topo-force');
-            tfs.initForce(forceG, uplink, dim);
+            tfs.initForce(svg, forceG, uplink, dim);
             tis.initInst({ showMastership: tfs.showMastership });
             tps.initPanels({ sendEvent: tes.sendEvent });
             tes.openSock();
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index d97111f..2509290 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var $log, fs, sus, is, ts, flash, tis, tms, td3, tss, tts, tos, fltr,
+    var $log, fs, sus, is, ts, flash, tis, tms, td3, tss, tts, tos, fltr, tls,
         icfg, uplink;
 
     // configuration
@@ -728,15 +728,25 @@
         };
     }
 
+    function mkLinkApi(svg, forceG, uplink) {
+        return {
+            svg: svg,
+            forceG: forceG,
+            zoomer: uplink.zoomer(),
+            network: network,
+            showHosts: function () { return showHosts; }
+        };
+    }
+
     angular.module('ovTopo')
     .factory('TopoForceService',
         ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
             'FlashService', 'TopoInstService', 'TopoModelService',
             'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
-            'TopoObliqueService', 'TopoFilterService',
+            'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
 
         function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
-                  _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_) {
+                  _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
             $log = _$log_;
             fs = _fs_;
             sus = _sus_;
@@ -750,6 +760,7 @@
             tts = _tts_;
             tos = _tos_;
             fltr = _fltr_;
+            tls = _tls_;
 
             icfg = is.iconConfig();
 
@@ -762,7 +773,7 @@
             // uplink is the api from the main topo source file
             // dim is the initial dimensions of the SVG as [w,h]
             // opts are, well, optional :)
-            function initForce(forceG, _uplink_, _dim_, opts) {
+            function initForce(svg, forceG, _uplink_, _dim_, opts) {
                 uplink = _uplink_;
                 dim = _dim_;
 
@@ -774,6 +785,7 @@
                 tts.initTraffic(mkTrafficApi(uplink));
                 tos.initOblique(mkObliqueApi(uplink, fltr));
                 fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
+                tls.initLink(mkLinkApi(svg, forceG, uplink));
 
                 settings = angular.extend({}, defaultSettings, opts);
 
@@ -808,6 +820,7 @@
             }
 
             function destroyForce() {
+                tls.destroyLink();
                 fltr.destroyFilter();
                 tos.destroyOblique();
                 tts.destroyTraffic();
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
+                };
+            }]);
+}());
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 530bfa9..5f7632c 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -91,6 +91,7 @@
     <script src="app/view/topo/topoFilter.js"></script>
     <script src="app/view/topo/topoForce.js"></script>
     <script src="app/view/topo/topoInst.js"></script>
+    <script src="app/view/topo/topoLink.js"></script>
     <script src="app/view/topo/topoModel.js"></script>
     <script src="app/view/topo/topoOblique.js"></script>
     <script src="app/view/topo/topoPanel.js"></script>