ONOS-5726: completed implementation of "showIntent" overlay support.
(note, there is still some cleanup to be done).

Change-Id: I6c805ad954b97ca261b5536240b277df7712a834
diff --git a/apps/pathpainter/src/main/resources/app/view/ppTopov/ppTopovOverlay.js b/apps/pathpainter/src/main/resources/app/view/ppTopov/ppTopovOverlay.js
index 5593cb0..322c69e 100644
--- a/apps/pathpainter/src/main/resources/app/view/ppTopov/ppTopovOverlay.js
+++ b/apps/pathpainter/src/main/resources/app/view/ppTopov/ppTopovOverlay.js
@@ -17,7 +17,7 @@
         // NOTE: this must match the ID defined in AppUiTopovOverlay
         overlayId: 'pp-overlay',
         glyphId: 'm_topo',
-        tooltip: 'Path Painter Topo Overlay',
+        tooltip: 'Path Painter Overlay',
 
         activate: function () {
             $log.debug("Path painter topology overlay ACTIVATED");
@@ -122,11 +122,12 @@
 
         hooks: {
             // hook for handling escape key
-            // Must return true to consume ESC, false otherwise.
+            // TODO: Must return true to consume ESC, false otherwise.
             escape: function () {
                 selectionCallback();
                 pps.setSrc();
                 pps.setDst();
+                // FIXME: should explicitly return true or false
             },
 
             // hooks for when the selection changes...
diff --git a/web/gui/src/main/webapp/app/fw/util/prefs.js b/web/gui/src/main/webapp/app/fw/util/prefs.js
index 14bc5e2..16f2607 100644
--- a/web/gui/src/main/webapp/app/fw/util/prefs.js
+++ b/web/gui/src/main/webapp/app/fw/util/prefs.js
@@ -43,15 +43,28 @@
     }
 
     // converts string values to numbers for selected (or all) keys
-    function asNumbers(obj, keys) {
+    // asNumbers(obj, ['a', 'b'])        <-- convert keys .a, .b to numbers
+    // asNumbers(obj, ['a', 'b'], true)  <-- convert ALL BUT keys .a, .b to numbers
+
+    function asNumbers(obj, keys, not) {
         if (!obj) return null;
 
-        if (!keys) {
+        var skip = {};
+        if (not) {
+            keys.forEach(function (k) {
+                skip[k] = 1;
+            });
+        }
+
+        if (!keys || not) {
             // do them all
             angular.forEach(obj, function (v, k) {
-                obj[k] = Number(obj[k]);
+                if (!not || !skip[k]) {
+                    obj[k] = Number(obj[k]);
+                }
             });
         } else {
+            // do the explicitly named keys
             keys.forEach(function (k) {
                 obj[k] = Number(obj[k]);
             });
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.css b/web/gui/src/main/webapp/app/view/intent/intent.css
index 223168f..41a49c4 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.css
+++ b/web/gui/src/main/webapp/app/view/intent/intent.css
@@ -30,3 +30,29 @@
 #ov-intent td.details {
     padding-left: 36px;
 }
+
+#ov-intent div.show-intent-btn {
+    position:relative;
+}
+
+#ov-intent .dropdown {
+    position: absolute;
+    top: 42px;
+    left: 0;
+    display: none;
+
+    border: 1px solid black;
+    background-color: #aec5dc;
+}
+
+#ov-intent .overlay-choice {
+    padding: 2px;
+    white-space: nowrap;
+    font-size: 10pt;
+
+    color: black;
+}
+
+#ov-intent .overlay-choice:hover {
+    color: white;
+}
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.html b/web/gui/src/main/webapp/app/view/intent/intent.html
index 9de4c38..95fe5ca 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.html
+++ b/web/gui/src/main/webapp/app/view/intent/intent.html
@@ -27,10 +27,14 @@
 
             <div class="separator"></div>
 
-            <div ng-class="{active: !!selId}"
-                 icon icon-id="topo" icon-size="42"
-                 tooltip tt-msg="topoTip"
-                 ng-click="showIntent()"></div>
+            <div class="show-intent-btn">
+                <div ng-class="{active: !!selId}"
+                     icon icon-id="topo" icon-size="42"
+                     tooltip tt-msg="topoTip"
+                     ng-click="showIntent()">
+                </div>
+                <div class="dropdown"></div>
+            </div>
 
             <div ng-class="{'active': !!selId && isIntentWithdrawn()}"
                  icon icon-id="play" icon-size="42"
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.js b/web/gui/src/main/webapp/app/view/intent/intent.js
index 559dca1..a2375ba 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.js
+++ b/web/gui/src/main/webapp/app/view/intent/intent.js
@@ -24,7 +24,8 @@
     var dialogId = 'remove-intent-dialog',
         dialogOpts = {
             edge: 'right'
-        };
+        },
+        dropdown;
 
     angular.module('ovIntent', [])
         .controller('OvIntentCtrl',
@@ -37,6 +38,9 @@
             $scope.brief = true;
             $scope.intentState = 'NA';
             $scope.fired = false;
+            $scope.showOverlays = false;
+
+            dropdown = d3.select('div.show-intent-btn .dropdown');
 
             function selCb($event, row) {
                 $log.debug('Got a click on:', row);
@@ -51,6 +55,7 @@
                 } : null;
 
                 $scope.intentState = row.state;
+                showDropdown(false);
             }
 
             function respCb() {
@@ -76,17 +81,67 @@
             $scope.purgeTip = 'Purge selected intent';
             $scope.purgeAllTip = 'Purge withdrawn intents';
 
-            $scope.showIntent = function () {
-                var d = $scope.intentData;
-                if (d) {
-                    // TODO: if more than one overlay registered, provide dropdown
-                    $log.debug('SHOW-INTENT: overlay list:', tov.list());
-                    $log.debug('SHOW-INTENT: overlay info:', tov.list(true));
 
+            function showDropdown(b) {
+                dropdown.style('display', b ? 'block' : 'none');
+            }
+
+            $scope.showIntent = function () {
+                var d = $scope.intentData,
+                    tovData,
+                    ncb;
+
+                if (!d) {
+                    // no intent selected - nothing to do
+                    return;
+                }
+
+                function setOvAndNavigate(info) {
+                    d.overlayId = info.id;
                     ns.navTo('topo', d);
                 }
+
+                function clickMe(data) {
+                    showDropdown(false);
+                    setOvAndNavigate(data);
+                }
+
+                function setUpSelection(tovData) {
+                    dropdown.text(null);
+
+                    tovData.forEach(function (data) {
+                        var div = dropdown.append('div');
+                        div.classed('overlay-choice', true);
+                        div.text(data.tt);
+                        div.on('click', function () {
+                            clickMe(data);
+                        });
+                    });
+
+                    showDropdown(true);
+                }
+
+                tovData = tov.listOverlaysThatShowIntents();
+                ncb = tovData.length;
+                // NOTE: ncb should be at least 1, (traffic overlay)
+
+                if (ncb === 1) {
+                    setOvAndNavigate(tovData[0]);
+
+                } else if (ncb > 1) {
+                    // let the user choose which overlay to invoke...
+                    setUpSelection(tovData);
+
+                } else {
+                    $log.error('Internal Error - no overlay configured',
+                    'to show selected intent on topology view');
+                }
             };
 
+
+
+            // TODO: clean up the following code...
+
             $scope.isIntentInstalled = function () {
                 return $scope.intentState === 'Installed';
             };
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 90e9411..1762cf4 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -476,7 +476,9 @@
 
     function restoreConfigFromPrefs() {
         // NOTE: toolbar will have set this for us..
-        prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs));
+        prefsState = ps.asNumbers(
+            ps.getPrefs('topo_prefs', ttbs.defaultPrefs), ['ovid'], true
+        );
 
         $log.debug('TOPO- Prefs State:', prefsState);
 
@@ -498,7 +500,9 @@
     //  have opened the websocket to the server; hence this extra function
     // invoked after tes.start()
     function restoreSummaryFromPrefs() {
-        prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs));
+        prefsState = ps.asNumbers(
+            ps.getPrefs('topo_prefs', ttbs.defaultPrefs), ['ovid'], true
+        );
         $log.debug('TOPO- Prefs SUMMARY State:', prefsState.summary);
 
         flash.enable(false);
@@ -518,9 +522,6 @@
 
             // if an intent should be shown, invoke the appropriate callback
             if ($scope.intentData) {
-
-                // TODO: if a specific overlay was requested, activate that first
-
                 tov.hooks.showIntent($scope.intentData);
             }
 
@@ -546,7 +547,9 @@
                   _tds_, _t3s_, _tes_,
                   _tfs_, _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_,
                   _ttbs_, _tms_, _tspr_, _ttip_, _tov_) {
+
             var params = _$loc_.search(),
+                selOverlay = params.overlayId,
                 projection,
                 dim,
                 uplink = {
@@ -668,7 +671,8 @@
             tps.initPanels();
 
             restoreConfigFromPrefs();
-            ttbs.setDefaultOverlay(prefsState.ovidx);
+
+            ttbs.selectOverlay(selOverlay || prefsState.ovid);
 
             $log.debug('registered overlays...', tov.list());
             $log.log('OvTopoCtrl has been created');
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index dbb1cc3..ad42470 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -108,25 +108,27 @@
         $log.debug(tos + 'registered overlay: ' + id, overlay);
     }
 
-    // Returns the list of overlay identifiers. If a truthy argument is supplied,
-    // returns an augmented list of overlay tokens, providing overlay ID,
-    // glyph ID and overlay Tooltip text.
-    function list(x) {
-        var oids = d3.map(overlays).keys(),
-            info = [];
+    // Returns the list of overlay identifiers.
+    function list() {
+        return d3.map(overlays).keys();
+    }
 
-        if (!x) {
-            return oids;
-        }
+    // returns data on overlays that implement the showIntent callback
+    function listShowIntents() {
+        var result = [];
+        angular.forEach(overlays, function (ov) {
+            var hooks = fs.isO(ov.hooks) || {},
+                sicb = fs.isF(hooks.showintent);
 
-        oids.forEach(function (oid) {
-            var o = overlays[oid],
-                ot = o.tooltip || '%' + o.overlayId + '%',
-                og = o._glyphId;
-
-            info.push({ id: oid, tt: ot, gid: og });
+            if (sicb) {
+                result.push({
+                    id: ov.overlayId,
+                    tt: ov.tooltip || '%' + ov.overlayId + '%',
+                    gid: ov._glyphId
+                });
+            }
         });
-        return info;
+        return result;
     }
 
     // add a radio button for each registered overlay
@@ -302,7 +304,6 @@
 
     // Request from Intent View to visualize an intent on the topo view
     function showIntentHook(intentData) {
-        $log.debug('^^ topoOverlay.showIntentHook(...) ^^');
         var cb = _hook('showintent');
         return cb && cb(intentData);
     }
@@ -445,6 +446,7 @@
                 register: register,
                 setApi: setApi,
                 list: list,
+                listOverlaysThatShowIntents: listShowIntents,
                 augmentRbset: augmentRbset,
                 mkGlyphId: mkGlyphId,
                 tbSelection: tbSelection,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index 6d07da7..351154c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -78,7 +78,7 @@
             porthl: 1,
             bg: 0,
             spr: 0,
-            ovidx: 1,   // default to traffic overlay
+            ovid: 'traffic',   // default to traffic overlay
             toolbar: 0
         },
         prefsMap = {
@@ -104,7 +104,9 @@
     }
 
     function setInitToggleState() {
-        cachedState = ps.asNumbers(ps.getPrefs(cooktag, defaultPrefsState));
+        cachedState = ps.asNumbers(
+            ps.getPrefs(cooktag, defaultPrefsState), ['ovid'], true
+        );
         $log.debug('TOOLBAR---- read prefs state:', cachedState);
 
         if (!cachedState) {
@@ -199,7 +201,7 @@
         thirdRow.clear();
 
         // persist our choice of overlay...
-        persistTopoPrefs('ovidx', ovIndex[oid] || 0);
+        persistTopoPrefs('ovid', oid);
 
         if (!order.length) {
             thirdRow.setText(selOver);
@@ -277,10 +279,11 @@
         persistTopoPrefs('toolbar');
     }
     
-    function setDefaultOverlay(prefsIdx) {
-        var idx = ovIndex[defaultOverlay] || 0;
-        if (prefsIdx >= 0 && prefsIdx < ovRset.size()) {
-            idx = prefsIdx;
+    function selectOverlay(ovid) {
+        var idx = ovIndex[defaultOverlay] || 0,
+            pidx = (ovid === null) ? 0 : ovIndex[ovid] || -1;
+        if (pidx >= 0 && pidx < ovRset.size()) {
+            idx = pidx;
         }
         ovRset.selectedIndex(idx);
     }
@@ -311,7 +314,7 @@
                 destroyToolbar: destroyToolbar,
                 keyListener: keyListener,
                 toggleToolbar: toggleToolbar,
-                setDefaultOverlay: setDefaultOverlay,
+                selectOverlay: selectOverlay,
                 defaultPrefs: defaultPrefsState,
                 fnkey: fnkey
             };
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index 13a5f2a..312b652 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -144,9 +144,9 @@
             },
 
             // intent visualization hook
-            showintent: function (intentData) {
-                $log.debug('^^ trafficOverlay.showintent() ^^');
-                tts.selectIntent(intentData);
+            showintent: function (info) {
+                $log.debug('^^ trafficOverlay.showintent() ^^', info);
+                tts.selectIntent(info);
             }
         }
     };
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 e37872f..632dd14 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -211,7 +211,7 @@
             // tps.initPanels();
 
             // restoreConfigFromPrefs();
-            // ttbs.setDefaultOverlay(prefsState.ovidx);
+            // ttbs.setDefaultOverlay(prefsState.ovid);
 
             // $log.debug('registered overlays...', tov.list());
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
index 690ac59..9eaf243 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
@@ -29,7 +29,7 @@
         porthl: 1,
         bg: 0,
         spr: 0,
-        ovidx: 1,   // default to traffic overlay
+        ovid: 'traffic',   // default to traffic overlay
         toolbar: 0
     };