mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
popup for edge selection
This commit is contained in:
parent
f92c5a53bd
commit
f9542551d5
5 changed files with 230 additions and 42 deletions
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue