diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdge.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdge.java index 459740c9c0..03a1bf3225 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdge.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdge.java @@ -19,6 +19,16 @@ import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.graph.viewer.VisualEdge; import ghidra.program.model.symbol.FlowType; +/** + * This version of the {@link VisualEdge} adds a few methods. + * + *

The {@link #setDefaultAlpha(double)} method was added here instead of the base interface, as it + * was not needed any higher at the time of writing. It can be pulled-up, but there is most + * likely a better pattern for specifying visual attributes of an edge. If we find we need more + * methods like this, then that is a good time for a refactor to change how we manipulate + * rending attributes from various parts of the API (e.g., from the layouts and from animation + * jobs). + */ public interface FGEdge extends VisualEdge { public FlowType getFlowType(); @@ -27,6 +37,30 @@ public interface FGEdge extends VisualEdge { public void setLabel(String label); + /** + * Set this edge's base alpha, which determines how much of the edge is visible/see through. + * 0 is completely transparent. + * + *

This differs from {@link #setAlpha(double)} in that the latter is used for + * temporary display effects. This method is used to set the alpha value for the edge when + * it is not part of a temporary display effect. + * + * @param alpha the alpha value + */ + public void setDefaultAlpha(double alpha); + + /** + * Set this edge's base alpha, which determines how much of the edge is visible/see through. + * 0 is completely transparent. + * + *

This differs from {@link #getAlpha()} in that the latter is used for + * temporary display effects. This method is used to set the alpha value for the edge when + * it is not part of a temporary display effect. + * + * @return the alpha value + */ + public double getDefaultAlpha(); + @SuppressWarnings("unchecked") // Suppressing warning on the return type; we know our class is the right type @Override diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java index 442af5cb77..1024265903 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java @@ -34,10 +34,12 @@ public class FGEdgeImpl implements FGEdge { boolean doHashCode = true; int hashCode; - private boolean inActivePath = false; + private boolean inHoveredPath = false; + private boolean inFocusedPath = false; private boolean selected = false; private double emphasis = 0D; - private double alpha = 1D; + private double defaultAlpha = 1D; + private double alpha = defaultAlpha; private String edgeLabel = null; public FGEdgeImpl(FGVertex startVertex, FGVertex destinationVertex, FlowType flowType, @@ -50,13 +52,23 @@ public class FGEdgeImpl implements FGEdge { } @Override - public boolean isInActivePath() { - return inActivePath; + public boolean isInHoveredVertexPath() { + return inHoveredPath; } @Override - public void setInActivePath(boolean inActivePath) { - this.inActivePath = inActivePath; + public boolean isInFocusedVertexPath() { + return inFocusedPath; + } + + @Override + public void setInHoveredVertexPath(boolean inPath) { + this.inHoveredPath = inPath; + } + + @Override + public void setInFocusedVertexPath(boolean inPath) { + this.inFocusedPath = inPath; } @Override @@ -89,6 +101,17 @@ public class FGEdgeImpl implements FGEdge { return alpha; } + @Override + public void setDefaultAlpha(double alpha) { + this.defaultAlpha = alpha; + this.alpha = alpha; + } + + @Override + public double getDefaultAlpha() { + return defaultAlpha; + } + @Override public List getArticulationPoints() { return layoutArticulationPoints; @@ -135,7 +158,9 @@ public class FGEdgeImpl implements FGEdge { newEdge.layoutArticulationPoints = newPoints; newEdge.alpha = alpha; - newEdge.inActivePath = inActivePath; + newEdge.defaultAlpha = defaultAlpha; + newEdge.inHoveredPath = inHoveredPath; + newEdge.inFocusedPath = inFocusedPath; newEdge.selected = selected; return newEdge; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/AbstractGroupingFunctionGraphJob.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/AbstractGroupingFunctionGraphJob.java index 16535bb732..43aba57889 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/AbstractGroupingFunctionGraphJob.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/AbstractGroupingFunctionGraphJob.java @@ -68,7 +68,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG */ AbstractGroupingFunctionGraphJob(FGController controller, GroupedFunctionGraphVertex groupVertex, Set newVertices, - Set verticesToRemove, boolean relayloutOverride, boolean useAnimation) { + Set verticesToRemove, boolean relayoutOverride, boolean useAnimation) { super(controller, useAnimation); @@ -87,7 +87,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG FunctionGraphOptions options = controller.getFunctionGraphOptions(); RelayoutOption relayoutOption = options.getRelayoutOption(); this.relayout = relayoutOption == VERTEX_GROUPING_CHANGES || relayoutOption == ALWAYS || - relayloutOverride; + relayoutOverride; } @Override @@ -158,7 +158,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG return positions; } - /** + /* * Subclasses must return locations for vertices. This method will be called when no * relayout will be performed. * @@ -191,15 +191,21 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG @Override protected void updateOpacity(double percentComplete) { + double oldComponentsAlpha = 1.0 - percentComplete; Collection vertices = getVerticesToBeRemoved(); for (FGVertex vertex : vertices) { + vertex.setAlpha(oldComponentsAlpha); Collection edges = getEdges(vertex); for (FGEdge edge : edges) { - edge.setAlpha(oldComponentsAlpha); + + // don't go past the alpha when removing + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(oldComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } } @@ -210,7 +216,11 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG Collection edges = getEdges(vertex); for (FGEdge edge : edges) { - edge.setAlpha(newComponentsAlpha); + + // don't go past the alpha when adding + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(newComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } } } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/MergeVertexFunctionGraphJob.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/MergeVertexFunctionGraphJob.java index a434da90d1..357430b21f 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/MergeVertexFunctionGraphJob.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/MergeVertexFunctionGraphJob.java @@ -19,6 +19,7 @@ import java.awt.Rectangle; import java.awt.geom.Point2D; import java.util.*; +import org.apache.commons.collections4.IterableUtils; import org.jdesktop.animation.timing.Animator; import org.jdesktop.animation.timing.interpolation.PropertySetter; @@ -197,22 +198,25 @@ public class MergeVertexFunctionGraphJob extends AbstractAnimatorJob { parentVertex.setAlpha(oldComponentsAlpha); childVertex.setAlpha(oldComponentsAlpha); - Collection edges = getEdges(parentVertex); + Iterable edges = + IterableUtils.chainedIterable(getEdges(parentVertex), getEdges(childVertex)); for (FGEdge edge : edges) { - edge.setAlpha(oldComponentsAlpha); - } - edges = getEdges(childVertex); - for (FGEdge edge : edges) { - edge.setAlpha(oldComponentsAlpha); + // don't go past the alpha when removing + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(oldComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } double newComponentsAlpha = percentComplete; mergedVertex.setAlpha(newComponentsAlpha); - edges = getEdges(mergedVertex); for (FGEdge edge : edges) { - edge.setAlpha(newComponentsAlpha); + + // don't go past the alpha when adding + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(newComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/SplitVertexFunctionGraphJob.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/SplitVertexFunctionGraphJob.java index a0d8d25cda..6880a0ea07 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/SplitVertexFunctionGraphJob.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/SplitVertexFunctionGraphJob.java @@ -19,6 +19,7 @@ import java.awt.Rectangle; import java.awt.geom.Point2D; import java.util.*; +import org.apache.commons.collections4.IterableUtils; import org.jdesktop.animation.timing.Animator; import org.jdesktop.animation.timing.interpolation.PropertySetter; @@ -114,9 +115,21 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob { controller.synchronizeProgramLocationAfterEdit(); + restoreEdgeDisplayAttributes(); + viewer.repaint(); } + private void restoreEdgeDisplayAttributes() { + + Iterable edges = + IterableUtils.chainedIterable(getEdges(parentVertex), getEdges(childVertex)); + for (FGEdge edge : edges) { + double alpha = edge.getDefaultAlpha(); + edge.setAlpha(alpha); + } + } + public void setPercentComplete(double percentComplete) { trace("setPercentComplete() callback: " + percentComplete); updateNewVertexPositions(percentComplete); @@ -214,7 +227,11 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob { Collection edges = getEdges(toSplitVertex); for (FGEdge edge : edges) { - edge.setAlpha(oldComponentsAlpha); + + // don't go past the alpha when removing + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(oldComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } double newComponentsAlpha = percentComplete; @@ -223,12 +240,19 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob { edges = getEdges(parentVertex); for (FGEdge edge : edges) { - edge.setAlpha(newComponentsAlpha); + + // don't go past the alpha when adding + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(newComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } edges = getEdges(childVertex); for (FGEdge edge : edges) { - edge.setAlpha(newComponentsAlpha); + // don't go past the alpha when adding + double defaultAlpha = edge.getDefaultAlpha(); + double alpha = Math.min(newComponentsAlpha, defaultAlpha); + edge.setAlpha(alpha); } } diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java index c9c6dfa189..105d854aac 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java @@ -21,6 +21,7 @@ import java.awt.Color; import java.awt.geom.Point2D; import java.util.*; +import org.apache.commons.collections4.IterableUtils; import org.junit.*; import docking.ActionContext; @@ -37,6 +38,7 @@ import ghidra.framework.plugintool.util.PluginException; import ghidra.graph.viewer.options.RelayoutOption; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; +import util.CollectionUtils; public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { @@ -809,14 +811,41 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { verifyDefaultColor(v2); } + @Test + public void testEdgeDefaultAlphaPersistsAfterGrouping() { + + graphFunction("01002cf5"); + + FGVertex v1 = vertex("01002cf5"); + FGVertex v2 = vertex("01002d0f"); + + FunctionGraph graph = getFunctionGraph(); + Iterable edges = graph.getEdges(v1, v2); + assertEquals(1, IterableUtils.size(edges)); + FGEdge edge = CollectionUtils.any(edges); + + Double alpha = edge.getAlpha(); + assertTrue(alpha < 1.0); // this is the default flow + + GroupedFunctionGraphVertex group = group("A", v1, v2); + ungroup(group); + + edges = graph.getEdges(v1, v2); + assertEquals(1, IterableUtils.size(edges)); + edge = CollectionUtils.any(edges); + + Double alphAfterGroup = edge.getAlpha(); + assertEquals(alpha, alphAfterGroup); + } + @Test public void testSymbolAddedWhenGrouped_SymbolOutsideOfGroupNode() { // TODO } - //================================================================================================== - // Private Methods - //================================================================================================== +//================================================================================================== +// Private Methods +//================================================================================================== // @formatter:off @Override diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/graph/layout/TestFGLayoutProvider.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/graph/layout/TestFGLayoutProvider.java index af3235cc0d..3f8aea4d1c 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/graph/layout/TestFGLayoutProvider.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/graph/layout/TestFGLayoutProvider.java @@ -313,7 +313,7 @@ public class TestFGLayoutProvider extends FGLayoutProvider { } else if (startCol.index > endCol.index) { // flow return - e.setAlpha(.25); + e.setDefaultAlpha(.25); Shape shape = transformer.apply(startVertex); Rectangle bounds = shape.getBounds(); @@ -338,7 +338,7 @@ public class TestFGLayoutProvider extends FGLayoutProvider { else { // same column--nothing to route // straight line, which is the default - e.setAlpha(.25); + e.setDefaultAlpha(.25); } newEdgeArticulations.put(e, articulations); } diff --git a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DecompilerDominanceArticulatedEdgeTransformer.java b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java similarity index 89% rename from Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DecompilerDominanceArticulatedEdgeTransformer.java rename to Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java index 832fed273a..78f780469b 100644 --- a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DecompilerDominanceArticulatedEdgeTransformer.java +++ b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java @@ -17,7 +17,7 @@ package ghidra.app.plugin.core.functiongraph.graph.jung.renderer; import ghidra.app.plugin.core.functiongraph.graph.FGEdge; -public class DecompilerDominanceArticulatedEdgeTransformer extends FGArticulatedEdgeTransformer { +public class DNLArticulatedEdgeTransformer extends FGArticulatedEdgeTransformer { @Override public int getOverlapOffset(FGEdge edge) { diff --git a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java index 39062c64c8..e2f8247885 100644 --- a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java +++ b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java @@ -34,7 +34,7 @@ import ghidra.app.decompiler.DecompInterface; import ghidra.app.decompiler.DecompileOptions; import ghidra.app.plugin.core.functiongraph.graph.FGEdge; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; -import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.DecompilerDominanceArticulatedEdgeTransformer; +import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.DNLArticulatedEdgeTransformer; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex; import ghidra.graph.VisualGraph; @@ -103,7 +103,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout { @Override public Function getEdgeShapeTransformer() { - return new DecompilerDominanceArticulatedEdgeTransformer(); + return new DNLArticulatedEdgeTransformer(); } @Override @@ -715,7 +715,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout { // assumption: edges that move to the left in this layout are return flows that happen // after the code block has been executed. We dim those a bit so that they // produce less clutter. - e.setAlpha(.25); + e.setDefaultAlpha(.25); } private Column getOutermostCol(LayoutLocationMap layoutLocations, diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java index 0aac7fdce1..da370f41de 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java @@ -61,7 +61,7 @@ public class ColorUtils { // This can be addressed with some polar plotting: // Let the hue be the degree, and the saturation be the radius, so that the range // of values covers an area of a circle of radius 1. Let the circle be centered - // at the origin. Plot the two colors and compute their distance in euclidean + // at the origin. Plot the two colors and compute their distance in Euclidean // space. // Start by plotting the given background @@ -76,7 +76,7 @@ public class ColorUtils { // It's not pleasant to put two highly-saturated colors next to each other // Because of this restriction, we know that the maximum distance the two plotted - // points can be from eachother is 1, because their total distance to the center + // points can be from each other is 1, because their total distance to the center // is at most 1. vals[1] = 1.0f - vals[1]; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/DefaultVisualGraph.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/DefaultVisualGraph.java index 533dbfba6d..513c6369e8 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/DefaultVisualGraph.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/DefaultVisualGraph.java @@ -15,8 +15,6 @@ */ package ghidra.graph.graphs; -import static com.google.common.collect.Iterables.concat; -import static com.google.common.collect.Iterables.filter; import static util.CollectionUtils.nonNull; import java.awt.Point; @@ -206,9 +204,11 @@ public abstract class DefaultVisualGraph outs = nonNull(getOutEdges(start)); Collection ins = nonNull(getInEdges(end)); + Set unique = new HashSet<>(); + unique.addAll(outs); + unique.addAll(ins); - Iterable concatenated = concat(outs, ins); - Iterable filtered = filter(concatenated, e -> { + Iterable filtered = IterableUtils.filteredIterable(unique, e -> { return e.getStart().equals(start) && e.getEnd().equals(end); }); return filtered; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java index 5f073cabca..957753e2ae 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java @@ -699,9 +699,10 @@ public class GraphViewerUtils { return createHollowEgdeLoopInGraphSpace(vertexShape, startX, startY); } - // translate the edge to the starting vertex + // 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; @@ -713,7 +714,7 @@ public class GraphViewerUtils { double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY); xform.scale(dist, 1.0f); - // apply the transformations + // apply the transformations; converting the given shape from model space into graph space return xform.createTransformedShape(edgeShape); } @@ -1052,7 +1053,7 @@ public class GraphViewerUtils { LinkedList filteredEdges = new LinkedList<>(); if (useHover) { for (E edge : edges) { - if (edge.isInActivePath()) { + if (edge.isInHoveredVertexPath()) { filteredEdges.add(edge); } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java index 9f79325b66..9f381622b2 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java @@ -25,7 +25,9 @@ import ghidra.graph.GEdge; * *

An edge can be selected, which means that it has been clicked by the user. Also, an * edge can be part of an active path. This allows the UI to paint the edge differently if it - * is in the active path. + * is in the active path. The active path concept applies to both hovered and focused vertices + * separately. A hovered vertex is one that the user moves the mouse over; a focused vertex is + * one that is selected. * * *

Articulations - The start and end points are always part of the @@ -44,7 +46,7 @@ import ghidra.graph.GEdge; public interface VisualEdge extends GEdge { /** - * Sets this edge selected + * Sets this edge selected. This is usually in response to the user selecting the edge. * * @param selected true to select this edge; false to de-select this vertex */ @@ -58,20 +60,36 @@ public interface VisualEdge extends GEdge { public boolean isSelected(); /** - * Sets this edge to be marked as in the active path + * Sets this edge to be marked as in the active path of a currently hovered vertex * - * @param inActivePath true to be marked as in the active path; false to be marked as not + * @param inPath true to be marked as in the active path; false to be marked as not * in the active path */ - public void setInActivePath(boolean inActivePath); + public void setInHoveredVertexPath(boolean inPath); /** - * Returns true if this edge is part of an active path (this allows the edge to be - * differently rendered) + * Returns true if this edge is part of an active path for a currently hovered + * vertex (this allows the edge to be differently rendered) * * @return true if this edge is part of the active path */ - public boolean isInActivePath(); + public boolean isInHoveredVertexPath(); + + /** + * Sets this edge to be marked as in the active path of a currently focused/selected vertex + * + * @param inPath true to be marked as in the active path; false to be marked as not + * in the active path + */ + public void setInFocusedVertexPath(boolean inPath); + + /** + * Returns true if this edge is part of an active path for a currently focused/selected + * vertex (this allows the edge to be differently rendered) + * + * @return true if this edge is part of the active path + */ + public boolean isInFocusedVertexPath(); /** * Returns the points (in {@link GraphViewerUtils} View Space) of the articulation @@ -132,10 +150,10 @@ public interface VisualEdge extends GEdge { public void setAlpha(double alpha); /** - * Get the alpha, which determines how much of the edge is visible/see through. 0 is - * completely transparent. This attribute allows transitional for animations. - * - * @return the alpha value - */ + * Get the alpha, which determines how much of the edge is visible/see through. 0 is + * completely transparent. This attribute allows transitional for animations. + * + * @return the alpha value + */ public double getAlpha(); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java index 46df28b247..a8b98c495c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java @@ -32,9 +32,10 @@ public abstract class AbstractVisualEdge implements Visu private V start; private V end; - private boolean selected; - private boolean inActivePath; + private boolean inHoveredPath = false; + private boolean inFocusedPath = false; private double alpha = 1.0; + private boolean selected; private double emphasis; private List articulations = new ArrayList<>(); @@ -65,13 +66,23 @@ public abstract class AbstractVisualEdge implements Visu } @Override - public void setInActivePath(boolean inActivePath) { - this.inActivePath = inActivePath; + public boolean isInHoveredVertexPath() { + return inHoveredPath; } @Override - public boolean isInActivePath() { - return inActivePath; + public boolean isInFocusedVertexPath() { + return inFocusedPath; + } + + @Override + public void setInHoveredVertexPath(boolean inPath) { + this.inHoveredPath = inPath; + } + + @Override + public void setInFocusedVertexPath(boolean inPath) { + this.inFocusedPath = inPath; } @Override diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java index 466a9c671b..95f7ca3595 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java @@ -81,14 +81,15 @@ import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer; public abstract class VisualEdgeRenderer> extends BasicEdgeRenderer { - private static final float HOVERED_STROKE_WIDTH = 8.0f; - private static final float SELECTED_STROKE_WIDTH = 4.0f; + private static final float HOVERED_PATH_STROKE_WIDTH = 8.0f; + private static final float FOCUSED_PATH_STROKE_WIDTH = 4.0f; + private static final float SELECTED_STROKE_WIDTH = FOCUSED_PATH_STROKE_WIDTH + 2; private static final float EMPHASIZED_STOKE_WIDTH = SELECTED_STROKE_WIDTH + 3.0f; private float dashingPatternOffset; - private Color baseColor = Color.BLACK; - private Color highlightColor = Color.GRAY; + private Color defaultBaseColor = Color.BLACK; + private Color defaultHighlightColor = Color.GRAY; /** * Sets the offset value for painting dashed lines. This allows clients to animate the @@ -103,24 +104,29 @@ public abstract class VisualEdgeRenderer g, E e) { - return baseColor; + return defaultBaseColor; } public void setHighlightColor(Color highlightColor) { - this.highlightColor = highlightColor; + this.defaultHighlightColor = highlightColor; } public Color getHighlightColor(Graph g, E e) { - return highlightColor; + return defaultHighlightColor; } // template method - protected boolean isInActivePath(E e) { - return e.isInActivePath(); + protected boolean isInHoveredVertexPath(E e) { + return e.isInHoveredVertexPath(); + } + + // template method + protected boolean isInFocusedVertexPath(E e) { + return e.isInFocusedVertexPath(); } // template method @@ -153,12 +159,17 @@ public abstract class VisualEdgeRenderer, E> context = Context., E> getInstance(graph, e); boolean edgeHit = vt.transform(edgeShape).intersects(deviceRectangle); - if (edgeHit) { - - Paint oldPaint = g.getPaint(); - - // get Paints for filling and drawing - // (filling is done first so that drawing and label use same Paint) - Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e); - BasicStroke selectedStroke = getSelectedStroke(e, scale); - BasicStroke hoverStroke = getHoveredStroke(e, scale); - BasicStroke empahsisStroke = getEmphasisStroke(e, scale); - - if (fillPaint != null) { - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.fill(edgeShape); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.fill(edgeShape); - g.setStroke(saveStroke); - } - - g.setPaint(fillPaint); - g.fill(edgeShape); - } - - Paint drawPaint = rc.getEdgeDrawPaintTransformer().apply(e); - if (drawPaint != null) { - if (isEmphasized) { - Stroke saveStroke = g.getStroke(); - g.setPaint(drawPaint); - g.setStroke(empahsisStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - g.setPaint(drawPaint); - g.draw(edgeShape); - - // debug - draw a box around the edge - //Rectangle shapeBounds = edgeShape.getBounds(); - //g.setPaint(Color.ORANGE); - //g.draw(shapeBounds); - } - - Predicate, E>> predicate = rc.getEdgeArrowPredicate(); - boolean drawArrow = predicate.apply(context); - if (drawArrow) { - - Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e); - Stroke old_stroke = g.getStroke(); - if (new_stroke != null) { - g.setStroke(new_stroke); - } - - Shape vs2 = getVertexShapeForArrow(rc, layout, v2); // end vertex - - boolean arrowHit = vt.transform(vs2).intersects(deviceRectangle); - Paint arrowFillPaint = rc.getArrowFillPaintTransformer().apply(e); - Paint arrowDrawPaint = rc.getArrowDrawPaintTransformer().apply(e); - - if (arrowHit) { - - EdgeArrowRenderingSupport arrowRenderingSupport = - new BasicEdgeArrowRenderingSupport<>(); - - AffineTransform at = - arrowRenderingSupport.getArrowTransform(rc, edgeShape, vs2); - if (at == null) { - return; - } - - Shape arrow = rc.getEdgeArrowTransformer().apply(context); - arrow = scaleArrowForBetterVisibility(rc, arrow); - arrow = at.createTransformedShape(arrow); - - if (isEmphasized) { - Stroke saveStroke = g.getStroke(); - g.setPaint(arrowDrawPaint); - g.setStroke(empahsisStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - g.setPaint(arrowFillPaint); - g.fill(arrow); - g.setPaint(arrowDrawPaint); - g.draw(arrow); - } - - // restore paint and stroke - if (new_stroke != null) { - g.setStroke(old_stroke); - } - } - - // restore old paint - g.setPaint(oldPaint); + if (!edgeHit) { + return; } + + Paint oldPaint = g.getPaint(); + + // get Paints for filling and drawing + // (filling is done first so that drawing and label use the same Paint) + BasicStroke hoverStroke = getHoveredPathStroke(e, scale); + BasicStroke focusedStroke = getFocusedPathStroke(e, scale); + BasicStroke selectedStroke = getSelectedStroke(e, scale); + BasicStroke selectedAccentStroke = getSelectedAccentStroke(e, scale); + BasicStroke empahsisStroke = getEmphasisStroke(e, scale); + + // + // Fill + // + Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e); + if (fillPaint != null) { + // basic shape + g.setPaint(fillPaint); + g.fill(edgeShape); + + // Currently, graphs with complicated edge shapes (those with articulations) do not + // use a fill paint. If we execute this code with articulated edges, the display + // looks unusual. So, for now, only 'fill' with these effects when the client has + // explicitly used a fill paint transformer. + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(fillPaint); + g.setStroke(empahsisStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + } + + // + // Draw + // + Paint drawPaint = rc.getEdgeDrawPaintTransformer().apply(e); + if (drawPaint != null) { + // basic shape + g.setPaint(drawPaint); + g.draw(edgeShape); + } + + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(drawPaint); + g.setStroke(empahsisStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + + g.setPaint(selectedAccentColor); + g.setStroke(selectedAccentStroke); + g.draw(edgeShape); + + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + // debug - draw a box around the edge + //Rectangle shapeBounds = edgeShape.getBounds(); + //g.setPaint(Color.ORANGE); + //g.draw(shapeBounds); + + // + // Arrow Head + // + Predicate, E>> predicate = rc.getEdgeArrowPredicate(); + boolean drawArrow = predicate.apply(context); + if (!drawArrow) { + g.setPaint(oldPaint); + return; + } + + Stroke arrowStroke = rc.getEdgeArrowStrokeTransformer().apply(e); + Stroke oldArrowStroke = g.getStroke(); + if (arrowStroke != null) { + g.setStroke(arrowStroke); + } + + Shape vs2 = getVertexShapeForArrow(rc, layout, v2); // end vertex + boolean arrowHit = vt.transform(vs2).intersects(deviceRectangle); + if (!arrowHit) { + g.setPaint(oldPaint); + return; + } + + EdgeArrowRenderingSupport arrowRenderingSupport = + new BasicEdgeArrowRenderingSupport<>(); + AffineTransform at = arrowRenderingSupport.getArrowTransform(rc, edgeShape, vs2); + if (at == null) { + g.setPaint(oldPaint); + g.setStroke(oldArrowStroke); + return; + } + + Paint arrowFillPaint = rc.getArrowFillPaintTransformer().apply(e); + Paint arrowDrawPaint = rc.getArrowDrawPaintTransformer().apply(e); + Shape arrow = rc.getEdgeArrowTransformer().apply(context); + arrow = scaleArrowForBetterVisibility(rc, arrow); + arrow = at.createTransformedShape(arrow); + + // basic shape + g.setPaint(arrowFillPaint); + g.fill(arrow); + g.setPaint(arrowDrawPaint); + g.draw(arrow); + + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(arrowDrawPaint); + g.setStroke(empahsisStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + g.setStroke(oldArrowStroke); + g.setPaint(oldPaint); } protected Shape getVertexShapeForArrow(RenderContext rc, Layout layout, V v) { @@ -343,10 +407,10 @@ public abstract class VisualEdgeRenderer rc, Graph graph, E e, float x1, float y1, float x2, float y2, boolean isLoop, Shape vertexShape); - private BasicStroke getHoveredStroke(E e, float scale) { - float width = HOVERED_STROKE_WIDTH / (float) Math.pow(scale, .80); - return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 0f, + private BasicStroke getHoveredPathStroke(E e, float scale) { + float width = HOVERED_PATH_STROKE_WIDTH / (float) Math.pow(scale, .80); + return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0f, new float[] { width * 1, width * 2 }, width * 3 * dashingPatternOffset); } + private BasicStroke getFocusedPathStroke(E e, float scale) { + float width = FOCUSED_PATH_STROKE_WIDTH / (float) Math.pow(scale, .80); + return new BasicStroke(width); + } + private BasicStroke getSelectedStroke(E e, float scale) { float width = SELECTED_STROKE_WIDTH / (float) Math.pow(scale, .80); return new BasicStroke(width); } + private BasicStroke getSelectedAccentStroke(E e, float scale) { + float width = (SELECTED_STROKE_WIDTH + 2) / (float) Math.pow(scale, .80); + return new BasicStroke(width); + } + private BasicStroke getEmphasisStroke(E e, float scale) { double emphasisRatio = e.getEmphasis(); // this value is 0 when no emphasis float fullEmphasis = EMPHASIZED_STOKE_WIDTH; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java index 58b4b3591b..f933a92ba7 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java @@ -27,6 +27,8 @@ import ghidra.graph.viewer.VisualVertex; * A renderer designed to override default edge rendering to NOT paint emphasizing effects. We * do this because space is limited in the satellite and because this rendering can take excess * processing time. + * @param the vertex type + * @param the edge type */ public class VisualGraphEdgeSatelliteRenderer> extends VisualEdgeRenderer { @@ -38,7 +40,12 @@ public class VisualGraphEdgeSatelliteRenderer sources = GraphAlgorithms.getSources(graph); + if (sources.isEmpty()) { + Msg.debug(this, "No sources found for graph; cannot calculate dominance: " + + graph.getClass().getSimpleName()); + return null; + } + try { // note: calling the constructor performs the work return new ChkDominanceAlgorithm<>(graph, timeoutMonitor); @@ -268,16 +275,19 @@ public class VisualGraphPathHighlighter> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setOutFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setForwardScopedFlowFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setReverseScopedFlowFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setInOutFocusedEdgesSwing(V vertex) { @@ -525,46 +535,46 @@ public class VisualGraphPathHighlighter> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(inSupplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(inSupplier), null); Supplier> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - focusRunManager.runNext(new SelectRunnable(outSupplier), null); + focusRunManager.runNext(new SetFocusedEdgesRunnable(outSupplier), null); } private void setVertexCycleFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getCircuitEdgesAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setAllCycleFocusedEdgesSwing() { Supplier> supplier = () -> getAllCircuitFlowEdgesAsync(); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setInHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setOutHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setForwardScopedFlowHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setReverseScopedFlowHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setInOutHoveredEdgesSwing(V vertex) { @@ -573,32 +583,32 @@ public class VisualGraphPathHighlighter> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(inSupplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(inSupplier), null); Supplier> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNext(new HoverRunnable(outSupplier), null); + hoverRunManager.runNext(new SetHoveredEdgesRunnable(outSupplier), null); } private void setVertexCycleHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getCircuitEdgesAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setVertexToVertexPathHoveredEdgesSwing(V start, V end) { Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end); - focusRunManager.runNow(new SlowHoverRunnable(callback), null); + focusRunManager.runNow(new SlowSetHoveredEdgesRunnable(callback), null); } - private void selectSwing(Collection edges) { - edges.forEach(e -> e.setSelected(true)); + private void setInFocusedPathOnSwing(Collection edges) { + edges.forEach(e -> e.setInFocusedVertexPath(true)); listener.pathHighlightChanged(false); } - private void activateSwing(Collection edges) { - edges.forEach(e -> e.setInActivePath(true)); + private void setInHoverPathOnSwing(Collection edges) { + edges.forEach(e -> e.setInHoveredVertexPath(true)); listener.pathHighlightChanged(true); } @@ -626,9 +636,14 @@ public class VisualGraphPathHighlighter> accumulator = new CallbackAccumulator<>(path -> { Collection edges = pathToEdgesAsync(path); - SystemUtilities.runSwingLater(() -> activateSwing(edges)); + SystemUtilities.runSwingLater(() -> setInHoverPathOnSwing(edges)); }); TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT, @@ -890,12 +905,12 @@ public class VisualGraphPathHighlighter> edgeSupplier; private Set edges; - HoverRunnable(Supplier> edgeSupplier) { + SetHoveredEdgesRunnable(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @@ -915,20 +930,20 @@ public class VisualGraphPathHighlighter> edgeSupplier; private Set edges; - SelectRunnable(Supplier> edgeSupplier) { + SetFocusedEdgesRunnable(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @@ -948,7 +963,7 @@ public class VisualGraphPathHighlighter sourceViewer; private final VisualizationViewer otherViewer; + private SwingUpdateManager mouseHoverUpdater = new SwingUpdateManager(this::updateMouseHovers); + private MouseEvent lastMouseEvent; private V hoveredVertex; public VisualGraphHoverMousePlugin(GraphComponent graphComponent, @@ -60,16 +63,23 @@ public class VisualGraphHoverMousePlugin viewer = getGraphViewer(e); - V newHoveredVertex = GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint()); + PathHighlightMode hoverMode = graphComponent.getVertexHoverPathHighlightMode(); + if (hoverMode == PathHighlightMode.OFF) { + return; + } + + GraphViewer viewer = getGraphViewer(lastMouseEvent); + V newHoveredVertex = + GraphViewerUtils.getVertexFromPointInViewSpace(viewer, lastMouseEvent.getPoint()); if (newHoveredVertex == hoveredVertex) { return; } @@ -123,7 +133,9 @@ public class VisualGraphHoverMousePlugin updater = viewer.getViewUpdater(); return updater; } + + /** + * Signals to perform any cleanup when this plugin is going away + */ + public default void dispose() { + // stub + } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java index ae9e523b68..e8f92dbc8c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java @@ -95,6 +95,11 @@ public class VisualGraphPluggableGraphMouse) mp).dispose(); + } + } mousePlugins.clear(); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java index 0bdcf9a209..1195fa4848 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java @@ -57,9 +57,13 @@ public class ArticulatedEdgeRenderer the vertex type + * @param the edge type */ public class ArticulatedEdgeTransformer> extends ParallelEdgeShapeTransformer { @@ -75,38 +77,50 @@ public class ArticulatedEdgeTransformer reverse = new ArrayList<>(articulations); - Collections.reverse(reverse); - for (Point2D pt : reverse) { - generalPath.lineTo((float) (pt.getX() - originX) + offset, - (float) (pt.getY() - originY) + offset); - } 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(generalPath); + return transform.createTransformedShape(path); } 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(generalPath); + Shape transformedShape = inverse.createTransformedShape(path); return transformedShape; } catch (NoninvertibleTransformException e1) { diff --git a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java index 1b0edff5f3..92cf6370bc 100644 --- a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java +++ b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java @@ -675,12 +675,12 @@ public class VisualGraphPathHighlighterTest extends AbstractVisualGraphTest { nonHoveredEdges.removeAll(expectedEdges); for (TestEdge e : expectedEdges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertTrue("Edge was not hovered: " + e, isHovered); } for (TestEdge e : nonHoveredEdges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertFalse("Edge hovered when it should not have been: " + e, isHovered); } } @@ -694,7 +694,7 @@ public class VisualGraphPathHighlighterTest extends AbstractVisualGraphTest { private void assertNotHovered(TestEdge... edges) { for (TestEdge e : edges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertFalse("Edge should not have been hovered: " + e, isHovered); } }