mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Adding capbility to add DockingActions to GraphDisplay. Also, wired in the help for existing actions.
This commit is contained in:
parent
b647c6cd5b
commit
532a1d4fd0
24 changed files with 1142 additions and 533 deletions
|
@ -20,10 +20,7 @@ import java.util.Objects;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import docking.widgets.EventTrigger;
|
||||
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;
|
||||
|
@ -42,7 +39,7 @@ import ghidra.util.Swing;
|
|||
public abstract class AddressBasedGraphDisplayListener
|
||||
implements GraphDisplayListener, PluginEventListener, DomainObjectListener {
|
||||
|
||||
private PluginTool tool;
|
||||
protected PluginTool tool;
|
||||
private GraphDisplay graphDisplay;
|
||||
protected Program program;
|
||||
private SymbolTable symbolTable;
|
||||
|
@ -168,21 +165,6 @@ 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) ||
|
||||
|
|
|
@ -39,6 +39,9 @@ import ghidra.util.HelpLocation;
|
|||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.layout.VerticalLayout;
|
||||
|
||||
/**
|
||||
* Dialog used to a label or to edit an existing label.
|
||||
*/
|
||||
public class AddEditDialog extends DialogComponentProvider {
|
||||
private static final int MAX_RETENTION = 10;
|
||||
private PluginTool tool;
|
||||
|
@ -69,6 +72,72 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
setDefaultButton(okButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to add a new label in the given program at the given address
|
||||
* @param address the address at which to add a new label
|
||||
* @param prog the program in which to add a new label
|
||||
*/
|
||||
public void addLabel(Address address, Program prog) {
|
||||
addLabel(address, prog, tool.getActiveWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to add a new label in the given program at the given address
|
||||
* @param address the address at which to add a new label
|
||||
* @param targetProgram the program in which to add a new label
|
||||
* @param provider the ComponentProvider to parent and center the dialog over.
|
||||
*/
|
||||
public void addLabel(Address address, Program targetProgram, ComponentProvider provider) {
|
||||
initDialogForAdd(targetProgram, address);
|
||||
tool.showDialog(this, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to add a new label in the given program at the given address
|
||||
* @param address the address at which to add a new label
|
||||
* @param targetProgram the program in which to add a new label
|
||||
* @param centeredOverComponent the component over which to center the dialog
|
||||
*/
|
||||
public void addLabel(Address address, Program targetProgram, Component centeredOverComponent) {
|
||||
initDialogForAdd(targetProgram, address);
|
||||
tool.showDialog(this, centeredOverComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to edit an existing label in the given program
|
||||
* @param targetSymbol the symbol(label) to edit
|
||||
* @param targetProgram the program containing the symbol
|
||||
*/
|
||||
public void editLabel(Symbol targetSymbol, Program targetProgram) {
|
||||
ComponentProvider componentProvider =
|
||||
tool.getComponentProvider(PluginConstants.CODE_BROWSER);
|
||||
JComponent component = componentProvider.getComponent();
|
||||
editLabel(targetSymbol, targetProgram, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to edit an existing label in the given program
|
||||
* @param targetSymbol the symbol(label) to edit
|
||||
* @param targetProgram the program containing the symbol
|
||||
* @param centeredOverComponent the component over which to center the dialog
|
||||
*/
|
||||
public void editLabel(Symbol targetSymbol, Program targetProgram,
|
||||
Component centeredOverComponent) {
|
||||
initDialogForEdit(targetProgram, targetSymbol);
|
||||
tool.showDialog(this, centeredOverComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the dialog to edit an existing label in the given program
|
||||
* @param targetSymbol the symbol(label) to edit
|
||||
* @param targetProgram the program containing the symbol
|
||||
* @param provider the ComponentProvider to parent and center the dialog over.
|
||||
*/
|
||||
public void editLabel(Symbol targetSymbol, Program targetProgram, ComponentProvider provider) {
|
||||
initDialogForEdit(targetProgram, targetSymbol);
|
||||
tool.showDialog(this, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
|
||||
|
@ -134,28 +203,6 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
return new SymbolPath(symbolName);
|
||||
}
|
||||
|
||||
private boolean isLocalNamespace(Namespace namespace, String symbolName) {
|
||||
FunctionSymbol functionSymbol = getFunctionSymbol(addr);
|
||||
if (functionSymbol == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isInFunctionNamespace(namespace)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isInFunctionNamespace(Namespace namespace) {
|
||||
for (Namespace p = namespace; p != null; p = p.getParentNamespace()) {
|
||||
if (p instanceof Function) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Namespace getSelectedNamespace() {
|
||||
Object selectedItem = namespaceChoices.getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
|
@ -240,7 +287,8 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
Namespace currentNamespace = program.getSymbolTable().getNamespace(addr);
|
||||
|
||||
// no symbol or not editing a function symbol
|
||||
if ((symbol == null) || (symbol != null && symbol.getSymbolType() != SymbolType.FUNCTION)) {
|
||||
if ((symbol == null) ||
|
||||
(symbol != null && symbol.getSymbolType() != SymbolType.FUNCTION)) {
|
||||
// walk the tree of namespaces and collect all of the items
|
||||
for (; (currentNamespace != globalNamespace); currentNamespace =
|
||||
currentNamespace.getParentNamespace()) {
|
||||
|
@ -277,7 +325,8 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
*/
|
||||
private void selectNamespace() {
|
||||
if (symbol != null && symbol.getParentNamespace() != null) {
|
||||
namespaceChoices.setSelectedItem(new NamespaceWrapper(symbol.getParentNamespace()));
|
||||
namespaceChoices
|
||||
.setSelectedItem(new NamespaceWrapper(symbol.getParentNamespace()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -330,18 +379,14 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
public void addLabel(Address address, Program p) {
|
||||
addLabel(address, p, tool.getActiveWindow());
|
||||
}
|
||||
|
||||
public void addLabel(Address address, Program p, Component centeredOverComponent) {
|
||||
private void initDialogForAdd(Program p, Address address) {
|
||||
if (!address.isMemoryAddress()) {
|
||||
throw new IllegalArgumentException(
|
||||
"AddEditDialog.addLabel only valid for memory address");
|
||||
}
|
||||
this.addr = address;
|
||||
this.program = p;
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
SymbolTable symbolTable = p.getSymbolTable();
|
||||
symbol = null;
|
||||
setTitle("Add Label at " + address);
|
||||
initRecentChoices();
|
||||
|
@ -365,17 +410,10 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
namespaceChoices.setEnabled(true);
|
||||
initNamespaces();
|
||||
clearStatusText();
|
||||
tool.showDialog(this, centeredOverComponent);
|
||||
|
||||
}
|
||||
|
||||
public void editLabel(Symbol s, Program p) {
|
||||
ComponentProvider componentProvider =
|
||||
tool.getComponentProvider(PluginConstants.CODE_BROWSER);
|
||||
JComponent component = componentProvider.getComponent();
|
||||
editLabel(s, p, component);
|
||||
}
|
||||
|
||||
public void editLabel(Symbol s, Program p, Component centeredOverComponent) {
|
||||
private void initDialogForEdit(Program p, Symbol s) {
|
||||
this.symbol = s;
|
||||
this.program = p;
|
||||
this.addr = s.getAddress();
|
||||
|
@ -386,7 +424,8 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
if (s.getSymbolType() == SymbolType.FUNCTION) {
|
||||
String title;
|
||||
if (s.isExternal()) {
|
||||
ExternalLocation extLoc = p.getExternalManager().getExternalLocation(s);
|
||||
ExternalLocation extLoc =
|
||||
program.getExternalManager().getExternalLocation(s);
|
||||
Address fnAddr = extLoc.getAddress();
|
||||
title = "Rename External Function";
|
||||
if (fnAddr != null) {
|
||||
|
@ -433,7 +472,7 @@ public class AddEditDialog extends DialogComponentProvider {
|
|||
}
|
||||
initNamespaces();
|
||||
clearStatusText();
|
||||
tool.showDialog(this, centeredOverComponent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -169,7 +169,8 @@ public class GraphAST extends GhidraScript {
|
|||
return vert;
|
||||
}
|
||||
|
||||
protected AttributedVertex getVarnodeVertex(Map<Integer, AttributedVertex> vertices, VarnodeAST vn) {
|
||||
protected AttributedVertex getVarnodeVertex(Map<Integer, AttributedVertex> vertices,
|
||||
VarnodeAST vn) {
|
||||
AttributedVertex res;
|
||||
res = vertices.get(vn.getUniqueId());
|
||||
if (res == null) {
|
||||
|
@ -259,5 +260,10 @@ public class GraphAST extends GhidraScript {
|
|||
String addrString = vertexId.substring(0, firstSpace);
|
||||
return getAddress(addrString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
|
||||
return new ASTGraphDisplayListener(tool, graphDisplay, highfunc, currentProgram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.program.model.address.*;
|
|||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.model.pcode.PcodeBlockBasic;
|
||||
import ghidra.service.graph.GraphDisplay;
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
|
@ -112,4 +113,9 @@ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
|
||||
return new ASTGraphDisplayListener(tool, graphDisplay, hfunction, graphType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public class ASTGraphTask extends Task {
|
|||
enum GraphType {
|
||||
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
|
||||
private String name;
|
||||
|
||||
GraphType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -194,7 +195,8 @@ public class ASTGraphTask extends Task {
|
|||
}
|
||||
}
|
||||
|
||||
private AttributedVertex getOpVertex(AttributedGraph graph, PcodeOpAST op, TaskMonitor monitor) {
|
||||
private AttributedVertex getOpVertex(AttributedGraph graph, PcodeOpAST op,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
String key = "O_" + Integer.toString(op.getSeqnum().getTime());
|
||||
AttributedVertex vertex = graph.getVertex(key);
|
||||
|
@ -226,7 +228,8 @@ public class ASTGraphTask extends Task {
|
|||
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
|
||||
}
|
||||
|
||||
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node, TaskMonitor monitor) {
|
||||
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
// TODO: Missing Varnode unique ID ??
|
||||
|
||||
|
@ -288,7 +291,8 @@ public class ASTGraphTask extends Task {
|
|||
}
|
||||
}
|
||||
|
||||
private AttributedVertex getBlockVertex(AttributedGraph graph, PcodeBlock pblock, TaskMonitor monitor) {
|
||||
private AttributedVertex getBlockVertex(AttributedGraph graph, PcodeBlock pblock,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
String key = Integer.toString(pblock.getIndex());
|
||||
AttributedVertex vertex = graph.getVertex(key);
|
||||
|
|
|
@ -37,44 +37,71 @@
|
|||
<li>Ctrl+MouseButton1+drag on an empty area will create a rectangular area and select enclosed vertices</li>
|
||||
<li>Ctrl+MouseButton1+drag over a vertex will reposition all selected vertices</li>
|
||||
</ul>
|
||||
<H2>Upper-right Icon Buttons:</H2>
|
||||
<ul>
|
||||
<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"> toggle button, when 'set' will open a rectangular magnification lens in the graph view</li>
|
||||
<H2>Toolbar Buttons</H2>
|
||||
<P><A name="Scroll_To_Selection"/> The <IMG src="images/locationIn.gif"> toggle button, when 'set' will cause a focused vertex (red arrow) to be scrolled to the center of the view</P>
|
||||
<P><A name="Free_Form_Selection"/>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.</P>
|
||||
<P><A name="SatelliteView"/>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</P>
|
||||
<P><A name="Reset_View"/>The <IMG src="images/reload3.png"> button will reset any visual transformations on the graph and center it at a best-effort size</P>
|
||||
<P><A name="View_Magnifier"/>The <IMG src="images/magnifier.png"> toggle button, when 'set' will open a rectangular magnification lens in the graph view</P>
|
||||
<BLOCKQUOTE><BLOCKQUOTE>
|
||||
<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>MouseWheel will change the magnification of the lens</li>
|
||||
</ul>
|
||||
<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>
|
||||
</BLOCKQUOTE></BLOCKQUOTE>
|
||||
|
||||
<P><A name="Show_Filters"/>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.
|
||||
The Filter dialog buttons are created by examining the graph vertex/edge properties to discover candidates for filtering.</P></BLOCKQUOTE></BLOCKQUOTE>
|
||||
|
||||
<P><A name="Arrangement"/>The <IMG src="images/katomic.png" width="16" height="16"> Arrangement menu is used to select one of several graph layout algorithms.</P>
|
||||
<BLOCKQUOTE><BLOCKQUOTE>
|
||||
|
||||
<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>
|
||||
<li><A name="Compact_Hierarchical"/><B>Compact Hierarchical</B> is the <b>TidierTree Layout Algorithm</b>. It builds a tree structure and attempts to reduce horizontal space.</li>
|
||||
<li><A name="Hierarchical"/><B>Hierarchical</B> is a basic Tree algorithm. It prioritizes 'important' edges while constructing the tree.</li>
|
||||
<li><A name="Compact Radial"/><B>Compact Radial</B> is the <b>TidierTree Layout Algorithm</b> with the root(s) at the center and child vertices radiating outwards.</li>
|
||||
<li><B>Hierarchical MinCross</B> is the <b>Sugiyama Layout Algorithm</b>. It attempts to route edges around vertices in order to reduce crossing.There are four layering 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>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>
|
||||
<li><A name="Hierarchical_MinCross_Top_Down"/><B>Top Down</B> - biases the vertices to the top</li>
|
||||
<li><A name="Hierarchical_MinCross_Longest_Path"/><B>Longest Path</B> - biases the vertices to the bottom</li>
|
||||
<li><A name="Hierarchical_MinCross_Network_Simplex"/><B>Network Simplex</B> - layers after finding an 'optimal tree'</li>
|
||||
<li><A name="Hierarchical_MinCross_Coffman_Graham"/><B>Coffman Graham</B> - biases the vertices using a scheduling algorithm to minimize length</li>
|
||||
</ul>
|
||||
|
||||
<li><A name="Circle"/><B>Circle</B> 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><A name="Force_Balanced"/><B>Force Balanced</B> 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><A name="Force_Directed"/><B>Force Directed</B> 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><A name="Radial"/><B>Radial</B> is a Tree structure with the root(s) at the center and child vertices radiating outwards.</li>
|
||||
<li><A name="Balloon"/><B>Balloon</B> is a Tree structure with the root(s) at the centers of circles in a radial pattern</li>
|
||||
<li><A name="Gem__Graph_Embedder_"/><B>GEM</B> is a Force Directed layout with locally separated components </li>
|
||||
</ul>
|
||||
</BLOCKQUOTE></BLOCKQUOTE>
|
||||
<H2>Popup Actions</H2>
|
||||
<BLOCKQUOTE>
|
||||
<H3> Standard Popup Actions</H3>
|
||||
<ul>
|
||||
<li><A name="Hide Selected"/><B>Hide Selected</B> - Causes the display to not show selected vertices. </li>
|
||||
<li><A name="Hide Unselected"/><B>Hide Unselected</B> - Causes the display to not show unselected vertices.</li>
|
||||
<li><A name="Invert Selection"/><B>Invert Selection</B> - Unselects all selected nodes and selects all unselected nodes.</li>
|
||||
<li><A name="Grow Selection From Sources"/><B>Grow Selection From Sources</B> - Adds to the selection all vertices that have outgoing edges to the current selection.</li>
|
||||
<li><A name="Grow Selection To Targets"/><B>Grow Selection To Targets</B> - Adds to the selection all vertices that have incoming edges from the current selection.</li>
|
||||
<li><A name="Create Subgraph"/><B>Display Selected As New Graph</B> - Creates a new graph and display from the currently selected vertices.</li>
|
||||
</ul>
|
||||
<H3> Vertex Popup Actions</H3>
|
||||
<ul>
|
||||
<li><A name="Select Vertex"/><B>Select Vertex</B> - Selects the vertex that this action was invoked on.</li>
|
||||
<li><A name="Deselect Vertex"/><B>Deselect Vertex</B> - Deselects the vertex that this action was invoked on.</li>
|
||||
</ul>
|
||||
|
||||
<H3> Edge Popup Actions</H3>
|
||||
<ul>
|
||||
<li><A name="Edge Source"/><B>Go To Edge Source</B> - Makes this edge's source vertex be the focused vertex.</li>
|
||||
<li><A name="Edge Target"/><B>Go To Edge Target</B> - Makes this edge's destination vertex be the focused vertex.</li>
|
||||
<li><A name="Select Edge"/><B>Select Edge</B> - Add this edge and its associated vertices to the selection</li>
|
||||
<li><A name="Deselect Edge"/><B>Deselect Edge</B> - Removes this edge and its associated vertices from the selection </li>
|
||||
</ul>
|
||||
</BLOCKQUOTE>
|
||||
</BODY>
|
||||
</HTML>
|
|
@ -15,10 +15,11 @@
|
|||
*/
|
||||
package ghidra.graph.export;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.jgrapht.Graph;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.service.graph.*;
|
||||
|
@ -116,4 +117,19 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAction(DockingAction action) {
|
||||
// do nothing, actions are not supported by this display
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFocusedVertexId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSelectedVertexIds() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.awt.geom.Point2D;
|
|||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -32,6 +33,7 @@ import javax.swing.event.AncestorEvent;
|
|||
import javax.swing.event.AncestorListener;
|
||||
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.graph.AsSubgraph;
|
||||
import org.jungrapht.visualization.*;
|
||||
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
|
||||
|
@ -51,17 +53,16 @@ import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
|
|||
import org.jungrapht.visualization.util.RectangleUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.*;
|
||||
import docking.menu.ActionState;
|
||||
import docking.widgets.EventTrigger;
|
||||
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.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import resources.Icons;
|
||||
|
@ -102,9 +103,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
private final PluginTool pluginTool;
|
||||
|
||||
/**
|
||||
* the {@link Plugin} that manages this {@link GraphDisplay}
|
||||
* the "owner name" for action - mainly affects default help location
|
||||
*/
|
||||
private final String pluginName = "ProgramGraphPlugin";
|
||||
private final String actionOwnerName = "GraphServices";
|
||||
|
||||
/**
|
||||
* provides the component for the {@link GraphDisplay}
|
||||
|
@ -161,12 +162,16 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
*/
|
||||
private boolean freeFormSelection;
|
||||
|
||||
/**
|
||||
* Handles the popup
|
||||
*/
|
||||
private GhidraGraphMouse graphMouse;
|
||||
|
||||
/**
|
||||
* 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 -> {
|
||||
Consumer<Graph<AttributedVertex, AttributedEdge>> subgraphConsumer = g -> {
|
||||
try {
|
||||
AttributedGraph attributedGraph = new AttributedGraph();
|
||||
g.vertexSet().forEach(attributedGraph::addVertex);
|
||||
|
@ -181,6 +186,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
// noop
|
||||
}
|
||||
};
|
||||
private ToggleDockingAction hideSelectedAction;
|
||||
private ToggleDockingAction hideUnselectedAction;
|
||||
private SwitchableSelectionItemListener switchableSelectionListener;
|
||||
|
||||
/**
|
||||
|
@ -217,7 +224,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
.builder(viewer.getRenderContext().getVertexBoundsFunction())
|
||||
.build());
|
||||
|
||||
createActions();
|
||||
graphMouse = new GhidraGraphMouse(componentProvider, viewer);
|
||||
|
||||
createToolbarActions();
|
||||
createPopupActions();
|
||||
connectSelectionStateListeners();
|
||||
}
|
||||
|
||||
|
@ -287,10 +297,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
/**
|
||||
* create the action icon buttons on the upper-right of the graph display window
|
||||
*/
|
||||
private void createActions() {
|
||||
private void createToolbarActions() {
|
||||
|
||||
// create a toggle for 'scroll to selected vertex'
|
||||
new ToggleActionBuilder("Scroll To Selection", pluginName)
|
||||
new ToggleActionBuilder("Scroll To Selection", actionOwnerName)
|
||||
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
|
||||
.description("Ensure that the 'focused' vertex is visible")
|
||||
.selected(true)
|
||||
|
@ -302,7 +312,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
// 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)
|
||||
new ToggleActionBuilder("Free-Form Selection", actionOwnerName)
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
|
||||
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)")
|
||||
.selected(false)
|
||||
|
@ -311,13 +321,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create an icon button to display the satellite view
|
||||
new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View")
|
||||
new ToggleActionBuilder("SatelliteView", actionOwnerName).description("Show Satellite View")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON)
|
||||
.onAction(this::toggleSatellite)
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create an icon button to reset the view transformations to identity (scaled to layout)
|
||||
new ActionBuilder("Reset View", pluginName)
|
||||
new ActionBuilder("Reset View", actionOwnerName)
|
||||
.description("Reset all view transforms to center graph in display")
|
||||
.toolBarIcon(Icons.REFRESH_ICON)
|
||||
.onAction(context -> viewer.scaleToLayout())
|
||||
|
@ -325,7 +335,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
// create a button to show the view magnify lens
|
||||
LensSupport<LensGraphMouse> magnifyViewSupport = createMagnifier();
|
||||
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", pluginName)
|
||||
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", actionOwnerName)
|
||||
.description("Show View Magnifier")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
|
||||
.onAction(context -> magnifyViewSupport.activate(
|
||||
|
@ -336,13 +346,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
componentProvider.addLocalAction(lensToggle);
|
||||
|
||||
// create an action button to show a dialog with generated filters
|
||||
new ActionBuilder("Show Filters", pluginName).description("Show Graph Filters")
|
||||
new ActionBuilder("Show Filters", actionOwnerName).description("Show Graph Filters")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.FILTER_ICON)
|
||||
.onAction(context -> showFilterDialog())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
// create a menu with graph layout algorithm selections
|
||||
new MultiStateActionBuilder<String>("Arrangement", pluginName)
|
||||
new MultiStateActionBuilder<String>("Arrangement", actionOwnerName)
|
||||
.description("Select Layout Arrangement Algorithm")
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON)
|
||||
.fireFirstAction(false)
|
||||
|
@ -365,6 +375,179 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
});
|
||||
}
|
||||
|
||||
private void createPopupActions() {
|
||||
new ActionBuilder("Select Vertex", actionOwnerName)
|
||||
.popupMenuPath("Select Vertex")
|
||||
.popupMenuGroup("selection", "1")
|
||||
.withContext(VertexGraphActionContext.class)
|
||||
.enabledWhen(c -> !viewer.getSelectedVertexState().isSelected(c.getClickedVertex()))
|
||||
.onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Deselect Vertex", actionOwnerName)
|
||||
.popupMenuPath("Deselect Vertex")
|
||||
.popupMenuGroup("selection", "2")
|
||||
.withContext(VertexGraphActionContext.class)
|
||||
.enabledWhen(c -> viewer.getSelectedVertexState().isSelected(c.getClickedVertex()))
|
||||
.onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Select Edge", actionOwnerName)
|
||||
.popupMenuPath("Select Edge")
|
||||
.popupMenuGroup("selection", "1")
|
||||
.withContext(EdgeGraphActionContext.class)
|
||||
.enabledWhen(c -> !viewer.getSelectedEdgeState().isSelected(c.getClickedEdge()))
|
||||
.onAction(c -> selectEdge(c.getClickedEdge()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Deselect Edge", actionOwnerName)
|
||||
.popupMenuPath("Deselect Edge")
|
||||
.popupMenuGroup("selection", "2")
|
||||
.withContext(EdgeGraphActionContext.class)
|
||||
.enabledWhen(c -> viewer.getSelectedEdgeState().isSelected(c.getClickedEdge()))
|
||||
.onAction(c -> deselectEdge(c.getClickedEdge()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Edge Source", actionOwnerName)
|
||||
.popupMenuPath("Go To Edge Source")
|
||||
.popupMenuGroup("Go To")
|
||||
.withContext(EdgeGraphActionContext.class)
|
||||
.onAction(c -> setFocusedVertex(graph.getEdgeSource(c.getClickedEdge())))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Edge Target", actionOwnerName)
|
||||
.popupMenuPath("Go To Edge Target")
|
||||
.popupMenuGroup("Go To")
|
||||
.withContext(EdgeGraphActionContext.class)
|
||||
.onAction(c -> setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge())))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
hideSelectedAction = new ToggleActionBuilder("Hide Selected", actionOwnerName)
|
||||
.popupMenuPath("Hide Selected")
|
||||
.popupMenuGroup("z", "1")
|
||||
.description("Toggles whether or not to show selected vertices and edges")
|
||||
.onAction(c -> manageVertexDisplay())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", actionOwnerName)
|
||||
.popupMenuPath("Hide Unselected")
|
||||
.popupMenuGroup("z", "2")
|
||||
.description("Toggles whether or not to show selected vertices and edges")
|
||||
.onAction(c -> manageVertexDisplay())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Invert Selection", actionOwnerName)
|
||||
.popupMenuPath("Invert Selection")
|
||||
.popupMenuGroup("z", "3")
|
||||
.description("Inverts the current selection")
|
||||
.onAction(c -> invertSelection())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Grow Selection To Targets", actionOwnerName)
|
||||
.popupMenuPath("Grow Selection To Targets")
|
||||
.popupMenuGroup("z", "4")
|
||||
.description("Extends the current selection by including the target vertex " +
|
||||
"of all edges whose source is selected")
|
||||
.keyBinding("ctrl O")
|
||||
.enabledWhen(c -> !isAllSelected(getTargetVerticesFromSelected()))
|
||||
.onAction(c -> growSelection(getTargetVerticesFromSelected()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Grow Selection From Sources", actionOwnerName)
|
||||
.popupMenuPath("Grow Selection From Sources")
|
||||
.popupMenuGroup("z", "4")
|
||||
.description("Extends the current selection by including the target vertex " +
|
||||
"of all edges whose source is selected")
|
||||
.keyBinding("ctrl I")
|
||||
.enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()))
|
||||
.onAction(c -> growSelection(getSourceVerticesFromSelected()))
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Create Subgraph", actionOwnerName)
|
||||
.popupMenuPath("Display Selected as New Graph")
|
||||
.popupMenuGroup("z", "5")
|
||||
.description("Creates a subgraph from the selected nodes")
|
||||
.enabledWhen(c -> !viewer.getSelectedVertexState().getSelected().isEmpty())
|
||||
.onAction(c -> createAndDisplaySubGraph())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
}
|
||||
|
||||
private void createAndDisplaySubGraph() {
|
||||
GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
display.setGraph(createSubGraph(), "SubGraph", false, TaskMonitor.DUMMY);
|
||||
display.setGraphDisplayListener(listener.cloneWith(display));
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// using Dummy, so can't happen
|
||||
}
|
||||
}
|
||||
|
||||
private AttributedGraph createSubGraph() {
|
||||
Set<AttributedVertex> selected = viewer.getSelectedVertexState().getSelected();
|
||||
Graph<AttributedVertex, AttributedEdge> subGraph = new AsSubgraph<>(graph, selected);
|
||||
|
||||
AttributedGraph newGraph = new AttributedGraph();
|
||||
subGraph.vertexSet().forEach(newGraph::addVertex);
|
||||
subGraph.edgeSet().forEach(e -> {
|
||||
AttributedVertex source = subGraph.getEdgeSource(e);
|
||||
AttributedVertex target = subGraph.getEdgeTarget(e);
|
||||
newGraph.addEdge(source, target, e);
|
||||
});
|
||||
return newGraph;
|
||||
}
|
||||
|
||||
private void growSelection(Set<AttributedVertex> vertices) {
|
||||
viewer.getSelectedVertexState().select(vertices);
|
||||
}
|
||||
|
||||
private boolean isAllSelected(Set<AttributedVertex> vertices) {
|
||||
return viewer.getSelectedVertexState().getSelected().containsAll(vertices);
|
||||
}
|
||||
|
||||
private Set<AttributedVertex> getTargetVerticesFromSelected() {
|
||||
Set<AttributedVertex> targets = new HashSet<>();
|
||||
Set<AttributedVertex> selectedVertices = getSelectedVertices();
|
||||
selectedVertices.forEach(v -> {
|
||||
Set<AttributedEdge> edges = graph.outgoingEdgesOf(v);
|
||||
edges.forEach(e -> targets.add(graph.getEdgeTarget(e)));
|
||||
});
|
||||
return targets;
|
||||
}
|
||||
|
||||
private Set<AttributedVertex> getSourceVerticesFromSelected() {
|
||||
Set<AttributedVertex> sources = new HashSet<>();
|
||||
Set<AttributedVertex> selectedVertices = getSelectedVertices();
|
||||
selectedVertices.forEach(v -> {
|
||||
Set<AttributedEdge> edges = graph.incomingEdgesOf(v);
|
||||
edges.forEach(e -> sources.add(graph.getEdgeSource(e)));
|
||||
});
|
||||
return sources;
|
||||
}
|
||||
|
||||
private void invertSelection() {
|
||||
switchableSelectionListener.setEnabled(false);
|
||||
try {
|
||||
MutableSelectedState<AttributedVertex> selectedVertexState =
|
||||
viewer.getSelectedVertexState();
|
||||
graph.vertexSet().forEach(v -> {
|
||||
if (selectedVertexState.isSelected(v)) {
|
||||
selectedVertexState.deselect(v);
|
||||
}
|
||||
else {
|
||||
selectedVertexState.select(v);
|
||||
}
|
||||
});
|
||||
Set<AttributedVertex> selected = selectedVertexState.getSelected();
|
||||
List<String> selectedIds =
|
||||
selected.stream().map(AttributedVertex::getId).collect(Collectors.toList());
|
||||
notifySelectionChanged(selectedIds);
|
||||
}
|
||||
finally {
|
||||
switchableSelectionListener.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a {@code List} of {@code ActionState} buttons for the
|
||||
* configured layout algorithms
|
||||
|
@ -374,8 +557,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
String[] names = layoutTransitionManager.getLayoutNames();
|
||||
List<ActionState<String>> actionStates = new ArrayList<>();
|
||||
for (String layoutName : names) {
|
||||
actionStates.add(new ActionState<>(layoutName,
|
||||
DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName));
|
||||
ActionState<String> state = new ActionState<>(layoutName,
|
||||
DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName);
|
||||
state.setHelpLocation(new HelpLocation(actionOwnerName, layoutName));
|
||||
actionStates.add(state);
|
||||
}
|
||||
return actionStates;
|
||||
}
|
||||
|
@ -489,6 +674,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
listener.graphClosed();
|
||||
}
|
||||
listener = null;
|
||||
componentProvider.closeComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -501,18 +687,23 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
this.listener.graphClosed();
|
||||
}
|
||||
this.listener = listener;
|
||||
DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse =
|
||||
GhidraGraphMouse.<AttributedVertex, AttributedEdge> builder()
|
||||
.viewer(viewer)
|
||||
.subgraphConsumer(subgraphConsumer)
|
||||
.locatedVertexConsumer(this::setFocusedVertex)
|
||||
.graphDisplayListener(listener)
|
||||
.vertexIdFunction(AttributedVertex::getId)
|
||||
.vertexNameFunction(AttributedVertex::getName)
|
||||
.build();
|
||||
viewer.setGraphMouse(graphMouse);
|
||||
}
|
||||
|
||||
private void deselectEdge(AttributedEdge edge) {
|
||||
viewer.getSelectedEdgeState().deselect(edge);
|
||||
AttributedVertex source = graph.getEdgeSource(edge);
|
||||
AttributedVertex target = graph.getEdgeTarget(edge);
|
||||
viewer.getSelectedVertexState().deselect(Set.of(source, target));
|
||||
}
|
||||
|
||||
private void selectEdge(AttributedEdge edge) {
|
||||
viewer.getSelectedEdgeState().select(edge);
|
||||
AttributedVertex source = graph.getEdgeSource(edge);
|
||||
AttributedVertex target = graph.getEdgeTarget(edge);
|
||||
viewer.getSelectedVertexState().select(Set.of(source, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* connect the selection state to to the visualization
|
||||
*/
|
||||
|
@ -1006,6 +1197,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
vv.getComponent().requestFocus();
|
||||
vv.setBackground(Color.WHITE);
|
||||
MouseListener[] mouseListeners = vv.getComponent().getMouseListeners();
|
||||
for (MouseListener mouseListener : mouseListeners) {
|
||||
vv.getComponent().removeMouseListener(mouseListener);
|
||||
}
|
||||
|
||||
return vv;
|
||||
}
|
||||
|
||||
|
@ -1051,6 +1247,78 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAction(DockingAction action) {
|
||||
componentProvider.addLocalAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFocusedVertexId() {
|
||||
return focusedVertex == null ? null : focusedVertex.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSelectedVertexIds() {
|
||||
Set<AttributedVertex> selectedVertices = getSelectedVertices();
|
||||
return selectedVertices.stream().map(v -> v.getId()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public ActionContext getActionContext(MouseEvent e) {
|
||||
|
||||
AttributedVertex pickedVertex = graphMouse.getPickedVertex(e);
|
||||
if (pickedVertex != null) {
|
||||
return new VertexGraphActionContext(componentProvider, graph, getSelectedVertices(),
|
||||
focusedVertex, pickedVertex);
|
||||
}
|
||||
|
||||
AttributedEdge pickedEdge = graphMouse.getPickedEdge(e);
|
||||
if (pickedEdge != null) {
|
||||
return new EdgeGraphActionContext(componentProvider, graph, getSelectedVertices(),
|
||||
focusedVertex, pickedEdge);
|
||||
}
|
||||
|
||||
return new GraphActionContext(componentProvider, graph, getSelectedVertices(),
|
||||
focusedVertex);
|
||||
|
||||
}
|
||||
|
||||
private Set<AttributedVertex> getSelectedVertices() {
|
||||
return viewer.getSelectedVertexState().getSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the hide selected action states 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>
|
||||
*/
|
||||
private void manageVertexDisplay() {
|
||||
boolean hideSelected = hideSelectedAction.isSelected();
|
||||
boolean hideUnselected = hideUnselectedAction.isSelected();
|
||||
MutableSelectedState<AttributedVertex> selectedVertexState =
|
||||
viewer.getSelectedVertexState();
|
||||
if (hideSelected && hideUnselected) {
|
||||
viewer.getRenderContext()
|
||||
.setVertexIncludePredicate(v -> false);
|
||||
}
|
||||
else if (hideSelected) {
|
||||
viewer.getRenderContext()
|
||||
.setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected));
|
||||
}
|
||||
else if (hideUnselected) {
|
||||
viewer.getRenderContext()
|
||||
.setVertexIncludePredicate(selectedVertexState::isSelected);
|
||||
}
|
||||
else {
|
||||
viewer.getRenderContext()
|
||||
.setVertexIncludePredicate(v -> true);
|
||||
}
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,10 +15,14 @@
|
|||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.service.graph.GraphDisplay;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
|
@ -28,7 +32,7 @@ public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapt
|
|||
|
||||
static final String WINDOW_GROUP = "ProgramGraph";
|
||||
private static final String WINDOW_MENU_GROUP_NAME = "Graph";
|
||||
private final DefaultGraphDisplay display;
|
||||
private DefaultGraphDisplay display;
|
||||
|
||||
DefaultGraphDisplayComponentProvider(DefaultGraphDisplay display, PluginTool pluginTool) {
|
||||
super(pluginTool, "Graph", "DefaultGraphDisplay");
|
||||
|
@ -52,7 +56,17 @@ public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapt
|
|||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
if (display != null) {
|
||||
super.closeComponent();
|
||||
display.close();
|
||||
// to prevent looping, null out display before callings its close method.
|
||||
GraphDisplay closingDisplay = display;
|
||||
display = null;
|
||||
closingDisplay.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
return display.getActionContext(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,148 +15,94 @@
|
|||
*/
|
||||
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.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.selection.ShapePickSupport;
|
||||
import static org.jungrapht.visualization.VisualizationServer.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jungrapht.visualization.VisualizationServer.PREFIX;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
import org.jungrapht.visualization.control.*;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.selection.ShapePickSupport;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
public class GhidraGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> {
|
||||
|
||||
private static final String PICK_AREA_SIZE = PREFIX + "pickAreaSize";
|
||||
private static final String PICK_AREA_SIZE_PROPERTY = 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;
|
||||
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
|
||||
|
||||
/**
|
||||
* Accepts a vertex that was 'located'
|
||||
*/
|
||||
Consumer<V> locatedVertexConsumer;
|
||||
/**
|
||||
* 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
|
||||
* @param <V> vertex type
|
||||
* @param <E> edge type
|
||||
* @return a configured GhidraGraphMouseBuilder
|
||||
*/
|
||||
public static <V, E> GhidraGraphMouseBuilder<V, E, ?, ?> builder() {
|
||||
return new GhidraGraphMouseBuilder<>();
|
||||
}
|
||||
private int pickSize;
|
||||
|
||||
/**
|
||||
* create an instance with default values
|
||||
* @param componentProvider the graph component provider
|
||||
* @param viewer the graph viewer component
|
||||
*/
|
||||
GhidraGraphMouse(GhidraGraphMouseBuilder<V, E, ?, ?> builder) {
|
||||
super(builder.vertexSelectionOnly(true));
|
||||
this.viewer = builder.viewer;
|
||||
this.subgraphConsumer = builder.subgraphConsumer;
|
||||
this.locatedVertexConsumer = builder.locatedVertexConsumer;
|
||||
this.graphDisplayListener = builder.graphDisplayListener;
|
||||
this.vertexIdFunction = builder.vertexIdFunction;
|
||||
this.vertexNameFunction = builder.vertexNameFunction;
|
||||
GhidraGraphMouse(ComponentProvider componentProvider,
|
||||
VisualizationViewer<AttributedVertex, AttributedEdge> viewer) {
|
||||
|
||||
super(DefaultGraphMouse.<AttributedVertex, AttributedEdge> builder());
|
||||
this.viewer = viewer;
|
||||
pickSize = Integer.getInteger(GhidraGraphMouse.PICK_AREA_SIZE_PROPERTY, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the plugins, and load them
|
||||
*/
|
||||
@Override
|
||||
public void loadPlugins() {
|
||||
add(new PopupPlugin());
|
||||
super.loadPlugins();
|
||||
}
|
||||
|
||||
class PopupPlugin extends AbstractPopupGraphMousePlugin {
|
||||
|
||||
SelectionFilterMenu<V, E> selectionFilterMenu;
|
||||
|
||||
PopupPlugin() {
|
||||
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,
|
||||
private Rectangle2D getFootprint(MouseEvent e) {
|
||||
return new Rectangle2D.Float(
|
||||
e.getPoint().x - pickSize / 2f,
|
||||
e.getPoint().y - pickSize / 2f,
|
||||
pickSize,
|
||||
pickSize);
|
||||
}
|
||||
|
||||
LayoutModel<V> layoutModel = viewer.getVisualizationModel().getLayoutModel();
|
||||
GraphElementAccessor<V, E> pickSupport = viewer.getPickSupport();
|
||||
V pickedVertex;
|
||||
E pickedEdge = null;
|
||||
AttributedEdge getPickedEdge(MouseEvent e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
Rectangle2D footprintRectangle = getFootprint(e);
|
||||
LayoutModel<AttributedVertex> layoutModel = viewer.getVisualizationModel().getLayoutModel();
|
||||
GraphElementAccessor<AttributedVertex, AttributedEdge> pickSupport =
|
||||
viewer.getPickSupport();
|
||||
if (pickSupport instanceof ShapePickSupport) {
|
||||
ShapePickSupport<V, E> shapePickSupport =
|
||||
(ShapePickSupport<V, E>) pickSupport;
|
||||
pickedVertex = shapePickSupport.getVertex(layoutModel, footprintRectangle);
|
||||
if (pickedVertex == null) {
|
||||
pickedEdge = shapePickSupport.getEdge(layoutModel, footprintRectangle);
|
||||
ShapePickSupport<AttributedVertex, AttributedEdge> shapePickSupport =
|
||||
(ShapePickSupport<AttributedVertex, AttributedEdge>) pickSupport;
|
||||
return shapePickSupport.getEdge(layoutModel, footprintRectangle);
|
||||
}
|
||||
} else {
|
||||
TransformSupport<V, E> transformSupport = viewer.getTransformSupport();
|
||||
|
||||
TransformSupport<AttributedVertex, AttributedEdge> transformSupport =
|
||||
viewer.getTransformSupport();
|
||||
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
|
||||
pickedVertex = pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY());
|
||||
if (pickedVertex == null) {
|
||||
pickedEdge = pickSupport.getEdge(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 if (pickedEdge != null) {
|
||||
OnEdgeSelectionMenu<V, E> menu =
|
||||
new OnEdgeSelectionMenu<>(viewer, locatedVertexConsumer, pickedEdge);
|
||||
menu.show(viewer.getComponent(), e.getX(), e.getY());
|
||||
} else {
|
||||
selectionFilterMenu.show(viewer.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
return pickSupport.getEdge(layoutModel, layoutPoint.getX(), layoutPoint.getY());
|
||||
}
|
||||
|
||||
AttributedVertex getPickedVertex(MouseEvent e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
Rectangle2D footprintRectangle = getFootprint(e);
|
||||
LayoutModel<AttributedVertex> layoutModel = viewer.getVisualizationModel().getLayoutModel();
|
||||
GraphElementAccessor<AttributedVertex, AttributedEdge> pickSupport =
|
||||
viewer.getPickSupport();
|
||||
if (pickSupport instanceof ShapePickSupport) {
|
||||
ShapePickSupport<AttributedVertex, AttributedEdge> shapePickSupport =
|
||||
(ShapePickSupport<AttributedVertex, AttributedEdge>) pickSupport;
|
||||
return shapePickSupport.getVertex(layoutModel, footprintRectangle);
|
||||
}
|
||||
|
||||
TransformSupport<AttributedVertex, AttributedEdge> transformSupport =
|
||||
viewer.getTransformSupport();
|
||||
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
|
||||
return pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/* ###
|
||||
* 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.DefaultGraphMouse;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jungrapht.visualization.VisualizationServer.PREFIX;
|
||||
|
||||
/**
|
||||
* An extension of the jungrapht DefaultGraphMouse.Builder. 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 GhidraGraphMouseBuilder<V, E, T extends GhidraGraphMouse<V, E>, B extends GhidraGraphMouseBuilder<V, E, T, B>>
|
||||
extends DefaultGraphMouse.Builder<V, E, T, B> {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* accepts a 'located' vertex via a menu driven action
|
||||
*/
|
||||
Consumer<V> locatedVertexConsumer;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public B self() {
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B viewer(VisualizationViewer<V, E> viewer) {
|
||||
this.viewer = viewer;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B subgraphConsumer(Consumer<Graph<V, E>> subgraphConsumer) {
|
||||
this.subgraphConsumer = subgraphConsumer;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B locatedVertexConsumer(Consumer<V> locatedVertexConsumer) {
|
||||
this.locatedVertexConsumer = locatedVertexConsumer;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B graphDisplayListener(GraphDisplayListener graphDisplayListener) {
|
||||
this.graphDisplayListener = graphDisplayListener;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B vertexIdFunction(Function<V, String> vertexIdFunction) {
|
||||
this.vertexIdFunction = vertexIdFunction;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B vertexNameFunction(Function<V, String> vertexNameFunction) {
|
||||
this.vertexNameFunction = vertexIdFunction;
|
||||
return self();
|
||||
}
|
||||
|
||||
public T build() {
|
||||
return (T) new GhidraGraphMouse(this);
|
||||
}
|
||||
|
||||
public static <V, E> GhidraGraphMouseBuilder<V, E, ?, ?> builder() {
|
||||
return new GhidraGraphMouseBuilder<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/* ###
|
||||
* 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.jungrapht.visualization.VisualizationViewer;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* a Popup menu to allow actions relative to a particular edge.
|
||||
* The popup appears on a right click over an edge in the display.
|
||||
* The user can select/deselect the edge. When the edge is selected,
|
||||
* its endpoints are also selected and the target endpoint is 'located'
|
||||
*/
|
||||
public class OnEdgeSelectionMenu<V, E> extends JPopupMenu {
|
||||
|
||||
public OnEdgeSelectionMenu(VisualizationViewer<V, E> visualizationViewer,
|
||||
Consumer<V> locatedVertexConsumer,
|
||||
E edge) {
|
||||
Graph<V, E> graph = visualizationViewer.getVisualizationModel().getGraph();
|
||||
V source = graph.getEdgeSource(edge);
|
||||
V target = graph.getEdgeTarget(edge);
|
||||
AbstractButton selectButton = new JMenuItem("Select Edge");
|
||||
AbstractButton deselectButton = new JMenuItem("Deselect Edge");
|
||||
selectButton.addActionListener(evt -> {
|
||||
visualizationViewer.getSelectedEdgeState().select(edge);
|
||||
visualizationViewer.getSelectedVertexState().select(Set.of(source, target));
|
||||
locatedVertexConsumer.accept(target);
|
||||
});
|
||||
deselectButton.addActionListener(evt -> {
|
||||
visualizationViewer.getSelectedEdgeState().deselect(edge);
|
||||
visualizationViewer.getSelectedVertexState().deselect(Set.of(source, target));
|
||||
});
|
||||
add(visualizationViewer.getSelectedEdgeState().isSelected(edge) ? deselectButton : selectButton);
|
||||
AbstractButton locateSourceButton = new JMenuItem("Locate Edge Source");
|
||||
locateSourceButton.addActionListener(evt -> {
|
||||
locatedVertexConsumer.accept(source);
|
||||
});
|
||||
AbstractButton locateTargetButton = new JMenuItem("Locate Edge Target");
|
||||
locateTargetButton.addActionListener(evt -> {
|
||||
locatedVertexConsumer.accept(target);
|
||||
});
|
||||
add(locateSourceButton);
|
||||
add(locateTargetButton);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/* ###
|
||||
* 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 Vertex");
|
||||
AbstractButton deselectButton = new JMenuItem("Deselect Vertex");
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -113,7 +113,6 @@ public class BlockGraphTask extends Task {
|
|||
private String actionName;
|
||||
private Program program;
|
||||
|
||||
|
||||
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode,
|
||||
boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection,
|
||||
ProgramLocation location, CodeBlockModel blockModel,
|
||||
|
@ -210,7 +209,6 @@ public class BlockGraphTask extends Task {
|
|||
return graph;
|
||||
}
|
||||
|
||||
|
||||
private CodeBlockIterator getBlockIterator() throws CancelledException {
|
||||
if (selection == null || selection.isEmpty()) {
|
||||
return blockModel.getCodeBlocks(taskMonitor);
|
||||
|
@ -218,7 +216,8 @@ public class BlockGraphTask extends Task {
|
|||
return blockModel.getCodeBlocksContaining(selection, taskMonitor);
|
||||
}
|
||||
|
||||
private Address graphBlock(AttributedGraph graph, CodeBlock curBB, List<AttributedVertex> entries)
|
||||
private Address graphBlock(AttributedGraph graph, CodeBlock curBB,
|
||||
List<AttributedVertex> entries)
|
||||
throws CancelledException {
|
||||
|
||||
Address[] startAddrs = curBB.getStartAddresses();
|
||||
|
@ -255,7 +254,6 @@ public class BlockGraphTask extends Task {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
protected AttributedVertex graphBasicBlock(AttributedGraph graph, CodeBlock curBB)
|
||||
throws CancelledException {
|
||||
|
||||
|
@ -297,7 +295,8 @@ public class BlockGraphTask extends Task {
|
|||
return fromVertex;
|
||||
}
|
||||
|
||||
private void setEdgeColor(AttributedEdge edge, AttributedVertex fromVertex, AttributedVertex toVertex) {
|
||||
private void setEdgeColor(AttributedEdge edge, AttributedVertex fromVertex,
|
||||
AttributedVertex toVertex) {
|
||||
// color the edge: first on the 'from' vertex, then try to 'to' vertex
|
||||
String fromColor = fromVertex.getAttribute("Color");
|
||||
String toColor = toVertex.getAttribute("Color");
|
||||
|
|
|
@ -17,14 +17,15 @@ package ghidra.graph.program;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
|
||||
import ghidra.app.util.AddEditDialog;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.block.*;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolTable;
|
||||
import ghidra.service.graph.GraphDisplay;
|
||||
import ghidra.service.graph.GraphDisplayListener;
|
||||
import ghidra.service.graph.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -39,6 +40,17 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList
|
|||
GraphDisplay display) {
|
||||
super(tool, blockModel.getProgram(), display);
|
||||
this.blockModel = blockModel;
|
||||
addActions(display);
|
||||
}
|
||||
|
||||
private void addActions(GraphDisplay display) {
|
||||
display.addAction(new ActionBuilder("Rename Vertex", "Block Graph")
|
||||
.popupMenuPath("Rename Vertex")
|
||||
.withContext(VertexGraphActionContext.class)
|
||||
// only enable action when vertex corresponds to an address
|
||||
.enabledWhen(c -> getAddress(c.getClickedVertex().getId()) != null)
|
||||
.onAction(this::updateVertexName)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -137,4 +149,24 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList
|
|||
return program.getMemory().contains(addr) || addr.isExternalAddress();
|
||||
}
|
||||
|
||||
private void updateVertexName(VertexGraphActionContext context) {
|
||||
String vertexId = context.getClickedVertex().getId();
|
||||
Address address = getAddressForVertexId(vertexId);
|
||||
Symbol symbol = program.getSymbolTable().getPrimarySymbol(address);
|
||||
|
||||
if (symbol == null) {
|
||||
AddEditDialog dialog = new AddEditDialog("Create Label", tool);
|
||||
dialog.addLabel(address, program, context.getComponentProvider());
|
||||
}
|
||||
else {
|
||||
AddEditDialog dialog = new AddEditDialog("Edit Label", tool);
|
||||
dialog.editLabel(symbol, program, context.getComponentProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
|
||||
return new BlockModelGraphDisplayListener(tool, blockModel, graphDisplay);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.graph.program;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import ghidra.service.graph.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -110,4 +111,20 @@ public class TestGraphDisplay implements GraphDisplay {
|
|||
public void selectionChanged(List<String> vertexIds) {
|
||||
listener.selectionChanged(vertexIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAction(DockingAction action) {
|
||||
// do nothing, actions are not supported by this display
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFocusedVertexId() {
|
||||
return currentFocusedVertex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSelectedVertexIds() {
|
||||
return new HashSet<String>(currentSelection);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -91,7 +91,8 @@ public class PopupActionManager implements PropertyChangeListener {
|
|||
popupMenu.show(c, e.getX(), e.getY());
|
||||
}
|
||||
|
||||
JPopupMenu createPopupMenu(Iterator<DockingActionIf> localActions, ActionContext context) {
|
||||
protected JPopupMenu createPopupMenu(Iterator<DockingActionIf> localActions,
|
||||
ActionContext context) {
|
||||
|
||||
if (localActions == null) {
|
||||
localActions = IteratorUtils.emptyIterator();
|
||||
|
|
|
@ -34,4 +34,9 @@ public class DummyGraphDisplayListener implements GraphDisplayListener {
|
|||
// I'm a dummy
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
|
||||
return new DummyGraphDisplayListener();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* ###
|
||||
* 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.service.graph;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
|
||||
/**
|
||||
* GraphActionContext for when user invokes a popup action on a graph edge.
|
||||
*/
|
||||
public class EdgeGraphActionContext extends GraphActionContext {
|
||||
|
||||
private AttributedEdge clickedEdge;
|
||||
|
||||
public EdgeGraphActionContext(ComponentProvider componentProvider,
|
||||
AttributedGraph graph, Set<AttributedVertex> selectedVertices,
|
||||
AttributedVertex locatedVertex, AttributedEdge clickedEdge) {
|
||||
|
||||
super(componentProvider, graph, selectedVertices, locatedVertex);
|
||||
this.clickedEdge = clickedEdge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edge from where the popup menu was launched
|
||||
* @return the edge from where the popup menu was launched
|
||||
*/
|
||||
public AttributedEdge getClickedEdge() {
|
||||
return clickedEdge;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* 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.service.graph;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
|
||||
/**
|
||||
* The base ActionContext for the GraphDisplay instances.
|
||||
*/
|
||||
public class GraphActionContext extends ActionContext {
|
||||
private final AttributedGraph graph;
|
||||
private final Set<AttributedVertex> selectedVertices;
|
||||
private final AttributedVertex focusedVertex;
|
||||
|
||||
public GraphActionContext(ComponentProvider componentProvider,
|
||||
AttributedGraph graph, Set<AttributedVertex> selectedVertices,
|
||||
AttributedVertex locatedVertex) {
|
||||
|
||||
super(componentProvider);
|
||||
this.graph = graph;
|
||||
this.selectedVertices = selectedVertices;
|
||||
this.focusedVertex = locatedVertex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the graph
|
||||
* @return the graph
|
||||
*/
|
||||
public AttributedGraph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of selectedVertices in the graph
|
||||
* @return the set of selectedVertices in the graph
|
||||
*/
|
||||
public Set<AttributedVertex> getSelectedVertices() {
|
||||
return selectedVertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the focused vertex (similar concept to the cursor in a text document)
|
||||
* @return the focused vertex
|
||||
*/
|
||||
public AttributedVertex getFocusedVertex() {
|
||||
return focusedVertex;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,9 @@
|
|||
package ghidra.service.graph;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -55,6 +57,12 @@ public interface GraphDisplay {
|
|||
*/
|
||||
public void setLocationFocus(String vertexID, EventTrigger eventTrigger);
|
||||
|
||||
/**
|
||||
* Returns the currently focused vertexID or null if no vertex is focussed.
|
||||
* @return the currently focused vertexID or null if no vertex is focussed.
|
||||
*/
|
||||
public String getFocusedVertexId();
|
||||
|
||||
/**
|
||||
* Tells the graph display window to select the vertices with the given ids
|
||||
*
|
||||
|
@ -68,6 +76,12 @@ public interface GraphDisplay {
|
|||
*/
|
||||
public void selectVertices(List<String> vertexList, EventTrigger eventTrigger);
|
||||
|
||||
/**
|
||||
* Returns a list of vertex ids for all the currently selected vertices
|
||||
* @return a list of vertex ids for all the currently selected vertices
|
||||
*/
|
||||
public Set<String> getSelectedVertexIds();
|
||||
|
||||
/**
|
||||
* Closes this graph display window.
|
||||
*/
|
||||
|
@ -127,4 +141,11 @@ public interface GraphDisplay {
|
|||
* @return the description of the current graph
|
||||
*/
|
||||
public String getGraphDescription();
|
||||
|
||||
/**
|
||||
* Adds the action to the graph display. Not all GraphDisplays support adding custom
|
||||
* actions, so this may have no effect.
|
||||
* @param action the action to add.
|
||||
*/
|
||||
public void addAction(DockingAction action);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package ghidra.service.graph;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for being notified when the user interacts with a visual graph display.
|
||||
* Interface for being notified when the user interacts with a visual graph display
|
||||
*/
|
||||
public interface GraphDisplayListener {
|
||||
/**
|
||||
|
@ -29,18 +29,24 @@ public interface GraphDisplayListener {
|
|||
/**
|
||||
* Notification that the list of selected vertices has changed
|
||||
*
|
||||
* @param vertexIds the list of vertex ids for the currently selected vertices.
|
||||
* @param vertexIds the list of vertex ids for the currently selected vertices
|
||||
*/
|
||||
public void selectionChanged(List<String> vertexIds);
|
||||
|
||||
/**
|
||||
* Notification that the "focused" (active) vertex has changed.
|
||||
* Notification that the "focused" (active) vertex has changed
|
||||
* @param vertexId the vertex id of the currently "focused" vertex
|
||||
*/
|
||||
public void locationFocusChanged(String vertexId);
|
||||
|
||||
default boolean updateVertexName(String vertexId, String oldName, String newName) {
|
||||
// no op
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Makes a new GraphDisplayListener of the same type as the specific
|
||||
* instance of this GraphDisplayListener
|
||||
*
|
||||
* @param graphDisplay the new {@link GraphDisplay} the new listener will support
|
||||
* @return A new instance of a GraphDisplayListener that is the same type as as the instance
|
||||
* on which it is called
|
||||
*/
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* ###
|
||||
* 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.service.graph;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
|
||||
/**
|
||||
* GraphActionContext for when user invokes a popup action on a graph vertex.
|
||||
*/
|
||||
public class VertexGraphActionContext extends GraphActionContext {
|
||||
|
||||
private AttributedVertex clickedVertex;
|
||||
|
||||
public VertexGraphActionContext(ComponentProvider componentProvider,
|
||||
AttributedGraph graph, Set<AttributedVertex> selectedVertices,
|
||||
AttributedVertex locatedVertex, AttributedVertex clickedVertex) {
|
||||
|
||||
super(componentProvider, graph, selectedVertices, locatedVertex);
|
||||
this.clickedVertex = clickedVertex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vertex from where the popup menu was launched
|
||||
* @return the vertex from where the popup menu was launched
|
||||
*/
|
||||
public AttributedVertex getClickedVertex() {
|
||||
return clickedVertex;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.EventTrigger;
|
||||
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
|
||||
import ghidra.app.services.GraphDisplayBroker;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.graph.visualization.DefaultGraphDisplayComponentProvider;
|
||||
import ghidra.service.graph.*;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private List<String> listenerCalls = new ArrayList<>();
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private AttributedGraph graph;
|
||||
private ComponentProvider graphComponentProvider;
|
||||
private GraphDisplay display;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.launchDefaultTool();
|
||||
tool.addPlugin(GraphDisplayBrokerPlugin.class.getName());
|
||||
graph = createGraph();
|
||||
|
||||
showGraph();
|
||||
|
||||
graphComponentProvider = tool.getComponentProvider("graph");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectVertexAction() {
|
||||
assertTrue(display.getSelectedVertexIds().isEmpty());
|
||||
|
||||
DockingActionIf action = getAction(tool, "Select Vertex");
|
||||
VertexGraphActionContext context =
|
||||
new VertexGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getVertex("B"));
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVertexIds = display.getSelectedVertexIds();
|
||||
assertEquals(1, selectedVertexIds.size());
|
||||
assertTrue(selectedVertexIds.contains(graph.getVertex("B").getId()));
|
||||
|
||||
// now try and select a second vertex
|
||||
context = new VertexGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getVertex("D"));
|
||||
performAction(action, context, true);
|
||||
selectedVertexIds = display.getSelectedVertexIds();
|
||||
assertEquals(2, selectedVertexIds.size());
|
||||
assertTrue(selectedVertexIds.contains(graph.getVertex("B").getId()));
|
||||
assertTrue(selectedVertexIds.contains(graph.getVertex("D").getId()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeSelectVertexAction() {
|
||||
display.selectVertices(Arrays.asList("A", "B", "C", "D"), EventTrigger.API_CALL);
|
||||
assertEquals(4, display.getSelectedVertexIds().size());
|
||||
|
||||
DockingActionIf action = getAction(tool, "Deselect Vertex");
|
||||
VertexGraphActionContext context =
|
||||
new VertexGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getVertex("B"));
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(3, selectedVerticeIds.size());
|
||||
assertTrue(selectedVerticeIds.contains(graph.getVertex("A").getId()));
|
||||
assertTrue(selectedVerticeIds.contains(graph.getVertex("D").getId()));
|
||||
assertTrue(selectedVerticeIds.contains(graph.getVertex("D").getId()));
|
||||
assertFalse(selectedVerticeIds.contains(graph.getVertex("B").getId()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectEdgeAction() {
|
||||
assertTrue(display.getSelectedVertexIds().isEmpty());
|
||||
|
||||
DockingActionIf action = getAction(tool, "Select Edge");
|
||||
EdgeGraphActionContext context =
|
||||
new EdgeGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(2, selectedVerticeIds.size());
|
||||
assertTrue(selectedVerticeIds.contains(graph.getVertex("A").getId()));
|
||||
assertTrue(selectedVerticeIds.contains(graph.getVertex("B").getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeSelectEdgeAction() {
|
||||
DockingActionIf action = getAction(tool, "Select Edge");
|
||||
EdgeGraphActionContext context =
|
||||
new EdgeGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(2, selectedVerticeIds.size());
|
||||
|
||||
action = getAction(tool, "Deselect Edge");
|
||||
|
||||
performAction(action, context, true);
|
||||
|
||||
selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(0, selectedVerticeIds.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectEdgeSource() {
|
||||
display.setLocationFocus("D", EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Edge Source");
|
||||
EdgeGraphActionContext context =
|
||||
new EdgeGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
|
||||
performAction(action, context, true);
|
||||
|
||||
assertEquals("A", display.getFocusedVertexId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectEdgeTarget() {
|
||||
display.setLocationFocus("D", EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Edge Target");
|
||||
EdgeGraphActionContext context =
|
||||
new EdgeGraphActionContext(graphComponentProvider, graph, null, null,
|
||||
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
|
||||
performAction(action, context, true);
|
||||
|
||||
assertEquals("B", display.getFocusedVertexId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvertSelection() {
|
||||
display.selectVertices(List.of("A", "C", "E"), EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Invert Selection");
|
||||
GraphActionContext context =
|
||||
new GraphActionContext(graphComponentProvider, graph, null, null);
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(3, selectedVerticeIds.size());
|
||||
assertTrue(selectedVerticeIds.contains("B"));
|
||||
assertTrue(selectedVerticeIds.contains("D"));
|
||||
assertTrue(selectedVerticeIds.contains("F"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrowSelectionOut() {
|
||||
display.selectVertices(List.of("A"), EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Grow Selection To Targets");
|
||||
GraphActionContext context =
|
||||
new GraphActionContext(graphComponentProvider, graph, null, null);
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(3, selectedVerticeIds.size());
|
||||
assertTrue(selectedVerticeIds.contains("A"));
|
||||
assertTrue(selectedVerticeIds.contains("B"));
|
||||
assertTrue(selectedVerticeIds.contains("C"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrowSelectionIn() {
|
||||
display.selectVertices(List.of("D"), EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Grow Selection From Sources");
|
||||
GraphActionContext context =
|
||||
new GraphActionContext(graphComponentProvider, graph, null, null);
|
||||
performAction(action, context, true);
|
||||
|
||||
Set<String> selectedVerticeIds = display.getSelectedVertexIds();
|
||||
assertEquals(3, selectedVerticeIds.size());
|
||||
assertTrue(selectedVerticeIds.contains("D"));
|
||||
assertTrue(selectedVerticeIds.contains("B"));
|
||||
assertTrue(selectedVerticeIds.contains("C"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSubGraph() {
|
||||
List<DefaultGraphDisplayComponentProvider> graphProviders = tool.getWindowManager()
|
||||
.getComponentProviders(DefaultGraphDisplayComponentProvider.class);
|
||||
|
||||
assertEquals(1, graphProviders.size());
|
||||
DefaultGraphDisplayComponentProvider original = graphProviders.get(0);
|
||||
|
||||
display.selectVertices(List.of("B", "C", "D"), EventTrigger.INTERNAL_ONLY);
|
||||
DockingActionIf action = getAction(tool, "Create Subgraph");
|
||||
GraphActionContext context =
|
||||
new GraphActionContext(graphComponentProvider, graph, null, null);
|
||||
performAction(action, context, true);
|
||||
|
||||
graphProviders = tool.getWindowManager()
|
||||
.getComponentProviders(DefaultGraphDisplayComponentProvider.class);
|
||||
|
||||
assertEquals(2, graphProviders.size());
|
||||
DefaultGraphDisplayComponentProvider newProvider = graphProviders.get(0);
|
||||
if (newProvider == original) {
|
||||
newProvider = graphProviders.get(1);
|
||||
}
|
||||
GraphActionContext actionContext = (GraphActionContext) newProvider.getActionContext(null);
|
||||
AttributedGraph newGraph = actionContext.getGraph();
|
||||
assertEquals(3, newGraph.getVertexCount());
|
||||
assertFalse(contains(newGraph, "A"));
|
||||
assertTrue(contains(newGraph, "B"));
|
||||
assertTrue(contains(newGraph, "C"));
|
||||
assertTrue(contains(newGraph, "D"));
|
||||
assertFalse(contains(newGraph, "E"));
|
||||
assertFalse(contains(newGraph, "F"));
|
||||
}
|
||||
|
||||
private boolean contains(AttributedGraph graph, String vertexId) {
|
||||
return graph.getVertex(vertexId) != null;
|
||||
}
|
||||
|
||||
private void showGraph() throws Exception {
|
||||
GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class);
|
||||
GraphDisplayProvider service = broker.getGraphDisplayProvider("Default Graph Display");
|
||||
display = service.getGraphDisplay(false, TaskMonitor.DUMMY);
|
||||
display.setGraph(graph, "test graph", false, TaskMonitor.DUMMY);
|
||||
display.setGraphDisplayListener(new TestGraphDisplayListener("test"));
|
||||
}
|
||||
|
||||
class TestGraphDisplayListener implements GraphDisplayListener {
|
||||
|
||||
private String name;
|
||||
|
||||
TestGraphDisplayListener(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void graphClosed() {
|
||||
listenerCalls.add(name + ": graph closed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectionChanged(List<String> vertexIds) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(name);
|
||||
buf.append(": selected: ");
|
||||
for (String id : vertexIds) {
|
||||
buf.append(id);
|
||||
buf.append(",");
|
||||
}
|
||||
listenerCalls.add(buf.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void locationFocusChanged(String vertexId) {
|
||||
listenerCalls.add(name + ": focus: " + vertexId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
|
||||
return new TestGraphDisplayListener("clone");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AttributedGraph createGraph() {
|
||||
AttributedGraph g = new AttributedGraph();
|
||||
AttributedVertex a = g.addVertex("A");
|
||||
AttributedVertex b = g.addVertex("B");
|
||||
AttributedVertex c = g.addVertex("C");
|
||||
AttributedVertex d = g.addVertex("D");
|
||||
AttributedVertex e = g.addVertex("E");
|
||||
AttributedVertex f = g.addVertex("F");
|
||||
|
||||
g.addEdge(a, b);
|
||||
g.addEdge(a, c);
|
||||
g.addEdge(b, d);
|
||||
g.addEdge(b, f);
|
||||
g.addEdge(c, d);
|
||||
g.addEdge(d, e);
|
||||
g.addEdge(e, f);
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue