GUI -- TopoView - Implemented much of the node selection logic. (WIP)
- introduced topoSelect.js.

Change-Id: Ic843c7d8dc2249fe0cb8c33de60dce12c07aea44
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
new file mode 100644
index 0000000..b7790f2
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -0,0 +1,293 @@
+/*
+ * 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 Selection Module.
+ Defines behavior when selecting nodes.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, fs, tps;
+
+    // api to topoForce
+    var api;
+    /*
+       node()                         // get ref to D3 selection of nodes
+       zoomingOrPanning( ev )
+       updateDeviceColors( [dev] )
+       sendEvent( type, {payload} )
+     */
+
+    // internal state
+    var hovered,                // the node over which the mouse is hovering
+        selections = {},        // currently selected nodes (by id)
+        selectOrder = [],       // the order in which we made selections
+        haveDetails = false,    // do we have details of one or more nodes?
+        useDetails = true;      // should we show details if we have 'em?
+
+    // ==========================
+
+    function nSel() {
+        return selectOrder.length;
+    }
+    function getSel(idx) {
+        return selections[selectOrder[idx]];
+    }
+    function allSelectionsClass(cls) {
+        for (var i=0, n=nSel(); i<n; i++) {
+            if (getSel(i).obj.class !== cls) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // ==========================
+
+    function nodeMouseOver(m) {
+        if (!m.dragStarted) {
+            $log.debug("MouseOver()...", m);
+            if (hovered != m) {
+                hovered = m;
+                requestTrafficForMode();
+            }
+        }
+    }
+
+    function nodeMouseOut(m) {
+        if (!m.dragStarted) {
+            if (hovered) {
+                hovered = null;
+                requestTrafficForMode();
+            }
+            $log.debug("MouseOut()...", m);
+        }
+    }
+
+    // ==========================
+
+    function selectObject(obj) {
+        var el = this,
+            ev = d3.event.sourceEvent,
+            n;
+
+        if (api.zoomingOrPanning(ev)) {
+            return;
+        }
+
+        if (el) {
+            n = d3.select(el);
+        } else {
+            api.node().each(function (d) {
+                if (d == obj) {
+                    n = d3.select(el = this);
+                }
+            });
+        }
+        if (!n) return;
+
+        if (ev.shiftKey && n.classed('selected')) {
+            deselectObject(obj.id);
+            updateDetail();
+            return;
+        }
+
+        if (!ev.shiftKey) {
+            deselectAll();
+        }
+
+        selections[obj.id] = { obj: obj, el: el };
+        selectOrder.push(obj.id);
+
+        n.classed('selected', true);
+        api.updateDeviceColors(obj);
+        updateDetail();
+
+        debugSel();
+    }
+
+    function deselectObject(id) {
+        var obj = selections[id];
+        if (obj) {
+            d3.select(obj.el).classed('selected', false);
+            delete selections[id];
+            fs.removeFromArray(id, selectOrder);
+            api.updateDeviceColors(obj.obj);
+        }
+
+        debugSel();
+    }
+
+    function deselectAll() {
+        // deselect all nodes in the network...
+        api.node().classed('selected', false);
+        selections = {};
+        selectOrder = [];
+        api.updateDeviceColors();
+        updateDetail();
+
+        debugSel();
+    }
+
+    function debugSel() {
+        $log.debug(' ..... Selected now >> ', selectOrder);
+    }
+
+    // === -----------------------------------------------------
+
+    function requestDetails() {
+        var data = getSel(0).obj;
+        api.sendEvent('requestDetails', {
+            id: data.id,
+            class: data.class
+        });
+    }
+
+    // === -----------------------------------------------------
+
+    function updateDetail() {
+        var nSel = selectOrder.length;
+        if (!nSel) {
+            emptySelect();
+        } else if (nSel === 1) {
+            singleSelect();
+        } else {
+            multiSelect();
+        }
+    }
+
+    function emptySelect() {
+        haveDetails = false;
+        tps.hideDetailPanel();
+        cancelTraffic();
+    }
+
+    function singleSelect() {
+        // NOTE: detail is shown from 'showDetails' event callback
+        requestDetails();
+        cancelTraffic();
+        requestTrafficForMode();
+    }
+
+    function multiSelect() {
+        haveDetails = true;
+
+        // display the selected nodes in the detail panel
+        tps.displayMulti(selectOrder);
+
+        // always add the 'show traffic' action
+        tps.addAction('Show Related Traffic', showRelatedIntentsAction);
+
+        // add other actions, based on what is selected...
+        if (nSel() === 2 && allSelectionsClass('host')) {
+            tps.addAction('Create Host-to-Host Flow', addHostIntentAction);
+        } else if (nSel() >= 2 && allSelectionsClass('host')) {
+            tps.addAction('Create Multi-Source Flow', addMultiSourceIntentAction);
+        }
+
+        cancelTraffic();
+        requestTrafficForMode();
+    }
+
+
+    // === -----------------------------------------------------
+    //  Event Handlers
+
+    function showDetails(data) {
+        haveDetails = true;
+
+        // display the data for the single selected node
+        tps.displaySingle(data);
+
+        // always add the 'show traffic' action
+        tps.addAction('Show Related Traffic', showRelatedIntentsAction);
+
+        // add other actions, based on what is selected...
+        if (data.type === 'switch') {
+            tps.addAction('Show Device Flows', showDeviceLinkFlowsAction);
+        }
+
+        // only show the details panel if the user hasn't "hidden" it
+        if (useDetails) {
+            tps.showDetailPanel();
+        }
+    }
+
+    // === -----------------------------------------------------
+    //  TODO: migrate these to topoTraffic.js
+
+    function cancelTraffic() {
+        $log.debug('TODO: cancelTraffic');
+
+    }
+    function requestTrafficForMode() {
+        $log.debug('TODO: requestTrafficForMode');
+
+    }
+    function showRelatedIntentsAction () {
+        $log.debug('TODO: showRelatedIntentsAction');
+
+    }
+    function addHostIntentAction () {
+        $log.debug('TODO: addHostIntentAction');
+
+    }
+    function addMultiSourceIntentAction () {
+        $log.debug('TODO: addMultiSourceIntentAction');
+
+    }
+    function showDeviceLinkFlowsAction () {
+        $log.debug('TODO: showDeviceLinkFlowsAction');
+
+    }
+
+
+    // === -----------------------------------------------------
+    // === MODULE DEFINITION ===
+
+    angular.module('ovTopo')
+        .factory('TopoSelectService',
+        ['$log', 'FnService', 'TopoPanelService',
+
+            function (_$log_, _fs_, _tps_) {
+                $log = _$log_;
+                fs = _fs_;
+                tps = _tps_;
+
+                function initSelect(_api_) {
+                    api = _api_;
+                }
+
+                function destroySelect() { }
+
+                return {
+                    initSelect: initSelect,
+                    destroySelect: destroySelect,
+
+                    showDetails: showDetails,
+
+                    nodeMouseOver: nodeMouseOver,
+                    nodeMouseOut: nodeMouseOut,
+                    selectObject: selectObject,
+                    deselectObject: deselectObject,
+                    deselectAll: deselectAll,
+                    hovered: function () { return hovered; }
+                };
+            }]);
+}());