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