mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-5481 Created prototype data graph feature
This commit is contained in:
parent
8d95e97521
commit
f54bd20d40
102 changed files with 9267 additions and 366 deletions
|
@ -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"
|
||||
|
|
|
@ -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|
|
||||
|
|
54
Ghidra/Framework/Graph/src/main/help/help/TOC_Source.xml
Normal file
54
Ghidra/Framework/Graph/src/main/help/help/TOC_Source.xml
Normal 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>
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 + ")";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue