mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Merge remote-tracking branch 'origin/GT-3020-dragonmacher-fg-edges-lose-alpha'
This commit is contained in:
commit
e36b22d268
23 changed files with 581 additions and 282 deletions
|
@ -19,6 +19,16 @@ import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||||
import ghidra.graph.viewer.VisualEdge;
|
import ghidra.graph.viewer.VisualEdge;
|
||||||
import ghidra.program.model.symbol.FlowType;
|
import ghidra.program.model.symbol.FlowType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This version of the {@link VisualEdge} adds a few methods.
|
||||||
|
*
|
||||||
|
* <p>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<FGVertex> {
|
public interface FGEdge extends VisualEdge<FGVertex> {
|
||||||
|
|
||||||
public FlowType getFlowType();
|
public FlowType getFlowType();
|
||||||
|
@ -27,6 +37,30 @@ public interface FGEdge extends VisualEdge<FGVertex> {
|
||||||
|
|
||||||
public void setLabel(String label);
|
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.
|
||||||
|
*
|
||||||
|
* <P>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.
|
||||||
|
*
|
||||||
|
* <P>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")
|
@SuppressWarnings("unchecked")
|
||||||
// Suppressing warning on the return type; we know our class is the right type
|
// Suppressing warning on the return type; we know our class is the right type
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -34,10 +34,12 @@ public class FGEdgeImpl implements FGEdge {
|
||||||
boolean doHashCode = true;
|
boolean doHashCode = true;
|
||||||
int hashCode;
|
int hashCode;
|
||||||
|
|
||||||
private boolean inActivePath = false;
|
private boolean inHoveredPath = false;
|
||||||
|
private boolean inFocusedPath = false;
|
||||||
private boolean selected = false;
|
private boolean selected = false;
|
||||||
private double emphasis = 0D;
|
private double emphasis = 0D;
|
||||||
private double alpha = 1D;
|
private double defaultAlpha = 1D;
|
||||||
|
private double alpha = defaultAlpha;
|
||||||
private String edgeLabel = null;
|
private String edgeLabel = null;
|
||||||
|
|
||||||
public FGEdgeImpl(FGVertex startVertex, FGVertex destinationVertex, FlowType flowType,
|
public FGEdgeImpl(FGVertex startVertex, FGVertex destinationVertex, FlowType flowType,
|
||||||
|
@ -50,13 +52,23 @@ public class FGEdgeImpl implements FGEdge {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInActivePath() {
|
public boolean isInHoveredVertexPath() {
|
||||||
return inActivePath;
|
return inHoveredPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInActivePath(boolean inActivePath) {
|
public boolean isInFocusedVertexPath() {
|
||||||
this.inActivePath = inActivePath;
|
return inFocusedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInHoveredVertexPath(boolean inPath) {
|
||||||
|
this.inHoveredPath = inPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInFocusedVertexPath(boolean inPath) {
|
||||||
|
this.inFocusedPath = inPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -89,6 +101,17 @@ public class FGEdgeImpl implements FGEdge {
|
||||||
return alpha;
|
return alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefaultAlpha(double alpha) {
|
||||||
|
this.defaultAlpha = alpha;
|
||||||
|
this.alpha = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDefaultAlpha() {
|
||||||
|
return defaultAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Point2D> getArticulationPoints() {
|
public List<Point2D> getArticulationPoints() {
|
||||||
return layoutArticulationPoints;
|
return layoutArticulationPoints;
|
||||||
|
@ -135,7 +158,9 @@ public class FGEdgeImpl implements FGEdge {
|
||||||
newEdge.layoutArticulationPoints = newPoints;
|
newEdge.layoutArticulationPoints = newPoints;
|
||||||
|
|
||||||
newEdge.alpha = alpha;
|
newEdge.alpha = alpha;
|
||||||
newEdge.inActivePath = inActivePath;
|
newEdge.defaultAlpha = defaultAlpha;
|
||||||
|
newEdge.inHoveredPath = inHoveredPath;
|
||||||
|
newEdge.inFocusedPath = inFocusedPath;
|
||||||
newEdge.selected = selected;
|
newEdge.selected = selected;
|
||||||
return newEdge;
|
return newEdge;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG
|
||||||
*/
|
*/
|
||||||
AbstractGroupingFunctionGraphJob(FGController controller,
|
AbstractGroupingFunctionGraphJob(FGController controller,
|
||||||
GroupedFunctionGraphVertex groupVertex, Set<FGVertex> newVertices,
|
GroupedFunctionGraphVertex groupVertex, Set<FGVertex> newVertices,
|
||||||
Set<FGVertex> verticesToRemove, boolean relayloutOverride, boolean useAnimation) {
|
Set<FGVertex> verticesToRemove, boolean relayoutOverride, boolean useAnimation) {
|
||||||
|
|
||||||
super(controller, useAnimation);
|
super(controller, useAnimation);
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG
|
||||||
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
||||||
RelayoutOption relayoutOption = options.getRelayoutOption();
|
RelayoutOption relayoutOption = options.getRelayoutOption();
|
||||||
this.relayout = relayoutOption == VERTEX_GROUPING_CHANGES || relayoutOption == ALWAYS ||
|
this.relayout = relayoutOption == VERTEX_GROUPING_CHANGES || relayoutOption == ALWAYS ||
|
||||||
relayloutOverride;
|
relayoutOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -158,7 +158,7 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Subclasses must return locations for vertices. This method will be called when no
|
* Subclasses must return locations for vertices. This method will be called when no
|
||||||
* relayout will be performed.
|
* relayout will be performed.
|
||||||
*
|
*
|
||||||
|
@ -191,15 +191,21 @@ public abstract class AbstractGroupingFunctionGraphJob extends AbstractFunctionG
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateOpacity(double percentComplete) {
|
protected void updateOpacity(double percentComplete) {
|
||||||
|
|
||||||
double oldComponentsAlpha = 1.0 - percentComplete;
|
double oldComponentsAlpha = 1.0 - percentComplete;
|
||||||
|
|
||||||
Collection<FGVertex> vertices = getVerticesToBeRemoved();
|
Collection<FGVertex> vertices = getVerticesToBeRemoved();
|
||||||
for (FGVertex vertex : vertices) {
|
for (FGVertex vertex : vertices) {
|
||||||
|
|
||||||
vertex.setAlpha(oldComponentsAlpha);
|
vertex.setAlpha(oldComponentsAlpha);
|
||||||
|
|
||||||
Collection<FGEdge> edges = getEdges(vertex);
|
Collection<FGEdge> edges = getEdges(vertex);
|
||||||
for (FGEdge edge : edges) {
|
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<FGEdge> edges = getEdges(vertex);
|
Collection<FGEdge> edges = getEdges(vertex);
|
||||||
for (FGEdge edge : edges) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.awt.Rectangle;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
import org.jdesktop.animation.timing.Animator;
|
import org.jdesktop.animation.timing.Animator;
|
||||||
import org.jdesktop.animation.timing.interpolation.PropertySetter;
|
import org.jdesktop.animation.timing.interpolation.PropertySetter;
|
||||||
|
|
||||||
|
@ -197,22 +198,25 @@ public class MergeVertexFunctionGraphJob extends AbstractAnimatorJob {
|
||||||
parentVertex.setAlpha(oldComponentsAlpha);
|
parentVertex.setAlpha(oldComponentsAlpha);
|
||||||
childVertex.setAlpha(oldComponentsAlpha);
|
childVertex.setAlpha(oldComponentsAlpha);
|
||||||
|
|
||||||
Collection<FGEdge> edges = getEdges(parentVertex);
|
Iterable<FGEdge> edges =
|
||||||
|
IterableUtils.chainedIterable(getEdges(parentVertex), getEdges(childVertex));
|
||||||
for (FGEdge edge : edges) {
|
for (FGEdge edge : edges) {
|
||||||
edge.setAlpha(oldComponentsAlpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
edges = getEdges(childVertex);
|
// don't go past the alpha when removing
|
||||||
for (FGEdge edge : edges) {
|
double defaultAlpha = edge.getDefaultAlpha();
|
||||||
edge.setAlpha(oldComponentsAlpha);
|
double alpha = Math.min(oldComponentsAlpha, defaultAlpha);
|
||||||
|
edge.setAlpha(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
double newComponentsAlpha = percentComplete;
|
double newComponentsAlpha = percentComplete;
|
||||||
mergedVertex.setAlpha(newComponentsAlpha);
|
mergedVertex.setAlpha(newComponentsAlpha);
|
||||||
|
|
||||||
edges = getEdges(mergedVertex);
|
edges = getEdges(mergedVertex);
|
||||||
for (FGEdge edge : edges) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.awt.Rectangle;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
import org.jdesktop.animation.timing.Animator;
|
import org.jdesktop.animation.timing.Animator;
|
||||||
import org.jdesktop.animation.timing.interpolation.PropertySetter;
|
import org.jdesktop.animation.timing.interpolation.PropertySetter;
|
||||||
|
|
||||||
|
@ -114,9 +115,21 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob {
|
||||||
|
|
||||||
controller.synchronizeProgramLocationAfterEdit();
|
controller.synchronizeProgramLocationAfterEdit();
|
||||||
|
|
||||||
|
restoreEdgeDisplayAttributes();
|
||||||
|
|
||||||
viewer.repaint();
|
viewer.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void restoreEdgeDisplayAttributes() {
|
||||||
|
|
||||||
|
Iterable<FGEdge> edges =
|
||||||
|
IterableUtils.chainedIterable(getEdges(parentVertex), getEdges(childVertex));
|
||||||
|
for (FGEdge edge : edges) {
|
||||||
|
double alpha = edge.getDefaultAlpha();
|
||||||
|
edge.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setPercentComplete(double percentComplete) {
|
public void setPercentComplete(double percentComplete) {
|
||||||
trace("setPercentComplete() callback: " + percentComplete);
|
trace("setPercentComplete() callback: " + percentComplete);
|
||||||
updateNewVertexPositions(percentComplete);
|
updateNewVertexPositions(percentComplete);
|
||||||
|
@ -214,7 +227,11 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob {
|
||||||
|
|
||||||
Collection<FGEdge> edges = getEdges(toSplitVertex);
|
Collection<FGEdge> edges = getEdges(toSplitVertex);
|
||||||
for (FGEdge edge : edges) {
|
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;
|
double newComponentsAlpha = percentComplete;
|
||||||
|
@ -223,12 +240,19 @@ public class SplitVertexFunctionGraphJob extends AbstractAnimatorJob {
|
||||||
|
|
||||||
edges = getEdges(parentVertex);
|
edges = getEdges(parentVertex);
|
||||||
for (FGEdge edge : edges) {
|
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);
|
edges = getEdges(childVertex);
|
||||||
for (FGEdge edge : edges) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.awt.Color;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
|
@ -37,6 +38,7 @@ import ghidra.framework.plugintool.util.PluginException;
|
||||||
import ghidra.graph.viewer.options.RelayoutOption;
|
import ghidra.graph.viewer.options.RelayoutOption;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
import util.CollectionUtils;
|
||||||
|
|
||||||
public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
|
public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
|
||||||
|
|
||||||
|
@ -809,14 +811,41 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
|
||||||
verifyDefaultColor(v2);
|
verifyDefaultColor(v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEdgeDefaultAlphaPersistsAfterGrouping() {
|
||||||
|
|
||||||
|
graphFunction("01002cf5");
|
||||||
|
|
||||||
|
FGVertex v1 = vertex("01002cf5");
|
||||||
|
FGVertex v2 = vertex("01002d0f");
|
||||||
|
|
||||||
|
FunctionGraph graph = getFunctionGraph();
|
||||||
|
Iterable<FGEdge> 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
|
@Test
|
||||||
public void testSymbolAddedWhenGrouped_SymbolOutsideOfGroupNode() {
|
public void testSymbolAddedWhenGrouped_SymbolOutsideOfGroupNode() {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Private Methods
|
// Private Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -313,7 +313,7 @@ public class TestFGLayoutProvider extends FGLayoutProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (startCol.index > endCol.index) { // flow return
|
else if (startCol.index > endCol.index) { // flow return
|
||||||
e.setAlpha(.25);
|
e.setDefaultAlpha(.25);
|
||||||
|
|
||||||
Shape shape = transformer.apply(startVertex);
|
Shape shape = transformer.apply(startVertex);
|
||||||
Rectangle bounds = shape.getBounds();
|
Rectangle bounds = shape.getBounds();
|
||||||
|
@ -338,7 +338,7 @@ public class TestFGLayoutProvider extends FGLayoutProvider {
|
||||||
|
|
||||||
else { // same column--nothing to route
|
else { // same column--nothing to route
|
||||||
// straight line, which is the default
|
// straight line, which is the default
|
||||||
e.setAlpha(.25);
|
e.setDefaultAlpha(.25);
|
||||||
}
|
}
|
||||||
newEdgeArticulations.put(e, articulations);
|
newEdgeArticulations.put(e, articulations);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.functiongraph.graph.jung.renderer;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||||
|
|
||||||
public class DecompilerDominanceArticulatedEdgeTransformer extends FGArticulatedEdgeTransformer {
|
public class DNLArticulatedEdgeTransformer extends FGArticulatedEdgeTransformer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOverlapOffset(FGEdge edge) {
|
public int getOverlapOffset(FGEdge edge) {
|
|
@ -34,7 +34,7 @@ import ghidra.app.decompiler.DecompInterface;
|
||||||
import ghidra.app.decompiler.DecompileOptions;
|
import ghidra.app.decompiler.DecompileOptions;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
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.FGVertex;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
|
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
|
||||||
import ghidra.graph.VisualGraph;
|
import ghidra.graph.VisualGraph;
|
||||||
|
@ -103,7 +103,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Function<FGEdge, Shape> getEdgeShapeTransformer() {
|
public Function<FGEdge, Shape> getEdgeShapeTransformer() {
|
||||||
return new DecompilerDominanceArticulatedEdgeTransformer();
|
return new DNLArticulatedEdgeTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// 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
|
// after the code block has been executed. We dim those a bit so that they
|
||||||
// produce less clutter.
|
// produce less clutter.
|
||||||
e.setAlpha(.25);
|
e.setDefaultAlpha(.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Column getOutermostCol(LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
|
private Column getOutermostCol(LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class ColorUtils {
|
||||||
// This can be addressed with some polar plotting:
|
// This can be addressed with some polar plotting:
|
||||||
// Let the hue be the degree, and the saturation be the radius, so that the range
|
// 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
|
// 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.
|
// space.
|
||||||
|
|
||||||
// Start by plotting the given background
|
// 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
|
// 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
|
// 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.
|
// is at most 1.
|
||||||
vals[1] = 1.0f - vals[1];
|
vals[1] = 1.0f - vals[1];
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.graph.graphs;
|
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 static util.CollectionUtils.nonNull;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
@ -206,9 +204,11 @@ public abstract class DefaultVisualGraph<V extends VisualVertex,
|
||||||
|
|
||||||
Collection<E> outs = nonNull(getOutEdges(start));
|
Collection<E> outs = nonNull(getOutEdges(start));
|
||||||
Collection<E> ins = nonNull(getInEdges(end));
|
Collection<E> ins = nonNull(getInEdges(end));
|
||||||
|
Set<E> unique = new HashSet<>();
|
||||||
|
unique.addAll(outs);
|
||||||
|
unique.addAll(ins);
|
||||||
|
|
||||||
Iterable<E> concatenated = concat(outs, ins);
|
Iterable<E> filtered = IterableUtils.filteredIterable(unique, e -> {
|
||||||
Iterable<E> filtered = filter(concatenated, e -> {
|
|
||||||
return e.getStart().equals(start) && e.getEnd().equals(end);
|
return e.getStart().equals(start) && e.getEnd().equals(end);
|
||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
|
|
|
@ -699,9 +699,10 @@ public class GraphViewerUtils {
|
||||||
return createHollowEgdeLoopInGraphSpace(vertexShape, startX, startY);
|
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);
|
AffineTransform xform = AffineTransform.getTranslateInstance(startX, startY);
|
||||||
Shape edgeShape = renderContext.getEdgeShapeTransformer().apply(e);
|
Shape edgeShape = renderContext.getEdgeShapeTransformer().apply(e);
|
||||||
|
|
||||||
double deltaX = endX - startX;
|
double deltaX = endX - startX;
|
||||||
double deltaY = endY - startY;
|
double deltaY = endY - startY;
|
||||||
|
|
||||||
|
@ -713,7 +714,7 @@ public class GraphViewerUtils {
|
||||||
double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
xform.scale(dist, 1.0f);
|
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);
|
return xform.createTransformedShape(edgeShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1052,7 +1053,7 @@ public class GraphViewerUtils {
|
||||||
LinkedList<E> filteredEdges = new LinkedList<>();
|
LinkedList<E> filteredEdges = new LinkedList<>();
|
||||||
if (useHover) {
|
if (useHover) {
|
||||||
for (E edge : edges) {
|
for (E edge : edges) {
|
||||||
if (edge.isInActivePath()) {
|
if (edge.isInHoveredVertexPath()) {
|
||||||
filteredEdges.add(edge);
|
filteredEdges.add(edge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ import ghidra.graph.GEdge;
|
||||||
*
|
*
|
||||||
* <P>An edge can be selected, which means that it has been clicked by the user. Also, an
|
* <P>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
|
* 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.
|
||||||
*
|
*
|
||||||
* <A NAME="articulations"></A>
|
* <A NAME="articulations"></A>
|
||||||
* <P><U>Articulations</U> - The start and end points are always part of the
|
* <P><U>Articulations</U> - The start and end points are always part of the
|
||||||
|
@ -44,7 +46,7 @@ import ghidra.graph.GEdge;
|
||||||
public interface VisualEdge<V extends VisualVertex> extends GEdge<V> {
|
public interface VisualEdge<V extends VisualVertex> extends GEdge<V> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @param selected true to select this edge; false to de-select this vertex
|
||||||
*/
|
*/
|
||||||
|
@ -58,20 +60,36 @@ public interface VisualEdge<V extends VisualVertex> extends GEdge<V> {
|
||||||
public boolean isSelected();
|
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
|
* 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
|
* Returns true if this edge is part of an active path for a currently hovered
|
||||||
* differently rendered)
|
* vertex (this allows the edge to be differently rendered)
|
||||||
*
|
*
|
||||||
* @return true if this edge is part of the active path
|
* @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
|
* Returns the points (in {@link GraphViewerUtils} View Space) of the articulation
|
||||||
|
@ -132,10 +150,10 @@ public interface VisualEdge<V extends VisualVertex> extends GEdge<V> {
|
||||||
public void setAlpha(double alpha);
|
public void setAlpha(double alpha);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the alpha, which determines how much of the edge is visible/see through. 0 is
|
* Get the alpha, which determines how much of the edge is visible/see through. 0 is
|
||||||
* completely transparent. This attribute allows transitional for animations.
|
* completely transparent. This attribute allows transitional for animations.
|
||||||
*
|
*
|
||||||
* @return the alpha value
|
* @return the alpha value
|
||||||
*/
|
*/
|
||||||
public double getAlpha();
|
public double getAlpha();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,10 @@ public abstract class AbstractVisualEdge<V extends VisualVertex> implements Visu
|
||||||
private V start;
|
private V start;
|
||||||
private V end;
|
private V end;
|
||||||
|
|
||||||
private boolean selected;
|
private boolean inHoveredPath = false;
|
||||||
private boolean inActivePath;
|
private boolean inFocusedPath = false;
|
||||||
private double alpha = 1.0;
|
private double alpha = 1.0;
|
||||||
|
private boolean selected;
|
||||||
private double emphasis;
|
private double emphasis;
|
||||||
|
|
||||||
private List<Point2D> articulations = new ArrayList<>();
|
private List<Point2D> articulations = new ArrayList<>();
|
||||||
|
@ -65,13 +66,23 @@ public abstract class AbstractVisualEdge<V extends VisualVertex> implements Visu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInActivePath(boolean inActivePath) {
|
public boolean isInHoveredVertexPath() {
|
||||||
this.inActivePath = inActivePath;
|
return inHoveredPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInActivePath() {
|
public boolean isInFocusedVertexPath() {
|
||||||
return inActivePath;
|
return inFocusedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInHoveredVertexPath(boolean inPath) {
|
||||||
|
this.inHoveredPath = inPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInFocusedVertexPath(boolean inPath) {
|
||||||
|
this.inFocusedPath = inPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -81,14 +81,15 @@ import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||||
public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
||||||
extends BasicEdgeRenderer<V, E> {
|
extends BasicEdgeRenderer<V, E> {
|
||||||
|
|
||||||
private static final float HOVERED_STROKE_WIDTH = 8.0f;
|
private static final float HOVERED_PATH_STROKE_WIDTH = 8.0f;
|
||||||
private static final float SELECTED_STROKE_WIDTH = 4.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 static final float EMPHASIZED_STOKE_WIDTH = SELECTED_STROKE_WIDTH + 3.0f;
|
||||||
|
|
||||||
private float dashingPatternOffset;
|
private float dashingPatternOffset;
|
||||||
|
|
||||||
private Color baseColor = Color.BLACK;
|
private Color defaultBaseColor = Color.BLACK;
|
||||||
private Color highlightColor = Color.GRAY;
|
private Color defaultHighlightColor = Color.GRAY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the offset value for painting dashed lines. This allows clients to animate the
|
* Sets the offset value for painting dashed lines. This allows clients to animate the
|
||||||
|
@ -103,24 +104,29 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBaseColor(Color color) {
|
public void setBaseColor(Color color) {
|
||||||
this.baseColor = color;
|
this.defaultBaseColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getBaseColor(Graph<V, E> g, E e) {
|
public Color getBaseColor(Graph<V, E> g, E e) {
|
||||||
return baseColor;
|
return defaultBaseColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHighlightColor(Color highlightColor) {
|
public void setHighlightColor(Color highlightColor) {
|
||||||
this.highlightColor = highlightColor;
|
this.defaultHighlightColor = highlightColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getHighlightColor(Graph<V, E> g, E e) {
|
public Color getHighlightColor(Graph<V, E> g, E e) {
|
||||||
return highlightColor;
|
return defaultHighlightColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// template method
|
// template method
|
||||||
protected boolean isInActivePath(E e) {
|
protected boolean isInHoveredVertexPath(E e) {
|
||||||
return e.isInActivePath();
|
return e.isInHoveredVertexPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// template method
|
||||||
|
protected boolean isInFocusedVertexPath(E e) {
|
||||||
|
return e.isInFocusedVertexPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// template method
|
// template method
|
||||||
|
@ -153,12 +159,17 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
|
||||||
float scalex = (float) g.getTransform().getScaleX();
|
float scalex = (float) g.getTransform().getScaleX();
|
||||||
float scaley = (float) g.getTransform().getScaleY();
|
float scaley = (float) g.getTransform().getScaleY();
|
||||||
|
|
||||||
boolean isActive = isInActivePath(e);
|
boolean isInHoveredPath = isInHoveredVertexPath(e);
|
||||||
|
boolean isInFocusedPath = isInFocusedVertexPath(e);
|
||||||
boolean isSelected = isSelected(e);
|
boolean isSelected = isSelected(e);
|
||||||
boolean isEmphasized = isEmphasiszed(e);
|
boolean isEmphasized = isEmphasiszed(e);
|
||||||
|
|
||||||
Color hoveredColor = getHighlightColor(graph, e);
|
Color highlightColor = getHighlightColor(graph, e);
|
||||||
Color selectedColor = getHighlightColor(graph, e).darker();
|
Color baseColor = getBaseColor(graph, e);
|
||||||
|
Color hoveredColor = highlightColor;
|
||||||
|
Color focusedColor = baseColor;
|
||||||
|
Color selectedColor = highlightColor.darker(); // note: we can do better for selected color
|
||||||
|
Color selectedAccentColor = highlightColor;
|
||||||
|
|
||||||
float scale = StrictMath.min(scalex, scaley);
|
float scale = StrictMath.min(scalex, scaley);
|
||||||
|
|
||||||
|
@ -190,146 +201,199 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
|
||||||
|
|
||||||
Context<Graph<V, E>, E> context = Context.<Graph<V, E>, E> getInstance(graph, e);
|
Context<Graph<V, E>, E> context = Context.<Graph<V, E>, E> getInstance(graph, e);
|
||||||
boolean edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
|
boolean edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
|
||||||
if (edgeHit) {
|
if (!edgeHit) {
|
||||||
|
return;
|
||||||
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<Context<Graph<V, E>, 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<V, E> 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Context<Graph<V, E>, 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<V, E> 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<V, E> rc, Layout<V, E> layout, V v) {
|
protected Shape getVertexShapeForArrow(RenderContext<V, E> rc, Layout<V, E> layout, V v) {
|
||||||
|
@ -343,10 +407,10 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
|
||||||
* @param rc the render context for the graph
|
* @param rc the render context for the graph
|
||||||
* @param graph the graph
|
* @param graph the graph
|
||||||
* @param e the edge to shape
|
* @param e the edge to shape
|
||||||
* @param x1 the start vertex point x
|
* @param x1 the start vertex point x; layout space
|
||||||
* @param y1 the start vertex point y
|
* @param y1 the start vertex point y; layout space
|
||||||
* @param x2 the end vertex point x
|
* @param x2 the end vertex point x; layout space
|
||||||
* @param y2 the end vertex point y
|
* @param y2 the end vertex point y; layout space
|
||||||
* @param isLoop true if the start == end, which is a self-loop
|
* @param isLoop true if the start == end, which is a self-loop
|
||||||
* @param vertexShape the vertex shape (used in the case of a loop to draw a circle from the
|
* @param vertexShape the vertex shape (used in the case of a loop to draw a circle from the
|
||||||
* shape to itself)
|
* shape to itself)
|
||||||
|
@ -355,17 +419,27 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
|
||||||
public abstract Shape getEdgeShape(RenderContext<V, E> rc, Graph<V, E> graph, E e, float x1,
|
public abstract Shape getEdgeShape(RenderContext<V, E> rc, Graph<V, E> graph, E e, float x1,
|
||||||
float y1, float x2, float y2, boolean isLoop, Shape vertexShape);
|
float y1, float x2, float y2, boolean isLoop, Shape vertexShape);
|
||||||
|
|
||||||
private BasicStroke getHoveredStroke(E e, float scale) {
|
private BasicStroke getHoveredPathStroke(E e, float scale) {
|
||||||
float width = HOVERED_STROKE_WIDTH / (float) Math.pow(scale, .80);
|
float width = HOVERED_PATH_STROKE_WIDTH / (float) Math.pow(scale, .80);
|
||||||
return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 0f,
|
return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0f,
|
||||||
new float[] { width * 1, width * 2 }, width * 3 * dashingPatternOffset);
|
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) {
|
private BasicStroke getSelectedStroke(E e, float scale) {
|
||||||
float width = SELECTED_STROKE_WIDTH / (float) Math.pow(scale, .80);
|
float width = SELECTED_STROKE_WIDTH / (float) Math.pow(scale, .80);
|
||||||
return new BasicStroke(width);
|
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) {
|
private BasicStroke getEmphasisStroke(E e, float scale) {
|
||||||
double emphasisRatio = e.getEmphasis(); // this value is 0 when no emphasis
|
double emphasisRatio = e.getEmphasis(); // this value is 0 when no emphasis
|
||||||
float fullEmphasis = EMPHASIZED_STOKE_WIDTH;
|
float fullEmphasis = EMPHASIZED_STOKE_WIDTH;
|
||||||
|
|
|
@ -27,6 +27,8 @@ import ghidra.graph.viewer.VisualVertex;
|
||||||
* A renderer designed to override default edge rendering to NOT paint emphasizing effects. We
|
* 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
|
* do this because space is limited in the satellite and because this rendering can take excess
|
||||||
* processing time.
|
* processing time.
|
||||||
|
* @param <V> the vertex type
|
||||||
|
* @param <E> the edge type
|
||||||
*/
|
*/
|
||||||
public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
||||||
extends VisualEdgeRenderer<V, E> {
|
extends VisualEdgeRenderer<V, E> {
|
||||||
|
@ -38,7 +40,12 @@ public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isInActivePath(E e) {
|
protected boolean isInHoveredVertexPath(E e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isInFocusedVertexPath(E e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,13 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
|
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
|
||||||
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
|
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
|
||||||
|
|
||||||
|
Set<V> sources = GraphAlgorithms.getSources(graph);
|
||||||
|
if (sources.isEmpty()) {
|
||||||
|
Msg.debug(this, "No sources found for graph; cannot calculate dominance: " +
|
||||||
|
graph.getClass().getSimpleName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// note: calling the constructor performs the work
|
// note: calling the constructor performs the work
|
||||||
return new ChkDominanceAlgorithm<>(graph, timeoutMonitor);
|
return new ChkDominanceAlgorithm<>(graph, timeoutMonitor);
|
||||||
|
@ -268,16 +275,19 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
|
|
||||||
public void setVertexHoverMode(PathHighlightMode mode) {
|
public void setVertexHoverMode(PathHighlightMode mode) {
|
||||||
this.vertexHoverMode = Objects.requireNonNull(mode);
|
this.vertexHoverMode = Objects.requireNonNull(mode);
|
||||||
|
if (vertexHoverMode == PathHighlightMode.OFF) {
|
||||||
|
clearHoveredEdgesSwing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHoveredVertex(V hoveredVertex) {
|
public void setHoveredVertex(V hoveredVertex) {
|
||||||
|
|
||||||
if (workPauser.isPaused()) {
|
|
||||||
return; // hovers a transient, no need to remember the request
|
|
||||||
}
|
|
||||||
|
|
||||||
clearHoveredEdgesSwing();
|
clearHoveredEdgesSwing();
|
||||||
|
|
||||||
|
if (workPauser.isPaused()) {
|
||||||
|
return; // hovers are transient, no need to remember the request
|
||||||
|
}
|
||||||
|
|
||||||
if (hoveredVertex == null) {
|
if (hoveredVertex == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -362,13 +372,13 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
|
|
||||||
private void clearHoveredEdgesSwing() {
|
private void clearHoveredEdgesSwing() {
|
||||||
for (E edge : graph.getEdges()) {
|
for (E edge : graph.getEdges()) {
|
||||||
edge.setInActivePath(false);
|
edge.setInHoveredVertexPath(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearFocusedEdgesSwing() {
|
private void clearFocusedEdgesSwing() {
|
||||||
for (E edge : graph.getEdges()) {
|
for (E edge : graph.getEdges()) {
|
||||||
edge.setSelected(false);
|
edge.setInFocusedVertexPath(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,25 +508,25 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
private void setInFocusedEdges(V vertex) {
|
private void setInFocusedEdges(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOutFocusedEdgesSwing(V vertex) {
|
private void setOutFocusedEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setForwardScopedFlowFocusedEdgesSwing(V vertex) {
|
private void setForwardScopedFlowFocusedEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setReverseScopedFlowFocusedEdgesSwing(V vertex) {
|
private void setReverseScopedFlowFocusedEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInOutFocusedEdgesSwing(V vertex) {
|
private void setInOutFocusedEdgesSwing(V vertex) {
|
||||||
|
@ -525,46 +535,46 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
// Select ins and outs, one after the other.
|
// Select ins and outs, one after the other.
|
||||||
//
|
//
|
||||||
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(inSupplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(inSupplier), null);
|
||||||
|
|
||||||
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
||||||
focusRunManager.runNext(new SelectRunnable(outSupplier), null);
|
focusRunManager.runNext(new SetFocusedEdgesRunnable(outSupplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVertexCycleFocusedEdgesSwing(V vertex) {
|
private void setVertexCycleFocusedEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAllCycleFocusedEdgesSwing() {
|
private void setAllCycleFocusedEdgesSwing() {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getAllCircuitFlowEdgesAsync();
|
Supplier<Set<E>> supplier = () -> getAllCircuitFlowEdgesAsync();
|
||||||
focusRunManager.runNow(new SelectRunnable(supplier), null);
|
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInHoveredEdgesSwing(V vertex) {
|
private void setInHoveredEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(supplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOutHoveredEdgesSwing(V vertex) {
|
private void setOutHoveredEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(supplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setForwardScopedFlowHoveredEdgesSwing(V vertex) {
|
private void setForwardScopedFlowHoveredEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(supplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setReverseScopedFlowHoveredEdgesSwing(V vertex) {
|
private void setReverseScopedFlowHoveredEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(supplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInOutHoveredEdgesSwing(V vertex) {
|
private void setInOutHoveredEdgesSwing(V vertex) {
|
||||||
|
@ -573,32 +583,32 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
// Select ins and outs, one after the other.
|
// Select ins and outs, one after the other.
|
||||||
//
|
//
|
||||||
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(inSupplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(inSupplier), null);
|
||||||
|
|
||||||
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
|
||||||
hoverRunManager.runNext(new HoverRunnable(outSupplier), null);
|
hoverRunManager.runNext(new SetHoveredEdgesRunnable(outSupplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVertexCycleHoveredEdgesSwing(V vertex) {
|
private void setVertexCycleHoveredEdgesSwing(V vertex) {
|
||||||
|
|
||||||
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
|
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
|
||||||
hoverRunManager.runNow(new HoverRunnable(supplier), null);
|
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVertexToVertexPathHoveredEdgesSwing(V start, V end) {
|
private void setVertexToVertexPathHoveredEdgesSwing(V start, V end) {
|
||||||
|
|
||||||
Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end);
|
Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end);
|
||||||
focusRunManager.runNow(new SlowHoverRunnable(callback), null);
|
focusRunManager.runNow(new SlowSetHoveredEdgesRunnable(callback), null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectSwing(Collection<E> edges) {
|
private void setInFocusedPathOnSwing(Collection<E> edges) {
|
||||||
edges.forEach(e -> e.setSelected(true));
|
edges.forEach(e -> e.setInFocusedVertexPath(true));
|
||||||
listener.pathHighlightChanged(false);
|
listener.pathHighlightChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activateSwing(Collection<E> edges) {
|
private void setInHoverPathOnSwing(Collection<E> edges) {
|
||||||
edges.forEach(e -> e.setInActivePath(true));
|
edges.forEach(e -> e.setInHoveredVertexPath(true));
|
||||||
listener.pathHighlightChanged(true);
|
listener.pathHighlightChanged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,9 +636,14 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cf.isCompletedExceptionally()) {
|
if (cf.isCompletedExceptionally()) {
|
||||||
// clear the contents of the future, as it is acting like a cache
|
return;
|
||||||
clearer.accept(cf.getNow(null));
|
}
|
||||||
|
|
||||||
|
// clear the contents of the future, as it is acting like a cache
|
||||||
|
T result = cf.getNow(null);
|
||||||
|
if (result != null) {
|
||||||
|
clearer.accept(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,7 +848,7 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
CallbackAccumulator<List<V>> accumulator = new CallbackAccumulator<>(path -> {
|
CallbackAccumulator<List<V>> accumulator = new CallbackAccumulator<>(path -> {
|
||||||
|
|
||||||
Collection<E> edges = pathToEdgesAsync(path);
|
Collection<E> edges = pathToEdgesAsync(path);
|
||||||
SystemUtilities.runSwingLater(() -> activateSwing(edges));
|
SystemUtilities.runSwingLater(() -> setInHoverPathOnSwing(edges));
|
||||||
});
|
});
|
||||||
|
|
||||||
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
|
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
|
||||||
|
@ -890,12 +905,12 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
* A class to handle off-loading the calculation of edges to be hovered. The results will
|
* A class to handle off-loading the calculation of edges to be hovered. The results will
|
||||||
* then be used to update the UI.
|
* then be used to update the UI.
|
||||||
*/
|
*/
|
||||||
private class HoverRunnable implements SwingRunnable {
|
private class SetHoveredEdgesRunnable implements SwingRunnable {
|
||||||
|
|
||||||
private Supplier<Set<E>> edgeSupplier;
|
private Supplier<Set<E>> edgeSupplier;
|
||||||
private Set<E> edges;
|
private Set<E> edges;
|
||||||
|
|
||||||
HoverRunnable(Supplier<Set<E>> edgeSupplier) {
|
SetHoveredEdgesRunnable(Supplier<Set<E>> edgeSupplier) {
|
||||||
this.edgeSupplier = edgeSupplier;
|
this.edgeSupplier = edgeSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -915,20 +930,20 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activateSwing(edges);
|
setInHoverPathOnSwing(edges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to handle off-loading the calculation of edges to be focused/selected.
|
* A class to handle off-loading the calculation of edges to be focused.
|
||||||
* The results will then be used to update the UI.
|
* The results will then be used to update the UI.
|
||||||
*/
|
*/
|
||||||
private class SelectRunnable implements SwingRunnable {
|
private class SetFocusedEdgesRunnable implements SwingRunnable {
|
||||||
|
|
||||||
private Supplier<Set<E>> edgeSupplier;
|
private Supplier<Set<E>> edgeSupplier;
|
||||||
private Set<E> edges;
|
private Set<E> edges;
|
||||||
|
|
||||||
SelectRunnable(Supplier<Set<E>> edgeSupplier) {
|
SetFocusedEdgesRunnable(Supplier<Set<E>> edgeSupplier) {
|
||||||
this.edgeSupplier = edgeSupplier;
|
this.edgeSupplier = edgeSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -948,7 +963,7 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectSwing(edges);
|
setInFocusedPathOnSwing(edges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,11 +971,11 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||||
* A class meant to run in the hover RunManager that is slow or open-ended. Work will
|
* A class meant to run in the hover RunManager that is slow or open-ended. Work will
|
||||||
* be performed as long as possible, updating results along the way.
|
* be performed as long as possible, updating results along the way.
|
||||||
*/
|
*/
|
||||||
private class SlowHoverRunnable implements MonitoredRunnable {
|
private class SlowSetHoveredEdgesRunnable implements MonitoredRunnable {
|
||||||
|
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
SlowHoverRunnable(Callback callback) {
|
SlowSetHoveredEdgesRunnable(Callback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
|
||||||
import ghidra.graph.VisualGraph;
|
import ghidra.graph.VisualGraph;
|
||||||
import ghidra.graph.viewer.*;
|
import ghidra.graph.viewer.*;
|
||||||
import ghidra.graph.viewer.edge.VisualGraphPathHighlighter;
|
import ghidra.graph.viewer.edge.VisualGraphPathHighlighter;
|
||||||
|
import ghidra.util.task.SwingUpdateManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mouse plugin to handle vertex hovers, to include animating paths in the graph, based
|
* A mouse plugin to handle vertex hovers, to include animating paths in the graph, based
|
||||||
|
@ -45,6 +46,8 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
|
||||||
private final VisualizationViewer<V, E> sourceViewer;
|
private final VisualizationViewer<V, E> sourceViewer;
|
||||||
private final VisualizationViewer<V, E> otherViewer;
|
private final VisualizationViewer<V, E> otherViewer;
|
||||||
|
|
||||||
|
private SwingUpdateManager mouseHoverUpdater = new SwingUpdateManager(this::updateMouseHovers);
|
||||||
|
private MouseEvent lastMouseEvent;
|
||||||
private V hoveredVertex;
|
private V hoveredVertex;
|
||||||
|
|
||||||
public VisualGraphHoverMousePlugin(GraphComponent<V, E, ?> graphComponent,
|
public VisualGraphHoverMousePlugin(GraphComponent<V, E, ?> graphComponent,
|
||||||
|
@ -60,16 +63,23 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseMoved(MouseEvent e) {
|
public void mouseMoved(MouseEvent e) {
|
||||||
updateMouseHovers(e);
|
lastMouseEvent = e;
|
||||||
|
mouseHoverUpdater.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMouseHovers(MouseEvent e) {
|
private void updateMouseHovers() {
|
||||||
if (graphComponent.isUninitialized()) {
|
if (graphComponent.isUninitialized()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphViewer<V, E> viewer = getGraphViewer(e);
|
PathHighlightMode hoverMode = graphComponent.getVertexHoverPathHighlightMode();
|
||||||
V newHoveredVertex = GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint());
|
if (hoverMode == PathHighlightMode.OFF) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphViewer<V, E> viewer = getGraphViewer(lastMouseEvent);
|
||||||
|
V newHoveredVertex =
|
||||||
|
GraphViewerUtils.getVertexFromPointInViewSpace(viewer, lastMouseEvent.getPoint());
|
||||||
if (newHoveredVertex == hoveredVertex) {
|
if (newHoveredVertex == hoveredVertex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +133,9 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
|
||||||
if (e.isPopupTrigger()) {
|
if (e.isPopupTrigger()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateMouseHovers(e);
|
|
||||||
|
lastMouseEvent = e;
|
||||||
|
updateMouseHovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,4 +152,9 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
// handled by dragged and released
|
// handled by dragged and released
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
mouseHoverUpdater.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,4 +108,11 @@ public interface VisualGraphMousePlugin<V extends VisualVertex, E extends Visual
|
||||||
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
|
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
|
||||||
return updater;
|
return updater;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals to perform any cleanup when this plugin is going away
|
||||||
|
*/
|
||||||
|
public default void dispose() {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,11 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
for (GraphMousePlugin mp : mousePlugins) {
|
||||||
|
if (mp instanceof VisualGraphMousePlugin) {
|
||||||
|
((VisualGraphMousePlugin<?, ?>) mp).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
mousePlugins.clear();
|
mousePlugins.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,13 @@ public class ArticulatedEdgeRenderer<V extends VisualVertex, E extends VisualEdg
|
||||||
new Point2D.Float((float) point.getX() + offset, (float) point.getY() + offset);
|
new Point2D.Float((float) point.getX() + offset, (float) point.getY() + offset);
|
||||||
point = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, offsetPoint);
|
point = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, offsetPoint);
|
||||||
path.lineTo((float) point.getX(), (float) point.getY());
|
path.lineTo((float) point.getX(), (float) point.getY());
|
||||||
|
path.moveTo((float) point.getX(), (float) point.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
path.lineTo(x2, y2);
|
path.lineTo(x2, y2);
|
||||||
|
path.moveTo(x2, y2);
|
||||||
|
path.closePath();
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.graph.viewer.shape;
|
||||||
|
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.awt.geom.*;
|
import java.awt.geom.*;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
|
||||||
import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
|
import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
|
||||||
import ghidra.graph.viewer.*;
|
import ghidra.graph.viewer.*;
|
||||||
|
@ -26,6 +26,8 @@ import ghidra.util.SystemUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An edge shape that renders as a series of straight lines between articulation points.
|
* An edge shape that renders as a series of straight lines between articulation points.
|
||||||
|
* @param <V> the vertex type
|
||||||
|
* @param <E> the edge type
|
||||||
*/
|
*/
|
||||||
public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends VisualEdge<V>>
|
public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends VisualEdge<V>>
|
||||||
extends ParallelEdgeShapeTransformer<V, E> {
|
extends ParallelEdgeShapeTransformer<V, E> {
|
||||||
|
@ -75,38 +77,50 @@ public class ArticulatedEdgeTransformer<V extends VisualVertex, E extends Visual
|
||||||
final double originY = p1.getY();
|
final double originY = p1.getY();
|
||||||
|
|
||||||
int offset = getOverlapOffset(e);
|
int offset = getOverlapOffset(e);
|
||||||
GeneralPath generalPath = new GeneralPath();
|
GeneralPath path = new GeneralPath();
|
||||||
generalPath.moveTo(0, 0);
|
path.moveTo(0, 0);
|
||||||
for (Point2D pt : articulations) {
|
for (Point2D pt : articulations) {
|
||||||
generalPath.lineTo((float) (pt.getX() - originX) + offset,
|
float x = (float) (pt.getX() - originX) + offset;
|
||||||
(float) (pt.getY() - originY) + offset);
|
float y = (float) (pt.getY() - originY) + offset;
|
||||||
|
path.lineTo(x, y);
|
||||||
|
path.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
generalPath.lineTo((float) (p2.getX() - originX), (float) (p2.getY() - originY));
|
float p2x = (float) (p2.getX() - originX);
|
||||||
|
float p2y = (float) (p2.getY() - originY);
|
||||||
|
path.lineTo(p2x, p2y);
|
||||||
|
path.moveTo(p2x, p2y);
|
||||||
|
path.closePath();
|
||||||
|
|
||||||
ArrayList<Point2D> 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();
|
AffineTransform transform = new AffineTransform();
|
||||||
|
|
||||||
final double deltaY = p2.getY() - originY;
|
final double deltaY = p2.getY() - originY;
|
||||||
final double deltaX = p2.getX() - originX;
|
final double deltaX = p2.getX() - originX;
|
||||||
if (deltaX == 0 && deltaY == 0) {
|
if (deltaX == 0 && deltaY == 0) {
|
||||||
// this implies the source and destination node are at the same location, which
|
// this implies the source and destination node are at the same location, which
|
||||||
// is possible if the user drags it there or during animations
|
// 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);
|
double theta = StrictMath.atan2(deltaY, deltaX);
|
||||||
transform.rotate(theta);
|
transform.rotate(theta);
|
||||||
double scale = StrictMath.sqrt(deltaY * deltaY + deltaX * deltaX);
|
double scale = StrictMath.sqrt(deltaY * deltaY + deltaX * deltaX);
|
||||||
transform.scale(scale, 1.0f);
|
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 {
|
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();
|
AffineTransform inverse = transform.createInverse();
|
||||||
Shape transformedShape = inverse.createTransformedShape(generalPath);
|
Shape transformedShape = inverse.createTransformedShape(path);
|
||||||
return transformedShape;
|
return transformedShape;
|
||||||
}
|
}
|
||||||
catch (NoninvertibleTransformException e1) {
|
catch (NoninvertibleTransformException e1) {
|
||||||
|
|
|
@ -675,12 +675,12 @@ public class VisualGraphPathHighlighterTest extends AbstractVisualGraphTest {
|
||||||
nonHoveredEdges.removeAll(expectedEdges);
|
nonHoveredEdges.removeAll(expectedEdges);
|
||||||
|
|
||||||
for (TestEdge e : expectedEdges) {
|
for (TestEdge e : expectedEdges) {
|
||||||
boolean isHovered = swing(() -> e.isInActivePath());
|
boolean isHovered = swing(() -> e.isInHoveredVertexPath());
|
||||||
assertTrue("Edge was not hovered: " + e, isHovered);
|
assertTrue("Edge was not hovered: " + e, isHovered);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TestEdge e : nonHoveredEdges) {
|
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);
|
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) {
|
private void assertNotHovered(TestEdge... edges) {
|
||||||
for (TestEdge e : edges) {
|
for (TestEdge e : edges) {
|
||||||
boolean isHovered = swing(() -> e.isInActivePath());
|
boolean isHovered = swing(() -> e.isInHoveredVertexPath());
|
||||||
assertFalse("Edge should not have been hovered: " + e, isHovered);
|
assertFalse("Edge should not have been hovered: " + e, isHovered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue