GP-5481 Created prototype data graph feature

This commit is contained in:
ghidragon 2025-07-02 13:20:47 -04:00
parent 8d95e97521
commit f54bd20d40
102 changed files with 9267 additions and 366 deletions

View file

@ -4,9 +4,9 @@
* 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.
@ -15,6 +15,7 @@
*/
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply from: "$rootProject.projectDir/gradle/javadoc.gradle"

View file

@ -14,6 +14,8 @@ src/main/docs/VisualGraphHierarchy.png||GHIDRA||||END|
src/main/docs/VisualGraphHierarchy.xml||GHIDRA||||END|
src/main/docs/VisualGraphViewer.png||GHIDRA||||END|
src/main/docs/VisualGraphViewer.xml||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/topics/VisualGraph/Visual_Graph.html||GHIDRA||||END|
src/main/resources/images/Lasso.png||GHIDRA||||END|
src/main/resources/images/color_swatch.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/network-wireless-16.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|

View file

@ -0,0 +1,54 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
</tocroot>

View file

@ -0,0 +1,149 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Visual Graphs</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="Visual_Graph"></A>Visual Graphs</H1>
<BLOCKQUOTE>
<P>Visual Graphs are highly integrated graphs that all share common features and
actions. They typically have both a <A href="#Visual_Graph_Primary_View">Primary View</A> and
a <A href="#Satellite_View">Satellite View</A>
</BLOCKQUOTE>
<H2><A name="Visual_Graph_Primary_View"></A>Primary View</H2>
<BLOCKQUOTE>
<P>The primary view is the main way to view and interact with the graph whose vertices
and edges are specialized for the particular visual graph instance.</P>
</BLOCKQUOTE>
<H2><A name="Satellite_View"></A>Satellite View</H2>
<BLOCKQUOTE>
<P>The Satellite View provides an overview of the graph. From this view you may also perform
basic adjustment of the overall graph location. In addition to the complete graph, the
satellite view contains a <B>lens</B> (the white rectangle) that indicates how much of the
current graph fits into the primary view.</P>
<P>The Satellite View can be in one of three states: docked, undocked, or not showing. A
docked satellite view will be display in a corner of the primary graph. An undocked view
will be displayed in its own window. If the view is not showing, there will be an
<IMG src= "images/network-wireless.png" alt="" border="1">icon in the corner to indicate that
it is available.</P>
<P>When you left-click in the satellite view the graph is centered around the
corresponding point in the primary view. Alternatively, you may drag the lens of the
satellite view to the desired location by performing a mouse drag operation on the lens.</P>
<P>You may show/hide the satellite view by right-clicking anywhere in the Primary View and
selecting or deselecting the <B>Display Satellite View</B> toggle button from the popup
menu.</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/tip.png" alt="" border="0"> If the Primary View is painting
sluggishly, then hiding the Satellite View cause the Primary View to be more
responsive.</P>
</BLOCKQUOTE>
<H3><A name="Satellite_View_Dock"></A>Detached Satellite</H3>
<BLOCKQUOTE>
<P>The Satellite View is docked by default. However, you can choose to instead
undock it and display it in its own window.</P>
<P>To undock the Satellite View, right-click in the graph and deselect the <B>Dock
Satellite View</B> menu item.</P>
<P>To re-dock the Satellite View, right-click in the graph and select the <B>Dock Satellite
View</B> menu item.</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/tip.png" alt="" border="0"> To show the Satellite View if it is
hidden, whether docked or undocked, you can press the <IMG src=
"images/network-wireless.png" alt="" border="1"> button. This button is in the lower-right
hand corner of the graph and is only visible if the Satellite View is hidden or
undocked.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3><A name="Satellite_Location"></A>Docked Satellite Location</H3>
<BLOCKQUOTE>
<P>When the Satellite View is attached, or <B>docked</B>, to the Primary View, you can
choose which corner to show the satellite view. To change the corner, right-click in the
graph, select <B>Docked Satellite Position</B> and then select the appropriate sub-menu for
the desired corner.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Standar Graph Operations</H2>
<BLOCKQUOTE>
<H3><A name="Pan"></A>Panning</H3>
<BLOCKQUOTE>
<P>There are various ways to move the graph. To move the graph in any direction you can
drag from the whitespace of the graph.</P>
<P>By default, the scroll wheel zooms the graph. The scroll wheel can also be used
to scroll the graph vertically by holding the <TT><B>Ctrl</B></TT> key while
using the scroll wheel. Alternatively, you can move the graph left to right using the
mouse while holding <TT><B>Ctrl-Alt</B></TT>.</P>
<P>The satellite viewer may also be used to move the primary graphs view by dragging and
clicking inside of the satellite viewer.</P>
</BLOCKQUOTE>
<H3><A name="Zoom"></A>Zooming</H3>
<BLOCKQUOTE>
<P>At <B>full zoom</B>, or <B>block level zoom</B>, each block is rendered at its natural
size. From that point, which is a
1:1 zoom level, you can zoom out in order to fit more of the graph into the display.</P>
<BLOCKQUOTE>
<P>To change the zoom you may use the mouse scroll wheel. This works
whether the mouse is over the primary viewer or the satellite viewer.</P>
</BLOCKQUOTE>
<P>The <A href="#Satellite_View">satellite viewer</A> is always zoomed out far enough to
fit the entire graph into its window.</P>
</BLOCKQUOTE>
<H3><A name="Interaction_Threshold"></A>Interaction Threshold</H3>
<BLOCKQUOTE>
<P>While zooming out (away from the vertices) you will eventually reach a point where you
can no longer interact with the component inside of the block. The blocks provide a subtle
visual indication when they are zoomed past this level, in the form of a drop-shadow. The
image below shows this drop-shadow. The block on the left is not past the interaction
threshold, but the block on the right is, and thus has a drop-shadow. This example is for
illustrative purposes only and during normal usage all blocks will share the save zoom
level. So, if one block is zoomed past the interaction threshold, all other blocks will
be as well.</P>
</BLOCKQUOTE>
<H3>Painting Threshold</H3>
<BLOCKQUOTE>
<P>While zooming out (away from the blocks) you will eventually reach a point where
contents each vertex will not be painted. Instead, each block will be painted by a
rectangle that is painted with the current background color of the vertex.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<BR>
<BR>
</BODY>
</HTML>

View file

@ -4,9 +4,9 @@
* 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.
@ -26,6 +26,7 @@ import ghidra.framework.options.SaveState;
import ghidra.graph.featurette.VgSatelliteFeaturette;
import ghidra.graph.featurette.VisualGraphFeaturette;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.GraphComponent.SatellitePosition;
import ghidra.graph.viewer.actions.*;
import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
@ -137,11 +138,16 @@ public abstract class VisualGraphComponentProvider<V extends VisualVertex,
* Adds the satellite viewer functionality to this provider
*/
protected void addSatelliteFeature() {
addSatelliteFeature(true, SatellitePosition.LOWER_RIGHT);
}
protected void addSatelliteFeature(boolean satelliteVisible, SatellitePosition position) {
VgSatelliteFeaturette<V, E, G> satelliteFeature = new VgSatelliteFeaturette<>();
satelliteFeature.init(this);
subFeatures.add(satelliteFeature);
satelliteFeature.setSatellitePosition(position);
satelliteFeature.setSatelliteVisible(satelliteVisible);
}
/*
Features to provide

View file

@ -97,27 +97,11 @@ public class VgSatelliteFeaturette<V extends VisualVertex,
view.setSatelliteDocked(dockSatellite);
boolean showSatellite = saveState.getBoolean(DISPLAY_SATELLITE, true);
toggleSatelliteAction.setSelected(showSatellite);
view.setSatelliteVisible(showSatellite);
setSatelliteVisible(showSatellite);
String positionString = saveState.getString(DOCK_SATELLITE_POSITION, LOWER_RIGHT.name());
SatellitePosition position = SatellitePosition.valueOf(positionString);
view.setSatellitePosition(position);
deselectAllSatellitePositions();
switch (position) {
case LOWER_LEFT:
lowerLeftAction.setSelected(true);
break;
case LOWER_RIGHT:
lowerRightAction.setSelected(true);
break;
case UPPER_LEFT:
upperLeftAction.setSelected(true);
break;
case UPPER_RIGHT:
upperRightAction.setSelected(true);
break;
}
setSatellitePosition(position);
}
@Override
@ -183,7 +167,7 @@ public class VgSatelliteFeaturette<V extends VisualVertex,
toggleSatelliteAction.setPopupMenuData(
new MenuData(new String[] { "Display Satellite View" }));
toggleSatelliteAction.setHelpLocation(
new HelpLocation("FunctionCallGraphPlugin", "Satellite_View"));
new HelpLocation("VisualGraph", "Satellite_View"));
dockSatelliteAction = new ToggleDockingAction("Dock Satellite View", owner) {
@Override
@ -208,8 +192,7 @@ public class VgSatelliteFeaturette<V extends VisualVertex,
};
dockSatelliteAction.setSelected(true);
dockSatelliteAction.setPopupMenuData(new MenuData(new String[] { "Dock Satellite View" }));
dockSatelliteAction.setHelpLocation(
new HelpLocation("FunctionCallGraphPlugin", "Satellite_View"));
dockSatelliteAction.setHelpLocation(new HelpLocation("VisualGraph", "Satellite_View"));
upperLeftAction = new SatellitePositionAction("Upper Left", UPPER_LEFT, provider);
upperRightAction = new SatellitePositionAction("Upper Right", UPPER_RIGHT, provider);
@ -234,7 +217,31 @@ public class VgSatelliteFeaturette<V extends VisualVertex,
}
}
public void deselectAllSatellitePositions() {
public void setSatelliteVisible(boolean visible) {
toggleSatelliteAction.setSelected(visible);
view.setSatelliteVisible(visible);
}
public void setSatellitePosition(SatellitePosition position) {
deselectAllSatellitePositions();
switch (position) {
case LOWER_LEFT:
lowerLeftAction.setSelected(true);
break;
case LOWER_RIGHT:
lowerRightAction.setSelected(true);
break;
case UPPER_LEFT:
upperLeftAction.setSelected(true);
break;
case UPPER_RIGHT:
upperRightAction.setSelected(true);
break;
}
view.setSatellitePosition(position);
}
void deselectAllSatellitePositions() {
upperLeftAction.setSelected(false);
upperRightAction.setSelected(false);
lowerLeftAction.setSelected(false);
@ -355,7 +362,7 @@ public class VgSatelliteFeaturette<V extends VisualVertex,
this.position = posiiton;
this.provider = provider;
setPopupMenuData(new MenuData(new String[] { "Docked Satellite Position", name }));
setHelpLocation(new HelpLocation("FunctionCallGraphPlugin", "Satellite_Location"));
setHelpLocation(new HelpLocation("VisualGraph", "Satellite_Location"));
}
@Override

View file

@ -0,0 +1,78 @@
/* ###
* 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.job;
import java.awt.Point;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.*;
import ghidra.graph.viewer.*;
public class RelayoutAndCenterVertexGraphJob<V extends VisualVertex, E extends VisualEdge<V>>
extends RelayoutFunctionGraphJob<V, E> {
private V vertex;
private Point2D destination;
private Point2D lastPoint = new Point2D.Double(0, 0);
public RelayoutAndCenterVertexGraphJob(GraphViewer<V, E> viewer, V vertex,
boolean useAnimation) {
super(viewer, useAnimation);
this.vertex = vertex;
}
@Override
protected void initializeVertexLocations() {
super.initializeVertexLocations();
TransitionPoints transitionPoints = vertexLocations.get(vertex);
Point2D centerPoint = transitionPoints.destinationPoint;
Point p = new Point((int) centerPoint.getX(), (int) centerPoint.getY());
destination = GraphViewerUtils.getOffsetFromCenterInLayoutSpace(viewer, p);
}
@Override
public void setPercentComplete(double percentComplete) {
super.setPercentComplete(percentComplete);
double finalX = destination.getX();
double finalY = destination.getY();
double lastX = lastPoint.getX();
double lastY = lastPoint.getY();
double deltaX = (percentComplete * finalX) - lastX;
double deltaY = (percentComplete * finalY) - lastY;
lastPoint.setLocation(lastX + deltaX, lastY + deltaY);
if (deltaX == 0 && deltaY == 0) {
return;
}
RenderContext<V, E> renderContext = viewer.getRenderContext();
MultiLayerTransformer xform = renderContext.getMultiLayerTransformer();
xform.getTransformer(Layer.LAYOUT).translate(deltaX, deltaY);
viewer.repaint();
}
@Override
protected void finished() {
if (isShortcut) {
destination = GraphViewerUtils.getVertexOffsetFromLayoutCenter(viewer, vertex);
}
super.finished();
}
}

View file

@ -0,0 +1,166 @@
/* ###
* 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.job;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.*;
import ghidra.graph.viewer.*;
/**
* Graph job to move the entire graph to ensure one or two vertices are fully on screen. If both
* vertices can't be fully shown at the same time, the primary vertex gets precedence.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class RelayoutAndEnsureVisible<V extends VisualVertex, E extends VisualEdge<V>>
extends RelayoutFunctionGraphJob<V, E> {
private static final int VIEW_BOUNDARY_PADDING = 50;
private V primaryVertex;
private V secondaryVertex;
private Distance moveDistance;
private Distance movedSoFar = new Distance(0, 0);
public RelayoutAndEnsureVisible(GraphViewer<V, E> viewer, V primaryVertex, V secondaryVertex,
boolean useAnimation) {
super(viewer, useAnimation);
this.primaryVertex = primaryVertex;
this.secondaryVertex = secondaryVertex;
}
@Override
protected void initializeVertexLocations() {
super.initializeVertexLocations();
Shape layoutViewerShape =
GraphViewerUtils.translateShapeFromViewSpaceToLayoutSpace(viewer.getBounds(), viewer);
Rectangle layoutViewerBounds = layoutViewerShape.getBounds();
// get layout destination position for each vertex
Rectangle primaryLayoutVertexBounds =
GraphViewerUtils.getVertexBoundsInLayoutSpace(viewer, primaryVertex);
Rectangle secondaryLayoutVertexBounds =
GraphViewerUtils.getVertexBoundsInLayoutSpace(viewer, secondaryVertex);
setRectangleLocationToFinalDestination(primaryLayoutVertexBounds, primaryVertex);
setRectangleLocationToFinalDestination(secondaryLayoutVertexBounds, secondaryVertex);
padVertexBounds(primaryLayoutVertexBounds);
padVertexBounds(secondaryLayoutVertexBounds);
// This is the distance we need to move the view to ensure the less important vertex is
// is fully visible in the view.
Distance secondaryMoveDistance =
getMoveDistanceToContainVertexInView(layoutViewerBounds, secondaryLayoutVertexBounds);
// Assuming we already moved the layout as computed above, how much additional movement is
// needed to bring the preferred vertex fully into view. Note that if both vertices don't
// fit, the secondary vertex may no longer be visible after all movement is applied.
layoutViewerBounds.x -= secondaryMoveDistance.deltaX;
layoutViewerBounds.y -= secondaryMoveDistance.deltaY;
Distance primaryMoveDistance =
getMoveDistanceToContainVertexInView(layoutViewerBounds, primaryLayoutVertexBounds);
// The total distance we need to move the view is the net effect of combining the first
// move and the second move.
moveDistance = secondaryMoveDistance.add(primaryMoveDistance);
}
private Distance getMoveDistanceToContainVertexInView(
Rectangle layoutViewerBounds, Rectangle layoutVertexBounds) {
// if the vertex is already fully in the view, no move needed
if (layoutViewerBounds.contains(layoutVertexBounds)) {
return new Distance(0, 0);
}
int deltaX = 0;
int deltaY = 0;
int view1x = layoutViewerBounds.x;
int view1y = layoutViewerBounds.y;
int view2x = layoutViewerBounds.x + layoutViewerBounds.width;
int view2y = layoutViewerBounds.y + layoutViewerBounds.height;
int vertex1x = layoutVertexBounds.x;
int vertex1y = layoutVertexBounds.y;
int vertex2x = layoutVertexBounds.x + layoutVertexBounds.width;
int vertex2y = layoutVertexBounds.y + layoutVertexBounds.height;
if (view1x > vertex1x) {
deltaX = -(vertex1x - view1x);
}
else if (view2x < vertex2x) {
deltaX = -(vertex2x - view2x);
}
if (view1y > vertex1y) {
deltaY = -(vertex1y - view1y);
}
else if (view2y < vertex2y) {
deltaY = -(vertex2y - view2y);
}
return new Distance(deltaX, deltaY);
}
private void padVertexBounds(Rectangle layoutVertexBounds) {
layoutVertexBounds.x -= VIEW_BOUNDARY_PADDING;
layoutVertexBounds.y -= VIEW_BOUNDARY_PADDING;
layoutVertexBounds.width += 2 * VIEW_BOUNDARY_PADDING;
layoutVertexBounds.height += 2 * VIEW_BOUNDARY_PADDING;
}
private void setRectangleLocationToFinalDestination(Rectangle layoutVertexBounds, V v) {
TransitionPoints transitionPoints = vertexLocations.get(v);
Point2D centerPoint = transitionPoints.destinationPoint;
int upperLeftCornerX = (int) centerPoint.getX() - layoutVertexBounds.width / 2;
int upperLeftCornerY = (int) centerPoint.getY() - layoutVertexBounds.height / 2;
layoutVertexBounds.setLocation(upperLeftCornerX, upperLeftCornerY);
}
@Override
public void setPercentComplete(double percentComplete) {
super.setPercentComplete(percentComplete);
Distance newMovedSoFar = moveDistance.scale(percentComplete);
double deltaX = newMovedSoFar.deltaX - movedSoFar.deltaX;
double deltaY = newMovedSoFar.deltaY - movedSoFar.deltaY;
RenderContext<V, E> renderContext = viewer.getRenderContext();
MultiLayerTransformer xform = renderContext.getMultiLayerTransformer();
xform.getTransformer(Layer.LAYOUT).translate(deltaX, deltaY);
viewer.repaint();
movedSoFar = newMovedSoFar;
}
private record Distance(int deltaX, int deltaY) {
Distance scale(double scale) {
return new Distance((int) (deltaX * scale), (int) (deltaY * scale));
}
public Distance add(Distance other) {
return new Distance(deltaX + other.deltaX, deltaY + other.deltaY);
}
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -19,9 +19,6 @@ import java.awt.geom.Point2D;
import java.util.*;
import java.util.Map.Entry;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.LayoutPositions;
@ -32,25 +29,6 @@ public class RelayoutFunctionGraphJob<V extends VisualVertex, E extends VisualEd
super(viewer, useAnimation);
}
@Override
protected Animator createAnimator() {
initializeVertexLocations();
clearLocationCache();
if (!useAnimation) {
return null;
}
updateOpacity(0);
Animator newAnimator =
PropertySetter.createAnimator(duration, this, "percentComplete", 0.0, 1.0);
newAnimator.setAcceleration(0f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
@Override
protected void finished() {
@ -77,6 +55,7 @@ public class RelayoutFunctionGraphJob<V extends VisualVertex, E extends VisualEd
// Create the new vertex locations.
//
Collection<V> vertices = graph.getVertices();
for (V vertex : vertices) {
Point2D currentPoint = toLocation(vertex);
Point2D startPoint = (Point2D) currentPoint.clone();

View file

@ -293,7 +293,7 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
// the layout defines the shape of the edge (this gives the layout flexibility in how
// to render its shape)
Function<E, Shape> edgeTransformer = layout.getEdgeShapeTransformer();
Function<E, Shape> edgeTransformer = layout.getEdgeShapeTransformer(renderContext);
renderContext.setEdgeShapeTransformer(edgeTransformer);
renderContext.setArrowPlacementTolerance(5.0f);
@ -361,7 +361,7 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
visualEdgeRenderer.setHoveredColorTransformer(
e -> new GColor("color.visualgraph.view.satellite.edge.hovered"));
Function<E, Shape> edgeTransformer = layout.getEdgeShapeTransformer();
Function<E, Shape> edgeTransformer = layout.getEdgeShapeTransformer(renderContext);
renderContext.setEdgeShapeTransformer(edgeTransformer);
renderContext.setVertexShapeTransformer(new VisualGraphVertexShapeTransformer<>());
@ -471,14 +471,9 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
button.setOpaque(false);
button.setToolTipText(tooltip);
/*
TODO fix when the Generic Visual Graph help module is created
HelpService helpService = DockingWindowManager.getHelpService();
helpService.registerHelp(button,
new HelpLocation("GraphTopic", "Satellite_View_Dock"));
*/
new HelpLocation("Visual_Graph", "Satellite_View_Dock"));
return button;
}
@ -1011,7 +1006,7 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
v.setLocation(newLocation);
if (changeType == ChangeType.RESTORE) {
if (changeType.isTransitional()) {
// ignore these events, as they are a bulk operation and will be handled later
return;
}
@ -1183,6 +1178,12 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
@Override
public void verticesRemoved(Iterable<V> vertices) {
getPathHighlighter().clearEdgeCache();
// clear any deleted nodes from the pick state
PickedState<V> pickedState = primaryViewer.getPickedVertexState();
for (V v : vertices) {
pickedState.pick(v, false);
}
}
@Override

View file

@ -117,9 +117,15 @@ public class GraphViewerUtils {
public static <V, E> Point getVertexUpperLeftCornerInLayoutSpace(
VisualizationServer<V, E> viewer, V vertex) {
Point vertexCenterInLayoutSpace = getVertexCenterPointInLayoutSpace(viewer, vertex);
Point vertexGraphSpaceLocation = getVertexUpperLeftCornerInGraphSpace(viewer, vertex);
return translatePointFromGraphSpaceToLayoutSpace(vertexGraphSpaceLocation, viewer);
RenderContext<V, E> renderContext = viewer.getRenderContext();
Shape shape = renderContext.getVertexShapeTransformer().apply(vertex);
Rectangle shapeBounds = shape.getBounds();
Point vertexUpperLeftPointRelativeToVertexCenter = shapeBounds.getLocation();
return new Point(vertexCenterInLayoutSpace.x + vertexUpperLeftPointRelativeToVertexCenter.x,
vertexCenterInLayoutSpace.y + vertexUpperLeftPointRelativeToVertexCenter.y);
}
public static <V, E> Point getVertexUpperLeftCornerInViewSpace(VisualizationServer<V, E> viewer,
@ -215,17 +221,8 @@ public class GraphViewerUtils {
public static <V, E> Point getVertexUpperLeftCornerInGraphSpace(
VisualizationServer<V, E> viewer, V vertex) {
Point vertexCenterInLayoutSpace = getVertexCenterPointInLayoutSpace(viewer, vertex);
Point vertexCenterInGraphSpace =
translatePointFromLayoutSpaceToGraphSpace(vertexCenterInLayoutSpace, viewer);
RenderContext<V, E> renderContext = viewer.getRenderContext();
Shape shape = renderContext.getVertexShapeTransformer().apply(vertex);
Rectangle shapeBounds = shape.getBounds();
Point vertexUpperLeftPointRelativeToVertexCenter = shapeBounds.getLocation();
return new Point(vertexCenterInGraphSpace.x + vertexUpperLeftPointRelativeToVertexCenter.x,
vertexCenterInGraphSpace.y + vertexUpperLeftPointRelativeToVertexCenter.y);
Point layoutPoint = getVertexUpperLeftCornerInLayoutSpace(viewer, vertex);
return translatePointFromLayoutSpaceToGraphSpace(layoutPoint, viewer);
}
public static <V, E> Point translatePointFromLayoutSpaceToGraphSpace(Point2D pointInLayoutSpace,
@ -688,32 +685,11 @@ public class GraphViewerUtils {
double endY = (float) endVertexCenter.getY();
RenderContext<V, E> renderContext = viewer.getRenderContext();
if (isLoop) {
//
// Our edge loops are sized and positioned according to the shared
// code in the utils class. We do this so that our hit detection matches our rendering.
//
Function<? super V, Shape> vertexShapeTransformer =
renderContext.getVertexShapeTransformer();
Shape vertexShape = getVertexShapeForEdge(endVertex, vertexShapeTransformer);
return createHollowEgdeLoopInGraphSpace(vertexShape, startX, startY);
}
// translate the edge from 0,0 to the starting vertex point
AffineTransform xform = AffineTransform.getTranslateInstance(startX, startY);
Shape edgeShape = renderContext.getEdgeShapeTransformer().apply(e);
double deltaX = endX - startX;
double deltaY = endY - startY;
// rotate the edge to the angle between the vertices
double theta = Math.atan2(deltaY, deltaX);
xform.rotate(theta);
// stretch the edge to span the distance between the vertices
double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
xform.scale(dist, 1.0f);
// apply the transformations; converting the given shape from model space into graph space
return xform.createTransformedShape(edgeShape);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -17,6 +17,7 @@ package ghidra.graph.viewer.event.mouse;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Point2D;
import java.util.Objects;
@ -46,9 +47,11 @@ public class VertexMouseInfo<V extends VisualVertex, E extends VisualEdge<V>> {
private final V vertex;
private MouseEvent translatedMouseEvent;
private Component mousedDestinationComponent;
private Point2D vertexBasedClickPoint;
public VertexMouseInfo(MouseEvent originalMouseEvent, V vertex, Point2D vertexBasedClickPoint,
GraphViewer<V, E> viewer) {
this.vertexBasedClickPoint = vertexBasedClickPoint;
this.originalMouseEvent = Objects.requireNonNull(originalMouseEvent);
this.vertex = Objects.requireNonNull(vertex);
this.viewer = Objects.requireNonNull(viewer);
@ -59,6 +62,10 @@ public class VertexMouseInfo<V extends VisualVertex, E extends VisualEdge<V>> {
setClickedComponent(deepestComponent, vertexBasedClickPoint);
}
public Point2D getVertexRelativeClickPoint() {
return vertexBasedClickPoint;
}
public boolean isScaledPastInteractionThreshold() {
RenderContext<V, E> renderContext = viewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
@ -75,6 +82,11 @@ public class VertexMouseInfo<V extends VisualVertex, E extends VisualEdge<V>> {
if (!isVertexSelected()) {
return HAND_CURSOR;
}
if (mousedDestinationComponent != null) {
return mousedDestinationComponent.getCursor();
}
return DEFAULT_CURSOR;
}
@ -221,12 +233,22 @@ public class VertexMouseInfo<V extends VisualVertex, E extends VisualEdge<V>> {
System.currentTimeMillis(), 0, 0, 0, 0, false);
}
private MouseEvent createMouseEventFromSource(Component source, MouseEvent progenitor,
private MouseEvent createMouseEventFromSource(Component source, MouseEvent ev,
Point2D clickPoint) {
return new MouseEvent(source, progenitor.getID(), progenitor.getWhen(),
progenitor.getModifiers() | progenitor.getModifiersEx(), (int) clickPoint.getX(),
(int) clickPoint.getY(), progenitor.getClickCount(), progenitor.isPopupTrigger(),
progenitor.getButton());
if (ev instanceof MouseWheelEvent wheelEvent) {
int scrollType = wheelEvent.getScrollType();
int scrollAmount = wheelEvent.getScrollAmount();
int wheelRotation = wheelEvent.getWheelRotation();
return new MouseWheelEvent(source, ev.getID(), ev.getWhen(),
ev.getModifiers() | ev.getModifiersEx(), (int) clickPoint.getX(),
(int) clickPoint.getY(), ev.getClickCount(), ev.isPopupTrigger(),
scrollType, scrollAmount, wheelRotation);
}
return new MouseEvent(source, ev.getID(), ev.getWhen(),
ev.getModifiers() | ev.getModifiersEx(), (int) clickPoint.getX(),
(int) clickPoint.getY(), ev.getClickCount(), ev.isPopupTrigger(),
ev.getButton());
}
public boolean isPopupClick() {

View file

@ -4,9 +4,9 @@
* 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.
@ -265,6 +265,7 @@ public class VisualGraphEventForwardingGraphMousePlugin<V extends VisualVertex,
currentMouseEnteredInfo = mouseMovedMouseInfo;
mouseMovedMouseInfo.forwardEvent();
updateCursor(mouseMovedMouseInfo);
}
private void triggerMouseExited(VertexMouseInfo<V, E> currentInfo,

View file

@ -4,9 +4,9 @@
* 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.
@ -28,8 +28,11 @@ import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.layout.ObservableCachingLayout;
import edu.uci.ics.jung.visualization.picking.PickedState;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.layout.VisualGraphLayout;
public class VisualGraphPickingGraphMousePlugin<V extends VisualVertex, E extends VisualEdge<V>>
extends JungPickingGraphMousePlugin<V, E> implements VisualGraphMousePlugin<V, E> {
@ -100,7 +103,13 @@ public class VisualGraphPickingGraphMousePlugin<V extends VisualVertex, E extend
for (V v : ps.getPicked()) {
Point2D vertexPoint = layout.apply(v);
vertexPoint.setLocation(vertexPoint.getX() + dx, vertexPoint.getY() + dy);
layout.setLocation(v, vertexPoint);
VisualGraphLayout<V, E> vgLayout = getVisualGraphLayout(layout);
if (vgLayout != null) {
vgLayout.setLocation(v, vertexPoint, ChangeType.USER);
}
else {
layout.setLocation(v, vertexPoint);
}
updatedArticulatedEdges(viewer, v);
}
@ -108,6 +117,16 @@ public class VisualGraphPickingGraphMousePlugin<V extends VisualVertex, E extend
e.consume();
}
private VisualGraphLayout<V, E> getVisualGraphLayout(Layout<V, E> layout) {
if (layout instanceof VisualGraphLayout<V, E> vgLayout) {
return vgLayout;
}
if (layout instanceof ObservableCachingLayout<V, E> observable) {
return getVisualGraphLayout(observable.getDelegate());
}
return null;
}
private void updatedArticulatedEdges(GraphViewer<V, E> viewer, V v) {
Layout<V, E> layout = viewer.getGraphLayout();

View file

@ -4,9 +4,9 @@
* 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.
@ -15,6 +15,7 @@
*/
package ghidra.graph.viewer.event.mouse;
import java.awt.Component;
import java.awt.event.*;
import java.util.concurrent.CopyOnWriteArrayList;
@ -113,14 +114,15 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseClicked(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseClicked() on " + p, e);
((MouseListener) p).mouseClicked(e);
if (e.isConsumed()) {
trace("mouseClicked() on " + p, copy);
((MouseListener) p).mouseClicked(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -129,14 +131,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mousePressed(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mousePressed() on " + p, e);
((MouseListener) p).mousePressed(e);
if (e.isConsumed()) {
trace("mousePressed() on " + p, copy);
((MouseListener) p).mousePressed(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -145,14 +149,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseReleased(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseReleased() on " + p, e);
((MouseListener) p).mouseReleased(e);
if (e.isConsumed()) {
trace("mouseReleased() on " + p, copy);
((MouseListener) p).mouseReleased(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -161,14 +167,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseEntered(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseEntered() on " + p, e);
((MouseListener) p).mouseEntered(e);
if (e.isConsumed()) {
trace("mouseEntered() on " + p, copy);
((MouseListener) p).mouseEntered(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -177,14 +185,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseExited(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseExited() on " + p, e);
((MouseListener) p).mouseExited(e);
if (e.isConsumed()) {
trace("mouseExited() on " + p, copy);
((MouseListener) p).mouseExited(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -193,14 +203,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseDragged(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseMotionListener)) {
continue;
}
trace("mouseDragged() on " + p, e);
((MouseMotionListener) p).mouseDragged(e);
if (e.isConsumed()) {
trace("mouseDragged() on " + p, copy);
((MouseMotionListener) p).mouseDragged(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -209,14 +221,16 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseMoved(MouseEvent e) {
MouseEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseMotionListener)) {
continue;
}
trace("mouseMoved() on " + p, e);
((MouseMotionListener) p).mouseMoved(e);
if (e.isConsumed()) {
trace("mouseMoved() on " + p, copy);
((MouseMotionListener) p).mouseMoved(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
@ -225,17 +239,59 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
MouseWheelEvent copy = copy(e);
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseWheelListener)) {
continue;
}
trace("mouseWheelMoved() on " + p, e);
((MouseWheelListener) p).mouseWheelMoved(e);
if (e.isConsumed()) {
trace("mouseWheelMoved() on " + p, copy);
((MouseWheelListener) p).mouseWheelMoved(copy);
if (copy.isConsumed()) {
e.consume();
trace("\tconsumed");
return;
}
}
}
/**
* Copies the given mouse event. We do this so that we allow our mouse plugins to process
* mouse events. This was done specifically allow us to update state when user right-clicks.
* Ghidra has code that will consume mouse clicks before we get the event.
* <P>
* This pluggable graph mouse sub-system will stop processing when one of the plugins consumes
* the mouse event. We have to create a copy to avoid an already consumed incoming event from
* short-circuiting our event processing.
* @param e
* @return a copy if the original incoming event with the consumed flag cleared.
*/
private MouseEvent copy(MouseEvent e) {
Component source = e.getComponent();
int id = e.getID();
int button = e.getButton();
long when = e.getWhen();
int modifiers = e.getModifiersEx();
int x = e.getX();
int y = e.getY();
int clickCount = e.getClickCount();
boolean popupTrigger = e.isPopupTrigger();
return new MouseEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger, button);
}
private MouseWheelEvent copy(MouseWheelEvent e) {
Component source = e.getComponent();
int id = e.getID();
long when = e.getWhen();
int modifiers = e.getModifiersEx();
int x = e.getX();
int y = e.getY();
int clickCount = e.getClickCount();
boolean popupTrigger = e.isPopupTrigger();
int scrollType = e.getScrollType();
int scrollAmount = e.getScrollAmount();
int wheelRotation = e.getWheelRotation();
return new MouseWheelEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger,
scrollType, scrollAmount, wheelRotation);
}
}

View file

@ -27,6 +27,7 @@ import com.google.common.base.Function;
import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer.EdgeLabel;
import ghidra.graph.VisualGraph;
@ -126,7 +127,8 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
}
@Override
public Function<E, Shape> getEdgeShapeTransformer() {
public Function<E, Shape> getEdgeShapeTransformer(RenderContext<V, E> context) {
edgeShapeTransformer.setRenderContext(context);
return edgeShapeTransformer;
}
@ -282,7 +284,7 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
for (Entry<V, Point2D> entry : entrySet) {
V vertex = entry.getKey();
Point2D location = entry.getValue();
setLocation(vertex, location);
setLocation(vertex, location, ChangeType.RESTORE);
vertex.setLocation(location);
}
}
@ -698,10 +700,10 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
}
private void fireVertexLocationChanged(V v, Point2D p) {
fireVertexLocationChanged(v, p, ChangeType.USER);
fireVertexLocationChanged(v, p, ChangeType.API);
}
private void fireVertexLocationChanged(V v, Point2D p, ChangeType type) {
protected void fireVertexLocationChanged(V v, Point2D p, ChangeType type) {
Iterator<LayoutListener<V, E>> iterator = listeners.iterator();
for (; iterator.hasNext();) {
LayoutListener<V, E> layoutListener = iterator.next();

View file

@ -97,4 +97,14 @@ public class GridBounds {
return true;
}
public void transpose() {
int temp = minRow;
minRow = minCol;
minCol = temp;
temp = maxRow;
maxRow = maxCol;
maxCol = temp;
}
}

View file

@ -44,13 +44,13 @@ public class GridLocationMap<V, E> {
protected Map<E, List<GridPoint>> edgePoints = new HashMap<>();
private GridBounds gridBounds = new GridBounds();
// Tree based algorithms might want to track the column of the root node as it changes when
// the grid is shifted or merged.Useful for determining the position of a parent node when
// Tree based algorithms might want to track the location of the root node as it changes when
// the grid is shifted or merged. Useful for determining the position of a parent node when
// building bottom up.
private int rootColumn = 0;
private GridPoint rootPoint;
public GridLocationMap() {
rootColumn = 0;
rootPoint = new GridPoint(0, 0);
}
/**
@ -60,7 +60,7 @@ public class GridLocationMap<V, E> {
* @param col the column for the initial vertex.
*/
public GridLocationMap(V root, int row, int col) {
this.rootColumn = col;
rootPoint = new GridPoint(row, col);
set(root, new GridPoint(row, col));
}
@ -69,7 +69,15 @@ public class GridLocationMap<V, E> {
* @return the column of the initial vertex in this grid
*/
public int getRootColumn() {
return rootColumn;
return rootPoint.col;
}
/**
* Returns the row of the initial vertex in this grid.
* @return the row of the initial vertex in this grid
*/
public int getRootRow() {
return rootPoint.row;
}
public Set<V> vertices() {
@ -245,7 +253,8 @@ public class GridLocationMap<V, E> {
p.row += rowShift;
p.col += colShift;
}
rootColumn += colShift;
rootPoint.row += rowShift;
rootPoint.col += colShift;
gridBounds.shift(rowShift, colShift);
}
@ -292,6 +301,30 @@ public class GridLocationMap<V, E> {
return rowRanges;
}
/**
* Returns the minimum/max row for all columns in the grid. This method is only defined for
* grids that have no negative columns. This is because the array returned will be 0 based, with
* the entry at index 0 containing the row bounds for column 0 and so on.
* @return the minimum/max row for all columns in the grid
* @throws IllegalStateException if this method is called on a grid with negative rows.
*/
public GridRange[] getVertexRowRanges() {
if (gridBounds.minCol() < 0) {
throw new IllegalStateException(
"getVertexColumnRanges not defined for grids with negative rows!");
}
GridRange[] colRanges = new GridRange[width()];
for (int i = 0; i < colRanges.length; i++) {
colRanges[i] = new GridRange();
}
for (GridPoint p : vertexPoints.values()) {
colRanges[p.col].add(p.row);
}
return colRanges;
}
public boolean containsVertex(V v) {
return vertexPoints.containsKey(v);
}
@ -371,7 +404,7 @@ public class GridLocationMap<V, E> {
private GridLocationMap<V, E> copy() {
GridLocationMap<V, E> map = new GridLocationMap<>();
map.rootColumn = rootColumn;
map.rootPoint = new GridPoint(rootPoint.row, rootPoint.col);
Set<Entry<V, GridPoint>> entries = vertexPoints.entrySet();
for (Entry<V, GridPoint> entry : entries) {

View file

@ -57,6 +57,12 @@ public class GridPoint {
return col == other.col && row == other.row;
}
public void transpose() {
int temp = row;
row = col;
col = temp;
}
@Override
public String toString() {
return "(r=" + row + ",c=" + col + ")";

View file

@ -4,9 +4,9 @@
* 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.
@ -26,6 +26,7 @@ import com.google.common.base.Function;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer.EdgeLabel;
import ghidra.graph.VisualGraph;
@ -182,7 +183,8 @@ public class JungWrappingVisualGraphLayoutAdapter<V extends VisualVertex,
}
@Override
public Function<E, Shape> getEdgeShapeTransformer() {
public Function<E, Shape> getEdgeShapeTransformer(RenderContext<V, E> context) {
edgeShapeTransformer.setRenderContext(context);
return edgeShapeTransformer;
}
@ -239,7 +241,7 @@ public class JungWrappingVisualGraphLayoutAdapter<V extends VisualVertex,
@Override
public void setLocation(V v, Point2D location) {
delegate.setLocation(v, location);
fireVertexLocationChanged(v, location, ChangeType.USER);
fireVertexLocationChanged(v, location, ChangeType.API);
}
@Override

View file

@ -4,9 +4,9 @@
* 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.
@ -26,9 +26,14 @@ import java.awt.geom.Point2D;
public interface LayoutListener<V, E> {
public enum ChangeType {
USER, // real changes that should be tracked
API, // non-transient change to a vertex location made by an API call
TRANSIENT, // transient changes that can be ignored
RESTORE // changes that happen when re-serializing saved locations
RESTORE, // changes that happen when re-serializing saved locations
USER; // user initiated change, such as the user dragging a vertex
public boolean isTransitional() {
return this == RESTORE || this == TRANSIENT;
}
}
/**

View file

@ -4,9 +4,9 @@
* 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.
@ -107,4 +107,9 @@ public class LayoutPositions<V extends VisualVertex, E extends VisualEdge<V>> {
vertexLocations.clear();
edgeArticulations.clear();
}
@Override
public String toString() {
return vertexLocations.toString();
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -21,6 +21,7 @@ import java.awt.geom.Point2D;
import com.google.common.base.Function;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import ghidra.graph.VisualGraph;
@ -91,7 +92,9 @@ public interface VisualGraphLayout<V extends VisualVertex,
public VisualGraphLayout<V, E> cloneLayout(VisualGraph<V, E> newGraph);
/**
* Allows the client to change the location while specifying the type of change
* Allows the client to change the location while specifying the type of change.
* <P>
* Calling {@link #setLocation(Object, Point2D)} will use {@link ChangeType#API}.
*
* @param v the vertex
* @param location the new location
@ -116,9 +119,10 @@ public interface VisualGraphLayout<V extends VisualVertex,
/**
* Returns an optional edge shape transformer. This is used to create shapes for each edge.
*
* @param context RenderContext needed to get rendering information
* @return an optional edge shape transformer
*/
public Function<E, Shape> getEdgeShapeTransformer();
public Function<E, Shape> getEdgeShapeTransformer(RenderContext<V, E> context);
/**
* Returns an optional custom edge label renderer. This is used to add labels to the edges.

View file

@ -4,9 +4,9 @@
* 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.
@ -16,12 +16,15 @@
package ghidra.graph.viewer.shape;
import java.awt.Shape;
import java.awt.geom.*;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.List;
import com.google.common.base.Function;
import edu.uci.ics.jung.visualization.RenderContext;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
@ -33,6 +36,12 @@ import ghidra.util.SystemUtilities;
public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends VisualEdge<V>>
implements Function<E, Shape> {
private RenderContext<V, E> renderContext;
public void setRenderContext(RenderContext<V, E> context) {
this.renderContext = context;
}
/**
* Get the shape for this edge
*
@ -45,6 +54,21 @@ public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends Visual
V end = e.getEnd();
boolean isLoop = start.equals(end);
if (isLoop) {
//
// Our edge loops are sized and positioned according to the shared
// code in the utils class. We do this so that our hit detection matches our rendering.
//
Function<? super V, Shape> vertexShapeTransformer =
renderContext.getVertexShapeTransformer();
Shape vertexShape = getVertexShapeForEdge(end, vertexShapeTransformer);
Shape hollowEgdeLoop = GraphViewerUtils.createHollowEgdeLoop();
// we are not actually creating this in graph space, but by passing in 0,0, we are in
// unit space
return GraphViewerUtils.createEgdeLoopInGraphSpace(hollowEgdeLoop, vertexShape, 0, 0);
}
if (isLoop) {
return GraphViewerUtils.createHollowEgdeLoop();
}
@ -82,43 +106,23 @@ public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends Visual
path.lineTo(p2x, p2y);
path.moveTo(p2x, p2y);
path.closePath();
return path;
}
AffineTransform transform = new AffineTransform();
final double deltaY = p2.getY() - originY;
final double deltaX = p2.getX() - originX;
if (deltaX == 0 && deltaY == 0) {
// this implies the source and destination node are at the same location, which
// is possible if the user drags it there or during animations
return transform.createTransformedShape(path);
@SuppressWarnings({ "rawtypes", "unchecked" })
private static <V> Shape getVertexShapeForEdge(V v, Function<? super V, Shape> vertexShaper) {
if (vertexShaper instanceof VisualGraphVertexShapeTransformer) {
if (v instanceof VisualVertex) {
VisualVertex vv = (VisualVertex) v;
// Note: it is a bit odd that we 'know' to use the compact shape here for
// hit detection, but this is how the edge is painted, so we want the
// view to match the mouse.
return ((VisualGraphVertexShapeTransformer) vertexShaper).transformToCompactShape(
vv);
}
}
double theta = StrictMath.atan2(deltaY, deltaX);
transform.rotate(theta);
double scale = StrictMath.sqrt(deltaY * deltaY + deltaX * deltaX);
transform.scale(scale, 1.0f);
//
// TODO
// The current design and use of this transformer is a bit odd. We currently have code
// to create the edge shape here and in the ArticulatedEdgeRenderer. Ideally, this
// class would be the only one that creates the edge shape. Then, any clients of the
// edge transformer would have to take the shape and then transform it to the desired
// space (the view or graph space). The transformations could be done using the
// GraphViewerUtils.
//
try {
// TODO it is not clear why this is using an inverse transform; why not just create
// the transform that we want?
AffineTransform inverse = transform.createInverse();
Shape transformedShape = inverse.createTransformedShape(path);
return transformedShape;
}
catch (NoninvertibleTransformException e1) {
Msg.error(this, "Unexpected exception transforming an edge", e1);
}
return null;
return vertexShaper.apply(v);
}
private void logMissingLocation(E e, V v) {
@ -151,4 +155,5 @@ public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends Visual
throw new IllegalStateException("Edge vertex is missing a location");
}
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -66,7 +66,6 @@ public class VisualGraphShapePickSupport<V extends VisualVertex, E extends Visua
if (edgeShape == null) {
continue;
}
// because of the transform, the edgeShape is now a GeneralPath
// see if this edge is the closest of any that intersect
if (edgeShape.intersects(pickArea)) {