GP-310 - Graphing - Updated mouse handling to be consistent with other graph widgets.

This commit is contained in:
ghidravore 2020-11-02 14:15:01 -05:00
parent 956e8ef342
commit d9a1c8906f
43 changed files with 1468 additions and 474 deletions

View file

@ -50,11 +50,9 @@
<tocroot>
<tocref id="Graphing">
<tocdef id="Graph Services" text="Graph Services">
<tocdef id="Default Graph Display" text="Default Graph Display" target="help/topics/GraphServices/GraphDisplay.htm" />
<tocdef id="Exporting a Graph" text="Exporting a Graph" target="help/topics/GraphServices/GraphExport.htm" />
</tocdef>
<tocref id="Graph Services">
<tocdef id="Default Graph Display" text="Default Graph Display" target="help/topics/GraphServices/GraphDisplay.htm" />
<tocdef id="Exporting a Graph" text="Exporting a Graph" target="help/topics/GraphServices/GraphExport.htm" />
</tocref>
</tocroot>

View file

@ -5,103 +5,245 @@
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Graphing</TITLE>
<TITLE>Graph Display</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<A name="Default_Graph_Display"/>
<A name="Default_Graph_Display">
<H1>Default Graph Display</H1>
<H2>Visualization of a Graph</H2>
<BLOCKQUOTE>
<P>The visualization display will show the graph in a new window or in a new tab of a previously created graph window.</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P align="left"><IMG src="images/DefaultGraphDisplay.png" border="1"></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Manipulating the Graph:</H2>
<ul>
<li>MouseButton1+drag will translate the display in the x and y axis</li>
<li>Mouse Wheel will zoom in and out</li>
<li>CTRL+Mouse Wheel will zoom in and out in the X-Axis only</li>
<li>ALT+Mouse Wheel will zoom in and out in the Y-Axis only</li>
<li>Ctrl+MouseButton1 will select a vertex or edge</li>
<ul>
<li>Shift+Ctrl+MouseButton1 over an unselected vertex will add that vertex to the selection</li>
<li>Shift+Ctrl+MouseButton1 over a previously selected vertex will remove that vertex from the selection</li>
</ul>
<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>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>
</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>
<BLOCKQUOTE>
<P>The visualization display will show the graph in a new window or in a new tab of a
previously created graph window.</P>
<ul>
<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><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>
<CENTER>
<TABLE border="0" width="100%">
<TR>
<TD width="100%" align="center"><IMG alt="" border="1" src=
"images/DefaultGraphDisplay.png"></TD>
</TR>
</TABLE>
</CENTER>
</BLOCKQUOTE>
<H2>Manipulating the Graph</H2>
<UL>
<LI>Dragging in the graph or on any unselected vertices will pan the graph (translate the
display in the x and y axis)</LI>
<LI>Dragging a selected vertex will reposition all selected vertices</LI>
<LI>Using the <CODE>Mouse Wheel</CODE> will zoom the graph in and out</LI>
<LI><CODE>Control+Mouse Wheel</CODE> will zoom the graph in and out on the X-Axis only</LI>
<LI><CODE>ALT+Mouse Wheel</CODE> will zoom the graph in and out in the Y-Axis only</LI>
<LI><CODE>Ctrl+Click</CODE> will select a vertex
<UL>
<LI><CODE>Ctrl+Click</CODE> over an unselected vertex will add that vertex to the
selection</LI>
<LI><CODE>Ctrl+Click</CODE> over a previously selected vertex will remove that vertex
from the selection</LI>
</UL>
</LI>
<LI><CODE>Ctrl+drag</CODE> on an empty area will create a rectangular area and select
enclosed vertices</LI>
</UL>
<H2>Toolbar Buttons</H2>
<P><A name="Scroll_To_Selection">
The <IMG alt="" src="images/locationIn.gif"> toggle button, when 'set' will cause a focused
vertex (the vertex with the red arrow) to be moved to the center of the view</P>
<P><A name="Free_Form_Selection">
The <IMG alt="" 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 alt="" src="images/network-wireless-16.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 alt="" 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 alt="" 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>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P><A name="Show_Filters">
The <IMG alt="" src="Icons.CONFIGURE_FILTER_ICON"> 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>
<P><A name="Arrangement">
The <IMG alt="" 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><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>
<LI style="list-style: none">
<UL>
<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>
<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="Clear_Selection">
<B>Clear Selection</B> - Clears all edge and vertex 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>
<LI><A name="Display_Popup_Windows">
<B>Display Popup Windows</B> - When toggled off no tooltip popups will be displayed.</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>
<P class="providedbyplugin">Provided By:&nbsp; <I>GraphDisplayBrokerPlugin</I></P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
</UL><BR>
<BR>
</BODY>
</HTML>
</HTML>

View file

@ -11,19 +11,22 @@
</HEAD>
<BODY lang="EN-US">
<A name="Default Graph Exporter"/>
<A NAME="Graph_Exporter"/>
<H1>Graph Export Service</H1>
<H2> Export Dialog </H2>
<P> Whenever a graph is generated and the graph output is set to <B>Graph Export</B>, then the
following graph export dialog is displayed: </P>
<BR>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P align="left"><IMG src="images/ExportDialog.png"></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<CENTER>
<TABLE border="0" width="100%">
<TR>
<TD width="100%" align="center"><IMG alt="" border="1" src=
"images/ExportDialog.png"></TD>
</TR>
</TABLE>
</CENTER>
<BR>
<BLOCKQUOTE>
<P>The Export Graph dialog offers a choice of the following graph formats:</P>
@ -49,5 +52,16 @@
<p>The <b>Ok</b> button will marshal the graph to the selected file in the selected format and close the dialog.</p>
<p>The <b>Cancel</b> button will close the dialog and perform no other action.</p>
<P class="providedbyplugin">Provided By:&nbsp; <I>GraphDisplayBrokerPlugin</I></P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
</UL>
<BR>
<BR>
</BODY>
</HTML>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Before After
Before After

View file

@ -73,6 +73,6 @@ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvide
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("GraphServices", "Default_Graph_Exporter");
return new HelpLocation("GraphServices", "Graph_Exporter");
}
}

View file

@ -74,7 +74,7 @@ public class GraphExporterDialog extends DialogComponentProvider {
addWorkPanel(buildWorkPanel());
addOKButton();
addCancelButton();
setHelpLocation(new HelpLocation("ExporterPlugin", "Exporter_Dialog"));
setHelpLocation(new HelpLocation("GraphServices", "Graph_Exporter"));
validate();
}

View file

@ -27,10 +27,10 @@ final class DefaultDisplayGraphIcons {
private DefaultDisplayGraphIcons() {
}
public static final Icon SATELLITE_VIEW_ICON = Icons.get("images/sat2.png");
public static final Icon SATELLITE_VIEW_ICON = Icons.get("images/network-wireless-16.png");
public static final Icon VIEW_MAGNIFIER_ICON = Icons.get("images/magnifier.png");
public static final Icon PROGRAM_GRAPH_ICON = Icons.get("images/redspheregraph.png");
public static final Icon LAYOUT_ALGORITHM_ICON = Icons.get("images/katomic.png");
public static final Icon LASSO_ICON = Icons.get("images/Lasso.png");
public static final Icon FILTER_ICON = Icons.get("images/filter_on.png");
public static final Icon FILTER_ICON = Icons.CONFIGURE_FILTER_ICON;
}

View file

@ -23,7 +23,6 @@ import java.awt.event.*;
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;
@ -64,6 +63,8 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.AttributeFilters;
import ghidra.graph.job.GraphJobRunner;
import ghidra.graph.viewer.popup.*;
import ghidra.graph.visualization.mouse.JgtPluggableGraphMouse;
import ghidra.graph.visualization.mouse.JgtUtils;
import ghidra.service.graph.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
@ -76,19 +77,18 @@ import resources.Icons;
*/
public class DefaultGraphDisplay implements GraphDisplay {
public static final String FAVORED_EDGE = "Fall-Through";
private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000);
public static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
public static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
private static final String ACTION_OWNER = "GraphServices";
Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName());
private static final String FAVORED_EDGE = "Fall-Through";
private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000);
private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName());
private GraphDisplayListener listener = new DummyGraphDisplayListener();
private String title;
/**
* the {@link Graph} to visualize
*/
private AttributedGraph graph;
/**
@ -97,91 +97,64 @@ public class DefaultGraphDisplay implements GraphDisplay {
private final int displayId;
/**
* the delegate viewer to display the ProgramGraph
* The delegate viewer to display the ProgramGraph
*/
private final VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
/**
* the {@link PluginTool}
* The {@link PluginTool}
*/
private final PluginTool pluginTool;
/**
* the "owner name" for action - mainly affects default help location
*/
private final String actionOwnerName = "GraphServices";
/**
* provides the component for the {@link GraphDisplay}
*/
private final DefaultGraphDisplayComponentProvider componentProvider;
/**
* whether to ensure the focused vertex is visible, scrolling if necessary
* Whether to ensure the focused vertex is visible, scrolling if necessary
* the visualization in order to center the selected vertex
* or the center of the set of selected vertices
*/
private boolean ensureVertexIsVisible = false;
/**
* allows selection of various {@link LayoutAlgorithm} ('arrangements')
* Allows selection of various {@link LayoutAlgorithm} ('arrangements')
*/
private final LayoutTransitionManager layoutTransitionManager;
/**
* provides graph displays for supplied graphs
* Provides graph displays for supplied graphs
*/
private final DefaultGraphDisplayProvider graphDisplayProvider;
/**
* the vertex that has been nominated to be 'focused' in the graph display and listing
*/
private AttributedVertex focusedVertex;
/**
* Runs animation jobs for updating the display
*/
private final GraphJobRunner jobRunner = new GraphJobRunner();
/**
* a satellite view that shows in the lower left corner as a birds-eye view of the graph display
*/
private final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer;
/**
* generated filters on edges
*/
private AttributeFilters edgeFilters;
/**
* generated filters on vertices
*/
private AttributeFilters vertexFilters;
/**
* a dialog populated with generated vertex/edge filters
*/
private FilterDialog filterDialog;
/**
* holds the vertex icons (instead of recomputing them)
*/
private AttributeFilters edgeFilters;
private AttributeFilters vertexFilters;
private GhidraIconCache iconCache;
/**
* multi-selection is done in a free-form traced shape instead of a rectangle
* Multi-selection is done in a free-form traced shape instead of a rectangle
*/
private boolean freeFormSelection;
/**
* Handles the popup
* Handles all mouse interaction
*/
private GhidraGraphMouse graphMouse;
private JgtPluggableGraphMouse 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 -> {
AttributedGraph attributedGraph = new AttributedGraph();
g.vertexSet().forEach(attributedGraph::addVertex);
g.edgeSet().forEach(e -> {
AttributedVertex source = g.getEdgeSource(e);
AttributedVertex target = g.getEdgeTarget(e);
attributedGraph.addEdge(source, target, e);
});
displaySubGraph(attributedGraph);
};
private ToggleDockingAction hideSelectedAction;
private ToggleDockingAction hideUnselectedAction;
private SwitchableSelectionItemListener switchableSelectionListener;
@ -226,7 +199,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.builder(viewer.getRenderContext().getVertexBoundsFunction())
.build());
graphMouse = new GhidraGraphMouse(componentProvider, viewer);
graphMouse = new JgtPluggableGraphMouse(this);
createToolbarActions();
createPopupActions();
@ -302,7 +275,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
private void createToolbarActions() {
// create a toggle for 'scroll to selected vertex'
new ToggleActionBuilder("Scroll To Selection", actionOwnerName)
new ToggleActionBuilder("Scroll To Selection", ACTION_OWNER)
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.description("Ensure that the 'focused' vertex is visible")
.selected(true)
@ -314,7 +287,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", actionOwnerName)
new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER)
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)")
.selected(false)
@ -323,14 +296,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
.buildAndInstallLocal(componentProvider);
// create an icon button to display the satellite view
new ToggleActionBuilder("SatelliteView", actionOwnerName).description("Show Satellite View")
new ToggleActionBuilder("SatelliteView", ACTION_OWNER).description("Show Satellite View")
.toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON)
.onAction(this::toggleSatellite)
.selected(graphDisplayProvider.getDefaultSatelliteState())
.buildAndInstallLocal(componentProvider);
// create an icon button to reset the view transformations to identity (scaled to layout)
new ActionBuilder("Reset View", actionOwnerName)
new ActionBuilder("Reset View", ACTION_OWNER)
.description("Reset all view transforms to center graph in display")
.toolBarIcon(Icons.REFRESH_ICON)
.onAction(context -> viewer.scaleToLayout())
@ -338,7 +311,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", actionOwnerName)
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER)
.description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
.onAction(context -> magnifyViewSupport.activate(
@ -349,90 +322,91 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.addLocalAction(lensToggle);
// create an action button to show a dialog with generated filters
new ActionBuilder("Show Filters", actionOwnerName).description("Show Graph Filters")
new ActionBuilder("Show Filters", ACTION_OWNER).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", actionOwnerName)
.description("Select Layout Arrangement Algorithm")
List<ActionState<String>> layoutActionStates = getLayoutActionStates();
new MultiStateActionBuilder<String>("Arrangement", ACTION_OWNER)
.description("Arrangement: " + layoutActionStates.get(0).getName())
.toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON)
.fireFirstAction(false)
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
.addStates(getLayoutActionStates())
.addStates(layoutActionStates)
.buildAndInstallLocal(componentProvider);
}
private void createPopupActions() {
new ActionBuilder("Select Vertex", actionOwnerName)
new ActionBuilder("Select Vertex", ACTION_OWNER)
.popupMenuPath("Select Vertex")
.popupMenuGroup("selection", "1")
.withContext(VertexGraphActionContext.class)
.enabledWhen(c -> !viewer.getSelectedVertexState().isSelected(c.getClickedVertex()))
.enabledWhen(c -> !isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Deselect Vertex", actionOwnerName)
new ActionBuilder("Deselect Vertex", ACTION_OWNER)
.popupMenuPath("Deselect Vertex")
.popupMenuGroup("selection", "2")
.withContext(VertexGraphActionContext.class)
.enabledWhen(c -> viewer.getSelectedVertexState().isSelected(c.getClickedVertex()))
.enabledWhen(c -> isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Select Edge", actionOwnerName)
new ActionBuilder("Select Edge", ACTION_OWNER)
.popupMenuPath("Select Edge")
.popupMenuGroup("selection", "1")
.withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> !viewer.getSelectedEdgeState().isSelected(c.getClickedEdge()))
.enabledWhen(c -> !isSelected(c.getClickedEdge()))
.onAction(c -> selectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Deselect Edge", actionOwnerName)
new ActionBuilder("Deselect Edge", ACTION_OWNER)
.popupMenuPath("Deselect Edge")
.popupMenuGroup("selection", "2")
.withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> viewer.getSelectedEdgeState().isSelected(c.getClickedEdge()))
.enabledWhen(c -> isSelected(c.getClickedEdge()))
.onAction(c -> deselectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Edge Source", actionOwnerName)
new ActionBuilder("Edge Source", ACTION_OWNER)
.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)
new ActionBuilder("Edge Target", ACTION_OWNER)
.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)
hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER)
.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)
hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", ACTION_OWNER)
.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)
new ActionBuilder("Invert Selection", ACTION_OWNER)
.popupMenuPath("Invert Selection")
.popupMenuGroup("z", "3")
.description("Inverts the current selection")
.onAction(c -> invertSelection())
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Grow Selection To Targets", actionOwnerName)
new ActionBuilder("Grow Selection To Targets", ACTION_OWNER)
.popupMenuPath("Grow Selection To Targets")
.popupMenuGroup("z", "4")
.description("Extends the current selection by including the target vertex " +
@ -442,7 +416,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> growSelection(getTargetVerticesFromSelected()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Grow Selection From Sources", actionOwnerName)
new ActionBuilder("Grow Selection From Sources", ACTION_OWNER)
.popupMenuPath("Grow Selection From Sources")
.popupMenuGroup("z", "4")
.description("Extends the current selection by including the target vertex " +
@ -452,15 +426,23 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> growSelection(getSourceVerticesFromSelected()))
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Create Subgraph", actionOwnerName)
.popupMenuPath("Display Selected as New Graph")
new ActionBuilder("Clear Selection", ACTION_OWNER)
.popupMenuPath("Clear Selection")
.popupMenuGroup("z", "5")
.keyBinding("escape")
.enabledWhen(c -> hasSelection())
.onAction(c -> clearSelection())
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Create Subgraph", ACTION_OWNER)
.popupMenuPath("Display Selected as New Graph")
.popupMenuGroup("zz", "5")
.description("Creates a subgraph from the selected nodes")
.enabledWhen(c -> !viewer.getSelectedVertexState().getSelected().isEmpty())
.onAction(c -> createAndDisplaySubGraph())
.buildAndInstallLocal(componentProvider);
togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", actionOwnerName)
togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", ACTION_OWNER)
.popupMenuPath("Display Popup Windows")
.popupMenuGroup("zz", "1")
.description("Toggles whether or not to show popup windows, such as tool tips")
@ -471,6 +453,24 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private void clearSelection() {
viewer.getSelectedVertexState().clear();
viewer.getSelectedEdgeState().clear();
}
private boolean hasSelection() {
return !(viewer.getSelectedVertexState().getSelected().isEmpty() &&
viewer.getSelectedEdgeState().getSelected().isEmpty());
}
private boolean isSelected(AttributedVertex v) {
return viewer.getSelectedVertexState().isSelected(v);
}
private boolean isSelected(AttributedEdge e) {
return viewer.getSelectedEdgeState().isSelected(e);
}
private void createAndDisplaySubGraph() {
GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
try {
@ -556,7 +556,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
for (String layoutName : names) {
ActionState<String> state = new ActionState<>(layoutName,
DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName);
state.setHelpLocation(new HelpLocation(actionOwnerName, layoutName));
state.setHelpLocation(new HelpLocation(ACTION_OWNER, layoutName));
actionStates.add(state);
}
return actionStates;
@ -603,24 +603,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.repaint();
}
private void displaySubGraph(Graph<AttributedVertex, AttributedEdge> subGraph) {
try {
GraphDisplay graphDisplay =
graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
graphDisplay.setGraph((AttributedGraph) subGraph, "SubGraph", false, TaskMonitor.DUMMY);
graphDisplay.setGraphDisplayListener(listener);
}
catch (CancelledException e) {
// can't happen while using a dummy monitor
}
}
/**
* create a SatelliteViewer for the Visualization
* @param parentViewer the main visualization 'parent' of the satellite view
* @return a new SatelliteVisualizationViewer
*/
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> createSatelliteViewer(
VisualizationViewer<AttributedVertex, AttributedEdge> parentViewer) {
Dimension viewerSize = parentViewer.getSize();
@ -649,9 +631,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
return satellite;
}
/**
* close this graph display
*/
@Override
public void close() {
graphDisplayProvider.remove(this);
@ -662,10 +641,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.closeComponent();
}
/**
* accept a {@code GraphDisplayListener}
* @param listener the listener to be notified
*/
@Override
public void setGraphDisplayListener(GraphDisplayListener listener) {
if (this.listener != null) {
@ -697,7 +672,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.getSelectedVertexState().addItemListener(switchableSelectionListener);
}
protected void setFocusedVertex(AttributedVertex vertex) {
public void setFocusedVertex(AttributedVertex vertex) {
setFocusedVertex(vertex, EventTrigger.API_CALL);
}
@ -1008,8 +983,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
return new Point2D.Double(p.x, p.y);
}
// they did not pick a vertex to center, so
// just center the graph
// they did not pick a vertex to center, so just center the graph
Point2D center = viewer.getCenter();
Point p = Point.of(center.getX(), center.getY());
return new Point2D.Double(p.x, p.y);
@ -1211,13 +1185,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
public ActionContext getActionContext(MouseEvent e) {
AttributedVertex pickedVertex = graphMouse.getPickedVertex(e);
AttributedVertex pickedVertex = JgtUtils.getVertex(e, viewer);
if (pickedVertex != null) {
return new VertexGraphActionContext(componentProvider, graph, getSelectedVertices(),
focusedVertex, pickedVertex);
}
AttributedEdge pickedEdge = graphMouse.getPickedEdge(e);
AttributedEdge pickedEdge = JgtUtils.getEdge(e, viewer);
if (pickedEdge != null) {
return new EdgeGraphActionContext(componentProvider, graph, getSelectedVertices(),
focusedVertex, pickedEdge);

View file

@ -0,0 +1,311 @@
/* ###
* 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.mouse;
import java.awt.Cursor;
import java.awt.event.*;
import org.jungrapht.visualization.SatelliteVisualizationViewer;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.control.AbstractGraphMousePlugin;
import org.jungrapht.visualization.selection.MutableSelectedState;
/**
* Graph mouse plugin base class.
*
* Usage Notes:
* <ul>
* <li>We clear state on mouseReleased() and mouseExited(), since we will get
* at least one of those calls</li>
* </ul>
* @param <V> the vertex type
* @param <E> the edge type
*/
//@formatter:off
public abstract class AbstractJgtGraphMousePlugin<V, E>
extends AbstractGraphMousePlugin
implements MouseListener, MouseMotionListener {
//@formatter:on
protected boolean isHandlingMouseEvents;
protected V selectedVertex;
protected E selectedEdge;
public AbstractJgtGraphMousePlugin() {
this(InputEvent.BUTTON1_DOWN_MASK);
}
public AbstractJgtGraphMousePlugin(int selectionModifiers) {
super(selectionModifiers);
}
public VisualizationViewer<V, E> getViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = getGraphViewer(e);
return viewer;
}
/**
* Returns the <b>primary/master</b> graph viewer.
*
* @param e the mouse event from which to get the viewer
* @return the viewer
*/
@SuppressWarnings("unchecked")
public VisualizationViewer<V, E> getGraphViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
// is this the satellite viewer?
if (viewer instanceof SatelliteVisualizationViewer) {
return ((SatelliteVisualizationViewer<V, E>) viewer).getMaster();
}
return viewer;
}
/**
* Returns the satellite graph viewer. This assumes that the mouse event originated from
* the satellite viewer.
*
* @param e the mouse event from which to get the viewer
* @return the viewer
*/
@SuppressWarnings("unchecked")
public SatelliteVisualizationViewer<V, E> getSatelliteGraphViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
// is this the satellite viewer?
if (viewer instanceof SatelliteVisualizationViewer) {
return (SatelliteVisualizationViewer<V, E>) viewer;
}
throw new IllegalStateException("Do not have a satellite GraphViewer");
}
/**
* Signals to perform any cleanup when this plugin is going away
*/
public void dispose() {
// stub
}
/**
* Checks the given mouse event to see if it is a valid event for selecting a vertex at the
* mouse location. If so, then the vertex is selected in this mouse handler and the event
* is consumed.
* @param e the event
* @return true if a vertex was selected
*/
protected boolean checkForVertex(MouseEvent e) {
if (!checkModifiers(e)) {
selectedVertex = null;
return false;
}
VisualizationViewer<V, E> vv = getViewer(e);
selectedVertex = JgtUtils.getVertex(e, vv);
if (selectedVertex == null) {
return false;
}
e.consume();
return true;
}
/**
* Checks the given mouse event to see if it is a valid event for selecting an edge at the
* mouse location. If so, then the edge is selected in this mouse handler and the event
* is consumed.
* @param e the event
* @return true if an edge was selected
*/
protected boolean checkForEdge(MouseEvent e) {
if (!checkModifiers(e) || isOverVertex(e)) {
selectedEdge = null;
return false;
}
VisualizationViewer<V, E> vv = getViewer(e);
selectedEdge = JgtUtils.getEdge(e, vv);
if (selectedEdge == null) {
return false;
}
e.consume();
isHandlingMouseEvents = true;
return true;
}
/**
* Selects the given vertex
* @param vertex the vertex
* @param viewer the graph viewer
* @return true if the vertex is selected
*/
protected boolean selectVertex(V vertex, VisualizationViewer<V, E> viewer) {
MutableSelectedState<V> selectedVertexState = viewer.getSelectedVertexState();
if (selectedVertexState == null) {
return false;
}
selectedVertexState.isSelected(vertex);
if (selectedVertexState.isSelected(vertex) == false) {
selectedVertexState.clear();
selectedVertexState.select(vertex, true);
}
return true;
}
/**
* Selects the given edge
* @param edge the edge
* @param viewer the graph viewer
* @return true if the edge is selected
*/
protected boolean selectEdge(E edge, VisualizationViewer<V, E> viewer) {
MutableSelectedState<E> selectedVertexState = viewer.getSelectedEdgeState();
if (selectedVertexState == null) {
return false;
}
selectedVertexState.isSelected(edge);
if (selectedVertexState.isSelected(edge) == false) {
selectedVertexState.clear();
selectedVertexState.select(edge, true);
}
return true;
}
/**
* Returns true if the location of the mouse event is over a vertex
* @param e the event
* @return true if the location of the mouse event is over a vertex
*/
protected boolean isOverVertex(MouseEvent e) {
return getVertex(e) != null;
}
/**
* Returns the vertex if the mouse event is over a vertex
* @param e the event
* @return a vertex or null
*/
protected V getVertex(MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
return JgtUtils.getVertex(e, viewer);
}
/**
* Returns true if the location of the mouse event is over a edge
* @param e the event
* @return true if the location of the mouse event is over a edge
*/
protected boolean isOverEdge(MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
E edge = JgtUtils.getEdge(e, viewer);
if (edge == null) {
return false;
}
return !isOverVertex(e);
}
protected void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
viewer.setCursor(newCursor);
}
protected boolean shouldShowCursor(MouseEvent e) {
return isOverVertex(e); // to showing cursor over vertices
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
// override this method to do stuff
}
@Override
public void mouseClicked(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
e.consume();
resetState();
}
protected void resetState() {
isHandlingMouseEvents = false;
selectedVertex = null;
selectedEdge = null;
}
@Override
public void mouseDragged(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
e.consume();
resetState();
}
@Override
public void mouseMoved(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
// only "turn on" the cursor; resetting is handled elsewhere (in the mouse driver)
if (shouldShowCursor(e)) {
installCursor(cursor, e);
e.consume();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
if (shouldShowCursor(e)) {
installCursor(cursor, e);
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (shouldShowCursor(e)) {
installCursor(cursor, e);
e.consume();
}
}
@Override
public void mouseExited(MouseEvent e) {
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
}
}

View file

@ -0,0 +1,55 @@
/* ###
* 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.mouse;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.control.AbstractGraphMousePlugin;
/**
* Restores the cursor after other graph mouse operations.
*
* Future: this is copied from the Visual Graph counterpart--consolidate these
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JgtCursorRestoringPlugin<V, E> extends AbstractGraphMousePlugin
implements MouseMotionListener {
public JgtCursorRestoringPlugin() {
super(0);
}
@Override
public void mouseDragged(MouseEvent e) {
// don't care
}
@Override
public void mouseMoved(MouseEvent e) {
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
}
@SuppressWarnings("unchecked")
private void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
viewer.setCursor(newCursor);
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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.mouse;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import org.jgrapht.Graph;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.selection.MutableSelectedState;
import ghidra.graph.visualization.CenterAnimationJob;
/**
* Mouse plugin to allow for edge navigation
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JgtEdgeNavigationPlugin<V, E> extends AbstractJgtGraphMousePlugin<V, E> {
public JgtEdgeNavigationPlugin() {
this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
if (e.getClickCount() != 2) {
return;
}
checkForEdge(e); // this will select an edge if we can and store off the edge
}
@Override
public void mouseClicked(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
E edge = selectedEdge; // save off before we reset
e.consume();
resetState();
// on double-clicks we go to the vertex in the current edge direction unless that vertex
// is already selected, then we go to the other vertex
VisualizationViewer<V, E> viewer = getViewer(e);
MutableSelectedState<V> selectedState = viewer.getSelectedVertexState();
Graph<V, E> graph = viewer.getVisualizationModel().getGraph();
V end = graph.getEdgeTarget(edge);
if (!selectedState.isSelected(end)) {
pickAndShowVertex(end, selectedState, viewer);
return;
}
// the destination was picked, go the other direction
V source = graph.getEdgeSource(edge);
pickAndShowVertex(source, selectedState, viewer);
}
private void pickAndShowVertex(V vertex, MutableSelectedState<V> selectedVertexState,
VisualizationViewer<V, E> viewer) {
// TODO animate; this requires a single view updater
Point2D existingCenter = viewer.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(viewer.getCenter());
Point vp = viewer.getVisualizationModel().getLayoutModel().get(vertex);
Point2D newCenter = new Point2D.Double(vp.x, vp.y);
CenterAnimationJob job = new CenterAnimationJob(viewer, existingCenter, newCenter);
job.finished();
selectedVertexState.clear();
selectedVertexState.select(vertex);
/*
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
updater.moveVertexToCenterWithAnimation(vertex, isBusy -> {
// pick the vertex after the animation has run
if (!isBusy) {
GPickedState<V> pickedStateWrapper = (GPickedState<V>) selectedVertexState;
pickedStateWrapper.pickToActivate(vertex);
}
});
*/
}
@Override
protected boolean shouldShowCursor(MouseEvent e) {
return isOverEdge(e);
}
}

View file

@ -0,0 +1,70 @@
/* ###
* 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.mouse;
import java.awt.event.InputEvent;
import org.jungrapht.visualization.control.*;
import docking.DockingUtils;
import ghidra.graph.visualization.DefaultGraphDisplay;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
/**
* Pluggable graph mouse for jungrapht
*/
public class JgtPluggableGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> {
private DefaultGraphDisplay graphDisplay;
// TODO we should not need the graph display for any mouse plugins, but the API is net yet
// robust enough to communicate fully without it
public JgtPluggableGraphMouse(DefaultGraphDisplay graphDisplay) {
super(DefaultGraphMouse.<AttributedVertex, AttributedEdge> builder());
this.graphDisplay = graphDisplay;
}
@Override
public void loadPlugins() {
//
// Note: the order of these additions matters, as an event will flow to each plugin until
// it is handled.
//
// edge
add(new JgtEdgeNavigationPlugin<AttributedVertex, AttributedEdge>());
add(new JgtVertexFocusingPlugin<AttributedVertex, AttributedEdge>(graphDisplay));
// scaling
add(new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out));
// the grab/pan feature
add(new JgtTranslatingPlugin<AttributedVertex, AttributedEdge>());
add(new SelectingGraphMousePlugin<AttributedVertex, AttributedEdge>(
InputEvent.BUTTON1_DOWN_MASK,
0,
DockingUtils.CONTROL_KEY_MODIFIER_MASK));
// cursor cleanup
add(new JgtCursorRestoringPlugin<AttributedVertex, AttributedEdge>());
setPluginsLoaded();
}
}

View file

@ -0,0 +1,177 @@
/* ###
* 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.mouse;
import java.awt.Cursor;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import org.jungrapht.visualization.*;
import org.jungrapht.visualization.MultiLayerTransformer.Layer;
import org.jungrapht.visualization.control.TranslatingGraphMousePlugin;
import org.jungrapht.visualization.transform.MutableTransformer;
/**
* Note: this class is based on {@link TranslatingGraphMousePlugin}.
* <p>
* TranslatingGraphMousePlugin uses a MouseButtonOne press and drag gesture to translate
* the graph display in the x and y direction. The default MouseButtonOne modifier can be overridden
* to cause a different mouse gesture to translate the display.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JgtTranslatingPlugin<V, E>
extends AbstractJgtGraphMousePlugin<V, E> {
private boolean panning;
private boolean isHandlingEvent;
public JgtTranslatingPlugin() {
this(InputEvent.BUTTON1_DOWN_MASK);
}
public JgtTranslatingPlugin(int modifiers) {
super(modifiers);
this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
}
@Override
public void mousePressed(MouseEvent e) {
boolean accepted = checkModifiers(e) && isInDraggingArea(e);
if (!accepted) {
return;
}
down = e.getPoint();
VisualizationViewer<V, E> viewer = getGraphViewer(e);
viewer.setCursor(cursor);
isHandlingEvent = true;
e.consume();
}
@Override
public void mouseReleased(MouseEvent e) {
boolean wasHandlingEvent = isHandlingEvent;
isHandlingEvent = false;
down = null;
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
// NOTE: we are only consuming the event here if we actually did pan...this allows follow-on
// mouse handlers to process the mouseReleased() event. This is a bit odd and not the
// normal event processing (which is to consume all related events).
if (wasHandlingEvent && panning) {
e.consume();
}
panning = false;
}
@Override
public void mouseDragged(MouseEvent e) {
boolean accepted = checkModifiers(e);
if (!accepted) {
return;
}
if (!isHandlingEvent) {
return;
}
panning = true;
VisualizationViewer<V, E> viewer = getGraphViewer(e);
RenderContext<V, E> context = viewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = context.getMultiLayerTransformer();
MutableTransformer layoutTransformer = multiLayerTransformer.getTransformer(Layer.LAYOUT);
viewer.setCursor(cursor);
Point2D downPoint = multiLayerTransformer.inverseTransform(down);
Point2D p = multiLayerTransformer.inverseTransform(e.getPoint());
float dx = (float) (p.getX() - downPoint.getX());
float dy = (float) (p.getY() - downPoint.getY());
layoutTransformer.translate(dx, dy);
down.x = e.getX();
down.y = e.getY();
e.consume();
viewer.repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
// don't care
}
@Override
public void mouseEntered(MouseEvent e) {
if (isHandlingEvent) {
return;
}
if (!isInDraggingArea(e)) {
return;
}
if (!checkModifiersForCursor(e)) {
return;
}
installCursor(cursor, e);
}
@Override
public void mouseExited(MouseEvent e) {
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
}
@Override
public void mouseMoved(MouseEvent e) {
if (!checkModifiersForCursor(e)) {
return;
}
if (isHandlingEvent) {
e.consume();
}
if (isInDraggingArea(e)) {
installCursor(cursor, e);
e.consume();
}
}
private boolean checkModifiersForCursor(MouseEvent e) {
if (e.getModifiersEx() == 0) {
return true;
}
return false;
}
//==================================================================================================
// Private methods
//==================================================================================================
private boolean isInDraggingArea(MouseEvent e) {
return !(isOverVertex(e) || isOverEdge(e));
}
@Override
public void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
viewer.setCursor(newCursor);
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
package ghidra.graph.visualization.mouse;
import static org.jungrapht.visualization.VisualizationServer.*;
@ -22,42 +22,86 @@ import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.control.*;
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 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)
*
* Keeper of shared logic for jungrapht handling
*/
public class GhidraGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> {
public class JgtUtils {
private static final String PICK_AREA_SIZE_PROPERTY = PREFIX + "pickAreaSize";
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
private int pickSize;
/**
* create an instance with default values
* @param componentProvider the graph component provider
* @param viewer the graph viewer component
* Returns the edge under the given mouse event
*
* @param <V> the vertex type
* @param <E> the edge type
* @param e the event
* @param viewer the graph viewer
* @return the edge
*/
GhidraGraphMouse(ComponentProvider componentProvider,
VisualizationViewer<AttributedVertex, AttributedEdge> viewer) {
public static <V, E> E getEdge(MouseEvent e, VisualizationViewer<V, E> viewer) {
if (e == null) {
return null;
}
super(DefaultGraphMouse.<AttributedVertex, AttributedEdge> builder());
this.viewer = viewer;
pickSize = Integer.getInteger(GhidraGraphMouse.PICK_AREA_SIZE_PROPERTY, 4);
Rectangle2D footprintRectangle = getFootprint(e);
LayoutModel<V> layoutModel = viewer.getVisualizationModel().getLayoutModel();
GraphElementAccessor<V, E> pickSupport = viewer.getPickSupport();
if (pickSupport == null) {
return null;
}
if (pickSupport instanceof ShapePickSupport) {
ShapePickSupport<V, E> shapePickSupport =
(ShapePickSupport<V, E>) pickSupport;
return shapePickSupport.getEdge(layoutModel, footprintRectangle);
}
TransformSupport<V, E> transformSupport =
viewer.getTransformSupport();
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
return pickSupport.getEdge(layoutModel, layoutPoint.getX(), layoutPoint.getY());
}
private Rectangle2D getFootprint(MouseEvent e) {
/**
* Returns the vertex under the given mouse event
*
* @param <V> the vertex type
* @param <E> the edge type
* @param e the event
* @param viewer the graph viewer
* @return the vertex
*/
public static <V, E> V getVertex(MouseEvent e, VisualizationViewer<V, E> viewer) {
if (e == null) {
return null;
}
Rectangle2D footprintRectangle = getFootprint(e);
LayoutModel<V> layoutModel = viewer.getVisualizationModel().getLayoutModel();
GraphElementAccessor<V, E> pickSupport = viewer.getPickSupport();
if (pickSupport == null) {
return null;
}
if (pickSupport instanceof ShapePickSupport) {
ShapePickSupport<V, E> shapePickSupport =
(ShapePickSupport<V, E>) pickSupport;
return shapePickSupport.getVertex(layoutModel, footprintRectangle);
}
TransformSupport<V, E> transformSupport =
viewer.getTransformSupport();
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
return pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY());
}
private static Rectangle2D getFootprint(MouseEvent e) {
int pickSize = Integer.getInteger(PICK_AREA_SIZE_PROPERTY, 4);
return new Rectangle2D.Float(
e.getPoint().x - pickSize / 2f,
e.getPoint().y - pickSize / 2f,
@ -65,44 +109,4 @@ public class GhidraGraphMouse extends DefaultGraphMouse<AttributedVertex, Attrib
pickSize);
}
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<AttributedVertex, AttributedEdge> shapePickSupport =
(ShapePickSupport<AttributedVertex, AttributedEdge>) pickSupport;
return shapePickSupport.getEdge(layoutModel, footprintRectangle);
}
TransformSupport<AttributedVertex, AttributedEdge> transformSupport =
viewer.getTransformSupport();
Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint());
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());
}
}

View file

@ -0,0 +1,57 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization.mouse;
import java.awt.event.MouseEvent;
import ghidra.graph.visualization.DefaultGraphDisplay;
import ghidra.service.graph.AttributedVertex;
/**
* A mouse plugin to focus a vertex when clicked
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JgtVertexFocusingPlugin<V, E> extends AbstractJgtGraphMousePlugin<V, E> {
private DefaultGraphDisplay graphDisplay;
public JgtVertexFocusingPlugin(DefaultGraphDisplay graphDisplay) {
this.graphDisplay = graphDisplay;
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
selectedVertex = getVertex(e);
}
@Override
public void mouseClicked(MouseEvent e) {
if (selectedVertex == null) {
return;
}
graphDisplay.setFocusedVertex((AttributedVertex) selectedVertex);
// Note: do not consume the event. We will just focus our vertex, regardless of further
// mouse event processing.
}
}

View file

@ -62,3 +62,10 @@ jungrapht.initialDimensionVertexDensity=0.3f
jungrapht.minScale=0.001
jungrapht.maxScale=1.0
11:45 AM
# not using spatial data structures for edges (fixed in versions after 1.0)
jungrapht.edgeSpatialSupport=NONE
jungrapht.vertexSpatialSupport=NONE