Simon Hunt

GUI -- Augmented pan/zoom & select/drag integration by having a toggle button fo…

…r whether meta needs to be pressed for panning (default) or selecting.
 - multi-select requires the shift key to be held down.
 - Also re-wired deselectAll() to the ESC key, instead of click on background.

Change-Id: I63502839368c6ca10c64ee583a58f836576c4546
...@@ -37,6 +37,9 @@ ...@@ -37,6 +37,9 @@
37 if (!$.isFunction(atDragEnd)) { 37 if (!$.isFunction(atDragEnd)) {
38 alert('d3util.createDragBehavior(): atDragEnd is not a function') 38 alert('d3util.createDragBehavior(): atDragEnd is not a function')
39 } 39 }
40 + if (!$.isFunction(requireMeta)) {
41 + alert('d3util.createDragBehavior(): requireMeta is not a function')
42 + }
40 43
41 function dragged(d) { 44 function dragged(d) {
42 var threshold = draggedThreshold(force.alpha()), 45 var threshold = draggedThreshold(force.alpha()),
...@@ -51,7 +54,7 @@ ...@@ -51,7 +54,7 @@
51 drag = d3.behavior.drag() 54 drag = d3.behavior.drag()
52 .origin(function(d) { return d; }) 55 .origin(function(d) { return d; })
53 .on('dragstart', function(d) { 56 .on('dragstart', function(d) {
54 - if (requireMeta ^ !d3.event.sourceEvent.metaKey) { 57 + if (requireMeta() ^ !d3.event.sourceEvent.metaKey) {
55 d3.event.sourceEvent.stopPropagation(); 58 d3.event.sourceEvent.stopPropagation();
56 59
57 d.oldX = d.x; 60 d.oldX = d.x;
...@@ -62,7 +65,7 @@ ...@@ -62,7 +65,7 @@
62 } 65 }
63 }) 66 })
64 .on('drag', function(d) { 67 .on('drag', function(d) {
65 - if (requireMeta ^ !d3.event.sourceEvent.metaKey) { 68 + if (requireMeta() ^ !d3.event.sourceEvent.metaKey) {
66 d.px = d3.event.x; 69 d.px = d3.event.x;
67 d.py = d3.event.y; 70 d.py = d3.event.y;
68 if (dragged(d)) { 71 if (dragged(d)) {
......
...@@ -100,6 +100,7 @@ ...@@ -100,6 +100,7 @@
100 } 100 }
101 101
102 #bb .btn { 102 #bb .btn {
103 + margin: 0 4px;
103 padding: 2px 6px; 104 padding: 2px 6px;
104 font-size: 9pt; 105 font-size: 9pt;
105 cursor: pointer; 106 cursor: pointer;
......
...@@ -120,15 +120,16 @@ ...@@ -120,15 +120,16 @@
120 120
121 // key bindings 121 // key bindings
122 var keyDispatch = { 122 var keyDispatch = {
123 - M: testMe, // TODO: remove (testing only) 123 + //M: testMe, // TODO: remove (testing only)
124 - S: injectStartupEvents, // TODO: remove (testing only) 124 + //S: injectStartupEvents, // TODO: remove (testing only)
125 - space: injectTestEvent, // TODO: remove (testing only) 125 + //space: injectTestEvent, // TODO: remove (testing only)
126 126
127 - B: toggleBg, // TODO: do we really need this? 127 + B: toggleBg,
128 L: cycleLabels, 128 L: cycleLabels,
129 P: togglePorts, 129 P: togglePorts,
130 U: unpin, 130 U: unpin,
131 R: resetZoomPan, 131 R: resetZoomPan,
132 + esc: deselectAll,
132 133
133 W: requestTraffic, // bag of selections 134 W: requestTraffic, // bag of selections
134 X: cancelTraffic, 135 X: cancelTraffic,
...@@ -1040,7 +1041,6 @@ ...@@ -1040,7 +1041,6 @@
1040 node.append('circle') 1041 node.append('circle')
1041 .attr('r', 8); // TODO: define host circle radius 1042 .attr('r', 8); // TODO: define host circle radius
1042 1043
1043 - // TODO: are we attaching labels to hosts?
1044 node.append('text') 1044 node.append('text')
1045 .text(hostLabel) 1045 .text(hostLabel)
1046 .attr('dy', '1.3em') 1046 .attr('dy', '1.3em')
...@@ -1231,7 +1231,13 @@ ...@@ -1231,7 +1231,13 @@
1231 1231
1232 function selectObject(obj, el) { 1232 function selectObject(obj, el) {
1233 var n, 1233 var n,
1234 - meta = d3.event.sourceEvent.metaKey; 1234 + srcEv = d3.event.sourceEvent,
1235 + meta = srcEv.metaKey,
1236 + shift = srcEv.shiftKey;
1237 +
1238 + if ((metaSelect() && !meta) || (!metaSelect() && meta)) {
1239 + return;
1240 + }
1235 1241
1236 if (el) { 1242 if (el) {
1237 n = d3.select(el); 1243 n = d3.select(el);
...@@ -1244,13 +1250,13 @@ ...@@ -1244,13 +1250,13 @@
1244 } 1250 }
1245 if (!n) return; 1251 if (!n) return;
1246 1252
1247 - if (meta && n.classed('selected')) { 1253 + if (shift && n.classed('selected')) {
1248 deselectObject(obj.id); 1254 deselectObject(obj.id);
1249 updateDetailPane(); 1255 updateDetailPane();
1250 return; 1256 return;
1251 } 1257 }
1252 1258
1253 - if (!meta) { 1259 + if (!shift) {
1254 deselectAll(); 1260 deselectAll();
1255 } 1261 }
1256 1262
...@@ -1282,15 +1288,6 @@ ...@@ -1282,15 +1288,6 @@
1282 updateDetailPane(); 1288 updateDetailPane();
1283 } 1289 }
1284 1290
1285 - // FIXME: this click handler does not get unloaded when the view does
1286 - $('#view').on('click', function(e) {
1287 - if (!$(e.target).closest('.node').length) {
1288 - if (!e.metaKey) {
1289 - deselectAll();
1290 - }
1291 - }
1292 - });
1293 -
1294 // update the state of the detail pane, based on current selections 1291 // update the state of the detail pane, based on current selections
1295 function updateDetailPane() { 1292 function updateDetailPane() {
1296 var nSel = selectOrder.length; 1293 var nSel = selectOrder.length;
...@@ -1376,7 +1373,7 @@ ...@@ -1376,7 +1373,7 @@
1376 1373
1377 function setupZoomPan() { 1374 function setupZoomPan() {
1378 function zoomed() { 1375 function zoomed() {
1379 - if (!d3.event.sourceEvent.metaKey) { 1376 + if (!metaSelect() ^ !d3.event.sourceEvent.metaKey) {
1380 zoomPan(d3.event.scale, d3.event.translate); 1377 zoomPan(d3.event.scale, d3.event.translate);
1381 } 1378 }
1382 } 1379 }
...@@ -1425,26 +1422,31 @@ ...@@ -1425,26 +1422,31 @@
1425 1422
1426 } 1423 }
1427 1424
1428 - function para(sel, text) { 1425 + // ==============================
1429 - sel.append('p').text(text); 1426 + // Toggle Buttons in masthead
1430 - }
1431 1427
1432 // TODO: toggle button (and other widgets in the masthead) should be provided 1428 // TODO: toggle button (and other widgets in the masthead) should be provided
1433 // by the framework; not generated by the view. 1429 // by the framework; not generated by the view.
1434 1430
1435 - var showTrafficOnHover; 1431 + var showTrafficOnHover,
1432 + metaToSelect;
1436 1433
1437 function addButtonBar(view) { 1434 function addButtonBar(view) {
1438 var bb = d3.select('#mast') 1435 var bb = d3.select('#mast')
1439 .append('span').classed('right', true).attr('id', 'bb'); 1436 .append('span').classed('right', true).attr('id', 'bb');
1440 1437
1441 - showTrafficOnHover = bb.append('div') 1438 + metaToSelect = bb.append('span')
1439 + .classed('btn', true)
1440 + .text('Meta to select')
1441 + .on('click', toggleMetaSelect);
1442 +
1443 + showTrafficOnHover = bb.append('span')
1442 .classed('btn', true) 1444 .classed('btn', true)
1443 .text('Show traffic on hover') 1445 .text('Show traffic on hover')
1444 - .on('click', toggleShowTraffic); 1446 + .on('click', toggleTrafficHover);
1445 } 1447 }
1446 1448
1447 - function toggleShowTraffic() { 1449 + function toggleTrafficHover() {
1448 showTrafficOnHover.classed('active', !trafficHover()); 1450 showTrafficOnHover.classed('active', !trafficHover());
1449 } 1451 }
1450 1452
...@@ -1452,6 +1454,14 @@ ...@@ -1452,6 +1454,14 @@
1452 return showTrafficOnHover.classed('active'); 1454 return showTrafficOnHover.classed('active');
1453 } 1455 }
1454 1456
1457 + function toggleMetaSelect() {
1458 + metaToSelect.classed('active', !metaSelect());
1459 + }
1460 +
1461 + function metaSelect() {
1462 + return metaToSelect.classed('active');
1463 + }
1464 +
1455 // ============================== 1465 // ==============================
1456 // View life-cycle callbacks 1466 // View life-cycle callbacks
1457 1467
...@@ -1519,8 +1529,8 @@ ...@@ -1519,8 +1529,8 @@
1519 id: d.id, 1529 id: d.id,
1520 'class': d.class, 1530 'class': d.class,
1521 'memento': { 1531 'memento': {
1522 - x: Math.floor(d.x), 1532 + x: d.x,
1523 - y: Math.floor(d.y) 1533 + y: d.y
1524 } 1534 }
1525 }); 1535 });
1526 } 1536 }
...@@ -1537,7 +1547,8 @@ ...@@ -1537,7 +1547,8 @@
1537 .linkStrength(lstrg) 1547 .linkStrength(lstrg)
1538 .on('tick', tick); 1548 .on('tick', tick);
1539 1549
1540 - network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd, true); // true=require meta 1550 + network.drag = d3u.createDragBehavior(network.force,
1551 + selectCb, atDragEnd, metaSelect);
1541 1552
1542 // create mask layer for when we lose connection to server. 1553 // create mask layer for when we lose connection to server.
1543 mask = view.$div.append('div').attr('id','topo-mask'); 1554 mask = view.$div.append('div').attr('id','topo-mask');
...@@ -1546,6 +1557,11 @@ ...@@ -1546,6 +1557,11 @@
1546 para(mask, 'Try refreshing the page.'); 1557 para(mask, 'Try refreshing the page.');
1547 } 1558 }
1548 1559
1560 + function para(sel, text) {
1561 + sel.append('p').text(text);
1562 + }
1563 +
1564 +
1549 function load(view, ctx, flags) { 1565 function load(view, ctx, flags) {
1550 // resize, in case the window was resized while we were not loaded 1566 // resize, in case the window was resized while we were not loaded
1551 resize(view, ctx, flags); 1567 resize(view, ctx, flags);
...@@ -1647,9 +1663,6 @@ ...@@ -1647,9 +1663,6 @@
1647 1663
1648 function resize(view, ctx, flags) { 1664 function resize(view, ctx, flags) {
1649 setSize(svg, view); 1665 setSize(svg, view);
1650 -
1651 - // TODO: hook to recompute layout, perhaps? work with zoom/pan code
1652 - // adjust force layout size
1653 } 1666 }
1654 1667
1655 1668
......