GUI -- Implemented node lock ('X' key) in preparation for oblique view.

Change-Id: I485ca7977d18fe50ca7e5c500b3fc616506f4543
diff --git a/web/gui/src/main/webapp/d3Utils.js b/web/gui/src/main/webapp/d3Utils.js
index 0161459..6620f7d 100644
--- a/web/gui/src/main/webapp/d3Utils.js
+++ b/web/gui/src/main/webapp/d3Utils.js
@@ -27,7 +27,9 @@
         return $.isFunction(f) ? f : null;
     }
 
-    function createDragBehavior(force, selectCb, atDragEnd, enabled) {
+    // TODO: sensible defaults
+    function createDragBehavior(force, selectCb, atDragEnd,
+                                dragEnabled, clickEnabled) {
         var draggedThreshold = d3.scale.linear()
                 .domain([0, 0.1])
                 .range([5, 20])
@@ -35,7 +37,8 @@
             drag,
             fSel = isF(selectCb),
             fEnd = isF(atDragEnd),
-            fEnb = isF(enabled),
+            fDEn = isF(dragEnabled),
+            fCEn = isF(clickEnabled),
             bad = [];
 
         function naf(what) {
@@ -48,8 +51,11 @@
         if (!fEnd) {
             bad.push(naf('atDragEnd'));
         }
-        if (!fEnb) {
-            bad.push(naf('enabled'));
+        if (!fDEn) {
+            bad.push(naf('dragEnabled'));
+        }
+        if (!fCEn) {
+            bad.push(naf('clickEnabled'));
         }
 
         if (bad.length) {
@@ -71,7 +77,7 @@
         drag = d3.behavior.drag()
             .origin(function(d) { return d; })
             .on('dragstart', function(d) {
-                if (enabled()) {
+                if (clickEnabled() || dragEnabled()) {
                     d3.event.sourceEvent.stopPropagation();
 
                     d.oldX = d.x;
@@ -82,7 +88,7 @@
                 }
             })
             .on('drag', function(d) {
-                if (enabled()) {
+                if (dragEnabled()) {
                     d.px = d3.event.x;
                     d.py = d3.event.y;
                     if (dragged(d)) {
@@ -96,13 +102,18 @@
                 if (d.dragStarted) {
                     d.dragStarted = false;
                     if (!dragged(d)) {
-                        // consider this the same as a 'click' (selection of node)
-                        selectCb(d, this); // TODO: set 'this' context instead of param
+                        // consider this the same as a 'click'
+                        // (selection of a node)
+                        if (clickEnabled()) {
+                            selectCb(d, this); // TODO: set 'this' context instead of param
+                        }
                     }
                     d.fixed &= ~6;
 
                     // hook at the end of a drag gesture
-                    atDragEnd(d, this); // TODO: set 'this' context instead of param
+                    if (dragEnabled()) {
+                        atDragEnd(d, this); // TODO: set 'this' context instead of param
+                    }
                 }
             });
 
diff --git a/web/gui/src/main/webapp/topo.js b/web/gui/src/main/webapp/topo.js
index 97918f0..d2e67bf 100644
--- a/web/gui/src/main/webapp/topo.js
+++ b/web/gui/src/main/webapp/topo.js
@@ -148,6 +148,8 @@
         V: [showTrafficAction, 'Show related traffic'],
         A: [showAllTrafficAction, 'Show all traffic'],
         F: [showDeviceLinkFlowsAction, 'Show device link flows'],
+        X: [toggleNodeLock, 'Lock / unlock node positions'],
+        Z: [toggleOblique, 'Toggle oblique view'],
         esc: handleEscape
     };
 
@@ -203,7 +205,8 @@
         showOffline = true,
         useDetails = true,
         haveDetails = false,
-        dragAllowed = true;
+        nodeLock = false,
+        oblique = false;
 
     // constants
     var hoverModeAll = 1,
@@ -323,6 +326,16 @@
         bgImg.style('visibility', visVal(vis === 'hidden'));
     }
 
+    function toggleNodeLock() {
+        nodeLock = !nodeLock;
+        flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
+    }
+
+    function toggleOblique() {
+        oblique = !oblique;
+        // TODO: oblique transformation
+    }
+
     function toggleHosts() {
         showHosts = !showHosts;
         updateHostVisibility();
@@ -2704,9 +2717,12 @@
         // predicate that indicates when dragging is active
         function dragEnabled() {
             // meta key pressed means we are zooming/panning (so disable drag)
-            return dragAllowed && !d3.event.sourceEvent.metaKey;
-            // dragAllowed will be set false when we are in oblique view
-            // or when we 'lock' node positions
+            return !nodeLock && !d3.event.sourceEvent.metaKey;
+        }
+
+        // predicate that indicates when clicking is active
+        function clickEnabled() {
+            return true;
         }
 
         // set up the force layout
@@ -2722,7 +2738,7 @@
             .on('tick', tick);
 
         network.drag = d3u.createDragBehavior(network.force,
-            selectCb, atDragEnd, dragEnabled);
+            selectCb, atDragEnd, dragEnabled, clickEnabled);
 
 
         // create mask layer for when we lose connection to server.