GT-3020 - Function Graph - for edges, split 'in active path' state into

2 parts so we can track hovered/focused/selected separately, which
prevents the focused/selected state changes from overwriting each other
This commit is contained in:
dragonmacher 2019-08-09 10:21:09 -04:00
parent 85e25af7e8
commit cb94773ce5
12 changed files with 368 additions and 218 deletions

View file

@ -34,7 +34,8 @@ 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;
@ -50,13 +51,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
@ -135,7 +146,8 @@ public class FGEdgeImpl implements FGEdge {
newEdge.layoutArticulationPoints = newPoints;
newEdge.alpha = alpha;
newEdge.inActivePath = inActivePath;
newEdge.inHoveredPath = inHoveredPath;
newEdge.inFocusedPath = inFocusedPath;
newEdge.selected = selected;
return newEdge;
}

View file

@ -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

View file

@ -1052,7 +1052,7 @@ public class GraphViewerUtils {
LinkedList<E> filteredEdges = new LinkedList<>();
if (useHover) {
for (E edge : edges) {
if (edge.isInActivePath()) {
if (edge.isInHoveredVertexPath()) {
filteredEdges.add(edge);
}
}

View file

@ -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
* 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>
* <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> {
/**
* 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<V extends VisualVertex> extends GEdge<V> {
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

View file

@ -32,9 +32,10 @@ public abstract class AbstractVisualEdge<V extends VisualVertex> 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<Point2D> articulations = new ArrayList<>();
@ -65,13 +66,23 @@ public abstract class AbstractVisualEdge<V extends VisualVertex> 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

View file

@ -81,14 +81,15 @@ import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends VisualEdge<V>>
extends BasicEdgeRenderer<V, E> {
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<V extends VisualVertex, E extends Visua
}
public void setBaseColor(Color color) {
this.baseColor = color;
this.defaultBaseColor = color;
}
public Color getBaseColor(Graph<V, E> g, E e) {
return baseColor;
return defaultBaseColor;
}
public void setHighlightColor(Color highlightColor) {
this.highlightColor = highlightColor;
this.defaultHighlightColor = highlightColor;
}
public Color getHighlightColor(Graph<V, E> 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<V extends VisualVertex, E extends Visua
float scalex = (float) g.getTransform().getScaleX();
float scaley = (float) g.getTransform().getScaleY();
boolean isActive = isInActivePath(e);
boolean isInHoveredPath = isInHoveredVertexPath(e);
boolean isInFocusedPath = isInFocusedVertexPath(e);
boolean isSelected = isSelected(e);
boolean isEmphasized = isEmphasiszed(e);
Color hoveredColor = getHighlightColor(graph, e);
Color selectedColor = getHighlightColor(graph, e).darker();
Color highlightColor = getHighlightColor(graph, e);
Color baseColor = getBaseColor(graph, e);
Color hoveredColor = highlightColor;
Color focusedColor = baseColor;
Color selectedColor = highlightColor.darker();
Color selectedAccentColor = highlightColor;
float scale = StrictMath.min(scalex, scaley);
@ -190,19 +201,39 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
Context<Graph<V, E>, E> context = Context.<Graph<V, E>, E> getInstance(graph, e);
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);
// (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 hoverStroke = getHoveredStroke(e, scale);
BasicStroke selectedAccentStroke = getSelectedAccentStroke(e, scale);
BasicStroke empahsisStroke = getEmphasisStroke(e, scale);
//
// Fill
//
Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e);
if (fillPaint != null) {
if (isActive) {
// basic shape
g.setPaint(fillPaint);
g.fill(edgeShape);
}
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);
@ -210,6 +241,14 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
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);
@ -218,12 +257,16 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
g.setStroke(saveStroke);
}
g.setPaint(fillPaint);
g.fill(edgeShape);
}
//
// 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);
@ -232,7 +275,7 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
g.setStroke(saveStroke);
}
if (isActive) {
if (isInHoveredPath) {
Stroke saveStroke = g.getStroke();
g.setPaint(hoveredColor);
g.setStroke(hoverStroke);
@ -240,54 +283,76 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
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);
}
g.setPaint(drawPaint);
g.draw(edgeShape);
// 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) {
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) {
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);
@ -297,7 +362,7 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
g.setStroke(saveStroke);
}
if (isActive) {
if (isInHoveredPath) {
Stroke saveStroke = g.getStroke();
g.setPaint(hoveredColor);
g.setStroke(hoverStroke);
@ -306,6 +371,14 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
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);
@ -315,22 +388,9 @@ public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends Visua
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.setStroke(oldArrowStroke);
g.setPaint(oldPaint);
}
}
protected Shape getVertexShapeForArrow(RenderContext<V, E> rc, Layout<V, E> layout, V v) {
// we use the default shape (the full shape) for arrow detection
@ -355,17 +415,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,
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);
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_BEVEL, 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;

View file

@ -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 <V> the vertex type
* @param <E> the edge type
*/
public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends VisualEdge<V>>
extends VisualEdgeRenderer<V, E> {
@ -38,7 +40,12 @@ public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends
}
@Override
protected boolean isInActivePath(E e) {
protected boolean isInHoveredVertexPath(E e) {
return false;
}
@Override
protected boolean isInFocusedVertexPath(E e) {
return false;
}

View file

@ -268,6 +268,9 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
public void setVertexHoverMode(PathHighlightMode mode) {
this.vertexHoverMode = Objects.requireNonNull(mode);
if (vertexHoverMode == PathHighlightMode.OFF) {
clearHoveredEdgesSwing();
}
}
public void setHoveredVertex(V hoveredVertex) {
@ -362,13 +365,13 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
private void clearHoveredEdgesSwing() {
for (E edge : graph.getEdges()) {
edge.setInActivePath(false);
edge.setInHoveredVertexPath(false);
}
}
private void clearFocusedEdgesSwing() {
for (E edge : graph.getEdges()) {
edge.setSelected(false);
edge.setInFocusedVertexPath(false);
}
}
@ -498,25 +501,25 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
private void setInFocusedEdges(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setOutFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setForwardScopedFlowFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setReverseScopedFlowFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setInOutFocusedEdgesSwing(V vertex) {
@ -525,46 +528,46 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
// Select ins and outs, one after the other.
//
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(inSupplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(inSupplier), null);
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
focusRunManager.runNext(new SelectRunnable(outSupplier), null);
focusRunManager.runNext(new SetFocusedEdgesRunnable(outSupplier), null);
}
private void setVertexCycleFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setAllCycleFocusedEdgesSwing() {
Supplier<Set<E>> supplier = () -> getAllCircuitFlowEdgesAsync();
focusRunManager.runNow(new SelectRunnable(supplier), null);
focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null);
}
private void setInHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
}
private void setOutHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
}
private void setForwardScopedFlowHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
}
private void setReverseScopedFlowHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null);
}
private void setInOutHoveredEdgesSwing(V vertex) {
@ -573,32 +576,32 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
// Select ins and outs, one after the other.
//
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(inSupplier), null);
hoverRunManager.runNow(new SetHoveredEdgesRunnable(inSupplier), null);
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNext(new HoverRunnable(outSupplier), null);
hoverRunManager.runNext(new SetHoveredEdgesRunnable(outSupplier), null);
}
private void setVertexCycleHoveredEdgesSwing(V 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) {
Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end);
focusRunManager.runNow(new SlowHoverRunnable(callback), null);
focusRunManager.runNow(new SlowSetHoveredEdgesRunnable(callback), null);
}
private void selectSwing(Collection<E> edges) {
edges.forEach(e -> e.setSelected(true));
private void setInFocusedPathOnSwing(Collection<E> edges) {
edges.forEach(e -> e.setInFocusedVertexPath(true));
listener.pathHighlightChanged(false);
}
private void activateSwing(Collection<E> edges) {
edges.forEach(e -> e.setInActivePath(true));
private void setInHoverPathOnSwing(Collection<E> edges) {
edges.forEach(e -> e.setInHoveredVertexPath(true));
listener.pathHighlightChanged(true);
}
@ -833,7 +836,7 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
CallbackAccumulator<List<V>> accumulator = new CallbackAccumulator<>(path -> {
Collection<E> edges = pathToEdgesAsync(path);
SystemUtilities.runSwingLater(() -> activateSwing(edges));
SystemUtilities.runSwingLater(() -> setInHoverPathOnSwing(edges));
});
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
@ -890,12 +893,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
* then be used to update the UI.
*/
private class HoverRunnable implements SwingRunnable {
private class SetHoveredEdgesRunnable implements SwingRunnable {
private Supplier<Set<E>> edgeSupplier;
private Set<E> edges;
HoverRunnable(Supplier<Set<E>> edgeSupplier) {
SetHoveredEdgesRunnable(Supplier<Set<E>> edgeSupplier) {
this.edgeSupplier = edgeSupplier;
}
@ -915,20 +918,20 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
if (isCancelled) {
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.
*/
private class SelectRunnable implements SwingRunnable {
private class SetFocusedEdgesRunnable implements SwingRunnable {
private Supplier<Set<E>> edgeSupplier;
private Set<E> edges;
SelectRunnable(Supplier<Set<E>> edgeSupplier) {
SetFocusedEdgesRunnable(Supplier<Set<E>> edgeSupplier) {
this.edgeSupplier = edgeSupplier;
}
@ -948,7 +951,7 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
if (isCancelled) {
return;
}
selectSwing(edges);
setInFocusedPathOnSwing(edges);
}
}
@ -956,11 +959,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
* be performed as long as possible, updating results along the way.
*/
private class SlowHoverRunnable implements MonitoredRunnable {
private class SlowSetHoveredEdgesRunnable implements MonitoredRunnable {
private Callback callback;
SlowHoverRunnable(Callback callback) {
SlowSetHoveredEdgesRunnable(Callback callback) {
this.callback = callback;
}

View file

@ -22,6 +22,7 @@ import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
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
@ -45,6 +46,8 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
private final VisualizationViewer<V, E> sourceViewer;
private final VisualizationViewer<V, E> otherViewer;
private SwingUpdateManager mouseHoverUpdater = new SwingUpdateManager(this::updateMouseHovers);
private MouseEvent lastMouseEvent;
private V hoveredVertex;
public VisualGraphHoverMousePlugin(GraphComponent<V, E, ?> graphComponent,
@ -60,16 +63,23 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
@Override
public void mouseMoved(MouseEvent e) {
updateMouseHovers(e);
lastMouseEvent = e;
mouseHoverUpdater.update();
}
private void updateMouseHovers(MouseEvent e) {
private void updateMouseHovers() {
if (graphComponent.isUninitialized()) {
return;
}
GraphViewer<V, E> viewer = getGraphViewer(e);
V newHoveredVertex = GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint());
PathHighlightMode hoverMode = graphComponent.getVertexHoverPathHighlightMode();
if (hoverMode == PathHighlightMode.OFF) {
return;
}
GraphViewer<V, E> viewer = getGraphViewer(lastMouseEvent);
V newHoveredVertex =
GraphViewerUtils.getVertexFromPointInViewSpace(viewer, lastMouseEvent.getPoint());
if (newHoveredVertex == hoveredVertex) {
return;
}
@ -123,7 +133,9 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
if (e.isPopupTrigger()) {
return;
}
updateMouseHovers(e);
lastMouseEvent = e;
updateMouseHovers();
}
@Override
@ -140,4 +152,9 @@ public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends Visua
public void mouseClicked(MouseEvent e) {
// handled by dragged and released
}
@Override
public void dispose() {
mouseHoverUpdater.dispose();
}
}

View file

@ -108,4 +108,11 @@ public interface VisualGraphMousePlugin<V extends VisualVertex, E extends Visual
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
return updater;
}
/**
* Signals to perform any cleanup when this plugin is going away
*/
public default void dispose() {
// stub
}
}

View file

@ -95,6 +95,11 @@ public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends Vi
}
public void dispose() {
for (GraphMousePlugin mp : mousePlugins) {
if (mp instanceof VisualGraphMousePlugin) {
((VisualGraphMousePlugin<?, ?>) mp).dispose();
}
}
mousePlugins.clear();
}

View file

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