From f9542551d59f33ad7e31787dbfed3ea2ff87bc2e Mon Sep 17 00:00:00 2001 From: ghidravore Date: Mon, 31 Aug 2020 13:21:28 -0400 Subject: [PATCH] popup for edge selection --- .../visualization/DefaultGraphDisplay.java | 18 +-- .../graph/visualization/GhidraGraphMouse.java | 70 ++++++----- .../GhidraGraphMouseBuilder.java | 116 ++++++++++++++++++ .../visualization/OnEdgeSelectionMenu.java | 64 ++++++++++ .../visualization/OnVertexSelectionMenu.java | 4 +- 5 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouseBuilder.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnEdgeSelectionMenu.java 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 31b61bb5bb..8403361533 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 @@ -96,7 +96,7 @@ import java.util.function.Consumer; import java.util.logging.Logger; import java.util.stream.Collectors; -import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*; +import static org.jungrapht.visualization.MultiLayerTransformer.Layer.VIEW; import static org.jungrapht.visualization.renderers.BiModalRenderer.LIGHTWEIGHT; /** @@ -532,11 +532,14 @@ public class DefaultGraphDisplay implements GraphDisplay { } this.listener = listener; DefaultGraphMouse graphMouse = - new GhidraGraphMouse<>(viewer, - subgraphConsumer, - listener, - AttributedVertex::getId, - AttributedVertex::getName); + GhidraGraphMouse.builder() + .viewer(viewer) + .subgraphConsumer(subgraphConsumer) + .locatedVertexConsumer(this::setLocatedVertex) + .graphDisplayListener(listener) + .vertexIdFunction(AttributedVertex::getId) + .vertexNameFunction(AttributedVertex::getName) + .build(); viewer.setGraphMouse(graphMouse); } @@ -578,6 +581,8 @@ public class DefaultGraphDisplay implements GraphDisplay { this.locatedVertex = vertex; if (locatedVertex != null && changed) { notifyLocationChanged(locatedVertex.getId()); + scrollToSelected(locatedVertex); + viewer.repaint(); } } @@ -858,7 +863,6 @@ public class DefaultGraphDisplay implements GraphDisplay { Point2D existingCenter = viewer.getRenderContext() .getMultiLayerTransformer() .inverseTransform(viewer.getCenter()); - jobRunner.schedule(new CenterAnimation<>(viewer, existingCenter, newCenter)); } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java index 06a2b241b1..dff9e24479 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java @@ -22,14 +22,12 @@ import org.jungrapht.visualization.control.AbstractPopupGraphMousePlugin; import org.jungrapht.visualization.control.DefaultGraphMouse; import org.jungrapht.visualization.control.GraphElementAccessor; import org.jungrapht.visualization.control.TransformSupport; -import org.jungrapht.visualization.control.VertexSelectingGraphMousePlugin; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.selection.ShapePickSupport; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -57,6 +55,11 @@ public class GhidraGraphMouse extends DefaultGraphMouse { * will accept a {@link Graph} and display it in a new tab or window */ Consumer> subgraphConsumer; + + /** + * Accepts a vertex that was 'located' + */ + Consumer locatedVertexConsumer; /** * a listener for events, notably the event to request change of a vertex name */ @@ -70,20 +73,27 @@ public class GhidraGraphMouse extends DefaultGraphMouse { */ Function vertexNameFunction; + /** + * create an instance + * @param vertex type + * @param edge type + * @return a configured GhidraGraphMouseBuilder + */ + public static GhidraGraphMouseBuilder builder() { + return new GhidraGraphMouseBuilder<>(); + } + /** * create an instance with default values */ - public GhidraGraphMouse(VisualizationViewer viewer, - Consumer> subgraphConsumer, - GraphDisplayListener graphDisplayListener, - Function vertexIdFunction, - Function vertexNameFunction) { - super(DefaultGraphMouse.builder().vertexSelectionOnly(true)); - this.viewer = viewer; - this.subgraphConsumer = subgraphConsumer; - this.graphDisplayListener = graphDisplayListener; - this.vertexIdFunction = vertexIdFunction; - this.vertexNameFunction = vertexNameFunction; + GhidraGraphMouse(GhidraGraphMouseBuilder builder) { + super(builder.vertexSelectionOnly(true)); + this.viewer = builder.viewer; + this.subgraphConsumer = builder.subgraphConsumer; + this.locatedVertexConsumer = builder.locatedVertexConsumer; + this.graphDisplayListener = builder.graphDisplayListener; + this.vertexIdFunction = builder.vertexIdFunction; + this.vertexNameFunction = builder.vertexNameFunction; } /** @@ -91,32 +101,15 @@ public class GhidraGraphMouse extends DefaultGraphMouse { */ @Override public void loadPlugins() { - add(new PopupPlugin<>(viewer, subgraphConsumer, graphDisplayListener, - vertexIdFunction, vertexNameFunction)); + add(new PopupPlugin()); super.loadPlugins(); } - static class PopupPlugin extends AbstractPopupGraphMousePlugin { + class PopupPlugin extends AbstractPopupGraphMousePlugin { - VisualizationViewer viewer; - Consumer> subgraphConsumer; - GraphDisplayListener graphDisplayListener; - Function vertexIdFunction; - Function vertexNameFunction; SelectionFilterMenu selectionFilterMenu; - PopupPlugin(VisualizationViewer viewer, - Consumer> subgraphConsumer, - GraphDisplayListener graphDisplayListener, - Function vertexIdFunction, - Function vertexNameFunction - - ) { - this.viewer = viewer; - this.subgraphConsumer = subgraphConsumer; - this.graphDisplayListener = graphDisplayListener; - this.vertexIdFunction = vertexIdFunction; - this.vertexNameFunction = vertexNameFunction; + PopupPlugin() { this.selectionFilterMenu = new SelectionFilterMenu<>(viewer, subgraphConsumer); } @@ -133,14 +126,21 @@ public class GhidraGraphMouse extends DefaultGraphMouse { LayoutModel layoutModel = viewer.getVisualizationModel().getLayoutModel(); GraphElementAccessor pickSupport = viewer.getPickSupport(); V pickedVertex; + E pickedEdge = null; if (pickSupport instanceof ShapePickSupport) { ShapePickSupport shapePickSupport = (ShapePickSupport) pickSupport; pickedVertex = shapePickSupport.getVertex(layoutModel, footprintRectangle); + if (pickedVertex == null) { + pickedEdge = shapePickSupport.getEdge(layoutModel, footprintRectangle); + } } else { TransformSupport transformSupport = viewer.getTransformSupport(); Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint()); pickedVertex = pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY()); + if (pickedVertex == null) { + pickedEdge = pickSupport.getEdge(layoutModel, layoutPoint.getX(), layoutPoint.getY()); + } } if (pickedVertex != null) { OnVertexSelectionMenu menu = @@ -148,6 +148,10 @@ public class GhidraGraphMouse extends DefaultGraphMouse { vertexIdFunction, vertexNameFunction, pickedVertex); menu.show(viewer.getComponent(), e.getX(), e.getY()); + } else if (pickedEdge != null) { + OnEdgeSelectionMenu menu = + new OnEdgeSelectionMenu<>(viewer, locatedVertexConsumer, pickedEdge); + menu.show(viewer.getComponent(), e.getX(), e.getY()); } else { selectionFilterMenu.show(viewer.getComponent(), e.getX(), e.getY()); } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouseBuilder.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouseBuilder.java new file mode 100644 index 0000000000..f48a7e6709 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouseBuilder.java @@ -0,0 +1,116 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization; + +import ghidra.service.graph.GraphDisplayListener; +import org.jgrapht.Graph; +import org.jungrapht.visualization.VisualizationViewer; +import org.jungrapht.visualization.control.DefaultGraphMouse; + +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.jungrapht.visualization.VisualizationServer.PREFIX; + +/** + * An extension of the jungrapht DefaultGraphMouse.Builder. This class has references to + *
    + *
  • a {@link VisualizationViewer} (to access the Graph and LayoutModel) + *
  • a {@link Consumer} of the Subgraph (to make new Graph displays) + *
  • a {@link GraphDisplayListener} (to react to changes in node attributes) + *
  • a {@code Function} to supply the id for a given vertex + *
  • a {@code Function} to supply the name for a given vertex + * + */ +public class GhidraGraphMouseBuilder, B extends GhidraGraphMouseBuilder> + extends DefaultGraphMouse.Builder { + + private static final String PICK_AREA_SIZE = PREFIX + "pickAreaSize"; + + /** + * holds the context for graph visualization + */ + VisualizationViewer viewer; + + /** + * will accept a {@link Graph} and display it in a new tab or window + */ + Consumer> subgraphConsumer; + + /** + * accepts a 'located' vertex via a menu driven action + */ + Consumer locatedVertexConsumer; + + /** + * a listener for events, notably the event to request change of a vertex name + */ + GraphDisplayListener graphDisplayListener; + + /** + * supplies the id for a given vertex + */ + Function vertexIdFunction; + + /** + * supplies the name for a given vertex + */ + Function vertexNameFunction; + + public B self() { + return (B) this; + } + + public B viewer(VisualizationViewer viewer) { + this.viewer = viewer; + return self(); + } + + public B subgraphConsumer(Consumer> subgraphConsumer) { + this.subgraphConsumer = subgraphConsumer; + return self(); + } + + public B locatedVertexConsumer(Consumer locatedVertexConsumer) { + this.locatedVertexConsumer = locatedVertexConsumer; + return self(); + } + + public B graphDisplayListener(GraphDisplayListener graphDisplayListener) { + this.graphDisplayListener = graphDisplayListener; + return self(); + } + + public B vertexIdFunction(Function vertexIdFunction) { + this.vertexIdFunction = vertexIdFunction; + return self(); + } + + public B vertexNameFunction(Function vertexNameFunction) { + this.vertexNameFunction = vertexIdFunction; + return self(); + } + + public T build() { + return (T) new GhidraGraphMouse(this); + } + + public static GhidraGraphMouseBuilder builder() { + return new GhidraGraphMouseBuilder<>(); + } +} + + diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnEdgeSelectionMenu.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnEdgeSelectionMenu.java new file mode 100644 index 0000000000..ceda4b173b --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnEdgeSelectionMenu.java @@ -0,0 +1,64 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization; + +import org.jgrapht.Graph; +import org.jungrapht.visualization.VisualizationViewer; + +import javax.swing.AbstractButton; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import java.util.Set; +import java.util.function.Consumer; + +/** + * a Popup menu to allow actions relative to a particular edge. + * The popup appears on a right click over an edge in the display. + * The user can select/deselect the edge. When the edge is selected, + * its endpoints are also selected and the target endpoint is 'located' + */ +public class OnEdgeSelectionMenu extends JPopupMenu { + + public OnEdgeSelectionMenu(VisualizationViewer visualizationViewer, + Consumer locatedVertexConsumer, + E edge) { + Graph graph = visualizationViewer.getVisualizationModel().getGraph(); + V source = graph.getEdgeSource(edge); + V target = graph.getEdgeTarget(edge); + AbstractButton selectButton = new JMenuItem("Select Edge"); + AbstractButton deselectButton = new JMenuItem("Deselect Edge"); + selectButton.addActionListener(evt -> { + visualizationViewer.getSelectedEdgeState().select(edge); + visualizationViewer.getSelectedVertexState().select(Set.of(source, target)); + locatedVertexConsumer.accept(target); + }); + deselectButton.addActionListener(evt -> { + visualizationViewer.getSelectedEdgeState().deselect(edge); + visualizationViewer.getSelectedVertexState().deselect(Set.of(source, target)); + }); + add(visualizationViewer.getSelectedEdgeState().isSelected(edge) ? deselectButton : selectButton); + AbstractButton locateSourceButton = new JMenuItem("Locate Edge Source"); + locateSourceButton.addActionListener(evt -> { + locatedVertexConsumer.accept(source); + }); + AbstractButton locateTargetButton = new JMenuItem("Locate Edge Target"); + locateTargetButton.addActionListener(evt -> { + locatedVertexConsumer.accept(target); + }); + add(locateSourceButton); + add(locateTargetButton); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnVertexSelectionMenu.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnVertexSelectionMenu.java index d4b4979a17..061b03b219 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnVertexSelectionMenu.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/OnVertexSelectionMenu.java @@ -41,8 +41,8 @@ public class OnVertexSelectionMenu extends JPopupMenu { Function vertexIdFunction, Function vertexNameFunction, V vertex) { - AbstractButton selectButton = new JMenuItem("Select"); - AbstractButton deselectButton = new JMenuItem("Deselect"); + AbstractButton selectButton = new JMenuItem("Select Vertex"); + AbstractButton deselectButton = new JMenuItem("Deselect Vertex"); AbstractButton renameAttributeButton = new JMenuItem("Rename vertex"); renameAttributeButton.addActionListener(evt -> { String newName = JOptionPane.showInputDialog("New Name Attribute");