popup for edge selection

This commit is contained in:
ghidravore 2020-08-31 13:21:28 -04:00
parent f92c5a53bd
commit f9542551d5
5 changed files with 230 additions and 42 deletions

View file

@ -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<AttributedVertex, AttributedEdge> graphMouse =
new GhidraGraphMouse<>(viewer,
subgraphConsumer,
listener,
AttributedVertex::getId,
AttributedVertex::getName);
GhidraGraphMouse.<AttributedVertex, AttributedEdge>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));
}

View file

@ -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<V, E> extends DefaultGraphMouse<V, E> {
* will accept a {@link Graph} and display it in a new tab or window
*/
Consumer<Graph<V, E>> subgraphConsumer;
/**
* Accepts a vertex that was 'located'
*/
Consumer<V> locatedVertexConsumer;
/**
* a listener for events, notably the event to request change of a vertex name
*/
@ -70,20 +73,27 @@ public class GhidraGraphMouse<V, E> extends DefaultGraphMouse<V, E> {
*/
Function<V, String> vertexNameFunction;
/**
* create an instance
* @param <V> vertex type
* @param <E> edge type
* @return a configured GhidraGraphMouseBuilder
*/
public static <V, E> GhidraGraphMouseBuilder<V, E, ?, ?> builder() {
return new GhidraGraphMouseBuilder<>();
}
/**
* create an instance with default values
*/
public GhidraGraphMouse(VisualizationViewer<V, E> viewer,
Consumer<Graph<V, E>> subgraphConsumer,
GraphDisplayListener graphDisplayListener,
Function<V, String> vertexIdFunction,
Function<V, String> vertexNameFunction) {
super(DefaultGraphMouse.<V, E>builder().vertexSelectionOnly(true));
this.viewer = viewer;
this.subgraphConsumer = subgraphConsumer;
this.graphDisplayListener = graphDisplayListener;
this.vertexIdFunction = vertexIdFunction;
this.vertexNameFunction = vertexNameFunction;
GhidraGraphMouse(GhidraGraphMouseBuilder<V, E, ?, ?> 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<V, E> extends DefaultGraphMouse<V, E> {
*/
@Override
public void loadPlugins() {
add(new PopupPlugin<>(viewer, subgraphConsumer, graphDisplayListener,
vertexIdFunction, vertexNameFunction));
add(new PopupPlugin());
super.loadPlugins();
}
static class PopupPlugin<V, E> extends AbstractPopupGraphMousePlugin {
class PopupPlugin extends AbstractPopupGraphMousePlugin {
VisualizationViewer<V, E> viewer;
Consumer<Graph<V, E>> subgraphConsumer;
GraphDisplayListener graphDisplayListener;
Function<V, String> vertexIdFunction;
Function<V, String> vertexNameFunction;
SelectionFilterMenu<V, E> selectionFilterMenu;
PopupPlugin(VisualizationViewer<V, E> viewer,
Consumer<Graph<V, E>> subgraphConsumer,
GraphDisplayListener graphDisplayListener,
Function<V, String> vertexIdFunction,
Function<V, String> 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<V, E> extends DefaultGraphMouse<V, E> {
LayoutModel<V> layoutModel = viewer.getVisualizationModel().getLayoutModel();
GraphElementAccessor<V, E> pickSupport = viewer.getPickSupport();
V pickedVertex;
E pickedEdge = null;
if (pickSupport instanceof ShapePickSupport) {
ShapePickSupport<V, E> shapePickSupport =
(ShapePickSupport<V, E>) pickSupport;
pickedVertex = shapePickSupport.getVertex(layoutModel, footprintRectangle);
if (pickedVertex == null) {
pickedEdge = shapePickSupport.getEdge(layoutModel, footprintRectangle);
}
} else {
TransformSupport<V, E> 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<V, E> menu =
@ -148,6 +148,10 @@ public class GhidraGraphMouse<V, E> extends DefaultGraphMouse<V, E> {
vertexIdFunction, vertexNameFunction,
pickedVertex);
menu.show(viewer.getComponent(), e.getX(), e.getY());
} else if (pickedEdge != null) {
OnEdgeSelectionMenu<V, E> menu =
new OnEdgeSelectionMenu<>(viewer, locatedVertexConsumer, pickedEdge);
menu.show(viewer.getComponent(), e.getX(), e.getY());
} else {
selectionFilterMenu.show(viewer.getComponent(), e.getX(), e.getY());
}

View file

@ -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
* <ul>
* <li>a {@link VisualizationViewer} (to access the Graph and LayoutModel)
* <li>a {@link Consumer} of the Subgraph (to make new Graph displays)
* <li>a {@link GraphDisplayListener} (to react to changes in node attributes)
* <li>a {@code Function} to supply the id for a given vertex
* <li>a {@code Function} to supply the name for a given vertex
*
*/
public class GhidraGraphMouseBuilder<V, E, T extends GhidraGraphMouse<V, E>, B extends GhidraGraphMouseBuilder<V, E, T, B>>
extends DefaultGraphMouse.Builder<V, E, T, B> {
private static final String PICK_AREA_SIZE = PREFIX + "pickAreaSize";
/**
* holds the context for graph visualization
*/
VisualizationViewer<V, E> viewer;
/**
* will accept a {@link Graph} and display it in a new tab or window
*/
Consumer<Graph<V, E>> subgraphConsumer;
/**
* accepts a 'located' vertex via a menu driven action
*/
Consumer<V> 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<V, String> vertexIdFunction;
/**
* supplies the name for a given vertex
*/
Function<V, String> vertexNameFunction;
public B self() {
return (B) this;
}
public B viewer(VisualizationViewer<V, E> viewer) {
this.viewer = viewer;
return self();
}
public B subgraphConsumer(Consumer<Graph<V, E>> subgraphConsumer) {
this.subgraphConsumer = subgraphConsumer;
return self();
}
public B locatedVertexConsumer(Consumer<V> locatedVertexConsumer) {
this.locatedVertexConsumer = locatedVertexConsumer;
return self();
}
public B graphDisplayListener(GraphDisplayListener graphDisplayListener) {
this.graphDisplayListener = graphDisplayListener;
return self();
}
public B vertexIdFunction(Function<V, String> vertexIdFunction) {
this.vertexIdFunction = vertexIdFunction;
return self();
}
public B vertexNameFunction(Function<V, String> vertexNameFunction) {
this.vertexNameFunction = vertexIdFunction;
return self();
}
public T build() {
return (T) new GhidraGraphMouse(this);
}
public static <V, E> GhidraGraphMouseBuilder<V, E, ?, ?> builder() {
return new GhidraGraphMouseBuilder<>();
}
}

View file

@ -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<V, E> extends JPopupMenu {
public OnEdgeSelectionMenu(VisualizationViewer<V, E> visualizationViewer,
Consumer<V> locatedVertexConsumer,
E edge) {
Graph<V, E> 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);
}
}

View file

@ -41,8 +41,8 @@ public class OnVertexSelectionMenu<V, E> extends JPopupMenu {
Function<V, String> vertexIdFunction,
Function<V, String> 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");