GUI -- Added "No Devices Connected" layer; (themed too).

Change-Id: I80e3cc20c426c9c9781ad73a891e0f2fe93594b9
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 b2c963e..bef8248 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -236,12 +236,17 @@
                 return s.replace(/px$/,'');
             }
 
+            function makeVisible(el, b) {
+                el.style('visibility', (b ? 'visible' : 'hidden'));
+            }
+
             return {
                 createDragBehavior: createDragBehavior,
                 loadGlow: loadGlow,
                 cat7: cat7,
                 translate: translate,
-                stripPx: stripPx
+                stripPx: stripPx,
+                makeVisible: makeVisible
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index 48f60a5..c3d4ee5 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -18,25 +18,51 @@
  ONOS GUI -- Topology View -- CSS file
  */
 
-/* --- Topo Map --- */
+/* --- Base SVG Layer --- */
 
 .light #ov-topo svg {
     background-color: #fff;
-    /* For Debugging the placement of the SVG layer... */
-    /*border: 1px solid red;*/
 }
 .dark #ov-topo svg {
     background-color: #2b2b2b;
 }
 
+
+/* --- "No Devices" Layer --- */
+
+#ov-topo svg #topo-noDevsLayer {
+    visibility: hidden;
+}
+
+.light #ov-topo svg .noDevsBird {
+    fill: #ecd;
+}
+.dark #ov-topo svg .noDevsBird {
+    fill: #683434;
+}
+
+#ov-topo svg #topo-noDevsLayer text {
+    font-size: 60pt;
+    font-style: italic;
+}
+.light #ov-topo svg #topo-noDevsLayer text {
+    fill: #dde;
+}
+.dark #ov-topo svg #topo-noDevsLayer text {
+    fill: #3b3b4f;
+}
+
+
+/* --- Topo Map --- */
+
 #ov-topo svg #topo-map {
     stroke-width: 2px;
     fill: transparent;
 }
 
 .light #ov-topo svg #topo-map {
-    /*stroke: #eee;*/
-    stroke: #88b;
+    stroke: #ddd;
+    /*stroke: #88b;*/
 }
 .dark #ov-topo svg #topo-map {
     stroke: #444;
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 c4a30b5..28b79ac 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -28,10 +28,10 @@
     ];
 
     // references to injected services etc.
-    var $log, fs, ks, zs, gs, ms, tfs;
+    var $log, fs, ks, zs, gs, ms, sus, tfs;
 
     // DOM elements
-    var ovtopo, svg, defs, zoomLayer, mapG, forceG;
+    var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
 
     // Internal state
     var zoomer;
@@ -102,12 +102,37 @@
 
     // callback invoked when the SVG view has been resized..
     function svgResized(dim) {
-        //$log.debug('TopoView just resized... ', dim);
         tfs.resize(dim);
     }
 
     // --- Background Map ------------------------------------------------
 
+    function setUpNoDevs() {
+        var g, box;
+        noDevsLayer = svg.append('g').attr({
+            id: 'topo-noDevsLayer',
+            transform: sus.translate(500,500)
+        });
+        // Note, SVG viewbox is '0 0 1000 1000', defined in topo.html.
+        // We are translating this layer to have its origin at the center
+
+        g = noDevsLayer.append('g');
+        gs.addGlyph(g, 'bird', 100).attr('class', 'noDevsBird');
+        g.append('text').text('No devices are connected')
+            .attr({ x: 120, y: 80});
+
+        box = g.node().getBBox();
+        box.x -= box.width/2;
+        box.y -= box.height/2;
+        g.attr('transform', sus.translate(box.x, box.y));
+
+        showNoDevs(true);
+    }
+
+    function showNoDevs(b) {
+        sus.makeVisible(noDevsLayer, b);
+    }
+
     function showCallibrationPoints() {
         // temp code for calibration
         var points = [
@@ -144,12 +169,12 @@
         .controller('OvTopoCtrl', [
             '$scope', '$log', '$location', '$timeout',
             'FnService', 'MastService', 'KeyService', 'ZoomService',
-            'GlyphService', 'MapService',
+            'GlyphService', 'MapService', 'SvgUtilService',
             'TopoEventService', 'TopoForceService', 'TopoPanelService',
             'TopoInstService',
 
         function ($scope, _$log_, $loc, $timeout, _fs_, mast,
-                  _ks_, _zs_, _gs_, _ms_, tes, _tfs_, tps, tis) {
+                  _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, tis) {
             var self = this;
             $log = _$log_;
             fs = _fs_;
@@ -157,6 +182,7 @@
             zs = _zs_;
             gs = _gs_;
             ms = _ms_;
+            sus = _sus_;
             tfs = _tfs_;
 
             self.notifyResize = function () {
@@ -180,6 +206,7 @@
             setUpKeys();
             setUpDefs();
             setUpZoom();
+            setUpNoDevs();
             setUpMap();
             setUpForce();
 
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
index 689729a..993dfdb 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
@@ -18,7 +18,7 @@
  ONOS GUI -- SVG -- SVG Util Service - Unit Tests
  */
 describe('factory: fw/svg/svgUtil.js', function() {
-    var $log, fs, sus, d3Elem;
+    var $log, fs, sus, svg, d3Elem;
 
     beforeEach(module('onosUtil', 'onosSvg'));
 
@@ -26,7 +26,8 @@
         $log = _$log_;
         fs = FnService;
         sus = SvgUtilService;
-        d3Elem = d3.select('body').append('svg').append('defs').attr('id', 'myDefs');
+        svg = d3.select('body').append('svg').attr('id', 'mySvg');
+        d3Elem = svg.append('defs');
     }));
 
     afterEach(function () {
@@ -39,7 +40,8 @@
 
     it('should define api functions', function () {
         expect(fs.areFunctions(sus, [
-            'createDragBehavior', 'loadGlow', 'cat7', 'translate', 'stripPx'
+            'createDragBehavior', 'loadGlow', 'cat7', 'translate', 'stripPx',
+            'makeVisible'
         ])).toBeTruthy();
     });
 
@@ -111,4 +113,16 @@
     it('should remove trailing px', function () {
         expect(sus.stripPx('4px')).toEqual('4');
     });
+
+    // === makeVisible()
+
+    it('should hide and show an element', function () {
+        var r = svg.append('rect');
+
+        sus.makeVisible(r, false);
+        expect(r.style('visibility')).toEqual('hidden');
+
+        sus.makeVisible(r, true);
+        expect(r.style('visibility')).toEqual('visible');
+    });
 });