From cb94773ce594ccec810a7db13c48eab89c05c57a Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 9 Aug 2019 10:21:09 -0400 Subject: [PATCH] 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 --- .../core/functiongraph/graph/FGEdgeImpl.java | 24 +- .../src/main/java/ghidra/util/ColorUtils.java | 4 +- .../ghidra/graph/viewer/GraphViewerUtils.java | 2 +- .../java/ghidra/graph/viewer/VisualEdge.java | 34 +- .../graph/viewer/edge/AbstractVisualEdge.java | 23 +- .../graph/viewer/edge/VisualEdgeRenderer.java | 378 +++++++++++------- .../VisualGraphEdgeSatelliteRenderer.java | 9 +- .../edge/VisualGraphPathHighlighter.java | 67 ++-- .../mouse/VisualGraphHoverMousePlugin.java | 27 +- .../event/mouse/VisualGraphMousePlugin.java | 7 + .../mouse/VisualGraphPluggableGraphMouse.java | 5 + .../edge/VisualGraphPathHighlighterTest.java | 6 +- 12 files changed, 368 insertions(+), 218 deletions(-) diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java index 442af5cb77..491239ae5b 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGEdgeImpl.java @@ -34,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; } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java index 0aac7fdce1..da370f41de 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java @@ -61,7 +61,7 @@ public class ColorUtils { // This can be addressed with some polar plotting: // Let the hue be the degree, and the saturation be the radius, so that the range // of values covers an area of a circle of radius 1. Let the circle be centered - // at the origin. Plot the two colors and compute their distance in euclidean + // at the origin. Plot the two colors and compute their distance in Euclidean // space. // Start by plotting the given background @@ -76,7 +76,7 @@ public class ColorUtils { // It's not pleasant to put two highly-saturated colors next to each other // Because of this restriction, we know that the maximum distance the two plotted - // points can be from eachother is 1, because their total distance to the center + // points can be from each other is 1, because their total distance to the center // is at most 1. vals[1] = 1.0f - vals[1]; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java index 5f073cabca..0db047ff6d 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java @@ -1052,7 +1052,7 @@ public class GraphViewerUtils { LinkedList filteredEdges = new LinkedList<>(); if (useHover) { for (E edge : edges) { - if (edge.isInActivePath()) { + if (edge.isInHoveredVertexPath()) { filteredEdges.add(edge); } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java index 9f79325b66..acccbdfd5a 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualEdge.java @@ -25,7 +25,9 @@ import ghidra.graph.GEdge; * *

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

Articulations - The start and end points are always part of the @@ -44,7 +46,7 @@ import ghidra.graph.GEdge; public interface VisualEdge extends GEdge { /** - * Sets this edge selected + * Sets this edge selected. This is usually in response to the user selecting the edge. * * @param selected true to select this edge; false to de-select this vertex */ @@ -58,20 +60,36 @@ public interface VisualEdge extends GEdge { public boolean isSelected(); /** - * Sets this edge to be marked as in the active path + * Sets this edge to be marked as in the active path of a currently hovered vertex * - * @param inActivePath true to be marked as in the active path; false to be marked as not + * @param inPath true to be marked as in the active path; false to be marked as not * in the active path */ - public void setInActivePath(boolean inActivePath); + public void setInHoveredVertexPath(boolean inPath); /** - * Returns true if this edge is part of an active path (this allows the edge to be - * differently rendered) + * Returns true if this edge is part of an active path for a currently hovered + * vertex (this allows the edge to be differently rendered) * * @return true if this edge is part of the active path */ - public boolean isInActivePath(); + public boolean isInHoveredVertexPath(); + + /** + * Sets this edge to be marked as in the active path of a currently focused/selected vertex + * + * @param inPath true to be marked as in the active path; false to be marked as not + * in the active path + */ + public void setInFocusedVertexPath(boolean inPath); + + /** + * Returns true if this edge is part of an active path for a currently focused/selected + * vertex (this allows the edge to be differently rendered) + * + * @return true if this edge is part of the active path + */ + public boolean isInFocusedVertexPath(); /** * Returns the points (in {@link GraphViewerUtils} View Space) of the articulation diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java index 46df28b247..a8b98c495c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/AbstractVisualEdge.java @@ -32,9 +32,10 @@ public abstract class AbstractVisualEdge implements Visu private V start; private V end; - private boolean selected; - private boolean inActivePath; + private boolean inHoveredPath = false; + private boolean inFocusedPath = false; private double alpha = 1.0; + private boolean selected; private double emphasis; private List articulations = new ArrayList<>(); @@ -65,13 +66,23 @@ public abstract class AbstractVisualEdge implements Visu } @Override - public void setInActivePath(boolean inActivePath) { - this.inActivePath = inActivePath; + public boolean isInHoveredVertexPath() { + return inHoveredPath; } @Override - public boolean isInActivePath() { - return inActivePath; + public boolean isInFocusedVertexPath() { + return inFocusedPath; + } + + @Override + public void setInHoveredVertexPath(boolean inPath) { + this.inHoveredPath = inPath; + } + + @Override + public void setInFocusedVertexPath(boolean inPath) { + this.inFocusedPath = inPath; } @Override diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java index 466a9c671b..9120363695 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualEdgeRenderer.java @@ -81,14 +81,15 @@ import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer; public abstract class VisualEdgeRenderer> extends BasicEdgeRenderer { - private static final float HOVERED_STROKE_WIDTH = 8.0f; - private static final float SELECTED_STROKE_WIDTH = 4.0f; + private static final float HOVERED_PATH_STROKE_WIDTH = 8.0f; + private static final float FOCUSED_PATH_STROKE_WIDTH = 4.0f; + private static final float SELECTED_STROKE_WIDTH = FOCUSED_PATH_STROKE_WIDTH + 2; private static final float EMPHASIZED_STOKE_WIDTH = SELECTED_STROKE_WIDTH + 3.0f; private float dashingPatternOffset; - private Color baseColor = Color.BLACK; - private Color highlightColor = Color.GRAY; + private Color defaultBaseColor = Color.BLACK; + private Color defaultHighlightColor = Color.GRAY; /** * Sets the offset value for painting dashed lines. This allows clients to animate the @@ -103,24 +104,29 @@ public abstract class VisualEdgeRenderer g, E e) { - return baseColor; + return defaultBaseColor; } public void setHighlightColor(Color highlightColor) { - this.highlightColor = highlightColor; + this.defaultHighlightColor = highlightColor; } public Color getHighlightColor(Graph g, E e) { - return highlightColor; + return defaultHighlightColor; } // template method - protected boolean isInActivePath(E e) { - return e.isInActivePath(); + protected boolean isInHoveredVertexPath(E e) { + return e.isInHoveredVertexPath(); + } + + // template method + protected boolean isInFocusedVertexPath(E e) { + return e.isInFocusedVertexPath(); } // template method @@ -153,12 +159,17 @@ public abstract class VisualEdgeRenderer, E> context = Context., E> getInstance(graph, e); boolean edgeHit = vt.transform(edgeShape).intersects(deviceRectangle); - if (edgeHit) { - - Paint oldPaint = g.getPaint(); - - // get Paints for filling and drawing - // (filling is done first so that drawing and label use same Paint) - Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e); - BasicStroke selectedStroke = getSelectedStroke(e, scale); - BasicStroke hoverStroke = getHoveredStroke(e, scale); - BasicStroke empahsisStroke = getEmphasisStroke(e, scale); - - if (fillPaint != null) { - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.fill(edgeShape); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.fill(edgeShape); - g.setStroke(saveStroke); - } - - g.setPaint(fillPaint); - g.fill(edgeShape); - } - - Paint drawPaint = rc.getEdgeDrawPaintTransformer().apply(e); - if (drawPaint != null) { - if (isEmphasized) { - Stroke saveStroke = g.getStroke(); - g.setPaint(drawPaint); - g.setStroke(empahsisStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.draw(edgeShape); - g.setStroke(saveStroke); - } - - g.setPaint(drawPaint); - g.draw(edgeShape); - - // debug - draw a box around the edge - //Rectangle shapeBounds = edgeShape.getBounds(); - //g.setPaint(Color.ORANGE); - //g.draw(shapeBounds); - } - - Predicate, E>> predicate = rc.getEdgeArrowPredicate(); - boolean drawArrow = predicate.apply(context); - if (drawArrow) { - - Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e); - Stroke old_stroke = g.getStroke(); - if (new_stroke != null) { - g.setStroke(new_stroke); - } - - Shape vs2 = getVertexShapeForArrow(rc, layout, v2); // end vertex - - boolean arrowHit = vt.transform(vs2).intersects(deviceRectangle); - Paint arrowFillPaint = rc.getArrowFillPaintTransformer().apply(e); - Paint arrowDrawPaint = rc.getArrowDrawPaintTransformer().apply(e); - - if (arrowHit) { - - EdgeArrowRenderingSupport arrowRenderingSupport = - new BasicEdgeArrowRenderingSupport<>(); - - AffineTransform at = - arrowRenderingSupport.getArrowTransform(rc, edgeShape, vs2); - if (at == null) { - return; - } - - Shape arrow = rc.getEdgeArrowTransformer().apply(context); - arrow = scaleArrowForBetterVisibility(rc, arrow); - arrow = at.createTransformedShape(arrow); - - if (isEmphasized) { - Stroke saveStroke = g.getStroke(); - g.setPaint(arrowDrawPaint); - g.setStroke(empahsisStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - if (isActive) { - Stroke saveStroke = g.getStroke(); - g.setPaint(hoveredColor); - g.setStroke(hoverStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - if (isSelected) { - Stroke saveStroke = g.getStroke(); - g.setPaint(selectedColor); - g.setStroke(selectedStroke); - g.fill(arrow); - g.draw(arrow); - g.setStroke(saveStroke); - } - - g.setPaint(arrowFillPaint); - g.fill(arrow); - g.setPaint(arrowDrawPaint); - g.draw(arrow); - } - - // restore paint and stroke - if (new_stroke != null) { - g.setStroke(old_stroke); - } - } - - // restore old paint - g.setPaint(oldPaint); + if (!edgeHit) { + return; } + + Paint oldPaint = g.getPaint(); + + // get Paints for filling and drawing + // (filling is done first so that drawing and label use the same Paint) + BasicStroke hoverStroke = getHoveredPathStroke(e, scale); + BasicStroke focusedStroke = getFocusedPathStroke(e, scale); + BasicStroke selectedStroke = getSelectedStroke(e, scale); + BasicStroke selectedAccentStroke = getSelectedAccentStroke(e, scale); + BasicStroke empahsisStroke = getEmphasisStroke(e, scale); + + // + // Fill + // + Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e); + if (fillPaint != null) { + // basic shape + g.setPaint(fillPaint); + g.fill(edgeShape); + } + + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(fillPaint); + g.setStroke(empahsisStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.fill(edgeShape); + g.setStroke(saveStroke); + } + + // + // Draw + // + Paint drawPaint = rc.getEdgeDrawPaintTransformer().apply(e); + if (drawPaint != null) { + // basic shape + g.setPaint(drawPaint); + g.draw(edgeShape); + } + + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(drawPaint); + g.setStroke(empahsisStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + + g.setPaint(selectedAccentColor); + g.setStroke(selectedAccentStroke); + g.draw(edgeShape); + + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + // debug - draw a box around the edge + //Rectangle shapeBounds = edgeShape.getBounds(); + //g.setPaint(Color.ORANGE); + //g.draw(shapeBounds); + + // + // Arrow Head + // + Predicate, E>> predicate = rc.getEdgeArrowPredicate(); + boolean drawArrow = predicate.apply(context); + if (!drawArrow) { + g.setPaint(oldPaint); + return; + } + + Stroke arrowStroke = rc.getEdgeArrowStrokeTransformer().apply(e); + Stroke oldArrowStroke = g.getStroke(); + if (arrowStroke != null) { + g.setStroke(arrowStroke); + } + + Shape vs2 = getVertexShapeForArrow(rc, layout, v2); // end vertex + boolean arrowHit = vt.transform(vs2).intersects(deviceRectangle); + if (!arrowHit) { + g.setPaint(oldPaint); + return; + } + + EdgeArrowRenderingSupport arrowRenderingSupport = + new BasicEdgeArrowRenderingSupport<>(); + AffineTransform at = arrowRenderingSupport.getArrowTransform(rc, edgeShape, vs2); + if (at == null) { + g.setPaint(oldPaint); + g.setStroke(oldArrowStroke); + return; + } + + Paint arrowFillPaint = rc.getArrowFillPaintTransformer().apply(e); + Paint arrowDrawPaint = rc.getArrowDrawPaintTransformer().apply(e); + Shape arrow = rc.getEdgeArrowTransformer().apply(context); + arrow = scaleArrowForBetterVisibility(rc, arrow); + arrow = at.createTransformedShape(arrow); + + // basic shape + g.setPaint(arrowFillPaint); + g.fill(arrow); + g.setPaint(arrowDrawPaint); + g.draw(arrow); + + if (isEmphasized) { + Stroke saveStroke = g.getStroke(); + g.setPaint(arrowDrawPaint); + g.setStroke(empahsisStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + if (isInHoveredPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(hoveredColor); + g.setStroke(hoverStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + if (isInFocusedPath) { + Stroke saveStroke = g.getStroke(); + g.setPaint(focusedColor); + g.setStroke(focusedStroke); + g.draw(edgeShape); + g.setStroke(saveStroke); + } + + if (isSelected) { + Stroke saveStroke = g.getStroke(); + g.setPaint(selectedColor); + g.setStroke(selectedStroke); + g.fill(arrow); + g.draw(arrow); + g.setStroke(saveStroke); + } + + g.setStroke(oldArrowStroke); + g.setPaint(oldPaint); } protected Shape getVertexShapeForArrow(RenderContext rc, Layout layout, V v) { @@ -355,17 +415,27 @@ public abstract class VisualEdgeRenderer rc, Graph graph, E e, float x1, float y1, float x2, float y2, boolean isLoop, Shape vertexShape); - private BasicStroke getHoveredStroke(E e, float scale) { - float width = HOVERED_STROKE_WIDTH / (float) Math.pow(scale, .80); + 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; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java index 58b4b3591b..f933a92ba7 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphEdgeSatelliteRenderer.java @@ -27,6 +27,8 @@ import ghidra.graph.viewer.VisualVertex; * A renderer designed to override default edge rendering to NOT paint emphasizing effects. We * do this because space is limited in the satellite and because this rendering can take excess * processing time. + * @param the vertex type + * @param the edge type */ public class VisualGraphEdgeSatelliteRenderer> extends VisualEdgeRenderer { @@ -38,7 +40,12 @@ public class VisualGraphEdgeSatelliteRenderer> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setOutFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setForwardScopedFlowFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setReverseScopedFlowFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setInOutFocusedEdgesSwing(V vertex) { @@ -525,46 +528,46 @@ public class VisualGraphPathHighlighter> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - focusRunManager.runNow(new SelectRunnable(inSupplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(inSupplier), null); Supplier> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - focusRunManager.runNext(new SelectRunnable(outSupplier), null); + focusRunManager.runNext(new SetFocusedEdgesRunnable(outSupplier), null); } private void setVertexCycleFocusedEdgesSwing(V vertex) { Supplier> supplier = () -> getCircuitEdgesAsync(vertex); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setAllCycleFocusedEdgesSwing() { Supplier> supplier = () -> getAllCircuitFlowEdgesAsync(); - focusRunManager.runNow(new SelectRunnable(supplier), null); + focusRunManager.runNow(new SetFocusedEdgesRunnable(supplier), null); } private void setInHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setOutHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setForwardScopedFlowHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setReverseScopedFlowHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setInOutHoveredEdgesSwing(V vertex) { @@ -573,32 +576,32 @@ public class VisualGraphPathHighlighter> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(inSupplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(inSupplier), null); Supplier> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex); - hoverRunManager.runNext(new HoverRunnable(outSupplier), null); + hoverRunManager.runNext(new SetHoveredEdgesRunnable(outSupplier), null); } private void setVertexCycleHoveredEdgesSwing(V vertex) { Supplier> supplier = () -> getCircuitEdgesAsync(vertex); - hoverRunManager.runNow(new HoverRunnable(supplier), null); + hoverRunManager.runNow(new SetHoveredEdgesRunnable(supplier), null); } private void setVertexToVertexPathHoveredEdgesSwing(V start, V end) { Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end); - focusRunManager.runNow(new SlowHoverRunnable(callback), null); + focusRunManager.runNow(new SlowSetHoveredEdgesRunnable(callback), null); } - private void selectSwing(Collection edges) { - edges.forEach(e -> e.setSelected(true)); + private void setInFocusedPathOnSwing(Collection edges) { + edges.forEach(e -> e.setInFocusedVertexPath(true)); listener.pathHighlightChanged(false); } - private void activateSwing(Collection edges) { - edges.forEach(e -> e.setInActivePath(true)); + private void setInHoverPathOnSwing(Collection edges) { + edges.forEach(e -> e.setInHoveredVertexPath(true)); listener.pathHighlightChanged(true); } @@ -833,7 +836,7 @@ public class VisualGraphPathHighlighter> accumulator = new CallbackAccumulator<>(path -> { Collection edges = pathToEdgesAsync(path); - SystemUtilities.runSwingLater(() -> activateSwing(edges)); + SystemUtilities.runSwingLater(() -> setInHoverPathOnSwing(edges)); }); TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT, @@ -890,12 +893,12 @@ public class VisualGraphPathHighlighter> edgeSupplier; private Set edges; - HoverRunnable(Supplier> edgeSupplier) { + SetHoveredEdgesRunnable(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @@ -915,20 +918,20 @@ public class VisualGraphPathHighlighter> edgeSupplier; private Set edges; - SelectRunnable(Supplier> edgeSupplier) { + SetFocusedEdgesRunnable(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @@ -948,7 +951,7 @@ public class VisualGraphPathHighlighter sourceViewer; private final VisualizationViewer otherViewer; + private SwingUpdateManager mouseHoverUpdater = new SwingUpdateManager(this::updateMouseHovers); + private MouseEvent lastMouseEvent; private V hoveredVertex; public VisualGraphHoverMousePlugin(GraphComponent graphComponent, @@ -60,16 +63,23 @@ public class VisualGraphHoverMousePlugin viewer = getGraphViewer(e); - V newHoveredVertex = GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint()); + PathHighlightMode hoverMode = graphComponent.getVertexHoverPathHighlightMode(); + if (hoverMode == PathHighlightMode.OFF) { + return; + } + + GraphViewer viewer = getGraphViewer(lastMouseEvent); + V newHoveredVertex = + GraphViewerUtils.getVertexFromPointInViewSpace(viewer, lastMouseEvent.getPoint()); if (newHoveredVertex == hoveredVertex) { return; } @@ -123,7 +133,9 @@ public class VisualGraphHoverMousePlugin updater = viewer.getViewUpdater(); return updater; } + + /** + * Signals to perform any cleanup when this plugin is going away + */ + public default void dispose() { + // stub + } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java index ae9e523b68..e8f92dbc8c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPluggableGraphMouse.java @@ -95,6 +95,11 @@ public class VisualGraphPluggableGraphMouse) mp).dispose(); + } + } mousePlugins.clear(); } diff --git a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java index 1b0edff5f3..92cf6370bc 100644 --- a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java +++ b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighterTest.java @@ -675,12 +675,12 @@ public class VisualGraphPathHighlighterTest extends AbstractVisualGraphTest { nonHoveredEdges.removeAll(expectedEdges); for (TestEdge e : expectedEdges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertTrue("Edge was not hovered: " + e, isHovered); } for (TestEdge e : nonHoveredEdges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertFalse("Edge hovered when it should not have been: " + e, isHovered); } } @@ -694,7 +694,7 @@ public class VisualGraphPathHighlighterTest extends AbstractVisualGraphTest { private void assertNotHovered(TestEdge... edges) { for (TestEdge e : edges) { - boolean isHovered = swing(() -> e.isInActivePath()); + boolean isHovered = swing(() -> e.isInHoveredVertexPath()); assertFalse("Edge should not have been hovered: " + e, isHovered); } }