GUI -- [ONOS-267] -- Mouse gestures added to quick help.
- added ability for view to define 'gestures' to be added to quick help.
- improved the layout of quick help by computing the widest 'key' and using that to space descriptions column.

Change-Id: I5a5a38d3218857ba9bca33c8fc79be38f17f7316
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 6e808ce..cc595a9 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -63,7 +63,8 @@
                 globalKeys: {},
                 maskedKeys: {},
                 viewKeys: {},
-                viewFn: null
+                viewFn: null,
+                viewGestures: []
             },
             alerts = {
                 open: false,
@@ -452,6 +453,10 @@
             return true;
         }
 
+        function setGestureNotes(g) {
+            keyHandler.viewGestures = isA(g) || [];
+        }
+
         function setKeyBindings(keyArg) {
             var viewKeys,
                 masked = [];
@@ -614,6 +619,7 @@
                     uid: this.uid,
                     setRadio: this.setRadio,
                     setKeys: this.setKeys,
+                    setGestures: this.setGestures,
                     dataLoadError: this.dataLoadError,
                     alert: this.alert,
                     flash: this.flash,
@@ -719,6 +725,10 @@
                 setKeyBindings(keyArg);
             },
 
+            setGestures: function (g) {
+                setGestureNotes(g);
+            },
+
             getTheme: function () {
                 return current.theme;
             },
diff --git a/web/gui/src/main/webapp/onosQuickHelp.js b/web/gui/src/main/webapp/onosQuickHelp.js
index 9c40c36..596276b 100644
--- a/web/gui/src/main/webapp/onosQuickHelp.js
+++ b/web/gui/src/main/webapp/onosQuickHelp.js
@@ -43,7 +43,8 @@
         svg = qhdiv.select('svg'),
         pane,
         rect,
-        items;
+        items,
+        keyAgg;
 
     // General functions
     function isA(a) {
@@ -73,7 +74,7 @@
     var pad = 8,
         offy = 45,
         dy = 14,
-        offDesc = 40;
+        offDesc = 8;
 
     // D3 magic
     function updateKeyItems() {
@@ -91,7 +92,7 @@
             var el = d3.select(this),
                 y = offy + dy * i;
 
-            if (d.id === '_') {
+            if (d.id[0] === '_') {
                 el.append('line')
                     .attr({ x1: 0, y1: y, x2: 1, y2: y});
             } else {
@@ -102,6 +103,8 @@
                         x: 0,
                         y: y
                     });
+                // NOTE: used for sizing column width...
+                keyAgg.append('text').text(d.key).attr('class', 'key');
 
                 el.append('text')
                     .text(d.desc)
@@ -113,11 +116,14 @@
             }
         });
 
+        var kbox = keyAgg.node().getBBox();
+        items.selectAll('.desc').attr('x', kbox.width + offDesc);
+
         var box = items.node().getBBox(),
             paneW = box.width + pad * 2,
             paneH = box.height + offy;
 
-        items.select('line').attr('x2', box.width);
+        items.selectAll('line').attr('x2', box.width);
         items.attr('transform', translate(-paneW/2, -pad));
         rect.attr({
             width: paneW,
@@ -134,25 +140,47 @@
         var gmap = d3.map(bindings.globalKeys),
             vmap = d3.map(bindings.viewKeys),
             gkeys = gmap.keys(),
-            vkeys = vmap.keys();
+            vkeys = vmap.keys(),
+            vgest = bindings.viewGestures,
+            sep = 0;
 
         gkeys.sort();
         vkeys.sort();
 
         data = [];
         gkeys.forEach(function (k) {
-            addItem('global', k, gmap.get(k));
+            addItem('glob', k, gmap.get(k));
         });
-        addItem('separator');
+        addItem('sep');
         vkeys.forEach(function (k) {
             addItem('view', k, vmap.get(k));
         });
+        addItem('sep');
+        vgest.forEach(function (g) {
+            if (g.length === 2) {
+                addItem('gest', g[0], g[1]);
+            }
+        });
+
 
         function addItem(type, k, d) {
             var id = type + '-' + k,
                 a = isA(d),
                 desc = a && a[1];
-            if (desc) {
+
+            if (type === 'sep') {
+                data.push({
+                    id: '_' + sep++,
+                    type: type
+                });
+            } else if (type === 'gest') {
+                data.push({
+                    id: id,
+                    type: type,
+                    key: k,
+                    desc: d
+                });
+            } else if (desc) {
                 data.push(
                     {
                         id: id,
@@ -161,11 +189,6 @@
                         desc: desc
                     }
                 );
-            } else if (type === 'separator') {
-                data.push({
-                    id: '_',
-                    type: type
-                });
             }
         }
     }
@@ -189,6 +212,7 @@
             });
 
         items = pane.append('g');
+        keyAgg = pane.append('g').style('visibility', 'hidden');
 
         aggregateData(bindings);
         updateKeyItems();
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 87632dc..17baa61 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -146,14 +146,23 @@
         H: [toggleHosts, 'Toggle host visibility'],
         L: [cycleLabels, 'Cycle Device labels'],
         P: togglePorts,
-        U: [unpin, 'Unpin node'],
-        R: [resetZoomPan, 'Reset zoom/pan'],
+        U: [unpin, 'Unpin node (hover mouse over)'],
+        R: [resetZoomPan, 'Reset zoom / pan'],
         V: [showTrafficAction, 'Show related traffic'],
         A: [showAllTrafficAction, 'Show all traffic'],
         F: [showDeviceLinkFlowsAction, 'Show device link flows'],
         esc: handleEscape
     };
 
+    // mouse gestures
+    var gestures = [
+        ['click', 'Select the item and show details'],
+        ['shift-click', 'Toggle selection state'],
+        ['drag', 'Reposition (and pin) device / host'],
+        ['cmd-scroll', 'Zoom in / out'],
+        ['cmd-drag', 'Pan']
+    ];
+
     // state variables
     var network = {
             view: null,     // view token reference
@@ -2385,6 +2394,7 @@
 
     //var showInstances;
 
+/*
     function addButtonBar(view) {
         var bb = d3.select('#mast')
             .append('span').classed('right', true).attr('id', 'bb');
@@ -2398,6 +2408,7 @@
 
         //showInstances = mkTogBtn('Show Instances', toggleInst);
     }
+*/
 
     function panZoom() {
         return false;
@@ -2555,10 +2566,11 @@
         // set our radio buttons and key bindings
         layerBtnSet = view.setRadio(layerButtons);
         view.setKeys(keyDispatch);
+        view.setGestures(gestures);
 
         // patch in our "button bar" for now
         // TODO: implement a more official frameworky way of doing this..
-        addButtonBar(view);
+        //addButtonBar(view);
 
         // Load map data asynchronously; complete startup after that..
         loadGeoJsonData();