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);
}
}