added javadoc and improved root predicate for loop vertices. changed CIRCLE symbol name

GP-625 updated module IP for jungrapht 1.2
missing comma
changed jungrapht version to 1.2
changed favored edge and comparator
rename for updates to exp layouts
updates for graph display api generalization
renamed exp layout
added placeholders for exp layouts
removed problematic changes to layouts
allow edge selection to be enabled. Begin to organize default graph api symbols in GraphDisplay
updates
full fix for GP-625
This commit is contained in:
dragonmacher 2021-03-03 09:22:36 -05:00 committed by ghidra1
parent 45927bb9c3
commit 76b66e2a53
12 changed files with 235 additions and 101 deletions

View file

@ -31,6 +31,7 @@ import ghidra.program.model.pcode.*;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import java.util.*; import java.util.*;
import static ghidra.service.graph.GraphDisplay.*;
public class GraphAST extends GhidraScript { public class GraphAST extends GhidraScript {
protected static final String COLOR_ATTRIBUTE = "Color"; protected static final String COLOR_ATTRIBUTE = "Color";
@ -66,11 +67,12 @@ public class GraphAST extends GhidraScript {
buildGraph(); buildGraph();
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put("selectedVertexColor", "0xFF1493"); properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
properties.put("selectedEdgeColor", "0xFF1493"); properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put("displayVerticesAsIcons", "false"); properties.put(DISPLAY_VERTICES_AS_ICONS, "false");
properties.put("vertexLabelPosition", "S"); properties.put(VERTEX_LABEL_POSITION, "S");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay graphDisplay = GraphDisplay graphDisplay =
graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor); graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor);
// graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); // // graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); //

View file

@ -34,6 +34,8 @@ import ghidra.util.exception.GraphException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import static ghidra.service.graph.GraphDisplay.*;
public class ASTGraphTask extends Task { public class ASTGraphTask extends Task {
enum GraphType { enum GraphType {
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow"); CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
@ -104,9 +106,10 @@ public class ASTGraphTask extends Task {
createControlFlowGraph(graph, monitor); createControlFlowGraph(graph, monitor);
} }
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put("selectedVertexColor", "0xFF1493"); properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
properties.put("selectedEdgeColor", "0xFF1493"); properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor); GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor);
ASTGraphDisplayListener displayListener = ASTGraphDisplayListener displayListener =
new ASTGraphDisplayListener(tool, display, hfunction, graphType); new ASTGraphDisplayListener(tool, display, hfunction, graphType);

View file

@ -1,7 +1,7 @@
EXCLUDE FROM GHIDRA JAR: true EXCLUDE FROM GHIDRA JAR: true
MODULE FILE LICENSE: lib/jungrapht-visualization-1.1.jar BSD MODULE FILE LICENSE: lib/jungrapht-visualization-1.2.jar BSD
MODULE FILE LICENSE: lib/jungrapht-layout-1.1.jar BSD MODULE FILE LICENSE: lib/jungrapht-layout-1.2.jar BSD
MODULE FILE LICENSE: lib/jgrapht-core-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jgrapht-core-1.5.0.jar LGPL 2.1
MODULE FILE LICENSE: lib/jgrapht-io-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jgrapht-io-1.5.0.jar LGPL 2.1
MODULE FILE LICENSE: lib/jheaps-0.13.jar Apache License 2.0 MODULE FILE LICENSE: lib/jheaps-0.13.jar Apache License 2.0

View file

@ -12,8 +12,8 @@ dependencies {
compile project(":Base") compile project(":Base")
// jungrapht - exclude slf4j which produces a conflict with other uses with Ghidra // jungrapht - exclude slf4j which produces a conflict with other uses with Ghidra
compile ("com.github.tomnelson:jungrapht-visualization:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } compile ("com.github.tomnelson:jungrapht-visualization:1.2") { exclude group: "org.slf4j", module: "slf4j-api" }
compile ("com.github.tomnelson:jungrapht-layout:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } compile ("com.github.tomnelson:jungrapht-layout:1.2") { exclude group: "org.slf4j", module: "slf4j-api" }
compile "org.jgrapht:jgrapht-core:1.5.0" compile "org.jgrapht:jgrapht-core:1.5.0"

View file

@ -103,28 +103,33 @@ public class DefaultGraphDisplay implements GraphDisplay {
private static final String ACTION_OWNER = "GraphServices"; private static final String ACTION_OWNER = "GraphServices";
/*
A handful of properties that can be set via the constructor
*/
private static final String SELECTED_VERTEX_COLOR = "selectedVertexColor";
private static final String SELECTED_EDGE_COLOR = "selectedEdgeColor";
private static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm";
private static final String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons";
private static final String VERTEX_LABEL_POSITION = "vertexLabelPosition";
private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000); private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000);
private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName()); private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName());
private Map<String, String> displayProperties = new HashMap<>(); private Map<String, String> displayProperties;
private Set<DockingActionIf> addedActions = new LinkedHashSet<>(); private Set<DockingActionIf> addedActions = new LinkedHashSet<>();
private GraphDisplayListener listener = new DummyGraphDisplayListener(); private GraphDisplayListener listener = new DummyGraphDisplayListener();
private String title; private String title;
private AttributedGraph graph; private AttributedGraph graph;
private static String DEFAULT_EDGE_TYPE_PRIORITY_LIST =
"Fall-Through,"+
"Conditional-Return,"+
"Unconditional-Jump,"+
"Conditional-Jump,"+
"Unconditional-Call,"+
"Conditional-Call,"+
"Terminator,"+
"Computed,"+
"Indirection,"+
"Entry";
private static String DEFAULT_FAVORED_EDGES = "Fall-Through";
/** /**
* a unique id for this {@link GraphDisplay} * a unique id for this {@link GraphDisplay}
*/ */
@ -219,7 +224,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.getComponent().add(satelliteViewer.getComponent()); viewer.getComponent().add(satelliteViewer.getComponent());
} }
layoutTransitionManager = layoutTransitionManager =
new LayoutTransitionManager(viewer, this::isRoot); new LayoutTransitionManager(viewer, this::isRoot,
getEdgeTypePriorityList(),
getFavoredEdgePredicate());
viewer.getComponent().addComponentListener(new ComponentAdapter() { viewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override @Override
@ -252,6 +259,19 @@ public class DefaultGraphDisplay implements GraphDisplay {
return Colors.getHexColor(property); return Colors.getHexColor(property);
} }
private List<String> getEdgeTypePriorityList() {
return Arrays.asList(displayProperties
.getOrDefault(EDGE_TYPE_PRIORITY_LIST, DEFAULT_EDGE_TYPE_PRIORITY_LIST)
.split(","));
}
private Predicate<AttributedEdge> getFavoredEdgePredicate() {
String[] favoredEdges = displayProperties.getOrDefault(FAVORED_EDGES, DEFAULT_FAVORED_EDGES)
.split(",");
return attributedEdge -> Arrays.stream(favoredEdges)
.anyMatch(s -> s.equals(attributedEdge.getAttribute("EdgeType")));
}
JComponent getComponent() { JComponent getComponent() {
JComponent component = viewer.getComponent(); JComponent component = viewer.getComponent();
component.setFocusable(true); component.setFocusable(true);
@ -486,7 +506,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
"Extends the current selection by including the target/source vertices " + "Extends the current selection by including the target/source vertices " +
"of all edges whose source/target is selected") "of all edges whose source/target is selected")
.keyBinding("ctrl C") .keyBinding("ctrl C")
.enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) && .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) ||
!isAllSelected(getTargetVerticesFromSelected())) !isAllSelected(getTargetVerticesFromSelected()))
.onAction(c -> growSelection(getAllComponentVerticesFromSelected())) .onAction(c -> growSelection(getAllComponentVerticesFromSelected()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
@ -610,6 +630,23 @@ public class DefaultGraphDisplay implements GraphDisplay {
private void growSelection(Set<AttributedVertex> vertices) { private void growSelection(Set<AttributedVertex> vertices) {
viewer.getSelectedVertexState().select(vertices); viewer.getSelectedVertexState().select(vertices);
selectEdgesConnecting(vertices);
}
// select all the edges that connect the supplied vertices
private void selectEdgesConnecting(Collection<AttributedVertex> vertices) {
viewer.getSelectedEdgeState().select(
graph.edgeSet()
.stream()
.filter(
e -> {
AttributedVertex source = graph.getEdgeSource(e);
AttributedVertex target = graph.getEdgeTarget(e);
return vertices.contains(source)
&& vertices.contains(target);
})
.collect(Collectors.toSet()));
} }
private boolean isAllSelected(Set<AttributedVertex> vertices) { private boolean isAllSelected(Set<AttributedVertex> vertices) {
@ -617,8 +654,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private Set<AttributedVertex> getSourceVerticesFromSelected() { private Set<AttributedVertex> getSourceVerticesFromSelected() {
Set<AttributedVertex> sources = new HashSet<>();
Set<AttributedVertex> selectedVertices = getSelectedVertices(); Set<AttributedVertex> selectedVertices = getSelectedVertices();
Set<AttributedVertex> sources = new HashSet<>(selectedVertices);
for (AttributedVertex v : selectedVertices) { for (AttributedVertex v : selectedVertices) {
Set<AttributedEdge> edges = graph.incomingEdgesOf(v); Set<AttributedEdge> edges = graph.incomingEdgesOf(v);
edges.forEach(e -> sources.add(graph.getEdgeSource(e))); edges.forEach(e -> sources.add(graph.getEdgeSource(e)));
@ -635,8 +672,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private Set<AttributedVertex> getTargetVerticesFromSelected() { private Set<AttributedVertex> getTargetVerticesFromSelected() {
Set<AttributedVertex> targets = new HashSet<>();
Set<AttributedVertex> selectedVertices = getSelectedVertices(); Set<AttributedVertex> selectedVertices = getSelectedVertices();
Set<AttributedVertex> targets = new HashSet<>(selectedVertices);
for (AttributedVertex v : selectedVertices) { for (AttributedVertex v : selectedVertices) {
Set<AttributedEdge> edges = graph.outgoingEdgesOf(v); Set<AttributedEdge> edges = graph.outgoingEdgesOf(v);
edges.forEach(e -> targets.add(graph.getEdgeTarget(e))); edges.forEach(e -> targets.add(graph.getEdgeTarget(e)));
@ -674,9 +711,20 @@ public class DefaultGraphDisplay implements GraphDisplay {
return upstream; return upstream;
} }
/**
* Gather all source and target vertices until there are no more available.
* @return all the vertices in the component(s) of the selected vertices
*/
public Set<AttributedVertex> getAllComponentVerticesFromSelected() { public Set<AttributedVertex> getAllComponentVerticesFromSelected() {
Set<AttributedVertex> componentVertices = getAllDownstreamVerticesFromSelected(); Set<AttributedVertex> componentVertices = new HashSet<>(viewer.getSelectedVertices());
componentVertices.addAll(getAllUpstreamVerticesFromSelected()); Set<AttributedVertex> downstream = getAllDownstreamVerticesFromSelected();
Set<AttributedVertex> upstream = getAllUpstreamVerticesFromSelected();
while (!downstream.isEmpty() || !upstream.isEmpty()) {
componentVertices.addAll(downstream);
componentVertices.addAll(upstream);
downstream = getAllDownstreamVerticesFromSelected();
upstream = getAllUpstreamVerticesFromSelected();
}
return componentVertices; return componentVertices;
} }
@ -974,13 +1022,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
/** /**
* Determines if a vertex is a root. For our purpose, a root either has no incoming edges * Determines if a vertex is a root. For our purpose, a root either has no incoming edges
* or has at least one outgoing "favored" edge and no incoming "favored" edge * or if all edges of a vertex are 'loop' edges
* @param vertex the vertex to test if it is a root * @param vertex the vertex to test if it is a root
* @return true if the vertex is a root * @return true if the vertex is a root
*/ */
private boolean isRoot(AttributedVertex vertex) { private boolean isRoot(AttributedVertex vertex) {
Set<AttributedEdge> incomingEdgesOf = graph.incomingEdgesOf(vertex); Set<AttributedEdge> incomingEdgesOf = graph.incomingEdgesOf(vertex);
return incomingEdgesOf.isEmpty(); return incomingEdgesOf.isEmpty() ||
graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex));
} }
/** /**
@ -1249,12 +1298,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
setVertexPreferences(vv); setVertexPreferences(vv);
// the selectedEdgeState will be controlled by the vertices that are selected.
// if both endpoints of an edge are selected, select that edge.
vv.setSelectedEdgeState(
new VertexEndpointsSelectedEdgeSelectedState<>(vv.getVisualizationModel()::getGraph,
vv.getSelectedVertexState()));
// selected edges will be drawn with a wider stroke // selected edges will be drawn with a wider stroke
renderContext.setEdgeStrokeFunction( renderContext.setEdgeStrokeFunction(
e -> isSelected(e) ? new BasicStroke(20.f) e -> isSelected(e) ? new BasicStroke(20.f)
@ -1278,7 +1321,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
// cause the lightweight (optimized) renderer to use the vertex shapes instead // cause the lightweight (optimized) renderer to use the vertex shapes instead
// of using default shapes. // of using default shapes.
if (vertexRenderer instanceof LightweightVertexRenderer) { if (vertexRenderer instanceof LightweightVertexRenderer) {
Function<AttributedVertex, Shape> vertexShapeFunction = Function<AttributedVertex, Shape> vertexShapeFunction =
renderContext.getVertexShapeFunction(); renderContext.getVertexShapeFunction();
@ -1301,7 +1343,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
vv.getComponent().removeMouseListener(mouseListener); vv.getComponent().removeMouseListener(mouseListener);
} }
graphMouse = new JgtGraphMouse(this); graphMouse = new JgtGraphMouse(this,
Boolean.parseBoolean(displayProperties.getOrDefault(ENABLE_EDGE_SELECTION,
"false")));
vv.setGraphMouse(graphMouse); vv.setGraphMouse(graphMouse);
return vv; return vv;

View file

@ -15,7 +15,9 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import java.util.Comparator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import org.jungrapht.visualization.layout.algorithms.*; import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
@ -36,22 +38,38 @@ class LayoutFunction
static final String KAMADA_KAWAI = "Force Balanced"; static final String KAMADA_KAWAI = "Force Balanced";
static final String FRUCTERMAN_REINGOLD = "Force Directed"; static final String FRUCTERMAN_REINGOLD = "Force Directed";
static final String CIRCLE_MINCROSS = "Circle"; static final String CIRCLE = "Circle";
static final String TIDIER_TREE = "Compact Hierarchical"; static final String TIDIER_TREE = "Compact Hierarchical";
static final String TIDIER_RADIAL_TREE = "Compact Radial"; static final String TIDIER_RADIAL_TREE = "Compact Radial";
static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down"; static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path"; static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex"; static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham"; static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
static final String EXP_MIN_CROSS_TOP_DOWN = "Experimental Hierarchical MinCross Top Down";
static final String EXP_MIN_CROSS_LONGEST_PATH = "Experimental Hierarchical MinCross Longest Path";
static final String EXP_MIN_CROSS_NETWORK_SIMPLEX = "Experimental Hierarchical MinCross Network Simplex";
static final String EXP_MIN_CROSS_COFFMAN_GRAHAM = "Experimental Hierarchical MinCross Coffman Graham";
static final String TREE = "Hierarchical"; static final String TREE = "Hierarchical";
static final String RADIAL = "Radial"; static final String RADIAL = "Radial";
static final String BALLOON = "Balloon"; static final String BALLOON = "Balloon";
static final String GEM = "Gem (Graph Embedder)"; static final String GEM = "Gem (Graph Embedder)";
Predicate<AttributedEdge> favoredEdgePredicate;
Comparator<AttributedEdge> edgeTypeComparator;
LayoutFunction(Comparator<AttributedEdge> edgeTypeComparator, Predicate<AttributedEdge> favoredEdgePredicate) {
this.edgeTypeComparator = edgeTypeComparator;
this.favoredEdgePredicate = favoredEdgePredicate;
}
public String[] getNames() { public String[] getNames() {
return new String[] { TIDIER_TREE, TREE, return new String[] { TIDIER_TREE, TREE,
TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH, TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH,
MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE_MINCROSS, MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE,
EXP_MIN_CROSS_TOP_DOWN,
EXP_MIN_CROSS_LONGEST_PATH,
EXP_MIN_CROSS_NETWORK_SIMPLEX,
EXP_MIN_CROSS_COFFMAN_GRAHAM,
KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM
}; };
} }
@ -67,27 +85,56 @@ class LayoutFunction
case FRUCTERMAN_REINGOLD: case FRUCTERMAN_REINGOLD:
return FRLayoutAlgorithm.<AttributedVertex> builder() return FRLayoutAlgorithm.<AttributedVertex> builder()
.repulsionContractBuilder(BarnesHutFRRepulsion.builder()); .repulsionContractBuilder(BarnesHutFRRepulsion.builder());
case CIRCLE_MINCROSS: case CIRCLE:
return CircleLayoutAlgorithm.<AttributedVertex> builder() return CircleLayoutAlgorithm.<AttributedVertex> builder()
.reduceEdgeCrossing(true); .reduceEdgeCrossing(false);
case TIDIER_RADIAL_TREE: case TIDIER_RADIAL_TREE:
return TidierRadialTreeLayoutAlgorithm return TidierRadialTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder(); .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator);
case MIN_CROSS_TOP_DOWN: case MIN_CROSS_TOP_DOWN:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.layering(Layering.TOP_DOWN); .layering(Layering.TOP_DOWN);
case MIN_CROSS_LONGEST_PATH: case MIN_CROSS_LONGEST_PATH:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.layering(Layering.LONGEST_PATH); .layering(Layering.LONGEST_PATH);
case MIN_CROSS_NETWORK_SIMPLEX: case MIN_CROSS_NETWORK_SIMPLEX:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.layering(Layering.NETWORK_SIMPLEX); .layering(Layering.NETWORK_SIMPLEX);
case MIN_CROSS_COFFMAN_GRAHAM: case MIN_CROSS_COFFMAN_GRAHAM:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.layering(Layering.COFFMAN_GRAHAM);
case EXP_MIN_CROSS_TOP_DOWN:
return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.favoredEdgePredicate(favoredEdgePredicate)
.layering(Layering.TOP_DOWN);
case EXP_MIN_CROSS_LONGEST_PATH:
return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.favoredEdgePredicate(favoredEdgePredicate)
.layering(Layering.LONGEST_PATH);
case EXP_MIN_CROSS_NETWORK_SIMPLEX:
return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.favoredEdgePredicate(favoredEdgePredicate)
.layering(Layering.NETWORK_SIMPLEX);
case EXP_MIN_CROSS_COFFMAN_GRAHAM:
return EiglspergerLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator)
.favoredEdgePredicate(favoredEdgePredicate)
.layering(Layering.COFFMAN_GRAHAM); .layering(Layering.COFFMAN_GRAHAM);
case RADIAL: case RADIAL:
return RadialTreeLayoutAlgorithm return RadialTreeLayoutAlgorithm
@ -103,7 +150,9 @@ class LayoutFunction
case TIDIER_TREE: case TIDIER_TREE:
default: default:
return TidierTreeLayoutAlgorithm return TidierTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder(); .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator);
} }
} }
} }

View file

@ -38,7 +38,7 @@ import ghidra.service.graph.AttributedVertex;
*/ */
class LayoutTransitionManager { class LayoutTransitionManager {
LayoutFunction layoutFunction = new LayoutFunction(); LayoutFunction layoutFunction;
/** /**
* the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm} * the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm}
*/ */
@ -49,24 +49,6 @@ class LayoutTransitionManager {
*/ */
Predicate<AttributedVertex> rootPredicate; Predicate<AttributedVertex> rootPredicate;
public static final List<String> EDGE_PRIORITY_LIST =
List.of(
"Fall-Through",
"Conditional-Return",
"Unconditional-Jump",
"Conditional-Jump",
"Unconditional-Call",
"Conditional-Call",
"Terminator",
"Computed",
"Indirection",
"Entry");
/**
* a {@link Comparator} to sort edges during layout graph traversal
* The default uses the {@code EDGE_PRIORTITY_LIST }
*/
Comparator<AttributedEdge> edgeComparator = new EdgeComparator(EDGE_PRIORITY_LIST);
/** /**
* a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices
*/ */
@ -81,47 +63,54 @@ class LayoutTransitionManager {
LayoutPaintable.RadialRings<AttributedVertex> radialLayoutRings; LayoutPaintable.RadialRings<AttributedVertex> radialLayoutRings;
/** /**
* Create an instance with passed parameters * Create an instance with passed parameters
* @param visualizationServer displays the graph * @param visualizationServer displays the graph
* @param rootPredicate selects root vertices * @param rootPredicate selects root vertices
* @param edgeTypePriorityList a {@code List} of EdgeType names in priority order
* @param favoredEdgePredicate q {@code Predicate} that will cause certain EdgeTypes to be favored during layout
*/ */
public LayoutTransitionManager( public LayoutTransitionManager(
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer, VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
Predicate<AttributedVertex> rootPredicate) { Predicate<AttributedVertex> rootPredicate,
List<String> edgeTypePriorityList,
Predicate<AttributedEdge> favoredEdgePredicate) {
this.visualizationServer = visualizationServer; this.visualizationServer = visualizationServer;
this.rootPredicate = rootPredicate; this.rootPredicate = rootPredicate;
this.renderContext = visualizationServer.getRenderContext(); this.renderContext = visualizationServer.getRenderContext();
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
} this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList),
favoredEdgePredicate);
public void setEdgeComparator(Comparator<AttributedEdge> edgeComparator) {
this.edgeComparator = edgeComparator;
} }
/** /**
* set the layout in order to configure the requested {@link LayoutAlgorithm} * set the layout in order to configure the requested {@link LayoutAlgorithm}
* @param layoutName the name of the layout algorithm to use * @param layoutName the name of the layout algorithm to use
*/ */
@SuppressWarnings("unchecked")
public void setLayout(String layoutName) { public void setLayout(String layoutName) {
LayoutAlgorithm.Builder<AttributedVertex, ?, ?> builder = layoutFunction.apply(layoutName); LayoutAlgorithm.Builder<AttributedVertex, ?, ?> builder = layoutFunction.apply(layoutName);
LayoutAlgorithm<AttributedVertex> layoutAlgorithm = builder.build(); LayoutAlgorithm<AttributedVertex> layoutAlgorithm = builder.build();
// layout algorithm considers the size of vertices
if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) { if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
((VertexBoundsFunctionConsumer<AttributedVertex>) layoutAlgorithm) ((VertexBoundsFunctionConsumer<AttributedVertex>) layoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction); .setVertexBoundsFunction(vertexBoundsFunction);
} }
// mincross layouts are 'layered'. put some bounds on the number of
// iterations of the level cross function based on the size of the graph
// very large graphs do not improve enough to out-weigh the cost of
// repeated iterations
if (layoutAlgorithm instanceof Layered) { if (layoutAlgorithm instanceof Layered) {
((Layered<AttributedVertex, AttributedEdge>) layoutAlgorithm) ((Layered<AttributedVertex, AttributedEdge>) layoutAlgorithm)
.setMaxLevelCrossFunction(g -> .setMaxLevelCrossFunction(g ->
Math.max(1, Math.min(10, 500 / g.vertexSet().size()))); Math.max(1, Math.min(10, 500 / g.vertexSet().size())));
} }
// tree layouts need a way to determine which vertices are roots
// especially when the graph is not a DAG
if (layoutAlgorithm instanceof TreeLayout) { if (layoutAlgorithm instanceof TreeLayout) {
((TreeLayout<AttributedVertex>) layoutAlgorithm).setRootPredicate(rootPredicate); ((TreeLayout<AttributedVertex>) layoutAlgorithm).setRootPredicate(rootPredicate);
} }
// remove any previously added layout paintables // remove any previously added layout paintables
// and apply paintables to these 2 algorithms
removePaintable(radialLayoutRings); removePaintable(radialLayoutRings);
removePaintable(balloonLayoutRings); removePaintable(balloonLayoutRings);
if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) { if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) {
@ -138,9 +127,7 @@ class LayoutTransitionManager {
visualizationServer.addPreRenderPaintable(radialLayoutRings); visualizationServer.addPreRenderPaintable(radialLayoutRings);
} }
if (layoutAlgorithm instanceof EdgeSorting) { // apply the layout algorithm
((EdgeSorting<AttributedEdge>) layoutAlgorithm).setEdgeComparator(edgeComparator);
}
LayoutAlgorithmTransition.apply(visualizationServer, LayoutAlgorithmTransition.apply(visualizationServer,
layoutAlgorithm); layoutAlgorithm);
} }
@ -151,7 +138,10 @@ class LayoutTransitionManager {
} }
} }
@SuppressWarnings("unchecked") /**
* Supplies the {@code LayoutAlgorithm} to be used for the initial @{code Graph} visualization
* @return
*/
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() { public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm = LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutFunction.apply(TIDIER_TREE).build(); layoutFunction.apply(TIDIER_TREE).build();
@ -159,12 +149,6 @@ class LayoutTransitionManager {
if (initialLayoutAlgorithm instanceof TreeLayout) { if (initialLayoutAlgorithm instanceof TreeLayout) {
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm) ((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setRootPredicate(rootPredicate); .setRootPredicate(rootPredicate);
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction);
}
if (initialLayoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<AttributedEdge>) initialLayoutAlgorithm)
.setEdgeComparator(edgeComparator);
} }
if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) { if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
((VertexBoundsFunctionConsumer<AttributedVertex>) initialLayoutAlgorithm) ((VertexBoundsFunctionConsumer<AttributedVertex>) initialLayoutAlgorithm)
@ -173,6 +157,10 @@ class LayoutTransitionManager {
return initialLayoutAlgorithm; return initialLayoutAlgorithm;
} }
/**
* Supplies a {@code String[]} array of the supported layout names
* @return
*/
public String[] getLayoutNames() { public String[] getLayoutNames() {
return layoutFunction.getNames(); return layoutFunction.getNames();
} }

View file

@ -29,12 +29,14 @@ import ghidra.service.graph.AttributedVertex;
public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> { public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> {
private DefaultGraphDisplay graphDisplay; private DefaultGraphDisplay graphDisplay;
private boolean allowEdgeSelection;
// TODO we should not need the graph display for any mouse plugins, but the API is net yet // TODO we should not need the graph display for any mouse plugins, but the API is net yet
// robust enough to communicate fully without it // robust enough to communicate fully without it
public JgtGraphMouse(DefaultGraphDisplay graphDisplay) { public JgtGraphMouse(DefaultGraphDisplay graphDisplay, boolean allowEdgeSelection) {
super(DefaultGraphMouse.builder()); super(DefaultGraphMouse.builder());
this.graphDisplay = graphDisplay; this.graphDisplay = graphDisplay;
this.allowEdgeSelection = allowEdgeSelection;
} }
@Override @Override
@ -54,13 +56,13 @@ public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, Attribute
// JUNGRAPHT CHANGE 1,2 // JUNGRAPHT CHANGE 1,2
// //
// Note: this code can go away when we can turn off the picking square // Note: this code can go away when we can turn off the picking square
add(new JgtSelectingGraphMousePlugin()); add(allowEdgeSelection ? new SelectingGraphMousePlugin() : new VertexSelectingGraphMousePlugin<>());
// add(new SelectingGraphMousePlugin<>()); // add(new SelectingGraphMousePlugin<>());
add(new RegionSelectingGraphMousePlugin<>()); add(new RegionSelectingGraphMousePlugin<>());
// the grab/pan feature // the grab/pan feature
add(new TranslatingGraphMousePlugin(InputEvent.BUTTON1_DOWN_MASK)); add(TranslatingGraphMousePlugin.builder().translatingMask(InputEvent.BUTTON1_DOWN_MASK).build());
// scaling // scaling
add(new ScalingGraphMousePlugin()); add(new ScalingGraphMousePlugin());

View file

@ -39,19 +39,21 @@ public class JgtSatelliteGraphMouse
// //
// JUNGRAPHT CHANGE 3 // JUNGRAPHT CHANGE 3
// //
// disable single selection in satellite view by setting masks to 0
SelectingGraphMousePlugin<AttributedVertex, AttributedEdge> mySelectingPlugin = SelectingGraphMousePlugin<AttributedVertex, AttributedEdge> mySelectingPlugin =
new JgtSelectingGraphMousePlugin(singleSelectionMask, addSingleSelectionMask); new JgtSelectingGraphMousePlugin(0, 0);
mySelectingPlugin.setLocked(true); mySelectingPlugin.setLocked(true);
selectingPlugin = mySelectingPlugin; selectingPlugin = mySelectingPlugin;
regionSelectingPlugin = regionSelectingPlugin =
RegionSelectingGraphMousePlugin.builder() RegionSelectingGraphMousePlugin.builder()
.regionSelectionMask(regionSelectionMask) .regionSelectionMask(regionSelectionMask)
.addRegionSelectionMask(addRegionSelectionMask) .toggleRegionSelectionMask(toggleRegionSelectionMask)
.regionSelectionCompleteMask(regionSelectionCompleteMask) .regionSelectionCompleteMask(regionSelectionCompleteMask)
.addRegionSelectionCompleteMask(addRegionSelectionCompleteMask) .toggleRegionSelectionCompleteMask(toggleRegionSelectionCompleteMask)
.build(); .build();
translatingPlugin = new SatelliteTranslatingGraphMousePlugin(translatingMask); translatingPlugin = SatelliteTranslatingGraphMousePlugin.builder()
.translatingMask(translatingMask).build();
add(selectingPlugin); add(selectingPlugin);
add(regionSelectingPlugin); add(regionSelectingPlugin);
add(translatingPlugin); add(translatingPlugin);

View file

@ -54,8 +54,10 @@ public class JgtSelectingGraphMousePlugin
this.pickFootprintPaintable = dummyPickFootprintPaintable; this.pickFootprintPaintable = dummyPickFootprintPaintable;
} }
public JgtSelectingGraphMousePlugin(int singleSelectionMask, int addSingleSelectionMask) { public JgtSelectingGraphMousePlugin(int singleSelectionMask, int toggleSingleSelectionMask) {
super(singleSelectionMask, addSingleSelectionMask); super(SelectingGraphMousePlugin.<AttributedVertex, AttributedEdge>builder()
.singleSelectionMask(singleSelectionMask)
.toggleSingleSelectionMask(toggleSingleSelectionMask));
// //
// JUNGRAPHT CHANGE 1 // JUNGRAPHT CHANGE 1
@ -89,7 +91,6 @@ public class JgtSelectingGraphMousePlugin
selectedVertexState.clear(); selectedVertexState.clear();
} }
selectedVertexState.select(vertex); selectedVertexState.select(vertex);
deselectedVertex = null;
} }
else { else {
// If this vertex is still around in mouseReleased, it will be deselected // If this vertex is still around in mouseReleased, it will be deselected
@ -99,9 +100,6 @@ public class JgtSelectingGraphMousePlugin
// //
// JUNGRAPHT CHANGE 2 HERE // JUNGRAPHT CHANGE 2 HERE
// //
if (addToSelection) {
deselectedVertex = vertex;
}
} }
e.consume(); e.consume();
return true; return true;

View file

@ -13,8 +13,8 @@ jungrapht.satelliteBackgroundTransparent=false
jungrapht.satelliteLensColor= 0xFAFAFA jungrapht.satelliteLensColor= 0xFAFAFA
jungrapht.pickedEdgeColor=0xFF0000 jungrapht.pickedEdgeColor=0xFF0000
jungrapht.edgeArrowLength=30 jungrapht.edgeArrowLength=50
jungrapht.edgeArrowWidth=20 jungrapht.edgeArrowWidth=40
# default spacing for tree layouts # default spacing for tree layouts
jungrapht.treeLayoutHorizontalSpacing=2 jungrapht.treeLayoutHorizontalSpacing=2
@ -34,7 +34,7 @@ jungrapht.lensStrokeWidth=10.0
# when scale is < .1, switch to lightweight rendering # when scale is < .1, switch to lightweight rendering
jungrapht.lightweightScaleThreshold=.1 jungrapht.lightweightScaleThreshold=.1
# under 50 vertices will use heavyweight rendering all the time # under 50 vertices will use heavyweight rendering all the time
jungrapht.lightweightCountThreshold=50 jungrapht.lightweightCountThreshold=80
# default pixels spacings for vertices # default pixels spacings for vertices
jungrapht.mincross.horizontalOffset=10 jungrapht.mincross.horizontalOffset=10
@ -69,12 +69,12 @@ jungrapht.edgeSpatialSupport=RTREE
# the mask for single vertex/edge selection # the mask for single vertex/edge selection
jungrapht.singleSelectionMask=MB1 jungrapht.singleSelectionMask=MB1
# the mask to augment the selection with a single vertex/edge # the mask to augment the selection with a single vertex/edge
jungrapht.addSingleSelectionMask=MB1_MENU jungrapht.toggleSingleSelectionMask=MB1_MENU
# the mask to select vertices within a region # the mask to select vertices within a region
jungrapht.regionSelectionMask=MB1_MENU jungrapht.regionSelectionMask=MB1_MENU
# the mask to augment the selection with vertices in a region # the mask to augment the selection with vertices in a region
jungrapht.addRegionSelectionMask=MB1_SHIFT_MENU jungrapht.toggleRegionSelectionMask=MB1_SHIFT_MENU
# the mask to indicate that the selection region is complete/closed and selection may commence # the mask to indicate that the selection region is complete/closed and selection may commence
jungrapht.regionSelectionCompleteMask=MENU jungrapht.regionSelectionCompleteMask=MENU
# the mask to indicate that the selection region for augmentation is complete/closed and selection may commence # the mask to indicate that the selection region for augmentation is complete/closed and selection may commence
jungrapht.addRegionSelectionCompleteMask=SHIFT_MENU jungrapht.toggleRegionSelectionCompleteMask=SHIFT_MENU

View file

@ -34,6 +34,52 @@ public interface GraphDisplay {
public static final int ALIGN_CENTER = 1; // aligns graph text to the center public static final int ALIGN_CENTER = 1; // aligns graph text to the center
public static final int ALIGN_RIGHT = 2; // aligns graph text to the right public static final int ALIGN_RIGHT = 2; // aligns graph text to the right
/**
* values are color names or rgb in hex '0xFF0000' is red
*/
String SELECTED_VERTEX_COLOR = "selectedVertexColor";
/**
* values are color names or rgb in hex '0xFF0000' is red
*/
String SELECTED_EDGE_COLOR = "selectedEdgeColor";
/**
* values are defined as String symbols in LayoutFunction class
*
* KAMADA_KAWAI,FRUCTERMAN_REINGOLD,CIRCLE_MINCROSS,TIDIER_TREE,TIDIER_RADIAL_TREE,
* MIN_CROSS_TOP_DOWN,MIN_CROSS_LONGEST_PATH,MIN_CROSS_NETWORK_SIMPLEX,MIN_CROSS_COFFMAN_GRAHAM,
* EXP_MIN_CROSS_TOP_DOWN,EXP_MIN_CROSS_LONGEST_PATH,EXP_MIN_CROSS_NETWORK_SIMPLEX,
* EXP_MIN_CROSS_COFFMAN_GRAHAM,TREE,RADIAL,BALLOON,GEM
*
* may have no meaning for a different graph visualization library
*/
String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm";
/**
* true or false
* may have no meaning for a different graph visualization library
*/
String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons";
/**
* values are the strings N,NE,E,SE,S,SW,W,NW,AUTO,CNTR
* may have no meaning for a different graph visualization library
*/
String VERTEX_LABEL_POSITION = "vertexLabelPosition";
/**
* true or false, whether edge selection via a mouse click is enabled.
* May not be supported by another graph visualization library
*/
String ENABLE_EDGE_SELECTION = "enableEdgeSelection";
/**
* a comma-separated list of edge type names in priority order
*/
String EDGE_TYPE_PRIORITY_LIST = "edgeTypePriorityList";
/**
* a comma-separated list of edge type names.
* any will be considered a favored edge for the min-cross layout
* algorithms.
* May have no meaning with a different graph visualization library
*/
String FAVORED_EDGES = "favoredEdges";
/** /**
* Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus
* or selects one or more nodes in a graph window * or selects one or more nodes in a graph window