[turbolizer] Features and bug-fixes

Fix bugs and add a few small useful features:

* Fix Schedule view to properly parse schedule output and respond to switching
  back to graph views.
* Add shorcuts for showing edges of selected nodes.
  - 'i' shows all inputs
  - 'o' shows all outputs
  - '1'-'9' shows all nodes nth input where 1 <= n <= 9
  - 'c' shows all control inputs
  - 'e' shows all effect inputs
* Holding the control key down when using a edge-showing shortcut toggles edge
  state rather the just showing.
* 'a' selects all nodes in graph view.
* Node selection is preserved between graph views and Schedule views.
* Holding control key down when using regular expression search shows currently
  hidden nodes that match the regex search.
* Pane expansion buttons now respond to clicks in entire button area.
* Default text in regex search box makes searching more discoverable.

Review-Url: https://codereview.chromium.org/2059193002
Cr-Commit-Position: refs/heads/master@{#36932}
This commit is contained in:
danno 2016-06-13 06:21:23 -07:00 committed by Commit bot
parent ab46151aea
commit e52907f807
4 changed files with 183 additions and 46 deletions

View File

@ -173,16 +173,30 @@ class GraphView extends View {
if (d3.event.keyCode == 13) {
graph.state.selection.clear();
var reg = new RegExp(this.value);
var filterFunction = function(n) {
return (reg.exec(n.getDisplayLabel()) != null ||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
reg.exec(n.opcode) != null);
};
if (d3.event.ctrlKey) {
graph.nodes.forEach(function(n, i) {
if (filterFunction(n)) {
n.visible = true;
}
});
graph.updateGraphVisibility();
}
var selected = graph.visibleNodes.each(function(n) {
if (reg.exec(n.getDisplayLabel()) != null ||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
reg.exec(n.opcode) != null) {
if (filterFunction(n)) {
graph.state.selection.select(this, true);
}
});
graph.connectVisibleSelectedNodes();
graph.updateGraphVisibility();
this.blur();
graph.viewSelection();
}
d3.event.stopPropagation();
});
// listen for key events
@ -243,9 +257,10 @@ class GraphView extends View {
}
initializeContent(data, rememberedSelection) {
this.createGraph(data);
this.createGraph(data, rememberedSelection);
if (rememberedSelection != null) {
this.attachSelection(rememberedSelection);
this.connectVisibleSelectedNodes();
}
this.updateGraphVisibility();
}
@ -259,7 +274,7 @@ class GraphView extends View {
}
};
createGraph(data) {
createGraph(data, initiallyVisibileIds) {
var g = this;
g.nodes = data.nodes;
g.nodeMap = [];
@ -298,6 +313,11 @@ class GraphView extends View {
});
g.nodes.forEach(function(n, i) {
n.visible = isNodeInitiallyVisible(n);
if (initiallyVisibileIds != undefined) {
if (initiallyVisibileIds.has(n.id)) {
n.visible = true;
}
}
});
g.fitGraphViewToWindow();
g.updateGraphVisibility();
@ -306,6 +326,23 @@ class GraphView extends View {
g.viewWholeGraph();
}
connectVisibleSelectedNodes() {
var graph = this;
graph.state.selection.selection.forEach(function(element) {
var edgeNumber = 0;
element.__data__.inputs.forEach(function(edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
element.__data__.outputs.forEach(function(edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
});
}
updateInputAndOutputBubbles() {
var g = this;
var s = g.visibleBubbles;
@ -359,11 +396,11 @@ class GraphView extends View {
detachSelection() {
var selection = this.state.selection.detachSelection();
var result = new Set();
var s = new Set();
for (var i of selection) {
result.add(i.__data__.id);
s.add(i.__data__.id);
};
return result;
return s;
}
pathMouseDown(path, d) {
@ -436,6 +473,17 @@ class GraphView extends View {
});
}
selectAllNodes(inEdges, filter) {
var graph = this;
if (!d3.event.shiftKey) {
graph.state.selection.clear();
}
graph.visibleNodes.each(function(n) {
graph.state.selection.select(this, true);
});
graph.updateGraphVisibility();
}
svgMouseDown() {
this.state.graphMouseDown = true;
}
@ -463,36 +511,109 @@ class GraphView extends View {
// Don't handle key press repetition
if(state.lastKeyDown !== -1) return;
var getEdgeFrontier = function(inEdges) {
var getEdgeFrontier = function(inEdges, edgeFilter) {
var frontierSet = new Set();
var newState = true;
if (d3.event.ctrlKey) {
state.selection.selection.forEach(function(element) {
var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
var edgeNumber = 0;
// Control key toggles edges rather than just turning them on
edges.forEach(function(i) {
if (edgeFilter == undefined || edgeFilter(i, edgeNumber)) {
if (i.visible) {
newState = false;
}
}
++edgeNumber;
});
});
}
state.selection.selection.forEach(function(element) {
var nodes = inEdges ? element.__data__.inputs : element.__data__.outputs;
nodes.forEach(function(i) {
i.visible = true;
var candidate = inEdges ? i.source : i.target;
candidate.visible = true;
frontierSet.add(candidate);
var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
var edgeNumber = 0;
edges.forEach(function(i) {
if (edgeFilter == undefined || edgeFilter(i, edgeNumber)) {
i.visible = newState;
if (newState) {
var candidate = inEdges ? i.source : i.target;
candidate.visible = true;
frontierSet.add(candidate);
}
}
++edgeNumber;
});
});
graph.updateGraphVisibility();
return graph.visibleNodes.filter(function(n) {
return frontierSet.has(n);
});
if (newState) {
return graph.visibleNodes.filter(function(n) {
return frontierSet.has(n);
});
} else {
return undefined;
}
}
var selectNodesThroughEdges = function(inEdges, filter, reselect) {
var frontier = getEdgeFrontier(inEdges, filter);
if (frontier != undefined) {
if (reselect) {
if (!d3.event.shiftKey) {
state.selection.clear();
}
frontier.each(function(n) {
state.selection.select(this, true);
});
}
graph.updateGraphVisibility();
}
allowRepetition = false;
}
var allowRepetition = true;
switch(d3.event.keyCode) {
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// '1'-'9'
selectNodesThroughEdges(true,
(edge, index) => { return index == (d3.event.keyCode - 49); },
false);
break;
case 67:
// 'c'
selectNodesThroughEdges(true,
(edge, index) => { return edge.type == 'control'; },
false);
break;
case 69:
// 'e'
selectNodesThroughEdges(true,
(edge, index) => { return edge.type == 'effect'; },
false);
break;
case 79:
// 'o'
selectNodesThroughEdges(false, undefined, false);
break;
case 73:
// 'i'
selectNodesThroughEdges(true, undefined, false);
break;
case 65:
// 'a'
graph.selectAllNodes();
allowRepetition = false;
break;
case 38:
case 40: {
var frontier = getEdgeFrontier(d3.event.keyCode == 38);
if (!d3.event.shiftKey) {
state.selection.clear();
}
frontier.each(function(n) {
state.selection.select(this, true);
});
graph.updateGraphVisibility();
allowRepetition = false;
selectNodesThroughEdges(d3.event.keyCode == 38, undefined, true);
break;
}
}

View File

@ -76,8 +76,8 @@ class ScheduleView extends TextView {
],
// Parse opcode including []
[
[/^[A-Za-z0-9_]+(\[[^\]]+\])?$/, NODE_STYLE, -1],
[/^[A-Za-z0-9_]+(\[[^\]]+\])?/, NODE_STYLE, 3]
[/^[A-Za-z0-9_]+(\[.+])?$/, NODE_STYLE, -1],
[/^[A-Za-z0-9_]+(\[.+])?/, NODE_STYLE, 3]
],
// Parse optional parameters
[
@ -105,4 +105,23 @@ class ScheduleView extends TextView {
this.setPatterns(patterns);
this.setNodePositionMap(nodePositionMap);
}
initializeContent(data, rememberedSelection) {
super.initializeContent(data, rememberedSelection);
var graph = this;
var locations = [];
for (var id of rememberedSelection) {
locations.push({ node_id : id });
}
this.selectLocations(locations, true, false);
}
detachSelection() {
var selection = this.selection.detachSelection();
var s = new Set();
for (var i of selection) {
s.add(i.location.node_id);
};
return s;
}
}

View File

@ -16,6 +16,7 @@
padding: 0.5em;
z-index: 5;
opacity: 0.7;
cursor: pointer;
}
.search-input {
@ -227,7 +228,7 @@ ul.noindent {
-webkit-margin-after: 0px;
}
input:hover {
input:hover, .collapse-pane:hover input {
opacity: 1;
cursor: pointer;
}

View File

@ -44,6 +44,10 @@ document.onload = (function(d3){
}
}
function toggleSourceExpanded() {
setSourceExpanded(!sourceExpanded);
}
function setSourceExpanded(newState) {
sourceExpanded = newState;
updatePanes();
@ -60,6 +64,10 @@ document.onload = (function(d3){
}
}
function toggleDisassemblyExpanded() {
setDisassemblyExpanded(!disassemblyExpanded);
}
function setDisassemblyExpanded(newState) {
disassemblyExpanded = newState;
updatePanes();
@ -118,26 +126,14 @@ document.onload = (function(d3){
selectionBroker = new SelectionBroker();
function initializeHandlers(g) {
d3.select("#source-expand").on("click", function(){
setSourceExpanded(true);
d3.select("#source-collapse").on("click", function(){
toggleSourceExpanded(true);
setTimeout(function(){
g.fitGraphViewToWindow();
}, 1000);
});
d3.select("#source-shrink").on("click", function(){
setSourceExpanded(false);
setTimeout(function(){
g.fitGraphViewToWindow();
}, 1000);
});
d3.select("#disassembly-expand").on("click", function(){
setDisassemblyExpanded(true);
setTimeout(function(){
g.fitGraphViewToWindow();
}, 1000);
});
d3.select("#disassembly-shrink").on("click", function(){
setDisassemblyExpanded(false);
d3.select("#disassembly-collapse").on("click", function(){
toggleDisassemblyExpanded();
setTimeout(function(){
g.fitGraphViewToWindow();
}, 1000);