mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch 'origin/GP-773_ghidravore_graph_visualization_options--SQUASHED'
This commit is contained in:
commit
69e8119211
84 changed files with 4102 additions and 1822 deletions
|
@ -248,6 +248,41 @@
|
|||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Graph_Type_Display_Options">Graph Type Display Options</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Graphs have a graph type which defines vertex types and edge types. Users can
|
||||
configure the display properties for each vertex and edge type. These options have the
|
||||
following subsections:</P>
|
||||
|
||||
<H3>Edge Colors</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows setting the color for each edge type. Each Edge type will be listed with its
|
||||
current color.</P>
|
||||
</BLOCKQUOTE>
|
||||
<H3>Miscellaneous</H3>
|
||||
<UL>
|
||||
<LI>Default Vertex Color - color for vertices with no defined vertex type</LI>
|
||||
<LI>Default Vertex Shape - shape for vertices with no defined vertex type</LI>
|
||||
<LI>Default Edge Color - color for edges with no defined edge type</LI>
|
||||
<LI>Favored Edge - edge type to be favored by graph layout algorithms</LI>
|
||||
</UL>
|
||||
|
||||
<H3>Vertex Colors</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows setting the color for each vertex type. Each vertex type will be listed with
|
||||
its current color.</P>
|
||||
</BLOCKQUOTE>
|
||||
<H3>Vertex Shapes</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows setting the shape for each vertex type. Each vertex type will be listed with a
|
||||
combo box for picking a supported shape. Supported shapes include Ellipse, Rectangle
|
||||
Diamond, TriangleUp, TriangleDown, Star, Pentagon, Hexagon, and Octagon.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
|
||||
<P class="providedbyplugin">Provided By: <I>GraphDisplayBrokerPlugin</I></P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
|
|
@ -155,7 +155,7 @@ public class AttributeFilters implements ItemSelectable {
|
|||
|
||||
// count up the unique attribute values (skipping the 'precluded names' we know we don't want)
|
||||
for (Attributed element : elements) {
|
||||
Map<String, String> attributeMap = new HashMap<>(element.getAttributeMap());
|
||||
Map<String, String> attributeMap = new HashMap<>(element.getAttributes());
|
||||
for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
|
||||
if (!precludedNames.contains(entry.getKey())) {
|
||||
multiset.add(entry.getValue());
|
||||
|
|
|
@ -24,6 +24,7 @@ import docking.widgets.EventTrigger;
|
|||
import ghidra.app.services.GraphDisplayBroker;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.service.graph.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
|
@ -77,23 +78,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineVertexAttribute(String attributeName) {
|
||||
// no effect
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineEdgeAttribute(String attributeName) {
|
||||
// no effect
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
|
||||
boolean monospace,
|
||||
int maxLines) {
|
||||
// no effect
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGraph(AttributedGraph graph, String title, boolean append,
|
||||
TaskMonitor monitor) {
|
||||
|
@ -102,6 +86,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||
doSetGraphData(graph);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
|
||||
boolean append, TaskMonitor monitor) throws CancelledException {
|
||||
this.setGraph(graph, title, append, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all vertices and edges from the {@link Graph}
|
||||
*/
|
||||
|
@ -149,5 +139,4 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||
public void selectVertices(Set<AttributedVertex> vertexList, EventTrigger eventTrigger) {
|
||||
// not interactive, so N/A
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,13 +50,13 @@ public class GraphMlGraphExporter extends AbstractAttributedGraphExporter {
|
|||
entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING))));
|
||||
|
||||
graph.vertexSet().stream()
|
||||
.map(Attributed::getAttributeMap)
|
||||
.map(Attributed::getAttributes)
|
||||
.flatMap(m -> m.entrySet().stream())
|
||||
.map(Map.Entry::getKey)
|
||||
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING));
|
||||
|
||||
graph.edgeSet().stream()
|
||||
.map(Attributed::getAttributeMap)
|
||||
.map(Attributed::getAttributes)
|
||||
.flatMap(m -> m.entrySet().stream())
|
||||
.map(Map.Entry::getKey)
|
||||
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING));
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JToolTip;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import ghidra.graph.viewer.popup.ToolTipInfo;
|
||||
import ghidra.service.graph.*;
|
||||
|
||||
/**
|
||||
* Generates tool tips for an {@link AttributedVertex} or {@link AttributedEdge} in
|
||||
* an {@link AttributedGraph}
|
||||
*/
|
||||
public class AttributedToolTipInfo extends ToolTipInfo<Attributed> {
|
||||
|
||||
AttributedToolTipInfo(Attributed graphObject, MouseEvent event) {
|
||||
super(event, graphObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent createToolTipComponent() {
|
||||
if (graphObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String toolTip = getToolTipText();
|
||||
if (StringUtils.isBlank(toolTip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JToolTip jToolTip = new JToolTip();
|
||||
jToolTip.setTipText(toolTip);
|
||||
return jToolTip;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emphasize() {
|
||||
// this graph display does not have a notion of emphasizing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deEmphasize() {
|
||||
// this graph display does not have a notion of emphasizing
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tool tip for the graphObject this object manages
|
||||
* @return the tool tip for the graphObject this object manages
|
||||
*/
|
||||
public String getToolTipText() {
|
||||
String tooltipText = graphObject.getDescription();
|
||||
if (tooltipText != null) {
|
||||
return tooltipText;
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("<HTML>");
|
||||
|
||||
if (graphObject instanceof AttributedVertex) {
|
||||
addToolTipTextForVertex(buf, (AttributedVertex) graphObject);
|
||||
}
|
||||
else if (graphObject instanceof AttributedEdge) {
|
||||
addToolTipTextForEdge(buf, (AttributedEdge) graphObject);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void addToolTipTextForVertex(StringBuilder buf, AttributedVertex vertex) {
|
||||
String vertexType = vertex.getVertexType();
|
||||
|
||||
buf.append("<H4>");
|
||||
buf.append(vertex.getName());
|
||||
if (vertexType != null) {
|
||||
buf.append("<br>");
|
||||
buf.append("Type: " + vertexType);
|
||||
}
|
||||
buf.append("</H4>");
|
||||
|
||||
addAttributes(buf, AttributedVertex.NAME_KEY, AttributedVertex.VERTEX_TYPE_KEY);
|
||||
}
|
||||
|
||||
private void addToolTipTextForEdge(StringBuilder buf, AttributedEdge edge) {
|
||||
String edgeType = edge.getEdgeType();
|
||||
if (edgeType != null) {
|
||||
buf.append("<H4>");
|
||||
buf.append("Type: " + edgeType);
|
||||
buf.append("</H4>");
|
||||
}
|
||||
addAttributes(buf, AttributedEdge.EDGE_TYPE_KEY);
|
||||
}
|
||||
|
||||
private void addAttributes(StringBuilder buf, String...excludedKeys) {
|
||||
|
||||
Set<Entry<String, String>> entries = graphObject.entrySet();
|
||||
|
||||
for (Map.Entry<String, String> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (ArrayUtils.contains(excludedKeys, key)) {
|
||||
continue; // skip keys handled in header
|
||||
}
|
||||
buf.append(key);
|
||||
buf.append(": ");
|
||||
String value = entry.getValue();
|
||||
value = StringEscapeUtils.escapeHtml4(value);
|
||||
String split = String.join("<br>", Splitter.on('\n').split(value));
|
||||
split = split.replaceAll("\\s", " ");
|
||||
buf.append(split);
|
||||
buf.append("<br>");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,288 +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 static java.util.Map.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Paint;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.service.graph.Attributed;
|
||||
|
||||
/**
|
||||
* support for coercing colors from attributes or color names
|
||||
*/
|
||||
public abstract class Colors {
|
||||
|
||||
private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
|
||||
|
||||
// cannot instantiate nor extend
|
||||
private Colors() {
|
||||
}
|
||||
|
||||
/**
|
||||
* a map of well-known 'web' color names to colors
|
||||
*/
|
||||
static Map<String, Color> WEB_COLOR_MAP = Map.ofEntries(
|
||||
entry("Black", Color.decode("0x000000")),
|
||||
entry("Navy", Color.decode("0x000080")),
|
||||
entry("DarkBlue", Color.decode("0x00008B")),
|
||||
entry("MediumBlue", Color.decode("0x0000CD")),
|
||||
entry("Blue", Color.decode("0x0000FF")),
|
||||
entry("DarkGreen", Color.decode("0x006400")),
|
||||
entry("Green", Color.decode("0x008000")),
|
||||
entry("Teal", Color.decode("0x008080")),
|
||||
entry("DarkCyan", Color.decode("0x008B8B")),
|
||||
entry("DeepSkyBlue", Color.decode("0x00BFFF")),
|
||||
entry("DarkTurquoise", Color.decode("0x00CED1")),
|
||||
entry("MediumSpringGreen", Color.decode("0x00FA9A")),
|
||||
entry("Lime", Color.decode("0x00FF00")),
|
||||
entry("SpringGreen", Color.decode("0x00FF7F")),
|
||||
entry("Aqua", Color.decode("0x00FFFF")),
|
||||
entry("Cyan", Color.decode("0x00FFFF")),
|
||||
entry("MidnightBlue", Color.decode("0x191970")),
|
||||
entry("DodgerBlue", Color.decode("0x1E90FF")),
|
||||
entry("LightSeaGreen", Color.decode("0x20B2AA")),
|
||||
entry("ForestGreen", Color.decode("0x228B22")),
|
||||
entry("SeaGreen", Color.decode("0x2E8B57")),
|
||||
entry("DarkSlateGray", Color.decode("0x2F4F4F")),
|
||||
entry("DarkSlateGrey", Color.decode("0x2F4F4F")),
|
||||
entry("LimeGreen", Color.decode("0x32CD32")),
|
||||
entry("MediumSeaGreen", Color.decode("0x3CB371")),
|
||||
entry("Turquoise", Color.decode("0x40E0D0")),
|
||||
entry("RoyalBlue", Color.decode("0x4169E1")),
|
||||
entry("SteelBlue", Color.decode("0x4682B4")),
|
||||
entry("DarkSlateBlue", Color.decode("0x483D8B")),
|
||||
entry("MediumTurquoise", Color.decode("0x48D1CC")),
|
||||
entry("Indigo", Color.decode("0x4B0082")),
|
||||
entry("DarkOliveGreen", Color.decode("0x556B2F")),
|
||||
entry("CadetBlue", Color.decode("0x5F9EA0")),
|
||||
entry("CornflowerBlue", Color.decode("0x6495ED")),
|
||||
entry("RebeccaPurple", Color.decode("0x663399")),
|
||||
entry("MediumAquaMarine", Color.decode("0x66CDAA")),
|
||||
entry("DimGray", Color.decode("0x696969")),
|
||||
entry("DimGrey", Color.decode("0x696969")),
|
||||
entry("SlateBlue", Color.decode("0x6A5ACD")),
|
||||
entry("OliveDrab", Color.decode("0x6B8E23")),
|
||||
entry("SlateGray", Color.decode("0x708090")),
|
||||
entry("SlateGrey", Color.decode("0x708090")),
|
||||
entry("LightSlateGray", Color.decode("0x778899")),
|
||||
entry("LightSlateGrey", Color.decode("0x778899")),
|
||||
entry("MediumSlateBlue", Color.decode("0x7B68EE")),
|
||||
entry("LawnGreen", Color.decode("0x7CFC00")),
|
||||
entry("Chartreuse", Color.decode("0x7FFF00")),
|
||||
entry("Aquamarine", Color.decode("0x7FFFD4")),
|
||||
entry("Maroon", Color.decode("0x800000")),
|
||||
entry("Purple", Color.decode("0x800080")),
|
||||
entry("Olive", Color.decode("0x808000")),
|
||||
entry("Gray", Color.decode("0x808080")),
|
||||
entry("Grey", Color.decode("0x808080")),
|
||||
entry("SkyBlue", Color.decode("0x87CEEB")),
|
||||
entry("LightSkyBlue", Color.decode("0x87CEFA")),
|
||||
entry("BlueViolet", Color.decode("0x8A2BE2")),
|
||||
entry("DarkRed", Color.decode("0x8B0000")),
|
||||
entry("DarkMagenta", Color.decode("0x8B008B")),
|
||||
entry("SaddleBrown", Color.decode("0x8B4513")),
|
||||
entry("DarkSeaGreen", Color.decode("0x8FBC8F")),
|
||||
entry("LightGreen", Color.decode("0x90EE90")),
|
||||
entry("MediumPurple", Color.decode("0x9370DB")),
|
||||
entry("DarkViolet", Color.decode("0x9400D3")),
|
||||
entry("PaleGreen", Color.decode("0x98FB98")),
|
||||
entry("DarkOrchid", Color.decode("0x9932CC")),
|
||||
entry("YellowGreen", Color.decode("0x9ACD32")),
|
||||
entry("Sienna", Color.decode("0xA0522D")),
|
||||
entry("Brown", Color.decode("0xA52A2A")),
|
||||
entry("DarkGray", Color.decode("0xA9A9A9")),
|
||||
entry("DarkGrey", Color.decode("0xA9A9A9")),
|
||||
entry("LightBlue", Color.decode("0xADD8E6")),
|
||||
entry("GreenYellow", Color.decode("0xADFF2F")),
|
||||
entry("PaleTurquoise", Color.decode("0xAFEEEE")),
|
||||
entry("LightSteelBlue", Color.decode("0xB0C4DE")),
|
||||
entry("PowderBlue", Color.decode("0xB0E0E6")),
|
||||
entry("FireBrick", Color.decode("0xB22222")),
|
||||
entry("DarkGoldenRod", Color.decode("0xB8860B")),
|
||||
entry("MediumOrchid", Color.decode("0xBA55D3")),
|
||||
entry("RosyBrown", Color.decode("0xBC8F8F")),
|
||||
entry("DarkKhaki", Color.decode("0xBDB76B")),
|
||||
entry("Silver", Color.decode("0xC0C0C0")),
|
||||
entry("MediumVioletRed", Color.decode("0xC71585")),
|
||||
entry("IndianRed", Color.decode("0xCD5C5C")),
|
||||
entry("Peru", Color.decode("0xCD853F")),
|
||||
entry("Chocolate", Color.decode("0xD2691E")),
|
||||
entry("Tan", Color.decode("0xD2B48C")),
|
||||
entry("LightGray", Color.decode("0xD3D3D3")),
|
||||
entry("LightGrey", Color.decode("0xD3D3D3")),
|
||||
entry("Thistle", Color.decode("0xD8BFD8")),
|
||||
entry("Orchid", Color.decode("0xDA70D6")),
|
||||
entry("GoldenRod", Color.decode("0xDAA520")),
|
||||
entry("PaleVioletRed", Color.decode("0xDB7093")),
|
||||
entry("Crimson", Color.decode("0xDC143C")),
|
||||
entry("Gainsboro", Color.decode("0xDCDCDC")),
|
||||
entry("Plum", Color.decode("0xDDA0DD")),
|
||||
entry("BurlyWood", Color.decode("0xDEB887")),
|
||||
entry("LightCyan", Color.decode("0xE0FFFF")),
|
||||
entry("Lavender", Color.decode("0xE6E6FA")),
|
||||
entry("DarkSalmon", Color.decode("0xE9967A")),
|
||||
entry("Violet", Color.decode("0xEE82EE")),
|
||||
entry("PaleGoldenRod", Color.decode("0xEEE8AA")),
|
||||
entry("LightCoral", Color.decode("0xF08080")),
|
||||
entry("Khaki", Color.decode("0xF0E68C")),
|
||||
entry("AliceBlue", Color.decode("0xF0F8FF")),
|
||||
entry("HoneyDew", Color.decode("0xF0FFF0")),
|
||||
entry("Azure", Color.decode("0xF0FFFF")),
|
||||
entry("SandyBrown", Color.decode("0xF4A460")),
|
||||
entry("Wheat", Color.decode("0xF5DEB3")),
|
||||
entry("Beige", Color.decode("0xF5F5DC")),
|
||||
entry("WhiteSmoke", Color.decode("0xF5F5F5")),
|
||||
entry("MintCream", Color.decode("0xF5FFFA")),
|
||||
entry("GhostWhite", Color.decode("0xF8F8FF")),
|
||||
entry("Salmon", Color.decode("0xFA8072")),
|
||||
entry("AntiqueWhite", Color.decode("0xFAEBD7")),
|
||||
entry("Linen", Color.decode("0xFAF0E6")),
|
||||
entry("LightGoldenRodYellow", Color.decode("0xFAFAD2")),
|
||||
entry("OldLace", Color.decode("0xFDF5E6")),
|
||||
entry("Red", Color.decode("0xFF0000")),
|
||||
entry("Fuchsia", Color.decode("0xFF00FF")),
|
||||
entry("Magenta", Color.decode("0xFF00FF")),
|
||||
entry("DeepPink", Color.decode("0xFF1493")),
|
||||
entry("OrangeRed", Color.decode("0xFF4500")),
|
||||
entry("Tomato", Color.decode("0xFF6347")),
|
||||
entry("HotPink", Color.decode("0xFF69B4")),
|
||||
entry("Coral", Color.decode("0xFF7F50")),
|
||||
entry("DarkOrange", Color.decode("0xFF8C00")),
|
||||
entry("LightSalmon", Color.decode("0xFFA07A")),
|
||||
entry("Orange", Color.decode("0xFFA500")),
|
||||
entry("LightPink", Color.decode("0xFFB6C1")),
|
||||
entry("Pink", Color.decode("0xFFC0CB")),
|
||||
entry("Gold", Color.decode("0xFFD700")),
|
||||
entry("PeachPuff", Color.decode("0xFFDAB9")),
|
||||
entry("NavajoWhite", Color.decode("0xFFDEAD")),
|
||||
entry("Moccasin", Color.decode("0xFFE4B5")),
|
||||
entry("Bisque", Color.decode("0xFFE4C4")),
|
||||
entry("MistyRose", Color.decode("0xFFE4E1")),
|
||||
entry("BlanchedAlmond", Color.decode("0xFFEBCD")),
|
||||
entry("PapayaWhip", Color.decode("0xFFEFD5")),
|
||||
entry("LavenderBlush", Color.decode("0xFFF0F5")),
|
||||
entry("SeaShell", Color.decode("0xFFF5EE")),
|
||||
entry("Cornsilk", Color.decode("0xFFF8DC")),
|
||||
entry("LemonChiffon", Color.decode("0xFFFACD")),
|
||||
entry("FloralWhite", Color.decode("0xFFFAF0")),
|
||||
entry("Snow", Color.decode("0xFFFAFA")),
|
||||
entry("Yellow", Color.decode("0xFFFF00")),
|
||||
entry("LightYellow", Color.decode("0xFFFFE0")),
|
||||
entry("Ivory", Color.decode("0xFFFFF0")),
|
||||
entry("White", Color.decode("0xFFFFFF"))
|
||||
);
|
||||
|
||||
/**
|
||||
* a blue that is not as dark as {@code Color.blue}
|
||||
*/
|
||||
private static Color blue = new Color(100, 100, 255);
|
||||
|
||||
/**
|
||||
* a yellow that is darker than {@code Color.yellow}
|
||||
*/
|
||||
private static Color darkerYellow = new Color(225, 225, 0);
|
||||
|
||||
/**
|
||||
* these are vertex or edge types that have defined colors
|
||||
* (the keys are the property values for the vertex/edge keys:
|
||||
* VertexType and EdgeType)
|
||||
*/
|
||||
public static Map<String,Paint> VERTEX_TYPE_TO_COLOR_MAP =
|
||||
Map.ofEntries(
|
||||
entry("Body", blue),
|
||||
entry("Entry", WEB_COLOR_MAP.get("DarkOrange")),
|
||||
entry("Exit", Color.magenta),
|
||||
entry("Switch", Color.cyan),
|
||||
entry("Bad",Color.red),
|
||||
entry("Entry-Nexus",Color.white),
|
||||
entry("External",Color.green),
|
||||
entry("Folder",WEB_COLOR_MAP.get("DarkOrange")),
|
||||
entry("Fragment",WEB_COLOR_MAP.get("Purple")),
|
||||
entry("Data",Color.pink)
|
||||
);
|
||||
|
||||
/**
|
||||
* these are vertex or edge types that have defined colors
|
||||
* (the keys are the property values for the vertex/edge keys:
|
||||
* VertexType and EdgeType)
|
||||
*/
|
||||
public static Map<String,Paint> EDGE_TYPE_TO_COLOR_MAP =
|
||||
Map.ofEntries(
|
||||
|
||||
entry("Entry", Color.gray), // white??
|
||||
entry("Fall-Through", Color.blue),
|
||||
entry("Conditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
|
||||
entry("Unconditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
|
||||
entry("Computed",Color.cyan),
|
||||
entry("Indirection",Color.pink),
|
||||
entry("Unconditional-Jump", Color.green),
|
||||
entry("Conditional-Jump", darkerYellow),
|
||||
entry("Terminator", WEB_COLOR_MAP.get("Purple")),
|
||||
entry("Conditional-Return", WEB_COLOR_MAP.get("Purple"))
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Determine a color for the given {@link Attributed} object.
|
||||
* <P>
|
||||
* The attributed object can be an vertex or an edge. This method examines the attributes
|
||||
* and tries to find an attribute that has a color mapping. Otherwise it returns a default
|
||||
* color
|
||||
* @param attributed the vertex or edge for which to determine a color
|
||||
* @return the color to paint the given Attributed
|
||||
*/
|
||||
public static Paint getColor(Attributed attributed) {
|
||||
Map<String, String> map = attributed.getAttributeMap();
|
||||
// if there is a 'VertexType' attribute key, use its value to choose a predefined color
|
||||
if (map.containsKey("VertexType")) {
|
||||
String typeValue = map.get("VertexType");
|
||||
return VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.blue);
|
||||
}
|
||||
// if there is an 'EdgeType' attribute key, use its value to choose a predefined color
|
||||
if (map.containsKey("EdgeType")) {
|
||||
String typeValue = map.get("EdgeType");
|
||||
return EDGE_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.green);
|
||||
}
|
||||
// if there is a 'Color' attribute key, use its value (either a color name or an RGB hex string)
|
||||
// to choose a color
|
||||
if (map.containsKey("Color")) {
|
||||
String colorName = map.get("Color");
|
||||
if (WEB_COLOR_MAP.containsKey(colorName)) {
|
||||
return WEB_COLOR_MAP.get(colorName);
|
||||
}
|
||||
// if the value matches an RGB hex string, turn that into a color
|
||||
Color c = getHexColor(colorName);
|
||||
if (c != null) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
// default value when nothing else matches
|
||||
return Color.green;
|
||||
}
|
||||
|
||||
public static Color getHexColor(String hexString) {
|
||||
Matcher matcher = HEX_PATTERN.matcher(hexString);
|
||||
if (matcher.matches()) {
|
||||
return Color.decode(hexString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -16,46 +16,32 @@
|
|||
package ghidra.graph.visualization;
|
||||
|
||||
import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*;
|
||||
import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import javax.swing.event.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.graph.AsSubgraph;
|
||||
import org.jungrapht.visualization.*;
|
||||
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.annotations.SelectedEdgePaintable;
|
||||
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
|
||||
import org.jungrapht.visualization.annotations.*;
|
||||
import org.jungrapht.visualization.control.*;
|
||||
import org.jungrapht.visualization.decorators.*;
|
||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
||||
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
|
||||
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||
import org.jungrapht.visualization.layout.model.Point;
|
||||
import org.jungrapht.visualization.layout.model.Rectangle;
|
||||
import org.jungrapht.visualization.renderers.*;
|
||||
import org.jungrapht.visualization.renderers.Renderer;
|
||||
import org.jungrapht.visualization.renderers.Renderer.VertexLabel;
|
||||
import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position;
|
||||
import org.jungrapht.visualization.selection.MutableSelectedState;
|
||||
import org.jungrapht.visualization.transform.*;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
|
||||
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
|
||||
import org.jungrapht.visualization.util.RectangleUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingActionProxy;
|
||||
|
@ -63,10 +49,15 @@ import docking.action.DockingActionIf;
|
|||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.*;
|
||||
import docking.menu.ActionState;
|
||||
import docking.menu.MultiStateDockingAction;
|
||||
import docking.options.editor.OptionsDialog;
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.OptionsService;
|
||||
import ghidra.graph.AttributeFilters;
|
||||
import ghidra.graph.job.GraphJobRunner;
|
||||
import ghidra.graph.viewer.popup.*;
|
||||
|
@ -107,29 +98,16 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
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());
|
||||
// layout algorithm categories
|
||||
static final String MIN_CROSS = "Hierarchical MinCross";
|
||||
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross";
|
||||
|
||||
private Map<String, String> displayProperties;
|
||||
private Set<DockingActionIf> addedActions = new LinkedHashSet<>();
|
||||
private GraphDisplayListener listener = new DummyGraphDisplayListener();
|
||||
private String title;
|
||||
|
||||
private AttributedGraph graph;
|
||||
|
||||
private static String DEFAULT_EDGE_TYPE_PRIORITY_LIST =
|
||||
"Fall-Through,"+
|
||||
"Conditional-Return,"+
|
||||
"Unconditional-Jump,"+
|
||||
"Conditional-Jump,"+
|
||||
"Unconditional-Call,"+
|
||||
"Conditional-Call,"+
|
||||
"Terminator,"+
|
||||
"Computed,"+
|
||||
"Indirection,"+
|
||||
"Entry";
|
||||
|
||||
private static String DEFAULT_FAVORED_EDGES = "Fall-Through";
|
||||
|
||||
/**
|
||||
* a unique id for this {@link GraphDisplay}
|
||||
*/
|
||||
|
@ -143,7 +121,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
/**
|
||||
* The {@link PluginTool}
|
||||
*/
|
||||
private final PluginTool pluginTool;
|
||||
private final PluginTool tool;
|
||||
|
||||
private final DefaultGraphDisplayComponentProvider componentProvider;
|
||||
|
||||
|
@ -182,13 +160,13 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
private AttributeFilters edgeFilters;
|
||||
private AttributeFilters vertexFilters;
|
||||
|
||||
private GhidraIconCache iconCache;
|
||||
private GraphRenderer graphRenderer = new DefaultGraphRenderer();
|
||||
|
||||
/**
|
||||
* Multi-selection is done in a free-form traced shape instead of a rectangle
|
||||
*/
|
||||
private boolean freeFormSelection;
|
||||
|
||||
|
||||
/**
|
||||
* Handles all mouse interaction
|
||||
*/
|
||||
|
@ -209,31 +187,32 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
private SelectedEdgePaintable<AttributedVertex, AttributedEdge> selectedEdgePaintable;
|
||||
|
||||
private GraphDisplayOptions graphDisplayOptions = GraphDisplayOptions.DEFAULT;
|
||||
|
||||
private ChangeListener graphDisplayOptionsChangeListener;
|
||||
|
||||
private MultiStateDockingAction<String> layoutAction;
|
||||
|
||||
/**
|
||||
* Create the initial display, the graph-less visualization viewer, and its controls
|
||||
* @param displayProvider provides a {@link PluginTool} for Docking features
|
||||
* @param displayProperties graph properties that will override the default graph properties
|
||||
* @param id the unique display id
|
||||
*/
|
||||
DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider,
|
||||
Map<String, String> displayProperties, int id) {
|
||||
DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, int id) {
|
||||
this.graphDisplayProvider = displayProvider;
|
||||
this.displayId = id;
|
||||
this.pluginTool = graphDisplayProvider.getPluginTool();
|
||||
this.displayProperties = displayProperties;
|
||||
this.tool = graphDisplayProvider.getPluginTool();
|
||||
this.viewer = createViewer();
|
||||
buildHighlighers();
|
||||
|
||||
componentProvider = new DefaultGraphDisplayComponentProvider(this, pluginTool);
|
||||
componentProvider = new DefaultGraphDisplayComponentProvider(this, tool);
|
||||
componentProvider.addToTool();
|
||||
satelliteViewer = createSatelliteViewer(viewer);
|
||||
if (graphDisplayProvider.getDefaultSatelliteState()) {
|
||||
viewer.getComponent().add(satelliteViewer.getComponent());
|
||||
}
|
||||
layoutTransitionManager =
|
||||
new LayoutTransitionManager(viewer, this::isRoot,
|
||||
getEdgeTypePriorityList(),
|
||||
getFavoredEdgePredicate());
|
||||
new LayoutTransitionManager(viewer, this::isRoot, graphRenderer);
|
||||
|
||||
viewer.getComponent().addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
|
@ -254,29 +233,26 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
createToolbarActions();
|
||||
createPopupActions();
|
||||
connectSelectionStateListeners();
|
||||
graphDisplayOptionsChangeListener = e -> refreshViewer();
|
||||
}
|
||||
|
||||
private void refreshViewer() {
|
||||
graphRenderer.clearCache();
|
||||
|
||||
graphRenderer.initializeViewer(viewer);
|
||||
|
||||
// bug in jungraphT library where vertex selection color doesn't update, but edge selection
|
||||
// color does, so just rebuild the highlighter
|
||||
buildHighlighers();
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
private Color getSelectedVertexColor() {
|
||||
String property = displayProperties.getOrDefault(SELECTED_VERTEX_COLOR, "0xFF0000");
|
||||
return Colors.getHexColor(property);
|
||||
return graphRenderer.getVertexSelectionColor();
|
||||
}
|
||||
|
||||
private Color getSelectedEdgeColor() {
|
||||
String property = displayProperties.getOrDefault(SELECTED_EDGE_COLOR, "0xFF0000");
|
||||
return Colors.getHexColor(property);
|
||||
}
|
||||
|
||||
private List<String> getEdgeTypePriorityList() {
|
||||
return Arrays.asList(displayProperties
|
||||
.getOrDefault(EDGE_TYPE_PRIORITY_LIST, DEFAULT_EDGE_TYPE_PRIORITY_LIST)
|
||||
.split(","));
|
||||
}
|
||||
|
||||
private Predicate<AttributedEdge> getFavoredEdgePredicate() {
|
||||
String[] favoredEdges = displayProperties.getOrDefault(FAVORED_EDGES, DEFAULT_FAVORED_EDGES)
|
||||
.split(",");
|
||||
return attributedEdge -> Arrays.stream(favoredEdges)
|
||||
.anyMatch(s -> s.equals(attributedEdge.getAttribute("EdgeType")));
|
||||
return graphRenderer.getEdgeSelectionColor();
|
||||
}
|
||||
|
||||
JComponent getComponent() {
|
||||
|
@ -429,7 +405,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
// create a menu with graph layout algorithm selections
|
||||
List<ActionState<String>> layoutActionStates = getLayoutActionStates();
|
||||
new MultiStateActionBuilder<String>("Arrangement", ACTION_OWNER)
|
||||
layoutAction = new MultiStateActionBuilder<String>("Arrangement", ACTION_OWNER)
|
||||
.description("Arrangement: " + layoutActionStates.get(0).getName())
|
||||
.toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON)
|
||||
.useCheckboxForIcons(true)
|
||||
|
@ -574,6 +550,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
.onAction(c -> ungroupSelectedVertices())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
new ActionBuilder("Graph Type Display Options", ACTION_OWNER)
|
||||
.popupMenuPath("Graph Type Options ...")
|
||||
.popupMenuGroup("zzz")
|
||||
.menuPath("Graph Type Options ...")
|
||||
.description("Brings up option editor for configuring vertex and edge types.")
|
||||
.onAction(c -> editGraphDisplayOptions())
|
||||
.buildAndInstallLocal(componentProvider);
|
||||
|
||||
togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", ACTION_OWNER)
|
||||
.popupMenuPath("Display Popup Windows")
|
||||
.popupMenuGroup("zz", "1")
|
||||
|
@ -585,6 +569,32 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
}
|
||||
|
||||
private void editGraphDisplayOptions() {
|
||||
String rootOptionsName = graphDisplayOptions.getRootOptionsName();
|
||||
String relativePath = rootOptionsName + ".Vertex Colors";
|
||||
|
||||
// if the options are registered in the tool options, just show them
|
||||
// otherwise, create a transient options and create an options dialog. This will
|
||||
// allow the user to edit the options for the current graph instance.
|
||||
|
||||
if (graphDisplayOptions.isRegisteredWithTool()) {
|
||||
OptionsService service = tool.getService(OptionsService.class);
|
||||
service.showOptionsDialog("Graph." + relativePath, "");
|
||||
}
|
||||
else {
|
||||
ToolOptions transientOptions = new ToolOptions("Graph");
|
||||
HelpLocation help = new HelpLocation("GraphServices", "Graph Type Display Options");
|
||||
graphDisplayOptions.registerOptions(transientOptions, help);
|
||||
transientOptions.addOptionsChangeListener(graphDisplayOptions);
|
||||
Options[] optionsArray = new Options[] { transientOptions };
|
||||
String dialogTitle = "Graph Instance Settings (Not Saved in Tool Options)";
|
||||
OptionsDialog dialog = new OptionsDialog(dialogTitle, "Graph", optionsArray, null);
|
||||
// we have one less level for these transient tool options, so no need to prepend "graph."
|
||||
dialog.displayCategory(relativePath, "");
|
||||
tool.showDialog(dialog, componentProvider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group the selected vertices into one vertex that represents them all
|
||||
*/
|
||||
|
@ -638,7 +648,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
private void createAndDisplaySubGraph() {
|
||||
GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
display.setGraph(createSubGraph(), title + " - Sub-graph", false, TaskMonitor.DUMMY);
|
||||
display.setGraph(createSubGraph(), graphRenderer.getGraphDisplayOptions(),
|
||||
title + " - Sub-graph", false, TaskMonitor.DUMMY);
|
||||
display.setGraphDisplayListener(listener.cloneWith(display));
|
||||
copyActionsToNewGraph((DefaultGraphDisplay) display);
|
||||
}
|
||||
|
@ -651,7 +662,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
Set<AttributedVertex> selected = viewer.getSelectedVertices();
|
||||
Graph<AttributedVertex, AttributedEdge> subGraph = new AsSubgraph<>(graph, selected);
|
||||
|
||||
AttributedGraph newGraph = new AttributedGraph();
|
||||
AttributedGraph newGraph =
|
||||
new AttributedGraph(graph.getName() + ": subgraph", graph.getGraphType());
|
||||
subGraph.vertexSet().forEach(newGraph::addVertex);
|
||||
for (AttributedEdge e : subGraph.edgeSet()) {
|
||||
AttributedVertex source = subGraph.getEdgeSource(e);
|
||||
|
@ -667,17 +679,17 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
// select all the edges that connect the supplied vertices
|
||||
private void selectEdgesConnecting(Collection<AttributedVertex> vertices) {
|
||||
viewer.getSelectedEdgeState().select(
|
||||
graph.edgeSet()
|
||||
.stream()
|
||||
.filter(
|
||||
e -> {
|
||||
AttributedVertex source = graph.getEdgeSource(e);
|
||||
AttributedVertex target = graph.getEdgeTarget(e);
|
||||
return vertices.contains(source)
|
||||
&& vertices.contains(target);
|
||||
})
|
||||
.collect(Collectors.toSet()));
|
||||
viewer.getSelectedEdgeState()
|
||||
.select(
|
||||
graph.edgeSet()
|
||||
.stream()
|
||||
.filter(
|
||||
e -> {
|
||||
AttributedVertex source = graph.getEdgeSource(e);
|
||||
AttributedVertex target = graph.getEdgeTarget(e);
|
||||
return vertices.contains(source) && vertices.contains(target);
|
||||
})
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
}
|
||||
|
||||
|
@ -782,7 +794,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
}
|
||||
|
||||
private List<ActionState<String>> getLayoutActionStates() {
|
||||
String[] names = layoutTransitionManager.getLayoutNames();
|
||||
List<String> names = LayoutAlgorithmNames.getLayoutAlgorithmNames();
|
||||
List<ActionState<String>> actionStates = new ArrayList<>();
|
||||
for (String layoutName : names) {
|
||||
ActionState<String> state = new ActionState<>(layoutName,
|
||||
|
@ -790,11 +802,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
|
||||
// condense hierarchical action help to the top-level help description
|
||||
String anchor = layoutName;
|
||||
if (layoutName.contains(LayoutFunction.VERT_MIN_CROSS)) {
|
||||
anchor = LayoutFunction.VERT_MIN_CROSS;
|
||||
if (layoutName.contains(VERT_MIN_CROSS)) {
|
||||
anchor = VERT_MIN_CROSS;
|
||||
}
|
||||
else if (layoutName.contains(LayoutFunction.MIN_CROSS)) {
|
||||
anchor = LayoutFunction.MIN_CROSS;
|
||||
else if (layoutName.contains(MIN_CROSS)) {
|
||||
anchor = MIN_CROSS;
|
||||
}
|
||||
|
||||
state.setHelpLocation(new HelpLocation(ACTION_OWNER, anchor));
|
||||
|
@ -846,25 +858,21 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
//
|
||||
satellite.setGraphMouse(new JgtSatelliteGraphMouse());
|
||||
|
||||
satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor);
|
||||
satellite.getRenderContext()
|
||||
.setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke);
|
||||
satellite.getRenderContext()
|
||||
.setEdgeDrawPaintFunction(viewer.getRenderContext().getEdgeDrawPaintFunction());
|
||||
satellite.getRenderContext()
|
||||
.setVertexFillPaintFunction(viewer.getRenderContext().getVertexFillPaintFunction());
|
||||
satellite.getRenderContext()
|
||||
.setVertexDrawPaintFunction(viewer.getRenderContext().getVertexDrawPaintFunction());
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderer = satellite.getRenderContext();
|
||||
RenderContext<AttributedVertex, AttributedEdge> viewerRenderer = viewer.getRenderContext();
|
||||
renderer.setEdgeDrawPaintFunction(viewerRenderer.getEdgeDrawPaintFunction());
|
||||
renderer.setEdgeStrokeFunction(viewerRenderer.getEdgeArrowStrokeFunction());
|
||||
renderer.setEdgeDrawPaintFunction(viewerRenderer.getEdgeDrawPaintFunction());
|
||||
renderer.setVertexFillPaintFunction(viewerRenderer.getVertexFillPaintFunction());
|
||||
renderer.setVertexDrawPaintFunction(viewerRenderer.getVertexDrawPaintFunction());
|
||||
|
||||
satellite.scaleToLayout();
|
||||
satellite.getRenderContext().setVertexLabelFunction(n -> null);
|
||||
// always get the current predicate from the main view and test with it,
|
||||
satellite.getRenderContext()
|
||||
.setVertexIncludePredicate(
|
||||
v -> viewer.getRenderContext().getVertexIncludePredicate().test(v));
|
||||
satellite.getRenderContext()
|
||||
.setEdgeIncludePredicate(
|
||||
e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e));
|
||||
renderer.setVertexLabelFunction(n -> null);
|
||||
|
||||
// the satellite should use the same vertex predicate so that it has the same vertices
|
||||
// as the main graph
|
||||
renderer.setVertexIncludePredicate(v -> viewerRenderer.getVertexIncludePredicate().test(v));
|
||||
renderer.setEdgeIncludePredicate(e -> viewerRenderer.getEdgeIncludePredicate().test(e));
|
||||
satellite.getComponent().setBorder(BorderFactory.createEtchedBorder());
|
||||
parentViewer.getComponent().addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
|
@ -885,6 +893,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
}
|
||||
listener = null;
|
||||
componentProvider.closeComponent();
|
||||
if (graphDisplayOptions != null) {
|
||||
graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1035,8 +1046,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
}
|
||||
|
||||
private void setInitialLayoutAlgorithm() {
|
||||
if (displayProperties.containsKey(INITIAL_LAYOUT_ALGORITHM)) {
|
||||
String layoutAlgorithmName = displayProperties.get(INITIAL_LAYOUT_ALGORITHM);
|
||||
String layoutAlgorithmName = graphDisplayOptions.getDefaultLayoutAlgorithmNameLayout();
|
||||
layoutAction.setCurrentActionStateByUserData(layoutAlgorithmName);
|
||||
if (layoutAlgorithmName != null) {
|
||||
layoutTransitionManager.setLayout(layoutAlgorithmName);
|
||||
}
|
||||
else {
|
||||
|
@ -1056,7 +1068,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
private boolean isRoot(AttributedVertex vertex) {
|
||||
Set<AttributedEdge> incomingEdgesOf = graph.incomingEdgesOf(vertex);
|
||||
return incomingEdgesOf.isEmpty() ||
|
||||
graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex));
|
||||
graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1075,7 +1087,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
.elements(vertices)
|
||||
.maxFactor(.05)
|
||||
.buttonSupplier(JRadioButton::new)
|
||||
.paintFunction(v -> Colors.VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(v, Color.blue))
|
||||
.paintFunction(v -> graphDisplayOptions.getVertexColor(v))
|
||||
.build();
|
||||
|
||||
vertexFilters.addItemListener(item -> {
|
||||
|
@ -1083,7 +1095,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
Set<String> selected = (Set<String>) item.getItem();
|
||||
viewer.getRenderContext()
|
||||
.setVertexIncludePredicate(
|
||||
v -> v.getAttributeMap().values().stream().noneMatch(selected::contains));
|
||||
v -> v.getAttributes().values().stream().noneMatch(selected::contains));
|
||||
viewer.repaint();
|
||||
|
||||
});
|
||||
|
@ -1093,7 +1105,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
.elements(edges)
|
||||
.maxFactor(.01)
|
||||
.buttonSupplier(JRadioButton::new)
|
||||
.paintFunction(e -> Colors.EDGE_TYPE_TO_COLOR_MAP.getOrDefault(e, Color.green))
|
||||
.paintFunction(e -> graphDisplayOptions.getEdgeColor(e))
|
||||
.build();
|
||||
|
||||
edgeFilters.addItemListener(item -> {
|
||||
|
@ -1101,7 +1113,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
Set<String> selected = (Set<String>) item.getItem();
|
||||
viewer.getRenderContext()
|
||||
.setEdgeIncludePredicate(
|
||||
e -> e.getAttributeMap().values().stream().noneMatch(selected::contains));
|
||||
e -> e.getAttributes().values().stream().noneMatch(selected::contains));
|
||||
viewer.repaint();
|
||||
});
|
||||
}
|
||||
|
@ -1124,36 +1136,21 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineVertexAttribute(String attributeName) {
|
||||
log.fine("defineVertexAttribute " + attributeName + " is not implemented");
|
||||
private void setGraphDisplayOptions(GraphDisplayOptions options) {
|
||||
if (graphDisplayOptions != null) {
|
||||
graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener);
|
||||
}
|
||||
graphDisplayOptions = options;
|
||||
graphDisplayOptions.addChangeListener(graphDisplayOptionsChangeListener);
|
||||
graphRenderer.setGraphTypeDisplayOptions(options);
|
||||
refreshViewer();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineEdgeAttribute(String attributeName) {
|
||||
log.fine("defineEdgeAttribute " + attributeName + " is not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
|
||||
boolean monospace,
|
||||
int maxLines) {
|
||||
log.fine("setVertexLabel " + attributeName);
|
||||
this.iconCache.setPreferredVertexLabelAttribute(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* consume a {@link Graph} and display it
|
||||
* @param graph the graph to display or consume
|
||||
* @param title a title for the graph
|
||||
* @param append if true, append the new graph to any existing graph.
|
||||
* @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation
|
||||
*/
|
||||
@Override
|
||||
public void setGraph(AttributedGraph graph, String title, boolean append,
|
||||
TaskMonitor monitor) {
|
||||
iconCache.clear();
|
||||
|
||||
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
|
||||
boolean append, TaskMonitor monitor) {
|
||||
setGraphDisplayOptions(options);
|
||||
if (append && Objects.equals(title, this.title) && this.graph != null) {
|
||||
graph = mergeGraphs(graph, this.graph);
|
||||
}
|
||||
|
@ -1165,12 +1162,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!",
|
||||
"Exceeded limit of " + MAX_NODES + " nodes.\n\n Graph contained " + count +
|
||||
" nodes!");
|
||||
graph = new AttributedGraph();
|
||||
graph = new AttributedGraph("Aborted", graph.getGraphType(), "Too Many Nodes");
|
||||
graph.addVertex("1", "Graph Aborted");
|
||||
}
|
||||
doSetGraphData(graph);
|
||||
graphCollapser = new GhidraGraphCollapser(viewer);
|
||||
|
||||
buildHighlighers();
|
||||
}
|
||||
|
||||
private AttributedGraph mergeGraphs(AttributedGraph newGraph, AttributedGraph oldGraph) {
|
||||
|
@ -1181,7 +1178,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
AttributedVertex from = oldGraph.getEdgeSource(edge);
|
||||
AttributedVertex to = oldGraph.getEdgeTarget(edge);
|
||||
AttributedEdge newEdge = newGraph.addEdge(from, to);
|
||||
Map<String, String> attributeMap = edge.getAttributeMap();
|
||||
Map<String, String> attributeMap = edge.getAttributes();
|
||||
for (String key : attributeMap.keySet()) {
|
||||
newEdge.setAttribute(key, edge.getAttribute(key));
|
||||
}
|
||||
|
@ -1265,7 +1262,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
@Override
|
||||
public void updateVertexName(AttributedVertex vertex, String newName) {
|
||||
vertex.setName(newName);
|
||||
iconCache.evict(vertex);
|
||||
graphRenderer.vertexChanged(vertex);
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
|
@ -1320,44 +1317,14 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
PopupSource<AttributedVertex, AttributedEdge> popupSource = new GraphDisplayPopupSource(vv);
|
||||
popupRegulator = new PopupRegulator<>(popupSource);
|
||||
|
||||
this.iconCache = new GhidraIconCache();
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
|
||||
|
||||
renderContext.getSelectedVertexState().addItemListener(item -> {
|
||||
renderContext.getSelectedEdgeState().clear();
|
||||
selectEdgesConnecting(renderContext.getSelectedVertexState().getSelected());
|
||||
});
|
||||
selectEdgesConnecting(renderContext.getSelectedVertexState().getSelected());
|
||||
});
|
||||
|
||||
setVertexPreferences(vv);
|
||||
|
||||
renderContext.setEdgeStrokeFunction(e -> ProgramGraphFunctions.getEdgeStroke(e));
|
||||
renderContext.setEdgeDrawPaintFunction(e -> Colors.getColor(e));
|
||||
renderContext.setArrowDrawPaintFunction(e -> Colors.getColor(e));
|
||||
renderContext.setArrowFillPaintFunction(e -> Colors.getColor(e));
|
||||
|
||||
// assign the shapes to the modal renderer
|
||||
ModalRenderer<AttributedVertex, AttributedEdge> modalRenderer = vv.getRenderer();
|
||||
// the modal renderer optimizes rendering for large graphs by removing detail
|
||||
|
||||
Renderer.Vertex<AttributedVertex, AttributedEdge> vertexRenderer =
|
||||
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
|
||||
|
||||
// cause the lightweight (optimized) renderer to use the vertex shapes instead
|
||||
// of using default shapes.
|
||||
if (vertexRenderer instanceof LightweightVertexRenderer) {
|
||||
Function<AttributedVertex, Shape> vertexShapeFunction =
|
||||
renderContext.getVertexShapeFunction();
|
||||
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
|
||||
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) vertexRenderer;
|
||||
lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction);
|
||||
}
|
||||
|
||||
renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black));
|
||||
renderContext.setVertexDrawPaintFunction(Colors::getColor);
|
||||
renderContext.setVertexFillPaintFunction(Colors::getColor);
|
||||
renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f));
|
||||
|
||||
renderContext.setEdgeShapeFunction(EdgeShape.line());
|
||||
graphRenderer.initializeViewer(vv);
|
||||
|
||||
vv.getComponent().requestFocus();
|
||||
vv.setBackground(Color.WHITE);
|
||||
|
@ -1366,46 +1333,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
vv.getComponent().removeMouseListener(mouseListener);
|
||||
}
|
||||
|
||||
graphMouse = new JgtGraphMouse(this,
|
||||
Boolean.parseBoolean(displayProperties.getOrDefault(ENABLE_EDGE_SELECTION,
|
||||
"false")));
|
||||
graphMouse = new JgtGraphMouse(this, false);
|
||||
vv.setGraphMouse(graphMouse);
|
||||
|
||||
return vv;
|
||||
}
|
||||
|
||||
private void setVertexPreferences(VisualizationViewer<AttributedVertex, AttributedEdge> vv) {
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
|
||||
String useIcons =
|
||||
displayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, Boolean.TRUE.toString());
|
||||
Function<Shape, Rectangle> toRectangle = s -> RectangleUtils.convert(s.getBounds2D());
|
||||
if (Boolean.parseBoolean(useIcons)) {
|
||||
// set up the shape and color functions
|
||||
IconShapeFunction<AttributedVertex> nodeShaper =
|
||||
new IconShapeFunction<>(new EllipseShapeFunction<>());
|
||||
|
||||
nodeShaper.setIconFunction(iconCache::get);
|
||||
renderContext.setVertexShapeFunction(nodeShaper);
|
||||
renderContext.setVertexIconFunction(iconCache::get);
|
||||
|
||||
vv.setInitialDimensionFunction(InitialDimensionFunction
|
||||
.builder(nodeShaper.andThen(toRectangle))
|
||||
.build());
|
||||
}
|
||||
else {
|
||||
vv.getRenderContext().setVertexShapeFunction(ProgramGraphFunctions::getVertexShape);
|
||||
vv.setInitialDimensionFunction(InitialDimensionFunction
|
||||
.builder(renderContext.getVertexShapeFunction()
|
||||
.andThen(toRectangle))
|
||||
.build());
|
||||
vv.getRenderContext().setVertexLabelFunction(Object::toString);
|
||||
vv.getRenderContext()
|
||||
.setVertexLabelPosition(
|
||||
VertexLabel.Position.valueOf(
|
||||
displayProperties.getOrDefault(VERTEX_LABEL_POSITION, "AUTO")));
|
||||
}
|
||||
}
|
||||
|
||||
private void copyActionsToNewGraph(DefaultGraphDisplay display) {
|
||||
|
||||
for (DockingActionIf action : addedActions) {
|
||||
|
@ -1454,7 +1387,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
return viewer.getSelectedVertices();
|
||||
}
|
||||
|
||||
public ActionContext getActionContext(MouseEvent e) {
|
||||
ActionContext getActionContext(MouseEvent e) {
|
||||
|
||||
AttributedVertex pickedVertex = JgtUtils.getVertex(e, viewer);
|
||||
if (pickedVertex != null) {
|
||||
|
@ -1596,39 +1529,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
|||
}
|
||||
}
|
||||
|
||||
private class AttributedToolTipInfo extends ToolTipInfo<Attributed> {
|
||||
|
||||
AttributedToolTipInfo(Attributed graphObject, MouseEvent event) {
|
||||
super(event, graphObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent createToolTipComponent() {
|
||||
if (graphObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String toolTip = graphObject.getHtmlString();
|
||||
if (StringUtils.isBlank(toolTip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JToolTip jToolTip = new JToolTip();
|
||||
jToolTip.setTipText(toolTip);
|
||||
return jToolTip;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emphasize() {
|
||||
// this graph display does not have a notion of emphasizing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deEmphasize() {
|
||||
// this graph display does not have a notion of emphasizing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item listener for selection changes in the graph with the additional
|
||||
* capability of being able to disable the listener without removing it.
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.framework.options.Options;
|
||||
|
@ -54,13 +52,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
|
||||
return getGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplay getGraphDisplay(boolean reuseGraph, Map<String, String> properties,
|
||||
TaskMonitor monitor) {
|
||||
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
|
||||
|
||||
if (reuseGraph && !displays.isEmpty()) {
|
||||
DefaultGraphDisplay visibleGraph = getVisibleGraph();
|
||||
|
@ -69,7 +61,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
|||
}
|
||||
|
||||
DefaultGraphDisplay display =
|
||||
Swing.runNow(() -> new DefaultGraphDisplay(this, properties, displayCounter++));
|
||||
Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++));
|
||||
displays.add(display);
|
||||
return display;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
/* ###
|
||||
* 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 static org.jungrapht.visualization.renderers.BiModalRenderer.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import org.jungrapht.visualization.RenderContext;
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
import org.jungrapht.visualization.decorators.*;
|
||||
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
|
||||
import org.jungrapht.visualization.renderers.*;
|
||||
import org.jungrapht.visualization.renderers.Renderer;
|
||||
import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position;
|
||||
import org.jungrapht.visualization.util.RectangleUtils;
|
||||
|
||||
import generic.util.image.ImageUtils;
|
||||
import ghidra.service.graph.*;
|
||||
|
||||
/**
|
||||
* Handles the rendering of graphs for the {@link DefaultGraphDisplay}
|
||||
*/
|
||||
public class DefaultGraphRenderer implements GraphRenderer {
|
||||
private static final int DEFAULT_MARGIN_BORDER_SIZE = 4;
|
||||
private static final int DEFAULT_STROKE_THICKNESS = 6;
|
||||
private static final int ICON_ZOOM = 5;
|
||||
|
||||
private int labelBorderSize = DEFAULT_MARGIN_BORDER_SIZE;
|
||||
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
|
||||
private JLabel label;
|
||||
|
||||
private GraphDisplayOptions options;
|
||||
private final Map<AttributedVertex, Icon> iconCache = new ConcurrentHashMap<>();
|
||||
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
||||
private Stroke edgeStroke = new BasicStroke(4.0f);
|
||||
public DefaultGraphRenderer() {
|
||||
this(new DefaultGraphDisplayOptions());
|
||||
}
|
||||
|
||||
public DefaultGraphRenderer(GraphDisplayOptions options) {
|
||||
this.options = options;
|
||||
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
label = new JLabel();
|
||||
label.setForeground(Color.black);
|
||||
label.setBackground(Color.white);
|
||||
label.setOpaque(false);
|
||||
Border marginBorder = BorderFactory.createEmptyBorder(labelBorderSize, 2 * labelBorderSize,
|
||||
labelBorderSize, 2 * labelBorderSize);
|
||||
label.setBorder(marginBorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGraphTypeDisplayOptions(GraphDisplayOptions options) {
|
||||
this.options = options;
|
||||
clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDisplayOptions getGraphDisplayOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
iconCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer) {
|
||||
|
||||
RenderContext<AttributedVertex, AttributedEdge> renderContext = viewer.getRenderContext();
|
||||
Function<Shape, org.jungrapht.visualization.layout.model.Rectangle> toRectangle =
|
||||
s -> RectangleUtils.convert(s.getBounds2D());
|
||||
|
||||
if (options.usesIcons()) {
|
||||
// set up the shape and color functions
|
||||
IconShapeFunction<AttributedVertex> nodeShaper =
|
||||
new IconShapeFunction<>(new EllipseShapeFunction<>());
|
||||
|
||||
nodeShaper.setIconFunction(this::getIcon);
|
||||
renderContext.setVertexShapeFunction(nodeShaper);
|
||||
renderContext.setVertexIconFunction(this::getIcon);
|
||||
int arrowLength = options.getArrowLength() * ICON_ZOOM;
|
||||
int arrowWidth = (int) (arrowLength * 1.3);
|
||||
renderContext.setEdgeArrowWidth(arrowWidth);
|
||||
renderContext.setEdgeArrowLength(arrowLength);
|
||||
renderContext.setVertexLabelFunction(v -> "");
|
||||
viewer.setInitialDimensionFunction(
|
||||
InitialDimensionFunction.builder(nodeShaper.andThen(toRectangle)).build());
|
||||
}
|
||||
else {
|
||||
renderContext.setVertexIconFunction(null);
|
||||
renderContext.setVertexShapeFunction(this::getVertexShape);
|
||||
viewer.setInitialDimensionFunction(InitialDimensionFunction
|
||||
.builder(renderContext.getVertexShapeFunction().andThen(toRectangle))
|
||||
.build());
|
||||
renderContext.setVertexLabelFunction(Object::toString);
|
||||
GraphLabelPosition labelPosition = options.getLabelPosition();
|
||||
renderContext.setVertexLabelPosition(getJungraphTPosition(labelPosition));
|
||||
|
||||
}
|
||||
|
||||
// assign the shapes to the modal renderer
|
||||
// the modal renderer optimizes rendering for large graphs by removing detail
|
||||
ModalRenderer<AttributedVertex, AttributedEdge> modalRenderer = viewer.getRenderer();
|
||||
Renderer.Vertex<AttributedVertex, AttributedEdge> lightWeightRenderer =
|
||||
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
|
||||
|
||||
|
||||
// set the lightweight (optimized) renderer to use the vertex shapes instead
|
||||
// of using default shapes.
|
||||
if (lightWeightRenderer instanceof LightweightVertexRenderer) {
|
||||
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
|
||||
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) lightWeightRenderer;
|
||||
|
||||
Function<AttributedVertex, Shape> vertexShapeFunction =
|
||||
renderContext.getVertexShapeFunction();
|
||||
lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction);
|
||||
}
|
||||
|
||||
renderContext.setVertexFontFunction(this::getFont);
|
||||
renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black));
|
||||
renderContext.setVertexDrawPaintFunction(this::getVertexColor);
|
||||
renderContext.setVertexFillPaintFunction(this::getVertexColor);
|
||||
renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f));
|
||||
|
||||
renderContext.setEdgeStrokeFunction(this::getEdgeStroke);
|
||||
renderContext.setEdgeDrawPaintFunction(this::getEdgeColor);
|
||||
renderContext.setArrowDrawPaintFunction(this::getEdgeColor);
|
||||
renderContext.setArrowFillPaintFunction(this::getEdgeColor);
|
||||
renderContext.setEdgeShapeFunction(EdgeShape.line());
|
||||
}
|
||||
|
||||
private Shape getVertexShape(AttributedVertex vertex) {
|
||||
if (vertex instanceof GroupVertex) {
|
||||
return VertexShape.STAR.getShape();
|
||||
}
|
||||
VertexShape vertexShape = options.getVertexShape(vertex);
|
||||
return vertexShape != null ? vertexShape.getShape() : VertexShape.RECTANGLE.getShape();
|
||||
}
|
||||
|
||||
private Position getJungraphTPosition(GraphLabelPosition labelPosition) {
|
||||
switch (labelPosition) {
|
||||
case CENTER:
|
||||
return Position.CNTR;
|
||||
case EAST:
|
||||
return Position.E;
|
||||
case NORTH:
|
||||
return Position.N;
|
||||
case NORTHEAST:
|
||||
return Position.NE;
|
||||
case NORTHWEST:
|
||||
return Position.NW;
|
||||
case SOUTH:
|
||||
return Position.S;
|
||||
case SOUTHEAST:
|
||||
return Position.SE;
|
||||
case SOUTHWEST:
|
||||
return Position.SW;
|
||||
case WEST:
|
||||
return Position.W;
|
||||
default:
|
||||
return Position.AUTO;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Color getVertexColor(AttributedVertex vertex) {
|
||||
return options.getVertexColor(vertex);
|
||||
}
|
||||
|
||||
private Color getEdgeColor(AttributedEdge edge) {
|
||||
return options.getEdgeColor(edge);
|
||||
}
|
||||
|
||||
private Icon getIcon(AttributedVertex vertex) {
|
||||
|
||||
// WARNING: very important to not use map's computeIfAbsent() method
|
||||
// because the map is synchronized and the createIcon() method will
|
||||
// attempt to acquire the AWT lock. That combination will cause a deadlock
|
||||
// if computeIfAbsent() is used and this method is called from non-swing thread.
|
||||
Icon icon = iconCache.get(vertex);
|
||||
if (icon == null) {
|
||||
icon = createIcon(vertex);
|
||||
iconCache.put(vertex, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Icon createIcon(AttributedVertex vertex) {
|
||||
VertexShape vertexShape = options.getVertexShape(vertex);
|
||||
Color vertexColor = options.getVertexColor(vertex);
|
||||
String labelText = options.getVertexLabel(vertex);
|
||||
|
||||
return createImage(vertexShape, labelText, vertexColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vertexChanged(AttributedVertex vertex) {
|
||||
iconCache.remove(vertex);
|
||||
}
|
||||
|
||||
private ImageIcon createImage(VertexShape vertexShape, String vertexName, Color vertexColor) {
|
||||
prepareLabel(vertexName, vertexColor);
|
||||
|
||||
Shape unitShape = vertexShape.getShape();
|
||||
Rectangle bounds = unitShape.getBounds();
|
||||
|
||||
int maxWidthToHeightRatio = vertexShape.getMaxWidthToHeightRatio();
|
||||
double sizeFactor = vertexShape.getShapeToLabelRatio();
|
||||
|
||||
int labelWidth = label.getWidth();
|
||||
int labelHeight = label.getHeight();
|
||||
|
||||
int iconWidth =
|
||||
(int) (Math.max(labelWidth, labelHeight * 2.0) * sizeFactor) + strokeThickness;
|
||||
int iconHeight =
|
||||
(int) (Math.max(label.getHeight(), labelWidth / maxWidthToHeightRatio) * sizeFactor) +
|
||||
strokeThickness;
|
||||
|
||||
|
||||
double scalex = iconWidth / bounds.getWidth();
|
||||
double scaley = iconHeight / bounds.getHeight();
|
||||
|
||||
|
||||
Shape scaledShape =
|
||||
AffineTransform.getScaleInstance(scalex, scaley).createTransformedShape(unitShape);
|
||||
|
||||
double labelOffsetRatio = vertexShape.getLabelPosition();
|
||||
|
||||
bounds = scaledShape.getBounds();
|
||||
|
||||
int width = bounds.width + 2 * strokeThickness;
|
||||
int height = bounds.height + strokeThickness;
|
||||
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
|
||||
Graphics2D graphics = bufferedImage.createGraphics();
|
||||
graphics.setRenderingHints(renderingHints);
|
||||
AffineTransform graphicsTransform = graphics.getTransform();
|
||||
|
||||
graphics.translate(-bounds.x + strokeThickness, -bounds.y + strokeThickness / 2);
|
||||
graphics.setPaint(Color.WHITE);
|
||||
graphics.fill(scaledShape);
|
||||
graphics.setPaint(vertexColor);
|
||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
||||
graphics.draw(scaledShape);
|
||||
|
||||
|
||||
graphics.setTransform(graphicsTransform);
|
||||
int xOffset = (width - label.getWidth()) / 2;
|
||||
int yOffset = (int) ((height - label.getHeight()) * labelOffsetRatio);
|
||||
graphics.translate(xOffset, yOffset);
|
||||
graphics.setPaint(Color.black);
|
||||
label.paint(graphics);
|
||||
|
||||
graphics.setTransform(graphicsTransform); // restore the original transform
|
||||
graphics.dispose();
|
||||
Image scaledImage =
|
||||
ImageUtils.createScaledImage(bufferedImage, width * ICON_ZOOM, height * ICON_ZOOM,
|
||||
Image.SCALE_FAST);
|
||||
|
||||
ImageIcon imageIcon = new ImageIcon(scaledImage);
|
||||
return imageIcon;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void prepareLabel(String vertexName, Color vertexColor) {
|
||||
label.setFont(options.getFont());
|
||||
label.setText(vertexName);
|
||||
Dimension labelSize = label.getPreferredSize();
|
||||
label.setSize(labelSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFavoredEdgeType() {
|
||||
return options.getFavoredEdgeType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEdgePriority(String edgeType) {
|
||||
return options.getEdgePriority(edgeType);
|
||||
}
|
||||
|
||||
private Stroke getEdgeStroke(AttributedEdge edge) {
|
||||
return edgeStroke;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getVertexSelectionColor() {
|
||||
return options.getVertexSelectionColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getEdgeSelectionColor() {
|
||||
return options.getEdgeSelectionColor();
|
||||
}
|
||||
|
||||
private Font getFont(AttributedVertex attributedvertex1) {
|
||||
return options.getFont();
|
||||
}
|
||||
}
|
|
@ -15,44 +15,42 @@
|
|||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.GraphType;
|
||||
|
||||
/**
|
||||
* {@code Comparator} to order {@code AttributedEdge}s based on their position in a
|
||||
* supplied {@code List}.
|
||||
*
|
||||
* Edge comparator that compares edges based on their edge type. The default renderer will use
|
||||
* the order in which the edge types were defined in the {@link GraphType}.
|
||||
*/
|
||||
public class EdgeComparator implements Comparator<AttributedEdge> {
|
||||
|
||||
/**
|
||||
* {@code Map} of EdgeType attribute value to integer priority
|
||||
*/
|
||||
private Map<String, Integer> edgePriorityMap = new HashMap();
|
||||
private GraphRenderer renderer;
|
||||
|
||||
/**
|
||||
* Create an instance and place the list values into the {@code edgePriorityMap}
|
||||
* with a one-up counter expressing their relative priority
|
||||
* @param edgePriorityList
|
||||
*/
|
||||
public EdgeComparator(List<String> edgePriorityList) {
|
||||
edgePriorityList.forEach(s -> edgePriorityMap.put(s, edgePriorityList.indexOf(s)));
|
||||
public EdgeComparator(GraphRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Compares the {@code AttributedEdge}s using their priority in the supplied {@code edgePriorityMap}
|
||||
*/
|
||||
@Override
|
||||
public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) {
|
||||
return priority(edgeOne).compareTo(priority(edgeTwo));
|
||||
public int compare(AttributedEdge edge1, AttributedEdge edge2) {
|
||||
String edgeType1 = edge1.getEdgeType();
|
||||
String edgeType2 = edge2.getEdgeType();
|
||||
|
||||
if (edgeType1 == null && edgeType2 == null) {
|
||||
return 0;
|
||||
}
|
||||
if (edgeType1 == null) {
|
||||
return 1;
|
||||
}
|
||||
if (edgeType2 == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Integer priority1 = renderer.getEdgePriority(edgeType1);
|
||||
Integer priority2 = renderer.getEdgePriority(edgeType2);
|
||||
|
||||
return priority1.compareTo(priority2);
|
||||
}
|
||||
|
||||
private Integer priority(AttributedEdge e) {
|
||||
return edgePriorityMap.getOrDefault(e.getAttribute("EdgeType"), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,210 +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 java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
public class GhidraIconCache {
|
||||
|
||||
private static final int DEFAULT_STROKE_THICKNESS = 12;
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
private static final String DEFAULT_FONT_NAME = "Dialog";
|
||||
private static final int DEFAULT_MARGIN_BORDER_SIZE = 8;
|
||||
private static final float LABEL_TO_ICON_PROPORTION = 1.1f;
|
||||
private final JLabel rendererLabel = new JLabel();
|
||||
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
||||
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
|
||||
|
||||
private final Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
|
||||
|
||||
private final IconShape.Function iconShapeFunction = new IconShape.Function();
|
||||
private String preferredVeretxLabelAttribute = null;
|
||||
|
||||
Icon get(AttributedVertex vertex) {
|
||||
|
||||
// WARNING: very important to not use map's computeIfAbsent() method
|
||||
// because the map is synchronized and the createIcon() method will
|
||||
// attempt to acquire the AWT lock. That combination will cause a deadlock
|
||||
// if computeIfAbsent() is used and this method is called from non-swing thread.
|
||||
Icon icon = map.get(vertex);
|
||||
if (icon == null) {
|
||||
icon = createIcon(vertex);
|
||||
map.put(vertex, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
GhidraIconCache() {
|
||||
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
private Icon createIcon(AttributedVertex vertex) {
|
||||
rendererLabel
|
||||
.setText(ProgramGraphFunctions.getLabel(vertex, preferredVeretxLabelAttribute));
|
||||
rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE));
|
||||
rendererLabel.setForeground(Color.black);
|
||||
rendererLabel.setBackground(Color.white);
|
||||
rendererLabel.setOpaque(true);
|
||||
Border lineBorder = BorderFactory.createLineBorder((Color) Colors.getColor(vertex), 2);
|
||||
Border marginBorder = BorderFactory.createEmptyBorder(DEFAULT_MARGIN_BORDER_SIZE,
|
||||
DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE);
|
||||
rendererLabel.setBorder(new CompoundBorder(lineBorder, marginBorder));
|
||||
|
||||
Dimension labelSize = rendererLabel.getPreferredSize();
|
||||
rendererLabel.setSize(labelSize);
|
||||
Shape shape = ProgramGraphFunctions.getVertexShape(vertex);
|
||||
|
||||
IconShape.Type shapeType = iconShapeFunction.apply(shape);
|
||||
|
||||
return createImageIcon(vertex, shapeType, rendererLabel, labelSize, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the shape and characteristics of the vertex label (color, text) create and cache an ImageIcon
|
||||
* that will be used to draw the vertex
|
||||
*
|
||||
* @param vertex the vertex to draw (and the key for the cache)
|
||||
* @param vertexShapeCategory the type of Ghidra vertex shape
|
||||
* @param label the {@link JLabel} used to draw the label. Note that it will parse html for formatting.
|
||||
* @param labelSize the dimensions of the JLabel after it has been parsed
|
||||
* @param vertexShape the primitive {@link Shape} used to represent the vertex
|
||||
*/
|
||||
private Icon createImageIcon(AttributedVertex vertex, IconShape.Type vertexShapeCategory,
|
||||
JLabel label, Dimension labelSize, Shape vertexShape) {
|
||||
int offset = 0;
|
||||
double scalex;
|
||||
double scaley;
|
||||
switch (vertexShapeCategory) {
|
||||
// triangles have a non-zero +/- yoffset instead of centering the label
|
||||
case TRIANGLE:
|
||||
// scale the vertex shape
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
|
||||
LABEL_TO_ICON_PROPORTION;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
|
||||
LABEL_TO_ICON_PROPORTION;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
||||
break;
|
||||
case INVERTED_TRIANGLE:
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
|
||||
LABEL_TO_ICON_PROPORTION;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
|
||||
LABEL_TO_ICON_PROPORTION;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
||||
break;
|
||||
|
||||
// rectangles can fit a full-sized label
|
||||
case RECTANGLE:
|
||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth();
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight();
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
break;
|
||||
|
||||
// diamonds and ellipses reduce the label size to fit
|
||||
case DIAMOND:
|
||||
default: // ELLIPSE
|
||||
scalex =
|
||||
labelSize.getWidth() / vertexShape.getBounds().getWidth() * 1.1;
|
||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 1.1;
|
||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
||||
.createTransformedShape(vertexShape);
|
||||
break;
|
||||
}
|
||||
Rectangle vertexBounds = vertexShape.getBounds();
|
||||
|
||||
BufferedImage bufferedImage = new BufferedImage(vertexBounds.width + (2 * strokeThickness),
|
||||
vertexBounds.height + (2 * strokeThickness), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D graphics = bufferedImage.createGraphics();
|
||||
graphics.setRenderingHints(renderingHints);
|
||||
AffineTransform graphicsTransform = graphics.getTransform();
|
||||
|
||||
// draw the shape, offset by 1/2 its width and the strokeThickness
|
||||
AffineTransform offsetTransform =
|
||||
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
|
||||
strokeThickness + vertexBounds.height / 2.0);
|
||||
offsetTransform.preConcatenate(graphicsTransform);
|
||||
graphics.setTransform(offsetTransform);
|
||||
graphics.setPaint(Color.white);
|
||||
graphics.fill(vertexShape);
|
||||
graphics.setPaint(Colors.getColor(vertex));
|
||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
||||
graphics.draw(vertexShape);
|
||||
|
||||
// draw the JLabel, offset by 1/2 its width and the strokeThickness
|
||||
int xoffset = strokeThickness + (vertexBounds.width - labelSize.width) / 2;
|
||||
int yoffset = strokeThickness + (vertexBounds.height - labelSize.height) / 2;
|
||||
offsetTransform = AffineTransform.getTranslateInstance(xoffset, yoffset + offset);
|
||||
offsetTransform.preConcatenate(graphicsTransform);
|
||||
graphics.setPaint(Color.black);
|
||||
graphics.setTransform(offsetTransform);
|
||||
label.paint(graphics);
|
||||
// draw the shape again, but lighter (on top of the label)
|
||||
offsetTransform =
|
||||
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
|
||||
strokeThickness + vertexBounds.height / 2.0);
|
||||
offsetTransform.preConcatenate(graphicsTransform);
|
||||
graphics.setTransform(offsetTransform);
|
||||
Paint paint = Colors.getColor(vertex);
|
||||
if (paint instanceof Color) {
|
||||
Color color = (Color) paint;
|
||||
Color transparent = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50);
|
||||
graphics.setPaint(transparent);
|
||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
||||
graphics.draw(vertexShape);
|
||||
}
|
||||
|
||||
graphics.setTransform(graphicsTransform); // restore the original transform
|
||||
graphics.dispose();
|
||||
return new ImageIcon(bufferedImage);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* evict the passed vertex from the cache so that it will be recomputed
|
||||
* with presumably changed values
|
||||
* @param vertex to remove from the cache
|
||||
*/
|
||||
public void evict(AttributedVertex vertex) {
|
||||
map.remove(vertex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertex label to the value of the passed attribute name
|
||||
* @param attributeName the attribute key for the vertex label value to be displayed
|
||||
*/
|
||||
public void setPreferredVertexLabelAttribute(String attributeName) {
|
||||
this.preferredVeretxLabelAttribute = attributeName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.jungrapht.visualization.VisualizationViewer;
|
||||
|
||||
import ghidra.service.graph.*;
|
||||
|
||||
/**
|
||||
* Interface for GraphRenderer used by the {@link DefaultGraphDisplay}. Developers can add new
|
||||
* implementations to change the graph rendering
|
||||
*/
|
||||
public interface GraphRenderer {
|
||||
|
||||
/**
|
||||
* Initializes the {@link VisualizationViewer}. When a new {@link DefaultGraphDisplay} is
|
||||
* created, it uses a JungraphT {@link VisualizationViewer} to display a graph. That viewer
|
||||
* has many configuration settings. The GraphRender needs to initialize the viewer so that
|
||||
* it calls back to this renderer to get the vertex and edge data/functions that it needs
|
||||
* to render a graph. This is how the GraphRender can inject is display style into the graph
|
||||
* display.
|
||||
* <P>
|
||||
* @param viewer the {@link VisualizationViewer}
|
||||
*/
|
||||
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer);
|
||||
|
||||
/**
|
||||
* Sets the graph display options that are specific to a particular graph type
|
||||
* @param options the {@link GraphDisplayOptions} which are options for a specific graph type
|
||||
*/
|
||||
public void setGraphTypeDisplayOptions(GraphDisplayOptions options);
|
||||
|
||||
/**
|
||||
* Returns the current {@link GraphDisplayOptions} being used
|
||||
* @return the current {@link GraphDisplayOptions} being used
|
||||
*/
|
||||
public GraphDisplayOptions getGraphDisplayOptions();
|
||||
|
||||
/**
|
||||
* Tells this renderer that the given vertex changed and needs to be redrawn
|
||||
* @param vertex the vertex that changed
|
||||
*/
|
||||
public void vertexChanged(AttributedVertex vertex);
|
||||
|
||||
/**
|
||||
* Returns the favored edge type
|
||||
* @return the favored edge type
|
||||
*/
|
||||
public String getFavoredEdgeType();
|
||||
|
||||
/**
|
||||
* Returns the edge priority for the edge type
|
||||
* @param edgeType the edge type to get priority for
|
||||
* @return the edge priority for the edge type
|
||||
*/
|
||||
public Integer getEdgePriority(String edgeType);
|
||||
|
||||
/**
|
||||
* Clears any cached renderings
|
||||
*/
|
||||
public void clearCache();
|
||||
|
||||
/**
|
||||
* Returns the vertex selection color
|
||||
* @return the vertex selection color
|
||||
*/
|
||||
public Color getVertexSelectionColor();
|
||||
|
||||
/**
|
||||
* Returns the edge selection color
|
||||
* @return the edge selection color
|
||||
*/
|
||||
public Color getEdgeSelectionColor();
|
||||
|
||||
}
|
|
@ -46,8 +46,7 @@ public class GroupVertex extends AttributedVertex {
|
|||
super(id);
|
||||
this.first = first;
|
||||
this.children = children;
|
||||
setAttribute("VertexType", "Collapsed");
|
||||
setAttribute("Icon", "Star");
|
||||
setVertexType("Collapsed Group");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,104 +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 java.awt.Shape;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds the enum for shape type and the Function to categorize the archetype Shapes into
|
||||
* IconShape.Types. Note that the archetype shapes are centered at the origin
|
||||
*/
|
||||
public class IconShape {
|
||||
|
||||
public enum Type {
|
||||
TRIANGLE, INVERTED_TRIANGLE, RECTANGLE, DIAMOND, ELLIPSE
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize the supplied Shape into one of several simple types.
|
||||
*
|
||||
*/
|
||||
static class Function implements java.util.function.Function<Shape, Type> {
|
||||
|
||||
@Override
|
||||
public Type apply(Shape shape) {
|
||||
List<Point2D> points = getShapePoints(shape);
|
||||
if (points.size() == 3) {
|
||||
if (isInvertedTriangle(points)) {
|
||||
return Type.INVERTED_TRIANGLE;
|
||||
} else {
|
||||
return Type.TRIANGLE;
|
||||
}
|
||||
}
|
||||
// there are 5 points because the final point is the same as the first
|
||||
// and closes the shape.
|
||||
if (points.size() == 5) {
|
||||
if (isDiamond(points)) {
|
||||
return Type.DIAMOND;
|
||||
} else {
|
||||
return Type.RECTANGLE;
|
||||
}
|
||||
}
|
||||
// default to ellipse for anything with more that 4 sides
|
||||
return Type.ELLIPSE;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Note that for awt drawing, the origin is at the upper left so positive y extends downwards.
|
||||
* @param threePoints odd number of points bounding a {@link Shape} centered at the origin
|
||||
* @return true it there are fewer points with y below 0
|
||||
*/
|
||||
boolean isInvertedTriangle(List<Point2D> threePoints) {
|
||||
if (threePoints.size() != 3) {
|
||||
throw new IllegalArgumentException("Shape from " + threePoints + " is not a triangle");
|
||||
}
|
||||
return threePoints.stream().filter(p -> p.getY() < 0).count() <= threePoints.size() / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fivePoints odd number of points bounding a {@link Shape} centered at the origin
|
||||
* @return true it there are 2 points with y value 0
|
||||
*/
|
||||
boolean isDiamond(List<Point2D> fivePoints) {
|
||||
if (fivePoints.size() != 5) {
|
||||
throw new IllegalArgumentException(
|
||||
"Shape from " + fivePoints + " is not a quadrilateral");
|
||||
}
|
||||
return fivePoints.stream().filter(p -> (int) p.getY() == 0).count() == 2;
|
||||
}
|
||||
|
||||
List<Point2D> getShapePoints(Shape shape) {
|
||||
float[] seg = new float[6];
|
||||
List<Point2D> points = new ArrayList<>();
|
||||
for (PathIterator i = shape.getPathIterator(null, 1); !i.isDone(); i.next()) {
|
||||
int ret = i.currentSegment(seg);
|
||||
if (ret == PathIterator.SEG_MOVETO) {
|
||||
points.add(new Point2D.Float(seg[0], seg[1]));
|
||||
}
|
||||
else if (ret == PathIterator.SEG_LINETO) {
|
||||
points.add(new Point2D.Float(seg[0], seg[1]));
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import static ghidra.service.graph.LayoutAlgorithmNames.*;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -23,6 +25,8 @@ import org.jungrapht.visualization.layout.algorithms.*;
|
|||
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
|
||||
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
|
@ -36,61 +40,32 @@ import ghidra.service.graph.AttributedVertex;
|
|||
class LayoutFunction
|
||||
implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
|
||||
|
||||
static final String KAMADA_KAWAI = "Force Balanced";
|
||||
static final String FRUCTERMAN_REINGOLD = "Force Directed";
|
||||
static final String CIRCLE = "Circle";
|
||||
static final String TIDIER_TREE = "Compact Hierarchical";
|
||||
static final String TIDIER_RADIAL_TREE = "Compact Radial";
|
||||
static final String MIN_CROSS = "Hierarchical MinCross"; //not an alg, just a parent category
|
||||
static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
|
||||
static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
|
||||
static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
|
||||
static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
|
||||
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross"; //not an alg, just a parent category
|
||||
static final String VERT_MIN_CROSS_TOP_DOWN = "Vertical Hierarchical MinCross Top Down";
|
||||
static final String VERT_MIN_CROSS_LONGEST_PATH = "Vertical Hierarchical MinCross Longest Path";
|
||||
static final String VERT_MIN_CROSS_NETWORK_SIMPLEX = "Vertical Hierarchical MinCross Network Simplex";
|
||||
static final String VERT_MIN_CROSS_COFFMAN_GRAHAM = "Vertical Hierarchical MinCross Coffman Graham";
|
||||
static final String TREE = "Hierarchical";
|
||||
static final String RADIAL = "Radial";
|
||||
static final String BALLOON = "Balloon";
|
||||
static final String GEM = "GEM";
|
||||
|
||||
Predicate<AttributedEdge> favoredEdgePredicate;
|
||||
Comparator<AttributedEdge> edgeTypeComparator;
|
||||
|
||||
LayoutFunction(Comparator<AttributedEdge> edgeTypeComparator, Predicate<AttributedEdge> favoredEdgePredicate) {
|
||||
this.edgeTypeComparator = edgeTypeComparator;
|
||||
this.favoredEdgePredicate = favoredEdgePredicate;
|
||||
LayoutFunction(GraphRenderer renderer) {
|
||||
this.edgeTypeComparator = new EdgeComparator(renderer);
|
||||
this.favoredEdgePredicate =
|
||||
edge -> Objects.equal(edge.getEdgeType(), renderer.getFavoredEdgeType());
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
return new String[] { TIDIER_TREE, TREE,
|
||||
TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH,
|
||||
MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE,
|
||||
VERT_MIN_CROSS_TOP_DOWN,
|
||||
VERT_MIN_CROSS_LONGEST_PATH,
|
||||
VERT_MIN_CROSS_NETWORK_SIMPLEX,
|
||||
VERT_MIN_CROSS_COFFMAN_GRAHAM,
|
||||
KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
|
||||
switch(name) {
|
||||
case GEM:
|
||||
return GEMLayoutAlgorithm.edgeAwareBuilder();
|
||||
case KAMADA_KAWAI:
|
||||
case FORCED_BALANCED:
|
||||
return KKLayoutAlgorithm.<AttributedVertex> builder()
|
||||
.preRelaxDuration(1000);
|
||||
case FRUCTERMAN_REINGOLD:
|
||||
case FORCE_DIRECTED:
|
||||
return FRLayoutAlgorithm.<AttributedVertex> builder()
|
||||
.repulsionContractBuilder(BarnesHutFRRepulsion.builder());
|
||||
case CIRCLE:
|
||||
return CircleLayoutAlgorithm.<AttributedVertex> builder()
|
||||
.reduceEdgeCrossing(false);
|
||||
case TIDIER_RADIAL_TREE:
|
||||
case COMPACT_RADIAL:
|
||||
return TidierRadialTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator);
|
||||
|
@ -146,10 +121,10 @@ class LayoutFunction
|
|||
return BalloonLayoutAlgorithm
|
||||
.<AttributedVertex> builder()
|
||||
.verticalVertexSpacing(300);
|
||||
case TREE:
|
||||
case HIERACHICAL:
|
||||
return EdgeAwareTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge>edgeAwareBuilder();
|
||||
case TIDIER_TREE:
|
||||
case COMPACT_HIERACHICAL:
|
||||
default:
|
||||
return TidierTreeLayoutAlgorithm
|
||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||
|
|
|
@ -15,10 +15,6 @@
|
|||
*/
|
||||
package ghidra.graph.visualization;
|
||||
|
||||
import static ghidra.graph.visualization.LayoutFunction.*;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
@ -30,8 +26,7 @@ import org.jungrapht.visualization.layout.model.Rectangle;
|
|||
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
|
||||
import org.jungrapht.visualization.util.LayoutPaintable;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
import ghidra.service.graph.*;
|
||||
|
||||
/**
|
||||
* Manages the selection and transition from one {@link LayoutAlgorithm} to another
|
||||
|
@ -73,14 +68,13 @@ class LayoutTransitionManager {
|
|||
public LayoutTransitionManager(
|
||||
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
|
||||
Predicate<AttributedVertex> rootPredicate,
|
||||
List<String> edgeTypePriorityList,
|
||||
Predicate<AttributedEdge> favoredEdgePredicate) {
|
||||
GraphRenderer renderer) {
|
||||
|
||||
this.visualizationServer = visualizationServer;
|
||||
this.rootPredicate = rootPredicate;
|
||||
this.renderContext = visualizationServer.getRenderContext();
|
||||
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
|
||||
this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList),
|
||||
favoredEdgePredicate);
|
||||
this.layoutFunction = new LayoutFunction(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +138,7 @@ class LayoutTransitionManager {
|
|||
*/
|
||||
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
|
||||
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
|
||||
layoutFunction.apply(TIDIER_TREE).build();
|
||||
layoutFunction.apply(LayoutAlgorithmNames.COMPACT_HIERACHICAL).build();
|
||||
|
||||
if (initialLayoutAlgorithm instanceof TreeLayout) {
|
||||
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
|
||||
|
@ -157,11 +151,4 @@ class LayoutTransitionManager {
|
|||
return initialLayoutAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies a {@code String[]} array of the supported layout names
|
||||
* @return
|
||||
*/
|
||||
public String[] getLayoutNames() {
|
||||
return layoutFunction.getNames();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,140 +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 static org.jungrapht.visualization.layout.util.PropertyLoader.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jungrapht.visualization.util.ShapeFactory;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import ghidra.service.graph.Attributed;
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
|
||||
/**
|
||||
* a container for various functions used by ProgramGraph
|
||||
*/
|
||||
abstract class ProgramGraphFunctions {
|
||||
static float edgeWidth = Float.parseFloat(System.getProperty(PREFIX + "edgeWidth", "4.0f"));
|
||||
|
||||
// cannot instantiate nor extend
|
||||
private ProgramGraphFunctions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* a default implementation of a {@link ShapeFactory} to supply shapes for attributed vertices and edges
|
||||
*/
|
||||
private static ShapeFactory<Attributed> shapeFactory = new ShapeFactory<>(n -> 50, n -> 1.0f);
|
||||
|
||||
/**
|
||||
* return various 'Shapes' based on an attribute name
|
||||
*
|
||||
* @param n the attributed key (a vertex or edge)
|
||||
* @param name the attribute name
|
||||
* @return a Shape for the passed 'n' with attribute 'name'
|
||||
*/
|
||||
private static Shape byShapeName(Attributed n, String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
switch (name) {
|
||||
case "Square":
|
||||
return shapeFactory.getRectangle(n);
|
||||
case "Circle":
|
||||
return shapeFactory.getEllipse(n);
|
||||
case "Triangle":
|
||||
return shapeFactory.getRegularPolygon(n, 3);
|
||||
case "TriangleDown":
|
||||
return shapeFactory.getRegularPolygon(n, 3, Math.PI);
|
||||
case "Diamond":
|
||||
return shapeFactory.getRectangle(n, Math.PI / 4);
|
||||
case "Star":
|
||||
return shapeFactory.getRegularStar(n, 5);
|
||||
case "Pentagon":
|
||||
return shapeFactory.getRegularPolygon(n, 5);
|
||||
case "Hexagon":
|
||||
return shapeFactory.getRegularPolygon(n, 6);
|
||||
case "Octagon":
|
||||
return shapeFactory.getRegularPolygon(n, 8);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the Shape object to use when drawing this vertex. If "Icon" attribute
|
||||
* is set it will use that, otherwise "VertexType" to will translate a code flow
|
||||
* name to a shape
|
||||
*
|
||||
* @param vertex the Attributed object to get a shape for
|
||||
* @return a Shape object to use when displaying the object
|
||||
*/
|
||||
public static Shape getVertexShape(Attributed vertex) {
|
||||
Shape shape = byShapeName(vertex, vertex.getAttribute("Icon"));
|
||||
if (shape != null) {
|
||||
return shape;
|
||||
}
|
||||
String vertexType = vertex.getAttribute("VertexType");
|
||||
if (vertexType == null) {
|
||||
return shapeFactory.getRectangle(vertex);
|
||||
}
|
||||
switch (vertexType) {
|
||||
case "Entry":
|
||||
return shapeFactory.getRegularPolygon(vertex, 3, Math.PI);
|
||||
case "Exit":
|
||||
return shapeFactory.getRegularPolygon(vertex, 3);
|
||||
case "Switch":
|
||||
return shapeFactory.getRectangle(vertex, Math.PI / 4);
|
||||
case "Body":
|
||||
case "External":
|
||||
return shapeFactory.getRectangle(vertex);
|
||||
default:
|
||||
return shapeFactory.getEllipse(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link Stroke} (line width and style) for an attributed edge
|
||||
* @param edge the edge to get a stroke value
|
||||
* @return the stroke for the edge
|
||||
*/
|
||||
public static Stroke getEdgeStroke(AttributedEdge edge) {
|
||||
String edgeType = edge.getAttribute("EdgeType");
|
||||
if (edgeType != null && edgeType.equals("Fall-Through")) {
|
||||
return new BasicStroke(edgeWidth * 2);
|
||||
}
|
||||
return new BasicStroke(edgeWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a display label from an {@link Attributed} object (vertex)
|
||||
* @param attributed the attributed object to get a label for
|
||||
* @param preferredLabelAttribute the attribute to use for the label, if available
|
||||
* @return the label for the given {@link Attributed}
|
||||
*/
|
||||
public static String getLabel(Attributed attributed, String preferredLabelAttribute) {
|
||||
Map<String, String> map = attributed.getAttributeMap();
|
||||
String name = StringEscapeUtils.escapeHtml4(map.get("Name"));
|
||||
if (map.containsKey(preferredLabelAttribute)) {
|
||||
name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute));
|
||||
}
|
||||
return "<html>" + String.join("<p>", Splitter.on('\n').split(name));
|
||||
}
|
||||
}
|
|
@ -73,4 +73,8 @@ public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, Attribute
|
|||
setPluginsLoaded();
|
||||
}
|
||||
|
||||
public boolean allowsEdgeSelection() {
|
||||
return allowEdgeSelection;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ jungrapht.edgeWidth=4.0f
|
|||
# stroke size for the magnifier lens
|
||||
jungrapht.lensStrokeWidth=10.0
|
||||
# when scale is < .1, switch to lightweight rendering
|
||||
jungrapht.lightweightScaleThreshold=.1
|
||||
jungrapht.lightweightScaleThreshold=.01
|
||||
# under 50 vertices will use heavyweight rendering all the time
|
||||
jungrapht.lightweightCountThreshold=80
|
||||
|
||||
|
|
|
@ -277,7 +277,7 @@ public class AttributedGraphExportersTest extends AbstractGenericTest {
|
|||
}
|
||||
|
||||
private AttributedGraph createGraph() {
|
||||
AttributedGraph g = new AttributedGraph();
|
||||
AttributedGraph g = new AttributedGraph("Test", new EmptyGraphType());
|
||||
AttributedVertex vA = g.addVertex("A");
|
||||
AttributedVertex vB = g.addVertex("B");
|
||||
AttributedVertex vC = g.addVertex("C");
|
||||
|
|
|
@ -311,7 +311,7 @@ public class GraphExportTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
private AttributedGraph createGraph() {
|
||||
AttributedGraph graph = new AttributedGraph();
|
||||
AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
|
||||
AttributedVertex vA = graph.addVertex("A");
|
||||
AttributedVertex vB = graph.addVertex("B");
|
||||
AttributedVertex vC = graph.addVertex("C");
|
||||
|
|
|
@ -1,75 +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 static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.service.graph.AttributedEdge;
|
||||
import ghidra.service.graph.AttributedVertex;
|
||||
|
||||
public class ColorsTest {
|
||||
@Test
|
||||
public void testParseHashHexColor() {
|
||||
Color hexColor = Colors.getHexColor("#ff0000");
|
||||
assertEquals(Color.RED, hexColor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseHexColor() {
|
||||
Color hexColor = Colors.getHexColor("0xff0000");
|
||||
assertEquals(Color.RED, hexColor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorFromVertexType() {
|
||||
AttributedVertex vertex = new AttributedVertex("A");
|
||||
vertex.setAttribute("VertexType", "Exit");
|
||||
vertex.setAttribute("Color", "0xffffff");
|
||||
assertEquals(Color.MAGENTA, Colors.getColor(vertex));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorFromVertexNoVertexType() {
|
||||
AttributedVertex vertex = new AttributedVertex("A");
|
||||
vertex.setAttribute("Color", "0xffffff");
|
||||
assertEquals(Color.WHITE, Colors.getColor(vertex));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorFromVertexNoAttributes() {
|
||||
AttributedVertex vertex = new AttributedVertex("A");
|
||||
assertEquals(Color.GREEN, Colors.getColor(vertex));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorFromEdgeType() {
|
||||
AttributedEdge edge = new AttributedEdge("A");
|
||||
edge.setAttribute("EdgeType", "Computed");
|
||||
edge.setAttribute("Color", "0xffffff");
|
||||
assertEquals(Color.CYAN, Colors.getColor(edge));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetColorFromEdgeNoEdgeType() {
|
||||
AttributedEdge edge = new AttributedEdge("A");
|
||||
edge.setAttribute("Color", "0xffffff");
|
||||
assertEquals(Color.WHITE, Colors.getColor(edge));
|
||||
}
|
||||
}
|
|
@ -1,70 +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.AttributedVertex;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public class IconShapeTest {
|
||||
|
||||
private IconShape.Function iconShapeFunction = new IconShape.Function();
|
||||
|
||||
@Test
|
||||
public void testShapes() {
|
||||
|
||||
Rectangle2D rectangle = new Rectangle2D.Double(-10, -10, 20, 20);
|
||||
|
||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(rectangle));
|
||||
|
||||
AttributedVertex v = new AttributedVertex("id", "name");
|
||||
// by vertex type
|
||||
v.setAttribute("VertexType", "Entry");
|
||||
Assert.assertEquals(IconShape.Type.TRIANGLE,
|
||||
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
|
||||
v.setAttribute("VertexType", "Exit");
|
||||
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE,
|
||||
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
|
||||
v.setAttribute("VertexType", "Switch");
|
||||
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("VertexType", "Body");
|
||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("VertexType", "External");
|
||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("VertexType", "Foo");
|
||||
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
|
||||
|
||||
// by vertex icon shape name
|
||||
v.removeAttribute("VertexType");
|
||||
v.setAttribute("Icon", "Square");
|
||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("Icon", "TriangleDown");
|
||||
Assert.assertEquals(IconShape.Type.TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("Icon", "Triangle");
|
||||
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("Icon", "Diamond");
|
||||
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("Icon", "Circle");
|
||||
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
v.setAttribute("Icon", "Foo");
|
||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue