Miscellanious bug fixes and code clean up.

This commit is contained in:
ghidravore 2020-09-30 17:31:17 -04:00
parent 608e74f873
commit b647c6cd5b
13 changed files with 405 additions and 346 deletions

View file

@ -19,6 +19,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import docking.widgets.EventTrigger;
import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.app.events.*; import ghidra.app.events.*;
@ -65,7 +66,7 @@ public abstract class AddressBasedGraphDisplayListener
} }
@Override @Override
public void locationChanged(String vertexId) { public void locationFocusChanged(String vertexId) {
Address address = getAddressForVertexId(vertexId); Address address = getAddressForVertexId(vertexId);
if (address != null) { if (address != null) {
ProgramLocation location = new ProgramLocation(program, address); ProgramLocation location = new ProgramLocation(program, address);
@ -101,7 +102,9 @@ public abstract class AddressBasedGraphDisplayListener
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event; ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
if (isMyProgram(ev.getProgram())) { if (isMyProgram(ev.getProgram())) {
ProgramLocation location = ev.getLocation(); ProgramLocation location = ev.getLocation();
graphDisplay.setLocation(getVertexIdForAddress(location.getAddress())); String id = getVertexIdForAddress(location.getAddress());
// update graph location, but tell it not to send out event
graphDisplay.setLocationFocus(id, EventTrigger.INTERNAL_ONLY);
} }
} }
else if (event instanceof ProgramSelectionPluginEvent) { else if (event instanceof ProgramSelectionPluginEvent) {
@ -110,7 +113,8 @@ public abstract class AddressBasedGraphDisplayListener
ProgramSelection selection = ev.getSelection(); ProgramSelection selection = ev.getSelection();
List<String> selectedVertices = getVertices(selection); List<String> selectedVertices = getVertices(selection);
if (selectedVertices != null) { if (selectedVertices != null) {
graphDisplay.selectVertices(selectedVertices); // since we are responding to an event, tell the GraphDisplay not to send event
graphDisplay.selectVertices(selectedVertices, EventTrigger.INTERNAL_ONLY);
} }
} }
} }

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*; import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType; import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType;
@ -26,6 +27,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.pcode.HighFunction; import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeBlockBasic; import ghidra.program.model.pcode.PcodeBlockBasic;
import ghidra.service.graph.GraphDisplay; import ghidra.service.graph.GraphDisplay;
import ghidra.util.exception.AssertException;
/** /**
* Listener for when an AST graph's nodes are selected. * Listener for when an AST graph's nodes are selected.
@ -43,8 +45,20 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
@Override @Override
protected List<String> getVertices(AddressSetView selection) { protected List<String> getVertices(AddressSetView selection) {
if (graphType != CONTROL_FLOW_GRAPH) {
return null; return null;
} }
List<String> vertices = new ArrayList<>();
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (PcodeBlockBasic block : blocks) {
Address start = block.getStart();
Address stop = block.getStop();
if (selection.intersects(start, stop)) {
vertices.add(Integer.toString(block.getIndex()));
}
}
return vertices;
}
@Override @Override
protected AddressSet getAddressSetForVertices(List<String> vertexIds) { protected AddressSet getAddressSetForVertices(List<String> vertexIds) {
@ -53,7 +67,6 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
} }
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
Address location = null;
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks(); List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (String vertixId : vertexIds) { for (String vertixId : vertexIds) {
try { try {
@ -61,9 +74,6 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
PcodeBlockBasic block = blocks.get(index); PcodeBlockBasic block = blocks.get(index);
Address start = block.getStart(); Address start = block.getStart();
set.addRange(start, block.getStop()); set.addRange(start, block.getStop());
if (location == null || start.compareTo(location) < 0) {
location = start;
}
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
// continue // continue
@ -90,7 +100,16 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
@Override @Override
protected Address getAddressForVertexId(String vertexId) { protected Address getAddressForVertexId(String vertexId) {
return null; List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
try {
int index = Integer.parseInt(vertexId);
PcodeBlockBasic block = blocks.get(index);
return block.getStart();
}
catch (NumberFormatException e) {
throw new AssertException("Bad vertex id, expected a number but got " + vertexId);
}
} }
} }

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.decompile.actions;
import java.util.Iterator; import java.util.Iterator;
import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker; import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -122,7 +123,9 @@ public class ASTGraphTask extends Task {
display.setGraph(graph, description, false, monitor); display.setGraph(graph, description, false, monitor);
// set the graph location // set the graph location
if (location != null) { if (location != null) {
display.setLocation(displayListener.getVertexIdForAddress(location)); String id = displayListener.getVertexIdForAddress(location);
// update graph location, but don't have it send out event
display.setLocationFocus(id, EventTrigger.INTERNAL_ONLY);
} }
} }

View file

@ -19,6 +19,7 @@ import java.util.List;
import org.jgrapht.Graph; import org.jgrapht.Graph;
import docking.widgets.EventTrigger;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.Swing; import ghidra.util.Swing;
@ -56,12 +57,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
} }
@Override @Override
public void selectVertices(List<String> vertexList) { public void selectVertices(List<String> vertexList, EventTrigger eventTrigger) {
// This display is not interactive, so N/A // This display is not interactive, so N/A
} }
@Override @Override
public void setLocation(String vertexID) { public void setLocationFocus(String vertexID, EventTrigger eventTrigger) {
// This display is not interactive, so N/A // This display is not interactive, so N/A
} }

View file

@ -23,17 +23,15 @@ import org.jungrapht.visualization.MultiLayerTransformer;
import org.jungrapht.visualization.VisualizationViewer; import org.jungrapht.visualization.VisualizationViewer;
import ghidra.graph.job.AbstractAnimatorJob; import ghidra.graph.job.AbstractAnimatorJob;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
public class CenterAnimation<V, E> extends AbstractAnimatorJob { public class CenterAnimationJob extends AbstractAnimatorJob {
protected int duration = 1000; protected int duration = 1000;
private final Point2D oldPoint; private final Point2D oldPoint;
private final Point2D newPoint; private final Point2D newPoint;
private final Point2D lastPoint = new Point2D.Double(); private final Point2D lastPoint = new Point2D.Double();
private final VisualizationViewer<V, E> viewer; private final VisualizationViewer<?, ?> viewer;
public CenterAnimation(VisualizationViewer<V, E> viewer, public CenterAnimationJob(VisualizationViewer<?, ?> viewer,
Point2D oldPoint, Point2D newPoint) { Point2D oldPoint, Point2D newPoint) {
this.viewer = viewer; this.viewer = viewer;
this.oldPoint = oldPoint; this.oldPoint = oldPoint;

View file

@ -15,89 +15,56 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import docking.ActionContext; import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*;
import docking.action.ToggleDockingAction; import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
import docking.action.builder.ActionBuilder;
import docking.action.builder.MultiStateActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.menu.ActionState;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.AttributeFilters;
import ghidra.graph.job.GraphJobRunner;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedGraph;
import ghidra.service.graph.AttributedVertex;
import ghidra.service.graph.DummyGraphDisplayListener;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayListener;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import org.jgrapht.Graph;
import org.jungrapht.visualization.RenderContext;
import org.jungrapht.visualization.SatelliteVisualizationViewer;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
import org.jungrapht.visualization.control.DefaultGraphMouse;
import org.jungrapht.visualization.control.DefaultLensGraphMouse;
import org.jungrapht.visualization.control.DefaultSatelliteGraphMouse;
import org.jungrapht.visualization.control.LensGraphMouse;
import org.jungrapht.visualization.control.LensMagnificationGraphMousePlugin;
import org.jungrapht.visualization.control.MultiSelectionStrategy;
import org.jungrapht.visualization.decorators.EdgeShape;
import org.jungrapht.visualization.decorators.EllipseShapeFunction;
import org.jungrapht.visualization.decorators.IconShapeFunction;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.renderers.JLabelVertexLabelRenderer;
import org.jungrapht.visualization.renderers.LightweightVertexRenderer;
import org.jungrapht.visualization.renderers.ModalRenderer;
import org.jungrapht.visualization.renderers.Renderer;
import org.jungrapht.visualization.selection.MutableSelectedState;
import org.jungrapht.visualization.selection.VertexEndpointsSelectedEdgeSelectedState;
import org.jungrapht.visualization.transform.Lens;
import org.jungrapht.visualization.transform.LensSupport;
import org.jungrapht.visualization.transform.MutableTransformer;
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
import org.jungrapht.visualization.util.RectangleUtils;
import resources.Icons;
import javax.swing.AbstractButton; import java.awt.*;
import javax.swing.BorderFactory; import java.awt.event.*;
import javax.swing.JComponent;
import javax.swing.JRadioButton;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.jungrapht.visualization.MultiLayerTransformer.Layer.VIEW; import javax.swing.*;
import static org.jungrapht.visualization.renderers.BiModalRenderer.LIGHTWEIGHT; import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import org.jgrapht.Graph;
import org.jungrapht.visualization.*;
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
import org.jungrapht.visualization.control.*;
import org.jungrapht.visualization.decorators.*;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
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.selection.MutableSelectedState;
import org.jungrapht.visualization.selection.VertexEndpointsSelectedEdgeSelectedState;
import org.jungrapht.visualization.transform.*;
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
import org.jungrapht.visualization.util.RectangleUtils;
import docking.ActionContext;
import docking.action.ToggleDockingAction;
import docking.action.builder.*;
import docking.menu.ActionState;
import docking.widgets.EventTrigger;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.AttributeFilters;
import ghidra.graph.job.GraphJobRunner;
import ghidra.service.graph.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import resources.Icons;
/** /**
* Delegates to a {@link VisualizationViewer} to draw a graph visualization * Delegates to a {@link VisualizationViewer} to draw a graph visualization
@ -145,15 +112,16 @@ public class DefaultGraphDisplay implements GraphDisplay {
private final DefaultGraphDisplayComponentProvider componentProvider; private final DefaultGraphDisplayComponentProvider componentProvider;
/** /**
* whether to scroll the visualization in order to center the selected vertex * whether to ensure the focused vertex is visible, scrolling if necessary
* (or the centroid of the selected vertices) * the visualization in order to center the selected vertex
* or the center of the set of selected vertices
*/ */
private boolean enableScrollToSelection = false; private boolean ensureVertexIsVisible = false;
/** /**
* allows selection of various {@link LayoutAlgorithm} ('arrangements') * allows selection of various {@link LayoutAlgorithm} ('arrangements')
*/ */
private final LayoutTransitionManager<AttributedVertex, AttributedEdge> layoutTransitionManager; private final LayoutTransitionManager layoutTransitionManager;
/** /**
* provides graph displays for supplied graphs * provides graph displays for supplied graphs
@ -164,9 +132,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
*/ */
private LayoutWorkingDialog layoutWorkingDialog; private LayoutWorkingDialog layoutWorkingDialog;
/** /**
* the vertex that has been nominated to be 'located' in the graph display and listing * the vertex that has been nominated to be 'focused' in the graph display and listing
*/ */
private AttributedVertex locatedVertex; private AttributedVertex focusedVertex;
private final GraphJobRunner jobRunner = new GraphJobRunner(); private final GraphJobRunner jobRunner = new GraphJobRunner();
/** /**
* a satellite view that shows in the lower left corner as a birds-eye view of the graph display * a satellite view that shows in the lower left corner as a birds-eye view of the graph display
@ -208,10 +176,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
attributedGraph.addEdge(source, target, e); attributedGraph.addEdge(source, target, e);
}); });
displaySubGraph(attributedGraph); displaySubGraph(attributedGraph);
} catch (CancelledException e) { }
catch (CancelledException e) {
// noop // noop
} }
}; };
private SwitchableSelectionItemListener switchableSelectionListener;
/** /**
* Create the initial display, the graph-less visualization viewer, and its controls * Create the initial display, the graph-less visualization viewer, and its controls
@ -229,7 +199,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.addToTool(); componentProvider.addToTool();
satelliteViewer = createSatelliteViewer(viewer); satelliteViewer = createSatelliteViewer(viewer);
layoutTransitionManager = layoutTransitionManager =
new LayoutTransitionManager<>(viewer, this::isRoot); new LayoutTransitionManager(viewer, this::isRoot);
viewer.getComponent().addComponentListener(new ComponentAdapter() { viewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override @Override
@ -244,7 +214,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
}); });
viewer.setInitialDimensionFunction(InitialDimensionFunction viewer.setInitialDimensionFunction(InitialDimensionFunction
.builder(viewer.getRenderContext().getVertexBoundsFunction()).build()); .builder(viewer.getRenderContext().getVertexBoundsFunction())
.build());
createActions(); createActions();
connectSelectionStateListeners(); connectSelectionStateListeners();
@ -286,22 +257,23 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
/** /**
* create the highlighters ({@code Paintable}s to show which vertices have been selected or located * create the highlighters ({@code Paintable}s to show which vertices have been selected or focused)
*/ */
private void buildHighlighers() { private void buildHighlighers() {
// for highlighting of multiple selected vertices // for highlighting of multiple selected vertices
MultiSelectedVertexPaintable<AttributedVertex, AttributedEdge> multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer) MultiSelectedVertexPaintable<AttributedVertex, AttributedEdge> multiSelectedVertexPaintable =
MultiSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(4.f) .selectionStrokeMin(4.f)
.selectionPaint(Color.red) .selectionPaint(Color.red)
.useBounds(false) .useBounds(false)
.build(); .build();
// manages highlight painting of a single selected vertex // manages highlight painting of a single selected vertex
SingleSelectedVertexPaintable<AttributedVertex, AttributedEdge> singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer) SingleSelectedVertexPaintable<AttributedVertex, AttributedEdge> singleSelectedVertexPaintable =
SingleSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(4.f) .selectionStrokeMin(4.f)
.selectionPaint(Color.red) .selectionPaint(Color.red)
.selectedVertexFunction(vs -> this.locatedVertex) .selectedVertexFunction(vs -> this.focusedVertex)
.build(); .build();
// draws the selection highlights // draws the selection highlights
@ -320,23 +292,24 @@ public class DefaultGraphDisplay implements GraphDisplay {
// create a toggle for 'scroll to selected vertex' // create a toggle for 'scroll to selected vertex'
new ToggleActionBuilder("Scroll To Selection", pluginName) new ToggleActionBuilder("Scroll To Selection", pluginName)
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.description("Scroll display to center the 'Located' vertex") .description("Ensure that the 'focused' vertex is visible")
.selected(false) .selected(true)
.onAction(context -> enableScrollToSelection = .onAction(context -> ensureVertexIsVisible =
((AbstractButton) context.getSourceObject()).isSelected()) ((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
this.ensureVertexIsVisible = true; // since we intialized action to selected
// create a toggle for enabling 'free-form' selection: selection is // create a toggle for enabling 'free-form' selection: selection is
// inside of a traced shape instead of a rectangle // inside of a traced shape instead of a rectangle
new ToggleActionBuilder("Free-Form Selection", pluginName) new ToggleActionBuilder("Free-Form Selection", pluginName)
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)")
.selected(false) .selected(false)
.onAction(context -> .onAction(context -> freeFormSelection =
freeFormSelection = ((AbstractButton) context.getSourceObject()).isSelected()) ((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
// create an icon button to display the satellite view // create an icon button to display the satellite view
new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View") new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View")
.toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON) .toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON)
@ -356,11 +329,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
.description("Show View Magnifier") .description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
.onAction(context -> magnifyViewSupport.activate( .onAction(context -> magnifyViewSupport.activate(
((AbstractButton) context.getSourceObject()).isSelected() ((AbstractButton) context.getSourceObject()).isSelected()))
))
.build(); .build();
magnifyViewSupport.addItemListener(itemEvent -> magnifyViewSupport.addItemListener(
lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED));
componentProvider.addLocalAction(lensToggle); componentProvider.addLocalAction(lensToggle);
// create an action button to show a dialog with generated filters // create an action button to show a dialog with generated filters
@ -379,16 +351,18 @@ public class DefaultGraphDisplay implements GraphDisplay {
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
// show a 'busy' dialog while the layout algorithm is computing vertex locations // show a 'busy' dialog while the layout algorithm is computing vertex locations
viewer.getVisualizationModel().getLayoutModel() viewer.getVisualizationModel()
.getLayoutStateChangeSupport().addLayoutStateChangeListener( .getLayoutModel()
.getLayoutStateChangeSupport()
.addLayoutStateChangeListener(
evt -> { evt -> {
if (evt.active) { if (evt.active) {
Swing.runLater(this::showLayoutWorking); Swing.runLater(this::showLayoutWorking);
} else { }
else {
Swing.runLater(this::hideLayoutWorking); Swing.runLater(this::hideLayoutWorking);
} }
} });
);
} }
/** /**
@ -465,14 +439,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.repaint(); viewer.repaint();
} }
/** private void displaySubGraph(Graph<AttributedVertex, AttributedEdge> subGraph)
* from the supplied {@link Graph}, create a new GraphDisplay in a new window or tab throws CancelledException {
* @param subGraph
* @throws CancelledException
*/
private void displaySubGraph(Graph<AttributedVertex, AttributedEdge> subGraph) throws CancelledException {
GraphDisplay graphDisplay = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); GraphDisplay graphDisplay = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
graphDisplay.setGraph((AttributedGraph)subGraph, "SubGraph", false, TaskMonitor.DUMMY); graphDisplay.setGraph((AttributedGraph) subGraph, "SubGraph", false, TaskMonitor.DUMMY);
graphDisplay.setGraphDisplayListener(listener); graphDisplay.setGraphDisplayListener(listener);
} }
@ -486,27 +456,27 @@ public class DefaultGraphDisplay implements GraphDisplay {
Dimension viewerSize = parentViewer.getSize(); Dimension viewerSize = parentViewer.getSize();
Dimension satelliteSize = new Dimension( Dimension satelliteSize = new Dimension(
viewerSize.width / 4, viewerSize.height / 4); viewerSize.width / 4, viewerSize.height / 4);
final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer = final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satellite =
SatelliteVisualizationViewer.builder(parentViewer) SatelliteVisualizationViewer.builder(parentViewer)
.viewSize(satelliteSize) .viewSize(satelliteSize)
.build(); .build();
satelliteViewer.setGraphMouse(new DefaultSatelliteGraphMouse()); satellite.setGraphMouse(new DefaultSatelliteGraphMouse());
satelliteViewer.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor); satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor);
satelliteViewer.getRenderContext() satellite.getRenderContext()
.setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke); .setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke);
satelliteViewer.getRenderContext().setVertexFillPaintFunction(Colors::getColor); satellite.getRenderContext().setVertexFillPaintFunction(Colors::getColor);
satelliteViewer.scaleToLayout(); satellite.scaleToLayout();
satelliteViewer.getRenderContext().setVertexLabelFunction(n -> null); satellite.getRenderContext().setVertexLabelFunction(n -> null);
satelliteViewer.getComponent().setBorder(BorderFactory.createEtchedBorder()); satellite.getComponent().setBorder(BorderFactory.createEtchedBorder());
parentViewer.getComponent().addComponentListener(new ComponentAdapter() { parentViewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override @Override
public void componentResized(ComponentEvent evt) { public void componentResized(ComponentEvent evt) {
Dimension size = evt.getComponent().getSize(); Dimension size = evt.getComponent().getSize();
Dimension quarterSize = new Dimension(size.width / 4, size.height / 4); Dimension quarterSize = new Dimension(size.width / 4, size.height / 4);
satelliteViewer.getComponent().setSize(quarterSize); satellite.getComponent().setSize(quarterSize);
} }
}); });
return satelliteViewer; return satellite;
} }
/** /**
@ -532,10 +502,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
this.listener = listener; this.listener = listener;
DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse = DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse =
GhidraGraphMouse.<AttributedVertex, AttributedEdge>builder() GhidraGraphMouse.<AttributedVertex, AttributedEdge> builder()
.viewer(viewer) .viewer(viewer)
.subgraphConsumer(subgraphConsumer) .subgraphConsumer(subgraphConsumer)
.locatedVertexConsumer(this::setLocatedVertex) .locatedVertexConsumer(this::setFocusedVertex)
.graphDisplayListener(listener) .graphDisplayListener(listener)
.vertexIdFunction(AttributedVertex::getId) .vertexIdFunction(AttributedVertex::getId)
.vertexNameFunction(AttributedVertex::getName) .vertexNameFunction(AttributedVertex::getName)
@ -547,54 +517,55 @@ public class DefaultGraphDisplay implements GraphDisplay {
* connect the selection state to to the visualization * connect the selection state to to the visualization
*/ */
private void connectSelectionStateListeners() { private void connectSelectionStateListeners() {
viewer.getSelectedVertexState().addItemListener(e -> Swing.runLater(() -> { switchableSelectionListener = new SwitchableSelectionItemListener();
// there was a change in the set of selected vertices. viewer.getSelectedVertexState().addItemListener(switchableSelectionListener);
// if the locatedVertex is null, set it from one of the selected }
// vertices
if (e.getStateChange() == ItemEvent.SELECTED) {
Collection<AttributedVertex> selectedVertices = getVertices(e.getItem());
List<String> selectedVertexIds = toVertexIds(selectedVertices);
notifySelectionChanged(selectedVertexIds);
if (selectedVertices.size() == 1) { protected void setFocusedVertex(AttributedVertex vertex) {
// if only one vertex was selected, make it the locatedVertex setFocusedVertex(vertex, EventTrigger.API_CALL);
setLocatedVertex(selectedVertices.stream().findFirst().get());
} else if (this.locatedVertex == null) {
// if there is currently no locatedVertex, attempt to get
// one from the selectedVertices
setLocatedVertex(selectedVertices.stream().findFirst().orElse(null));
} }
protected void setFocusedVertex(AttributedVertex vertex, EventTrigger eventTrigger) {
boolean changed = this.focusedVertex != vertex;
this.focusedVertex = vertex;
if (focusedVertex != null) {
if (changed && eventTrigger != EventTrigger.INTERNAL_ONLY) {
notifyLocationFocusChanged(focusedVertex.getId());
} }
else if (e.getStateChange() == ItemEvent.DESELECTED) { // make sure the vertex is visible, even if the vertex has not changed
notifySelectionChanged(Collections.emptyList()); scrollToSelected(focusedVertex);
}
viewer.repaint(); viewer.repaint();
})); }
} }
/** /**
* set the vertex that has been nominated to be 'located' * determines whether the passed layout coordinates are visible in the display
* @param vertex the lucky vertex * @param x of interest (layout coordinates)
* @param y of interest (layout coordinates)
* @return {@code true} if the coordinates are visible in the display view
*/ */
protected void setLocatedVertex(AttributedVertex vertex) { private boolean isVisible(double x, double y) {
boolean changed = this.locatedVertex != vertex; if (viewer.getComponent().isVisible() && !viewer.getBounds().isEmpty()) {
this.locatedVertex = vertex; // project the view bounds into the layout coordinate system, test for containing the coordinates
if (locatedVertex != null && changed) { return viewer.getRenderContext()
notifyLocationChanged(locatedVertex.getId()); .getMultiLayerTransformer()
scrollToSelected(locatedVertex); .inverseTransform(viewer.getBounds())
viewer.repaint(); .getBounds()
.contains(x, y);
} }
return true;
} }
/** /**
* transform the supplied {@code AttributedVertex} Set members to a List of their ids * transform the supplied {@code AttributedVertex}s to a List of their ids
* @param selectedVertices * @param selectedVertices the collections of vertices.
* @return * @return a list of vertex ids
*/ */
private List<String> toVertexIds(Collection<AttributedVertex> selectedVertices) { private List<String> toVertexIds(Collection<AttributedVertex> selectedVertices) {
return selectedVertices.stream().map(AttributedVertex::getId).collect(Collectors.toList()); return selectedVertices.stream().map(AttributedVertex::getId).collect(Collectors.toList());
} }
@SuppressWarnings("unchecked")
private Collection<AttributedVertex> getVertices(Object item) { private Collection<AttributedVertex> getVertices(Object item) {
if (item instanceof Collection) { if (item instanceof Collection) {
return (Collection<AttributedVertex>) item; return (Collection<AttributedVertex>) item;
@ -606,28 +577,30 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
/** /**
* fire an event to say the selected vertices changed * fire an event to notify the selected vertices changed
* @param vertexIds * @param vertexIds the list of vertexes
*/ */
private void notifySelectionChanged(List<String> vertexIds) { private void notifySelectionChanged(List<String> vertexIds) {
Swing.runLater(() -> listener.selectionChanged(vertexIds)); Swing.runLater(() -> listener.selectionChanged(vertexIds));
} }
/** /**
* fire and event to say the located vertex changed * fire and event to say the focused vertex changed
* @param vertexId * @param vertexId the id of the focused vertex
*/ */
private void notifyLocationChanged(String vertexId) { private void notifyLocationFocusChanged(String vertexId) {
Swing.runLater(() -> listener.locationChanged(vertexId)); Swing.runLater(() -> listener.locationFocusChanged(vertexId));
} }
/**
* Pass the supplied list of vertex id's to the underlying visualization to cause them to be 'selected' visually
* @param vertexIdList the vertex ids to select
*/
@Override @Override
public void selectVertices(List<String> vertexIdList) { public void selectVertices(List<String> vertexIdList, EventTrigger eventTrigger) {
MutableSelectedState<AttributedVertex> nodeSelectedState = viewer.getSelectedVertexState(); // if we are not to fire events, turn off the selection listener we provided to the
// graphing library.
switchableSelectionListener.setEnabled(eventTrigger != EventTrigger.INTERNAL_ONLY);
try {
MutableSelectedState<AttributedVertex> nodeSelectedState =
viewer.getSelectedVertexState();
Set<AttributedVertex> selected = getVertices(vertexIdList); Set<AttributedVertex> selected = getVertices(vertexIdList);
if (vertexIdList.isEmpty()) { if (vertexIdList.isEmpty()) {
nodeSelectedState.clear(); nodeSelectedState.clear();
@ -639,6 +612,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
viewer.repaint(); viewer.repaint();
} }
finally {
// always turn on the selection listener
switchableSelectionListener.setEnabled(true);
}
}
/** /**
* *
@ -653,20 +631,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
/**
* for the supplied vertex id, find the {@code AttributedVertex} and translate
* the display to center it
* @param vertexID the id of the vertex to focus
*/
@Override @Override
public void setLocation(String vertexID) { public void setLocationFocus(String vertexID, EventTrigger eventTrigger) {
Optional<AttributedVertex> located = Optional<AttributedVertex> vertexToFocus =
graph.vertexSet().stream().filter(v -> vertexID.equals(v.getId())).findFirst(); graph.vertexSet().stream().filter(v -> vertexID.equals(v.getId())).findFirst();
log.fine("picking address:" + vertexID + " returned " + located); log.fine("picking address:" + vertexID + " returned " + vertexToFocus);
viewer.repaint(); viewer.repaint();
located.ifPresent(v -> { vertexToFocus.ifPresent(v -> {
setLocatedVertex(v); setFocusedVertex(v, eventTrigger);
scrollToSelected(v);
}); });
viewer.repaint(); viewer.repaint();
} }
@ -854,23 +826,24 @@ public class DefaultGraphDisplay implements GraphDisplay {
* @param vertices the vertices to center * @param vertices the vertices to center
*/ */
void scrollToSelected(Collection<AttributedVertex> vertices) { void scrollToSelected(Collection<AttributedVertex> vertices) {
if (ensureVertexIsVisible) {
if (!enableScrollToSelection) { jobRunner.finishAllJobs();
return;
}
Point2D newCenter = getPointToCenter(vertices); Point2D newCenter = getPointToCenter(vertices);
if (!isVisible(newCenter.getX(), newCenter.getY())) {
Point2D existingCenter = viewer.getRenderContext() Point2D existingCenter = viewer.getRenderContext()
.getMultiLayerTransformer() .getMultiLayerTransformer()
.inverseTransform(viewer.getCenter()); .inverseTransform(viewer.getCenter());
jobRunner.schedule(new CenterAnimation<>(viewer, existingCenter, newCenter)); jobRunner.schedule(new CenterAnimationJob(viewer, existingCenter, newCenter));
}
}
} }
/**w /**
* scroll the visualization to center the passed vertex * scroll the visualization to center the passed vertex
* @param vertex the vertex to center * @param vertex the vertex to center
*/ */
void scrollToSelected(AttributedVertex vertex) { private void scrollToSelected(AttributedVertex vertex) {
List<AttributedVertex> vertices = List<AttributedVertex> vertices =
vertex == null ? Collections.emptyList() : List.of(vertex); vertex == null ? Collections.emptyList() : List.of(vertex);
scrollToSelected(vertices); scrollToSelected(vertices);
@ -908,8 +881,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override @Override
public void updateVertexName(String id, String newName) { public void updateVertexName(String id, String newName) {
// find the vertex, if present, change the name // find the vertex, if present, change the name
Optional<AttributedVertex> optional = graph.vertexSet().stream() Optional<AttributedVertex> optional = graph.vertexSet()
.filter(v -> v.getId().equals(id)).findFirst(); .stream()
.filter(v -> v.getId().equals(id))
.findFirst();
if (optional.isPresent()) { if (optional.isPresent()) {
AttributedVertex vertex = optional.get(); AttributedVertex vertex = optional.get();
vertex.setName(newName); vertex.setName(newName);
@ -930,13 +905,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
/** /**
* create and return a {@link VisualizationViewer} to display graphs * create and return a {@link VisualizationViewer} to display graphs
* @return * @return the new VisualizationViewer
*/ */
public VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() { public VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
final VisualizationViewer<AttributedVertex, AttributedEdge> vv = final VisualizationViewer<AttributedVertex, AttributedEdge> vv =
VisualizationViewer.<AttributedVertex, AttributedEdge> builder() VisualizationViewer.<AttributedVertex, AttributedEdge> builder()
.multiSelectionStrategySupplier(() -> freeFormSelection ? .multiSelectionStrategySupplier(
MultiSelectionStrategy.arbitrary() : MultiSelectionStrategy.rectangular()) () -> freeFormSelection ? MultiSelectionStrategy.arbitrary()
: MultiSelectionStrategy.rectangular())
.viewSize(PREFERRED_VIEW_SIZE) .viewSize(PREFERRED_VIEW_SIZE)
.layoutSize(PREFERRED_LAYOUT_SIZE) .layoutSize(PREFERRED_LAYOUT_SIZE)
.build(); .build();
@ -955,12 +931,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override @Override
public void ancestorRemoved(AncestorEvent ancestorEvent) { public void ancestorRemoved(AncestorEvent ancestorEvent) {
// do nothing
} }
@Override @Override
public void ancestorMoved(AncestorEvent ancestorEvent) { public void ancestorMoved(AncestorEvent ancestorEvent) {
// do nothing
} }
}); });
@ -981,7 +957,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
renderContext.setVertexIconFunction(iconCache::get); renderContext.setVertexIconFunction(iconCache::get);
vv.setInitialDimensionFunction(InitialDimensionFunction vv.setInitialDimensionFunction(InitialDimensionFunction
.builder(nodeImageShapeFunction.andThen(s -> RectangleUtils.convert(s.getBounds2D()))).build()); .builder(
nodeImageShapeFunction.andThen(s -> RectangleUtils.convert(s.getBounds2D())))
.build());
// the selectedEdgeState will be controlled by the vertices that are selected. // the selectedEdgeState will be controlled by the vertices that are selected.
// if both endpoints of an edge are selected, select that edge. // if both endpoints of an edge are selected, select that edge.
@ -1030,4 +1008,49 @@ public class DefaultGraphDisplay implements GraphDisplay {
vv.setBackground(Color.WHITE); vv.setBackground(Color.WHITE);
return vv; return vv;
} }
/**
* Item listener for selection changes in the graph with the additional
* capability of being able to disable the listener without removing it.
*/
class SwitchableSelectionItemListener implements ItemListener {
boolean enabled = true;
@Override
public void itemStateChanged(ItemEvent e) {
if (enabled) {
Swing.runLater(() -> run(e));
}
}
private void run(ItemEvent e) {
// there was a change in the set of selected vertices.
// if the focused vertex is null, set it from one of the selected
// vertices
if (e.getStateChange() == ItemEvent.SELECTED) {
Collection<AttributedVertex> selectedVertices = getVertices(e.getItem());
List<String> selectedVertexIds = toVertexIds(selectedVertices);
notifySelectionChanged(selectedVertexIds);
if (selectedVertices.size() == 1) {
// if only one vertex was selected, make it the focused vertex
setFocusedVertex(selectedVertices.stream().findFirst().get());
}
else if (DefaultGraphDisplay.this.focusedVertex == null) {
// if there is currently no focused Vertex, attempt to get
// one from the selectedVertices
setFocusedVertex(selectedVertices.stream().findFirst().orElse(null));
}
}
else if (e.getStateChange() == ItemEvent.DESELECTED) {
notifySelectionChanged(Collections.emptyList());
}
viewer.repaint();
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
} }

View file

@ -15,21 +15,14 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import org.jungrapht.visualization.layout.algorithms.BalloonLayoutAlgorithm; import java.util.function.Function;
import org.jungrapht.visualization.layout.algorithms.CircleLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.EiglspergerLayoutAlgorithm; import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.FRLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.GEMLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.KKLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.TidierRadialTreeLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.TidierTreeLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.TreeLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering; import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
import java.util.function.Function; import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
/** /**
* A central location to list and provide all layout algorithms, their names, and their builders * A central location to list and provide all layout algorithms, their names, and their builders
@ -38,8 +31,8 @@ import java.util.function.Function;
* This class provides LayoutAlgorithm builders instead of LayoutAlgorithms because some LayoutAlgorithms * This class provides LayoutAlgorithm builders instead of LayoutAlgorithms because some LayoutAlgorithms
* accumulate state information (so are used only one time). * accumulate state information (so are used only one time).
*/ */
class LayoutFunction<V, E> class LayoutFunction
implements Function<String, LayoutAlgorithm.Builder<V, ?, ?>> { implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
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";
@ -64,51 +57,53 @@ class LayoutFunction<V, E>
} }
@Override @Override
public LayoutAlgorithm.Builder<V, ?, ?> apply(String name) { public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
switch(name) { switch(name) {
case GEM: case GEM:
return GEMLayoutAlgorithm.edgeAwareBuilder(); return GEMLayoutAlgorithm.edgeAwareBuilder();
case KAMADA_KAWAI: case KAMADA_KAWAI:
return KKLayoutAlgorithm.<V> builder() return KKLayoutAlgorithm.<AttributedVertex> builder()
.preRelaxDuration(1000); .preRelaxDuration(1000);
case FRUCTERMAN_REINGOLD: case FRUCTERMAN_REINGOLD:
return FRLayoutAlgorithm.<V> builder() return FRLayoutAlgorithm.<AttributedVertex> builder()
.repulsionContractBuilder(BarnesHutFRRepulsion.builder()); .repulsionContractBuilder(BarnesHutFRRepulsion.builder());
case CIRCLE_MINCROSS: case CIRCLE_MINCROSS:
return CircleLayoutAlgorithm.<V> builder() return CircleLayoutAlgorithm.<AttributedVertex> builder()
.reduceEdgeCrossing(true); .reduceEdgeCrossing(true);
case TIDIER_RADIAL_TREE: case TIDIER_RADIAL_TREE:
return TidierRadialTreeLayoutAlgorithm.<V, E> edgeAwareBuilder(); return TidierRadialTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
case MIN_CROSS_TOP_DOWN: case MIN_CROSS_TOP_DOWN:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<V, E> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.layering(Layering.TOP_DOWN); .layering(Layering.TOP_DOWN);
case MIN_CROSS_LONGEST_PATH: case MIN_CROSS_LONGEST_PATH:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<V, E> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.layering(Layering.LONGEST_PATH); .layering(Layering.LONGEST_PATH);
case MIN_CROSS_NETWORK_SIMPLEX: case MIN_CROSS_NETWORK_SIMPLEX:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<V, E> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.layering(Layering.NETWORK_SIMPLEX); .layering(Layering.NETWORK_SIMPLEX);
case MIN_CROSS_COFFMAN_GRAHAM: case MIN_CROSS_COFFMAN_GRAHAM:
return EiglspergerLayoutAlgorithm return EiglspergerLayoutAlgorithm
.<V, E> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.layering(Layering.COFFMAN_GRAHAM); .layering(Layering.COFFMAN_GRAHAM);
case RADIAL: case RADIAL:
return RadialTreeLayoutAlgorithm return RadialTreeLayoutAlgorithm
.<V> builder() .<AttributedVertex> builder()
.verticalVertexSpacing(300); .verticalVertexSpacing(300);
case BALLOON: case BALLOON:
return BalloonLayoutAlgorithm return BalloonLayoutAlgorithm
.<V> builder() .<AttributedVertex> builder()
.verticalVertexSpacing(300); .verticalVertexSpacing(300);
case TREE: case TREE:
return TreeLayoutAlgorithm return TreeLayoutAlgorithm
.builder(); .builder();
case TIDIER_TREE: case TIDIER_TREE:
default: default:
return TidierTreeLayoutAlgorithm.<V, E> edgeAwareBuilder(); return TidierTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
} }
} }
} }

View file

@ -15,61 +15,57 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import org.jungrapht.visualization.RenderContext; import static ghidra.graph.visualization.LayoutFunction.*;
import org.jungrapht.visualization.VisualizationServer;
import org.jungrapht.visualization.layout.algorithms.Balloon;
import org.jungrapht.visualization.layout.algorithms.BalloonLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.EdgeSorting;
import org.jungrapht.visualization.layout.algorithms.Layered;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayout;
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.TreeLayout;
import org.jungrapht.visualization.layout.algorithms.util.VertexBoundsFunctionConsumer;
import org.jungrapht.visualization.layout.model.Rectangle;
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
import org.jungrapht.visualization.util.LayoutPaintable;
import java.util.Comparator; import java.util.Comparator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import static ghidra.graph.visualization.LayoutFunction.TIDIER_TREE; import org.jungrapht.visualization.RenderContext;
import org.jungrapht.visualization.VisualizationServer;
import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.util.VertexBoundsFunctionConsumer;
import org.jungrapht.visualization.layout.model.Rectangle;
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
import org.jungrapht.visualization.util.LayoutPaintable;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
/** /**
* Manages the selection and transition from one {@link LayoutAlgorithm} to another * Manages the selection and transition from one {@link LayoutAlgorithm} to another
*/ */
class LayoutTransitionManager<V, E> { class LayoutTransitionManager {
LayoutFunction layoutFunction = new LayoutFunction(); LayoutFunction layoutFunction = new 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}
*/ */
VisualizationServer<V, E> visualizationServer; VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer;
/** /**
* a {@link Predicate} to assist in determining which vertices are root vertices (for Tree layouts) * a {@link Predicate} to assist in determining which vertices are root vertices (for Tree layouts)
*/ */
Predicate<V> rootPredicate; Predicate<AttributedVertex> rootPredicate;
/** /**
* a {@link Comparator} to sort edges during layout graph traversal * a {@link Comparator} to sort edges during layout graph traversal
*/ */
Comparator<E> edgeComparator = (e1, e2) -> 0; Comparator<AttributedEdge> edgeComparator = (e1, e2) -> 0;
/** /**
* a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices
*/ */
Function<V, Rectangle> vertexBoundsFunction; Function<AttributedVertex, Rectangle> vertexBoundsFunction;
/** /**
* the {@link RenderContext} used to draw the graph * the {@link RenderContext} used to draw the graph
*/ */
RenderContext<V, E> renderContext; RenderContext<AttributedVertex, AttributedEdge> renderContext;
LayoutPaintable.BalloonRings<V, E> balloonLayoutRings; LayoutPaintable.BalloonRings<AttributedVertex, AttributedEdge> balloonLayoutRings;
LayoutPaintable.RadialRings<V> radialLayoutRings; LayoutPaintable.RadialRings<AttributedVertex> radialLayoutRings;
/** /**
@ -78,8 +74,8 @@ class LayoutTransitionManager<V, E> {
* @param rootPredicate selects root vertices * @param rootPredicate selects root vertices
*/ */
public LayoutTransitionManager( public LayoutTransitionManager(
VisualizationServer<V, E> visualizationServer, VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
Predicate<V> rootPredicate) { Predicate<AttributedVertex> rootPredicate) {
this.visualizationServer = visualizationServer; this.visualizationServer = visualizationServer;
this.rootPredicate = rootPredicate; this.rootPredicate = rootPredicate;
@ -87,7 +83,7 @@ class LayoutTransitionManager<V, E> {
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
} }
public void setEdgeComparator(Comparator<E> edgeComparator) { public void setEdgeComparator(Comparator<AttributedEdge> edgeComparator) {
this.edgeComparator = edgeComparator; this.edgeComparator = edgeComparator;
} }
@ -97,19 +93,19 @@ class LayoutTransitionManager<V, E> {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void setLayout(String layoutName) { public void setLayout(String layoutName) {
LayoutAlgorithm.Builder<V, ?, ?> builder = layoutFunction.apply(layoutName); LayoutAlgorithm.Builder<AttributedVertex, ?, ?> builder = layoutFunction.apply(layoutName);
LayoutAlgorithm<V> layoutAlgorithm = builder.build(); LayoutAlgorithm<AttributedVertex> layoutAlgorithm = builder.build();
if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) { if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
((VertexBoundsFunctionConsumer<V>) layoutAlgorithm) ((VertexBoundsFunctionConsumer<AttributedVertex>) layoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction); .setVertexBoundsFunction(vertexBoundsFunction);
} }
if (layoutAlgorithm instanceof Layered) { if (layoutAlgorithm instanceof Layered) {
((Layered<V, E>)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())));
} }
if (layoutAlgorithm instanceof TreeLayout) { if (layoutAlgorithm instanceof TreeLayout) {
((TreeLayout<V>) layoutAlgorithm).setRootPredicate(rootPredicate); ((TreeLayout<AttributedVertex>) layoutAlgorithm).setRootPredicate(rootPredicate);
} }
// remove any previously added layout paintables // remove any previously added layout paintables
removePaintable(radialLayoutRings); removePaintable(radialLayoutRings);
@ -117,18 +113,19 @@ class LayoutTransitionManager<V, E> {
if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) { if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) {
balloonLayoutRings = balloonLayoutRings =
new LayoutPaintable.BalloonRings<>( new LayoutPaintable.BalloonRings<>(
visualizationServer, (BalloonLayoutAlgorithm<V>) layoutAlgorithm); visualizationServer,
(BalloonLayoutAlgorithm<AttributedVertex>) layoutAlgorithm);
visualizationServer.addPreRenderPaintable(balloonLayoutRings); visualizationServer.addPreRenderPaintable(balloonLayoutRings);
} }
if (layoutAlgorithm instanceof RadialTreeLayout) { if (layoutAlgorithm instanceof RadialTreeLayout) {
radialLayoutRings = radialLayoutRings =
new LayoutPaintable.RadialRings<>( new LayoutPaintable.RadialRings<>(
visualizationServer, (RadialTreeLayout<V>) layoutAlgorithm); visualizationServer, (RadialTreeLayout<AttributedVertex>) layoutAlgorithm);
visualizationServer.addPreRenderPaintable(radialLayoutRings); visualizationServer.addPreRenderPaintable(radialLayoutRings);
} }
if (layoutAlgorithm instanceof EdgeSorting) { if (layoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<E>) layoutAlgorithm).setEdgeComparator(edgeComparator); ((EdgeSorting<AttributedEdge>) layoutAlgorithm).setEdgeComparator(edgeComparator);
} }
LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm); LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm);
} }
@ -140,22 +137,22 @@ class LayoutTransitionManager<V, E> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public LayoutAlgorithm<V> getInitialLayoutAlgorithm() { public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
LayoutAlgorithm<V> initialLayoutAlgorithm = LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutFunction.apply(TIDIER_TREE).build(); layoutFunction.apply(TIDIER_TREE).build();
if (initialLayoutAlgorithm instanceof TreeLayout) { if (initialLayoutAlgorithm instanceof TreeLayout) {
((TreeLayout<V>) initialLayoutAlgorithm) ((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setRootPredicate(rootPredicate); .setRootPredicate(rootPredicate);
((TreeLayout<V>) initialLayoutAlgorithm) ((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction); .setVertexBoundsFunction(vertexBoundsFunction);
} }
if (initialLayoutAlgorithm instanceof EdgeSorting) { if (initialLayoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<E>) initialLayoutAlgorithm) ((EdgeSorting<AttributedEdge>) initialLayoutAlgorithm)
.setEdgeComparator(edgeComparator); .setEdgeComparator(edgeComparator);
} }
if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) { if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
((VertexBoundsFunctionConsumer<V>) initialLayoutAlgorithm) ((VertexBoundsFunctionConsumer<AttributedVertex>) initialLayoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction); .setVertexBoundsFunction(vertexBoundsFunction);
} }
return initialLayoutAlgorithm; return initialLayoutAlgorithm;

View file

@ -18,6 +18,7 @@ package ghidra.graph.program;
import java.awt.Color; import java.awt.Color;
import java.util.*; import java.util.*;
import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -156,12 +157,15 @@ public class BlockGraphTask extends Task {
display.setGraph(graph, actionName, appendGraph, monitor); display.setGraph(graph, actionName, appendGraph, monitor);
if (location != null) { if (location != null) {
display.setLocation(listener.getVertexIdForAddress(location.getAddress())); // initialize the graph location, but don't have the graph send an event
String id = listener.getVertexIdForAddress(location.getAddress());
display.setLocationFocus(id, EventTrigger.INTERNAL_ONLY);
} }
if (selection != null && !selection.isEmpty()) { if (selection != null && !selection.isEmpty()) {
List<String> selectedVertices = listener.getVertices(selection); List<String> selectedVertices = listener.getVertices(selection);
if (selectedVertices != null) { if (selectedVertices != null) {
display.selectVertices(selectedVertices); // intialize the graph selection, but don't have the graph send an event
display.selectVertices(selectedVertices, EventTrigger.INTERNAL_ONLY);
} }
} }
} }

View file

@ -17,6 +17,7 @@ package ghidra.graph.program;
import java.util.*; import java.util.*;
import docking.widgets.EventTrigger;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -37,7 +38,7 @@ public class TestGraphDisplay implements GraphDisplay {
} }
@Override @Override
public void setLocation(String vertexID) { public void setLocationFocus(String vertexID, EventTrigger eventTrigger) {
currentFocusedVertex = vertexID; currentFocusedVertex = vertexID;
} }
@ -46,7 +47,7 @@ public class TestGraphDisplay implements GraphDisplay {
} }
@Override @Override
public void selectVertices(List<String> vertexList) { public void selectVertices(List<String> vertexList, EventTrigger eventTrigger) {
currentSelection = vertexList; currentSelection = vertexList;
} }
@ -103,7 +104,7 @@ public class TestGraphDisplay implements GraphDisplay {
} }
public void focusChanged(String vertexId) { public void focusChanged(String vertexId) {
listener.locationChanged(vertexId); listener.locationFocusChanged(vertexId);
} }
public void selectionChanged(List<String> vertexIds) { public void selectionChanged(List<String> vertexIds) {

View file

@ -30,7 +30,7 @@ public class DummyGraphDisplayListener implements GraphDisplayListener {
} }
@Override @Override
public void locationChanged(String vertexId) { public void locationFocusChanged(String vertexId) {
// I'm a dummy // I'm a dummy
} }

View file

@ -17,6 +17,7 @@ package ghidra.service.graph;
import java.util.List; import java.util.List;
import docking.widgets.EventTrigger;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -41,18 +42,31 @@ public interface GraphDisplay {
public void setGraphDisplayListener(GraphDisplayListener listener); public void setGraphDisplayListener(GraphDisplayListener listener);
/** /**
* Tells the graph display window to focus * Tells the graph display window to focus the vertex with the given id.
* *
* @param vertexID the id of the vertex to focus * @param vertexID the id of the vertex to focus
* @param eventTrigger Provides a hint to the GraphDisplay as to why we are updating the
* graph location so that the GraphDisplay can decide if it should send out a notification via
* the {@link GraphDisplayListener#locationFocusChanged(String)}. For example, if we are updating
* the the location due to an event from the main application, we don't want to notify the
* application the graph changed to avoid event cycles. See {@link EventTrigger} for more
* information.
*
*/ */
public void setLocation(String vertexID); public void setLocationFocus(String vertexID, EventTrigger eventTrigger);
/** /**
* Tells the graph display window to select the vertices with the given ids * Tells the graph display window to select the vertices with the given ids
* *
* @param vertexList the list of vertex ids to select * @param vertexList the list of vertex ids to select
* @param eventTrigger Provides a hint to the GraphDisplay as to why we are updating the
* graph location so that the GraphDisplay can decide if it should send out a notification via
* the {@link GraphDisplayListener#locationFocusChanged(String)}. For example, if we are updating
* the the location due to an event from the main application, we don't want to notify the
* application the graph changed to avoid event cycles. See {@link EventTrigger} for more
* information.
*/ */
public void selectVertices(List<String> vertexList); public void selectVertices(List<String> vertexList, EventTrigger eventTrigger);
/** /**
* Closes this graph display window. * Closes this graph display window.
@ -103,7 +117,7 @@ public interface GraphDisplay {
/** /**
* Updates a vertex to a new name * Updates a vertex to a new name
* @param id the vertix id * @param id the vertex id
* @param newName the new name of the vertex * @param newName the new name of the vertex
*/ */
public void updateVertexName(String id, String newName); public void updateVertexName(String id, String newName);

View file

@ -37,7 +37,7 @@ public interface GraphDisplayListener {
* Notification that the "focused" (active) vertex has changed. * Notification that the "focused" (active) vertex has changed.
* @param vertexId the vertex id of the currently "focused" vertex * @param vertexId the vertex id of the currently "focused" vertex
*/ */
public void locationChanged(String vertexId); public void locationFocusChanged(String vertexId);
default boolean updateVertexName(String vertexId, String oldName, String newName) { default boolean updateVertexName(String vertexId, String oldName, String newName) {
// no op // no op