Added Map Dialog
Added Map Background
Prevent click on node drag event

Change-Id: Ibb13085e3aa51eae49a2017251486f00d52f6265
diff --git a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
index 0f375e2..60a8be1 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -104,6 +104,8 @@
                 }
             })
             .on('dragend', function(d) {
+                d3.event.sourceEvent.preventDefault();
+
                 if (d.dragStarted) {
                     d.dragStarted = false;
                     if (!dragged(d)) {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
index fa3b36e..ccc50e0 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -25,7 +25,7 @@
 
     // references to injected services
     var $scope, $log, fs, mast, ks, zs,
-        gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs;
+        gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms;
 
     // DOM elements
     var ovtopo2, svg, defs, zoomLayer, forceG;
@@ -87,13 +87,13 @@
         'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
         'WebSocketService', 'PrefsService', 'ThemeService',
         'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService',
-        'Topo2BreadcrumbService', 'Topo2KeyCommandService',
+        'Topo2BreadcrumbService', 'Topo2KeyCommandService', 'Topo2MapService',
 
         function (_$scope_, _$log_, _$loc_,
             _fs_, _mast_, _ks_, _zs_,
             _gs_, _ms_, _sus_, _flash_,
             _wss_, _ps_, _th_,
-            _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_) {
+            _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_) {
 
             var params = _$loc_.search(),
                 dim,
@@ -126,8 +126,7 @@
             t2is = _t2is_;
             t2bcs = _t2bcs_;
             t2kcs = _t2kcs_;
-
-            t2kcs.init(t2fs);
+            t2ms = _t2ms_;
 
             // capture selected intent parameters (if they are set in the
             //  query string) so that the traffic overlay can highlight
@@ -172,10 +171,22 @@
             // make sure we can respond to topology events from the server
             t2es.bindHandlers();
 
+            // Add the map SVG Group
+            t2ms.init(zoomLayer).then(
+                function (proj) {
+                    var z = ps.getPrefs('topo_zoom', { tx: 0, ty: 0, sc: 1 });
+                    zoomer.panZoom([z.tx, z.ty], z.sc);
+                    $log.debug('** Zoom restored:', z);
+                    $log.debug('** We installed the projection:', proj);
+                }
+            );
+
             // initialize the force layout, ready to render the topology
             forceG = zoomLayer.append('g').attr('id', 'topo-force');
             t2fs.init(svg, forceG, uplink, dim);
             t2bcs.init();
+            t2kcs.init(t2fs);
+
 
             // =-=-=-=-=-=-=-=-
             // TODO: in future, we will load background map data
@@ -188,34 +199,9 @@
 
             // === ORIGINAL CODE ===
 
-            // setUpKeys();
             // setUpToolbar();
-            // setUpDefs();
-            // setUpZoom();
             // setUpNoDevs();
-            /*
-            setUpMap().then(
-                function (proj) {
-                    var z = ps.getPrefs('topo_zoom', { tx:0, ty:0, sc:1 });
-                    zoomer.panZoom([z.tx, z.ty], z.sc);
-                    $log.debug('** Zoom restored:', z);
 
-                    projection = proj;
-                    $log.debug('** We installed the projection:', proj);
-                    flash.enable(false);
-                    toggleMap(prefsState.bg);
-                    flash.enable(true);
-                    mapShader(true);
-
-                    // now we have the map projection, we are ready for
-                    //  the server to send us device/host data...
-                    tes.start();
-                    // need to do the following so we immediately get
-                    //  the summary panel data back from the server
-                    restoreSummaryFromPrefs();
-                }
-            );
-            */
             // tes.bindHandlers();
             // setUpSprites();
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js b/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js
new file mode 100644
index 0000000..13da1a8
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js
@@ -0,0 +1,46 @@
+/*
+* 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 Dialog Module.
+ Creates a dialog box for the topology view.
+ */
+
+(function () {
+    'use strict';
+
+    // constants
+    var idDialog = 'topo-p-dialog',
+        opts = {
+            cssCls: 'topo-p'
+        };
+
+    // ==========================
+
+    angular.module('ovTopo2')
+    .factory('Topo2DialogService',
+        ['DialogService',
+
+        function (ds) {
+            return {
+                openDialog: function () {
+                    return ds.openDialog(idDialog, opts);
+                },
+                closeDialog: ds.closeDialog,
+                createDiv: ds.createDiv
+            };
+        }]);
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
index f3b444d..ea6436d 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
@@ -17,12 +17,14 @@
 (function () {
 
     // Injected Services
-    var ks, t2ps;
+    var ks, t2ps, t2ms;
     var topo2ForceService;
 
     // Commmands
     var actionMap = {
-        L: [cycleDeviceLabels, 'Cycle device labels']
+        L: [cycleDeviceLabels, 'Cycle device labels'],
+        G: [openMapSelection, 'Select background geo map'],
+        B: [toggleMap, 'Toggle background geo map'],
     };
 
     function init(t2fs) {
@@ -49,13 +51,22 @@
         topo2ForceService.updateNodes();
     }
 
+    function openMapSelection() {
+        t2ms.openMapSelection();
+    }
+
+    function toggleMap(x) {
+        t2ms.toggle(x);
+    }
+
     angular.module('ovTopo2')
     .factory('Topo2KeyCommandService',
-    ['KeyService', 'Topo2PrefsService',
+    ['KeyService', 'Topo2PrefsService', 'Topo2MapService',
 
-        function (_ks_, _t2ps_) {
+        function (_ks_, _t2ps_, _t2ms_) {
 
             t2ps = _t2ps_;
+            t2ms = _t2ms_;
             ks = _ks_;
 
             return {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Map.js b/web/gui/src/main/webapp/app/view/topo2/topo2Map.js
new file mode 100644
index 0000000..4576528
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Map.js
@@ -0,0 +1,253 @@
+/*
+ * 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 Map Module.
+ Defines behavior for loading geographical maps into the map layer.
+ */
+
+(function () {
+    'use strict';
+
+    var $log, $loc, wss, ps, ms, t2ds, sus;
+
+    // internal state
+    var mapG, order, maps, map, mapItems, tintCheck, messageHandlers;
+
+    // constants
+    var mapRequest = 'mapSelectorRequest';
+
+    var countryFilters = {
+        s_america: function (c) {
+            return c.properties.continent === 'South America';
+        },
+
+        ns_america: function (c) {
+            return c.properties.custom === 'US-cont' ||
+                c.properties.subregion === 'Central America' ||
+                c.properties.continent === 'South America';
+        },
+
+        japan: function (c) {
+            return c.properties.geounit === 'Japan';
+        },
+
+        europe: function (c) {
+            return c.properties.continent === 'Europe';
+        },
+
+        italy: function (c) {
+            return c.properties.geounit === 'Italy';
+        },
+
+        uk: function (c) {
+            // technically, Ireland is not part of the United Kingdom,
+            // but the map looks weird without it showing.
+            return c.properties.adm0_a3 === 'GBR' ||
+                c.properties.adm0_a3 === 'IRL';
+        },
+
+        s_korea: function (c) {
+            return c.properties.adm0_a3 === 'KOR';
+        },
+
+        australia: function (c) {
+            return c.properties.adm0_a3 === 'AUS';
+        }
+    };
+
+    function init(zoomLayer) {
+
+        start();
+        return setUpMap(zoomLayer);
+    }
+
+    function setUpMap(zoomLayer) {
+        var prefs = currentMap(),
+            mapId = prefs.mapid,
+            mapFilePath = prefs.mapfilepath,
+            mapScale = prefs.mapscale,
+            tint = prefs.tint,
+            promise, cfilter;
+
+        mapG = d3.select('#topo-map');
+
+        if (mapG.empty()) {
+            mapG = zoomLayer.append('g').attr('id', 'topo-map');
+        } else {
+            mapG.each(function (d, i) {
+                d3.selectAll(this.childNodes).remove();
+            });
+        }
+
+        cfilter = countryFilters[mapId] || countryFilters.uk;
+
+        if (mapFilePath === '*countries') {
+
+            cfilter = countryFilters[mapId] || countryFilters.uk;
+
+            promise = ms.loadMapRegionInto(mapG, {
+                countryFilter: cfilter,
+                adjustScale: mapScale,
+                shading: ''
+            });
+        } else {
+
+            promise = ms.loadMapInto(mapG, mapFilePath, mapId, {
+                adjustScale: mapScale,
+                shading: ''
+            });
+        }
+
+        return promise;
+    }
+
+    function start() {
+        wss.bindHandlers(messageHandlers);
+    }
+
+    function stop() {
+        wss.unbindHandlers(messageHandlers);
+    }
+
+    function currentMap() {
+        return ps.getPrefs(
+            'topo_mapid',
+            {
+                mapid: 'usa',
+                mapscale: 1,
+                mapfilepath: '*continental_us',
+                tint: 'off'
+            },
+            $loc.search()
+        );
+    }
+
+    function openMapSelection() {
+        wss.sendEvent(mapRequest);
+    }
+
+    function opacifyMap(b) {
+        mapG.transition()
+            .duration(1000)
+            .attr('opacity', b ? 1 : 0);
+    }
+
+    function setMap(map) {
+        ps.setPrefs('topo_mapid', map);
+        setUpMap();
+        opacifyMap(true);
+    }
+
+    function dOk() {
+        var p = {
+            mapid: map.id,
+            mapscale: map.scale,
+            mapfilepath: map.filePath,
+            tint: 'off'
+            // tint: tintCheck.property('checked') ? 'on' : 'off'
+        };
+        setMap(p);
+        $log.debug('Dialog OK button clicked');
+    }
+
+    function dClose() {
+        $log.debug('Dialog Close button clicked (or Esc pressed)');
+    }
+
+    function selectMap() {
+        map = maps[this.options[this.selectedIndex].value];
+        $log.info('Selected map', map);
+    }
+
+    function createListContent() {
+        var content = t2ds.createDiv('map-list'),
+            form = content.append('form'),
+            current = currentMap();
+        map = maps[current.mapid];
+        mapItems = form.append('select').on('change', selectMap);
+        order.forEach(function (id) {
+            var m = maps[id];
+            mapItems.append('option')
+                .attr('value', m.id)
+                .attr('selected', m.id === current.mapid ? true : null)
+                .text(m.description);
+        });
+
+        return content;
+    }
+
+    function handleMapResponse(data) {
+        $log.info('Got response', data);
+        order = data.order;
+        maps = data.maps;
+        t2ds.openDialog()
+            .setTitle('Select Map')
+            .addContent(createListContent())
+            .addOk(dOk, 'OK')
+            .addCancel(dClose, 'Close')
+            .bindKeys();
+    }
+
+    // TODO: -- START -- Move to dedicated module
+    var prefsState = {};
+
+    function updatePrefsState(what, b) {
+        prefsState[what] = b ? 1 : 0;
+        ps.setPrefs('topo_prefs', prefsState);
+    }
+
+    function _togSvgLayer(x, G, tag, what) {
+        var on = (x === 'keyev') ? !sus.visible(G) : !!x,
+            verb = on ? 'Show' : 'Hide';
+        sus.visible(G, on);
+        updatePrefsState(tag, on);
+        // flash.flash(verb + ' ' + what);
+    }
+    // TODO: -- END -- Move to dedicated module
+
+    function toggle(x) {
+        _togSvgLayer(x, mapG, 'bg', 'background map');
+    }
+
+    angular.module('ovTopo2')
+    .factory('Topo2MapService',
+        ['$log', '$location', 'WebSocketService', 'PrefsService', 'MapService',
+            'SvgUtilService', 'Topo2DialogService',
+            function (_$log_, _$loc_, _wss_, _ps_, _ms_, _sus_, _t2ds_) {
+
+                $log = _$log_;
+                $loc = _$loc_;
+                wss = _wss_;
+                ps = _ps_;
+                ms = _ms_;
+                sus = _sus_;
+                t2ds = _t2ds_;
+
+                messageHandlers = {
+                    mapSelectorResponse: handleMapResponse
+                };
+
+                return {
+                    init: init,
+                    openMapSelection: openMapSelection,
+                    toggle: toggle,
+                    stop: stop
+                };
+            }
+        ]);
+
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
index dad55f5..2dd7997 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
@@ -75,6 +75,9 @@
                     nodeType: 'sub-region',
                     mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
                     onClick: function () {
+
+                        if (d3.event.defaultPrevented) return;
+
                         wss.sendEvent('topo2navRegion', {
                             dir: 'down',
                             rid: this.get('id')
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 2b6fc57..beef418 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -130,6 +130,7 @@
     <script src="app/view/topo2/topo2Breadcrumb.js"></script>
     <script src="app/view/topo2/topo2Collection.js"></script>
     <script src="app/view/topo2/topo2D3.js"></script>
+    <script src="app/view/topo2/topo2Dialog.js"></script>
     <script src="app/view/topo2/topo2Device.js"></script>
     <script src="app/view/topo2/topo2Event.js"></script>
     <script src="app/view/topo2/topo2Force.js"></script>
@@ -138,6 +139,7 @@
     <script src="app/view/topo2/topo2KeyCommands.js"></script>
     <script src="app/view/topo2/topo2Layout.js"></script>
     <script src="app/view/topo2/topo2Link.js"></script>
+    <script src="app/view/topo2/topo2Map.js"></script>
     <script src="app/view/topo2/topo2Model.js"></script>
     <script src="app/view/topo2/topo2NodeModel.js"></script>
     <script src="app/view/topo2/topo2Prefs.js"></script>