mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 13:11:47 +00:00
added javadoc and updated jungrapht versions to 1.0-RC9
fixed vertex naming to create labels if no label at address. removed local vertex renaming vertex-only selection enabled and grow selection made yellow edges darker fix initial layout scaling for subgraph display address warnings and increase stroke size for vertices comments and adjusting initial layout area size calculation multiselection strategy moved buttons out of function so they can be re-enabled in popup menu cleaned up and documented selection menus improve selection following by sending only one event, filtering hidden elements, and disabliing the butten when selection is complete improve grow selection to grow from all selected vertices better generics, simplified root predicate. added flag so multiaction does not have to fire an action on the first item support renaming from the graph view added rename popup. corrected some errors use function to control mincross work based on graph size fixed LayoutWorkingDialog close issue a better way. use toggle button for magnifier and connect with Lens kill switch html escape the attributes in case they include things like < and > changed thread stop and added initial dimension function
This commit is contained in:
parent
2892aeadc7
commit
2f447ae0d0
13
.gitignore
vendored
13
.gitignore
vendored
@ -69,3 +69,16 @@ Release
|
||||
*/*/*/*/src-gen
|
||||
*/*/*/*/model/generated
|
||||
*/*/*/*/test-bin
|
||||
|
||||
# Ignore Intellij metadata
|
||||
**/*.iml
|
||||
**/.idea
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# Ignore gradle wrapper files
|
||||
gradle/wrapper
|
||||
gradlew
|
||||
gradlew.*
|
||||
|
||||
|
||||
|
@ -19,7 +19,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import ghidra.app.cmd.label.AddLabelCmd;
|
||||
import ghidra.app.cmd.label.RenameLabelCmd;
|
||||
import ghidra.app.events.*;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginEvent;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@ -161,6 +164,21 @@ public abstract class AddressBasedGraphDisplayListener
|
||||
return p == program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateVertexName(String vertexId, String oldName, String newName) {
|
||||
Address address = getAddressForVertexId(vertexId);
|
||||
Symbol symbol = program.getSymbolTable().getPrimarySymbol(address);
|
||||
|
||||
Command command;
|
||||
if (symbol != null) {
|
||||
command = new RenameLabelCmd(address, oldName, newName, SourceType.USER_DEFINED);
|
||||
}
|
||||
else {
|
||||
command = new AddLabelCmd(address, newName, SourceType.USER_DEFINED);
|
||||
}
|
||||
return tool.execute(command, program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
if (!(ev.containsEvent(ChangeManager.DOCR_SYMBOL_ADDED) ||
|
||||
|
@ -1,7 +1,9 @@
|
||||
EXCLUDE FROM GHIDRA JAR: true
|
||||
|
||||
MODULE FILE LICENSE: lib/jungrapht-visualization-1.0-RC8.jar BSD
|
||||
MODULE FILE LICENSE: lib/jgrapht-core-1.4.0.jar LGPL 2.1
|
||||
MODULE FILE LICENSE: lib/jgrapht-io-1.4.0.jar LGPL 2.1
|
||||
MODULE FILE LICENSE: lib/jungrapht-visualization-1.0-SNAPSHOT.jar BSD
|
||||
MODULE FILE LICENSE: lib/jgrapht-core-1.5.0.jar LGPL 2.1
|
||||
MODULE FILE LICENSE: lib/jgrapht-io-1.5.0.jar LGPL 2.1
|
||||
MODULE FILE LICENSE: lib/jheaps-0.11.jar Apache License 2.0
|
||||
MODULE FILE LICENSE: lib/log4j-slf4j-impl-2.12.1.jar Apache License 2.0
|
||||
MODULE FILE LICENSE: lib/slf4j-api-1.7.25.jar MIT
|
||||
MODULE FILE LICENSE: lib/slf4j-nop-1.7.25.jar MIT
|
||||
|
@ -11,13 +11,17 @@ eclipse.project.name = 'Features Graph Services'
|
||||
dependencies {
|
||||
compile project(":Base")
|
||||
|
||||
compile "com.github.tomnelson:jungrapht-visualization:1.0-RC8"
|
||||
compile "org.jgrapht:jgrapht-core:1.4.0"
|
||||
compile "com.github.tomnelson:jungrapht-visualization:1.0-RC9"
|
||||
compile "com.github.tomnelson:jungrapht-layout:1.0-RC9"
|
||||
compile "org.jgrapht:jgrapht-core:1.5.0"
|
||||
|
||||
// not using jgrapht-io code that depends on antlr, so exclude antlr
|
||||
compile ("org.jgrapht:jgrapht-io:1.4.0") { exclude group: "org.antlr", module: "antlr4-runtime" }
|
||||
compile ("org.jgrapht:jgrapht-io:1.5.0") { exclude group: "org.antlr", module: "antlr4-runtime" }
|
||||
runtime "org.slf4j:slf4j-api:1.7.25"
|
||||
// use this if you want no slf4j log messages
|
||||
runtime "org.slf4j:slf4j-nop:1.7.25"
|
||||
// use this if you want slf4j log messages sent to log4j
|
||||
// runtime "org.apache.logging.log4j:log4j-slf4j-impl:2.12.1"
|
||||
runtime "org.jheaps:jheaps:0.11"
|
||||
|
||||
helpPath project(path: ":Base", configuration: 'helpPath')
|
||||
|
@ -15,6 +15,7 @@ src/main/help/help/topics/GraphServices/GraphDisplay.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/GraphServices/GraphExport.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/GraphServices/images/DefaultGraphDisplay.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/GraphServices/images/ExportDialog.png||GHIDRA||||END|
|
||||
src/main/resources/images/Lasso.png||GHIDRA||||END|
|
||||
src/main/resources/images/magnifier.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
src/main/resources/images/redspheregraph.png||GHIDRA||||END|
|
||||
src/main/resources/images/sat2.png||GHIDRA||||END|
|
||||
|
@ -27,6 +27,8 @@
|
||||
<ul>
|
||||
<li>MouseButton1+drag will translate the display in the x and y axis</li>
|
||||
<li>Mouse Wheel will zoom in and out</li>
|
||||
<li>CTRL+Mouse Wheel will zoom in and out in the X-Axis only</li>
|
||||
<li>ALT+Mouse Wheel will zoom in and out in the Y-Axis only</li>
|
||||
<li>Ctrl+MouseButton1 will select a vertex or edge</li>
|
||||
<ul>
|
||||
<li>Shift+Ctrl+MouseButton1 over an unselected vertex will add that vertex to the selection</li>
|
||||
@ -37,30 +39,41 @@
|
||||
</ul>
|
||||
<H2>Upper-right Icon Buttons:</H2>
|
||||
<ul>
|
||||
<li>The <IMG src="images/fingerPointer.png"> toggle button, when 'set' will cause a located vertex (red arrow) to be scrolled to the center of the view</li>
|
||||
<li>The <IMG src="images/sat2.png" width="16" height="16"> button will open a satellite mini view of the graph in the lower right corner. The mini-view can be manipulated with the mouse to affect the main view</li>
|
||||
<li>The <IMG src="images/locationIn.gif"> toggle button, when 'set' will cause a located vertex (red arrow) to be scrolled to the center of the view</li>
|
||||
<li>The <IMG src="images/Lasso.png" width="16" height="16"> toggle button, when 'set' will allow the user to draw a free-form shape that encloses the vertices they wish to select.
|
||||
<li>The <IMG src="images/sat2.png" width="16" height="16"> toggle button, when 'set' will open a satellite mini view of the graph in the lower right corner. The mini-view can be manipulated with the mouse to affect the main view</li>
|
||||
<li>The <IMG src="images/reload3.png"> button will reset any visual transformations on the graph and center it at a best-effort size</li>
|
||||
<li>The <IMG src="images/magnifier.png"> button will open a rectangular magnification lens in the graph view</li>
|
||||
<li>The <IMG src="images/magnifier.png"> toggle button, when 'set' will open a rectangular magnification lens in the graph view</li>
|
||||
<ul>
|
||||
<li>MouseButton1 click-drag on the lens center circle to move the magnifier lens</li>
|
||||
<li>MouseButton1 click-draw on a lens edge diamond to resize the magnifier lens </li>
|
||||
<li>MouseButton1 click on the upper-right circle-cross to dispose of the magnifier lens</li>
|
||||
<li>Ctrl-MouseWheel to change the magnification of the lens</li>
|
||||
<li>MouseWheel will change the magnification of the lens</li>
|
||||
</ul>
|
||||
<li>The <IMG src="images/view-filter.png"> button will open a Filter dialog. Select buttons in the dialog to hide specific vertices or edges in the display</li>
|
||||
<li>The <IMG src="images/filter_on.png"> button will open a Filter dialog. Select buttons in the dialog to hide specific vertices or edges in the display</li>
|
||||
<ul>
|
||||
<li>The Filter dialog buttons are created by examining the graph vertex/edge properties to discover candidates for filtering</li>
|
||||
</ul>
|
||||
<li>Pull-Down the <IMG src="images/katomic.png" width="16" height="16"> Arrangement menu to select one of several graph layout algorithms.</li>
|
||||
<ul>
|
||||
<li>Compact Hierarchical is the <b>TidierTree Layout Algorithm</b>. It builds a tree structure and attempts to reduce horizontal space.</li>
|
||||
<li>Hierarchical is a basic Tree algorithm. It prioritizes 'important' edges while constructing the tree.</li>
|
||||
<li>Hierarchical - Edge-Aware is a basic Tree algorithm that attempts to align important edges vertically.</li>
|
||||
<li>Hierarchical Multi Row is the Tree algorithm above, but it will create new rows (typewriter fashion) to reduce horizontal spread.</li>
|
||||
<li>Compact Radial is the <b>TidierTree Layout Algorithm</b> with the root(s) at the center and child vertices radiating outwards.</li>
|
||||
<li>Hierarchical MinCross is the <b>Sugiyama Layout Algorithm</b>. It attempts to route edges around vertices in order to reduce crossing.</li>
|
||||
<ul>There are four layering algorithms:
|
||||
<li>Top Down - biases the vertices to the top</li>
|
||||
<li>Longest Path - biases the vertices to the bottom</li>
|
||||
<li>Network Simplex - layers after finding an 'optimal tree'</li>
|
||||
<li>Coffman Graham - biases the vertices using a scheduling algorithm to minimize length</li>
|
||||
</ul>
|
||||
<li>Circle will arrange vertices in a Circle. If there are not too many edges (less than specified in the jungrapht.circle.reduceEdgeCrossingMaxEdges property with a default of 200), it will attempt to reduce edge crossing by rearranging the vertices.</li>
|
||||
<li>Force Balanced is a <b>Force Directed Layout Algorithm</b> using the the <b>Kamada Kawai</b> approach. It attempts to balance the graph by considering vertices and edge connections.</li>
|
||||
<li>Force Directed is a <b>Force Directed Layout Algorithm</b> using the <b>Fructermann Reingold</b> approach. It pushes unconnected vertices apart and draws connected vertices together.</li>
|
||||
<li>Circle will arrange vertices in a Circle. If there are not too many edges (less than specified in the jungrapht.circle.reduceEdgeCrossingMaxEdges property with a default of 200), it will attempt to reduce edge crossing by rearranging the vertices.</li>
|
||||
<li>Compact Hierarchical is the <b>TidierTree Layout Algorithm</b>. It builds a tree structure and attempts to reduce horizontal space.</li>
|
||||
<li>Hierarchical MinCross is the <b>Sugiyama Layout Algorithm</b>. It attempts to route edges around vertices in order to reduce crossing.</li>
|
||||
<li>Hierarchical is a basic Tree algorithm. It prioritizes 'important' edges while constructing the tree.</li>
|
||||
<li>Hierarchical Multi Row is the Tree algorithm above, but it will create new rows (typewriter fashion) to reduce horizontal spread.</li>
|
||||
<li>Radial is a Tree structure with the root(s) at the center and child vertices radiating outwards.</li>
|
||||
<li>Balloon is a Tree structure with the root(s) at the centers of circles in a radial pattern</li>
|
||||
<li>GEM is a Force Directed layout with locally separated components </li>
|
||||
</ul>
|
||||
|
||||
</BODY>
|
||||
|
@ -33,7 +33,7 @@ public class AttributedGraphExporterFactory
|
||||
AttributedGraphExporterFactory() {
|
||||
vertexLabelProvider = AttributedVertex::getName;
|
||||
edgeLabelProvider = Object::toString;
|
||||
edgeIdProvider = e -> e.getId();
|
||||
edgeIdProvider = AttributedEdge::getId;
|
||||
edgeAttributeProvider = AttributedGraphExporterFactory::getComponentAttributes;
|
||||
vertexAttributeProvider = AttributedGraphExporterFactory::getComponentAttributes;
|
||||
vertexIdProvider = AttributedVertex::getId;
|
||||
@ -54,7 +54,7 @@ public class AttributedGraphExporterFactory
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new AbstractMap.SimpleEntry<String, Attribute>(entry.getKey(),
|
||||
new DefaultAttribute<String>(entry.getValue(), AttributeType.STRING)))
|
||||
new DefaultAttribute<>(entry.getValue(), AttributeType.STRING)))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
class ExportAttributedGraphDisplay implements GraphDisplay {
|
||||
|
||||
private PluginTool pluginTool;
|
||||
private final PluginTool pluginTool;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
|
@ -52,8 +52,7 @@ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvide
|
||||
public GraphDisplay getGraphDisplay(boolean reuseGraph,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
ExportAttributedGraphDisplay display = new ExportAttributedGraphDisplay(this);
|
||||
return display;
|
||||
return new ExportAttributedGraphDisplay(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,7 +20,7 @@ enum GraphExportFormat {
|
||||
DIMACS("col"),
|
||||
DOT("gv"),
|
||||
GML("gml"),
|
||||
GRAPHML("graphhml"),
|
||||
GRAPHML("graphml"),
|
||||
JSON("json"),
|
||||
LEMON("lgf"),
|
||||
MATRIX("g"),
|
||||
|
@ -59,7 +59,7 @@ public class GraphExporterDialog extends DialogComponentProvider {
|
||||
private JTextField filePathTextField;
|
||||
private JButton fileChooserButton;
|
||||
private GhidraComboBox<GraphExportFormat> comboBox;
|
||||
private Graph<AttributedVertex, AttributedEdge> graph;
|
||||
private final Graph<AttributedVertex, AttributedEdge> graph;
|
||||
|
||||
/**
|
||||
* Construct a new ExporterDialog for exporting a program, optionally only exported a
|
||||
|
@ -26,14 +26,14 @@ import ghidra.graph.job.AbstractAnimatorJob;
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
public class CenterAnimation extends AbstractAnimatorJob {
|
||||
public class CenterAnimation<V, E> extends AbstractAnimatorJob {
|
||||
protected int duration = 1000;
|
||||
private Point2D oldPoint;
|
||||
private Point2D newPoint;
|
||||
private Point2D lastPoint = new Point2D.Double();
|
||||
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
|
||||
private final Point2D oldPoint;
|
||||
private final Point2D newPoint;
|
||||
private final Point2D lastPoint = new Point2D.Double();
|
||||
private final VisualizationViewer<V, E> viewer;
|
||||
|
||||
public CenterAnimation(VisualizationViewer<AttributedVertex, AttributedEdge> viewer,
|
||||
public CenterAnimation(VisualizationViewer<V, E> viewer,
|
||||
Point2D oldPoint, Point2D newPoint) {
|
||||
this.viewer = viewer;
|
||||
this.oldPoint = oldPoint;
|
||||
|
@ -195,6 +195,11 @@ public abstract class Colors {
|
||||
*/
|
||||
private static Color blue = new Color(100, 100, 255);
|
||||
|
||||
/**
|
||||
* a yellow that is darker than {@code Color.yellow}
|
||||
*/
|
||||
private static Color darkerYellow = new Color(225, 225, 0);
|
||||
|
||||
/**
|
||||
* these are vertex or edge types that have defined colors
|
||||
* (the keys are the property values for the vertex/edge keys:
|
||||
@ -229,7 +234,7 @@ public abstract class Colors {
|
||||
entry("Computed",Color.cyan),
|
||||
entry("Indirection",Color.pink),
|
||||
entry("Unconditional-Jump", Color.green),
|
||||
entry("Conditional-Jump", Color.yellow),
|
||||
entry("Conditional-Jump", darkerYellow),
|
||||
entry("Terminator", WEB_COLOR_MAP.get("Purple")),
|
||||
entry("Conditional-Return", WEB_COLOR_MAP.get("Purple"))
|
||||
);
|
||||
|
@ -29,7 +29,8 @@ final class DefaultDisplayGraphIcons {
|
||||
|
||||
public static final Icon SATELLITE_VIEW_ICON = Icons.get("images/sat2.png");
|
||||
public static final Icon VIEW_MAGNIFIER_ICON = Icons.get("images/magnifier.png");
|
||||
public static final Icon GRAPH_FILTERS_ICON = Icons.get("images/view-filter.png");
|
||||
public static final Icon PROGRAM_GRAPH_ICON = Icons.get("images/redspheregraph.png");
|
||||
public static final Icon LAYOUT_ALGORITHM_ICON = Icons.get("images/katomic.png");
|
||||
public static final Icon LASSO_ICON = Icons.get("images/Lasso.png");
|
||||
public static final Icon FILTER_ICON = Icons.get("images/filter_on.png");
|
||||
}
|
||||
|
@ -15,54 +15,97 @@
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.jgrapht.Graph;
|
||||
import org.jungrapht.visualization.*;
|
||||
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.control.*;
|
||||
import org.jungrapht.visualization.decorators.*;
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.layout.model.Point;
|
||||
import org.jungrapht.visualization.renderers.*;
|
||||
import org.jungrapht.visualization.renderers.Renderer;
|
||||
import org.jungrapht.visualization.selection.MutableSelectedState;
|
||||
import org.jungrapht.visualization.transform.*;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.builder.*;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.MultiStateActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import docking.menu.ActionState;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.graph.AttributeFilters;
|
||||
import ghidra.graph.job.GraphJobRunner;
|
||||
import ghidra.service.graph.*;
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedGraph;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
import ghidra.service.graph.DummyGraphDisplayListener;
|
||||
import ghidra.service.graph.GraphDisplay;
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jungrapht.visualization.RenderContext;
|
||||
import org.jungrapht.visualization.SatelliteVisualizationViewer;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.control.DefaultGraphMouse;
|
||||
import org.jungrapht.visualization.control.DefaultLensGraphMouse;
|
||||
import org.jungrapht.visualization.control.DefaultSatelliteGraphMouse;
|
||||
import org.jungrapht.visualization.control.LensGraphMouse;
|
||||
import org.jungrapht.visualization.control.LensMagnificationGraphMousePlugin;
|
||||
import org.jungrapht.visualization.control.MultiSelectionStrategy;
|
||||
import org.jungrapht.visualization.decorators.EdgeShape;
|
||||
import org.jungrapht.visualization.decorators.EllipseShapeFunction;
|
||||
import org.jungrapht.visualization.decorators.IconShapeFunction;
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.layout.model.Point;
|
||||
import org.jungrapht.visualization.renderers.JLabelVertexLabelRenderer;
|
||||
import org.jungrapht.visualization.renderers.LightweightVertexRenderer;
|
||||
import org.jungrapht.visualization.renderers.ModalRenderer;
|
||||
import org.jungrapht.visualization.renderers.Renderer;
|
||||
import org.jungrapht.visualization.selection.MutableSelectedState;
|
||||
import org.jungrapht.visualization.selection.VertexEndpointsSelectedEdgeSelectedState;
|
||||
import org.jungrapht.visualization.transform.Lens;
|
||||
import org.jungrapht.visualization.transform.LensSupport;
|
||||
import org.jungrapht.visualization.transform.MutableTransformer;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
|
||||
import org.jungrapht.visualization.util.RectangleUtils;
|
||||
import resources.Icons;
|
||||
import util.CollectionUtils;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JRadioButton;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*;
|
||||
import static org.jungrapht.visualization.renderers.BiModalRenderer.LIGHTWEIGHT;
|
||||
|
||||
/**
|
||||
* Delegates to a {@link VisualizationViewer} to draw a graph visualization
|
||||
*/
|
||||
public class DefaultGraphDisplay implements GraphDisplay {
|
||||
|
||||
public static final String FAVORED_EDGE = "Fall-Through";
|
||||
private static final int MAX_NODES = 10000;
|
||||
private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000);
|
||||
public static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
|
||||
public static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
|
||||
|
||||
@ -84,22 +127,22 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
/**
|
||||
* the delegate viewer to display the ProgramGraph
|
||||
*/
|
||||
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
|
||||
private final VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
|
||||
|
||||
/**
|
||||
* the {@link PluginTool}
|
||||
*/
|
||||
private PluginTool pluginTool;
|
||||
private final PluginTool pluginTool;
|
||||
|
||||
/**
|
||||
* the {@link Plugin} that manages this {@link GraphDisplay}
|
||||
*/
|
||||
private String pluginName = "ProgramGraphPlugin";
|
||||
private final String pluginName = "ProgramGraphPlugin";
|
||||
|
||||
/**
|
||||
* provides the component for the {@link GraphDisplay}
|
||||
*/
|
||||
private DefaultGraphDisplayComponentProvider componentProvider;
|
||||
private final DefaultGraphDisplayComponentProvider componentProvider;
|
||||
|
||||
/**
|
||||
* whether to scroll the visualization in order to center the selected vertex
|
||||
@ -110,14 +153,65 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
/**
|
||||
* allows selection of various {@link LayoutAlgorithm} ('arrangements')
|
||||
*/
|
||||
private LayoutTransitionManager layoutTransitionManager;
|
||||
private final LayoutTransitionManager<AttributedVertex, AttributedEdge> layoutTransitionManager;
|
||||
|
||||
/**
|
||||
* manages highlight painting of a single selected vertex
|
||||
* provides graph displays for supplied graphs
|
||||
*/
|
||||
private SingleSelectedVertexPaintable<AttributedVertex, AttributedEdge> singleSelectedVertexPaintable;
|
||||
private MultiSelectedVertexPaintable<AttributedVertex, AttributedEdge> multiSelectedVertexPaintable;
|
||||
private DefaultGraphDisplayProvider graphDisplayProvider;
|
||||
private final DefaultGraphDisplayProvider graphDisplayProvider;
|
||||
/**
|
||||
* a 'busy' dialog to show while the layout algorithm is working
|
||||
*/
|
||||
private LayoutWorkingDialog layoutWorkingDialog;
|
||||
/**
|
||||
* the vertex that has been nominated to be 'located' in the graph display and listing
|
||||
*/
|
||||
private AttributedVertex locatedVertex;
|
||||
private final GraphJobRunner jobRunner = new GraphJobRunner();
|
||||
/**
|
||||
* a satellite view that shows in the lower left corner as a birds-eye view of the graph display
|
||||
*/
|
||||
private final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer;
|
||||
/**
|
||||
* generated filters on edges
|
||||
*/
|
||||
private AttributeFilters edgeFilters;
|
||||
/**
|
||||
* generated filters on vertices
|
||||
*/
|
||||
private AttributeFilters vertexFilters;
|
||||
/**
|
||||
* a dialog populated with generated vertex/edge filters
|
||||
*/
|
||||
private FilterDialog filterDialog;
|
||||
/**
|
||||
* holds the vertex icons (instead of recomputing them)
|
||||
*/
|
||||
private GhidraIconCache iconCache;
|
||||
/**
|
||||
* multi-selection is done in a free-form traced shape instead of a rectangle
|
||||
*/
|
||||
private boolean freeFormSelection;
|
||||
|
||||
/**
|
||||
* Will accept a {@link Graph} and use it to create a new graph display in
|
||||
* a new tab or new window
|
||||
*/
|
||||
Consumer<Graph<AttributedVertex, AttributedEdge>> subgraphConsumer =
|
||||
g -> {
|
||||
try {
|
||||
AttributedGraph attributedGraph = new AttributedGraph();
|
||||
g.vertexSet().forEach(attributedGraph::addVertex);
|
||||
g.edgeSet().forEach(e -> {
|
||||
AttributedVertex source = g.getEdgeSource(e);
|
||||
AttributedVertex target = g.getEdgeTarget(e);
|
||||
attributedGraph.addEdge(source, target, e);
|
||||
});
|
||||
displaySubGraph(attributedGraph);
|
||||
} catch (CancelledException e) {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the initial display, the graph-less visualization viewer, and its controls
|
||||
@ -129,14 +223,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
this.displayId = id;
|
||||
this.pluginTool = graphDisplayProvider.getPluginTool();
|
||||
this.viewer = createViewer();
|
||||
|
||||
buildHighlighers();
|
||||
|
||||
componentProvider = new DefaultGraphDisplayComponentProvider(this, pluginTool);
|
||||
componentProvider.addToTool();
|
||||
satelliteViewer = createSatelliteViewer(viewer);
|
||||
layoutTransitionManager =
|
||||
new LayoutTransitionManager(viewer, this::isRoot, this::isFavoredEdge);
|
||||
new LayoutTransitionManager<>(viewer, this::isRoot);
|
||||
|
||||
viewer.getComponent().addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
@ -150,6 +243,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
}
|
||||
});
|
||||
|
||||
viewer.setInitialDimensionFunction(InitialDimensionFunction
|
||||
.builder(viewer.getRenderContext().getVertexBoundsFunction()).build());
|
||||
|
||||
createActions();
|
||||
connectSelectionStateListeners();
|
||||
}
|
||||
@ -164,7 +260,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
return displayId;
|
||||
}
|
||||
|
||||
private LensSupport<LensGraphMouse> createManifiers() {
|
||||
/**
|
||||
* create a magnification lens for the graph display
|
||||
* @return a {@link LensSupport} for the new magnifier
|
||||
*/
|
||||
private LensSupport<LensGraphMouse> createMagnifier() {
|
||||
Lens lens = Lens.builder().lensShape(Lens.Shape.RECTANGLE).magnification(3.f).build();
|
||||
lens.setMagnification(2.f);
|
||||
LensMagnificationGraphMousePlugin magnificationPlugin =
|
||||
@ -172,52 +272,71 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
|
||||
MutableTransformer transformer = viewer.getRenderContext()
|
||||
.getMultiLayerTransformer()
|
||||
.getTransformer(MultiLayerTransformer.Layer.VIEW);
|
||||
.getTransformer(VIEW);
|
||||
|
||||
MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens)
|
||||
// this lens' delegate is the viewer's VIEW layer
|
||||
.delegate(transformer)
|
||||
.build();
|
||||
|
||||
LensGraphMouse lensGraphMouse = new DefaultLensGraphMouse<>(magnificationPlugin);
|
||||
return MagnifyImageLensSupport.builder(viewer)
|
||||
.lensTransformer(shapeTransformer)
|
||||
.lensGraphMouse(new DefaultLensGraphMouse<>(magnificationPlugin))
|
||||
.lensGraphMouse(lensGraphMouse)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* create the highlighters ({@code Paintable}s to show which vertices have been selected or located
|
||||
*/
|
||||
private void buildHighlighers() {
|
||||
// for highlighting of multiple selected vertices
|
||||
this.multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer)
|
||||
MultiSelectedVertexPaintable<AttributedVertex, AttributedEdge> multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer)
|
||||
.selectionStrokeMin(4.f)
|
||||
.selectionPaint(Color.red)
|
||||
.useBounds(true)
|
||||
.useBounds(false)
|
||||
.build();
|
||||
|
||||
// for highlighting of single 'located' vertices
|
||||
this.singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer)
|
||||
|
||||
// manages highlight painting of a single selected vertex
|
||||
SingleSelectedVertexPaintable<AttributedVertex, AttributedEdge> singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer)
|
||||
.selectionStrokeMin(4.f)
|
||||
.selectionPaint(Color.red)
|
||||
.selectedVertexFunction(vs -> this.locatedVertex)
|
||||
.build();
|
||||
|
||||
// this draws the selection highlights
|
||||
// draws the selection highlights
|
||||
viewer.addPostRenderPaintable(multiSelectedVertexPaintable);
|
||||
|
||||
// this draws the location arrow
|
||||
// draws the location arrow
|
||||
viewer.addPostRenderPaintable(singleSelectedVertexPaintable);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* create the action icon buttons on the upper-right of the graph display window
|
||||
*/
|
||||
private void createActions() {
|
||||
|
||||
// create a toggle for 'scroll to selected vertex'
|
||||
new ToggleActionBuilder("Scroll To Selection", pluginName)
|
||||
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
|
||||
.description("Scroll to Selection")
|
||||
.description("Scroll display to center the 'Located' vertex")
|
||||
.selected(false)
|
||||
.onAction(context -> enableScrollToSelection =
|
||||
((AbstractButton) context.getSourceObject()).isSelected())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create a toggle for enabling 'free-form' selection: selection is
|
||||
// inside of a traced shape instead of a rectangle
|
||||
new ToggleActionBuilder("Free-Form Selection", pluginName)
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
|
||||
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)")
|
||||
.selected(false)
|
||||
.onAction(context ->
|
||||
freeFormSelection = ((AbstractButton) context.getSourceObject()).isSelected())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
|
||||
// create an icon button to display the satellite view
|
||||
new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON)
|
||||
@ -225,56 +344,81 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create an icon button to reset the view transformations to identity (scaled to layout)
|
||||
new ActionBuilder("Reset View", pluginName).description("Reset all view transforms")
|
||||
new ActionBuilder("Reset View", pluginName)
|
||||
.description("Reset all view transforms to center graph in display")
|
||||
.toolBarIcon(Icons.REFRESH_ICON)
|
||||
.onAction(context -> {
|
||||
viewer.reset();
|
||||
viewer.scaleToLayout(true);
|
||||
})
|
||||
.onAction(context -> viewer.scaleToLayout())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create a button to show the view magnify lens
|
||||
LensSupport<LensGraphMouse> magnifyViewSupport = createManifiers();
|
||||
@SuppressWarnings("unchecked")
|
||||
LensSupport<LensGraphMouse>[] lenses = new LensSupport[] { magnifyViewSupport };
|
||||
new ActionBuilder("View Magnifier", pluginName).description("Show View Magnifier")
|
||||
LensSupport<LensGraphMouse> magnifyViewSupport = createMagnifier();
|
||||
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", pluginName)
|
||||
.description("Show View Magnifier")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
|
||||
.onAction(context -> {
|
||||
Arrays.stream(lenses).forEach(LensSupport::deactivate);
|
||||
magnifyViewSupport.activate();
|
||||
})
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
.onAction(context -> magnifyViewSupport.activate(
|
||||
((AbstractButton) context.getSourceObject()).isSelected()
|
||||
))
|
||||
.build();
|
||||
magnifyViewSupport.addItemListener(itemEvent ->
|
||||
lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED));
|
||||
componentProvider.addLocalAction(lensToggle);
|
||||
|
||||
// create an action button to show a dialog with generated filters
|
||||
new ActionBuilder("Show Filters", pluginName).description("Show Graph Filters")
|
||||
.toolBarIcon(Icons.CONFIGURE_FILTER_ICON)
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.FILTER_ICON)
|
||||
.onAction(context -> showFilterDialog())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create a menu with graph layout algorithm selections
|
||||
new MultiStateActionBuilder<String>("Arrangement", pluginName)
|
||||
.description("Select Layout Arrangement")
|
||||
.description("Select Layout Arrangement Algorithm")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON)
|
||||
.fireFirstAction(false)
|
||||
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
|
||||
.addStates(getLayoutActionStates())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// show a 'busy' dialog while the layout algorithm is computing vertex locations
|
||||
viewer.getVisualizationModel().getLayoutModel()
|
||||
.getLayoutStateChangeSupport().addLayoutStateChangeListener(
|
||||
evt -> {
|
||||
if (evt.active) {
|
||||
Swing.runLater(this::showLayoutWorking);
|
||||
} else {
|
||||
Swing.runLater(this::hideLayoutWorking);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a {@code List} of {@code ActionState} buttons for the
|
||||
* configured layout algorithms
|
||||
* @return a {@code List} of {@code ActionState} buttons
|
||||
*/
|
||||
private List<ActionState<String>> getLayoutActionStates() {
|
||||
String[] names = layoutTransitionManager.getLayoutNames();
|
||||
List<ActionState<String>> actionStates = new ArrayList<>();
|
||||
for (String layoutName : names) {
|
||||
actionStates.add(new ActionState<String>(layoutName,
|
||||
actionStates.add(new ActionState<>(layoutName,
|
||||
DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName));
|
||||
}
|
||||
return actionStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* respond to a change in the layout name
|
||||
* @param layoutName the name of the layout algorithm to apply
|
||||
*/
|
||||
private void layoutChanged(String layoutName) {
|
||||
if (layoutTransitionManager != null) {
|
||||
layoutTransitionManager.setLayout(layoutName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show the dialog with generated filters
|
||||
*/
|
||||
private void showFilterDialog() {
|
||||
if (filterDialog == null) {
|
||||
if (vertexFilters == null) {
|
||||
@ -286,6 +430,31 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
componentProvider.getTool().showDialog(filterDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* show the 'busy' dialog indicating that the layout algorithm is working
|
||||
*/
|
||||
protected void showLayoutWorking() {
|
||||
if (this.layoutWorkingDialog != null) {
|
||||
layoutWorkingDialog.close();
|
||||
}
|
||||
this.layoutWorkingDialog =
|
||||
new LayoutWorkingDialog(viewer.getVisualizationModel().getLayoutAlgorithm());
|
||||
componentProvider.getTool().showDialog(layoutWorkingDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* hide the 'busy' dialog for the layout algorithm work
|
||||
*/
|
||||
protected void hideLayoutWorking() {
|
||||
if (this.layoutWorkingDialog != null) {
|
||||
layoutWorkingDialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add or remove the satellite viewer
|
||||
* @param context information about the event
|
||||
*/
|
||||
private void toggleSatellite(ActionContext context) {
|
||||
if (((AbstractButton) context.getSourceObject()).isSelected()) {
|
||||
viewer.getComponent().add(satelliteViewer.getComponent());
|
||||
@ -294,7 +463,17 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
viewer.getComponent().remove(satelliteViewer.getComponent());
|
||||
}
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* from the supplied {@link Graph}, create a new GraphDisplay in a new window or tab
|
||||
* @param subGraph
|
||||
* @throws CancelledException
|
||||
*/
|
||||
private void displaySubGraph(Graph<AttributedVertex, AttributedEdge> subGraph) throws CancelledException {
|
||||
GraphDisplay graphDisplay = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
|
||||
graphDisplay.setGraph((AttributedGraph)subGraph, "SubGraph", false, TaskMonitor.DUMMY);
|
||||
graphDisplay.setGraphDisplayListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,11 +483,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
*/
|
||||
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> createSatelliteViewer(
|
||||
VisualizationViewer<AttributedVertex, AttributedEdge> parentViewer) {
|
||||
Dimension viewerSize = parentViewer.getSize();
|
||||
Dimension satelliteSize = new Dimension(
|
||||
viewerSize.width / 4, viewerSize.height / 4);
|
||||
final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer =
|
||||
SatelliteVisualizationViewer.builder(parentViewer)
|
||||
.viewSize(new Dimension(250, 250))
|
||||
.viewSize(satelliteSize)
|
||||
.build();
|
||||
satelliteViewer.setGraphMouse(new DefaultSatelliteGraphMouse<>());
|
||||
satelliteViewer.setGraphMouse(new DefaultSatelliteGraphMouse());
|
||||
satelliteViewer.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor);
|
||||
satelliteViewer.getRenderContext()
|
||||
.setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke);
|
||||
@ -316,9 +498,20 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
satelliteViewer.scaleToLayout();
|
||||
satelliteViewer.getRenderContext().setVertexLabelFunction(n -> null);
|
||||
satelliteViewer.getComponent().setBorder(BorderFactory.createEtchedBorder());
|
||||
parentViewer.getComponent().addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent evt) {
|
||||
Dimension size = evt.getComponent().getSize();
|
||||
Dimension quarterSize = new Dimension(size.width / 4, size.height / 4);
|
||||
satelliteViewer.getComponent().setSize(quarterSize);
|
||||
}
|
||||
});
|
||||
return satelliteViewer;
|
||||
}
|
||||
|
||||
/**
|
||||
* close this graph display
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
graphDisplayProvider.remove(this);
|
||||
@ -328,12 +521,23 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
listener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* accept a {@code GraphDisplayListener}
|
||||
* @param listener the listener to be notified
|
||||
*/
|
||||
@Override
|
||||
public void setGraphDisplayListener(GraphDisplayListener listener) {
|
||||
if (this.listener != null) {
|
||||
this.listener.graphClosed();
|
||||
}
|
||||
this.listener = listener;
|
||||
DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse =
|
||||
new GhidraGraphMouse<>(viewer,
|
||||
subgraphConsumer,
|
||||
listener,
|
||||
AttributedVertex::getId,
|
||||
AttributedVertex::getName);
|
||||
viewer.setGraphMouse(graphMouse);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,14 +545,21 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
*/
|
||||
private void connectSelectionStateListeners() {
|
||||
viewer.getSelectedVertexState().addItemListener(e -> Swing.runLater(() -> {
|
||||
// there was a change in the set of selected vertices.
|
||||
// if the locatedVertex is null, set it from one of the selected
|
||||
// vertices
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
Collection<AttributedVertex> selectedVertices = getVertices(e.getItem());
|
||||
List<String> selectedVertexIds = toVertexIds(selectedVertices);
|
||||
notifySelectionChanged(selectedVertexIds);
|
||||
|
||||
AttributedVertex vertex = CollectionUtils.any(selectedVertices);
|
||||
if (vertex != null) {
|
||||
notifyLocationChanged(vertex.getId());
|
||||
if (selectedVertices.size() == 1) {
|
||||
// if only one vertex was selected, make it the locatedVertex
|
||||
setLocatedVertex(selectedVertices.stream().findFirst().get());
|
||||
} else if (this.locatedVertex == null) {
|
||||
// if there is currently no locatedVertex, attempt to get
|
||||
// one from the selectedVertices
|
||||
setLocatedVertex(selectedVertices.stream().findFirst().orElse(null));
|
||||
}
|
||||
}
|
||||
else if (e.getStateChange() == ItemEvent.DESELECTED) {
|
||||
@ -358,11 +569,27 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
}));
|
||||
}
|
||||
|
||||
private List<String> toVertexIds(Collection<AttributedVertex> selectedVertices) {
|
||||
return selectedVertices.stream().map(v -> v.getId()).collect(Collectors.toList());
|
||||
/**
|
||||
* set the vertex that has been nominated to be 'located'
|
||||
* @param vertex the lucky vertex
|
||||
*/
|
||||
protected void setLocatedVertex(AttributedVertex vertex) {
|
||||
boolean changed = this.locatedVertex != vertex;
|
||||
this.locatedVertex = vertex;
|
||||
if (locatedVertex != null && changed) {
|
||||
notifyLocationChanged(locatedVertex.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transform the supplied {@code AttributedVertex} Set members to a List of their ids
|
||||
* @param selectedVertices
|
||||
* @return
|
||||
*/
|
||||
private List<String> toVertexIds(Collection<AttributedVertex> selectedVertices) {
|
||||
return selectedVertices.stream().map(AttributedVertex::getId).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<AttributedVertex> getVertices(Object item) {
|
||||
if (item instanceof Collection) {
|
||||
return (Collection<AttributedVertex>) item;
|
||||
@ -373,10 +600,18 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* fire an event to say the selected vertices changed
|
||||
* @param vertexIds
|
||||
*/
|
||||
private void notifySelectionChanged(List<String> vertexIds) {
|
||||
Swing.runLater(() -> listener.selectionChanged(vertexIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* fire and event to say the located vertex changed
|
||||
* @param vertexId
|
||||
*/
|
||||
private void notifyLocationChanged(String vertexId) {
|
||||
Swing.runLater(() -> listener.locationChanged(vertexId));
|
||||
}
|
||||
@ -400,6 +635,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param vertexIds vertex ids of interest
|
||||
* @return a {@code Set} containing the {@code AttributedVertex} for ths supplied ids
|
||||
*/
|
||||
private Set<AttributedVertex> getVertices(Collection<String> vertexIds) {
|
||||
Set<String> vertexSet = new HashSet<>(vertexIds);
|
||||
return graph.vertexSet()
|
||||
@ -408,13 +648,21 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* for the supplied vertex id, find the {@code AttributedVertex} and translate
|
||||
* the display to center it
|
||||
* @param vertexID the id of the vertex to focus
|
||||
*/
|
||||
@Override
|
||||
public void setLocation(String vertexID) {
|
||||
Optional<AttributedVertex> selected =
|
||||
Optional<AttributedVertex> located =
|
||||
graph.vertexSet().stream().filter(v -> vertexID.equals(v.getId())).findFirst();
|
||||
log.fine("picking address:" + vertexID + " returned " + selected);
|
||||
log.fine("picking address:" + vertexID + " returned " + located);
|
||||
viewer.repaint();
|
||||
selected.ifPresent(this::scrollToSelected);
|
||||
located.ifPresent(v -> {
|
||||
setLocatedVertex(v);
|
||||
scrollToSelected(v);
|
||||
});
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
@ -425,21 +673,19 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
private void doSetGraphData(AttributedGraph attributedGraph) {
|
||||
graph = attributedGraph;
|
||||
|
||||
layoutTransitionManager.setGraph(graph);
|
||||
layoutTransitionManager.setEdgeComparator(new EdgeComparator(graph, "EdgeType",
|
||||
DefaultGraphDisplay.FAVORED_EDGE));
|
||||
|
||||
configureViewerPreferredSize();
|
||||
|
||||
Swing.runNow(() -> viewer.getVisualizationModel().setGraph(graph));
|
||||
|
||||
Swing.runNow(() -> {
|
||||
// set the graph but defer the layoutalgorithm setting
|
||||
viewer.getVisualizationModel().setGraph(graph, false);
|
||||
configureFilters();
|
||||
|
||||
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
|
||||
layoutTransitionManager.getInitialLayoutAlgorithm(graph);
|
||||
|
||||
layoutTransitionManager.getInitialLayoutAlgorithm();
|
||||
viewer.getVisualizationModel().setLayoutAlgorithm(initialLayoutAlgorithm);
|
||||
|
||||
viewer.scaleToLayout();
|
||||
|
||||
});
|
||||
componentProvider.setVisible(true);
|
||||
}
|
||||
|
||||
@ -451,16 +697,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
*/
|
||||
private boolean isRoot(AttributedVertex vertex) {
|
||||
Set<AttributedEdge> incomingEdgesOf = graph.incomingEdgesOf(vertex);
|
||||
if (incomingEdgesOf.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
Set<AttributedEdge> outgoingEdgesOf = graph.outgoingEdgesOf(vertex);
|
||||
return outgoingEdgesOf.stream().anyMatch(this::isFavoredEdge) &&
|
||||
incomingEdgesOf.stream().noneMatch(this::isFavoredEdge);
|
||||
return incomingEdgesOf.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* configure filters for the graph, based on the vertex and edge attributes
|
||||
*/
|
||||
private void configureFilters() {
|
||||
// close and rebuild filter dialog if exists
|
||||
if (filterDialog != null) {
|
||||
@ -502,10 +744,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
e -> e.getAttributeMap().values().stream().noneMatch(selected::contains));
|
||||
viewer.repaint();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* configure a preferred size based on the size of the graph to display
|
||||
*/
|
||||
private void configureViewerPreferredSize() {
|
||||
int vertexCount = graph.vertexSet().size();
|
||||
// attempt to set a reasonable size for the layout based on the number of vertices
|
||||
@ -541,6 +784,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
// this would have to set the label function, the label font function
|
||||
}
|
||||
|
||||
/**
|
||||
* consume a {@link Graph} and display it
|
||||
* @param graph the graph to display or consume
|
||||
* @param description a description of the graph
|
||||
* @param append if true, append the new graph to any existing graph.
|
||||
* @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation
|
||||
*/
|
||||
@Override
|
||||
public void setGraph(AttributedGraph graph, String description, boolean append,
|
||||
TaskMonitor monitor) {
|
||||
@ -578,8 +828,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
return newGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* cause the graph to be centered and scaled nicely for the view window
|
||||
*/
|
||||
public void centerAndScale() {
|
||||
viewer.reset();
|
||||
viewer.scaleToLayout();
|
||||
}
|
||||
|
||||
@ -607,7 +859,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
.getMultiLayerTransformer()
|
||||
.inverseTransform(viewer.getCenter());
|
||||
|
||||
jobRunner.schedule(new CenterAnimation(viewer, existingCenter, newCenter));
|
||||
jobRunner.schedule(new CenterAnimation<>(viewer, existingCenter, newCenter));
|
||||
}
|
||||
|
||||
/**w
|
||||
@ -620,6 +872,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
scrollToSelected(vertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* compute the centroid of a group of vertices, or the center of the graph display
|
||||
* @param vertices a collection of vertices from which to compute the centroid from their locations
|
||||
* @return the {@code Point2D} that is the center
|
||||
*/
|
||||
private Point2D getPointToCenter(Collection<AttributedVertex> vertices) {
|
||||
LayoutModel<AttributedVertex> layoutModel = viewer.getVisualizationModel().getLayoutModel();
|
||||
|
||||
@ -638,57 +895,76 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
return new Point2D.Double(p.x, p.y);
|
||||
}
|
||||
|
||||
private GraphJobRunner jobRunner = new GraphJobRunner();
|
||||
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer;
|
||||
private AttributeFilters edgeFilters;
|
||||
private AttributeFilters vertexFilters;
|
||||
private FilterDialog filterDialog;
|
||||
private GhidraIconCache iconCache;
|
||||
|
||||
/**
|
||||
* process a request to update the name attribute value of the vertex with the
|
||||
* supplied id
|
||||
* @param id the vertix id
|
||||
* @param newName the new name of the vertex
|
||||
*/
|
||||
@Override
|
||||
public void updateVertexName(String id, String newName) {
|
||||
// unsupported
|
||||
// find the vertex, if present, change the name
|
||||
Optional<AttributedVertex> optional = graph.vertexSet().stream()
|
||||
.filter(v -> v.getId().equals(id)).findFirst();
|
||||
if (optional.isPresent()) {
|
||||
AttributedVertex vertex = optional.get();
|
||||
vertex.setName(newName);
|
||||
vertex.clearCache();
|
||||
iconCache.evict(vertex);
|
||||
viewer.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a description of this graph
|
||||
*/
|
||||
@Override
|
||||
public String getGraphDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
private boolean isFavoredEdge(AttributedEdge edge) {
|
||||
if (edge.getAttributeMap().containsKey("EdgeType")) {
|
||||
return edge.getAttributeMap().getOrDefault("EdgeType", "NOTEQUAL").equals(FAVORED_EDGE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* create and return a {@link VisualizationViewer} to display graphs
|
||||
* @return
|
||||
*/
|
||||
public VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
|
||||
final VisualizationViewer<AttributedVertex, AttributedEdge> vv =
|
||||
VisualizationViewer.<AttributedVertex, AttributedEdge> builder()
|
||||
.multiSelectionStrategySupplier(() -> freeFormSelection ?
|
||||
MultiSelectionStrategy.arbitrary() : MultiSelectionStrategy.rectangular())
|
||||
.viewSize(PREFERRED_VIEW_SIZE)
|
||||
.layoutSize(PREFERRED_LAYOUT_SIZE)
|
||||
.build();
|
||||
|
||||
// Add a component listener to scale and center the graph after the component
|
||||
// has been initially sized. Remove the listener after the first time so that any
|
||||
// subsequent resizing does not affect the graph.
|
||||
vv.getComponent().addComponentListener(new ComponentAdapter() {
|
||||
// Add an ancestor listener to scale and center the graph after the component
|
||||
// has been initially shown.
|
||||
vv.getComponent().addAncestorListener(new AncestorListener() {
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
vv.getComponent().removeComponentListener(this);
|
||||
public void ancestorAdded(AncestorEvent ancestorEvent) {
|
||||
vv.getComponent().removeAncestorListener(this);
|
||||
Swing.runLater(() -> {
|
||||
vv.reset();
|
||||
vv.scaleToLayout();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent ancestorEvent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent ancestorEvent) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.iconCache = new GhidraIconCache();
|
||||
vv.setVertexToolTipFunction(AttributedVertex::getHtmlString);
|
||||
vv.setEdgeToolTipFunction(AttributedEdge::getHtmlString);
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
|
||||
|
||||
iconCache = new GhidraIconCache();
|
||||
|
||||
// set up the shape and color functions
|
||||
IconShapeFunction<AttributedVertex> nodeImageShapeFunction =
|
||||
new IconShapeFunction<>(new EllipseShapeFunction<>());
|
||||
@ -700,6 +976,15 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
renderContext.setVertexShapeFunction(nodeImageShapeFunction);
|
||||
renderContext.setVertexIconFunction(iconCache::get);
|
||||
|
||||
vv.setInitialDimensionFunction(InitialDimensionFunction
|
||||
.builder(nodeImageShapeFunction.andThen(s -> RectangleUtils.convert(s.getBounds2D()))).build());
|
||||
|
||||
// the selectedEdgeState will be controlled by the vertices that are selected.
|
||||
// if both endpoints of an edge are selected, select that edge.
|
||||
vv.setSelectedEdgeState(
|
||||
new VertexEndpointsSelectedEdgeSelectedState<>(vv.getVisualizationModel()::getGraph,
|
||||
vv.getSelectedVertexState()));
|
||||
|
||||
// selected edges will be drawn with a wider stroke
|
||||
renderContext.setEdgeStrokeFunction(
|
||||
e -> renderContext.getSelectedEdgeState().isSelected(e) ? new BasicStroke(20.f)
|
||||
@ -731,14 +1016,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||
|
||||
renderContext.setEdgeShapeFunction(EdgeShape.line());
|
||||
|
||||
DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse = new DefaultGraphMouse<>();
|
||||
vv.setGraphMouse(graphMouse);
|
||||
vv.getComponent().requestFocus();
|
||||
vv.setBackground(Color.WHITE);
|
||||
return vv;
|
||||
}
|
||||
|
||||
/**
|
||||
* a way to sort attributed vertices or edges based on attribute values
|
||||
*/
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapt
|
||||
|
||||
static final String WINDOW_GROUP = "ProgramGraph";
|
||||
private static final String WINDOW_MENU_GROUP_NAME = "Graph";
|
||||
private DefaultGraphDisplay display;
|
||||
private final DefaultGraphDisplay display;
|
||||
|
||||
DefaultGraphDisplayComponentProvider(DefaultGraphDisplay display, PluginTool pluginTool) {
|
||||
super(pluginTool, "Graph", "DefaultGraphDisplay");
|
||||
|
@ -28,7 +28,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
||||
|
||||
private Set<DefaultGraphDisplay> displays = new HashSet<>();
|
||||
private final Set<DefaultGraphDisplay> displays = new HashSet<>();
|
||||
private PluginTool pluginTool;
|
||||
private Options options;
|
||||
private int displayCounter;
|
||||
|
@ -22,7 +22,7 @@ import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedGraph;
|
||||
|
||||
public class EdgeComparator implements Comparator<AttributedEdge> {
|
||||
private Set<AttributedEdge> prioritized;
|
||||
private final Set<AttributedEdge> prioritized;
|
||||
|
||||
public EdgeComparator(AttributedGraph graph, String attributeName, String value) {
|
||||
prioritized = graph.edgeSet()
|
||||
|
@ -41,7 +41,7 @@ public class FilterDialog extends DialogComponentProvider {
|
||||
/**
|
||||
* A {@code List} (possibly empty) of filter buttons for vertices
|
||||
*/
|
||||
private List<? extends AbstractButton> vertexButtons;
|
||||
private final List<? extends AbstractButton> vertexButtons;
|
||||
|
||||
/**
|
||||
* A {@code List} (possibly empty) of filter buttons for edges
|
||||
|
@ -0,0 +1,158 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
import org.jungrapht.visualization.control.AbstractPopupGraphMousePlugin;
|
||||
import org.jungrapht.visualization.control.DefaultGraphMouse;
|
||||
import org.jungrapht.visualization.control.GraphElementAccessor;
|
||||
import org.jungrapht.visualization.control.TransformSupport;
|
||||
import org.jungrapht.visualization.control.VertexSelectingGraphMousePlugin;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.selection.ShapePickSupport;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jungrapht.visualization.VisualizationServer.PREFIX;
|
||||
|
||||
/**
|
||||
* An extension of the jungrapht DefaultGraphMouse. This class has references to
|
||||
* <ul>
|
||||
* <li>a {@link VisualizationViewer} (to access the Graph and LayoutModel)
|
||||
* <li>a {@link Consumer} of the Subgraph (to make new Graph displays)
|
||||
* <li>a {@link GraphDisplayListener} (to react to changes in node attributes)
|
||||
* <li>a {@code Function} to supply the id for a given vertex
|
||||
* <li>a {@code Function} to supply the name for a given vertex
|
||||
*
|
||||
*/
|
||||
public class GhidraGraphMouse<V, E> extends DefaultGraphMouse<V, E> {
|
||||
|
||||
private static final String PICK_AREA_SIZE = PREFIX + "pickAreaSize";
|
||||
|
||||
/**
|
||||
* holds the context for graph visualization
|
||||
*/
|
||||
VisualizationViewer<V, E> viewer;
|
||||
/**
|
||||
* will accept a {@link Graph} and display it in a new tab or window
|
||||
*/
|
||||
Consumer<Graph<V, E>> subgraphConsumer;
|
||||
/**
|
||||
* a listener for events, notably the event to request change of a vertex name
|
||||
*/
|
||||
GraphDisplayListener graphDisplayListener;
|
||||
/**
|
||||
* supplies the id for a given vertex
|
||||
*/
|
||||
Function<V, String> vertexIdFunction;
|
||||
/**
|
||||
* supplies the name for a given vertex
|
||||
*/
|
||||
Function<V, String> vertexNameFunction;
|
||||
|
||||
/**
|
||||
* create an instance with default values
|
||||
*/
|
||||
public GhidraGraphMouse(VisualizationViewer<V, E> viewer,
|
||||
Consumer<Graph<V, E>> subgraphConsumer,
|
||||
GraphDisplayListener graphDisplayListener,
|
||||
Function<V, String> vertexIdFunction,
|
||||
Function<V, String> vertexNameFunction) {
|
||||
super(DefaultGraphMouse.<V, E>builder().vertexSelectionOnly(true));
|
||||
this.viewer = viewer;
|
||||
this.subgraphConsumer = subgraphConsumer;
|
||||
this.graphDisplayListener = graphDisplayListener;
|
||||
this.vertexIdFunction = vertexIdFunction;
|
||||
this.vertexNameFunction = vertexNameFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the plugins, and load them
|
||||
*/
|
||||
@Override
|
||||
public void loadPlugins() {
|
||||
add(new PopupPlugin<>(viewer, subgraphConsumer, graphDisplayListener,
|
||||
vertexIdFunction, vertexNameFunction));
|
||||
super.loadPlugins();
|
||||
}
|
||||
|
||||
static class PopupPlugin<V, E> extends AbstractPopupGraphMousePlugin {
|
||||
|
||||
VisualizationViewer<V, E> viewer;
|
||||
Consumer<Graph<V, E>> subgraphConsumer;
|
||||
GraphDisplayListener graphDisplayListener;
|
||||
Function<V, String> vertexIdFunction;
|
||||
Function<V, String> vertexNameFunction;
|
||||
SelectionFilterMenu<V, E> selectionFilterMenu;
|
||||
|
||||
PopupPlugin(VisualizationViewer<V, E> viewer,
|
||||
Consumer<Graph<V, E>> subgraphConsumer,
|
||||
GraphDisplayListener graphDisplayListener,
|
||||
Function<V, String> vertexIdFunction,
|
||||
Function<V, String> vertexNameFunction
|
||||
|
||||
) {
|
||||
this.viewer = viewer;
|
||||
this.subgraphConsumer = subgraphConsumer;
|
||||
this.graphDisplayListener = graphDisplayListener;
|
||||
this.vertexIdFunction = vertexIdFunction;
|
||||
this.vertexNameFunction = vertexNameFunction;
|
||||
this.selectionFilterMenu = new SelectionFilterMenu<>(viewer, subgraphConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlePopup(MouseEvent e) {
|
||||
int pickSize = Integer.getInteger(PICK_AREA_SIZE, 4);
|
||||
Rectangle2D footprintRectangle =
|
||||
new Rectangle2D.Float(
|
||||
(float) e.getPoint().x - pickSize / 2f,
|
||||
(float) e.getPoint().y - pickSize / 2f,
|
||||
pickSize,
|
||||
pickSize);
|
||||
|
||||
LayoutModel<V> layoutModel = viewer.getVisualizationModel().getLayoutModel();
|
||||
GraphElementAccessor<V, E> pickSupport = viewer.getPickSupport();
|
||||
V pickedVertex;
|
||||
if (pickSupport instanceof ShapePickSupport) {
|
||||
ShapePickSupport<V, E> shapePickSupport =
|
||||
(ShapePickSupport<V, E>) pickSupport;
|
||||
pickedVertex = shapePickSupport.getVertex(layoutModel, footprintRectangle);
|
||||
} else {
|
||||
TransformSupport<V, E> transformSupport = viewer.getTransformSupport();
|
||||
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
|
||||
pickedVertex = pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY());
|
||||
}
|
||||
if (pickedVertex != null) {
|
||||
OnVertexSelectionMenu<V, E> menu =
|
||||
new OnVertexSelectionMenu<>(viewer, graphDisplayListener,
|
||||
vertexIdFunction, vertexNameFunction,
|
||||
pickedVertex);
|
||||
menu.show(viewer.getComponent(), e.getX(), e.getY());
|
||||
} else {
|
||||
selectionFilterMenu.show(viewer.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,19 +30,18 @@ import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
public class GhidraIconCache {
|
||||
|
||||
private static final int DEFAULT_STROKE_THICKNESS = 8;
|
||||
private static final int DEFAULT_STROKE_THICKNESS = 12;
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
private static final String DEFAULT_FONT_NAME = "Dialog";
|
||||
private static final int DEFAULT_MARGIN_BORDER_SIZE = 4;
|
||||
private static final float LABEL_TO_ICON_PROPORTION_WAG = 1.4f;
|
||||
private static final double SQRT_2 = Math.sqrt(2.0);
|
||||
private JLabel rendererLabel = new JLabel();
|
||||
private Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
||||
private static final int DEFAULT_MARGIN_BORDER_SIZE = 8;
|
||||
private static final float LABEL_TO_ICON_PROPORTION = 1.1f;
|
||||
private final JLabel rendererLabel = new JLabel();
|
||||
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
||||
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
|
||||
|
||||
private Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
|
||||
private final Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
|
||||
|
||||
private IconShape.Function iconShapeFunction = new IconShape.Function();
|
||||
private final IconShape.Function iconShapeFunction = new IconShape.Function();
|
||||
|
||||
Icon get(AttributedVertex vertex) {
|
||||
|
||||
@ -64,7 +63,6 @@ public class GhidraIconCache {
|
||||
|
||||
private Icon createIcon(AttributedVertex vertex) {
|
||||
rendererLabel.setText(ProgramGraphFunctions.getLabel(vertex));
|
||||
|
||||
rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE));
|
||||
rendererLabel.setForeground(Color.black);
|
||||
rendererLabel.setBackground(Color.white);
|
||||
@ -102,15 +100,15 @@ public class GhidraIconCache {
|
||||
// triangles have a non-zero +/- yoffset instead of centering the label
|
||||
case TRIANGLE:
|
||||
// scale the vertex shape
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION_WAG;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION_WAG;
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
||||
break;
|
||||
case INVERTED_TRIANGLE:
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION_WAG;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION_WAG;
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
||||
@ -128,8 +126,8 @@ public class GhidraIconCache {
|
||||
case DIAMOND:
|
||||
default: // ELLIPSE
|
||||
scalex =
|
||||
labelSize.getWidth() / vertexShape.getBounds().getWidth() * SQRT_2;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 2;
|
||||
labelSize.getWidth() / vertexShape.getBounds().getWidth() * 1.1;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 1.1;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
break;
|
||||
@ -163,6 +161,21 @@ public class GhidraIconCache {
|
||||
graphics.setPaint(Color.black);
|
||||
graphics.setTransform(offsetTransform);
|
||||
label.paint(graphics);
|
||||
// draw the shape again, but lighter (on top of the label)
|
||||
offsetTransform =
|
||||
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
|
||||
strokeThickness + vertexBounds.height / 2.0);
|
||||
offsetTransform.preConcatenate(graphicsTransform);
|
||||
graphics.setTransform(offsetTransform);
|
||||
Paint paint = Colors.getColor(vertex);
|
||||
if (paint instanceof Color) {
|
||||
Color color = (Color)paint;
|
||||
Color transparent = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50);
|
||||
graphics.setPaint(transparent);
|
||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
||||
graphics.draw(vertexShape);
|
||||
}
|
||||
|
||||
graphics.setTransform(graphicsTransform); // restore the original transform
|
||||
graphics.dispose();
|
||||
return new ImageIcon(bufferedImage);
|
||||
@ -171,4 +184,13 @@ public class GhidraIconCache {
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* evict the passed vertex from the cache so that it will be recomputed
|
||||
* with presumably changed values
|
||||
* @param vertex to remove from the cache
|
||||
*/
|
||||
public void evict(AttributedVertex vertex) {
|
||||
map.remove(vertex);
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,21 @@
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jungrapht.visualization.layout.algorithms.*;
|
||||
import org.jungrapht.visualization.layout.algorithms.BalloonLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.CircleLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.EiglspergerLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.FRLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.GEMLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.KKLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.TidierRadialTreeLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.TidierTreeLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.TreeLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
|
||||
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
|
||||
|
||||
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A central location to list and provide all layout algorithms, their names, and their builders
|
||||
@ -32,68 +38,77 @@ import ghidra.service.graph.AttributedVertex;
|
||||
* This class provides LayoutAlgorithm builders instead of LayoutAlgorithms because some LayoutAlgorithms
|
||||
* accumulate state information (so are used only one time).
|
||||
*/
|
||||
class LayoutFunction
|
||||
implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
|
||||
class LayoutFunction<V, E>
|
||||
implements Function<String, LayoutAlgorithm.Builder<V, ?, ?>> {
|
||||
|
||||
static final String KAMADA_KAWAI = "Force Balanced";
|
||||
static final String FRUCTERMAN_REINGOLD = "Force Directed";
|
||||
static final String CIRCLE_MINCROSS = "Circle";
|
||||
static final String TIDIER_TREE = "Compact Hierarchical";
|
||||
static final String TIDIER_RADIAL_TREE = "Compact Radial";
|
||||
static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
|
||||
static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
|
||||
static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
|
||||
static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
|
||||
static final String MULTI_ROW_EDGE_AWARE_TREE = "Hierarchical MultiRow";
|
||||
static final String EDGE_AWARE_TREE = "Hierarchical";
|
||||
static final String EDGE_AWARE_RADIAL = "Radial";
|
||||
static final String TREE = "Hierarchical";
|
||||
static final String RADIAL = "Radial";
|
||||
static final String BALLOON = "Balloon";
|
||||
static final String GEM = "Gem (Graph Embedder)";
|
||||
|
||||
public String[] getNames() {
|
||||
return new String[] { EDGE_AWARE_TREE, MULTI_ROW_EDGE_AWARE_TREE, TIDIER_TREE,
|
||||
MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH, MIN_CROSS_NETWORK_SIMPLEX,
|
||||
MIN_CROSS_COFFMAN_GRAHAM, CIRCLE_MINCROSS, KAMADA_KAWAI, FRUCTERMAN_REINGOLD,
|
||||
EDGE_AWARE_RADIAL };
|
||||
return new String[] { TIDIER_TREE, TREE,
|
||||
TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH,
|
||||
MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE_MINCROSS,
|
||||
KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
|
||||
public LayoutAlgorithm.Builder<V, ?, ?> apply(String name) {
|
||||
switch(name) {
|
||||
case GEM:
|
||||
return GEMLayoutAlgorithm.edgeAwareBuilder();
|
||||
case KAMADA_KAWAI:
|
||||
return KKLayoutAlgorithm.<AttributedVertex> builder().preRelaxDuration(1000);
|
||||
return KKLayoutAlgorithm.<V> builder()
|
||||
.preRelaxDuration(1000);
|
||||
case FRUCTERMAN_REINGOLD:
|
||||
return FRLayoutAlgorithm.<AttributedVertex> builder()
|
||||
.repulsionContractBuilder(BarnesHutFRRepulsion.barnesHutBuilder());
|
||||
return FRLayoutAlgorithm.<V> builder()
|
||||
.repulsionContractBuilder(BarnesHutFRRepulsion.builder());
|
||||
case CIRCLE_MINCROSS:
|
||||
return CircleLayoutAlgorithm.<AttributedVertex> builder()
|
||||
return CircleLayoutAlgorithm.<V> builder()
|
||||
.reduceEdgeCrossing(true);
|
||||
case TIDIER_TREE:
|
||||
return TidierTreeLayoutAlgorithm.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
|
||||
case TIDIER_RADIAL_TREE:
|
||||
return TidierRadialTreeLayoutAlgorithm.<V, E> edgeAwareBuilder();
|
||||
case MIN_CROSS_TOP_DOWN:
|
||||
return HierarchicalMinCrossLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.layering(Layering.TOP_DOWN);
|
||||
case MIN_CROSS_LONGEST_PATH:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.layering(Layering.LONGEST_PATH);
|
||||
case MIN_CROSS_NETWORK_SIMPLEX:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.layering(Layering.NETWORK_SIMPLEX);
|
||||
case MIN_CROSS_COFFMAN_GRAHAM:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.layering(Layering.COFFMAN_GRAHAM);
|
||||
case MULTI_ROW_EDGE_AWARE_TREE:
|
||||
return MultiRowEdgeAwareTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
|
||||
case EDGE_AWARE_RADIAL:
|
||||
return RadialEdgeAwareTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
case RADIAL:
|
||||
return RadialTreeLayoutAlgorithm
|
||||
.<V> builder()
|
||||
.verticalVertexSpacing(300);
|
||||
case EDGE_AWARE_TREE:
|
||||
case BALLOON:
|
||||
return BalloonLayoutAlgorithm
|
||||
.<V> builder()
|
||||
.verticalVertexSpacing(300);
|
||||
case TREE:
|
||||
return TreeLayoutAlgorithm
|
||||
.builder();
|
||||
case TIDIER_TREE:
|
||||
default:
|
||||
return EdgeAwareTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
|
||||
return TidierTreeLayoutAlgorithm.<V, E> edgeAwareBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,101 +15,80 @@
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import static ghidra.graph.visualization.LayoutFunction.*;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jgrapht.Graph;
|
||||
import org.jungrapht.visualization.RenderContext;
|
||||
import org.jungrapht.visualization.VisualizationServer;
|
||||
import org.jungrapht.visualization.layout.algorithms.*;
|
||||
import org.jungrapht.visualization.layout.algorithms.util.*;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.util.Context;
|
||||
import org.jungrapht.visualization.layout.algorithms.Balloon;
|
||||
import org.jungrapht.visualization.layout.algorithms.BalloonLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.EdgeSorting;
|
||||
import org.jungrapht.visualization.layout.algorithms.Layered;
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayout;
|
||||
import org.jungrapht.visualization.layout.algorithms.RadialTreeLayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.TreeLayout;
|
||||
import org.jungrapht.visualization.layout.algorithms.util.VertexBoundsFunctionConsumer;
|
||||
import org.jungrapht.visualization.layout.model.Rectangle;
|
||||
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
|
||||
import org.jungrapht.visualization.util.LayoutPaintable;
|
||||
|
||||
import docking.menu.MultiActionDockingAction;
|
||||
import ghidra.service.graph.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static ghidra.graph.visualization.LayoutFunction.TIDIER_TREE;
|
||||
|
||||
/**
|
||||
* Manages the selection and transition from one {@link LayoutAlgorithm} to another
|
||||
*/
|
||||
class LayoutTransitionManager {
|
||||
class LayoutTransitionManager<V, E> {
|
||||
|
||||
LayoutFunction layoutFunction = new LayoutFunction();
|
||||
/**
|
||||
* the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm}
|
||||
*/
|
||||
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer;
|
||||
VisualizationServer<V, E> visualizationServer;
|
||||
|
||||
/**
|
||||
* a {@link Predicate} to assist in determining which vertices are root vertices (for Tree layouts)
|
||||
*/
|
||||
Predicate<AttributedVertex> rootPredicate;
|
||||
|
||||
/**
|
||||
* a {@link Predicate} to allow different handling of specific edge types
|
||||
*/
|
||||
Predicate<AttributedEdge> edgePredicate;
|
||||
Predicate<V> rootPredicate;
|
||||
|
||||
/**
|
||||
* a {@link Comparator} to sort edges during layout graph traversal
|
||||
*/
|
||||
Comparator<AttributedEdge> edgeComparator = (e1, e2) -> 0;
|
||||
Comparator<E> edgeComparator = (e1, e2) -> 0;
|
||||
|
||||
/**
|
||||
* a {@link MultiActionDockingAction} to allow the user to select a layout algorithm
|
||||
* a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices
|
||||
*/
|
||||
MultiActionDockingAction multiActionDockingAction;
|
||||
|
||||
/**
|
||||
* the currently active {@code LayoutAlgorithm.Builder}
|
||||
*/
|
||||
LayoutAlgorithm.Builder<AttributedVertex, ?, ?> activeBuilder;
|
||||
|
||||
/**
|
||||
* a {@link Function} to provide {@link Shape} (and thus bounds} for vertices
|
||||
*/
|
||||
Function<AttributedVertex, Shape> vertexShapeFunction;
|
||||
Function<V, Rectangle> vertexBoundsFunction;
|
||||
|
||||
/**
|
||||
* the {@link RenderContext} used to draw the graph
|
||||
*/
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderContext;
|
||||
RenderContext<V, E> renderContext;
|
||||
|
||||
/**
|
||||
* a LayoutAlgorithm may change the edge shape function (Sugiyama for articulated edges)
|
||||
* This is a reference to the original edge shape function so that it can be returned to
|
||||
* the original edge shape function for subsequent LayoutAlgorithm requests
|
||||
*/
|
||||
private Function<Context<Graph<AttributedVertex, AttributedEdge>, AttributedEdge>, Shape> originalEdgeShapeFunction;
|
||||
LayoutPaintable.BalloonRings<V, E> balloonLayoutRings;
|
||||
|
||||
LayoutPaintable.RadialRings<V> radialLayoutRings;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance with passed parameters
|
||||
* @param visualizationServer displays the graph
|
||||
* @param rootPredicate selects root vertices
|
||||
* @param edgePredicate differentiates edges
|
||||
*/
|
||||
public LayoutTransitionManager(
|
||||
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
|
||||
Predicate<AttributedVertex> rootPredicate, Predicate<AttributedEdge> edgePredicate) {
|
||||
VisualizationServer<V, E> visualizationServer,
|
||||
Predicate<V> rootPredicate) {
|
||||
this.visualizationServer = visualizationServer;
|
||||
this.rootPredicate = rootPredicate;
|
||||
this.edgePredicate = edgePredicate;
|
||||
|
||||
this.renderContext = visualizationServer.getRenderContext();
|
||||
this.vertexShapeFunction = visualizationServer.getRenderContext().getVertexShapeFunction();
|
||||
this.originalEdgeShapeFunction =
|
||||
visualizationServer.getRenderContext().getEdgeShapeFunction();
|
||||
|
||||
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
|
||||
}
|
||||
|
||||
public void setGraph(AttributedGraph graph) {
|
||||
edgeComparator = new EdgeComparator(graph, "EdgeType", DefaultGraphDisplay.FAVORED_EDGE);
|
||||
public void setEdgeComparator(Comparator<E> edgeComparator) {
|
||||
this.edgeComparator = edgeComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,93 +97,71 @@ class LayoutTransitionManager {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setLayout(String layoutName) {
|
||||
LayoutAlgorithm.Builder<AttributedVertex, ?, ?> builder = layoutFunction.apply(layoutName);
|
||||
visualizationServer.getRenderContext().getMultiLayerTransformer().setToIdentity();
|
||||
LayoutAlgorithm<AttributedVertex> layoutAlgorithm = builder.build();
|
||||
|
||||
if (layoutAlgorithm instanceof RenderContextAware) {
|
||||
((RenderContextAware<AttributedVertex, AttributedEdge>) layoutAlgorithm)
|
||||
.setRenderContext(visualizationServer.getRenderContext());
|
||||
LayoutAlgorithm.Builder<V, ?, ?> builder = layoutFunction.apply(layoutName);
|
||||
LayoutAlgorithm<V> layoutAlgorithm = builder.build();
|
||||
if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
|
||||
((VertexBoundsFunctionConsumer<V>) layoutAlgorithm)
|
||||
.setVertexBoundsFunction(vertexBoundsFunction);
|
||||
}
|
||||
else {
|
||||
visualizationServer.getRenderContext().setEdgeShapeFunction(originalEdgeShapeFunction);
|
||||
}
|
||||
if (layoutAlgorithm instanceof VertexShapeAware) {
|
||||
((VertexShapeAware<AttributedVertex>) layoutAlgorithm)
|
||||
.setVertexShapeFunction(vertexShapeFunction);
|
||||
if (layoutAlgorithm instanceof Layered) {
|
||||
((Layered<V, E>)layoutAlgorithm)
|
||||
.setMaxLevelCrossFunction(g ->
|
||||
Math.max(1, Math.min(10, 500 / g.vertexSet().size())));
|
||||
}
|
||||
if (layoutAlgorithm instanceof TreeLayout) {
|
||||
((TreeLayout<AttributedVertex>) layoutAlgorithm).setRootPredicate(rootPredicate);
|
||||
((TreeLayout<V>) layoutAlgorithm).setRootPredicate(rootPredicate);
|
||||
}
|
||||
// remove any previously added layout paintables
|
||||
removePaintable(radialLayoutRings);
|
||||
removePaintable(balloonLayoutRings);
|
||||
if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) {
|
||||
balloonLayoutRings =
|
||||
new LayoutPaintable.BalloonRings<>(
|
||||
visualizationServer, (BalloonLayoutAlgorithm<V>) layoutAlgorithm);
|
||||
visualizationServer.addPreRenderPaintable(balloonLayoutRings);
|
||||
}
|
||||
if (layoutAlgorithm instanceof RadialTreeLayout) {
|
||||
radialLayoutRings =
|
||||
new LayoutPaintable.RadialRings<>(
|
||||
visualizationServer, (RadialTreeLayout<V>) layoutAlgorithm);
|
||||
visualizationServer.addPreRenderPaintable(radialLayoutRings);
|
||||
}
|
||||
|
||||
if (layoutAlgorithm instanceof EdgeSorting) {
|
||||
((EdgeSorting<AttributedEdge>) layoutAlgorithm).setEdgeComparator(edgeComparator);
|
||||
((EdgeSorting<E>) layoutAlgorithm).setEdgeComparator(edgeComparator);
|
||||
}
|
||||
if (layoutAlgorithm instanceof EdgePredicated) {
|
||||
((EdgePredicated<AttributedEdge>) layoutAlgorithm).setEdgePredicate(edgePredicate);
|
||||
LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm);
|
||||
}
|
||||
if (!(layoutAlgorithm instanceof TreeLayout)) {
|
||||
LayoutModel<AttributedVertex> layoutModel =
|
||||
visualizationServer.getVisualizationModel().getLayoutModel();
|
||||
int preferredWidth = layoutModel.getPreferredWidth();
|
||||
int preferredHeight = layoutModel.getPreferredHeight();
|
||||
layoutModel.setSize(preferredWidth, preferredHeight);
|
||||
|
||||
private void removePaintable(VisualizationServer.Paintable paintable) {
|
||||
if (paintable != null) {
|
||||
visualizationServer.removePreRenderPaintable(paintable);
|
||||
}
|
||||
if (layoutAlgorithm instanceof RenderContextAware) {
|
||||
((RenderContextAware<AttributedVertex, AttributedEdge>) layoutAlgorithm)
|
||||
.setRenderContext(renderContext);
|
||||
}
|
||||
if (layoutAlgorithm instanceof AfterRunnable) {
|
||||
((AfterRunnable) layoutAlgorithm).setAfter(visualizationServer::scaleToLayout);
|
||||
}
|
||||
LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm,
|
||||
visualizationServer::scaleToLayout);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm(
|
||||
AttributedGraph graph) {
|
||||
Set<AttributedVertex> roots = getRoots(graph);
|
||||
|
||||
// if there are no roots, don't attempt to create a Tree layout
|
||||
if (roots.size() == 0) {
|
||||
return layoutFunction.apply(FRUCTERMAN_REINGOLD).build();
|
||||
}
|
||||
|
||||
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
|
||||
layoutFunction.apply(EDGE_AWARE_TREE).build();
|
||||
public LayoutAlgorithm<V> getInitialLayoutAlgorithm() {
|
||||
LayoutAlgorithm<V> initialLayoutAlgorithm =
|
||||
layoutFunction.apply(TIDIER_TREE).build();
|
||||
|
||||
if (initialLayoutAlgorithm instanceof TreeLayout) {
|
||||
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
|
||||
((TreeLayout<V>) initialLayoutAlgorithm)
|
||||
.setRootPredicate(rootPredicate);
|
||||
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
|
||||
.setVertexShapeFunction(vertexShapeFunction);
|
||||
((TreeLayout<V>) initialLayoutAlgorithm)
|
||||
.setVertexBoundsFunction(vertexBoundsFunction);
|
||||
}
|
||||
if (initialLayoutAlgorithm instanceof EdgeSorting) {
|
||||
((EdgeSorting<AttributedEdge>) initialLayoutAlgorithm)
|
||||
((EdgeSorting<E>) initialLayoutAlgorithm)
|
||||
.setEdgeComparator(edgeComparator);
|
||||
}
|
||||
if (initialLayoutAlgorithm instanceof EdgePredicated) {
|
||||
((EdgePredicated<AttributedEdge>) initialLayoutAlgorithm)
|
||||
.setEdgePredicate(edgePredicate);
|
||||
}
|
||||
if (initialLayoutAlgorithm instanceof ShapeFunctionAware) {
|
||||
((ShapeFunctionAware<AttributedVertex>) initialLayoutAlgorithm)
|
||||
.setVertexShapeFunction(vertexShapeFunction);
|
||||
if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) {
|
||||
((VertexBoundsFunctionConsumer<V>) initialLayoutAlgorithm)
|
||||
.setVertexBoundsFunction(vertexBoundsFunction);
|
||||
}
|
||||
return initialLayoutAlgorithm;
|
||||
}
|
||||
|
||||
private Set<AttributedVertex> getRoots(AttributedGraph graph) {
|
||||
return graph.edgeSet()
|
||||
.stream()
|
||||
.sorted(edgeComparator)
|
||||
.map(graph::getEdgeSource)
|
||||
.filter(rootPredicate)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
public String[] getLayoutNames() {
|
||||
return layoutFunction.getNames();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
/**
|
||||
* Extends DialogComponentProvider to make a dialog with buttons to show that the
|
||||
* layout arrangement algorithm is busy
|
||||
*/
|
||||
public class LayoutWorkingDialog extends DialogComponentProvider {
|
||||
|
||||
public LayoutWorkingDialog(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
|
||||
super("Working....", false);
|
||||
super.addWorkPanel(createPanel(layoutAlgorithm));
|
||||
setRememberSize(false);
|
||||
addDismissButton();
|
||||
setDefaultButton(dismissButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a layout-formatted JComponent holding 2 vertical lists
|
||||
* of buttons, one list for vertex filter buttons and one list for
|
||||
* edge filter buttons. Each list has a border and title.
|
||||
* @return a formatted JComponent (container)
|
||||
*/
|
||||
JComponent createPanel(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
|
||||
JProgressBar progressBar = new JProgressBar();
|
||||
progressBar.setIndeterminate(true);
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(progressBar, BorderLayout.CENTER);
|
||||
panel.add(new JLabel("Please wait......."), BorderLayout.NORTH);
|
||||
addCancelButton();
|
||||
cancelButton.addActionListener(evt -> layoutAlgorithm.cancel());
|
||||
return panel;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPopupMenu;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* a Popup menu to allow actions relative to a particular vertex.
|
||||
* The popup appears on a right click over a vertex in the display.
|
||||
* The user can:
|
||||
* <ul>
|
||||
* <li>select/deselect the vertex
|
||||
* <li>rename the selected vertex (may modify the value in the listing)
|
||||
* <li>re-label the selected vertex locally (affects only the local visual display)
|
||||
*/
|
||||
public class OnVertexSelectionMenu<V, E> extends JPopupMenu {
|
||||
|
||||
public OnVertexSelectionMenu(VisualizationViewer<V, E> visualizationViewer,
|
||||
GraphDisplayListener graphDisplayListener,
|
||||
Function<V, String> vertexIdFunction,
|
||||
Function<V, String> vertexNameFunction,
|
||||
V vertex) {
|
||||
AbstractButton selectButton = new JMenuItem("Select");
|
||||
AbstractButton deselectButton = new JMenuItem("Deselect");
|
||||
AbstractButton renameAttributeButton = new JMenuItem("Rename vertex");
|
||||
renameAttributeButton.addActionListener(evt -> {
|
||||
String newName = JOptionPane.showInputDialog("New Name Attribute");
|
||||
if (!StringUtils.isEmpty(newName)) {
|
||||
graphDisplayListener.updateVertexName(vertexIdFunction.apply(vertex),
|
||||
vertexNameFunction.apply(vertex), newName);
|
||||
}
|
||||
}
|
||||
);
|
||||
selectButton.addActionListener(evt -> {
|
||||
if (vertex != null) {
|
||||
visualizationViewer.getSelectedVertexState().select(vertex);
|
||||
}
|
||||
});
|
||||
deselectButton.addActionListener(evt -> {
|
||||
if (vertex != null) {
|
||||
visualizationViewer.getSelectedVertexState().deselect(vertex);
|
||||
}
|
||||
});
|
||||
add(visualizationViewer.getSelectedVertexState().isSelected(vertex) ? deselectButton : selectButton);
|
||||
add(renameAttributeButton);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPopupMenu;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* a Popup menu to allow actions relative to a particular vertex.
|
||||
* The popup appears on a right click over a vertex in the display.
|
||||
* The user can:
|
||||
* <ul>
|
||||
* <li>select/deselect the vertex
|
||||
* <li>rename the selected vertex (may modify the value in the listing)
|
||||
* <li>re-label the selected vertex locally (affects only the local visual display)
|
||||
*/
|
||||
public class OnVertexSelectionMenu<V, E> extends JPopupMenu {
|
||||
|
||||
public OnVertexSelectionMenu(VisualizationViewer<V, E> visualizationViewer,
|
||||
GraphDisplayListener graphDisplayListener,
|
||||
Function<V, String> vertexIdFunction,
|
||||
Function<V, String> vertexNameFunction,
|
||||
V vertex) {
|
||||
AbstractButton selectButton = new JMenuItem("Select");
|
||||
AbstractButton deselectButton = new JMenuItem("Deselect");
|
||||
AbstractButton renameAttributeButton = new JMenuItem("Rename vertex");
|
||||
renameAttributeButton.addActionListener(evt -> {
|
||||
String newName = JOptionPane.showInputDialog("New Name Attribute");
|
||||
if (!StringUtils.isEmpty(newName)) {
|
||||
graphDisplayListener.updateVertexName(vertexIdFunction.apply(vertex),
|
||||
vertexNameFunction.apply(vertex), newName);
|
||||
}
|
||||
}
|
||||
);
|
||||
selectButton.addActionListener(evt -> {
|
||||
if (vertex != null) {
|
||||
visualizationViewer.getSelectedVertexState().select(vertex);
|
||||
}
|
||||
});
|
||||
deselectButton.addActionListener(evt -> {
|
||||
if (vertex != null) {
|
||||
visualizationViewer.getSelectedVertexState().deselect(vertex);
|
||||
}
|
||||
});
|
||||
add(visualizationViewer.getSelectedVertexState().isSelected(vertex) ? deselectButton : selectButton);
|
||||
add(renameAttributeButton);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package ghidra.graph.visualization;
|
||||
import com.google.common.base.Splitter;
|
||||
import ghidra.service.graph.Attributed;
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jungrapht.visualization.util.ShapeFactory;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
@ -129,7 +130,7 @@ abstract class ProgramGraphFunctions {
|
||||
public static String getLabel(Attributed attributed) {
|
||||
Map<String, String> map = attributed.getAttributeMap();
|
||||
if (map.get("Code") != null) {
|
||||
String code = map.get("Code");
|
||||
String code = StringEscapeUtils.escapeHtml4(map.get("Code"));
|
||||
return "<html>" + String.join("<p>", Splitter.on('\n').split(code));
|
||||
}
|
||||
return map.get("Name");
|
||||
|
@ -0,0 +1,228 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.graph.AsSubgraph;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
import org.jungrapht.visualization.selection.MutableSelectedState;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* {@code PopupMenu to offer vertex selection options. The user can
|
||||
* <ul>
|
||||
* <li>hide vertices that are not selected.
|
||||
* <li>hide vertices that are selected.
|
||||
* <li>invert the selection.
|
||||
* <li>'grow' the selected vertex set outwards following outgoing edges.
|
||||
* <li>'grow' the selected vertex set inwards following incoming edges.
|
||||
* <li>Create a new graph display consisting of the SubGraph of the selected vertices and their edges.
|
||||
*/
|
||||
public class SelectionFilterMenu<V, E> extends JPopupMenu {
|
||||
|
||||
/**
|
||||
* holds the context of graph visualization
|
||||
*/
|
||||
private final VisualizationViewer<V, E> visualizationViewer;
|
||||
|
||||
/**
|
||||
* button to extend the selection outwards along outgoing edges
|
||||
* its a class member so that it can be re-enabled any time the popup is shown
|
||||
*/
|
||||
AbstractButton growSelectionOutButton;
|
||||
|
||||
/**
|
||||
* button to extend the selection inwards along incoming edges
|
||||
* its a class member so that it can be re-enabled any time the popup is shown
|
||||
*/
|
||||
AbstractButton growSelectionInButton;
|
||||
|
||||
/**
|
||||
* Create the popup menu and populate with buttons to:
|
||||
* <ul>
|
||||
* <li>hide unselected vertices</li>
|
||||
* <li>hide selected vertices</li>
|
||||
* <li>Invert the selection</li>
|
||||
* <li>Grow the selection outwards following outgoing edges</li>
|
||||
* <li>Grow the selection inwards following incoming edges</li>
|
||||
* <li>Display the selected vertices only in a new Graph display</li>
|
||||
* </ul>
|
||||
* @param visualizationViewer the {@link VisualizationViewer} that holds the context for graph visualization
|
||||
* @param subgraphConsumer a {@code Consumer} of a {@link Graph} to display in a new tab or window
|
||||
*/
|
||||
public SelectionFilterMenu(VisualizationViewer<V, E> visualizationViewer,
|
||||
Consumer<Graph<V, E>> subgraphConsumer) {
|
||||
this.visualizationViewer = visualizationViewer;
|
||||
MutableSelectedState<V> selectedVertexState = visualizationViewer.getSelectedVertexState();
|
||||
AbstractButton hideUnselectedToggleButton = new JCheckBox("Hide Unselected");
|
||||
AbstractButton hideSelectedToggleButton = new JCheckBox("Hide Selected");
|
||||
hideUnselectedToggleButton.addItemListener(evt ->
|
||||
manageVertexDisplay(hideSelectedToggleButton.isSelected(), hideUnselectedToggleButton.isSelected()));
|
||||
hideSelectedToggleButton.addItemListener(evt ->
|
||||
manageVertexDisplay(hideSelectedToggleButton.isSelected(), hideUnselectedToggleButton.isSelected()));
|
||||
AbstractButton toggleSelectionButton = new JCheckBox("Invert Selection");
|
||||
toggleSelectionButton.addActionListener(evt -> {
|
||||
Graph<V, E> graph = visualizationViewer.getVisualizationModel().getGraph();
|
||||
graph.vertexSet().forEach(v -> {
|
||||
if (selectedVertexState.isSelected(v)) {
|
||||
selectedVertexState.deselect(v);
|
||||
} else {
|
||||
selectedVertexState.select(v);
|
||||
}
|
||||
}
|
||||
);
|
||||
visualizationViewer.repaint();
|
||||
});
|
||||
this.growSelectionOutButton = new JButton("Grow Selection Outwards");
|
||||
this.growSelectionInButton = new JButton("Grow Selection Inwards");
|
||||
|
||||
this.growSelectionOutButton.addActionListener(evt -> {
|
||||
growSelection(Graph::outgoingEdgesOf, Graph::getEdgeTarget);
|
||||
growSelectionInButton.setEnabled(canGrowSelection(Graph::incomingEdgesOf));
|
||||
growSelectionOutButton.setEnabled(canGrowSelection(Graph::outgoingEdgesOf));
|
||||
});
|
||||
|
||||
this.growSelectionInButton.addActionListener(evt -> {
|
||||
growSelection(Graph::incomingEdgesOf, Graph::getEdgeSource);
|
||||
growSelectionInButton.setEnabled(canGrowSelection(Graph::incomingEdgesOf));
|
||||
growSelectionOutButton.setEnabled(canGrowSelection(Graph::outgoingEdgesOf));
|
||||
});
|
||||
|
||||
JMenuItem subgraphDisplay = new JMenuItem("Display Selected As Graph");
|
||||
subgraphDisplay.addActionListener(evt -> {
|
||||
Graph<V, E> graph = visualizationViewer.getVisualizationModel().getGraph();
|
||||
subgraphConsumer.accept(new AsSubgraph<>(graph, selectedVertexState.getSelected()));
|
||||
});
|
||||
|
||||
add(hideSelectedToggleButton);
|
||||
add(hideUnselectedToggleButton);
|
||||
add(toggleSelectionButton);
|
||||
add(growSelectionInButton);
|
||||
add(growSelectionOutButton);
|
||||
add(subgraphDisplay);
|
||||
}
|
||||
|
||||
private boolean canGrowSelection(BiFunction<Graph<V, E>, V, Set<E>> growthFunction) {
|
||||
Graph<V, E> graph = visualizationViewer.getVisualizationModel().getGraph();
|
||||
Set<V> selectedVertices = visualizationViewer.getSelectedVertexState().getSelected();
|
||||
Set<E> selectedEdges = visualizationViewer.getSelectedEdgeState().getSelected();
|
||||
|
||||
return selectedVertices.stream()
|
||||
.map(v -> growthFunction.apply(graph, v))
|
||||
.anyMatch(adjacentEdges -> !selectedEdges.containsAll(adjacentEdges));
|
||||
}
|
||||
|
||||
/**
|
||||
* re-enaable the grow buttons in case the user cleared the selection in the graph display
|
||||
* @param invoker
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
@Override
|
||||
public void show(Component invoker, int x, int y) {
|
||||
this.growSelectionInButton.setEnabled(true);
|
||||
this.growSelectionOutButton.setEnabled(true);
|
||||
super.show(invoker, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the supplied boolean flags to determine what vertices are shown:
|
||||
* <ul>
|
||||
* <li>unselected vertices only</li>
|
||||
* <li>selected vertices only</li>
|
||||
* <li>both selected and unselected vertices are shown</li>
|
||||
* <li>neither selected nor unselected vertices are shown</li>
|
||||
* </ul>
|
||||
* @param hideSelected a {@code boolean} flag to request hiding of selected vertices
|
||||
* @param hideUnselected a {@code boolean} flag to request hiding of unselected vertices
|
||||
*/
|
||||
private void manageVertexDisplay(boolean hideSelected, boolean hideUnselected) {
|
||||
MutableSelectedState<V> selectedVertexState = visualizationViewer.getSelectedVertexState();
|
||||
if (hideSelected && hideUnselected) {
|
||||
visualizationViewer.getRenderContext()
|
||||
.setVertexIncludePredicate(v -> false);
|
||||
} else if (hideSelected) {
|
||||
visualizationViewer.getRenderContext()
|
||||
.setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected));
|
||||
} else if (hideUnselected) {
|
||||
visualizationViewer.getRenderContext()
|
||||
.setVertexIncludePredicate(selectedVertexState::isSelected);
|
||||
} else {
|
||||
visualizationViewer.getRenderContext()
|
||||
.setVertexIncludePredicate(v -> true);
|
||||
}
|
||||
visualizationViewer.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* select all vertices that are one hop away from any currently selected vertices
|
||||
* @param growthFunction either outgoing or incoming edges
|
||||
* @param neighborFunction either target or source vertices
|
||||
* @return true if the selection has changed
|
||||
*/
|
||||
private boolean growSelection(BiFunction<Graph<V, E>, V, Set<E>> growthFunction,
|
||||
BiFunction<Graph<V, E>, E, V> neighborFunction) {
|
||||
MutableSelectedState<V> selectedVertexState = visualizationViewer.getSelectedVertexState();
|
||||
|
||||
Set<V> selectedVertices = new HashSet<>(selectedVertexState.getSelected());
|
||||
selectedVertexState.getSelected()
|
||||
.forEach(v -> selectedVertices.addAll(growSelection(v, growthFunction, neighborFunction)));
|
||||
return selectedVertexState.select(selectedVertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* select all vertices that are one hop away from the supplied vertex. The outgoing ar
|
||||
* incoming edges are not followed if they are already selected, or if they are hidden
|
||||
* by the edgeIncludePredicate. Likewise
|
||||
* @param vertex the vertex to start the selection from
|
||||
* @param growthFunction either outgoing or incoming edges
|
||||
* @param neighborFunction either target or source vertices
|
||||
* @return a collection of selected vertices
|
||||
*/
|
||||
private Collection<V> growSelection(V vertex,
|
||||
BiFunction<Graph<V, E>, V, Set<E>> growthFunction,
|
||||
BiFunction<Graph<V, E>, E, V> neighborFunction) {
|
||||
Graph<V, E> graph = visualizationViewer.getVisualizationModel().getGraph();
|
||||
MutableSelectedState<V> selectedVertexState = visualizationViewer.getSelectedVertexState();
|
||||
MutableSelectedState<E> selectedEdgeState = visualizationViewer.getSelectedEdgeState();
|
||||
Predicate<E> edgeIncludePredicate = visualizationViewer.getRenderContext().getEdgeIncludePredicate();
|
||||
Predicate<V> vertexIncludePredicate = visualizationViewer.getRenderContext().getVertexIncludePredicate();
|
||||
// filter out edges we have already selected
|
||||
Set<E> connectingEdges = growthFunction.apply(graph, vertex)
|
||||
.stream().filter(e -> !selectedEdgeState.isSelected(e))
|
||||
.filter(edgeIncludePredicate)
|
||||
.collect(Collectors.toSet());
|
||||
visualizationViewer.getSelectedEdgeState().select(connectingEdges);
|
||||
// get the opposite endpoints for each edge and select them, if they are not already selected
|
||||
return connectingEdges.stream().map(e -> neighborFunction.apply(graph, e))
|
||||
.filter(v -> !selectedVertexState.isSelected(v))
|
||||
.filter(vertexIncludePredicate)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 364 B |
@ -10,10 +10,11 @@ jungrapht.modalRendererTimerSleep=30
|
||||
|
||||
# whether the satellite view is drawn with a transparent background
|
||||
jungrapht.satelliteBackgroundTransparent=false
|
||||
jungrapht.satelliteLensColor= 0xFAFAFA
|
||||
|
||||
# default spacing for tree layouts
|
||||
jungrapht.treeLayoutHorizontalSpacing=400
|
||||
jungrapht.treeLayoutVerticalSpacing=300
|
||||
jungrapht.treeLayoutHorizontalSpacing=2
|
||||
jungrapht.treeLayoutVerticalSpacing=2
|
||||
|
||||
# default area of pick footprint (item is picked when footprint intersects item shape)
|
||||
jungrapht.pickAreaSize=20
|
||||
@ -26,14 +27,14 @@ jungrapht.edgeWidth=4.0f
|
||||
|
||||
# stroke size for the magnifier lens
|
||||
jungrapht.lensStrokeWidth=10.0
|
||||
# when scale is < .3, switch to lightweight rendering
|
||||
jungrapht.lightweightScaleThreshold=.8
|
||||
# when scale is < .1, switch to lightweight rendering
|
||||
jungrapht.lightweightScaleThreshold=.1
|
||||
# under 50 vertices will use heavyweight rendering all the time
|
||||
jungrapht.lightweightCountThreshold=50
|
||||
|
||||
# default pixels spacings for vertices
|
||||
jungrapht.mincross.horizontalOffset=100
|
||||
jungrapht.mincross.verticalOffset=50
|
||||
jungrapht.mincross.horizontalOffset=10
|
||||
jungrapht.mincross.verticalOffset=5
|
||||
|
||||
# how many times to run the full all-level cross count
|
||||
jungrapht.mincross.maxLevelCross=10
|
||||
@ -50,4 +51,7 @@ jungrapht.mincross.eiglspergerThreshold=200
|
||||
# over 200 edges and the reduce edge crossing algorithm will not run
|
||||
jungrapht.circle.reduceEdgeCrossingMaxEdges=200
|
||||
|
||||
|
||||
# density of a graph in layout. (non-zero) values are 0 < density <= 1.0
|
||||
# for layout algorithms that attempt to make the area optimal for the number
|
||||
# of graph vertices, this sets how dense the space is.
|
||||
jungrapht.initialDimensionVertexDensity=0.3f
|
||||
|
@ -35,6 +35,7 @@ public class MultiStateActionBuilder<T> extends
|
||||
|
||||
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
|
||||
private boolean performActionOnButtonClick = false;
|
||||
private boolean fireFirstAction = true;
|
||||
|
||||
private List<ActionState<T>> states = new ArrayList<>();
|
||||
|
||||
@ -114,6 +115,17 @@ public class MultiStateActionBuilder<T> extends
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* controls whether the first action added will automatically fire an event or not
|
||||
*
|
||||
* @param fireFirstAction do fire an action on the first action. Defaults to {@code true}
|
||||
* @return this MultiActionDockingActionBuilder (for chaining)
|
||||
*/
|
||||
public MultiStateActionBuilder<T> fireFirstAction(boolean fireFirstAction) {
|
||||
this.fireFirstAction = fireFirstAction;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiStateDockingAction<T> build() {
|
||||
validate();
|
||||
@ -133,6 +145,7 @@ public class MultiStateActionBuilder<T> extends
|
||||
}
|
||||
}
|
||||
};
|
||||
action.setFireFirstEvent(fireFirstAction);
|
||||
|
||||
for (ActionState<T> actionState : states) {
|
||||
action.addActionState(actionState);
|
||||
|
@ -53,6 +53,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||
private int currentStateIndex = 0;
|
||||
private MultiActionDockingActionIf multiActionGenerator;
|
||||
private MultipleActionDockingToolbarButton multipleButton;
|
||||
private boolean fireFirstEvent = true;
|
||||
|
||||
private boolean performActionOnPrimaryButtonClick = true;
|
||||
|
||||
@ -136,6 +137,21 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||
doActionPerformed(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the first action automatically fire its event
|
||||
*/
|
||||
public boolean isFireFirstEvent() {
|
||||
return fireFirstEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the flag to fire an event on the first action
|
||||
* @param fireFirstEvent whether to fire the event
|
||||
*/
|
||||
public void setFireFirstEvent(boolean fireFirstEvent) {
|
||||
this.fireFirstEvent = fireFirstEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback to be overridden when the child wishes to respond to user button
|
||||
* presses that are on the button and not the drop-down. This will only be called if
|
||||
@ -176,9 +192,14 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||
tbd.setToolBarSubGroup(subGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* add the supplied {@code ActionState}
|
||||
* if {@code fireFirstEvent} is {@code true} the first one will fire its event
|
||||
* @param actionState the {@code ActionState} to add
|
||||
*/
|
||||
public void addActionState(ActionState<T> actionState) {
|
||||
actionStates.add(actionState);
|
||||
if (actionStates.size() == 1) {
|
||||
if (actionStates.size() == 1 && fireFirstEvent) {
|
||||
setCurrentActionState(actionState);
|
||||
}
|
||||
}
|
||||
@ -188,8 +209,10 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||
throw new IllegalArgumentException("You must provide at least one ActionState");
|
||||
}
|
||||
actionStates = new ArrayList<>(newStates);
|
||||
if (fireFirstEvent) {
|
||||
setCurrentActionState(actionStates.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public T getCurrentUserData() {
|
||||
return actionStates.get(currentStateIndex).getUserData();
|
||||
|
@ -73,6 +73,8 @@
|
||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
||||
<logger name="ghidra.util.task" level="DEBUG" />
|
||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||
|
||||
<Root level="ALL">
|
||||
<AppenderRef ref="console" level="DEBUG"/>
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.service.graph;
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -74,6 +76,10 @@ public class AttributedVertex extends Attributed {
|
||||
return getName() + " (" + id + ")";
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
this.htmlString = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse (one time) then cache the attributes to html
|
||||
* @return the html string
|
||||
@ -84,7 +90,7 @@ public class AttributedVertex extends Attributed {
|
||||
for (Map.Entry<String, String> entry : entrySet()) {
|
||||
buf.append(entry.getKey());
|
||||
buf.append(":");
|
||||
buf.append(entry.getValue());
|
||||
buf.append(StringEscapeUtils.escapeHtml4(entry.getValue()));
|
||||
buf.append("<br>");
|
||||
}
|
||||
htmlString = buf.toString();
|
||||
|
@ -38,4 +38,9 @@ public interface GraphDisplayListener {
|
||||
* @param vertexId the vertex id of the currently "focused" vertex
|
||||
*/
|
||||
public void locationChanged(String vertexId);
|
||||
|
||||
default boolean updateVertexName(String vertexId, String oldName, String newName) {
|
||||
// no op
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user