From b647c6cd5b637df244e5dfc53544a6f36f3d761c Mon Sep 17 00:00:00 2001 From: ghidravore Date: Wed, 30 Sep 2020 17:31:17 -0400 Subject: [PATCH] Miscellanious bug fixes and code clean up. --- .../AddressBasedGraphDisplayListener.java | 10 +- .../actions/ASTGraphDisplayListener.java | 31 +- .../core/decompile/actions/ASTGraphTask.java | 5 +- .../export/ExportAttributedGraphDisplay.java | 5 +- ...Animation.java => CenterAnimationJob.java} | 8 +- .../visualization/DefaultGraphDisplay.java | 527 +++++++++--------- .../graph/visualization/LayoutFunction.java | 47 +- .../LayoutTransitionManager.java | 77 ++- .../ghidra/graph/program/BlockGraphTask.java | 8 +- .../graph/program/TestGraphDisplay.java | 7 +- .../graph/DummyGraphDisplayListener.java | 2 +- .../ghidra/service/graph/GraphDisplay.java | 22 +- .../service/graph/GraphDisplayListener.java | 2 +- 13 files changed, 405 insertions(+), 346 deletions(-) rename Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/{CenterAnimation.java => CenterAnimationJob.java} (89%) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java index 82761e641a..fe55d867fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import docking.widgets.EventTrigger; import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.events.*; @@ -65,7 +66,7 @@ public abstract class AddressBasedGraphDisplayListener } @Override - public void locationChanged(String vertexId) { + public void locationFocusChanged(String vertexId) { Address address = getAddressForVertexId(vertexId); if (address != null) { ProgramLocation location = new ProgramLocation(program, address); @@ -101,7 +102,9 @@ public abstract class AddressBasedGraphDisplayListener ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event; if (isMyProgram(ev.getProgram())) { 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) { @@ -110,7 +113,8 @@ public abstract class AddressBasedGraphDisplayListener ProgramSelection selection = ev.getSelection(); List selectedVertices = getVertices(selection); 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); } } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java index 6493033efc..377d916e4a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.decompile.actions; import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*; +import java.util.ArrayList; import java.util.List; 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.PcodeBlockBasic; import ghidra.service.graph.GraphDisplay; +import ghidra.util.exception.AssertException; /** * Listener for when an AST graph's nodes are selected. @@ -43,7 +45,19 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { @Override protected List getVertices(AddressSetView selection) { - return null; + if (graphType != CONTROL_FLOW_GRAPH) { + return null; + } + List vertices = new ArrayList<>(); + List 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 @@ -53,7 +67,6 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { } AddressSet set = new AddressSet(); - Address location = null; List blocks = hfunction.getBasicBlocks(); for (String vertixId : vertexIds) { try { @@ -61,9 +74,6 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { PcodeBlockBasic block = blocks.get(index); Address start = block.getStart(); set.addRange(start, block.getStop()); - if (location == null || start.compareTo(location) < 0) { - location = start; - } } catch (NumberFormatException e) { // continue @@ -90,7 +100,16 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { @Override protected Address getAddressForVertexId(String vertexId) { - return null; + List 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); + } } } 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 5f586f9084..db8a03986c 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 @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.decompile.actions; import java.util.Iterator; +import docking.widgets.EventTrigger; import ghidra.app.services.GraphDisplayBroker; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -122,7 +123,9 @@ public class ASTGraphTask extends Task { display.setGraph(graph, description, false, monitor); // set the graph location 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); } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java index 0ad99cd15c..27e1bea081 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java @@ -19,6 +19,7 @@ import java.util.List; import org.jgrapht.Graph; +import docking.widgets.EventTrigger; import ghidra.framework.plugintool.PluginTool; import ghidra.service.graph.*; import ghidra.util.Swing; @@ -56,12 +57,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay { } @Override - public void selectVertices(List vertexList) { + public void selectVertices(List vertexList, EventTrigger eventTrigger) { // This display is not interactive, so N/A } @Override - public void setLocation(String vertexID) { + public void setLocationFocus(String vertexID, EventTrigger eventTrigger) { // This display is not interactive, so N/A } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimation.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimationJob.java similarity index 89% rename from Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimation.java rename to Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimationJob.java index 53f44688e9..c1a73583c5 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimation.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/CenterAnimationJob.java @@ -23,17 +23,15 @@ import org.jungrapht.visualization.MultiLayerTransformer; import org.jungrapht.visualization.VisualizationViewer; import ghidra.graph.job.AbstractAnimatorJob; -import ghidra.service.graph.AttributedEdge; -import ghidra.service.graph.AttributedVertex; -public class CenterAnimation extends AbstractAnimatorJob { +public class CenterAnimationJob extends AbstractAnimatorJob { protected int duration = 1000; private final Point2D oldPoint; private final Point2D newPoint; private final Point2D lastPoint = new Point2D.Double(); - private final VisualizationViewer viewer; + private final VisualizationViewer viewer; - public CenterAnimation(VisualizationViewer viewer, + public CenterAnimationJob(VisualizationViewer viewer, Point2D oldPoint, Point2D newPoint) { this.viewer = viewer; this.oldPoint = oldPoint; 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 362e07d538..d1591d9d8e 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 @@ -15,89 +15,56 @@ */ package ghidra.graph.visualization; -import docking.ActionContext; -import docking.action.ToggleDockingAction; -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 static org.jungrapht.visualization.MultiLayerTransformer.Layer.*; +import static org.jungrapht.visualization.renderers.BiModalRenderer.*; -import javax.swing.AbstractButton; -import javax.swing.BorderFactory; -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.*; +import java.awt.event.*; import java.awt.geom.Point2D; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.util.*; 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.logging.Logger; import java.util.stream.Collectors; -import static org.jungrapht.visualization.MultiLayerTransformer.Layer.VIEW; -import static org.jungrapht.visualization.renderers.BiModalRenderer.LIGHTWEIGHT; +import javax.swing.*; +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 @@ -145,15 +112,16 @@ public class DefaultGraphDisplay implements GraphDisplay { private final DefaultGraphDisplayComponentProvider componentProvider; /** - * whether to scroll the visualization in order to center the selected vertex - * (or the centroid of the selected vertices) + * whether to ensure the focused vertex is visible, scrolling if necessary + * 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') */ - private final LayoutTransitionManager layoutTransitionManager; + private final LayoutTransitionManager layoutTransitionManager; /** * provides graph displays for supplied graphs @@ -164,9 +132,9 @@ public class DefaultGraphDisplay implements GraphDisplay { */ 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(); /** * a satellite view that shows in the lower left corner as a birds-eye view of the graph display @@ -198,20 +166,22 @@ public class DefaultGraphDisplay implements GraphDisplay { * a new tab or new window */ Consumer> subgraphConsumer = - g -> { - try { - AttributedGraph attributedGraph = new AttributedGraph(); - g.vertexSet().forEach(attributedGraph::addVertex); - g.edgeSet().forEach(e -> { - AttributedVertex source = g.getEdgeSource(e); - AttributedVertex target = g.getEdgeTarget(e); - attributedGraph.addEdge(source, target, e); - }); - displaySubGraph(attributedGraph); - } catch (CancelledException e) { - // noop - } - }; + g -> { + try { + AttributedGraph attributedGraph = new AttributedGraph(); + g.vertexSet().forEach(attributedGraph::addVertex); + g.edgeSet().forEach(e -> { + AttributedVertex source = g.getEdgeSource(e); + AttributedVertex target = g.getEdgeTarget(e); + attributedGraph.addEdge(source, target, e); + }); + displaySubGraph(attributedGraph); + } + catch (CancelledException e) { + // noop + } + }; + private SwitchableSelectionItemListener switchableSelectionListener; /** * Create the initial display, the graph-less visualization viewer, and its controls @@ -229,7 +199,7 @@ public class DefaultGraphDisplay implements GraphDisplay { componentProvider.addToTool(); satelliteViewer = createSatelliteViewer(viewer); layoutTransitionManager = - new LayoutTransitionManager<>(viewer, this::isRoot); + new LayoutTransitionManager(viewer, this::isRoot); viewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -244,7 +214,8 @@ public class DefaultGraphDisplay implements GraphDisplay { }); viewer.setInitialDimensionFunction(InitialDimensionFunction - .builder(viewer.getRenderContext().getVertexBoundsFunction()).build()); + .builder(viewer.getRenderContext().getVertexBoundsFunction()) + .build()); createActions(); connectSelectionStateListeners(); @@ -286,23 +257,24 @@ 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() { // for highlighting of multiple selected vertices - MultiSelectedVertexPaintable multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer) - .selectionStrokeMin(4.f) - .selectionPaint(Color.red) - .useBounds(false) - .build(); - + MultiSelectedVertexPaintable multiSelectedVertexPaintable = + MultiSelectedVertexPaintable.builder(viewer) + .selectionStrokeMin(4.f) + .selectionPaint(Color.red) + .useBounds(false) + .build(); // manages highlight painting of a single selected vertex - SingleSelectedVertexPaintable singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer) - .selectionStrokeMin(4.f) - .selectionPaint(Color.red) - .selectedVertexFunction(vs -> this.locatedVertex) - .build(); + SingleSelectedVertexPaintable singleSelectedVertexPaintable = + SingleSelectedVertexPaintable.builder(viewer) + .selectionStrokeMin(4.f) + .selectionPaint(Color.red) + .selectedVertexFunction(vs -> this.focusedVertex) + .build(); // draws the selection highlights viewer.addPostRenderPaintable(multiSelectedVertexPaintable); @@ -320,23 +292,24 @@ public class DefaultGraphDisplay implements GraphDisplay { // create a toggle for 'scroll to selected vertex' new ToggleActionBuilder("Scroll To Selection", pluginName) .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) - .description("Scroll display to center the 'Located' vertex") - .selected(false) - .onAction(context -> enableScrollToSelection = + .description("Ensure that the 'focused' vertex is visible") + .selected(true) + .onAction(context -> ensureVertexIsVisible = ((AbstractButton) context.getSourceObject()).isSelected()) .buildAndInstallLocal(componentProvider); + this.ensureVertexIsVisible = true; // since we intialized action to selected + // create a toggle for enabling 'free-form' selection: selection is // inside of a traced shape instead of a rectangle new ToggleActionBuilder("Free-Form Selection", pluginName) .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") .selected(false) - .onAction(context -> - freeFormSelection = ((AbstractButton) context.getSourceObject()).isSelected()) + .onAction(context -> freeFormSelection = + ((AbstractButton) context.getSourceObject()).isSelected()) .buildAndInstallLocal(componentProvider); - // create an icon button to display the satellite view new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View") .toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON) @@ -356,11 +329,10 @@ public class DefaultGraphDisplay implements GraphDisplay { .description("Show View Magnifier") .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) .onAction(context -> magnifyViewSupport.activate( - ((AbstractButton) context.getSourceObject()).isSelected() - )) + ((AbstractButton) context.getSourceObject()).isSelected())) .build(); - magnifyViewSupport.addItemListener(itemEvent -> - lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); + magnifyViewSupport.addItemListener( + itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); componentProvider.addLocalAction(lensToggle); // create an action button to show a dialog with generated filters @@ -379,16 +351,18 @@ public class DefaultGraphDisplay implements GraphDisplay { .buildAndInstallLocal(componentProvider); // show a 'busy' dialog while the layout algorithm is computing vertex locations - viewer.getVisualizationModel().getLayoutModel() - .getLayoutStateChangeSupport().addLayoutStateChangeListener( - evt -> { - if (evt.active) { - Swing.runLater(this::showLayoutWorking); - } else { - Swing.runLater(this::hideLayoutWorking); - } - } - ); + viewer.getVisualizationModel() + .getLayoutModel() + .getLayoutStateChangeSupport() + .addLayoutStateChangeListener( + evt -> { + if (evt.active) { + Swing.runLater(this::showLayoutWorking); + } + else { + Swing.runLater(this::hideLayoutWorking); + } + }); } /** @@ -438,7 +412,7 @@ public class DefaultGraphDisplay implements GraphDisplay { layoutWorkingDialog.close(); } this.layoutWorkingDialog = - new LayoutWorkingDialog(viewer.getVisualizationModel().getLayoutAlgorithm()); + new LayoutWorkingDialog(viewer.getVisualizationModel().getLayoutAlgorithm()); componentProvider.getTool().showDialog(layoutWorkingDialog); } @@ -465,14 +439,10 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.repaint(); } - /** - * from the supplied {@link Graph}, create a new GraphDisplay in a new window or tab - * @param subGraph - * @throws CancelledException - */ - private void displaySubGraph(Graph subGraph) throws CancelledException { + private void displaySubGraph(Graph subGraph) + throws CancelledException { 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); } @@ -485,28 +455,28 @@ public class DefaultGraphDisplay implements GraphDisplay { VisualizationViewer parentViewer) { Dimension viewerSize = parentViewer.getSize(); Dimension satelliteSize = new Dimension( - viewerSize.width / 4, viewerSize.height / 4); - final SatelliteVisualizationViewer satelliteViewer = + viewerSize.width / 4, viewerSize.height / 4); + final SatelliteVisualizationViewer satellite = SatelliteVisualizationViewer.builder(parentViewer) .viewSize(satelliteSize) .build(); - satelliteViewer.setGraphMouse(new DefaultSatelliteGraphMouse()); - satelliteViewer.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor); - satelliteViewer.getRenderContext() + satellite.setGraphMouse(new DefaultSatelliteGraphMouse()); + satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor); + satellite.getRenderContext() .setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke); - satelliteViewer.getRenderContext().setVertexFillPaintFunction(Colors::getColor); - satelliteViewer.scaleToLayout(); - satelliteViewer.getRenderContext().setVertexLabelFunction(n -> null); - satelliteViewer.getComponent().setBorder(BorderFactory.createEtchedBorder()); + satellite.getRenderContext().setVertexFillPaintFunction(Colors::getColor); + satellite.scaleToLayout(); + satellite.getRenderContext().setVertexLabelFunction(n -> null); + satellite.getComponent().setBorder(BorderFactory.createEtchedBorder()); parentViewer.getComponent().addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { Dimension size = evt.getComponent().getSize(); Dimension quarterSize = new Dimension(size.width / 4, size.height / 4); - satelliteViewer.getComponent().setSize(quarterSize); + satellite.getComponent().setSize(quarterSize); } }); - return satelliteViewer; + return satellite; } /** @@ -532,14 +502,14 @@ public class DefaultGraphDisplay implements GraphDisplay { } this.listener = listener; DefaultGraphMouse graphMouse = - GhidraGraphMouse.builder() - .viewer(viewer) - .subgraphConsumer(subgraphConsumer) - .locatedVertexConsumer(this::setLocatedVertex) - .graphDisplayListener(listener) - .vertexIdFunction(AttributedVertex::getId) - .vertexNameFunction(AttributedVertex::getName) - .build(); + GhidraGraphMouse. builder() + .viewer(viewer) + .subgraphConsumer(subgraphConsumer) + .locatedVertexConsumer(this::setFocusedVertex) + .graphDisplayListener(listener) + .vertexIdFunction(AttributedVertex::getId) + .vertexNameFunction(AttributedVertex::getName) + .build(); viewer.setGraphMouse(graphMouse); } @@ -547,54 +517,55 @@ public class DefaultGraphDisplay implements GraphDisplay { * connect the selection state to to the visualization */ private void connectSelectionStateListeners() { - viewer.getSelectedVertexState().addItemListener(e -> Swing.runLater(() -> { - // there was a change in the set of selected vertices. - // if the locatedVertex is null, set it from one of the selected - // vertices - if (e.getStateChange() == ItemEvent.SELECTED) { - Collection selectedVertices = getVertices(e.getItem()); - List selectedVertexIds = toVertexIds(selectedVertices); - notifySelectionChanged(selectedVertexIds); - - if (selectedVertices.size() == 1) { - // if only one vertex was selected, make it the locatedVertex - 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)); - } - } - else if (e.getStateChange() == ItemEvent.DESELECTED) { - notifySelectionChanged(Collections.emptyList()); - } - viewer.repaint(); - })); + switchableSelectionListener = new SwitchableSelectionItemListener(); + viewer.getSelectedVertexState().addItemListener(switchableSelectionListener); } - /** - * set the vertex that has been nominated to be 'located' - * @param vertex the lucky vertex - */ - protected void setLocatedVertex(AttributedVertex vertex) { - boolean changed = this.locatedVertex != vertex; - this.locatedVertex = vertex; - if (locatedVertex != null && changed) { - notifyLocationChanged(locatedVertex.getId()); - scrollToSelected(locatedVertex); + protected void setFocusedVertex(AttributedVertex vertex) { + setFocusedVertex(vertex, EventTrigger.API_CALL); + } + + 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()); + } + // make sure the vertex is visible, even if the vertex has not changed + scrollToSelected(focusedVertex); viewer.repaint(); } } /** - * transform the supplied {@code AttributedVertex} Set members to a List of their ids - * @param selectedVertices - * @return + * determines whether the passed layout coordinates are visible in the display + * @param x of interest (layout coordinates) + * @param y of interest (layout coordinates) + * @return {@code true} if the coordinates are visible in the display view + */ + private boolean isVisible(double x, double y) { + if (viewer.getComponent().isVisible() && !viewer.getBounds().isEmpty()) { + // project the view bounds into the layout coordinate system, test for containing the coordinates + return viewer.getRenderContext() + .getMultiLayerTransformer() + .inverseTransform(viewer.getBounds()) + .getBounds() + .contains(x, y); + } + return true; + } + + /** + * transform the supplied {@code AttributedVertex}s to a List of their ids + * @param selectedVertices the collections of vertices. + * @return a list of vertex ids */ private List toVertexIds(Collection selectedVertices) { return selectedVertices.stream().map(AttributedVertex::getId).collect(Collectors.toList()); } + @SuppressWarnings("unchecked") private Collection getVertices(Object item) { if (item instanceof Collection) { return (Collection) item; @@ -606,38 +577,45 @@ public class DefaultGraphDisplay implements GraphDisplay { } /** - * fire an event to say the selected vertices changed - * @param vertexIds + * fire an event to notify the selected vertices changed + * @param vertexIds the list of vertexes */ private void notifySelectionChanged(List vertexIds) { Swing.runLater(() -> listener.selectionChanged(vertexIds)); } /** - * fire and event to say the located vertex changed - * @param vertexId + * fire and event to say the focused vertex changed + * @param vertexId the id of the focused vertex */ - private void notifyLocationChanged(String vertexId) { - Swing.runLater(() -> listener.locationChanged(vertexId)); + private void notifyLocationFocusChanged(String 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 - public void selectVertices(List vertexIdList) { - MutableSelectedState nodeSelectedState = viewer.getSelectedVertexState(); - Set selected = getVertices(vertexIdList); - if (vertexIdList.isEmpty()) { - nodeSelectedState.clear(); + public void selectVertices(List vertexIdList, EventTrigger eventTrigger) { + // 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 nodeSelectedState = + viewer.getSelectedVertexState(); + Set selected = getVertices(vertexIdList); + if (vertexIdList.isEmpty()) { + nodeSelectedState.clear(); + } + else if (!Arrays.asList(nodeSelectedState.getSelectedObjects()).containsAll(selected)) { + nodeSelectedState.clear(); + nodeSelectedState.select(selected, false); + scrollToSelected(selected); + } + viewer.repaint(); } - else if (!Arrays.asList(nodeSelectedState.getSelectedObjects()).containsAll(selected)) { - nodeSelectedState.clear(); - nodeSelectedState.select(selected, false); - scrollToSelected(selected); + finally { + // always turn on the selection listener + switchableSelectionListener.setEnabled(true); } - viewer.repaint(); } /** @@ -653,20 +631,14 @@ public class DefaultGraphDisplay implements GraphDisplay { .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 - public void setLocation(String vertexID) { - Optional located = + public void setLocationFocus(String vertexID, EventTrigger eventTrigger) { + Optional vertexToFocus = 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(); - located.ifPresent(v -> { - setLocatedVertex(v); - scrollToSelected(v); + vertexToFocus.ifPresent(v -> { + setFocusedVertex(v, eventTrigger); }); viewer.repaint(); } @@ -679,16 +651,16 @@ public class DefaultGraphDisplay implements GraphDisplay { graph = attributedGraph; layoutTransitionManager.setEdgeComparator(new EdgeComparator(graph, "EdgeType", - DefaultGraphDisplay.FAVORED_EDGE)); + DefaultGraphDisplay.FAVORED_EDGE)); configureViewerPreferredSize(); - Swing.runNow(() -> { + Swing.runNow(() -> { // set the graph but defer the layoutalgorithm setting viewer.getVisualizationModel().setGraph(graph, false); configureFilters(); LayoutAlgorithm initialLayoutAlgorithm = - layoutTransitionManager.getInitialLayoutAlgorithm(); + layoutTransitionManager.getInitialLayoutAlgorithm(); viewer.getVisualizationModel().setLayoutAlgorithm(initialLayoutAlgorithm); }); componentProvider.setVisible(true); @@ -818,7 +790,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } private AttributedGraph mergeGraphs(AttributedGraph newGraph, AttributedGraph oldGraph) { - for (AttributedVertex vertex : oldGraph.vertexSet()) { + for (AttributedVertex vertex : oldGraph.vertexSet()) { newGraph.addVertex(vertex); } for (AttributedEdge edge : oldGraph.edgeSet()) { @@ -854,23 +826,24 @@ public class DefaultGraphDisplay implements GraphDisplay { * @param vertices the vertices to center */ void scrollToSelected(Collection vertices) { + if (ensureVertexIsVisible) { + jobRunner.finishAllJobs(); - if (!enableScrollToSelection) { - return; + Point2D newCenter = getPointToCenter(vertices); + if (!isVisible(newCenter.getX(), newCenter.getY())) { + Point2D existingCenter = viewer.getRenderContext() + .getMultiLayerTransformer() + .inverseTransform(viewer.getCenter()); + jobRunner.schedule(new CenterAnimationJob(viewer, existingCenter, newCenter)); + } } - - Point2D newCenter = getPointToCenter(vertices); - Point2D existingCenter = viewer.getRenderContext() - .getMultiLayerTransformer() - .inverseTransform(viewer.getCenter()); - jobRunner.schedule(new CenterAnimation<>(viewer, existingCenter, newCenter)); } - /**w + /** * scroll the visualization to center the passed vertex * @param vertex the vertex to center */ - void scrollToSelected(AttributedVertex vertex) { + private void scrollToSelected(AttributedVertex vertex) { List vertices = vertex == null ? Collections.emptyList() : List.of(vertex); scrollToSelected(vertices); @@ -908,8 +881,10 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public void updateVertexName(String id, String newName) { // find the vertex, if present, change the name - Optional optional = graph.vertexSet().stream() - .filter(v -> v.getId().equals(id)).findFirst(); + Optional optional = graph.vertexSet() + .stream() + .filter(v -> v.getId().equals(id)) + .findFirst(); if (optional.isPresent()) { AttributedVertex vertex = optional.get(); vertex.setName(newName); @@ -930,13 +905,14 @@ public class DefaultGraphDisplay implements GraphDisplay { /** * create and return a {@link VisualizationViewer} to display graphs - * @return + * @return the new VisualizationViewer */ public VisualizationViewer createViewer() { final VisualizationViewer vv = VisualizationViewer. builder() - .multiSelectionStrategySupplier(() -> freeFormSelection ? - MultiSelectionStrategy.arbitrary() : MultiSelectionStrategy.rectangular()) + .multiSelectionStrategySupplier( + () -> freeFormSelection ? MultiSelectionStrategy.arbitrary() + : MultiSelectionStrategy.rectangular()) .viewSize(PREFERRED_VIEW_SIZE) .layoutSize(PREFERRED_LAYOUT_SIZE) .build(); @@ -955,12 +931,12 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public void ancestorRemoved(AncestorEvent ancestorEvent) { - + // do nothing } @Override public void ancestorMoved(AncestorEvent ancestorEvent) { - + // do nothing } }); @@ -981,13 +957,15 @@ public class DefaultGraphDisplay implements GraphDisplay { renderContext.setVertexIconFunction(iconCache::get); 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. // if both endpoints of an edge are selected, select that edge. vv.setSelectedEdgeState( - new VertexEndpointsSelectedEdgeSelectedState<>(vv.getVisualizationModel()::getGraph, - vv.getSelectedVertexState())); + new VertexEndpointsSelectedEdgeSelectedState<>(vv.getVisualizationModel()::getGraph, + vv.getSelectedVertexState())); // selected edges will be drawn with a wider stroke renderContext.setEdgeStrokeFunction( @@ -998,11 +976,11 @@ public class DefaultGraphDisplay implements GraphDisplay { e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red : Colors.getColor(e)); renderContext.setArrowDrawPaintFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red - : Colors.getColor(e)); + e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red + : Colors.getColor(e)); renderContext.setArrowFillPaintFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red - : Colors.getColor(e)); + e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red + : Colors.getColor(e)); vv.setToolTipText(""); // assign the shapes to the modal renderer @@ -1030,4 +1008,49 @@ public class DefaultGraphDisplay implements GraphDisplay { vv.setBackground(Color.WHITE); 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 selectedVertices = getVertices(e.getItem()); + List 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; + } + + } } 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 469e076399..a37f94c048 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,21 +15,14 @@ */ package ghidra.graph.visualization; -import org.jungrapht.visualization.layout.algorithms.BalloonLayoutAlgorithm; -import org.jungrapht.visualization.layout.algorithms.CircleLayoutAlgorithm; -import org.jungrapht.visualization.layout.algorithms.EiglspergerLayoutAlgorithm; -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 java.util.function.Function; + +import org.jungrapht.visualization.layout.algorithms.*; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; 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 @@ -38,8 +31,8 @@ import java.util.function.Function; * This class provides LayoutAlgorithm builders instead of LayoutAlgorithms because some LayoutAlgorithms * accumulate state information (so are used only one time). */ -class LayoutFunction - implements Function> { +class LayoutFunction + implements Function> { static final String KAMADA_KAWAI = "Force Balanced"; static final String FRUCTERMAN_REINGOLD = "Force Directed"; @@ -64,51 +57,53 @@ class LayoutFunction } @Override - public LayoutAlgorithm.Builder apply(String name) { + public LayoutAlgorithm.Builder apply(String name) { switch(name) { case GEM: return GEMLayoutAlgorithm.edgeAwareBuilder(); case KAMADA_KAWAI: - return KKLayoutAlgorithm. builder() + return KKLayoutAlgorithm. builder() .preRelaxDuration(1000); case FRUCTERMAN_REINGOLD: - return FRLayoutAlgorithm. builder() + return FRLayoutAlgorithm. builder() .repulsionContractBuilder(BarnesHutFRRepulsion.builder()); case CIRCLE_MINCROSS: - return CircleLayoutAlgorithm. builder() + return CircleLayoutAlgorithm. builder() .reduceEdgeCrossing(true); case TIDIER_RADIAL_TREE: - return TidierRadialTreeLayoutAlgorithm. edgeAwareBuilder(); + return TidierRadialTreeLayoutAlgorithm + . edgeAwareBuilder(); case MIN_CROSS_TOP_DOWN: return EiglspergerLayoutAlgorithm - . edgeAwareBuilder() + . edgeAwareBuilder() .layering(Layering.TOP_DOWN); case MIN_CROSS_LONGEST_PATH: return EiglspergerLayoutAlgorithm - . edgeAwareBuilder() + . edgeAwareBuilder() .layering(Layering.LONGEST_PATH); case MIN_CROSS_NETWORK_SIMPLEX: return EiglspergerLayoutAlgorithm - . edgeAwareBuilder() + . edgeAwareBuilder() .layering(Layering.NETWORK_SIMPLEX); case MIN_CROSS_COFFMAN_GRAHAM: return EiglspergerLayoutAlgorithm - . edgeAwareBuilder() + . edgeAwareBuilder() .layering(Layering.COFFMAN_GRAHAM); case RADIAL: return RadialTreeLayoutAlgorithm - . builder() + . builder() .verticalVertexSpacing(300); case BALLOON: return BalloonLayoutAlgorithm - . builder() + . builder() .verticalVertexSpacing(300); case TREE: return TreeLayoutAlgorithm .builder(); case TIDIER_TREE: default: - return TidierTreeLayoutAlgorithm. edgeAwareBuilder(); + return TidierTreeLayoutAlgorithm + . edgeAwareBuilder(); } } } 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 1eb49bdbc4..8f89c5d3f6 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 @@ -15,61 +15,57 @@ */ package ghidra.graph.visualization; -import org.jungrapht.visualization.RenderContext; -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 static ghidra.graph.visualization.LayoutFunction.*; import java.util.Comparator; import java.util.function.Function; 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 */ -class LayoutTransitionManager { +class LayoutTransitionManager { LayoutFunction layoutFunction = new LayoutFunction(); /** * the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm} */ - VisualizationServer visualizationServer; + VisualizationServer visualizationServer; /** * a {@link Predicate} to assist in determining which vertices are root vertices (for Tree layouts) */ - Predicate rootPredicate; + Predicate rootPredicate; /** * a {@link Comparator} to sort edges during layout graph traversal */ - Comparator edgeComparator = (e1, e2) -> 0; + Comparator edgeComparator = (e1, e2) -> 0; /** * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices */ - Function vertexBoundsFunction; + Function vertexBoundsFunction; /** * the {@link RenderContext} used to draw the graph */ - RenderContext renderContext; + RenderContext renderContext; - LayoutPaintable.BalloonRings balloonLayoutRings; + LayoutPaintable.BalloonRings balloonLayoutRings; - LayoutPaintable.RadialRings radialLayoutRings; + LayoutPaintable.RadialRings radialLayoutRings; /** @@ -78,8 +74,8 @@ class LayoutTransitionManager { * @param rootPredicate selects root vertices */ public LayoutTransitionManager( - VisualizationServer visualizationServer, - Predicate rootPredicate) { + VisualizationServer visualizationServer, + Predicate rootPredicate) { this.visualizationServer = visualizationServer; this.rootPredicate = rootPredicate; @@ -87,7 +83,7 @@ class LayoutTransitionManager { this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); } - public void setEdgeComparator(Comparator edgeComparator) { + public void setEdgeComparator(Comparator edgeComparator) { this.edgeComparator = edgeComparator; } @@ -97,19 +93,19 @@ class LayoutTransitionManager { */ @SuppressWarnings("unchecked") public void setLayout(String layoutName) { - LayoutAlgorithm.Builder builder = layoutFunction.apply(layoutName); - LayoutAlgorithm layoutAlgorithm = builder.build(); + LayoutAlgorithm.Builder builder = layoutFunction.apply(layoutName); + LayoutAlgorithm layoutAlgorithm = builder.build(); if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) { - ((VertexBoundsFunctionConsumer) layoutAlgorithm) + ((VertexBoundsFunctionConsumer) layoutAlgorithm) .setVertexBoundsFunction(vertexBoundsFunction); } if (layoutAlgorithm instanceof Layered) { - ((Layered)layoutAlgorithm) + ((Layered) layoutAlgorithm) .setMaxLevelCrossFunction(g -> Math.max(1, Math.min(10, 500 / g.vertexSet().size()))); } if (layoutAlgorithm instanceof TreeLayout) { - ((TreeLayout) layoutAlgorithm).setRootPredicate(rootPredicate); + ((TreeLayout) layoutAlgorithm).setRootPredicate(rootPredicate); } // remove any previously added layout paintables removePaintable(radialLayoutRings); @@ -117,18 +113,19 @@ class LayoutTransitionManager { if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) { balloonLayoutRings = new LayoutPaintable.BalloonRings<>( - visualizationServer, (BalloonLayoutAlgorithm) layoutAlgorithm); + visualizationServer, + (BalloonLayoutAlgorithm) layoutAlgorithm); visualizationServer.addPreRenderPaintable(balloonLayoutRings); } if (layoutAlgorithm instanceof RadialTreeLayout) { radialLayoutRings = new LayoutPaintable.RadialRings<>( - visualizationServer, (RadialTreeLayout) layoutAlgorithm); + visualizationServer, (RadialTreeLayout) layoutAlgorithm); visualizationServer.addPreRenderPaintable(radialLayoutRings); } if (layoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) layoutAlgorithm).setEdgeComparator(edgeComparator); + ((EdgeSorting) layoutAlgorithm).setEdgeComparator(edgeComparator); } LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm); } @@ -140,22 +137,22 @@ class LayoutTransitionManager { } @SuppressWarnings("unchecked") - public LayoutAlgorithm getInitialLayoutAlgorithm() { - LayoutAlgorithm initialLayoutAlgorithm = + public LayoutAlgorithm getInitialLayoutAlgorithm() { + LayoutAlgorithm initialLayoutAlgorithm = layoutFunction.apply(TIDIER_TREE).build(); if (initialLayoutAlgorithm instanceof TreeLayout) { - ((TreeLayout) initialLayoutAlgorithm) + ((TreeLayout) initialLayoutAlgorithm) .setRootPredicate(rootPredicate); - ((TreeLayout) initialLayoutAlgorithm) + ((TreeLayout) initialLayoutAlgorithm) .setVertexBoundsFunction(vertexBoundsFunction); } if (initialLayoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) initialLayoutAlgorithm) + ((EdgeSorting) initialLayoutAlgorithm) .setEdgeComparator(edgeComparator); } if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) { - ((VertexBoundsFunctionConsumer) initialLayoutAlgorithm) + ((VertexBoundsFunctionConsumer) initialLayoutAlgorithm) .setVertexBoundsFunction(vertexBoundsFunction); } return initialLayoutAlgorithm; diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java index d69d045693..070661d4d7 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java @@ -18,6 +18,7 @@ package ghidra.graph.program; import java.awt.Color; import java.util.*; +import docking.widgets.EventTrigger; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -156,12 +157,15 @@ public class BlockGraphTask extends Task { display.setGraph(graph, actionName, appendGraph, monitor); 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()) { List selectedVertices = listener.getVertices(selection); 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); } } } diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java index 8b9159f068..0aab448826 100644 --- a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java +++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java @@ -17,6 +17,7 @@ package ghidra.graph.program; import java.util.*; +import docking.widgets.EventTrigger; import ghidra.service.graph.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -37,7 +38,7 @@ public class TestGraphDisplay implements GraphDisplay { } @Override - public void setLocation(String vertexID) { + public void setLocationFocus(String vertexID, EventTrigger eventTrigger) { currentFocusedVertex = vertexID; } @@ -46,7 +47,7 @@ public class TestGraphDisplay implements GraphDisplay { } @Override - public void selectVertices(List vertexList) { + public void selectVertices(List vertexList, EventTrigger eventTrigger) { currentSelection = vertexList; } @@ -103,7 +104,7 @@ public class TestGraphDisplay implements GraphDisplay { } public void focusChanged(String vertexId) { - listener.locationChanged(vertexId); + listener.locationFocusChanged(vertexId); } public void selectionChanged(List vertexIds) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/DummyGraphDisplayListener.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/DummyGraphDisplayListener.java index 5b388dd52d..97d9f7d685 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/DummyGraphDisplayListener.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/DummyGraphDisplayListener.java @@ -30,7 +30,7 @@ public class DummyGraphDisplayListener implements GraphDisplayListener { } @Override - public void locationChanged(String vertexId) { + public void locationFocusChanged(String vertexId) { // I'm a dummy } 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 89fdab32e6..de812e402a 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 @@ -17,6 +17,7 @@ package ghidra.service.graph; import java.util.List; +import docking.widgets.EventTrigger; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -41,18 +42,31 @@ public interface GraphDisplay { 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 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 * * @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 vertexList); + public void selectVertices(List vertexList, EventTrigger eventTrigger); /** * Closes this graph display window. @@ -103,7 +117,7 @@ public interface GraphDisplay { /** * 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 */ public void updateVertexName(String id, String newName); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java index c84e695d10..c3764a28ab 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java @@ -37,7 +37,7 @@ public interface GraphDisplayListener { * Notification that the "focused" (active) vertex has changed. * @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) { // no op