GP-377 - Graphing - A few small refactorings

This commit is contained in:
dragonmacher 2021-02-02 15:41:48 -05:00
parent 9f2090d71c
commit 8493c333c8
10 changed files with 112 additions and 130 deletions

View file

@ -15,9 +15,7 @@
*/
package ghidra.app.services;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
@ -62,20 +60,26 @@ public interface GraphDisplayBroker {
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
public default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException {
return getDefaultGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
}
/**
* A convenience method for getting a {@link GraphDisplay} from the currently active provider
*
* <p>This method allows users to override default graph properties defined by
* <b>jungrapht</b>. See that library for a complete list of available properties.
* Default properties can be changed in the {@code jungrapht.properties} file.
*
* @param reuseGraph if true, the provider will attempt to re-use a current graph display
* @param properties a {@code Map} of property key/values that can be used to customize the display
* @param monitor the {@link TaskMonitor} that can be used to cancel the operation
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties, TaskMonitor monitor)
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor)
throws GraphException;
/**

View file

@ -122,7 +122,7 @@ public class ASTGraphTask extends Task {
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
String description =

View file

@ -90,7 +90,7 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
}
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
public void setVertexLabelAttribute(String attributeName, int alignment, int size, boolean monospace,
int maxLines) {
// no effect
}

View file

@ -45,6 +45,7 @@ import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.renderers.*;
import org.jungrapht.visualization.renderers.Renderer;
import org.jungrapht.visualization.renderers.Renderer.VertexLabel;
import org.jungrapht.visualization.selection.MutableSelectedState;
import org.jungrapht.visualization.selection.VertexEndpointsSelectedEdgeSelectedState;
import org.jungrapht.visualization.transform.*;
@ -64,8 +65,7 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.AttributeFilters;
import ghidra.graph.job.GraphJobRunner;
import ghidra.graph.viewer.popup.*;
import ghidra.graph.visualization.mouse.JgtPluggableGraphMouse;
import ghidra.graph.visualization.mouse.JgtUtils;
import ghidra.graph.visualization.mouse.*;
import ghidra.service.graph.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
@ -82,6 +82,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
private static final String FAVORED_EDGE = "Fall-Through";
/*
A handful of jungrapht properties that re used by this graph
*/
private static final String SELECTED_VERTEX_COLOR = "selectedVertexColor";
private static final String SELECTED_EDGE_COLOR = "selectedEdgeColor";
private static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm";
@ -94,7 +97,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName());
private Map<String, String> graphDisplayProperties = new HashMap<>();
private Map<String, String> displayProperties = new HashMap<>();
private GraphDisplayListener listener = new DummyGraphDisplayListener();
private String title;
@ -162,7 +165,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
/**
* Handles all mouse interaction
*/
private JgtPluggableGraphMouse graphMouse;
private JgtGraphMouse graphMouse;
private ToggleDockingAction hideSelectedAction;
private ToggleDockingAction hideUnselectedAction;
@ -177,13 +180,15 @@ public class DefaultGraphDisplay implements GraphDisplay {
/**
* Create the initial display, the graph-less visualization viewer, and its controls
* @param displayProvider provides a {@link PluginTool} for Docking features
* @param displayProperties graph properties that will override the default graph properties
* @param id the unique display id
*/
DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, Map<String, String> graphDisplayProperties, int id) {
DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider,
Map<String, String> displayProperties, int id) {
this.graphDisplayProvider = displayProvider;
this.displayId = id;
this.pluginTool = graphDisplayProvider.getPluginTool();
this.graphDisplayProperties = graphDisplayProperties;
this.displayProperties = displayProperties;
this.viewer = createViewer();
buildHighlighers();
@ -217,30 +222,15 @@ public class DefaultGraphDisplay implements GraphDisplay {
connectSelectionStateListeners();
}
@Override
public void setProperty(String key, String value) {
this.graphDisplayProperties.put(key, value);
}
@Override
public String getValue(String key) {
return graphDisplayProperties.get(key);
}
@Override
public Map<String, String> getProperties() {
return Collections.unmodifiableMap(graphDisplayProperties);
}
private Color getSelectedVertexColor() {
return Colors.getHexColor(graphDisplayProperties.getOrDefault(SELECTED_VERTEX_COLOR,
return Colors.getHexColor(displayProperties.getOrDefault(SELECTED_VERTEX_COLOR,
"0xFF0000"));
}
private Color getSelectedEdgeColor() {
return Colors.getHexColor(graphDisplayProperties.getOrDefault(SELECTED_EDGE_COLOR, "0xFF0000"));
}
// private Color getSelectedEdgeColor() {
// return Colors
// .getHexColor(displayProperties.getOrDefault(SELECTED_EDGE_COLOR, "0xFF0000"));
// }
JComponent getComponent() {
JComponent component = viewer.getComponent();
@ -270,7 +260,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
// this lens' delegate is the viewer's VIEW layer
.delegate(transformer)
.build();
LensGraphMouse lensGraphMouse = DefaultLensGraphMouse.builder().magnificationPlugin(magnificationPlugin).build();
LensGraphMouse lensGraphMouse =
DefaultLensGraphMouse.builder().magnificationPlugin(magnificationPlugin).build();
return MagnifyImageLensSupport.builder(viewer)
.lensTransformer(shapeTransformer)
.lensGraphMouse(lensGraphMouse)
@ -471,14 +462,15 @@ public class DefaultGraphDisplay implements GraphDisplay {
new ActionBuilder("Grow Selection To Entire Component", ACTION_OWNER)
.popupMenuPath("Grow Selection To Entire Component")
.popupMenuGroup("z", "4")
.description("Extends the current selection by including the target/source vertices " +
.description(
"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()) && !isAllSelected(getTargetVerticesFromSelected()))
.enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) &&
!isAllSelected(getTargetVerticesFromSelected()))
.onAction(c -> growSelection(getAllComponentVerticesFromSelected()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Clear Selection", ACTION_OWNER)
.popupMenuPath("Clear Selection")
.popupMenuGroup("z", "5")
@ -615,9 +607,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private Set<AttributedVertex> getUnselectedSourceVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState = viewer.getSelectedVertexState();
MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState();
return getSourceVerticesFromSelected().stream()
.filter(v -> !selectedVertexState.isSelected(v)).collect(Collectors.toSet());
.filter(v -> !selectedVertexState.isSelected(v))
.collect(Collectors.toSet());
}
private Set<AttributedVertex> getTargetVerticesFromSelected() {
@ -631,13 +625,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private Set<AttributedVertex> getUnselectedTargetVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState = viewer.getSelectedVertexState();
MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState();
return getTargetVerticesFromSelected().stream()
.filter(v -> !selectedVertexState.isSelected(v)).collect(Collectors.toSet());
.filter(v -> !selectedVertexState.isSelected(v))
.collect(Collectors.toSet());
}
private Set<AttributedVertex> getAllDownstreamVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState = viewer.getSelectedVertexState();
Set<AttributedVertex> downstream = new HashSet<>();
Set<AttributedVertex> targets = getUnselectedTargetVerticesFromSelected();
while (!targets.isEmpty()) {
@ -649,7 +644,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private Set<AttributedVertex> getAllUpstreamVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState = viewer.getSelectedVertexState();
Set<AttributedVertex> upstream = new HashSet<>();
Set<AttributedVertex> sources = getUnselectedSourceVerticesFromSelected();
while (!sources.isEmpty()) {
@ -666,7 +660,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
return componentVertices;
}
private void invertSelection() {
switchableSelectionListener.setEnabled(false);
try {
@ -756,7 +749,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
SatelliteVisualizationViewer.builder(parentViewer)
.viewSize(satelliteSize)
.build();
satellite.setGraphMouse(new DefaultSatelliteGraphMouse<>());
//
// JUNGRAPHT CHANGE 3
//
satellite.setGraphMouse(new JgtSatelliteGraphMouse());
satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor);
satellite.getRenderContext()
.setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke);
@ -771,9 +769,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
satellite.getRenderContext().setVertexLabelFunction(n -> null);
// always get the current predicate from the main view and test with it,
satellite.getRenderContext()
.setVertexIncludePredicate(v -> viewer.getRenderContext().getVertexIncludePredicate().test(v));
.setVertexIncludePredicate(
v -> viewer.getRenderContext().getVertexIncludePredicate().test(v));
satellite.getRenderContext()
.setEdgeIncludePredicate(e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e));
.setEdgeIncludePredicate(
e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e));
satellite.getComponent().setBorder(BorderFactory.createEtchedBorder());
parentViewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override
@ -943,10 +943,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private void setInitialLayoutAlgorithm() {
if (graphDisplayProperties.containsKey(INITIAL_LAYOUT_ALGORITHM)) {
String layoutAlgorithmName = graphDisplayProperties.get(INITIAL_LAYOUT_ALGORITHM);
if (displayProperties.containsKey(INITIAL_LAYOUT_ALGORITHM)) {
String layoutAlgorithmName = displayProperties.get(INITIAL_LAYOUT_ALGORITHM);
layoutTransitionManager.setLayout(layoutAlgorithmName);
} else {
}
else {
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutTransitionManager.getInitialLayoutAlgorithm();
initialLayoutAlgorithm.setAfter(() -> centerAndScale());
@ -1040,15 +1041,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
log.fine("defineEdgeAttribute " + attributeName + " is not implemented");
}
/*
* @see ghidra.program.model.graph.GraphDisplay#setVertexLabel(java.lang.String, int, int, boolean, int)
*/
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
boolean monospace,
int maxLines) {
log.fine("setVertexLabel " + attributeName);
this.iconCache.setPreferredLabel(attributeName);
// this would have to set the label function, the label font function
this.iconCache.setPreferredVertexLabelAttribute(attributeName);
}
/**
@ -1184,15 +1182,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
/**
* create and return a {@link VisualizationViewer} to display graphs
* Create and return a {@link VisualizationViewer} to display graphs
* @return the new VisualizationViewer
*/
public VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
Color selectedVertexColor =
Colors.getHexColor(graphDisplayProperties.getOrDefault("selectedVertexColor", "0xFF0000"));
Color selectedEdgeColor =
Colors.getHexColor(graphDisplayProperties.getOrDefault("selectedEdgeColor", "0xFF0000"));
final VisualizationViewer<AttributedVertex, AttributedEdge> vv =
protected VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
VisualizationViewer<AttributedVertex, AttributedEdge> vv =
VisualizationViewer.<AttributedVertex, AttributedEdge> builder()
.multiSelectionStrategySupplier(
() -> freeFormSelection ? MultiSelectionStrategy.arbitrary()
@ -1247,7 +1241,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
renderContext.setEdgeStrokeFunction(
e -> vv.getSelectedEdges().contains(e) ? new BasicStroke(20.f)
: ProgramGraphFunctions.getEdgeStroke(e));
// selected edges will be drawn in red (instead of default)
Color selectedEdgeColor =
Colors.getHexColor(
displayProperties.getOrDefault("selectedEdgeColor", "0xFF0000"));
renderContext.setEdgeDrawPaintFunction(
e -> vv.getSelectedEdges().contains(e) ? selectedEdgeColor
: Colors.getColor(e));
@ -1286,7 +1284,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
vv.getComponent().removeMouseListener(mouseListener);
}
graphMouse = new JgtPluggableGraphMouse(this);
graphMouse = new JgtGraphMouse(this);
vv.setGraphMouse(graphMouse);
return vv;
@ -1294,7 +1292,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
private void setVertexPreferences(VisualizationViewer<AttributedVertex, AttributedEdge> vv) {
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
if (Boolean.parseBoolean(graphDisplayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, "true"))) {
String useIcons =
displayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, Boolean.TRUE.toString());
if (Boolean.parseBoolean(useIcons)) {
// set up the shape and color functions
IconShapeFunction<AttributedVertex> nodeImageShapeFunction =
new IconShapeFunction<>(new EllipseShapeFunction<>());
@ -1305,9 +1305,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
vv.setInitialDimensionFunction(InitialDimensionFunction
.builder(
nodeImageShapeFunction.andThen(s -> RectangleUtils.convert(s.getBounds2D())))
nodeImageShapeFunction
.andThen(s -> RectangleUtils.convert(s.getBounds2D())))
.build());
} else {
}
else {
vv.getRenderContext().setVertexShapeFunction(ProgramGraphFunctions::getVertexShape);
vv.setInitialDimensionFunction(InitialDimensionFunction
.builder(
@ -1315,9 +1317,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
.andThen(s -> RectangleUtils.convert(s.getBounds2D())))
.build());
vv.getRenderContext().setVertexLabelFunction(Object::toString);
vv.getRenderContext().setVertexLabelPosition(
vv.getRenderContext()
.setVertexLabelPosition(
VertexLabel.Position.valueOf(
graphDisplayProperties.getOrDefault(VERTEX_LABEL_POSITION, "AUTO")));
displayProperties.getOrDefault(VERTEX_LABEL_POSITION, "AUTO")));
}
}

View file

@ -27,7 +27,6 @@ import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import ghidra.service.graph.AttributedVertex;
import ghidra.service.graph.GraphDisplay;
public class GhidraIconCache {
@ -43,8 +42,7 @@ public class GhidraIconCache {
private final Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
private final IconShape.Function iconShapeFunction = new IconShape.Function();
private String preferredLabel = null;
private int labelAlignment = GraphDisplay.ALIGN_CENTER;
private String preferredVeretxLabelAttribute = null;
Icon get(AttributedVertex vertex) {
@ -65,7 +63,8 @@ public class GhidraIconCache {
}
private Icon createIcon(AttributedVertex vertex) {
rendererLabel.setText(ProgramGraphFunctions.getLabel(vertex, preferredLabel));
rendererLabel
.setText(ProgramGraphFunctions.getLabel(vertex, preferredVeretxLabelAttribute));
rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE));
rendererLabel.setForeground(Color.black);
rendererLabel.setBackground(Color.white);
@ -103,15 +102,19 @@ public class GhidraIconCache {
// triangles have a non-zero +/- yoffset instead of centering the label
case TRIANGLE:
// scale the vertex shape
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION;
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
LABEL_TO_ICON_PROPORTION;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
break;
case INVERTED_TRIANGLE:
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION;
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
LABEL_TO_ICON_PROPORTION;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
@ -201,7 +204,7 @@ public class GhidraIconCache {
* Sets the vertex label to the value of the passed attribute name
* @param attributeName the attribute key for the vertex label value to be displayed
*/
public void setPreferredLabel(String attributeName) {
this.preferredLabel = attributeName;
public void setPreferredVertexLabelAttribute(String attributeName) {
this.preferredVeretxLabelAttribute = attributeName;
}
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.graph.visualization;
import static org.jungrapht.visualization.layout.util.PropertyLoader.*;
import java.awt.*;
import java.util.Map;
@ -26,8 +28,6 @@ import com.google.common.base.Splitter;
import ghidra.service.graph.Attributed;
import ghidra.service.graph.AttributedEdge;
import static org.jungrapht.visualization.layout.util.PropertyLoader.PREFIX;
/**
* a container for various functions used by ProgramGraph
*/
@ -125,14 +125,14 @@ abstract class ProgramGraphFunctions {
/**
* gets a display label from an {@link Attributed} object (vertex)
* @param attributed the attributed object to get a label for
* @param preferredLabelKey the attribute to use for the label, if available
* @param preferredLabelAttribute the attribute to use for the label, if available
* @return the label for the given {@link Attributed}
*/
public static String getLabel(Attributed attributed, String preferredLabelKey) {
public static String getLabel(Attributed attributed, String preferredLabelAttribute) {
Map<String, String> map = attributed.getAttributeMap();
String name = StringEscapeUtils.escapeHtml4(map.get("Name"));
if (map.containsKey(preferredLabelKey)) {
name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelKey));
if (map.containsKey(preferredLabelAttribute)) {
name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute));
}
return "<html>" + String.join("<p>", Splitter.on('\n').split(name));
}

View file

@ -150,7 +150,7 @@ public class BlockGraphTask extends Task {
if (showCode) {
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
codeLimitPerBlock + 1);
}
display.setGraph(graph, graphTitle, appendGraph, monitor);

View file

@ -74,7 +74,7 @@ public class TestGraphDisplay implements GraphDisplay {
}
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
public void setVertexLabelAttribute(String attributeName, int alignment, int size, boolean monospace,
int maxLines) {
// nothing
}

View file

@ -15,8 +15,6 @@
*/
package ghidra.service.graph;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import docking.action.DockingAction;
@ -115,7 +113,7 @@ public interface GraphDisplay {
* @param monospace true if the font should be monospaced
* @param maxLines the maximum number lines to display in the vertex labels
*/
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
public void setVertexLabelAttribute(String attributeName, int alignment, int size, boolean monospace,
int maxLines);
/**
@ -154,30 +152,4 @@ public interface GraphDisplay {
* @param action the action to add.
*/
public void addAction(DockingAction action);
/**
* set a property key/value pair. This may be used to pass preferences to the implementation
* @param key the property key
* @param value the propery value
*/
default void setProperty(String key, String value) {
}
/**
*
* @param key the key to fetch a value for
* @return the value associated with the passed key
*/
default String getValue(String key) {
return null;
}
/**
* Returns the property {@code Map} Should be implemented to pass an unmodifiable or copy
* @return the complete {@code Map} of properties.
*/
default Map<String, String> getProperties() {
return Collections.emptyMap();
}
}