From 76b66e2a53c6c2e766acea7fe55d00ca8d34d5d3 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 3 Mar 2021 09:22:36 -0500 Subject: [PATCH] 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 --- .../Decompiler/ghidra_scripts/GraphAST.java | 12 ++- .../core/decompile/actions/ASTGraphTask.java | 9 +- Ghidra/Features/GraphServices/Module.manifest | 4 +- Ghidra/Features/GraphServices/build.gradle | 6 +- .../visualization/DefaultGraphDisplay.java | 96 ++++++++++++++----- .../graph/visualization/LayoutFunction.java | 61 ++++++++++-- .../LayoutTransitionManager.java | 62 +++++------- .../visualization/mouse/JgtGraphMouse.java | 8 +- .../mouse/JgtSatelliteGraphMouse.java | 10 +- .../mouse/JgtSelectingGraphMousePlugin.java | 10 +- .../src/main/resources/jungrapht.properties | 12 +-- .../ghidra/service/graph/GraphDisplay.java | 46 +++++++++ 12 files changed, 235 insertions(+), 101 deletions(-) diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java index ed2f6f1df1..ddad023997 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java @@ -31,6 +31,7 @@ import ghidra.program.model.pcode.*; import ghidra.service.graph.*; import ghidra.util.Msg; import java.util.*; +import static ghidra.service.graph.GraphDisplay.*; public class GraphAST extends GhidraScript { protected static final String COLOR_ATTRIBUTE = "Color"; @@ -66,11 +67,12 @@ public class GraphAST extends GhidraScript { buildGraph(); Map properties = new HashMap<>(); - properties.put("selectedVertexColor", "0xFF1493"); - properties.put("selectedEdgeColor", "0xFF1493"); - properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); - properties.put("displayVerticesAsIcons", "false"); - properties.put("vertexLabelPosition", "S"); + properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); + properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); + properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham"); + properties.put(DISPLAY_VERTICES_AS_ICONS, "false"); + properties.put(VERTEX_LABEL_POSITION, "S"); + properties.put(ENABLE_EDGE_SELECTION, "true"); GraphDisplay graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor); // graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); // diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java index ebe02584e0..0a6a4e7db3 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java @@ -34,6 +34,8 @@ import ghidra.util.exception.GraphException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; +import static ghidra.service.graph.GraphDisplay.*; + public class ASTGraphTask extends Task { enum GraphType { CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow"); @@ -104,9 +106,10 @@ public class ASTGraphTask extends Task { createControlFlowGraph(graph, monitor); } Map properties = new HashMap<>(); - properties.put("selectedVertexColor", "0xFF1493"); - properties.put("selectedEdgeColor", "0xFF1493"); - properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); + properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); + properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); + properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham"); + properties.put(ENABLE_EDGE_SELECTION, "true"); GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor); ASTGraphDisplayListener displayListener = new ASTGraphDisplayListener(tool, display, hfunction, graphType); diff --git a/Ghidra/Features/GraphServices/Module.manifest b/Ghidra/Features/GraphServices/Module.manifest index 361687fc49..ad8b9ffc53 100644 --- a/Ghidra/Features/GraphServices/Module.manifest +++ b/Ghidra/Features/GraphServices/Module.manifest @@ -1,7 +1,7 @@ EXCLUDE FROM GHIDRA JAR: true -MODULE FILE LICENSE: lib/jungrapht-visualization-1.1.jar BSD -MODULE FILE LICENSE: lib/jungrapht-layout-1.1.jar BSD +MODULE FILE LICENSE: lib/jungrapht-visualization-1.2.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-io-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jheaps-0.13.jar Apache License 2.0 diff --git a/Ghidra/Features/GraphServices/build.gradle b/Ghidra/Features/GraphServices/build.gradle index 9d3c632bf9..2c53c410ca 100644 --- a/Ghidra/Features/GraphServices/build.gradle +++ b/Ghidra/Features/GraphServices/build.gradle @@ -12,9 +12,9 @@ dependencies { compile project(":Base") // 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-layout: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.2") { exclude group: "org.slf4j", module: "slf4j-api" } + compile "org.jgrapht:jgrapht-core:1.5.0" // not using jgrapht-io code that depends on antlr, so exclude antlr diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index 41a21ce1e2..4bf2416581 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -103,28 +103,33 @@ public class DefaultGraphDisplay implements GraphDisplay { 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 Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName()); - private Map displayProperties = new HashMap<>(); + private Map displayProperties; private Set addedActions = new LinkedHashSet<>(); private GraphDisplayListener listener = new DummyGraphDisplayListener(); private String title; 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} */ @@ -219,7 +224,9 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.getComponent().add(satelliteViewer.getComponent()); } layoutTransitionManager = - new LayoutTransitionManager(viewer, this::isRoot); + new LayoutTransitionManager(viewer, this::isRoot, + getEdgeTypePriorityList(), + getFavoredEdgePredicate()); viewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -252,6 +259,19 @@ public class DefaultGraphDisplay implements GraphDisplay { return Colors.getHexColor(property); } + private List getEdgeTypePriorityList() { + return Arrays.asList(displayProperties + .getOrDefault(EDGE_TYPE_PRIORITY_LIST, DEFAULT_EDGE_TYPE_PRIORITY_LIST) + .split(",")); + } + + private Predicate 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 component = viewer.getComponent(); component.setFocusable(true); @@ -486,7 +506,7 @@ public class DefaultGraphDisplay implements GraphDisplay { "Extends the current selection by including the target/source vertices " + "of all edges whose source/target is selected") .keyBinding("ctrl C") - .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) && + .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) || !isAllSelected(getTargetVerticesFromSelected())) .onAction(c -> growSelection(getAllComponentVerticesFromSelected())) .buildAndInstallLocal(componentProvider); @@ -610,6 +630,23 @@ public class DefaultGraphDisplay implements GraphDisplay { private void growSelection(Set vertices) { viewer.getSelectedVertexState().select(vertices); + selectEdgesConnecting(vertices); + } + + // select all the edges that connect the supplied vertices + private void selectEdgesConnecting(Collection 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 vertices) { @@ -617,8 +654,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private Set getSourceVerticesFromSelected() { - Set sources = new HashSet<>(); Set selectedVertices = getSelectedVertices(); + Set sources = new HashSet<>(selectedVertices); for (AttributedVertex v : selectedVertices) { Set edges = graph.incomingEdgesOf(v); edges.forEach(e -> sources.add(graph.getEdgeSource(e))); @@ -635,8 +672,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private Set getTargetVerticesFromSelected() { - Set targets = new HashSet<>(); Set selectedVertices = getSelectedVertices(); + Set targets = new HashSet<>(selectedVertices); for (AttributedVertex v : selectedVertices) { Set edges = graph.outgoingEdgesOf(v); edges.forEach(e -> targets.add(graph.getEdgeTarget(e))); @@ -674,9 +711,20 @@ public class DefaultGraphDisplay implements GraphDisplay { 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 getAllComponentVerticesFromSelected() { - Set componentVertices = getAllDownstreamVerticesFromSelected(); - componentVertices.addAll(getAllUpstreamVerticesFromSelected()); + Set componentVertices = new HashSet<>(viewer.getSelectedVertices()); + Set downstream = getAllDownstreamVerticesFromSelected(); + Set upstream = getAllUpstreamVerticesFromSelected(); + while (!downstream.isEmpty() || !upstream.isEmpty()) { + componentVertices.addAll(downstream); + componentVertices.addAll(upstream); + downstream = getAllDownstreamVerticesFromSelected(); + upstream = getAllUpstreamVerticesFromSelected(); + } 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 - * 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 * @return true if the vertex is a root */ private boolean isRoot(AttributedVertex vertex) { Set 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); - // 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 renderContext.setEdgeStrokeFunction( 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 // of using default shapes. - if (vertexRenderer instanceof LightweightVertexRenderer) { Function vertexShapeFunction = renderContext.getVertexShapeFunction(); @@ -1301,7 +1343,9 @@ public class DefaultGraphDisplay implements GraphDisplay { vv.getComponent().removeMouseListener(mouseListener); } - graphMouse = new JgtGraphMouse(this); + graphMouse = new JgtGraphMouse(this, + Boolean.parseBoolean(displayProperties.getOrDefault(ENABLE_EDGE_SELECTION, + "false"))); vv.setGraphMouse(graphMouse); return vv; diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java index c248961f26..00690e8436 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java @@ -15,7 +15,9 @@ */ package ghidra.graph.visualization; +import java.util.Comparator; import java.util.function.Function; +import java.util.function.Predicate; import org.jungrapht.visualization.layout.algorithms.*; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; @@ -36,22 +38,38 @@ class LayoutFunction static final String KAMADA_KAWAI = "Force Balanced"; 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_RADIAL_TREE = "Compact Radial"; 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_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex"; 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 RADIAL = "Radial"; static final String BALLOON = "Balloon"; static final String GEM = "Gem (Graph Embedder)"; + Predicate favoredEdgePredicate; + Comparator edgeTypeComparator; + + LayoutFunction(Comparator edgeTypeComparator, Predicate favoredEdgePredicate) { + this.edgeTypeComparator = edgeTypeComparator; + this.favoredEdgePredicate = favoredEdgePredicate; + } + public String[] getNames() { return new String[] { TIDIER_TREE, TREE, 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 }; } @@ -67,27 +85,56 @@ class LayoutFunction case FRUCTERMAN_REINGOLD: return FRLayoutAlgorithm. builder() .repulsionContractBuilder(BarnesHutFRRepulsion.builder()); - case CIRCLE_MINCROSS: + case CIRCLE: return CircleLayoutAlgorithm. builder() - .reduceEdgeCrossing(true); + .reduceEdgeCrossing(false); case TIDIER_RADIAL_TREE: return TidierRadialTreeLayoutAlgorithm - . edgeAwareBuilder(); + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator); case MIN_CROSS_TOP_DOWN: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.TOP_DOWN); case MIN_CROSS_LONGEST_PATH: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.LONGEST_PATH); case MIN_CROSS_NETWORK_SIMPLEX: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.NETWORK_SIMPLEX); case MIN_CROSS_COFFMAN_GRAHAM: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.COFFMAN_GRAHAM); + case EXP_MIN_CROSS_TOP_DOWN: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.TOP_DOWN); + case EXP_MIN_CROSS_LONGEST_PATH: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.LONGEST_PATH); + case EXP_MIN_CROSS_NETWORK_SIMPLEX: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.NETWORK_SIMPLEX); + case EXP_MIN_CROSS_COFFMAN_GRAHAM: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) .layering(Layering.COFFMAN_GRAHAM); case RADIAL: return RadialTreeLayoutAlgorithm @@ -103,7 +150,9 @@ class LayoutFunction case TIDIER_TREE: default: return TidierTreeLayoutAlgorithm - . edgeAwareBuilder(); + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator); + } } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java index c500ba8f51..6849d81829 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java @@ -38,7 +38,7 @@ import ghidra.service.graph.AttributedVertex; */ class LayoutTransitionManager { - LayoutFunction layoutFunction = new LayoutFunction(); + LayoutFunction layoutFunction; /** * the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm} */ @@ -49,24 +49,6 @@ class LayoutTransitionManager { */ Predicate rootPredicate; - public static final List 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 edgeComparator = new EdgeComparator(EDGE_PRIORITY_LIST); - /** * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices */ @@ -81,47 +63,54 @@ class LayoutTransitionManager { LayoutPaintable.RadialRings radialLayoutRings; - /** * Create an instance with passed parameters * @param visualizationServer displays the graph * @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( VisualizationServer visualizationServer, - Predicate rootPredicate) { + Predicate rootPredicate, + List edgeTypePriorityList, + Predicate favoredEdgePredicate) { this.visualizationServer = visualizationServer; this.rootPredicate = rootPredicate; - this.renderContext = visualizationServer.getRenderContext(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); - } - - public void setEdgeComparator(Comparator edgeComparator) { - this.edgeComparator = edgeComparator; + this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList), + favoredEdgePredicate); } /** * set the layout in order to configure the requested {@link LayoutAlgorithm} * @param layoutName the name of the layout algorithm to use */ - @SuppressWarnings("unchecked") public void setLayout(String layoutName) { LayoutAlgorithm.Builder builder = layoutFunction.apply(layoutName); LayoutAlgorithm layoutAlgorithm = builder.build(); + // layout algorithm considers the size of vertices if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) { ((VertexBoundsFunctionConsumer) layoutAlgorithm) .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) { ((Layered) layoutAlgorithm) .setMaxLevelCrossFunction(g -> 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) { ((TreeLayout) layoutAlgorithm).setRootPredicate(rootPredicate); } // remove any previously added layout paintables + // and apply paintables to these 2 algorithms removePaintable(radialLayoutRings); removePaintable(balloonLayoutRings); if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) { @@ -138,9 +127,7 @@ class LayoutTransitionManager { visualizationServer.addPreRenderPaintable(radialLayoutRings); } - if (layoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) layoutAlgorithm).setEdgeComparator(edgeComparator); - } + // apply the layout algorithm LayoutAlgorithmTransition.apply(visualizationServer, 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 getInitialLayoutAlgorithm() { LayoutAlgorithm initialLayoutAlgorithm = layoutFunction.apply(TIDIER_TREE).build(); @@ -159,12 +149,6 @@ class LayoutTransitionManager { if (initialLayoutAlgorithm instanceof TreeLayout) { ((TreeLayout) initialLayoutAlgorithm) .setRootPredicate(rootPredicate); - ((TreeLayout) initialLayoutAlgorithm) - .setVertexBoundsFunction(vertexBoundsFunction); - } - if (initialLayoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) initialLayoutAlgorithm) - .setEdgeComparator(edgeComparator); } if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) { ((VertexBoundsFunctionConsumer) initialLayoutAlgorithm) @@ -173,6 +157,10 @@ class LayoutTransitionManager { return initialLayoutAlgorithm; } + /** + * Supplies a {@code String[]} array of the supported layout names + * @return + */ public String[] getLayoutNames() { return layoutFunction.getNames(); } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java index b198d513b1..fc77954316 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java @@ -29,12 +29,14 @@ import ghidra.service.graph.AttributedVertex; public class JgtGraphMouse extends DefaultGraphMouse { private DefaultGraphDisplay graphDisplay; + private boolean allowEdgeSelection; // 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 - public JgtGraphMouse(DefaultGraphDisplay graphDisplay) { + public JgtGraphMouse(DefaultGraphDisplay graphDisplay, boolean allowEdgeSelection) { super(DefaultGraphMouse.builder()); this.graphDisplay = graphDisplay; + this.allowEdgeSelection = allowEdgeSelection; } @Override @@ -54,13 +56,13 @@ public class JgtGraphMouse extends DefaultGraphMouse()); // add(new SelectingGraphMousePlugin<>()); add(new RegionSelectingGraphMousePlugin<>()); // the grab/pan feature - add(new TranslatingGraphMousePlugin(InputEvent.BUTTON1_DOWN_MASK)); + add(TranslatingGraphMousePlugin.builder().translatingMask(InputEvent.BUTTON1_DOWN_MASK).build()); // scaling add(new ScalingGraphMousePlugin()); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java index da8f9dab09..668777ea9d 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java @@ -39,19 +39,21 @@ public class JgtSatelliteGraphMouse // // JUNGRAPHT CHANGE 3 // + // disable single selection in satellite view by setting masks to 0 SelectingGraphMousePlugin mySelectingPlugin = - new JgtSelectingGraphMousePlugin(singleSelectionMask, addSingleSelectionMask); + new JgtSelectingGraphMousePlugin(0, 0); mySelectingPlugin.setLocked(true); selectingPlugin = mySelectingPlugin; regionSelectingPlugin = RegionSelectingGraphMousePlugin.builder() .regionSelectionMask(regionSelectionMask) - .addRegionSelectionMask(addRegionSelectionMask) + .toggleRegionSelectionMask(toggleRegionSelectionMask) .regionSelectionCompleteMask(regionSelectionCompleteMask) - .addRegionSelectionCompleteMask(addRegionSelectionCompleteMask) + .toggleRegionSelectionCompleteMask(toggleRegionSelectionCompleteMask) .build(); - translatingPlugin = new SatelliteTranslatingGraphMousePlugin(translatingMask); + translatingPlugin = SatelliteTranslatingGraphMousePlugin.builder() + .translatingMask(translatingMask).build(); add(selectingPlugin); add(regionSelectingPlugin); add(translatingPlugin); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java index e2f15d4917..680a2c0199 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java @@ -54,8 +54,10 @@ public class JgtSelectingGraphMousePlugin this.pickFootprintPaintable = dummyPickFootprintPaintable; } - public JgtSelectingGraphMousePlugin(int singleSelectionMask, int addSingleSelectionMask) { - super(singleSelectionMask, addSingleSelectionMask); + public JgtSelectingGraphMousePlugin(int singleSelectionMask, int toggleSingleSelectionMask) { + super(SelectingGraphMousePlugin.builder() + .singleSelectionMask(singleSelectionMask) + .toggleSingleSelectionMask(toggleSingleSelectionMask)); // // JUNGRAPHT CHANGE 1 @@ -89,7 +91,6 @@ public class JgtSelectingGraphMousePlugin selectedVertexState.clear(); } selectedVertexState.select(vertex); - deselectedVertex = null; } else { // If this vertex is still around in mouseReleased, it will be deselected @@ -99,9 +100,6 @@ public class JgtSelectingGraphMousePlugin // // JUNGRAPHT CHANGE 2 HERE // - if (addToSelection) { - deselectedVertex = vertex; - } } e.consume(); return true; diff --git a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties index 707a0815ae..1adeb4b1d0 100644 --- a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties +++ b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties @@ -13,8 +13,8 @@ jungrapht.satelliteBackgroundTransparent=false jungrapht.satelliteLensColor= 0xFAFAFA jungrapht.pickedEdgeColor=0xFF0000 -jungrapht.edgeArrowLength=30 -jungrapht.edgeArrowWidth=20 +jungrapht.edgeArrowLength=50 +jungrapht.edgeArrowWidth=40 # default spacing for tree layouts jungrapht.treeLayoutHorizontalSpacing=2 @@ -34,7 +34,7 @@ jungrapht.lensStrokeWidth=10.0 # when scale is < .1, switch to lightweight rendering jungrapht.lightweightScaleThreshold=.1 # under 50 vertices will use heavyweight rendering all the time -jungrapht.lightweightCountThreshold=50 +jungrapht.lightweightCountThreshold=80 # default pixels spacings for vertices jungrapht.mincross.horizontalOffset=10 @@ -69,12 +69,12 @@ jungrapht.edgeSpatialSupport=RTREE # the mask for single vertex/edge selection jungrapht.singleSelectionMask=MB1 # 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 jungrapht.regionSelectionMask=MB1_MENU # 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 jungrapht.regionSelectionCompleteMask=MENU # 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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java index 696671614e..0f1358f53f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java @@ -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_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 * or selects one or more nodes in a graph window