mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GT-3019 - Function Graph - Edge Routing - review fixes:
-fixed broken edge labels (labels were not offset correctly) -fixed complex edge routing for edges going upward -fixed bug that allowed toolbar multi-state actions to fire twice when clicked
This commit is contained in:
@@ -855,7 +855,7 @@ class FGActionManager {
|
|||||||
HelpLocation layoutHelpLocation =
|
HelpLocation layoutHelpLocation =
|
||||||
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
||||||
|
|
||||||
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) {
|
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName(), true) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doActionPerformed(ActionContext context) {
|
protected void doActionPerformed(ActionContext context) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
|
|||||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||||
|
import ghidra.program.model.symbol.FlowType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An edge label renderer used with the {@link DecompilerNestedLayout}
|
* An edge label renderer used with the {@link DecompilerNestedLayout}
|
||||||
@@ -86,11 +87,18 @@ class DNLEdgeLabelRenderer<V extends FGVertex, E extends FGEdge>
|
|||||||
double cx = start.getX();
|
double cx = start.getX();
|
||||||
double cy = start.getY();
|
double cy = start.getY();
|
||||||
|
|
||||||
|
EdgeLabelRenderer labelRenderer = rc.getEdgeLabelRenderer();
|
||||||
|
Font font = rc.getEdgeFontTransformer().apply(e);
|
||||||
|
boolean isSelected = rc.getPickedEdgeState().isPicked(e);
|
||||||
|
Component component = labelRenderer.getEdgeLabelRendererComponent(rc.getScreenDevice(),
|
||||||
|
text, font, isSelected, e);
|
||||||
|
int labelWidth = component.getPreferredSize().width;
|
||||||
|
|
||||||
List<Point2D> articulationPoints = e.getArticulationPoints();
|
List<Point2D> articulationPoints = e.getArticulationPoints();
|
||||||
if (articulationPoints.isEmpty()) {
|
if (articulationPoints.isEmpty()) {
|
||||||
double vertexBottom = start.getY() + (vertexBounds.height >> 1); // location is centered
|
double vertexBottom = start.getY() + (vertexBounds.height >> 1); // location is centered
|
||||||
int textY = (int) (vertexBottom + edgeOffset); // below the vertex; above the bend
|
double textY = (int) (vertexBottom + edgeOffset); // below the vertex; above the bend
|
||||||
int textX = (int) (start.getX() + xDisplacement); // right of the edge
|
double textX = (int) (start.getX() + xDisplacement); // right of the edge
|
||||||
labelPointOffset.setLocation(textX, textY);
|
labelPointOffset.setLocation(textX, textY);
|
||||||
}
|
}
|
||||||
else if (articulationPoints.size() == 1) {
|
else if (articulationPoints.size() == 1) {
|
||||||
@@ -109,29 +117,26 @@ class DNLEdgeLabelRenderer<V extends FGVertex, E extends FGEdge>
|
|||||||
|
|
||||||
double bx1 = bend1.getX();
|
double bx1 = bend1.getX();
|
||||||
|
|
||||||
|
FlowType flow = e.getFlowType();
|
||||||
|
boolean isRight = flow.isFallthrough() || flow.isUnConditional();
|
||||||
|
|
||||||
if (articulationPoints.size() == 2) {
|
if (articulationPoints.size() == 2) {
|
||||||
|
|
||||||
int textX = (int) (vertexSide + edgeOffset); // right of the vertex
|
double textX = (int) (vertexSide + edgeOffset); // right of the vertex
|
||||||
int textY = (int) (cy + edgeOffset); // above the edge
|
double textY = (int) (cy + edgeOffset); // above the edge
|
||||||
labelPointOffset.setLocation(textX, textY);
|
labelPointOffset.setLocation(textX, textY);
|
||||||
}
|
}
|
||||||
else if (articulationPoints.size() == 3) {
|
else { // 3 or 4 articulations
|
||||||
|
|
||||||
|
double textY = (int) (vertexBottom + edgeOffset); // below the vertex; above the bend
|
||||||
|
double textX = (int) (bx1 + xDisplacement); // right of the edge
|
||||||
|
if (!isRight) {
|
||||||
|
textX = bx1 - xDisplacement - labelWidth;
|
||||||
|
}
|
||||||
|
|
||||||
int textY = (int) (vertexBottom + edgeOffset); // below the vertex; above the bend
|
|
||||||
int textX = (int) (bx1 + xDisplacement); // right of the edge
|
|
||||||
labelPointOffset.setLocation(textX, textY);
|
|
||||||
}
|
|
||||||
else { // if (articulationPoints.size() == 4) {
|
|
||||||
int textY = (int) (vertexBottom + edgeOffset); // below the vertex; above the bend
|
|
||||||
int textX = (int) (bx1 + xDisplacement); // right of the edge
|
|
||||||
labelPointOffset.setLocation(textX, textY);
|
labelPointOffset.setLocation(textX, textY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EdgeLabelRenderer labelRenderer = rc.getEdgeLabelRenderer();
|
|
||||||
Font font = rc.getEdgeFontTransformer().apply(e);
|
|
||||||
boolean isSelected = rc.getPickedEdgeState().isPicked(e);
|
|
||||||
Component component = labelRenderer.getEdgeLabelRendererComponent(rc.getScreenDevice(),
|
|
||||||
text, font, isSelected, e);
|
|
||||||
|
|
||||||
Dimension d = component.getPreferredSize();
|
Dimension d = component.getPreferredSize();
|
||||||
|
|
||||||
@@ -142,7 +147,37 @@ class DNLEdgeLabelRenderer<V extends FGVertex, E extends FGEdge>
|
|||||||
|
|
||||||
g.setTransform(xform);
|
g.setTransform(xform);
|
||||||
g.draw(component, rc.getRendererPane(), 0, 0, d.width, d.height, true);
|
g.draw(component, rc.getRendererPane(), 0, 0, d.width, d.height, true);
|
||||||
|
|
||||||
g.setTransform(old);
|
g.setTransform(old);
|
||||||
|
|
||||||
|
// debug
|
||||||
|
//labelArticulations(component, g, rc, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") // used during debug
|
||||||
|
private void labelArticulations(Component component, GraphicsDecorator g,
|
||||||
|
RenderContext<V, E> rc, E e) {
|
||||||
|
|
||||||
|
int offset = 5;
|
||||||
|
int counter = 1;
|
||||||
|
List<Point2D> points = e.getArticulationPoints();
|
||||||
|
for (Point2D p : points) {
|
||||||
|
|
||||||
|
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
|
||||||
|
p = multiLayerTransformer.transform(Layer.LAYOUT, p);
|
||||||
|
|
||||||
|
EdgeLabelRenderer labelRenderer = rc.getEdgeLabelRenderer();
|
||||||
|
Font font = rc.getEdgeFontTransformer().apply(e);
|
||||||
|
boolean isSelected = rc.getPickedEdgeState().isPicked(e);
|
||||||
|
component = labelRenderer.getEdgeLabelRendererComponent(rc.getScreenDevice(),
|
||||||
|
"p" + counter++, font, isSelected, e);
|
||||||
|
|
||||||
|
Dimension d = component.getPreferredSize();
|
||||||
|
AffineTransform old = g.getTransform();
|
||||||
|
AffineTransform xform = new AffineTransform(old);
|
||||||
|
xform.translate(p.getX() + offset, p.getY());
|
||||||
|
g.setTransform(xform);
|
||||||
|
g.draw(component, rc.getRendererPane(), 0, 0, d.width, d.height, true);
|
||||||
|
g.setTransform(old);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,11 +56,17 @@ import ghidra.util.task.TaskMonitor;
|
|||||||
* <p>Edges returning to the default code flow are painted lighter to de-emphasize them. This
|
* <p>Edges returning to the default code flow are painted lighter to de-emphasize them. This
|
||||||
* could be made into an option.
|
* could be made into an option.
|
||||||
*
|
*
|
||||||
|
* <p>Edge routing herein defaults to 'simple routing'; 'complex routing' is a user option.
|
||||||
|
* Simple routing will reduce edge noise as much as possible by combining/overlapping edges that
|
||||||
|
* flow towards the bottom of the function (returning code flow). Also, edges may fall behind
|
||||||
|
* vertices for some functions. Complex routing allows the user to visually follow the flow
|
||||||
|
* of an individual edge. Complex routing will prevent edges from overlapping and will route
|
||||||
|
* edges around vertices. Simple routing is better when the layout of the vertices is
|
||||||
|
* important to the user; complex routing is better when edges/relationships are more
|
||||||
|
* important to the user.
|
||||||
|
*
|
||||||
* TODO ideas:
|
* TODO ideas:
|
||||||
* -paint fallthrough differently for all, or just for those returning to the baseline
|
* -paint fallthrough differently for all, or just for those returning to the baseline
|
||||||
* -using 'edge routing mode' that avoids vertices can be tweaked so that all returning edges also
|
|
||||||
* will not overlap (do this by keeping some sort of mapping for a given edge's column); when
|
|
||||||
* not routing edges around vertices, the edges intentionally overlap, to reduce noise
|
|
||||||
*/
|
*/
|
||||||
public class DecompilerNestedLayout extends AbstractFGLayout {
|
public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
|
|
||||||
@@ -265,9 +271,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
Column loopEndColumn = layoutToGridMap.nextColumn(outermostCol);
|
Column loopEndColumn = layoutToGridMap.nextColumn(outermostCol);
|
||||||
List<Point2D> articulations = routeLoopEdge(start, end, loopEndColumn);
|
List<Point2D> articulations = routeLoopEdge(start, end, loopEndColumn);
|
||||||
newEdgeArticulations.put(e, articulations);
|
newEdgeArticulations.put(e, articulations);
|
||||||
|
continue;
|
||||||
vertex2dFactory.dispose();
|
|
||||||
return newEdgeArticulations;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +344,11 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
//
|
//
|
||||||
|
|
||||||
int delta = start.rowIndex - end.rowIndex;
|
int delta = start.rowIndex - end.rowIndex;
|
||||||
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
int multiplier = EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
if (useSimpleRouting()) {
|
||||||
|
multiplier = 1; // we allow edges to overlap with 'simple routing'
|
||||||
|
}
|
||||||
|
int distanceSpacing = delta * multiplier;
|
||||||
|
|
||||||
// Condensing Note: we have guilty knowledge that our parent class my condense the
|
// Condensing Note: we have guilty knowledge that our parent class my condense the
|
||||||
// vertices and edges towards the center of the graph after we calculate positions.
|
// vertices and edges towards the center of the graph after we calculate positions.
|
||||||
@@ -375,6 +383,9 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
double y2 = y1;
|
double y2 = y1;
|
||||||
articulations.add(new Point2D.Double(x2, y2));
|
articulations.add(new Point2D.Double(x2, y2));
|
||||||
|
|
||||||
|
Column column = vertex2dFactory.getColumn(x1);
|
||||||
|
routeAroundColumnVertices(start, end, column.index, vertex2dFactory, articulations, x2);
|
||||||
|
|
||||||
double x3 = x2;
|
double x3 = x2;
|
||||||
double y3 = end.getBottom() - VERTEX_BORDER_THICKNESS;
|
double y3 = end.getBottom() - VERTEX_BORDER_THICKNESS;
|
||||||
|
|
||||||
@@ -386,9 +397,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
y3 = Math.max(y3, endYLimit);
|
y3 = Math.max(y3, endYLimit);
|
||||||
articulations.add(new Point2D.Double(x3, y3));
|
articulations.add(new Point2D.Double(x3, y3));
|
||||||
|
|
||||||
Column column = vertex2dFactory.getColumn(x1);
|
|
||||||
routeAroundColumnVertices(start, end, column.index, vertex2dFactory, articulations, x3);
|
|
||||||
|
|
||||||
double x4 = end.getX();
|
double x4 = end.getX();
|
||||||
double y4 = y3;
|
double y4 = y3;
|
||||||
articulations.add(new Point2D.Double(x4, y4)); // point is hidden behind the vertex
|
articulations.add(new Point2D.Double(x4, y4)); // point is hidden behind the vertex
|
||||||
@@ -431,7 +439,11 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
//
|
//
|
||||||
|
|
||||||
int delta = end.rowIndex - start.rowIndex;
|
int delta = end.rowIndex - start.rowIndex;
|
||||||
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
int multiplier = EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
if (useSimpleRouting()) {
|
||||||
|
multiplier = 1; // we allow edges to overlap with 'simple routing'
|
||||||
|
}
|
||||||
|
int distanceSpacing = delta * multiplier;
|
||||||
|
|
||||||
double x1 = start.getX() - VERTEX_BORDER_THICKNESS; // start at the center
|
double x1 = start.getX() - VERTEX_BORDER_THICKNESS; // start at the center
|
||||||
|
|
||||||
@@ -462,7 +474,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
|
|
||||||
// restrict x from moving past the end vertex center x
|
// restrict x from moving past the end vertex center x
|
||||||
int edgeOffset = 0;
|
int edgeOffset = 0;
|
||||||
if (getLayoutOptions().useEdgeRoutingAroundVertices()) {
|
if (usesEdgeArticulations()) {
|
||||||
// for now, only offset edge lines when we are performing complex routing
|
// for now, only offset edge lines when we are performing complex routing
|
||||||
edgeOffset = EDGE_OFFSET_INCOMING_FROM_LEFT;
|
edgeOffset = EDGE_OFFSET_INCOMING_FROM_LEFT;
|
||||||
}
|
}
|
||||||
@@ -489,7 +501,11 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
//
|
//
|
||||||
|
|
||||||
int delta = end.rowIndex - start.rowIndex;
|
int delta = end.rowIndex - start.rowIndex;
|
||||||
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
int multiplier = EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
if (useSimpleRouting()) {
|
||||||
|
multiplier = 1; // we allow edges to overlap with 'simple routing'
|
||||||
|
}
|
||||||
|
int distanceSpacing = delta * multiplier;
|
||||||
|
|
||||||
double startRightX = start.getRight();
|
double startRightX = start.getRight();
|
||||||
double x1 = startRightX - VERTEX_BORDER_THICKNESS; // start at the end
|
double x1 = startRightX - VERTEX_BORDER_THICKNESS; // start at the end
|
||||||
@@ -540,14 +556,15 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
private void routeAroundColumnVertices(Vertex2d start, Vertex2d end, int column,
|
private void routeAroundColumnVertices(Vertex2d start, Vertex2d end, int column,
|
||||||
Vertex2dFactory vertex2dFactory, List<Point2D> articulations, double edgeX) {
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations, double edgeX) {
|
||||||
|
|
||||||
if (!getLayoutOptions().useEdgeRoutingAroundVertices()) {
|
if (useSimpleRouting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean goingDown = true;
|
||||||
int startRow = start.rowIndex;
|
int startRow = start.rowIndex;
|
||||||
int endRow = end.rowIndex;
|
int endRow = end.rowIndex;
|
||||||
|
|
||||||
if (startRow > endRow) { // going upwards
|
if (startRow > endRow) { // going upwards
|
||||||
|
goingDown = false;
|
||||||
endRow = start.rowIndex;
|
endRow = start.rowIndex;
|
||||||
startRow = end.rowIndex;
|
startRow = end.rowIndex;
|
||||||
}
|
}
|
||||||
@@ -578,15 +595,20 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double centerX = otherVertex.getX();
|
double centerX = otherVertex.getX();
|
||||||
boolean isLeft = edgeX < centerX;
|
boolean goingLeft = edgeX < centerX;
|
||||||
|
|
||||||
// no need to check the 'y' value, as the end vertex is below this one
|
if (!goingDown) {
|
||||||
|
// for now, any time an edge goes up, we route it to the right
|
||||||
|
goingLeft = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLeft) {
|
VertexClipper vertexClipper = new VertexClipper(goingLeft, goingDown);
|
||||||
if (edgeX >= otherVertex.getLeft()) {
|
|
||||||
// less than center; after the start of the vertex
|
|
||||||
|
|
||||||
/*
|
// no need to check the 'y' value, as the end vertex is above/below this one
|
||||||
|
if (vertexClipper.isClippingX(otherVertex, edgeX)) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
Must route around this vertex - new points:
|
Must route around this vertex - new points:
|
||||||
-p1 - just above the intersection point
|
-p1 - just above the intersection point
|
||||||
-p2 - just past the left edge
|
-p2 - just past the left edge
|
||||||
@@ -599,89 +621,60 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
| | |
|
| | |
|
||||||
| '-----'
|
| '-----'
|
||||||
'---.
|
'---.
|
||||||
|
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// p1 - same x; y just above vertex
|
||||||
|
double x = edgeX;
|
||||||
|
double y = vertexClipper.getTopOffset(otherVertex, vertexToEdgeOffset);
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// Maybe merge points if they are too close together. Visually, many lines
|
||||||
|
// moving around intersecting vertices looks busy. When the intersecting
|
||||||
|
// vertices are close together, we remove some of the articulations in order to
|
||||||
|
// smooth out the edges.
|
||||||
|
if (articulations.size() > 2) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
The last articulation is the one added before this method was called, which
|
||||||
|
lies just below the intersecting vertex. The articulation before that is
|
||||||
|
the one that is the one that is sending the x value straight into the
|
||||||
|
intersecting vertex. Delete that point as well so that the entire edge is
|
||||||
|
shifted to the outside of the intersecting vertex. This will get repeated
|
||||||
|
for each vertex that is intersecting.
|
||||||
*/
|
*/
|
||||||
|
Point2D previousArticulation = articulations.get(articulations.size() - 2);
|
||||||
// p1 - same x; y just above vertex
|
int closenessHeight = 50;
|
||||||
double x = edgeX;
|
double previousY = previousArticulation.getY();
|
||||||
double y = otherVertex.getTop() - vertexToEdgeOffset;
|
if (vertexClipper.isTooCloseY(y, previousY, closenessHeight)) {
|
||||||
articulations.add(new Point2D.Double(x, y));
|
articulations.remove(articulations.size() - 1);
|
||||||
|
articulations.remove(articulations.size() - 1);
|
||||||
// maybe merge points if they are too close together
|
Point2D newPrevious = articulations.get(articulations.size() - 1);
|
||||||
if (articulations.size() > 2) {
|
y = newPrevious.getY();
|
||||||
Point2D previousArticulation = articulations.get(articulations.size() - 2);
|
|
||||||
int closenessHeight = 50;
|
|
||||||
double previousY = previousArticulation.getY();
|
|
||||||
if (y - previousY < closenessHeight) {
|
|
||||||
// remove our first articulation and the one before that so that
|
|
||||||
// the get merged into on line on the outside of the vertex
|
|
||||||
articulations.remove(articulations.size() - 1);
|
|
||||||
articulations.remove(articulations.size() - 1);
|
|
||||||
Point2D newPrevious = articulations.get(articulations.size() - 1);
|
|
||||||
y = newPrevious.getY();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// p2 - move over; same y
|
|
||||||
int offset = Math.max(vertexToEdgeOffset, distanceSpacing);
|
|
||||||
offset *= exaggerationFactor;
|
|
||||||
x = otherVertex.getLeft() - offset;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
|
|
||||||
// p3 - same x; move y below the vertex
|
|
||||||
y = otherVertex.getBottom() + vertexToEdgeOffset;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
|
|
||||||
// p4 - move over back to our original x; same y
|
|
||||||
x = edgeX;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else { // right
|
|
||||||
|
|
||||||
double otherEnd = otherVertex.getRight();
|
// p2 - move over; same y
|
||||||
if (edgeX < otherEnd) {
|
int offset = Math.max(vertexToEdgeOffset, distanceSpacing);
|
||||||
// greater than center; before the end of the vertex
|
offset *= exaggerationFactor;
|
||||||
// (see not above for routing info)
|
x = vertexClipper.getSideOffset(otherVertex, offset);
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
// p1 - same x; y just above vertex
|
// p3 - same x; move y above/below the vertex
|
||||||
double x = edgeX;
|
y = vertexClipper.getBottomOffset(otherVertex, vertexToEdgeOffset);
|
||||||
double y = otherVertex.getTop() - vertexToEdgeOffset;
|
articulations.add(new Point2D.Double(x, y));
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
|
|
||||||
// maybe merge points if they are too close together
|
// p4 - move over back to our original x; same y
|
||||||
if (articulations.size() > 2) {
|
x = edgeX;
|
||||||
Point2D previousArticulation = articulations.get(articulations.size() - 2);
|
articulations.add(new Point2D.Double(x, y));
|
||||||
int closenessHeight = 50;
|
|
||||||
double previousY = previousArticulation.getY();
|
|
||||||
if (y - previousY < closenessHeight) {
|
|
||||||
// remove our first articulation and the one before that so that
|
|
||||||
// the get merged into on line on the outside of the vertex
|
|
||||||
articulations.remove(articulations.size() - 1);
|
|
||||||
articulations.remove(articulations.size() - 1);
|
|
||||||
Point2D newPrevious = articulations.get(articulations.size() - 1);
|
|
||||||
y = newPrevious.getY();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// p2 - move over; same y
|
|
||||||
int offset = Math.max(vertexToEdgeOffset, distanceSpacing);
|
|
||||||
offset *= exaggerationFactor;
|
|
||||||
x = otherEnd + offset;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
|
|
||||||
// p3 - same x; move y below the vertex
|
|
||||||
y = otherVertex.getBottom() + vertexToEdgeOffset;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
|
|
||||||
// p4 - move over back to our original x; same y
|
|
||||||
x = edgeX;
|
|
||||||
articulations.add(new Point2D.Double(x, y));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean useSimpleRouting() {
|
||||||
|
return !getLayoutOptions().useEdgeRoutingAroundVertices();
|
||||||
|
}
|
||||||
|
|
||||||
private List<Point2D> routeLoopEdge(Vertex2d start, Vertex2d end, Column loopEndColumn) {
|
private List<Point2D> routeLoopEdge(Vertex2d start, Vertex2d end, Column loopEndColumn) {
|
||||||
|
|
||||||
// going backwards
|
// going backwards
|
||||||
@@ -691,8 +684,17 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
int halfWidth = loopEndColumn.getPaddedWidth(isCondensedLayout()) >> 1;
|
int halfWidth = loopEndColumn.getPaddedWidth(isCondensedLayout()) >> 1;
|
||||||
double x = loopEndColumn.x + halfWidth; // middle of the column
|
double x = loopEndColumn.x + halfWidth; // middle of the column
|
||||||
|
|
||||||
Point2D startVertexPoint = start.center;
|
int startRow = start.rowIndex;
|
||||||
|
int endRow = end.rowIndex;
|
||||||
|
if (startRow > endRow) { // going upwards
|
||||||
|
endRow = start.rowIndex;
|
||||||
|
startRow = end.rowIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delta = endRow - startRow;
|
||||||
|
x += delta; // adding the delta makes overlap less likely
|
||||||
|
|
||||||
|
Point2D startVertexPoint = start.center;
|
||||||
double y1 = startVertexPoint.getY();
|
double y1 = startVertexPoint.getY();
|
||||||
Point2D first = new Point2D.Double(x, y1);
|
Point2D first = new Point2D.Double(x, y1);
|
||||||
articulations.add(first);
|
articulations.add(first);
|
||||||
@@ -745,7 +747,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void debug(String text) {
|
private void debug(String text) {
|
||||||
// System.err.println(text);
|
// System.err.println(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printParts(int depth, BlockGraph block) {
|
private void printParts(int depth, BlockGraph block) {
|
||||||
@@ -937,6 +939,59 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates knowledge of edge direction (up/down, left/right) and uses that knowledge
|
||||||
|
* to report vertex offsets from the appropriate side and top/bottom
|
||||||
|
*/
|
||||||
|
private class VertexClipper {
|
||||||
|
|
||||||
|
boolean goingLeft;
|
||||||
|
boolean goingDown;
|
||||||
|
|
||||||
|
VertexClipper(boolean isLeft, boolean isBottom) {
|
||||||
|
this.goingLeft = isLeft;
|
||||||
|
this.goingDown = isBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getSide(Vertex2d v) {
|
||||||
|
return goingLeft ? v.getLeft() : v.getRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getTopOffset(Vertex2d v, int offset) {
|
||||||
|
return goingDown ? v.getTop() - offset : v.getBottom() + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getBottomOffset(Vertex2d v, int offset) {
|
||||||
|
return goingDown ? v.getBottom() + offset : v.getTop() - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSideOffset(Vertex2d v, int offset) {
|
||||||
|
|
||||||
|
double side = getSide(v);
|
||||||
|
if (goingLeft) {
|
||||||
|
return side - offset;
|
||||||
|
}
|
||||||
|
return side + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTooCloseY(double topY, double bottomY, double threshold) {
|
||||||
|
double delta = goingDown ? topY - bottomY : bottomY - topY;
|
||||||
|
return delta < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isClippingX(Vertex2d v, double x) {
|
||||||
|
|
||||||
|
double side = getSide(v);
|
||||||
|
if (goingLeft) {
|
||||||
|
return x >= side;
|
||||||
|
}
|
||||||
|
return x < side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating and caching {@link Vertex2d} objects
|
||||||
|
*/
|
||||||
private class Vertex2dFactory {
|
private class Vertex2dFactory {
|
||||||
|
|
||||||
private VisualGraphVertexShapeTransformer<FGVertex> vertexShaper;
|
private VisualGraphVertexShapeTransformer<FGVertex> vertexShaper;
|
||||||
@@ -1216,7 +1271,8 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return PcodeBlock.typeToName(pcodeBlock.getType()) + " - " + pcodeBlock.getStart();
|
return PcodeBlock.typeToName(pcodeBlock.getType()) + " - " + getName() + " - " +
|
||||||
|
pcodeBlock.getStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ import resources.icons.EmptyIcon;
|
|||||||
* drop-down icon that allows users to change the state of the button. Also, by default, as
|
* drop-down icon that allows users to change the state of the button. Also, by default, as
|
||||||
* the user presses the button, it will execute the action corresponding to the current
|
* the user presses the button, it will execute the action corresponding to the current
|
||||||
* state.
|
* state.
|
||||||
|
*
|
||||||
|
* <p>Warning: if you use this action in a toolbar, then be sure to call the
|
||||||
|
* {@link #MultiStateDockingAction(String, String, boolean) correct constructor}. If you call
|
||||||
|
* another constructor, or pass false for this boolean above, your
|
||||||
|
* {@link #doActionPerformed(ActionContext)} method will get called twice.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the user data
|
* @param <T> the type of the user data
|
||||||
* @see MultiActionDockingAction
|
* @see MultiActionDockingAction
|
||||||
@@ -56,10 +61,25 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
|||||||
// stub for toolbar actions
|
// stub for toolbar actions
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this constructor with this action will not be added to a toolbar
|
||||||
|
*
|
||||||
|
* @param name the action name
|
||||||
|
* @param owner the owner
|
||||||
|
* @see #MultiStateDockingAction(String, String, boolean)
|
||||||
|
*/
|
||||||
public MultiStateDockingAction(String name, String owner) {
|
public MultiStateDockingAction(String name, String owner) {
|
||||||
this(name, owner, false);
|
this(name, owner, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this constructor explicitly when this action is used in a toolbar, passing true
|
||||||
|
* for <code>isToolbarAction</code> (see the javadoc header note).
|
||||||
|
*
|
||||||
|
* @param name the action name
|
||||||
|
* @param owner the owner
|
||||||
|
* @param isToolbarAction true if this action is a toolbar action
|
||||||
|
*/
|
||||||
protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) {
|
protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) {
|
||||||
super(name, owner);
|
super(name, owner);
|
||||||
multiActionGenerator = context -> getStateActions();
|
multiActionGenerator = context -> getStateActions();
|
||||||
@@ -87,6 +107,9 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
|||||||
* <p>
|
* <p>
|
||||||
* Also, if the parameter is true, then the button will behave like a button in terms of
|
* Also, if the parameter is true, then the button will behave like a button in terms of
|
||||||
* mouse feedback. If false, then the button will behave more like a label.
|
* mouse feedback. If false, then the button will behave more like a label.
|
||||||
|
*
|
||||||
|
* @param doPerformAction true to call {@link #doActionPerformed(ActionContext)} when the
|
||||||
|
* user presses the button for this action (not the drop-down menu; see above)
|
||||||
*/
|
*/
|
||||||
public void setPerformActionOnPrimaryButtonClick(boolean doPerformAction) {
|
public void setPerformActionOnPrimaryButtonClick(boolean doPerformAction) {
|
||||||
performActionOnPrimaryButtonClick = doPerformAction;
|
performActionOnPrimaryButtonClick = doPerformAction;
|
||||||
@@ -116,6 +139,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
|||||||
* This is the callback to be overridden when the child wishes to respond to user button
|
* This is the callback to be overridden when the child wishes to respond to user button
|
||||||
* presses that are on the button and not the drop-down. This will only be called if
|
* presses that are on the button and not the drop-down. This will only be called if
|
||||||
* {@link #performActionOnPrimaryButtonClick} is true.
|
* {@link #performActionOnPrimaryButtonClick} is true.
|
||||||
|
*
|
||||||
|
* @param context the action context
|
||||||
*/
|
*/
|
||||||
protected void doActionPerformed(ActionContext context) {
|
protected void doActionPerformed(ActionContext context) {
|
||||||
// override me to do work
|
// override me to do work
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ import ghidra.graph.viewer.layout.*;
|
|||||||
* This was created to add the ability to paint selected vertices above other vertices. We need
|
* This was created to add the ability to paint selected vertices above other vertices. We need
|
||||||
* this since the Jung Graph has no notion of Z-order and thus does not let us specify that any
|
* this since the Jung Graph has no notion of Z-order and thus does not let us specify that any
|
||||||
* particular vertex should be above another one.
|
* particular vertex should be above another one.
|
||||||
|
*
|
||||||
|
* @param <V> the vertex type
|
||||||
|
* @param <E> the edge type
|
||||||
*/
|
*/
|
||||||
public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
||||||
extends edu.uci.ics.jung.visualization.renderers.BasicRenderer<V, E> {
|
extends edu.uci.ics.jung.visualization.renderers.BasicRenderer<V, E> {
|
||||||
|
|||||||
Reference in New Issue
Block a user