Created the concept of graph types and display options for those graph types.

This commit is contained in:
ghidravore 2021-08-09 13:18:23 -04:00
parent cf293853e8
commit 210cc0bca0
84 changed files with 4102 additions and 1822 deletions

View File

@ -63,7 +63,7 @@ public class DisplayAsGraphAction extends DisplayAsAction {
public void addGraph(ObjectContainer container) {
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
AttributedVertex start = graph.addVertex(container.toString(), container.getName());
graphContainer(container, graph, start);
try {

View File

@ -81,7 +81,7 @@ public class DisplayFilteredGraphAction extends DisplayFilteredAction {
@Override
protected void finishGetOffspring(ObjectContainer container, final List<String> path) {
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
AttributedVertex start = graph.addVertex(container.getName(), container.toString());
graphContainer(container, graph, start);
try {

View File

@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Script to generate graph to test BrandesKopf algorithm
*/
public class GenerateBrandesKopfGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private AttributedGraph graph = new AttributedGraph("test", new EmptyGraphType());
private int nextEdgeID = 1;
@Override

View File

@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Sample script to test graph service
*/
public class GenerateTestGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
private int nextEdgeID = 1;
@Override

View File

@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Example script for creating and displaying a graph in ghidra
*/
public class ExampleGraphServiceScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
private int nextEdgeID = 1;
@Override

View File

@ -107,7 +107,7 @@ public class GraphClassesScript extends GhidraScript {
*/
private AttributedGraph createGraph() throws Exception {
AttributedGraph g = new AttributedGraph();
AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
for (Structure classStructure : classStructures) {

View File

@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.datamgr.actions;
import java.awt.Color;
import ghidra.app.util.ToolTipUtils;
import ghidra.program.model.data.*;
import ghidra.service.graph.*;
@ -35,9 +37,8 @@ public class TypeGraphTask extends Task {
private String graphTitle;
private GraphDisplayProvider graphService;
public static final String TYPE_ATTRIBUTE = "Type";
public static final String EMBEDDED = "Composite";
public static final String POINTER = "Reference";
public static final String COMPOSITE = "Composite";
public static final String REFERENCE = "Reference";
/*
* Constructor
@ -57,8 +58,18 @@ public class TypeGraphTask extends Task {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
GraphType graphType = new GraphTypeBuilder("Data Graph")
.edgeType(REFERENCE)
.edgeType(COMPOSITE)
.build();
AttributedGraph graph = new AttributedGraph();
GraphDisplayOptions options = new GraphDisplayOptionsBuilder(graphType)
.defaultVertexColor(Color.BLUE)
.edge(COMPOSITE, Color.MAGENTA)
.edge(REFERENCE, Color.BLUE)
.build();
AttributedGraph graph = new AttributedGraph(graphTitle, graphType);
try {
if (type instanceof Pointer) {
recursePointer((Pointer) type, graph, null, monitor);
@ -75,7 +86,7 @@ public class TypeGraphTask extends Task {
GraphDisplay display;
try {
display = graphService.getGraphDisplay(false, monitor);
display.setGraph(graph, graphTitle, false, monitor);
display.setGraph(graph, options, graphTitle, false, monitor);
}
catch (GraphException e) {
Msg.showError(this, null, "Data Type Graph Error",
@ -94,11 +105,8 @@ public class TypeGraphTask extends Task {
}
else {
AttributedEdge edge = graph.addEdge(lastVertex, newVertex);
if (edgeType == POINTER) {
edge.setAttribute("Color", "Blue");
}
edge.setAttribute(TYPE_ATTRIBUTE, edgeType);
if (edge.hasAttribute("Weight")) {
edge.setEdgeType(edgeType);
if (edge.hasAttribute(AttributedGraph.WEIGHT)) {
//did this already, don't cycle
return;
}
@ -115,7 +123,7 @@ public class TypeGraphTask extends Task {
recursePointer((Pointer) dt, graph, newVertex, monitor);
}
else if (dt instanceof Composite) {
recurseComposite((Composite) dt, graph, newVertex, EMBEDDED, monitor);
recurseComposite((Composite) dt, graph, newVertex, COMPOSITE, monitor);
}
}
}
@ -135,7 +143,7 @@ public class TypeGraphTask extends Task {
recursePointer((Pointer) ptrType, graph, lastVertex, monitor);
}
else if (ptrType instanceof Composite) {
recurseComposite((Composite) ptrType, graph, lastVertex, POINTER, monitor);
recurseComposite((Composite) ptrType, graph, lastVertex, REFERENCE, monitor);
}
}

View File

@ -148,10 +148,11 @@ public class GraphDisplayBrokerPlugin extends Plugin
}
@Override
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor) throws GraphException {
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException {
if (defaultGraphDisplayProvider != null) {
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor);
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor);
}
return null;
}

View File

@ -15,7 +15,7 @@
*/
package ghidra.app.services;
import java.util.*;
import java.util.List;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
@ -60,25 +60,7 @@ public interface GraphDisplayBroker {
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
public default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException {
return getDefaultGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
}
/**
* A convenience method for getting a {@link GraphDisplay} from the currently active provider
*
* <p>This method allows users to override default graph properties for the graph provider
* being created. See the graph provider implementation for a list of supported properties
*
* @param reuseGraph if true, the provider will attempt to re-use a current graph display
* @param properties a {@code Map} of property key/values that can be used to customize the display
* @param monitor the {@link TaskMonitor} that can be used to cancel the operation
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor)
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException;
/**

View File

@ -0,0 +1,24 @@
/* ###
* 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;
public class BlockFlowGraphType extends ProgramGraphType {
public BlockFlowGraphType() {
super("Block Flow Graph", "Shows program basic block flow");
}
}

View File

@ -0,0 +1,24 @@
/* ###
* 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;
public class CallGraphType extends ProgramGraphType {
public CallGraphType() {
super("Call Graph", "Shows relationships between functions");
}
}

View File

@ -0,0 +1,25 @@
/* ###
* 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;
public class CodeFlowGraphType extends ProgramGraphType {
public CodeFlowGraphType() {
super("Code Flow Graph",
"Shows code block flow (similar to Block Flow graph type, but shows the code in each veretx)");
}
}

View File

@ -0,0 +1,24 @@
/* ###
* 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;
public class DataFlowGraphType extends ProgramGraphType {
public DataFlowGraphType() {
super("Data Flow Graph", "Shows program data relationships");
}
}

View File

@ -0,0 +1,98 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph;
import static ghidra.graph.ProgramGraphType.*;
import static ghidra.service.graph.VertexShape.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplayOptions;
import ghidra.service.graph.VertexShape;
import ghidra.util.WebColors;
/**
* {@link GraphDisplayOptions} for {@link ProgramGraphType}
*/
public class ProgramGraphDisplayOptions extends GraphDisplayOptions {
/**
* constructor
* @param graphType the specific ProgramGraphType subclass for these options
* @param tool if non-null, will load values from tool options
*/
public ProgramGraphDisplayOptions(ProgramGraphType graphType, PluginTool tool) {
super(graphType, tool);
}
@Override
protected void initializeDefaults() {
setDefaultVertexShape(ELLIPSE);
setDefaultVertexColor(WebColors.RED);
setDefaultEdgeColor(WebColors.RED);
setFavoredEdgeType(FALL_THROUGH);
configureVertexType(BODY, RECTANGLE, WebColors.BLUE);
configureVertexType(ENTRY, TRIANGLE_DOWN, WebColors.DARK_ORANGE);
configureVertexType(EXIT, TRIANGLE_UP, WebColors.DARK_MAGENTA);
configureVertexType(SWITCH, DIAMOND, WebColors.DARK_CYAN);
configureVertexType(EXTERNAL, RECTANGLE, WebColors.DARK_GREEN);
configureVertexType(BAD, ELLIPSE, WebColors.RED);
configureVertexType(DATA, ELLIPSE, WebColors.PINK);
configureVertexType(ENTRY_NEXUS, ELLIPSE, WebColors.WHEAT);
configureVertexType(INSTRUCTION, VertexShape.HEXAGON, WebColors.BLUE);
configureVertexType(STACK, RECTANGLE, WebColors.GREEN);
configureEdgeType(ENTRY_EDGE, WebColors.GRAY);
configureEdgeType(FALL_THROUGH, WebColors.BLUE);
configureEdgeType(UNCONDITIONAL_JUMP, WebColors.DARK_GREEN);
configureEdgeType(UNCONDITIONAL_CALL, WebColors.DARK_ORANGE);
configureEdgeType(TERMINATOR, WebColors.PURPLE);
configureEdgeType(JUMP_TERMINATOR, WebColors.PURPLE);
configureEdgeType(INDIRECTION, WebColors.PINK);
configureEdgeType(CONDITIONAL_JUMP, WebColors.DARK_GOLDENROD);
configureEdgeType(CONDITIONAL_CALL, WebColors.DARK_ORANGE);
configureEdgeType(CONDITIONAL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(CONDITIONAL_CALL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(COMPUTED_JUMP, WebColors.CYAN);
configureEdgeType(COMPUTED_CALL, WebColors.CYAN);
configureEdgeType(COMPUTED_CALL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(CONDITIONAL_COMPUTED_CALL, WebColors.CYAN);
configureEdgeType(CONDITIONAL_COMPUTED_JUMP, WebColors.CYAN);
configureEdgeType(CALL_OVERRIDE_UNCONDITIONAL, WebColors.RED);
configureEdgeType(JUMP_OVERRIDE_UNCONDITIONAL, WebColors.RED);
configureEdgeType(CALLOTHER_OVERRIDE_CALL, WebColors.RED);
configureEdgeType(CALLOTHER_OVERRIDE_JUMP, WebColors.RED);
configureEdgeType(READ, WebColors.GREEN);
configureEdgeType(WRITE, WebColors.RED);
configureEdgeType(READ_WRITE, WebColors.DARK_GOLDENROD);
configureEdgeType(UNKNOWN_DATA, WebColors.BLACK);
configureEdgeType(EXTERNAL_REF, WebColors.PURPLE);
configureEdgeType(READ_INDIRECT, WebColors.DARK_GREEN);
configureEdgeType(WRITE_INDIRECT, WebColors.DARK_RED);
configureEdgeType(READ_WRITE_INDIRECT, WebColors.BROWN);
configureEdgeType(DATA_INDIRECT, WebColors.DARK_ORANGE);
configureEdgeType(PARAM, WebColors.CYAN);
configureEdgeType(THUNK, WebColors.BLUE);
}
}

View File

@ -0,0 +1,126 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph;
import java.util.*;
import org.apache.commons.text.WordUtils;
import ghidra.program.model.symbol.RefType;
import ghidra.service.graph.GraphType;
/**
* Defines a common set of vertex and edge types {@link GraphType} for program code and data flow
* graphs. Each specific type of program graph will use a subclass to specifically identify the
* graph type.
*/
public abstract class ProgramGraphType extends GraphType {
private static Map<RefType, String> refTypeToEdgeTypeMap = new HashMap<>();
private static List<String> vertexTypes = new ArrayList<>();
private static List<String> edgeTypes = new ArrayList<>();
//@formatter:off
// Vertex Types
public static final String BODY = vertex("Body");
public static final String ENTRY = vertex("Entry");
public static final String EXIT = vertex("Exit");
public static final String SWITCH = vertex("Switch");
public static final String EXTERNAL = vertex("External");
public static final String BAD = vertex("Bad");
public static final String INSTRUCTION = vertex("Instruction");
public static final String DATA = vertex("Data");
public static final String ENTRY_NEXUS = vertex("Entry-Nexus");
public static final String STACK = vertex("Stack");
// Edge Types - Flow
public static final String ENTRY_EDGE = edge("Entry"); // This edge if for adding an "Entry Nexus" Vertex
public static final String FALL_THROUGH = edge(map(RefType.FALL_THROUGH));
public static final String UNCONDITIONAL_JUMP = edge(map(RefType.UNCONDITIONAL_JUMP));
public static final String UNCONDITIONAL_CALL = edge(map(RefType.UNCONDITIONAL_CALL));
public static final String TERMINATOR = edge(map(RefType.TERMINATOR));
public static final String JUMP_TERMINATOR = edge(map(RefType.JUMP_TERMINATOR));
public static final String INDIRECTION = edge(map(RefType.INDIRECTION));
public static final String CONDITIONAL_JUMP = edge(map(RefType.CONDITIONAL_JUMP));
public static final String CONDITIONAL_CALL = edge(map(RefType.CONDITIONAL_CALL));
public static final String CONDITIONAL_TERMINATOR = edge(map(RefType.CONDITIONAL_TERMINATOR));
public static final String CONDITIONAL_CALL_TERMINATOR =edge(map(RefType.CONDITIONAL_CALL_TERMINATOR));
public static final String COMPUTED_JUMP = edge(map(RefType.COMPUTED_JUMP));
public static final String COMPUTED_CALL = edge(map(RefType.COMPUTED_CALL));
public static final String COMPUTED_CALL_TERMINATOR = edge(map(RefType.COMPUTED_CALL_TERMINATOR));
public static final String CONDITIONAL_COMPUTED_CALL = edge(map(RefType.CONDITIONAL_COMPUTED_CALL));
public static final String CONDITIONAL_COMPUTED_JUMP =edge(map(RefType.CONDITIONAL_COMPUTED_JUMP));
public static final String CALL_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String JUMP_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String CALLOTHER_OVERRIDE_CALL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String CALLOTHER_OVERRIDE_JUMP = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
// Edge Types Data Refs
public static final String READ = edge(map(RefType.READ));
public static final String WRITE = edge(map(RefType.WRITE));
public static final String READ_WRITE = edge(map(RefType.READ_WRITE));
public static final String UNKNOWN_DATA = edge(map(RefType.DATA));
public static final String EXTERNAL_REF = edge(map(RefType.EXTERNAL_REF));
public static final String READ_INDIRECT = edge(map(RefType.READ_IND));
public static final String WRITE_INDIRECT = edge(map(RefType.WRITE_IND));
public static final String READ_WRITE_INDIRECT = edge(map(RefType.READ_WRITE_IND));
public static final String DATA_INDIRECT = edge(map(RefType.DATA_IND));
public static final String PARAM = edge(map(RefType.PARAM));
public static final String THUNK = edge(map(RefType.THUNK));
//@formatter:on
protected ProgramGraphType(String name, String description) {
super(name, description, vertexTypes, edgeTypes);
}
private static String vertex(String vertexType) {
vertexTypes.add(vertexType);
return vertexType;
}
private static String edge(String edgeType) {
edgeTypes.add(edgeType);
return edgeType;
}
private static String map(RefType refType) {
String edgeTypeName = fixup(refType.getName());
refTypeToEdgeTypeMap.put(refType, edgeTypeName);
return edgeTypeName;
}
private static String fixup(String name) {
name = name.replace('_', ' ');
return WordUtils.capitalizeFully(name);
}
public static String getEdgeType(RefType refType) {
return refTypeToEdgeTypeMap.get(refType);
}
@Override
public String getOptionsName() {
return "Program Graph Display Options";
}
}

View File

@ -160,11 +160,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
// get the options panel
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(consoleNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertNotNull(comp);
assertTrue(comp.isShowing());
String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane);
final Component component = findPairedComponent(simpleOptionsPanel, optionName);
final Component component = findPairedComponent(comp, optionName);
assertNotNull(component);
// click the option to toggle its state
@ -244,7 +245,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode);
assertNotNull("Did not find options editor for name: " + simpleName, editor);
assertNotNull("simpleName = " + simpleName, findPairedComponent(editor, simpleName));
assertNotNull("simpleName = " + simpleName,
findPairedComponent(editor.getComponent(), simpleName));
}
}
@ -273,7 +275,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
}
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(parent);
assertNotNull(p);
assertNotNull(findPairedComponent(p, simpleName));
assertNotNull(findPairedComponent(p.getComponent(), simpleName));
}
}
@ -331,9 +333,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color");
Component component = findPairedComponent(comp, "Favorite Color");
assertNotNull(component);
Rectangle rect = component.getBounds();
clickMouse(component, 1, rect.x, rect.y, 2, 0);
@ -366,10 +369,11 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
assertNotNull(ps);
runSwing(() -> ps.setSelectedIndex(0));
assertEquals("LEFT", ps.getSelectedItem());
@ -513,10 +517,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
// change to "LEFT"
runSwing(() -> ps.setSelectedIndex(0));
@ -545,11 +551,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
// change to "LEFT"
runSwing(() -> ps.setSelectedIndex(0));
@ -592,15 +600,17 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode);
assertNotNull(p);
assertTrue(p.isShowing());
JComponent comp = p.getComponent();
JTextField field = (JTextField) findPairedComponent(p, "String Value 1");
assertTrue(comp.isShowing());
JTextField field = (JTextField) findPairedComponent(comp, "String Value 1");
assertNotNull(field);
field = (JTextField) findPairedComponent(p, "String Value 2");
field = (JTextField) findPairedComponent(comp, "String Value 2");
assertNotNull(field);
field = (JTextField) findPairedComponent(p, "String Value 3");
field = (JTextField) findPairedComponent(comp, "String Value 3");
assertNotNull(field);
field = (JTextField) findPairedComponent(p, "Int Value");
field = (JTextField) findPairedComponent(comp, "Int Value");
assertNotNull(field);
}
@ -614,9 +624,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color");
Component component = findPairedComponent(comp, "Favorite Color");
assertNotNull(component);
Rectangle rect = component.getBounds();
clickMouse(component, 1, rect.x, rect.y, 2, 0);
@ -659,9 +670,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing());
JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component canvas = findPairedComponent(simpleOptionsPanel, "Favorite Color");
Component canvas = findPairedComponent(comp, "Favorite Color");
assertNotNull(canvas);
Rectangle rect = canvas.getBounds();
clickMouse(canvas, 1, rect.x, rect.y, 2, 0);
@ -846,7 +858,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
}
private void pressBrowseButton(ScrollableOptionsEditor editor, String optionName) {
Component comp = findPairedComponent(editor, optionName);
Component comp = findPairedComponent(editor.getComponent(), optionName);
assertNotNull(comp);
AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton");
assertNotNull(button);
@ -856,7 +868,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
}
private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) {
Component comp = findPairedComponent(editor, optionName);
Component comp = findPairedComponent(editor.getComponent(), optionName);
assertNotNull(comp);
JTextField tf = findComponent((Container) comp, JTextField.class);
@ -877,7 +889,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(editor);
assertTrue(editor.isShowing());
assertTrue(editor.getComponent().isShowing());
return editor;
}
@ -905,7 +917,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor =
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName);
JCheckBox checkBox = (JCheckBox) findPairedComponent(editor.getComponent(), optionName);
return checkBox.isSelected();
}
@ -914,7 +926,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor =
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
final JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName);
final JCheckBox checkBox =
(JCheckBox) findPairedComponent(editor.getComponent(), optionName);
runSwing(() -> checkBox.setSelected(newValue));
assertEquals(newValue, checkBox.isSelected());
}
@ -923,7 +936,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
throws Exception {
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName);
JTextField textField =
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
return getText(textField);
}
@ -931,7 +945,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
String newValue) throws Exception {
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName);
JTextField textField =
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
setText(textField, newValue);
String updatedText = getText(textField);
@ -952,7 +967,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node);
assertNotNull(editor);
assertTrue(editor.isShowing());
assertTrue(editor.getComponent().isShowing());
return editor;
}
@ -975,7 +990,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode);
assertNotNull(editor);
assertTrue(editor.isShowing());
assertTrue(editor.getComponent().isShowing());
return editor;
}

View File

@ -30,13 +30,21 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.service.graph.*;
import ghidra.util.Msg;
import java.util.*;
import static ghidra.service.graph.GraphDisplay.*;
import ghidra.util.WebColors;
public class GraphAST extends GhidraScript {
protected static final String COLOR_ATTRIBUTE = "Color";
protected static final String ICON_ATTRIBUTE = "Icon";
private static final String SHAPE_ATTRIBUTE = "Shape";
protected static final String DEFAULT = "Default";
protected static final String CONSTANT = "Constant";
protected static final String REGISTER = "Register";
protected static final String UNIQUE = "Unique";
protected static final String PERSISTENT = "Persistent";
protected static final String ADDRESS_TIED = "Address Tied";
protected static final String OP = "Op";
protected static final String TYPE_OUTPUT = "Output";
protected static final String TYPE_INPUT = "Input";
private Function func;
private AttributedGraph graph;
protected HighFunction high;
@ -63,24 +71,48 @@ public class GraphAST extends GhidraScript {
buildAST();
graph = new AttributedGraph();
GraphType graphType = new GraphTypeBuilder("AST")
.vertexType(DEFAULT)
.vertexType(CONSTANT)
.vertexType(REGISTER)
.vertexType(UNIQUE)
.vertexType(PERSISTENT)
.vertexType(ADDRESS_TIED)
.vertexType(OP)
.edgeType(DEFAULT)
.edgeType(TYPE_OUTPUT)
.edgeType(TYPE_INPUT)
.build();
GraphDisplayOptions displayOptions = new GraphDisplayOptionsBuilder(graphType)
.vertexSelectionColor(WebColors.DEEP_PINK)
.edgeSelectionColor(WebColors.DEEP_PINK)
.defaultVertexColor(WebColors.RED)
.defaultEdgeColor(WebColors.NAVY)
.defaultVertexShape(VertexShape.ELLIPSE)
.defaultLayoutAlgorithm("Hierarchical MinCross Coffman Graham")
.useIcons(false)
.labelPosition(GraphLabelPosition.SOUTH)
.shapeOverrideAttribute(SHAPE_ATTRIBUTE)
.vertex(DEFAULT, VertexShape.ELLIPSE, WebColors.RED)
.vertex(CONSTANT, VertexShape.ELLIPSE, WebColors.DARK_GREEN)
.vertex(REGISTER, VertexShape.ELLIPSE, WebColors.NAVY)
.vertex(UNIQUE, VertexShape.ELLIPSE, WebColors.BLACK)
.vertex(PERSISTENT, VertexShape.ELLIPSE, WebColors.DARK_ORANGE)
.vertex(ADDRESS_TIED, VertexShape.ELLIPSE, WebColors.ORANGE)
.vertex(OP, VertexShape.RECTANGLE, WebColors.RED)
.edge(DEFAULT, WebColors.BLUE)
.edge(TYPE_OUTPUT, WebColors.BLACK)
.edge(TYPE_INPUT, WebColors.RED)
.build();
graph = new AttributedGraph("AST Graph", graphType);
buildGraph();
Map<String, String> properties = new HashMap<>();
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put(DISPLAY_VERTICES_AS_ICONS, "false");
properties.put(VERTEX_LABEL_POSITION, "S");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay graphDisplay =
graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor);
// graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); //
// graphDisplay.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
// graphDisplay.defineEdgeAttribute(EDGE_TYPE_ATTRIBUTE);
String description = "AST Data Flow Graph For " + func.getName();
GraphDisplay graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, monitor);
graphDisplay.setGraph(graph, description, false, monitor);
String description = "AST Data Flow Graph For " + func.getName();
graphDisplay.setGraph(graph, displayOptions, description, false, monitor);
// Install a handler so the selection/location will map
graphDisplay.setGraphDisplayListener(
@ -125,34 +157,33 @@ public class GraphAST extends GhidraScript {
protected AttributedVertex createVarnodeVertex(VarnodeAST vn) {
String name = vn.getAddress().toString(true);
String id = getVarnodeKey(vn);
String colorattrib = "Red";
String vertexType = DEFAULT;
if (vn.isConstant()) {
colorattrib = "DarkGreen";
vertexType = CONSTANT;
}
else if (vn.isRegister()) {
colorattrib = "Blue";
vertexType = REGISTER;
Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize());
if (reg != null) {
name = reg.getName();
}
}
else if (vn.isUnique()) {
colorattrib = "Black";
vertexType = UNIQUE;
}
else if (vn.isPersistent()) {
colorattrib = "DarkOrange";
vertexType = PERSISTENT;
}
else if (vn.isAddrTied()) {
colorattrib = "Orange";
vertexType = ADDRESS_TIED;
}
AttributedVertex vert = graph.addVertex(id, name);
vert.setVertexType(vertexType);
// if it is an input override the shape to be a triangle
if (vn.isInput()) {
vert.setAttribute(ICON_ATTRIBUTE, "TriangleDown");
vert.setAttribute(SHAPE_ATTRIBUTE, VertexShape.TRIANGLE_DOWN.getName());
}
else {
vert.setAttribute(ICON_ATTRIBUTE, "Circle");
}
vert.setAttribute(COLOR_ATTRIBUTE, colorattrib);
return vert;
}
@ -176,7 +207,7 @@ public class GraphAST extends GhidraScript {
}
}
AttributedVertex vert = graph.addVertex(id, name);
vert.setAttribute(ICON_ATTRIBUTE, "Square");
vert.setVertexType(OP);
return vert;
}
@ -192,7 +223,9 @@ public class GraphAST extends GhidraScript {
}
protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) {
return graph.addEdge(in, out);
AttributedEdge newEdge = graph.addEdge(in, out);
newEdge.setEdgeType(DEFAULT);
return newEdge;
}
protected void buildGraph() {

View File

@ -77,7 +77,7 @@ public class GraphASTAndFlow extends GraphAST {
}
if (prev != null && map.containsKey(prev) && map.containsKey(next)) {
AttributedEdge edge = createEdge(map.get(prev), map.get(next));
edge.setAttribute(COLOR_ATTRIBUTE, "Black");
edge.setEdgeType(TYPE_OUTPUT);
}
prev = next;
}
@ -92,7 +92,7 @@ public class GraphASTAndFlow extends GraphAST {
PcodeBlock in = block.getIn(i);
if (last.containsKey(in)) {
AttributedEdge edge = createEdge(last.get(in), first.get(block));
edge.setAttribute(COLOR_ATTRIBUTE, "Red");
edge.setEdgeType(TYPE_INPUT);
}
}
}

View File

@ -334,7 +334,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses)
throws CancelledException {
AttributedGraph g = new AttributedGraph();
AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator();
while (recoveredClassIterator.hasNext()) {

View File

@ -15,11 +15,11 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
import java.util.*;
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType;
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType;
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
@ -33,10 +33,10 @@ import ghidra.util.exception.AssertException;
*/
public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
private HighFunction hfunction;
private GraphType graphType;
private AstGraphSubType graphType;
ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction,
GraphType graphType) {
AstGraphSubType graphType) {
super(tool, hfunction.getFunction().getProgram(), display);
this.hfunction = hfunction;
this.graphType = graphType;

View File

@ -15,13 +15,15 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.HashMap;
import static ghidra.graph.ProgramGraphType.*;
import java.util.Iterator;
import java.util.Map;
import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.ProgramGraphDisplayOptions;
import ghidra.graph.ProgramGraphType;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
@ -34,14 +36,12 @@ import ghidra.util.exception.GraphException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
import static ghidra.service.graph.GraphDisplay.*;
public class ASTGraphTask extends Task {
enum GraphType {
enum AstGraphSubType {
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
private String name;
GraphType(String name) {
AstGraphSubType(String name) {
this.name = name;
}
@ -51,35 +51,19 @@ public class ASTGraphTask extends Task {
}
private static final String CODE_ATTRIBUTE = "Code";
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
private static final String VERTEX_TYPE_ATTRIBUTE = "VertexType";
// Vertex Types
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private GraphDisplayBroker graphService;
private boolean newGraph;
private int codeLimitPerBlock;
private Address location;
private HighFunction hfunction;
private GraphType graphType;
private AstGraphSubType astGraphType;
private int uniqueNum = 0;
private PluginTool tool;
public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock,
Address location, HighFunction hfunction, GraphType graphType, PluginTool tool) {
Address location, HighFunction hfunction, AstGraphSubType graphType, PluginTool tool) {
super("Graph " + graphType.getName(), true, false, true);
this.graphService = graphService;
@ -87,32 +71,30 @@ public class ASTGraphTask extends Task {
this.codeLimitPerBlock = codeLimitPerBlock;
this.location = location;
this.hfunction = hfunction;
this.graphType = graphType;
this.astGraphType = graphType;
this.tool = tool;
}
@Override
public void run(TaskMonitor monitor) {
GraphType graphType = new AstGraphType();
// get a new graph
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph(astGraphType.getName(), graphType);
try {
monitor.setMessage("Computing Graph...");
if (graphType == GraphType.DATA_FLOW_GRAPH) {
if (astGraphType == AstGraphSubType.DATA_FLOW_GRAPH) {
createDataFlowGraph(graph, monitor);
}
else {
createControlFlowGraph(graph, monitor);
}
Map<String, String> properties = new HashMap<>();
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor);
GraphDisplay display =
graphService.getDefaultGraphDisplay(!newGraph, monitor);
ASTGraphDisplayListener displayListener =
new ASTGraphDisplayListener(tool, display, hfunction, graphType);
new ASTGraphDisplayListener(tool, display, hfunction, astGraphType);
display.setGraphDisplayListener(displayListener);
monitor.setMessage("Obtaining handle to graph provider...");
@ -122,20 +104,19 @@ public class ASTGraphTask extends Task {
monitor.setCancelEnabled(false);
monitor.setMessage("Rendering Graph...");
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
String description =
graphType == GraphType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
astGraphType == AstGraphSubType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
description = description + " for " + hfunction.getFunction().getName();
display.setGraph(graph, description, false, monitor);
GraphDisplayOptions graphDisplayOptions =
new ProgramGraphDisplayOptions(new AstGraphType(), tool);
graphDisplayOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
display.setGraph(graph, graphDisplayOptions, description, false, monitor);
setGraphLocation(display, displayListener);
}
catch (GraphException e) {
Msg.showError(this, null, "Graph Error", e.getMessage());
Msg.showError(this, null, "Graph Error",
"Can't create graph display: " + e.getMessage());
}
catch (CancelledException e1) {
return;
@ -230,20 +211,20 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op));
String vertexType = BODY_NODE;
String vertexType = BODY;
switch (op.getOpcode()) {
case PcodeOp.BRANCH:
case PcodeOp.BRANCHIND:
case PcodeOp.CBRANCH:
case PcodeOp.CALL:
case PcodeOp.CALLIND:
vertexType = SWITCH_NODE;
vertexType = SWITCH;
break;
case PcodeOp.RETURN:
vertexType = EXIT_NODE;
vertexType = EXIT;
break;
}
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
vertex.setVertexType(vertexType);
}
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node,
@ -278,7 +259,7 @@ public class ASTGraphTask extends Task {
}
label += translateVarnode(node, false);
vertex.setAttribute(CODE_ATTRIBUTE, label);
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, DATA_NODE);
vertex.setVertexType(ProgramGraphType.DATA);
}
protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor)
@ -322,7 +303,7 @@ public class ASTGraphTask extends Task {
}
else {
vertex.setAttribute(CODE_ATTRIBUTE, "<???>");
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, BAD_NODE);
vertex.setVertexType(BAD);
}
}
return vertex;
@ -348,23 +329,23 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(CODE_ATTRIBUTE, buf.toString());
// Establish vertex type
String vertexType = BODY_NODE;
String vertexType = BODY;
if (basicBlk.getInSize() == 0) {
vertexType = ENTRY_NODE;
vertexType = ENTRY;
}
else {
switch (basicBlk.getOutSize()) {
case 0:
vertexType = EXIT_NODE;
vertexType = EXIT;
break;
case 1:
vertexType = BODY_NODE;
vertexType = BODY;
break;
default:
vertexType = SWITCH_NODE;
vertexType = SWITCH;
}
}
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
vertex.setVertexType(vertexType);
}
private String formatOpMnemonic(PcodeOp op) {

View File

@ -0,0 +1,26 @@
/* ###
* 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.app.plugin.core.decompile.actions;
import ghidra.graph.ProgramGraphType;
public class AstGraphType extends ProgramGraphType {
protected AstGraphType() {
super("AST", "Graph to show pcode for function");
}
}

View File

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
import docking.action.MenuData;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;

View File

@ -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:&nbsp; <I>GraphDisplayBrokerPlugin</I></P>
<P class="relatedtopic">Related Topics:</P>

View File

@ -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());

View File

@ -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
}
}

View File

@ -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));

View File

@ -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: &nbsp;" + 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: &nbsp;" + 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", "&nbsp;");
buf.append(split);
buf.append("<br>");
}
}
}

View File

@ -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;
}
}

View File

@ -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() {
@ -420,7 +396,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)
@ -565,6 +541,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")
@ -576,6 +560,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
*/
@ -629,7 +639,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);
}
@ -642,7 +653,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);
@ -658,17 +670,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()));
}
@ -773,7 +785,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,
@ -781,11 +793,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));
@ -837,25 +849,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
@ -876,6 +884,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
listener = null;
componentProvider.closeComponent();
if (graphDisplayOptions != null) {
graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener);
}
}
@Override
@ -1026,8 +1037,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 {
@ -1047,7 +1059,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));
}
/**
@ -1066,7 +1078,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 -> {
@ -1074,7 +1086,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();
});
@ -1084,7 +1096,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 -> {
@ -1092,7 +1104,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();
});
}
@ -1115,36 +1127,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);
}
@ -1156,12 +1153,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) {
@ -1172,7 +1169,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));
}
@ -1256,7 +1253,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();
}
@ -1311,44 +1308,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);
@ -1357,46 +1324,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) {
@ -1445,7 +1378,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) {
@ -1587,39 +1520,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.

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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");
}
/**

View File

@ -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;
}
}
}

View File

@ -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()

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -73,4 +73,8 @@ public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, Attribute
setPluginsLoaded();
}
public boolean allowsEdgeSelection() {
return allowEdgeSelection;
}
}

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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));
}
}

View File

@ -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)));
}
}

View File

@ -489,16 +489,18 @@
</BLOCKQUOTE>
</BLOCKQUOTE>
<BR>
<BR>
<BR>
<BR>
<BR>
</ul>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P>
<H2><A NAME="Program_Graphs_Display_Options">Program Graphs Display Options</H2>
<BLOCKQUOTE>
<P>These are the display options for graphs that are types of "Program Graphs" such as
Call graphs, Block graphs, etc. These types of graphs
use program elements as vertices and reference types as edges. See
<A href="help/topics/GraphServices/GraphDisplay.htm#Graph_Type_Display_Options">Graph Type Display Options</A> for
general help on graph type display options.</P>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>

View File

@ -15,7 +15,8 @@
*/
package ghidra.graph.program;
import java.awt.Color;
import static ghidra.graph.ProgramGraphType.*;
import java.util.*;
import docking.action.builder.ActionBuilder;
@ -23,6 +24,7 @@ import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.util.AddEditDialog;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.*;
import ghidra.program.model.address.*;
import ghidra.program.model.block.*;
import ghidra.program.model.listing.*;
@ -30,7 +32,8 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*;
import ghidra.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.GraphException;
import ghidra.util.task.Task;
@ -53,55 +56,6 @@ public class BlockGraphTask extends Task {
private ColorizingService colorizingService;
/**
* Edge flow tags
*/
protected final static int FALLTHROUGH = 0;
protected final static int CONDITIONAL_RETURN = 1;
protected final static int UNCONDITIONAL_JUMP = 2;
protected final static int CONDITIONAL_JUMP = 3;
protected final static int UNCONDITIONAL_CALL = 4;
protected final static int CONDITIONAL_CALL = 5;
protected final static int TERMINATOR = 6;
protected final static int COMPUTED = 7;
protected final static int INDIRECTION = 8;
protected final static int ENTRY = 9; // from Entry Nexus
protected final static String[] edgeNames =
{ "1", "2", "3", "4", "5", "6", "7", "13", "14", "15" };
// @formatter:off
protected final static String[] edgeTypes = {
"Fall-Through",
"Conditional-Return",
"Unconditional-Jump",
"Conditional-Jump",
"Unconditional-Call",
"Conditional-Call",
"Terminator",
"Computed",
"Indirection",
"Entry"
};
// @formatter:on
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private final static String ENTRY_NEXUS = "Entry-Nexus";
// "7"; //
private final static String EXTERNAL_NODE = "External";
// "8"; // node is external to program
private final static String ENTRY_NEXUS_NAME = "Entry Points";
private CodeBlockModel blockModel;
private AddressSetView selection;
@ -113,15 +67,17 @@ public class BlockGraphTask extends Task {
private Program program;
private AddressSetView graphScope;
private String graphTitle;
private ProgramGraphType graphType;
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode,
boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection,
ProgramLocation location, CodeBlockModel blockModel,
GraphDisplayProvider graphProvider) {
public BlockGraphTask(ProgramGraphType graphType,
boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph,
PluginTool tool, ProgramSelection selection, ProgramLocation location,
CodeBlockModel blockModel, GraphDisplayProvider graphProvider) {
super("Graph Program", true, false, true);
this.graphType = graphType;
this.graphEntryPointNexus = graphEntryPointNexus;
this.showCode = showCode;
this.showCode = graphType instanceof CodeFlowGraphType;
this.reuseGraph = reuseGraph;
this.appendGraph = appendGraph;
this.tool = tool;
@ -131,7 +87,7 @@ public class BlockGraphTask extends Task {
this.selection = selection;
this.location = location;
this.program = blockModel.getProgram();
this.graphTitle = actionName + ": ";
this.graphTitle = graphType.getName() + ": ";
}
/**
@ -140,22 +96,25 @@ public class BlockGraphTask extends Task {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
this.graphScope = getGraphScopeAndGenerateGraphTitle();
AttributedGraph graph = createGraph();
AttributedGraph graph = createGraph(graphTitle);
monitor.setMessage("Generating Graph...");
try {
GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor);
GraphDisplay display =
graphProvider.getGraphDisplay(reuseGraph, monitor);
GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool);
if (showCode) { // arrows need to be bigger as this generates larger vertices
graphOptions.setArrowLength(30);
}
BlockModelGraphDisplayListener listener =
new BlockModelGraphDisplayListener(tool, blockModel, display);
addActions(display, v -> listener.getAddress(v));
display.setGraphDisplayListener(listener);
if (showCode) {
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
codeLimitPerBlock + 1);
graphOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
}
display.setGraph(graph, graphTitle, appendGraph, monitor);
display.setGraph(graph, graphOptions, graphTitle, appendGraph, monitor);
if (location != null) {
// initialize the graph location, but don't have the graph send an event
@ -217,9 +176,9 @@ public class BlockGraphTask extends Task {
codeLimitPerBlock = maxLines;
}
protected AttributedGraph createGraph() throws CancelledException {
protected AttributedGraph createGraph(String name) throws CancelledException {
int blockCount = 0;
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph(name, graphType);
CodeBlockIterator it = getBlockIterator();
List<AttributedVertex> entryPoints = new ArrayList<>();
@ -334,8 +293,7 @@ public class BlockGraphTask extends Task {
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
for (AttributedVertex vertex : entries) {
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
edge.setAttribute("Name", edgeNames[ENTRY]);
edge.setAttribute("EdgeType", edgeTypes[ENTRY]);
edge.setAttribute("EdgeType", ENTRY_NEXUS);
}
}
@ -517,99 +475,36 @@ public class BlockGraphTask extends Task {
}
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
int edgeType;
FlowType flowType = ref.getFlowType();
if (flowType == RefType.FALL_THROUGH) {
edgeType = FALLTHROUGH;
}
else if (flowType == RefType.UNCONDITIONAL_JUMP) {
edgeType = UNCONDITIONAL_JUMP;
}
else if (flowType == RefType.CONDITIONAL_JUMP) {
edgeType = CONDITIONAL_JUMP;
}
else if (flowType == RefType.UNCONDITIONAL_CALL) {
edgeType = UNCONDITIONAL_CALL;
}
else if (flowType == RefType.CONDITIONAL_CALL) {
edgeType = CONDITIONAL_CALL;
}
else if (flowType.isComputed()) {
edgeType = COMPUTED;
}
else if (flowType.isIndirect()) {
edgeType = INDIRECTION;
}
else if (flowType == RefType.TERMINATOR) {
edgeType = TERMINATOR;
}
else { // only FlowType.CONDITIONAL_TERMINATOR remains unchecked
edgeType = CONDITIONAL_RETURN;
}
// set attributes on this edge
edge.setAttribute("Name", edgeNames[edgeType]);
edge.setAttribute("EdgeType", edgeTypes[edgeType]);
edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getFlowType()));
}
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
String vertexType = BODY_NODE;
String vertexType = BODY;
Address firstStartAddress = bb.getFirstStartAddress();
if (firstStartAddress.isExternalAddress()) {
vertexType = EXTERNAL_NODE;
vertexType = EXTERNAL;
}
else if (isEntry) {
vertexType = ENTRY_NODE;
vertexType = ENTRY;
}
else {
FlowType flowType = bb.getFlowType();
if (flowType.isTerminal()) {
vertexType = EXIT_NODE;
vertexType = EXIT;
}
else if (flowType.isComputed()) {
vertexType = SWITCH_NODE;
vertexType = SWITCH;
}
else if (flowType == RefType.INDIRECTION) {
vertexType = DATA_NODE;
vertexType = DATA;
}
else if (flowType == RefType.INVALID) {
vertexType = BAD_NODE;
vertexType = BAD;
}
}
vertex.setAttribute("VertexType", vertexType);
setVertexColor(vertex, vertexType, firstStartAddress);
}
private void setVertexColor(AttributedVertex vertex, String vertexType, Address address) {
if (colorizingService == null) {
return;
}
Color color = colorizingService.getBackgroundColor(address);
if (color == null) {
return;
}
// color format: RGBrrrgggbbb
// -where rrr/ggg/bbb is a three digit int value for each respective color range
String rgb = "RGB" + HTMLUtilities.toRGBString(color);
vertex.setAttribute("Color", rgb); // sets the vertex color
// This value triggers the vertex to be painted with its color and not a
// while background.
if (showCode) {
// our own custom override of Labels/Icons
vertex.setAttribute("VertexType", "ColorFilled");
}
else {
// the default preferences for VertexType
vertex.setAttribute("VertexType", vertexType + ".Filled");
}
vertex.setVertexType(vertexType);
}
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {

View File

@ -15,6 +15,8 @@
*/
package ghidra.graph.program;
import ghidra.graph.DataFlowGraphType;
import ghidra.graph.ProgramGraphType;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference;
@ -53,6 +55,7 @@ public class DataReferenceGraph extends AttributedGraph {
* @param depth the number of hops to graph per call (0 for recursion until no more hops)
*/
public DataReferenceGraph(Program program, int depth) {
super("Data Reference", new DataFlowGraphType());
this.program = program;
this.depthPerStep = depth;
}
@ -106,13 +109,14 @@ public class DataReferenceGraph extends AttributedGraph {
private void setupEdge(AttributedEdge edge, Reference ref) {
edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString());
edge.setAttribute(REF_TYPE_ATTRIBUTE, ref.getReferenceType().toString());
edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getReferenceType()));
if (ref.getSymbolID() != -1) {
edge.setAttribute(REF_SYMBOL_ATTRIBUTE,
program.getSymbolTable().getSymbol(ref.getSymbolID()).getName());
}
}
private void setupVertex(AttributedVertex vertex) {
Address address =
program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE));
@ -122,9 +126,13 @@ public class DataReferenceGraph extends AttributedGraph {
CodeUnit unit = program.getListing().getCodeUnitContaining(address);
if (unit instanceof Data) {
vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName());
vertex.setVertexType(ProgramGraphType.DATA);
}
else if (unit instanceof Instruction) {
vertex.setAttribute("Icon", "TriangleDown");
vertex.setVertexType(ProgramGraphType.INSTRUCTION);
}
else {
vertex.setVertexType(ProgramGraphType.STACK);
}
}

View File

@ -17,6 +17,8 @@ package ghidra.graph.program;
import docking.widgets.EventTrigger;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.DataFlowGraphType;
import ghidra.graph.ProgramGraphDisplayOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit;
@ -35,6 +37,7 @@ import ghidra.util.task.TaskMonitor;
*/
public class DataReferenceGraphTask extends Task {
private static final String VERTEX_COLOR_OVERRIDE = "Color";
private String graphTitle;
private GraphDisplayProvider graphProvider;
private boolean reuseGraph;
@ -117,7 +120,7 @@ public class DataReferenceGraphTask extends Task {
/* TODO
* Want to make initial vertex easy to find, is this the best way?
*/
centerVertex.setAttribute("Color", "Orange");
centerVertex.setAttribute(VERTEX_COLOR_OVERRIDE, "Orange");
}
}
catch (CancelledException e) {
@ -128,19 +131,16 @@ public class DataReferenceGraphTask extends Task {
try {
if (display == null) {
display = graphProvider.getGraphDisplay(reuseGraph, monitor);
display.defineEdgeAttribute(DataReferenceGraph.REF_SOURCE_ATTRIBUTE);
display.defineEdgeAttribute(DataReferenceGraph.REF_TYPE_ATTRIBUTE);
display.defineEdgeAttribute(DataReferenceGraph.REF_SYMBOL_ATTRIBUTE);
display.defineVertexAttribute(DataReferenceGraph.DATA_ATTRIBUTE);
display.setVertexLabelAttribute(DataReferenceGraph.LABEL_ATTRIBUTE,
GraphDisplay.ALIGN_LEFT, 12, true, maxLabelLength);
DataReferenceGraphDisplayListener listener =
new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth);
display.setGraphDisplayListener(listener);
}
display.setGraph(graph, graphTitle, appendGraph, monitor);
GraphDisplayOptions graphDisplayOptions =
new ProgramGraphDisplayOptions(new DataFlowGraphType(), tool);
// set the vertex color override so that we can color "initial" vertices differently
graphDisplayOptions.setVertexColorOverrideAttributeKey(VERTEX_COLOR_OVERRIDE);
display.setGraph(graph, graphDisplayOptions, graphTitle, appendGraph, monitor);
if (location != null) {
// initialize the graph location, but don't have the graph send an event

View File

@ -35,6 +35,7 @@ import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.graph.*;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
@ -106,6 +107,16 @@ public class ProgramGraphPlugin extends ProgramPlugin
public ProgramGraphPlugin(PluginTool tool) {
super(tool, true, true);
intializeOptions();
registerProgramFlowGraphDisplayOptionsWithTool();
}
private void registerProgramFlowGraphDisplayOptionsWithTool() {
ProgramGraphDisplayOptions displayOptions =
new ProgramGraphDisplayOptions(new BlockFlowGraphType(), null);
// this will register Program Flow Graph Type options with the tool
HelpLocation help = new HelpLocation(getName(), "Program Graphs Display Options");
displayOptions.registerOptions(tool.getOptions("Graph"), help);
}
private void intializeOptions() {
@ -315,19 +326,19 @@ public class ProgramGraphPlugin extends ProgramPlugin
}
private void graphBlockFlow() {
graph("Block Flow Graph", blockModelService.getActiveBlockModelName(), false);
graph(new BlockFlowGraphType(), blockModelService.getActiveBlockModelName());
}
private void graphCodeFlow() {
graph("Code Flow Graph", blockModelService.getActiveBlockModelName(), true);
graph(new CodeFlowGraphType(), blockModelService.getActiveBlockModelName());
}
private void graphSubroutines() {
graph("Call Graph", blockModelService.getActiveSubroutineModelName(), false);
graph(new CallGraphType(), blockModelService.getActiveSubroutineModelName());
}
private void graphSubroutinesUsing(String modelName) {
graph("Call Graph (" + modelName + ")", modelName, false);
graph(new CallGraphType(), modelName);
}
private void graphDataReferences() {
@ -342,11 +353,12 @@ public class ProgramGraphPlugin extends ProgramPlugin
graphData(DataReferenceGraph.Directions.FROM_ONLY);
}
private void graph(String actionName, String modelName, boolean showCode) {
private void graph(ProgramGraphType graphType, String modelName) {
try {
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, currentProgram, true);
BlockGraphTask task = new BlockGraphTask(actionName, graphEntryPointNexus, showCode,
BlockGraphTask task =
new BlockGraphTask(graphType, graphEntryPointNexus,
reuseGraph, appendToGraph, tool, currentSelection, currentLocation, model,
defaultGraphService);
task.setCodeLimitPerBlock(codeLimitPerBlock);

View File

@ -15,29 +15,25 @@
*/
package ghidra.feature.vt.gui.wizard;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import docking.options.editor.OptionsEditorPanel;
import docking.wizard.*;
import ghidra.feature.vt.api.main.VTProgramCorrelatorFactory;
import ghidra.feature.vt.api.util.VTOptions;
import ghidra.framework.options.EditorStateFactory;
import ghidra.framework.options.Options;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.MiddleLayout;
import ghidra.util.layout.VerticalLayout;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import util.CollectionUtils;
import docking.options.editor.OptionsEditorPanel;
import docking.wizard.*;
public class OptionsPanel extends AbstractMageJPanel<VTWizardStateKey> {
@ -118,7 +114,7 @@ public class OptionsPanel extends AbstractMageJPanel<VTWizardStateKey> {
if (optionNames.isEmpty()) {
continue;
}
Collections.sort(optionNames);
OptionsEditorPanel optionsPanel =
new OptionsEditorPanel(title, options, optionNames, editorStateFactory);
optionsPanel.setOptionsPropertyChangeListener(propertyChangeListener);
@ -222,13 +218,8 @@ public class OptionsPanel extends AbstractMageJPanel<VTWizardStateKey> {
}
private void applyOptions() {
try {
for (OptionsEditorPanel panel : optionsEditorPanelList) {
panel.apply();
}
}
catch (InvalidInputException e) {
Msg.showError(this, this, "Error", "could not apply options settings", e);
for (OptionsEditorPanel panel : optionsEditorPanelList) {
panel.apply();
}
}

View File

@ -17,9 +17,11 @@ package docking.options.editor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.border.Border;
import docking.help.Help;
@ -27,7 +29,6 @@ import docking.help.HelpService;
import ghidra.framework.options.*;
import ghidra.util.HelpLocation;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.VerticalLayout;
/**
@ -35,7 +36,7 @@ import ghidra.util.layout.VerticalLayout;
* Panel that shows each property in an Options category or a Group in an
* Options category.
*/
public class OptionsEditorPanel extends JPanel implements OptionsEditor {
public class OptionsEditorPanel extends JPanel {
private EditorStateFactory editorStateFactory;
private Options options;
@ -63,12 +64,9 @@ public class OptionsEditorPanel extends JPanel implements OptionsEditor {
this.optionNames = optionNames;
this.title = title;
Collections.sort(optionNames);
create();
}
@Override
public void dispose() {
propertyChangeListener = null;
editorInfoList.clear();
@ -116,33 +114,11 @@ public class OptionsEditorPanel extends JPanel implements OptionsEditor {
setBorder(border);
}
//==================================================================================================
// OptionsEditor Interface Methods
//==================================================================================================
@Override
public void apply() throws InvalidInputException {
public void apply() {
for (EditorState state : editorInfoList) {
state.applyValue();
}
}
@Override
public void cancel() {
// nothing to do
}
@Override
public void reload() {
// nothing to do, as this component is reloaded when options are changed
}
@Override
public JComponent getEditorComponent(Options o, EditorStateFactory factory) {
return this;
}
@Override
public void setOptionsPropertyChangeListener(PropertyChangeListener listener) {
this.propertyChangeListener = listener;
}

View File

@ -341,13 +341,13 @@ public class OptionsPanel extends JPanel {
if (options == null) {
return null;
}
List<String> optionList = node.getOptionNames();
editor = options.getOptionsEditor();
if (editor == null) {
List<String> optionList = node.getOptionNames();
Collections.sort(optionList);
if (optionList.size() > 0) {
editor = new ScrollableOptionsEditor(options.getName(), options, optionList,
editorStateFactory);
editor = new ScrollableOptionsEditor(options.getName(), optionList);
}
}

View File

@ -29,30 +29,36 @@ import ghidra.util.layout.MiddleLayout;
/**
* Panel that shows each property in an Options category or a Group in an Options category
*/
public class ScrollableOptionsEditor extends JScrollPane implements OptionsEditor {
public class ScrollableOptionsEditor implements OptionsEditor {
private OptionsEditorPanel optionsPanel;
private String title;
private List<String> optionNames;
private JScrollPane scrollPane;
private PropertyChangeListener listener;
/**
* Creates a panel for editing the given options
* Creates a panel for editing options. This version of the constructor allows the client
* to specify the option names to put them in some order other than the default alphabetical
* ordering.
*
* @param title The title of the options panel
* @param options the options for this panel
* @param optionNames the names of the options for this panel
* @param editorStateFactory the factory needed by the editor
*/
public ScrollableOptionsEditor(String title, Options options, List<String> optionNames,
EditorStateFactory editorStateFactory) {
public ScrollableOptionsEditor(String title, List<String> optionNames) {
this.title = title;
this.optionNames = optionNames;
optionsPanel = new OptionsEditorPanel(title, options, optionNames, editorStateFactory);
}
setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
// the outer panel is 'Scrollable' and uses a layout that centers the options panel
JPanel outerPanel = new ScollableOptionsPanel();
outerPanel.add(optionsPanel);
setViewportView(outerPanel);
/**
* Creates a panel for editing options. This version of the constructor will get the
* options names from the options object when
* {@link #getEditorComponent(Options, EditorStateFactory)} is called.
* @param title the title for the panel
*/
public ScrollableOptionsEditor(String title) {
this(title, null);
}
//==================================================================================================
@ -61,7 +67,9 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito
@Override
public void dispose() {
// stub
if (optionsPanel != null) {
optionsPanel.dispose();
}
}
@Override
@ -81,12 +89,27 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito
@Override
public JComponent getEditorComponent(Options options, EditorStateFactory factory) {
return this;
scrollPane = new JScrollPane();
optionsPanel = new OptionsEditorPanel(title, options, optionNames, factory);
optionsPanel.setOptionsPropertyChangeListener(listener);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
// the outer panel is 'Scrollable' and uses a layout that centers the options panel
JPanel outerPanel = new ScollableOptionsPanel();
outerPanel.add(optionsPanel);
scrollPane.setViewportView(outerPanel);
return scrollPane;
}
@Override
public void setOptionsPropertyChangeListener(PropertyChangeListener listener) {
optionsPanel.setOptionsPropertyChangeListener(listener);
this.listener = listener;
if (optionsPanel != null) {
optionsPanel.setOptionsPropertyChangeListener(listener);
}
}
//==================================================================================================
@ -120,7 +143,7 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito
// scrollbars.
//
Dimension mySize = getPreferredSize();
Dimension viewSize = ScrollableOptionsEditor.this.getViewport().getSize();
Dimension viewSize = scrollPane.getViewport().getSize();
boolean viewIsLarger = viewSize.height > mySize.height;
return viewIsLarger;
}
@ -135,7 +158,7 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito
// scrollbars.
//
Dimension mySize = getPreferredSize();
Dimension viewSize = ScrollableOptionsEditor.this.getViewport().getSize();
Dimension viewSize = scrollPane.getViewport().getSize();
boolean viewIsLarger = viewSize.width > mySize.width;
return viewIsLarger;
}
@ -147,4 +170,8 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito
}
}
// for testing
public JComponent getComponent() {
return scrollPane;
}
}

View File

@ -851,15 +851,8 @@ public class HTMLUtilities {
* @return a string of the format #RRGGBB.
*/
public static String toHexString(Color color) {
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
StringBuilder buffy = new StringBuilder("#");
buffy.append(StringUtilities.pad(Integer.toHexString(r), '0', 2));
buffy.append(StringUtilities.pad(Integer.toHexString(g), '0', 2));
buffy.append(StringUtilities.pad(Integer.toHexString(b), '0', 2));
return buffy.toString().toUpperCase();
// this will format a color value as a 6 digit hex string (e.g. #rrggbb)
return String.format("#%06X", color.getRGB() & 0xffffff);
}
}

View File

@ -0,0 +1,249 @@
/* ###
* 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.util;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class for web color support. This class defines many of the colors used by html. This class
* includes methods for converting a color to a string (name or hex value) and for converting
* those strings back to a color.
*/
public abstract class WebColors {
private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
private static final Map<String, Color> nameToColorMap = new HashMap<>();
private static final Map<Color, String> colorToNameMap = new HashMap<>();
//@formatter:off
public static final Color BLACK = registerColor("Black", Color.black);
public static final Color NAVY = registerColor("Navy", Color.decode("0x000080"));
public static final Color DARK_BLUE = registerColor("DarkBlue", Color.decode("0x00008B"));
public static final Color MEDIUM_BLUE = registerColor("MediumBlue", Color.decode("0x0000CD"));
public static final Color BLUE = registerColor("Blue", Color.decode("0x0000FF"));
public static final Color DARK_GREEN = registerColor("DarkGreen", Color.decode("0x006400"));
public static final Color GREEN = registerColor("Green", Color.decode("0x008000"));
public static final Color TEAL = registerColor("Teal", Color.decode("0x008080"));
public static final Color DARK_CYAN = registerColor("DarkCyan", Color.decode("0x008B8B"));
public static final Color DEEP_SKY_BLUE = registerColor("DeepSkyBlue", Color.decode("0x00BFFF"));
public static final Color DARK_TURQUOSE = registerColor("DarkTurquoise", Color.decode("0x00CED1"));
public static final Color LIME = registerColor("Lime", Color.decode("0x00FF00"));
public static final Color SPRING_GREEN = registerColor("SpringGreen", Color.decode("0x00FF7F"));
public static final Color AQUA = registerColor("Aqua", Color.decode("0x00FFFF"));
public static final Color CYAN = registerColor("Cyan", Color.decode("0x00FFFF"));
public static final Color MIDNIGHT_BLUE = registerColor("MidnightBlue", Color.decode("0x191970"));
public static final Color DOGER_BLUE = registerColor("DodgerBlue", Color.decode("0x1E90FF"));
public static final Color LIGHT_SEA_GREEN = registerColor("LightSeaGreen", Color.decode("0x20B2AA"));
public static final Color FOREST_GREEN = registerColor("ForestGreen", Color.decode("0x228B22"));
public static final Color SEA_GREEN = registerColor("SeaGreen", Color.decode("0x2E8B57"));
public static final Color DARK_SLATE_GRAY = registerColor("DarkSlateGray", Color.decode("0x2F4F4F"));
public static final Color LIME_GREEN = registerColor("LimeGreen", Color.decode("0x32CD32"));
public static final Color TURQUOISE = registerColor("Turquoise", Color.decode("0x40E0D0"));
public static final Color ROYAL_BLUE = registerColor("RoyalBlue", Color.decode("0x4169E1"));
public static final Color STEEL_BLUE = registerColor("SteelBlue", Color.decode("0x4682B4"));
public static final Color DARK_SLATE_BLUE = registerColor("DarkSlateBlue", Color.decode("0x483D8B"));
public static final Color INDIGO = registerColor("Indigo", Color.decode("0x4B0082"));
public static final Color CADET_BLUE = registerColor("CadetBlue", Color.decode("0x5F9EA0"));
public static final Color REBECCA_PURPLE = registerColor("RebeccaPurple", Color.decode("0x663399"));
public static final Color DIM_GRAY = registerColor("DimGray", Color.decode("0x696969"));
public static final Color SLATE_BLUE = registerColor("SlateBlue", Color.decode("0x6A5ACD"));
public static final Color OLIVE_DRAB = registerColor("OliveDrab", Color.decode("0x6B8E23"));
public static final Color SLATE_GRAY = registerColor("SlateGray", Color.decode("0x708090"));
public static final Color LAWN_GREEN = registerColor("LawnGreen", Color.decode("0x7CFC00"));
public static final Color CHARTREUSE = registerColor("Chartreuse", Color.decode("0x7FFF00"));
public static final Color AQUAMARINE = registerColor("Aquamarine", Color.decode("0x7FFFD4"));
public static final Color MAROON = registerColor("Maroon", Color.decode("0x800000"));
public static final Color PURPLE = registerColor("Purple", Color.decode("0x800080"));
public static final Color OLIVE = registerColor("Olive", Color.decode("0x808000"));
public static final Color GRAY = registerColor("Gray", Color.decode("0x808080"));
public static final Color SYY_BLUE = registerColor("SkyBlue", Color.decode("0x87CEEB"));
public static final Color LIGHT_SKY_BLUE = registerColor("LightSkyBlue", Color.decode("0x87CEFA"));
public static final Color BLUE_VIOLET = registerColor("BlueViolet", Color.decode("0x8A2BE2"));
public static final Color DARK_RED = registerColor("DarkRed", Color.decode("0x8B0000"));
public static final Color DARK_MAGENTA = registerColor("DarkMagenta", Color.decode("0x8B008B"));
public static final Color SADDLE_BROWN = registerColor("SaddleBrown", Color.decode("0x8B4513"));
public static final Color DARK_SEA_GREEN = registerColor("DarkSeaGreen", Color.decode("0x8FBC8F"));
public static final Color LIGHT_GREEN = registerColor("LightGreen", Color.decode("0x90EE90"));
public static final Color MEDIUM_PURPLE = registerColor("MediumPurple", Color.decode("0x9370DB"));
public static final Color DARK_VIOLET = registerColor("DarkViolet", Color.decode("0x9400D3"));
public static final Color PALE_GREEN = registerColor("PaleGreen", Color.decode("0x98FB98"));
public static final Color DARK_ORCHID = registerColor("DarkOrchid", Color.decode("0x9932CC"));
public static final Color YELLOW_GREEN = registerColor("YellowGreen", Color.decode("0x9ACD32"));
public static final Color SIENNA = registerColor("Sienna", Color.decode("0xA0522D"));
public static final Color BROWN = registerColor("Brown", Color.decode("0xA52A2A"));
public static final Color DARK_GRAY = registerColor("DarkGray", Color.decode("0xA9A9A9"));
public static final Color LIGHT_BLUE = registerColor("LightBlue", Color.decode("0xADD8E6"));
public static final Color GREEN_YELLOW = registerColor("GreenYellow", Color.decode("0xADFF2F"));
public static final Color PALE_TURQUOISE = registerColor("PaleTurquoise", Color.decode("0xAFEEEE"));
public static final Color POWDER_BLUE = registerColor("PowderBlue", Color.decode("0xB0E0E6"));
public static final Color FIRE_BRICK = registerColor("FireBrick", Color.decode("0xB22222"));
public static final Color DARK_GOLDENROD = registerColor("DarkGoldenRod", Color.decode("0xB8860B"));
public static final Color MEDIUM_ORCHID = registerColor("MediumOrchid", Color.decode("0xBA55D3"));
public static final Color ROSY_BROWN = registerColor("RosyBrown", Color.decode("0xBC8F8F"));
public static final Color DARK_KHAKI = registerColor("DarkKhaki", Color.decode("0xBDB76B"));
public static final Color SILVER = registerColor("Silver", Color.decode("0xC0C0C0"));
public static final Color INDIAN_RED = registerColor("IndianRed", Color.decode("0xCD5C5C"));
public static final Color PERU = registerColor("Peru", Color.decode("0xCD853F"));
public static final Color CHOCOLATE = registerColor("Chocolate", Color.decode("0xD2691E"));
public static final Color TAN = registerColor("Tan", Color.decode("0xD2B48C"));
public static final Color LIGHT_GRAY = registerColor("LightGray", Color.decode("0xD3D3D3"));
public static final Color THISTLE = registerColor("Thistle", Color.decode("0xD8BFD8"));
public static final Color ORCHID = registerColor("Orchid", Color.decode("0xDA70D6"));
public static final Color GOLDEN_ROD = registerColor("GoldenRod", Color.decode("0xDAA520"));
public static final Color PALE_VIOLET_RED = registerColor("PaleVioletRed", Color.decode("0xDB7093"));
public static final Color CRIMSON = registerColor("Crimson", Color.decode("0xDC143C"));
public static final Color GAINSBORO = registerColor("Gainsboro", Color.decode("0xDCDCDC"));
public static final Color PLUM = registerColor("Plum", Color.decode("0xDDA0DD"));
public static final Color BURLYWOOD = registerColor("BurlyWood", Color.decode("0xDEB887"));
public static final Color LIGHT_CYAN = registerColor("LightCyan", Color.decode("0xE0FFFF"));
public static final Color LAVENDER = registerColor("Lavender", Color.decode("0xE6E6FA"));
public static final Color DARK_SALMON = registerColor("DarkSalmon", Color.decode("0xE9967A"));
public static final Color VIOLET = registerColor("Violet", Color.decode("0xEE82EE"));
public static final Color PALE_GOLDENROD = registerColor("PaleGoldenRod", Color.decode("0xEEE8AA"));
public static final Color LIGHT_CORAL = registerColor("LightCoral", Color.decode("0xF08080"));
public static final Color KHAKE = registerColor("Khaki", Color.decode("0xF0E68C"));
public static final Color ALICE_BLUE = registerColor("AliceBlue", Color.decode("0xF0F8FF"));
public static final Color HONEY_DEW = registerColor("HoneyDew", Color.decode("0xF0FFF0"));
public static final Color AZURE = registerColor("Azure", Color.decode("0xF0FFFF"));
public static final Color SANDY_BROWN = registerColor("SandyBrown", Color.decode("0xF4A460"));
public static final Color WHEAT = registerColor("Wheat", Color.decode("0xF5DEB3"));
public static final Color BEIGE = registerColor("Beige", Color.decode("0xF5F5DC"));
public static final Color WHITE_SMOKE = registerColor("WhiteSmoke", Color.decode("0xF5F5F5"));
public static final Color MINT_CREAM = registerColor("MintCream", Color.decode("0xF5FFFA"));
public static final Color GHOST_WHITE = registerColor("GhostWhite", Color.decode("0xF8F8FF"));
public static final Color SALMON = registerColor("Salmon", Color.decode("0xFA8072"));
public static final Color ANTIQUE_WHITE = registerColor("AntiqueWhite", Color.decode("0xFAEBD7"));
public static final Color LINEN = registerColor("Linen", Color.decode("0xFAF0E6"));
public static final Color OLDLACE = registerColor("OldLace", Color.decode("0xFDF5E6"));
public static final Color RED = registerColor("Red", Color.decode("0xFF0000"));
public static final Color FUCHSIA = registerColor("Fuchsia", Color.decode("0xFF00FF"));
public static final Color MAGENTA = registerColor("Magenta", Color.decode("0xFF00FF"));
public static final Color DEEP_PINK = registerColor("DeepPink", Color.decode("0xFF1493"));
public static final Color ORANGE_RED = registerColor("OrangeRed", Color.decode("0xFF4500"));
public static final Color TOMATO = registerColor("Tomato", Color.decode("0xFF6347"));
public static final Color HOT_PINK = registerColor("HotPink", Color.decode("0xFF69B4"));
public static final Color CORAL = registerColor("Coral", Color.decode("0xFF7F50"));
public static final Color DARK_ORANGE = registerColor("DarkOrange", Color.decode("0xFF8C00"));
public static final Color LIGHT_SALMON = registerColor("LightSalmon", Color.decode("0xFFA07A"));
public static final Color ORANGE = registerColor("Orange", Color.decode("0xFFA500"));
public static final Color LIGHT_PINK = registerColor("LightPink", Color.decode("0xFFB6C1"));
public static final Color PINK = registerColor("Pink", Color.decode("0xFFC0CB"));
public static final Color GOLD = registerColor("Gold", Color.decode("0xFFD700"));
public static final Color PEACH_PUFF = registerColor("PeachPuff", Color.decode("0xFFDAB9"));
public static final Color NAVAJO_WHITE = registerColor("NavajoWhite", Color.decode("0xFFDEAD"));
public static final Color MOCCASIN = registerColor("Moccasin", Color.decode("0xFFE4B5"));
public static final Color BISQUE = registerColor("Bisque", Color.decode("0xFFE4C4"));
public static final Color MISTY_ROSE = registerColor("MistyRose", Color.decode("0xFFE4E1"));
public static final Color BLANCHED_ALMOND = registerColor("BlanchedAlmond", Color.decode("0xFFEBCD"));
public static final Color PAPAYA_WHIP = registerColor("PapayaWhip", Color.decode("0xFFEFD5"));
public static final Color LAVENDAR_BLUSH = registerColor("LavenderBlush", Color.decode("0xFFF0F5"));
public static final Color SEASHELL = registerColor("SeaShell", Color.decode("0xFFF5EE"));
public static final Color CORNSILK = registerColor("Cornsilk", Color.decode("0xFFF8DC"));
public static final Color LEMON_CHIFFON = registerColor("LemonChiffon", Color.decode("0xFFFACD"));
public static final Color FLORAL_WHITE = registerColor("FloralWhite", Color.decode("0xFFFAF0"));
public static final Color SNOW = registerColor("Snow", Color.decode("0xFFFAFA"));
public static final Color YELLOW = registerColor("Yellow", Color.decode("0xFFFF00"));
public static final Color LIGHT_YELLOW = registerColor("LightYellow", Color.decode("0xFFFFE0"));
public static final Color IVORY = registerColor("Ivory", Color.decode("0xFFFFF0"));
public static final Color WHITE = registerColor("White", Color.decode("0xFFFFFF"));
public static final Color MEDIUM_SPRING_GREEN = registerColor("MediumSpringGreen", Color.decode("0x00FA9A"));
public static final Color LIGHT_GOLDENROD = registerColor("LightGoldenRodYellow", Color.decode("0xFAFAD2"));
public static final Color MEDIUM_VIOLET_RED = registerColor("MediumVioletRed", Color.decode("0xC71585"));
public static final Color LIGHT_STEEL_BLUE = registerColor("LightSteelBlue", Color.decode("0xB0C4DE"));
public static final Color LIGHT_SLATE_GRAY = registerColor("LightSlateGray", Color.decode("0x778899"));
public static final Color MEDIUM_SLATE_BLUE = registerColor("MediumSlateBlue", Color.decode("0x7B68EE"));
public static final Color MEDIUM_SEA_GREEN = registerColor("MediumSeaGreen", Color.decode("0x3CB371"));
public static final Color MEDUM_AQUA_MARINE = registerColor("MediumAquaMarine", Color.decode("0x66CDAA"));
public static final Color MEDIUM_TURQOISE = registerColor("MediumTurquoise", Color.decode("0x48D1CC"));
public static final Color DARK_OLIVE_GREEN = registerColor("DarkOliveGreen", Color.decode("0x556B2F"));
public static final Color CORN_FLOWER_BLUE = registerColor("CornflowerBlue", Color.decode("0x6495ED"));
//@formatter:on
// cannot instantiate nor extend
private WebColors() {
}
/**
* Tries to find a color for the given String value. The String value can either be
* a hex string (see {@link Color#decode(String)}) or a web color name as defined
* above
*
* @param value the string value to interpret as a color
* @param defaultColor a default color to return if the string can't be converted to a color
* @return a color for the given string value or the default color if the string can't be translated
*/
public static Color getColorOrDefault(String value, Color defaultColor) {
Color color = getColor(value);
return color != null ? color : defaultColor;
}
/**
* Tries to find a color for the given String value. The String value can either be
* a hex string (see {@link Color#decode(String)}) or a web color name as defined
* in {@link WebColors}
*
* @param value the string value to interpret as a color
* @return a color for the given string value or null if the string can't be translated
*/
public static Color getColor(String value) {
Color color = nameToColorMap.get(value);
if (color != null) {
return color;
}
// if the value matches an RGB hex string, turn that into a color
color = getHexColor(value);
if (color != null) {
return color;
}
return null;
}
/**
* Converts a color to a string value. If there is a defined color for the given color value,
* the color name will be returned. Otherwise, it will return a hex string for the color as
* defined by {@link Color#toString()}. The result of this call can be passed to
* {@link #getColor(String)} and be guaranteed that a color will be returned
*
* @param color the color to convert to a string.
* @return the string representation for the given color.
*/
public static String toString(Color color) {
String name = colorToNameMap.get(color);
if (name != null) {
return name;
}
// this will format a color value as a 6 digit hex (e.g. #rrggbb)
return String.format("#%06X", color.getRGB() & 0xffffff);
}
private static Color getHexColor(String hexString) {
Matcher matcher = HEX_PATTERN.matcher(hexString);
if (matcher.matches()) {
return Color.decode(hexString);
}
return null;
}
private static Color registerColor(String name, Color color) {
nameToColorMap.put(name, color);
colorToNameMap.put(color, name);
return color;
}
}

View File

@ -0,0 +1,65 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
import static org.junit.Assert.*;
import java.awt.Color;
import org.junit.Test;
public class WebColorsTest {
@Test
public void testColorToStringFromDefinedValue() {
assertEquals("Navy", WebColors.toString(WebColors.NAVY));
}
@Test
public void testColorToStringNewColor() {
assertEquals("Navy", WebColors.toString(new Color(0, 0, 0x80)));
}
@Test
public void testColorToStringFromColorWithNoDefinedEntry() {
assertEquals("#0123EF", WebColors.toString(new Color(0x01, 0x23, 0xEF)));
}
@Test
public void testGetColorFromName() {
assertEquals(WebColors.NAVY, WebColors.getColor("Navy"));
}
@Test
public void testGetColorFromHexString() {
assertEquals(WebColors.NAVY, WebColors.getColor("0x000080"));
}
@Test
public void testGetColorFromHexString2() {
assertEquals(WebColors.NAVY, WebColors.getColor("#000080"));
}
@Test
public void testGetColorWithNoDefinedValue() {
assertEquals(new Color(0x12, 0x34, 0x56), WebColors.getColor("0x123456"));
}
@Test
public void testGetColorByBadName() {
assertNull(WebColors.getColor("ABCDEFG"));
}
}

View File

@ -16,17 +16,8 @@
package ghidra.service.graph;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.text.StringEscapeUtils;
import com.google.common.base.Splitter;
public class Attributed {
/**
* cache of the html rendering of the vertex attributes
*/
private String htmlString;
private static final String DESCRIPTION = "Description";
/**
@ -39,7 +30,7 @@ public class Attributed {
* @return an unmodifiable view of the attribute map
*/
public Map<String, String> getAttributeMap() {
public Map<String, String> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
@ -51,7 +42,6 @@ public class Attributed {
* @return the previous value of the attribute
*/
public String setAttribute(String key, String value) {
htmlString = null;
return attributes.put(key, value);
}
@ -154,7 +144,6 @@ public class Attributed {
* @return the previously set description
*/
public String setDescription(String value) {
htmlString = null;
return attributes.put(DESCRIPTION, value);
}
@ -167,35 +156,4 @@ public class Attributed {
return getAttribute(DESCRIPTION);
}
/**
* parse (one time) then cache the attributes to html
* @return the html string
*/
public String getHtmlString() {
if (htmlString != null) {
return htmlString;
}
htmlString = getDescription();
if (htmlString == null) { // if no description is set, create a default one
Set<Entry<String, String>> entries = entrySet();
if (entries.isEmpty()) {
return ""; // empty so tooltip clients can handle empty data
}
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> entry : entries) {
buf.append(entry.getKey());
buf.append(":");
String value = entry.getValue();
value = StringEscapeUtils.escapeHtml4(value);
String split = String.join("<br>", Splitter.on('\n').split(value));
split = split.replaceAll("\\s", "&nbsp;");
buf.append(split);
buf.append("<br>");
}
htmlString = buf.toString();
}
return htmlString;
}
}

View File

@ -19,6 +19,7 @@ package ghidra.service.graph;
* Generic directed graph edge implementation
*/
public class AttributedEdge extends Attributed {
public static final String EDGE_TYPE_KEY = "EdgeType";
private final String id;
/**
@ -62,4 +63,21 @@ public class AttributedEdge extends Attributed {
return id.equals(other.id);
}
/**
* Returns the edge type for this edge
* @return the edge type for this edge
*/
public String getEdgeType() {
return getAttribute(EDGE_TYPE_KEY);
}
/**
* Sets the edge type for this edge. Should be a value defined by the {@link GraphType} for
* this graph, but there is no enforcement for this. If the value is not defined in GraphType,
* it will be rendered using the default edge color for {@link GraphType}
* @param edgeType the edge type for this edge
*/
public void setEdgeType(String edgeType) {
setAttribute(EDGE_TYPE_KEY, edgeType);
}
}

View File

@ -33,29 +33,80 @@ import org.jgrapht.graph.DefaultGraphType;
* to the same source/destination vertex pair.
*/
public class AttributedGraph extends AbstractBaseGraph<AttributedVertex, AttributedEdge> {
private static final String WEIGHT = "Weight";
public static final String WEIGHT = "Weight";
private Map<String, AttributedVertex> vertexMap = new HashMap<>();
private final boolean collapseDuplicateEdges;
private String name;
private GraphType type;
private String description;
/**
* Create a new empty AttributedGraph that automatically collapses duplicate edges
*
* @param name the name of the graph
* @param type the {@link GraphType} which defines valid vertex and edge types.
*/
public AttributedGraph() {
this(true);
public AttributedGraph(String name, GraphType type) {
this(name, type, name, true);
}
/**
* Create a new empty AttributedGraph that automatically collapses duplicate edges
*
* @param name the name of the graph
* @param type the {@link GraphType} which defines valid vertex and edge types.
* @param description a description of the graph
*/
public AttributedGraph(String name, GraphType type, String description) {
this(name, type, description, true);
}
/**
* Create a new empty AttributedGraph.
*
*
* @param name the name of the graph
* @param type the {@link GraphType} which defines valid vertex and edge types.
* @param description a description of the graph
* @param collapseDuplicateEdges if true, duplicate edges will be collapsed into a single
* edge with a "Weight" attribute whose value is the number of edges between those vertices.
*/
public AttributedGraph(boolean collapseDuplicateEdges) {
public AttributedGraph(String name, GraphType type, String description,
boolean collapseDuplicateEdges) {
super(new VertexSupplier(), new EdgeSupplier(), DefaultGraphType.directedPseudograph());
this.name = name;
this.type = type;
this.description = description;
this.collapseDuplicateEdges = collapseDuplicateEdges;
}
/**
* Returns the name of the graph
* @return the name of the graph
*/
public String getName() {
return name;
}
/**
* Returns a description of the graph
* @return a description of the graph
*/
public String getDescription() {
return description;
}
/**
* Returns the {@link GraphType} for this graph
* @return the {@link GraphType} for this graph
*/
public GraphType getGraphType() {
return type;
}
/**
* Adds a new vertex with the given id. The vertex's name will be the same as the id.
* If a vertex already exists with that id,

View File

@ -20,6 +20,8 @@ package ghidra.service.graph;
*/
public class AttributedVertex extends Attributed {
public static final String NAME_KEY = "Name";
public static final String VERTEX_TYPE_KEY = "VertexType";
private final String id;
/**
@ -43,7 +45,7 @@ public class AttributedVertex extends Attributed {
* @param name the new name for the vertex
*/
public void setName(String name) {
setAttribute("Name", name);
setAttribute(NAME_KEY, name);
}
/**
@ -60,7 +62,7 @@ public class AttributedVertex extends Attributed {
* @return the name of the vertex
*/
public String getName() {
return getAttribute("Name");
return getAttribute(NAME_KEY);
}
@Override
@ -88,4 +90,22 @@ public class AttributedVertex extends Attributed {
return id.equals(other.id);
}
/**
* Returns the vertex type for this vertex
* @return the vertex type for this vertex
*/
public String getVertexType() {
return getAttribute(VERTEX_TYPE_KEY);
}
/**
* Sets the vertex type for this vertex. Should be a value defined by the {@link GraphType} for
* this graph, but there is no enforcement for this. If the value is not defined in GraphType,
* it will be rendered using the default vertex shape and color for the {@link GraphType}
* @param vertexType the vertex type for this vertex
*/
public void setVertexType(String vertexType) {
setAttribute(VERTEX_TYPE_KEY, vertexType);
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
/**
* Empty implementation of GraphDiaplayOptions. Used as an initial default to avoid null
* checks
*/
public class DefaultGraphDisplayOptions extends GraphDisplayOptions {
public DefaultGraphDisplayOptions() {
super(new EmptyGraphType());
}
}

View File

@ -0,0 +1,30 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.util.Collections;
/**
* Default GraphType implementation that has no vertex or edge types defined
*/
public class EmptyGraphType extends GraphType {
public EmptyGraphType() {
super("Empty Graph Type", "Graph type with no defined vertex or edge types",
Collections.emptyList(), Collections.emptyList());
}
}

View File

@ -0,0 +1,831 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.awt.Color;
import java.awt.Font;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.text.StringEscapeUtils;
import com.google.common.base.Splitter;
import docking.Tool;
import docking.options.editor.*;
import ghidra.framework.options.*;
import ghidra.util.HelpLocation;
import ghidra.util.WebColors;
import ghidra.util.bean.opteditor.OptionsVetoException;
/**
* Class for managing graph display options. This includes color options for each vertex
* and edge type and shapes for vertex types.
*/
public class GraphDisplayOptions implements OptionsChangeListener {
public static final GraphDisplayOptions DEFAULT =
new GraphDisplayOptions(new EmptyGraphType());
private static final String FONT = "Font";
private static final String LABEL_POSITION = "Label Position";
private static final String USE_ICONS = "Use Icons";
private static final String DEFAULT_LAYOUT_ALGORITHM = "Default Layout Algorithm";
private static final String EDGE_COLORS = "Edge Colors";
private static final String VERTEX_COLORS = "Vertex Colors";
private static final String VERTEX_SHAPES = "Vertex Shapes";
private static final String MISCELLANIOUS_OPTIONS = "Miscellanious";
private static final String DEFAULT_VERTEX_COLOR = "Default Vertex Color";
private static final String DEFAULT_EDGE_COLOR = "Default Edge Color";
private static final String DEFAULT_VERTEX_SHAPE = "Default Vertex Shape";
private static final String FAVORED_EDGE_TYPE = "Favored Edge Type";
private static final String VERTEX_SELECTION_COLOR = "Selected Vertex Color";
private static final String EDGE_SELECTION_COLOR = "Selected Edge Color";
private GraphType graphType;
private Map<String, Color> vertexColorMap = new HashMap<>();
private Map<String, Color> edgeColorMap = new HashMap<>();
private Map<String, VertexShape> vertexShapeMap = new HashMap<>();
private Map<String, Integer> edgePriorityMap = new HashMap<>();
private List<ChangeListener> changeListeners = new CopyOnWriteArrayList<>();
private Color vertexSelectionColor = Color.green;
private Color edgeSelectionColor = Color.green;
private Color defaultVertexColor = Color.blue;
private Color defaultEdgeColor = Color.blue;
private String favoredEdgeType;
private VertexShape defaultVertexShape = VertexShape.RECTANGLE;
private String vertexLabelOverride = null;
private String vertexColorOverride = null;
private String vertexShapeOverride = null;
private String edgeColorOverride = null;
private final String rootOptionsName;
private boolean registeredWithTool = false;
private String defaultLayoutAlgorithmName = LayoutAlgorithmNames.MIN_CROSS_COFFMAN_GRAHAM;
private boolean useIcons = true;
private GraphLabelPosition labelPosition = GraphLabelPosition.SOUTH;
private Font font = new Font("Dialog", Font.BOLD, 18);
private int arrowLength = 15;
/**
* Constructs a new GraphTypeDisplayOptions for the given {@link GraphType}
* @param graphType The {@link GraphType} for which to define display options
*/
public GraphDisplayOptions(GraphType graphType) {
this(graphType, null);
}
/**
* Constructs a new GraphTypeDisplayOptions for the given {@link GraphType} and initializes
* from tool options. Note this form should only be used for display options on
* {@link GraphType}s that have options registered in the tool.
* @param graphType The {@link GraphType} for which to define display options
* @param tool the tool from which to initialize from {@link ToolOptions}
*/
public GraphDisplayOptions(GraphType graphType, Tool tool) {
this.graphType = graphType;
rootOptionsName = graphType.getOptionsName();
List<String> edgeTypes = graphType.getEdgeTypes();
if (!edgeTypes.isEmpty()) {
favoredEdgeType = edgeTypes.iterator().next();
}
initializeEdgePriorities();
initializeDefaults();
initializeFromOptions(tool);
}
private void initializeEdgePriorities() {
// initialize priorities based on the order they were defined
for (String edgeType : graphType.getEdgeTypes()) {
edgePriorityMap.put(edgeType, edgePriorityMap.size());
}
}
protected void initializeDefaults() {
// Overridden by subclass to define defaultValues
}
/**
* Adds a ChangeListener to be notified when display options change
* @param listener the listener to be notified.
*/
public void addChangeListener(ChangeListener listener) {
changeListeners.add(listener);
}
/**
* Removes the listener so that it won't be notified of changes any longer
* @param listener the listener to be removed
*/
public void removeChangeListener(ChangeListener listener) {
changeListeners.remove(listener);
}
/**
* Sets the default shape to be used by vertices that don't have a vertex type set
* @param shape the default vertex shape
*/
public void setDefaultVertexShape(VertexShape shape) {
this.defaultVertexShape = Objects.requireNonNull(shape);
}
/**
* Sets the default color to be used by vertices that don't have a vertex type set
* @param color the default vertex shape
*/
public void setDefaultVertexColor(Color color) {
this.defaultVertexColor = Objects.requireNonNull(color);
}
/**
* Sets the default color to be used by edges that don't have a edge type set
* @param color the default edge shape
*/
public void setDefaultEdgeColor(Color color) {
this.defaultEdgeColor = Objects.requireNonNull(color);
}
/**
* Returns the default color for edges that don't have an edge type set
* @return the default color for edges that don't have an edge type set
*/
public Color getDefaultEdgeColor() {
return defaultEdgeColor;
}
/**
* Returns the default color for vertices that don't have an vertex type set
* @return the default color for vertices that don't have an vertex type set
*/
public Color getDefaultVertexColor() {
return defaultVertexColor;
}
/**
* Sets the attribute key that can be used to override the label text shown for the vertex.
* Normally, the vertex's name is shown as the label.
* @param attributeKey the attribute key that, if set, will be used to define the vertice's label
*/
public void setVertexLabelOverrideAttributeKey(String attributeKey) {
vertexLabelOverride = attributeKey;
}
/**
* Returns the attribute key that can override the vertices label text
* @return the attribute key that can override the vertices label text
*/
public String getVertexLabelOverride() {
return vertexLabelOverride;
}
/**
* Sets the attribute key that can be used to override the color for a vertex. Normally, the
* color is determined by the vertex type, which will be mapped to a color
* @param attributeKey the attribute key that, if set, will be used to define the vertice's color
*/
public void setVertexColorOverrideAttributeKey(String attributeKey) {
vertexColorOverride = attributeKey;
}
/**
* Sets the attribute key that can be used to override the color for an edge. Normally, the
* color is determined by the edge type, which will be mapped to a color
* @param attributeKey the attribute key that, if set, will be used to define the edge's color
*/
public void setEdgeColorOverrideAttributeKey(String attributeKey) {
edgeColorOverride = attributeKey;
}
/**
* Returns the attribute key that can be used to override the color of an edge
* @return the attribute key that can be used to override the color of an edge
*/
public String getEdgeColorOverrideAttributeKey() {
return edgeColorOverride;
}
/**
* Sets the attribute key that can be used to override the shape for a vertex. Normally, the
* shape is determined by the vertex type, which will be mapped to a shape
* @param attributeKey the attribute key that, if set, will be used to define the vertice's shape
*/
public void setVertexShapeOverrideAttributeKey(String attributeKey) {
vertexShapeOverride = attributeKey;
}
/**
* Returns the text that will be displayed as the label for the given vertex
* @param vertex the vertex for which to get label text
* @return the text that will be displayed as the label for the given vertex
*/
public String getVertexLabel(AttributedVertex vertex) {
String vertexLabel = null;
if (vertexLabelOverride != null) {
vertexLabel = vertex.getAttribute(vertexLabelOverride);
}
if (vertexLabel == null) {
vertexLabel = vertex.getName();
}
if (vertexLabel.contains("\n")) {
vertexLabel = StringEscapeUtils.escapeHtml4(vertexLabel);
return "<html>" + String.join("<p>", Splitter.on('\n').split(vertexLabel));
}
return vertexLabel;
}
/**
* Returns the {@link VertexShape} that will be used to draw the vertex's shape
* @param vertex the vertex for which to get the shape
* @return the {@link VertexShape} that will be used to draw the vertex's shape
*/
public VertexShape getVertexShape(AttributedVertex vertex) {
if (vertexShapeOverride != null) {
String shapeName = vertex.getAttribute(vertexShapeOverride);
if (shapeName != null) {
VertexShape shape = VertexShape.getShape(shapeName);
if (shape != null) {
return shape;
}
}
}
String vertexType = vertex.getVertexType();
return vertexShapeMap.getOrDefault(vertexType, defaultVertexShape);
}
/**
* Returns the color that will be used to draw the vertex
* @param vertex the vertex for which to get the color
* @return the color that will be used to draw the vertex
*/
public Color getVertexColor(AttributedVertex vertex) {
if (vertexColorOverride != null) {
String colorValue = vertex.getAttribute(vertexColorOverride);
if (colorValue != null) {
Color color = WebColors.getColor(colorValue);
if (color != null) {
return color;
}
}
}
String vertexType = vertex.getVertexType();
return vertexColorMap.getOrDefault(vertexType, defaultVertexColor);
}
/**
* Returns the color that will be used to draw the edge
* @param edge the edge for which to get the color
* @return the color that will be used to draw the edge
*/
public Color getEdgeColor(AttributedEdge edge) {
if (edgeColorOverride != null) {
String colorValue = edge.getAttribute(edgeColorOverride);
if (colorValue != null) {
Color color = WebColors.getColor(colorValue);
if (color != null) {
return color;
}
}
}
String edgeType = edge.getEdgeType();
return edgeColorMap.getOrDefault(edgeType, defaultEdgeColor);
}
/**
* Returns the priority for the given edge type. This is used by layout algorithms to
* determine which edges should have more influence on the layout.
* @param edgeType the edge type for which to get it's priority
* @return the priority for the given edge type
*/
public Integer getEdgePriority(String edgeType) {
return edgePriorityMap.getOrDefault(edgeType, Integer.MAX_VALUE);
}
/**
* Returns the edge type that is the preferred edge for layout purposes
* @return the edge type that is the preferred edge for layout purposes
*/
public String getFavoredEdgeType() {
return favoredEdgeType;
}
/**
* Sets the favored edge type. The favored edge type is used to influence layout algorithms
* @param favoredEdgeType the edge type that is to be favored by layout algorithms
*/
public void setFavoredEdgeType(String favoredEdgeType) {
checkEdgeType(favoredEdgeType);
this.favoredEdgeType = favoredEdgeType;
}
/**
* Returns the {@link GraphType} that this object provides display options for
* @return the {@link GraphType} that this object provides display options for
*/
public GraphType getGraphType() {
return graphType;
}
/**
* Returns the color for the given vertex type
* @param vertexType the vertex type to get the color for
* @return the color for the given vertex type
*/
public Color getVertexColor(String vertexType) {
return vertexColorMap.getOrDefault(vertexType, defaultVertexColor);
}
/**
* Sets the color for vertices with the given vertex type
* @param vertexType the vertex type for which to set its color
* @param color the color to use for vertices with the given vertex type
*/
public void setVertexColor(String vertexType, Color color) {
checkVertexType(vertexType);
vertexColorMap.put(vertexType, Objects.requireNonNull(color));
}
private String getVertexShapeName(String vertexType) {
VertexShape vertexShape = vertexShapeMap.getOrDefault(vertexType, defaultVertexShape);
return vertexShape.getName();
}
/**
* Sets the {@link VertexShape} to use for vertices with the given vertex type
* @param vertexType the vertex type for which to set its shape
* @param vertexShape the {@link VertexShape} to use for vertices with the given vertex type
*/
public void setVertexShape(String vertexType, VertexShape vertexShape) {
checkVertexType(vertexType);
vertexShapeMap.put(vertexType, Objects.requireNonNull(vertexShape));
}
/**
* Returns the color for the given edge type
* @param edgeType the edge type whose color is to be determined.
* @return the color for the given edge type.
*/
public Color getEdgeColor(String edgeType) {
return edgeColorMap.getOrDefault(edgeType, defaultEdgeColor);
}
/**
* Sets the color for edges with the given edge type
* @param edgeType the edge type for which to set its color
* @param color the new color for edges with the given edge type
*/
public void setEdgeColor(String edgeType, Color color) {
checkEdgeType(edgeType);
edgeColorMap.put(edgeType, Objects.requireNonNull(color));
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) throws OptionsVetoException {
if (optionName.startsWith(rootOptionsName)) {
updateOptions(options.getOptions(rootOptionsName));
}
notifyListeners();
}
/**
* Returns the name for the root Options name for this {@link GraphDisplayOptions}
* @return the name for the root Options name for this {@link GraphDisplayOptions}
*/
public String getRootOptionsName() {
return rootOptionsName;
}
/**
* Returns the attribute key that can be used to override the color of a vertex. Normally,
* a vertex is colored based on its vertex type. However, if this value is non-null, a vertex
* can override its color by setting an attribute using this key name.
* @return the attribute key that can be used to override the color of a vertex
*/
public String getVertexColorOverrideAttributeKey() {
return vertexColorOverride;
}
/**
* Returns the attribute key that can be used to override the shape of a vertex. Normally,
* a vertex has a shape based on its vertex type. However, if this value is non-null, a vertex
* can override its shape by setting an attribute using this key name.
* @return the attribute key that can be used to override the shape of a vertex
*/
public String getVertexShapeOverrideAttributeKey() {
return vertexShapeOverride;
}
/**
* returns the {@link VertexShape} for any vertex that has not vertex type defined
* @return the {@link VertexShape} for any vertex that has not vertex type defined
*/
public VertexShape getDefaultVertexShape() {
return defaultVertexShape;
}
/**
* Returns the {@link VertexShape} for vertices that have the given vertex type
* @param vertexType the vertex type for which to get its asigned shape
* @return the {@link VertexShape} for vertices that have the given vertex type
*/
public VertexShape getVertexShape(String vertexType) {
return vertexShapeMap.getOrDefault(vertexType, defaultVertexShape);
}
/**
* Returns the vertex selection color
* @return the vertex selection color
*/
public Color getVertexSelectionColor() {
return vertexSelectionColor;
}
/**
* Sets the vertex selection color
* @param vertexSelectionColor the color to use for highlighting selected vertices
*/
public void setVertexSelectionColor(Color vertexSelectionColor) {
this.vertexSelectionColor = vertexSelectionColor;
}
/**
* Returns the color for edge selections
* @return the color fore edge selections
*/
public Color getEdgeSelectionColor() {
return edgeSelectionColor;
}
/**
* Sets the edge selection color
* @param edgeSelectionColor color to use for highlighting selected edges
*/
public void setEdgeSelectionColor(Color edgeSelectionColor) {
this.edgeSelectionColor = edgeSelectionColor;
}
/**
* Returns the name of the default graph layout algorithm
* @return the name of the default graph layout algorithms
*/
public String getDefaultLayoutAlgorithmNameLayout() {
return defaultLayoutAlgorithmName;
}
/**
* Sets the name of the default layout algorithm
* @param defaultLayout the name of the layout algorithm to use by default
*/
public void setDefaultLayoutAlgorithmName(String defaultLayout) {
this.defaultLayoutAlgorithmName = defaultLayout;
}
/**
* Returns true if the rendering mode is to use icons for the vertices. If using
* icons, the label is drawn inside the shape.
* @return true if the rendering mode is to use icons.
*/
public boolean usesIcons() {
return useIcons;
}
/**
* Sets whether the graph rendering mode is to use icons or not. If using icons, the label and
* shape are drawn together into a cached icon. Otherwise, the shapes are drawn on the fly and
* labeled separately.
* @param b true to render in icon mode.
*/
public void setUsesIcons(boolean b) {
this.useIcons = b;
}
/**
* Returns the label position relative to the vertex. Note this is only relevant
* if {@link #usesIcons()} is false
* @return the label position relative to the vertex
*/
public GraphLabelPosition getLabelPosition() {
return labelPosition;
}
/**
* Sets the label position relative to the vertex. Note this is only relevant
* if {@link #usesIcons()} is false.
* @param labelPosition the {@link GraphLabelPosition} to use for rendering vertex labels
*/
public void setLabelPosition(GraphLabelPosition labelPosition) {
this.labelPosition = labelPosition;
}
/**
* Sets the font to use for drawing vertex labels
* @param font the font to use for drawing vertex labels
*/
public void setFont(Font font) {
this.font = font;
}
/**
* Returns the font being used to render vertex labels
* @return the font being used to render vertex labels
*/
public Font getFont() {
return font;
}
/**
* Returns the length of the arrow. The width will be proportional to the length.
* Note: this option is not exposed in the Options because it is too specific to a graph
* instance and wouldn't be appropriate to apply to shared options.
* @return the size if the arrow
*/
public int getArrowLength() {
return arrowLength;
}
/**
* Sets the length of the arrow. The width will be proportional to the length.
* Note: this option is not exposed in the Options because it is too specific to a graph
* instance and wouldn't be appropriate to apply to shared options.
* @param length the size of the arrow
*/
public void setArrowLength(int length) {
this.arrowLength = length;
}
/**
* Returns true if this {@link GraphDisplayOptions} instance has been constructed with
* a tool for getting/saving option values in the tool options
* @return true if this {@link GraphDisplayOptions} instance is connected to tool options
*/
public boolean isRegisteredWithTool() {
return registeredWithTool;
}
/**
* Registers this GraphTypeDisplayOptions with {@link ToolOptions}. Note: this should only
* be used by plugins or other objects that get instantiated immediately when the tool is
* constructed. Otherwise, if the tool exits and this hasn't been called, any saved option
* values will be lost.
* <P>
* @param toolOptions the {@link ToolOptions} to register these options with
* @param help the help location to be used by the {@link OptionsDialog} for display/editing
* these options
*/
public void registerOptions(ToolOptions toolOptions, HelpLocation help) {
Options rootOptions = toolOptions.getOptions(graphType.getOptionsName());
registerVertexColorOptions(rootOptions, help);
registerVertexShapeOptions(rootOptions, help);
registerEdgeColorOptions(rootOptions, help);
registerMiscellaniousOptions(rootOptions, help);
}
/**
* Sets default values for vertex types
* @param vertexType the vertex type whose default color and shape are being defined
* @param vertexShape the default vertex shape for the given vertex type
* @param color the default color for the given vertex type
*/
protected void configureVertexType(String vertexType, VertexShape vertexShape, Color color) {
checkVertexType(vertexType);
vertexShapeMap.put(vertexType, vertexShape);
vertexColorMap.put(vertexType, color);
}
/**
* Sets default values for edge types
* @param edgeType the edge type whose default color and shape are being defined
* @param color the default color for the given edge type
*/
protected void configureEdgeType(String edgeType, Color color) {
checkEdgeType(edgeType);
edgeColorMap.put(edgeType, color);
}
/**
* Loads values from tool options
*
* @param tool the tool from which to update values.
*/
public void initializeFromOptions(Tool tool) {
if (tool == null) {
return;
}
ToolOptions toolOptions = tool.getOptions("Graph");
toolOptions.addOptionsChangeListener(this);
updateOptions(toolOptions.getOptions(rootOptionsName));
registeredWithTool = true;
}
private void updateOptions(Options rootOptions) {
updateVertexColorsFromOptions(rootOptions);
updateEdgeColorsFromOptions(rootOptions);
updateVertexShapesFromOptions(rootOptions);
updateMiscellaniousOptions(rootOptions);
}
private void updateMiscellaniousOptions(Options rootOptions) {
Options options = rootOptions.getOptions(MISCELLANIOUS_OPTIONS);
String shapeName = options.getString(DEFAULT_VERTEX_SHAPE, defaultVertexShape.getName());
defaultVertexShape = VertexShape.getShape(shapeName);
defaultVertexColor = options.getColor(DEFAULT_VERTEX_COLOR, defaultVertexColor);
defaultEdgeColor = options.getColor(DEFAULT_EDGE_COLOR, defaultEdgeColor);
favoredEdgeType = options.getString(FAVORED_EDGE_TYPE, favoredEdgeType);
vertexSelectionColor = options.getColor(VERTEX_SELECTION_COLOR, vertexSelectionColor);
edgeSelectionColor = options.getColor(EDGE_SELECTION_COLOR, edgeSelectionColor);
defaultLayoutAlgorithmName =
options.getString(DEFAULT_LAYOUT_ALGORITHM, defaultLayoutAlgorithmName);
useIcons = options.getBoolean(USE_ICONS, useIcons);
labelPosition = options.getEnum(LABEL_POSITION, labelPosition);
font = options.getFont(FONT, font);
}
private void updateVertexShapesFromOptions(Options rootOptions) {
Options options = rootOptions.getOptions(VERTEX_SHAPES);
for (String vertexType : graphType.getVertexTypes()) {
String current = getVertexShapeName(vertexType);
String shapeName = options.getString(vertexType, current);
if (shapeName != null && !shapeName.equals(current)) {
VertexShape shape = VertexShape.getShape(shapeName);
if (shape != null) {
setVertexShape(vertexType, VertexShape.getShape(shapeName));
}
}
}
}
private void updateEdgeColorsFromOptions(Options rootOptions) {
Options options = rootOptions.getOptions(EDGE_COLORS);
for (String edgeType : graphType.getEdgeTypes()) {
Color current = getEdgeColor(edgeType);
Color color = options.getColor(edgeType, current);
if (color != null && !color.equals(current)) {
setEdgeColor(edgeType, color);
}
}
}
private void notifyListeners() {
for (ChangeListener changeListener : changeListeners) {
changeListener.stateChanged(new ChangeEvent(this));
}
}
private void updateVertexColorsFromOptions(Options rootOptions) {
Options options = rootOptions.getOptions(VERTEX_COLORS);
for (String vertexType : graphType.getVertexTypes()) {
Color current = getVertexColor(vertexType);
Color color = options.getColor(vertexType, current);
if (color != null && !color.equals(current)) {
setVertexColor(vertexType, color);
}
}
}
private void registerVertexColorOptions(Options rootOptions, HelpLocation help) {
Options options = rootOptions.getOptions(VERTEX_COLORS);
for (String vertexType : graphType.getVertexTypes()) {
options.registerOption(vertexType, OptionType.COLOR_TYPE,
getVertexColor(vertexType), help,
"Choose the color for this vertex type");
}
List<String> list = new ArrayList<>(graphType.getVertexTypes());
OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_COLORS, list);
options.registerOptionsEditor(editor);
}
private void registerVertexShapeOptions(Options rootOptions, HelpLocation help) {
Options options = rootOptions.getOptions(VERTEX_SHAPES);
List<String> shapeNames = VertexShape.getShapeNames();
for (String vertexType : graphType.getVertexTypes()) {
StringWithChoicesEditor editor = new StringWithChoicesEditor(shapeNames);
options.registerOption(vertexType, OptionType.STRING_TYPE,
getVertexShapeName(vertexType), help,
"Choose the shape for this vertex type", editor);
}
List<String> list = new ArrayList<>(graphType.getVertexTypes());
OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_SHAPES, list);
options.registerOptionsEditor(editor);
}
private void registerEdgeColorOptions(Options rootOptions, HelpLocation help) {
Options options = rootOptions.getOptions(EDGE_COLORS);
for (String edgeType : graphType.getEdgeTypes()) {
options.registerOption(edgeType, OptionType.COLOR_TYPE,
getEdgeColor(edgeType), help, "Choose the color for this edge type");
}
List<String> list = new ArrayList<>(graphType.getEdgeTypes());
OptionsEditor editor = new ScrollableOptionsEditor(EDGE_COLORS, list);
options.registerOptionsEditor(editor);
}
private void registerMiscellaniousOptions(Options rootOptions, HelpLocation help) {
Options options = rootOptions.getOptions(MISCELLANIOUS_OPTIONS);
StringWithChoicesEditor editor = new StringWithChoicesEditor(VertexShape.getShapeNames());
options.registerOption(VERTEX_SELECTION_COLOR, OptionType.COLOR_TYPE, vertexSelectionColor,
help, "Color for highlighting selected vertices");
options.registerOption(EDGE_SELECTION_COLOR, OptionType.COLOR_TYPE, edgeSelectionColor,
help, "Color for highlighting selected edge");
options.registerOption(DEFAULT_VERTEX_SHAPE, OptionType.STRING_TYPE,
defaultVertexShape.getName(),
help, "Shape for vertices that have no vertex type defined", editor);
options.registerOption(DEFAULT_VERTEX_COLOR, OptionType.COLOR_TYPE, defaultVertexColor,
help, "Color for vertices that have no vertex type defined");
options.registerOption(DEFAULT_EDGE_COLOR, OptionType.COLOR_TYPE, defaultEdgeColor,
help, "Color for edge that have no edge type defined");
editor = new StringWithChoicesEditor(graphType.getEdgeTypes());
options.registerOption(FAVORED_EDGE_TYPE, OptionType.STRING_TYPE, favoredEdgeType, help,
"Favored edge is used to influence layout algorithms", editor);
editor = new StringWithChoicesEditor(LayoutAlgorithmNames.getLayoutAlgorithmNames());
options.registerOption(DEFAULT_LAYOUT_ALGORITHM, OptionType.STRING_TYPE,
defaultLayoutAlgorithmName, help, "Initial layout algorithm", editor);
options.registerOption(USE_ICONS, OptionType.BOOLEAN_TYPE, useIcons, help,
"If true, vertices are drawn using pre-rendered images versus compact shapes");
options.registerOption(LABEL_POSITION, OptionType.ENUM_TYPE, labelPosition, help,
"Relative postion of labels to vertex shape (Only applicable if \"Use Icons\" is true");
options.registerOption(FONT, OptionType.FONT_TYPE, font, help,
"Font to use for vertex labels");
List<String> optionNamesInDisplayOrder = new ArrayList<>();
optionNamesInDisplayOrder.add(VERTEX_SELECTION_COLOR);
optionNamesInDisplayOrder.add(EDGE_SELECTION_COLOR);
optionNamesInDisplayOrder.add(DEFAULT_VERTEX_COLOR);
optionNamesInDisplayOrder.add(DEFAULT_EDGE_COLOR);
optionNamesInDisplayOrder.add(DEFAULT_VERTEX_SHAPE);
optionNamesInDisplayOrder.add(FAVORED_EDGE_TYPE);
optionNamesInDisplayOrder.add(DEFAULT_LAYOUT_ALGORITHM);
optionNamesInDisplayOrder.add(LABEL_POSITION);
optionNamesInDisplayOrder.add(FONT);
optionNamesInDisplayOrder.add(USE_ICONS);
OptionsEditor optionsEditor =
new ScrollableOptionsEditor(MISCELLANIOUS_OPTIONS, optionNamesInDisplayOrder);
options.registerOptionsEditor(optionsEditor);
}
private void checkVertexType(String vertexType) {
if (!getGraphType().containsVertexType(vertexType)) {
throw new IllegalArgumentException("VertexType \"" + vertexType +
"\" not defined in GraphType \"" + getGraphType().getName() + "\".");
}
}
private void checkEdgeType(String edgeType) {
if (!getGraphType().containsEdgeType(edgeType)) {
throw new IllegalArgumentException("EdgeType \"" + edgeType +
"\" not defined in GraphType \"" + getGraphType().getName() + "\".");
}
}
}

View File

@ -0,0 +1,193 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.awt.Color;
import java.util.Objects;
/**
* Builder for building {@link GraphDisplayOptions}
*/
public class GraphDisplayOptionsBuilder {
private GraphDisplayOptions displayOptions;
/**
* Create a new {@link GraphDisplayOptionsBuilder}
* @param graphType the {@link GraphType} of graphs that this instance configures.
*/
public GraphDisplayOptionsBuilder(GraphType graphType) {
displayOptions = new GraphDisplayOptions(graphType);
}
/**
* Sets the default vertex color for vertexes that don't have a registered vertex type
* @param c the default vertex color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder defaultVertexColor(Color c) {
displayOptions.setDefaultVertexColor(c);
return this;
}
/**
* Sets the default edge color for edges that don't have a registered edge type
* @param c the default edge color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder defaultEdgeColor(Color c) {
Objects.requireNonNull(c);
displayOptions.setDefaultEdgeColor(c);
return this;
}
/**
* Sets the vertex selection color
* @param color the vertex selection color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder vertexSelectionColor(Color color) {
displayOptions.setVertexSelectionColor(color);
return this;
}
/**
* Sets the edge selection color
* @param color the edge selection color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder edgeSelectionColor(Color color) {
displayOptions.setEdgeSelectionColor(color);
return this;
}
/**
* Sets the default vertex shape for vertices that don't have a registered vertex type
* @param vertexShape the {@link VertexShape} to use as a default
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder defaultVertexShape(VertexShape vertexShape) {
Objects.requireNonNull(vertexShape);
displayOptions.setDefaultVertexShape(vertexShape);
return this;
}
/**
* Sets the shape and color for vertices of the given type
* @param vertexType the vertex type to assign shape and color
* @param vertexShape the shape to use for the named vertex type
* @param color the color to use for the named vertex type
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder vertex(String vertexType, VertexShape vertexShape,
Color color) {
displayOptions.configureVertexType(vertexType, vertexShape, color);
return this;
}
/**
* Sets the color for edges of the given type
* @param edgeType the edge type to assign color
* @param color the color to use for the named edge type
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder edge(String edgeType, Color color) {
displayOptions.configureEdgeType(edgeType, color);
return this;
}
/**
* Sets the attribute used to override the color for a vertex
* @param colorAttributeKey the attribute key to use for overriding a vertex color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder vertexColorOverrideAttribute(String colorAttributeKey) {
displayOptions.setVertexColorOverrideAttributeKey(colorAttributeKey);
return this;
}
/**
* Sets the attribute used to override the color for a edge
* @param colorAttributeKey the attribute key to use for overriding an edge color
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder edgeColorOverrideAttribute(String colorAttributeKey) {
displayOptions.setEdgeColorOverrideAttributeKey(colorAttributeKey);
return this;
}
/**
* Sets the attribute used to override the shape for a vertex
* @param shapeAttributeKey the attribute key to use of shape override
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder shapeOverrideAttribute(String shapeAttributeKey) {
displayOptions.setVertexShapeOverrideAttributeKey(shapeAttributeKey);
return this;
}
/**
* Sets the name of the layout algorithm that will be used to initially layout the graph
* @param string the name of the layout algoritm to use to initially layout the graph
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder defaultLayoutAlgorithm(String string) {
displayOptions.setDefaultLayoutAlgorithmName(string);
return this;
}
/**
* Sets drawing "mode" for the graph display. If true, vertices are drawn as scaled
* cached images with the label inside the shapes. If false, vertices are drawn as smaller
* shapes with labels drawn near the shapes.
* @param b true to use pre-rendered icon images
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder useIcons(boolean b) {
displayOptions.setUsesIcons(b);
return this;
}
/**
* Sets the length of the arrows to display in the graph. The width will be sized proportionately.
* @param length the length the arrows to display in the graph
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder arrowLength(int length) {
displayOptions.setArrowLength(length);
return this;
}
/**
* Sets the vertex label position relative to vertex shape. This is only applicable if the
* {@link #useIcons(boolean)} is set to true.
* @param labelPosition the relative position to place the vertex label
* @return this {@link GraphDisplayOptionsBuilder}
*/
public GraphDisplayOptionsBuilder labelPosition(GraphLabelPosition labelPosition) {
displayOptions.setLabelPosition(labelPosition);
return this;
}
/**
* Returns a GraphTypeDisplayOptions as configured by this builder
* @return a GraphTypeDisplayOptions as configured by this builder
*/
public GraphDisplayOptions build() {
return displayOptions;
}
}

View File

@ -0,0 +1,23 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
/**
* Specification for the vertex label position relative to the vertex shape.
*/
public enum GraphLabelPosition {
NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST, CENTER
}

View File

@ -0,0 +1,140 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.util.*;
/**
* Class that defines a new graph type. It defines the set of valid vertex and edge types
*/
public class GraphType {
private final String name;
private final String description;
private final Set<String> vertexTypes;
private final Set<String> edgeTypes;
/**
* Constructs a new GraphType
*
* @param name the name of this GraphType instance
* @param description a brief description for graphs of this type
* @param vertexTypes a list of all valid vertex types for graphs of this type
* @param edgeTypes a list of all valid edge types for graphs of this type
*/
public GraphType(String name, String description, List<String> vertexTypes,
List<String> edgeTypes) {
this.name = Objects.requireNonNull(name);
this.description = Objects.requireNonNull(description);
this.vertexTypes = Collections.unmodifiableSet(new LinkedHashSet<String>(vertexTypes));
this.edgeTypes = Collections.unmodifiableSet(new LinkedHashSet<String>(edgeTypes));
}
/**
* Returns a name for this type of graph
* @return a name of this type of graph
*/
public String getName() {
return name;
}
/**
* Returns a description for this type of graph
* @return a description for this type of graph
*/
public String getDescription() {
return description;
}
/**
* Returns a list of valid vertex types for graphs of this type
* @return a list of valid vertex types for graphs of this type
*/
public List<String> getVertexTypes() {
return new ArrayList<>(vertexTypes);
}
/**
* Returns a list of valid edge types for graphs of this type
* @return a list of valid edge types for graphs of this type
*/
public List<String> getEdgeTypes() {
return new ArrayList<>(edgeTypes);
}
/**
* Test if the given string is a valid vertex type
* @param vertexType the string to test for being a valid vertex type
* @return true if the given string is a valid vertex type
*/
public boolean containsVertexType(String vertexType) {
return vertexTypes.contains(vertexType);
}
/**
* Test if the given string is a valid edge type
* @param edgeType the string to test for being a valid edge type
* @return true if the given string is a valid edge type
*/
public boolean containsEdgeType(String edgeType) {
return edgeTypes.contains(edgeType);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((edgeTypes == null) ? 0 : edgeTypes.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((vertexTypes == null) ? 0 : vertexTypes.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GraphType other = (GraphType) obj;
if (!name.equals(other.name)) {
return false;
}
if (!description.equals(other.description)) {
return false;
}
if (!edgeTypes.equals(other.edgeTypes)) {
return false;
}
if (!vertexTypes.equals(other.vertexTypes)) {
return false;
}
return true;
}
public String getOptionsName() {
return getName() + " Graph Type";
}
}

View File

@ -0,0 +1,76 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.util.ArrayList;
import java.util.List;
/**
* Builder class for building new {@link GraphType}s
*/
public class GraphTypeBuilder {
private List<String> vertexTypes = new ArrayList<>();
private List<String> edgeTypes = new ArrayList<>();
private final String name;
private String description;
/**
* Create a new builder
* @param name the name of the new {@link GraphType}
*/
public GraphTypeBuilder(String name) {
this.name = name;
this.description = name;
}
/**
* Sets the description for the {@link GraphType}
* @param text the description
* @return this GraphTypeBuilder
*/
public GraphTypeBuilder description(String text) {
this.description = text;
return this;
}
/**
* Defines a new vertex type
* @param type a string that names a new vertex type
* @return this GraphTypeBuilder
*/
public GraphTypeBuilder vertexType(String type) {
vertexTypes.add(type);
return this;
}
/**
* Defines a new edge type
* @param type a string that names a new edge type
* @return this GraphTypeBuilder
*/
public GraphTypeBuilder edgeType(String type) {
edgeTypes.add(type);
return this;
}
/**
* Builds a new GraphType
* @return a new GraphType
*/
public GraphType build() {
return new GraphType(name, description, vertexTypes, edgeTypes);
}
}

View File

@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.util.Arrays;
import java.util.List;
/**
* Just a static list of graph layout algorithm names
*/
public class LayoutAlgorithmNames {
//@formatter:off
public static final String FORCED_BALANCED = "Force Balanced";
public static final String FORCE_DIRECTED = "Force Directed";
public static final String CIRCLE = "Circle";
public static final String COMPACT_HIERACHICAL = "Compact Hierarchical";
public static final String COMPACT_RADIAL = "Compact Radial";
public static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
public static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
public static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
public static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
public static final String VERT_MIN_CROSS_TOP_DOWN = "Vertical Hierarchical MinCross Top Down";
public static final String VERT_MIN_CROSS_LONGEST_PATH ="Vertical Hierarchical MinCross Longest Path";
public static final String VERT_MIN_CROSS_NETWORK_SIMPLEX ="Vertical Hierarchical MinCross Network Simplex";
public static final String VERT_MIN_CROSS_COFFMAN_GRAHAM ="Vertical Hierarchical MinCross Coffman Graham";
public static final String HIERACHICAL = "Hierarchical";
public static final String RADIAL = "Radial";
public static final String BALLOON = "Balloon";
public static final String GEM = "GEM";
//@formatter:on
public static List<String> getLayoutAlgorithmNames() {
return Arrays.asList(COMPACT_HIERACHICAL, HIERACHICAL,
COMPACT_RADIAL, 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,
FORCED_BALANCED, FORCE_DIRECTED, RADIAL, BALLOON, GEM);
}
}

View File

@ -0,0 +1,339 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.*;
import java.util.*;
/**
* Class for defining shapes to use for rendering vertices in a graph
*/
public abstract class VertexShape {
private static Map<String, VertexShape> registeredShapes = new HashMap<>();
private static int SIZE = 50;
public static VertexShape RECTANGLE = new RectangleVertexShape(SIZE);
public static VertexShape ELLIPSE = new EllipseVertexShape(SIZE);
public static VertexShape TRIANGLE_UP = new TriangleUpVertexShape(SIZE);
public static VertexShape TRIANGLE_DOWN = new TriangleDownVertexShape(SIZE);
public static VertexShape STAR = new StarVertexShape(SIZE);
public static VertexShape DIAMOND = new DiamondVertexShape(SIZE);
public static VertexShape PENTAGON = new PentagonVertexShape(SIZE);
public static VertexShape HEXAGON = new HexagonVertexShape(SIZE);
public static VertexShape OCTAGON = new OctagonVertexShape(SIZE);
private Shape cachedShape;
private String name;
private int size;
VertexShape(String name, int size) {
this.name = name;
this.size = size;
registeredShapes.put(name, this);
}
/**
* Returns the name of the shape
* @return the name of the shape
*/
public String getName() {
return name;
}
/**
* Returns the {@link Shape} for this {@link VertexShape} instance
* @return the {@link Shape} for this {@link VertexShape} instance
*/
public Shape getShape() {
if (cachedShape == null) {
cachedShape = size(createShape());
}
return cachedShape;
}
private Shape size(Shape shape) {
AffineTransform transform = new AffineTransform();
Rectangle bounds = shape.getBounds();
double scale = size / bounds.getWidth();
transform.scale(scale, scale);
return transform.createTransformedShape(shape);
}
/**
* Gets the relative amount of margin space to allocate above the label. The default is
* 0.5 which will center the label in the associated shape. A value closer to 0 will move
* the label closer to the top and a value closer to 1 will move the label closer to the
* bottom.
* @return the relative amount of margin space to allocate obove the label.s
*/
public double getLabelPosition() {
return .5;
}
/**
* Returns the size factor for a shape relative to its label. Shapes are sized based on the
* label of a vertex so that the label can fit inside the shape (mostly). Some subclasses
* will need to override this value to some value > 1 to fit the label in the shape. For
* example, a rectangle shape does not need to be extended because text naturally fits. But
* for a shape like a triangle, its bounding box needs to be bigger so that text doesn't
* "stick out" in the narrow part of the triangle.
* @return the size factor for a shape relatvie to its label
*/
public double getShapeToLabelRatio() {
return 1.0;
}
/**
* This is a factor to keep some shapes from being so distorted by very long labels that they
* effectively lose their shape when seen by the user
* @return the max width to height ratio
*/
public int getMaxWidthToHeightRatio() {
return 10;
}
protected abstract Shape createShape();
/**
* Returns the {@link VertexShape} for the given shape name
* @param shapeName the name of the shape for which to get the {@link VertexShape}
* @return the {@link VertexShape} for the given shape name
*/
public static VertexShape getShape(String shapeName) {
return registeredShapes.get(shapeName);
}
/**
* Returns a list of names for all the supported {@link VertexShape}s
* @return a list of names for all the supported {@link VertexShape}s
*/
public static List<String> getShapeNames() {
ArrayList<String> list = new ArrayList<String>(registeredShapes.keySet());
Collections.sort(list);
return list;
}
@Override
public int hashCode() {
return Objects.hash(name, size);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
VertexShape other = (VertexShape) obj;
return Objects.equals(name, other.name) && size == other.size;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//Vertex Shape Classes
//////////////////////////////////////////////////////////////////////////////////////////////////
static class RectangleVertexShape extends VertexShape {
private RectangleVertexShape(int size) {
super("Rectangle", size);
}
protected Shape createShape() {
return new Rectangle2D.Double(-1.0, -1.0, 2.0, 2.0);
}
}
static class EllipseVertexShape extends VertexShape {
private EllipseVertexShape(int size) {
super("Ellipse", size);
}
protected Shape createShape() {
return new Ellipse2D.Double(-1.0, 1.0, 2.0, 2.0);
}
@Override
public double getShapeToLabelRatio() {
return 1.4;
}
}
static class TriangleUpVertexShape extends VertexShape {
private TriangleUpVertexShape(int size) {
super("Triangle Up", size);
}
protected Shape createShape() {
Path2D path = new Path2D.Double();
path.moveTo(-1.0, 1.0);
path.lineTo(1.0, 1.0);
path.lineTo(0.0, -1.0);
path.closePath();
return path;
}
@Override
public double getShapeToLabelRatio() {
return 1.6;
}
@Override
public double getLabelPosition() {
return 0.90;
}
}
static class TriangleDownVertexShape extends VertexShape {
private TriangleDownVertexShape(int size) {
super("Triangle Down", size);
}
protected Shape createShape() {
Path2D path = new Path2D.Double();
path.moveTo(-1.0, -1.0);
path.lineTo(1.0, -1.0);
path.lineTo(0.0, 1.0);
path.closePath();
return path;
}
@Override
public double getShapeToLabelRatio() {
return 1.6;
}
@Override
public double getLabelPosition() {
return 0.10;
}
}
static class StarVertexShape extends VertexShape {
private StarVertexShape(int size) {
super("Star", size);
}
protected Shape createShape() {
int numPoints = 7;
Path2D path = new Path2D.Double();
double outerRadius = 2;
double innerRadius = 1;
double deltaAngle = Math.PI / numPoints;
double angle = 3 * Math.PI / 2; // start such that star points up.
path.moveTo(outerRadius * Math.cos(angle), outerRadius * Math.sin(angle));
for (int i = 0; i < numPoints; i++) {
angle += deltaAngle;
path.lineTo(innerRadius * Math.cos(angle), innerRadius * Math.sin(angle));
angle += deltaAngle;
path.lineTo(outerRadius * Math.cos(angle), outerRadius * Math.sin(angle));
}
return path;
}
@Override
public double getShapeToLabelRatio() {
return 2.0;
}
}
static class DiamondVertexShape extends VertexShape {
private DiamondVertexShape(int size) {
super("Diamond", size);
}
protected Shape createShape() {
Path2D path = new Path2D.Double();
path.moveTo(0.0, -1.0);
path.lineTo(-1.0, 0.0);
path.lineTo(0.0, 1.0);
path.lineTo(1.0, 0.0);
path.closePath();
return path;
}
@Override
public double getShapeToLabelRatio() {
return 1.6;
}
}
static class EquilateralPolygonVertexShape extends VertexShape {
private int numSides;
private double startAngle;
protected EquilateralPolygonVertexShape(String name, int numSides, double startAngle,
int size) {
super(name, size);
this.numSides = numSides;
this.startAngle = startAngle;
}
protected Shape createShape() {
Path2D path = new Path2D.Double();
double deltaAngle = Math.PI * 2 / numSides;
double angle = startAngle;
path.moveTo(Math.cos(angle), Math.sin(angle));
for (int i = 0; i < numSides; i++) {
angle += deltaAngle;
path.lineTo(Math.cos(angle), Math.sin(angle));
}
return path;
}
@Override
public int getMaxWidthToHeightRatio() {
return 2;
}
@Override
public double getShapeToLabelRatio() {
return 1.4;
}
}
static class PentagonVertexShape extends EquilateralPolygonVertexShape {
private PentagonVertexShape(int size) {
super("Pentaon", 5, Math.PI + Math.PI / 10, size);
}
}
static class HexagonVertexShape extends EquilateralPolygonVertexShape {
private HexagonVertexShape(int size) {
super("Hexagon", 6, 0, size);
}
}
static class OctagonVertexShape extends EquilateralPolygonVertexShape {
private OctagonVertexShape(int size) {
super("Octagon", 8, 0, size);
}
}
}

View File

@ -28,7 +28,7 @@ public class AttributedGraphTest {
@Before
public void setup() {
graph = new AttributedGraph();
graph = new AttributedGraph("Test", new EmptyGraphType());
}
@Test
@ -192,7 +192,7 @@ public class AttributedGraphTest {
@Test
public void testNonCollapsingEdges() {
graph = new AttributedGraph(false);
graph = new AttributedGraph("Test", new EmptyGraphType(), "Test", false);
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B");

View File

@ -0,0 +1,281 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import static org.junit.Assert.*;
import java.awt.Color;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import docking.FakeDockingTool;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.util.HelpLocation;
import ghidra.util.WebColors;
public class GraphDisplayOptionsTest {
private GraphType graphType;
private GraphDisplayOptions options;
@Before
public void setUp() {
List<String> vertexTypes = Arrays.asList("V1", "V2", "V3" );
List<String> edgeTypes = Arrays.asList("E1", "E2", "E3" );
graphType = new GraphType("Test", "Test Description", vertexTypes, edgeTypes);
options = new GraphDisplayOptions(graphType);
}
@Test
public void testSetAndGetDefaultVertexShape() {
options.setDefaultVertexShape(VertexShape.STAR);
assertEquals(VertexShape.STAR, options.getDefaultVertexShape());
}
@Test
public void testSetAndGetDefaultVertexColor() {
options.setDefaultVertexColor(Color.MAGENTA);
assertEquals(Color.MAGENTA, options.getDefaultVertexColor());
}
@Test
public void testSetAndGetDefaultEdgeColor() {
options.setDefaultEdgeColor(Color.MAGENTA);
assertEquals(Color.MAGENTA, options.getDefaultEdgeColor());
}
@Test
public void testSetAndGetVertexLabelOverride() {
assertEquals(null, options.getVertexLabelOverride());
options.setVertexLabelOverrideAttributeKey("LABEL");
assertEquals("LABEL", options.getVertexLabelOverride());
}
@Test
public void testSetAndGetVertexColorOverrideAttributeKey() {
assertEquals(null, options.getVertexColorOverrideAttributeKey());
options.setVertexColorOverrideAttributeKey("COLOR");
assertEquals("COLOR", options.getVertexColorOverrideAttributeKey());
}
@Test
public void testSetAndGetEdgeColorOverrideAttributeKey() {
assertEquals(null, options.getEdgeColorOverrideAttributeKey());
options.setEdgeColorOverrideAttributeKey("COLOR");
assertEquals("COLOR", options.getEdgeColorOverrideAttributeKey());
}
@Test
public void testSetAndGetVertexShapeOverrideAttributeKey() {
assertEquals(null, options.getVertexShapeOverrideAttributeKey());
options.setVertexColorOverrideAttributeKey("SHAPE");
assertEquals("SHAPE", options.getVertexColorOverrideAttributeKey());
}
@Test
public void testGetVertexLabel() {
AttributedVertex vertex = new AttributedVertex("Foo");
assertEquals("Foo", options.getVertexLabel(vertex));
}
@Test
public void testGetVertexLabelWithLableOverride() {
options.setVertexLabelOverrideAttributeKey("Label");
AttributedVertex vertex = new AttributedVertex("Foo");
vertex.setAttribute("Label", "Bar");
assertEquals("Bar", options.getVertexLabel(vertex));
}
@Test
public void testGetVertexShape() {
options.setVertexShape("V1", VertexShape.DIAMOND);
options.setVertexShape("V2", VertexShape.PENTAGON);
AttributedVertex vertex = new AttributedVertex("Foo");
vertex.setVertexType("V1");
assertEquals(VertexShape.DIAMOND, options.getVertexShape(vertex));
vertex.setVertexType("V2");
assertEquals(VertexShape.PENTAGON, options.getVertexShape(vertex));
}
@Test
public void testGetVertexShapeWithOverride() {
options.setVertexShape("V1", VertexShape.DIAMOND);
options.setVertexShapeOverrideAttributeKey("Shape");
AttributedVertex vertex = new AttributedVertex("Foo");
vertex.setVertexType("V1");
assertEquals(VertexShape.DIAMOND, options.getVertexShape(vertex));
vertex.setAttribute("Shape", VertexShape.ELLIPSE.getName());
assertEquals(VertexShape.ELLIPSE, options.getVertexShape(vertex));
}
@Test
public void testGetVertexColor() {
options.setVertexColor("V1", Color.RED);
options.setVertexColor("V2", Color.GREEN);
AttributedVertex vertex = new AttributedVertex("Foo");
assertEquals(options.getDefaultVertexColor(), options.getVertexColor(vertex));
vertex.setVertexType("V1");
assertEquals(Color.RED, options.getVertexColor(vertex));
vertex.setVertexType("V2");
assertEquals(Color.GREEN, options.getVertexColor(vertex));
}
@Test
public void testGetVertexColorWithOverride() {
options.setVertexColor("V1", Color.RED);
options.setVertexColor("V2", Color.GREEN);
options.setVertexColorOverrideAttributeKey("Color");
AttributedVertex vertex = new AttributedVertex("Foo");
vertex.setVertexType("V1");
assertEquals(Color.RED, options.getVertexColor(vertex));
vertex.setAttribute("Color", WebColors.toString(Color.BLUE));
assertEquals(Color.BLUE, options.getVertexColor(vertex));
}
@Test
public void testGetEdgeColor() {
options.setEdgeColor("E1", Color.RED);
options.setEdgeColor("E2", Color.GREEN);
AttributedEdge edge = new AttributedEdge("1");
assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor(edge));
edge.setEdgeType("E1");
assertEquals(Color.RED, options.getEdgeColor(edge));
edge.setEdgeType("E2");
assertEquals(Color.GREEN, options.getEdgeColor(edge));
}
@Test
public void testGetEdgeColorWithOverride() {
options.setEdgeColor("E1", Color.RED);
options.setEdgeColor("E2", Color.GREEN);
options.setEdgeColorOverrideAttributeKey("Color");
AttributedEdge edge = new AttributedEdge("1");
assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor(edge));
edge.setEdgeType("E1");
assertEquals(Color.RED, options.getEdgeColor(edge));
edge.setAttribute("Color", WebColors.toString(Color.BLUE));
assertEquals(Color.BLUE, options.getEdgeColor(edge));
}
@Test
public void testGetEdgePriority() {
assertEquals(0, options.getEdgePriority("E1").intValue());
assertEquals(1, options.getEdgePriority("E2").intValue());
}
@Test
public void testGetFavoredEdgeType() {
// favored edge defaults to first edge defined
assertEquals("E1", options.getFavoredEdgeType());
options.setFavoredEdgeType("E2");
assertEquals("E2", options.getFavoredEdgeType());
}
@Test
public void testGetVertexColorForType() {
assertEquals(options.getDefaultVertexColor(), options.getVertexColor("V1"));
options.setVertexColor("V1", Color.RED);
assertEquals(Color.RED, options.getVertexColor("V1"));
}
@Test
public void testGetVertexShapeForType() {
assertEquals(options.getDefaultVertexShape(), options.getVertexShape("V1"));
options.setVertexShape("V1", VertexShape.STAR);
assertEquals(VertexShape.STAR, options.getVertexShape("V1"));
}
@Test
public void testGetEdgeColorForType() {
assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor("V1"));
options.setEdgeColor("E1", Color.RED);
assertEquals(Color.RED, options.getEdgeColor("E1"));
}
@Test
public void testRegisterOptions() {
ToolOptions toolOptions = new ToolOptions("Test");
HelpLocation help = new HelpLocation("Topic", "anchor");
options.registerOptions(toolOptions, help);
Options graphDisplayOptions = toolOptions.getOptions(options.getRootOptionsName());
assertNotNull(graphDisplayOptions);
Options vertexColorOptions = graphDisplayOptions.getOptions("Vertex Colors");
List<String> leafOptionNames = vertexColorOptions.getLeafOptionNames();
assertEquals(Arrays.asList("V1", "V2", "V3"), leafOptionNames);
assertEquals(options.getDefaultVertexColor(),
vertexColorOptions.getColor("V1", Color.WHITE));
Options vertexShapeOptions = graphDisplayOptions.getOptions("Vertex Shapes");
leafOptionNames = vertexShapeOptions.getLeafOptionNames();
assertEquals(Arrays.asList("V1", "V2", "V3"), leafOptionNames);
assertEquals(options.getDefaultVertexShape().getName(),
vertexShapeOptions.getString("V1", "Bob"));
Options edgeColorOptions = graphDisplayOptions.getOptions("Edge Colors");
leafOptionNames = edgeColorOptions.getLeafOptionNames();
assertEquals(Arrays.asList("E1", "E2", "E3"), leafOptionNames);
assertEquals(options.getDefaultEdgeColor(),
edgeColorOptions.getColor("E1", Color.WHITE));
Options miscellaniousOptions = graphDisplayOptions.getOptions("Miscellanious");
leafOptionNames = miscellaniousOptions.getLeafOptionNames();
assertEquals(Arrays.asList("Use Icons", "Selected Vertex Color", "Default Layout Algorithm",
"Default Vertex Color", "Default Vertex Shape", "Selected Edge Color", "Label Position",
"Default Edge Color", "Font", "Favored Edge Type"), leafOptionNames);
}
@Test
public void testChangingToolOptionsAffectsGraph() {
FakeDockingTool tool = new FakeDockingTool();
ToolOptions toolOptions = tool.getOptions("Graph");
options.registerOptions(toolOptions, null);
options.initializeFromOptions(tool);
AttributedVertex vertex = new AttributedVertex("Foo");
vertex.setVertexType("V1");
assertEquals(Color.BLUE, options.getVertexColor(vertex));
Options graphDisplayOptions = toolOptions.getOptions(options.getRootOptionsName());
Options vertexColorOptions = graphDisplayOptions.getOptions("Vertex Colors");
vertexColorOptions.setColor("V1", Color.CYAN);
assertEquals(Color.CYAN, options.getVertexColor(vertex));
}
}

View File

@ -0,0 +1,71 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
public class GraphTypeBuilderTest {
@Test
public void testName() {
GraphType graphType = new GraphTypeBuilder("Test").build();
assertEquals("Test", graphType.getName());
}
@Test
public void testDescription() {
GraphType graphType = new GraphTypeBuilder("Test")
.description("abc")
.build();
assertEquals("abc", graphType.getDescription());
}
@Test
public void testNoDescriptionUsesName() {
GraphType graphType = new GraphTypeBuilder("Test").build();
assertEquals("Test", graphType.getDescription());
}
@Test
public void testVertexType() {
GraphType graphType = new GraphTypeBuilder("Test")
.vertexType("V1")
.vertexType("V2")
.build();
List<String> vertexTypes = graphType.getVertexTypes();
assertEquals(2, vertexTypes.size());
assertEquals("V1", vertexTypes.get(0));
assertEquals("V2", vertexTypes.get(1));
}
@Test
public void testEdgeType() {
GraphType graphType = new GraphTypeBuilder("Test")
.edgeType("E1")
.edgeType("E2")
.build();
List<String> edgeTypes = graphType.getEdgeTypes();
assertEquals(2, edgeTypes.size());
assertEquals("E1", edgeTypes.get(0));
assertEquals("E2", edgeTypes.get(1));
}
}

View File

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
public class GraphTypeTest {
private GraphType graphType;
@Before
public void setUp() {
List<String> vertexTypes = Arrays.asList("V1", "V2", "V3" );
List<String> edgeTypes = Arrays.asList("E1", "E2", "E3" );
graphType = new GraphType("Test", "Test Description", vertexTypes, edgeTypes);
}
@Test
public void testName() {
assertEquals("Test", graphType.getName());
}
@Test
public void testDescription() {
assertEquals("Test Description", graphType.getDescription());
}
@Test
public void testGetVertexTypes() {
List<String> types = graphType.getVertexTypes();
assertEquals(3, types.size());
assertEquals("V1", types.get(0));
assertEquals("V2", types.get(1));
assertEquals("V3", types.get(2));
}
@Test
public void testGetEdgeTypes() {
List<String> types = graphType.getEdgeTypes();
assertEquals(3, types.size());
assertEquals("E1", types.get(0));
assertEquals("E2", types.get(1));
assertEquals("E3", types.get(2));
}
@Test
public void testContainsVertexType() {
assertTrue(graphType.containsVertexType("V1"));
assertTrue(graphType.containsVertexType("V2"));
assertTrue(graphType.containsVertexType("V3"));
assertFalse(graphType.containsVertexType("E1"));
}
@Test
public void testContainsEdgeType() {
assertTrue(graphType.containsEdgeType("E1"));
assertTrue(graphType.containsEdgeType("E2"));
assertTrue(graphType.containsEdgeType("E3"));
assertFalse(graphType.containsEdgeType("V2"));
}
}

View File

@ -139,33 +139,6 @@ public interface GraphDisplay {
*/
public void close();
/**
* Defines a vertex attribute type for this graph window
*
* @param name the name of the attribute which may be attached to vertices
*/
public void defineVertexAttribute(String name);
/**
* Defines an edge attribute type for this graph window
*
* @param name the name of the attribute which may be attached to edges.
*/
public void defineEdgeAttribute(String name);
/**
* Sets the name of the attribute which should be used as the primary vertex label
*
* @param attributeName the name of the attribute to use as the display label for vertices
* @param alignment (ALIGN_LEFT, ALIGN_RIGHT, or ALIGN_CENTER)
* @param size the font size to use for the display label
* @param monospace true if the font should be monospaced
* @param maxLines the maximum number lines to display in the vertex labels
*/
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
boolean monospace,
int maxLines);
/**
* Sets the graph to be displayed or consumed by this graph display
*
@ -174,10 +147,25 @@ public interface GraphDisplay {
* @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation
* @param append if true, append the new graph to any existing graph
* @throws CancelledException thrown if the graphing operation was cancelled
* @deprecated You should now use the form that takes in a {@link GraphDisplayOptions}
*/
public void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor)
throws CancelledException;
public default void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor) throws CancelledException {
setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor);
}
/**
* Sets the graph to be displayed or consumed by this graph display
*
* @param graph the graph to display or consume
* @param options {@link GraphDisplayOptions} for configuring how the display will
* render vertices and edges based on there vertex type and edge type respectively.
* @param title a title for the graph
* @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation
* @param append if true, append the new graph to any existing graph
* @throws CancelledException thrown if the graphing operation was cancelled
*/
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
boolean append, TaskMonitor monitor) throws CancelledException;
/**
* Clears all graph vertices and edges from this graph display

View File

@ -22,9 +22,6 @@ import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.exception.GraphException;
import ghidra.util.task.TaskMonitor;
import java.util.Collections;
import java.util.Map;
/**
* Basic interface for objects that can display or otherwise consume a generic graph
*/
@ -46,19 +43,6 @@ public interface GraphDisplayProvider extends ExtensionPoint {
*/
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException;
/**
* Returns a GraphDisplay that can be used to "display" a graph
*
* @param reuseGraph if true, this provider will attempt to re-use an existing GraphDisplay
* @param properties a {@code Map} of property key/values that can be used to customize the display
* @param monitor the {@link TaskMonitor} that can be used to monitor and cancel the operation
* @return A GraphDisplay that can be used to display (or otherwise consume - e.g. export) the graph
* @throws GraphException thrown if there is a problem creating a GraphDisplay
*/
default GraphDisplay getGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor) throws GraphException {
return getGraphDisplay(reuseGraph, monitor);
}
/**
* Provides an opportunity for this provider to register and read tool options

View File

@ -22,6 +22,7 @@ import org.junit.Test;
import docking.ComponentProvider;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.graph.CallGraphType;
import ghidra.graph.export.GraphExporterDialog;
import ghidra.graph.visualization.DefaultGraphDisplay;
import ghidra.graph.visualization.DefaultGraphDisplayComponentProvider;
@ -46,7 +47,7 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator {
GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class);
GraphDisplayProvider export = broker.getGraphDisplayProvider("Graph Export");
GraphDisplay display = export.getGraphDisplay(false, TaskMonitor.DUMMY);
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
display.setGraph(graph, "test", false, TaskMonitor.DUMMY);
GraphExporterDialog dialog = (GraphExporterDialog) getDialog();
dialog.setFilePath("/users/user1/graph");
@ -59,18 +60,18 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator {
GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class);
GraphDisplayProvider export = broker.getGraphDisplayProvider("Default Graph Display");
GraphDisplay display = export.getGraphDisplay(false, TaskMonitor.DUMMY);
AttributedGraph graph = new AttributedGraph();
AttributedGraph graph = new AttributedGraph("Test", new CallGraphType());
AttributedVertex v1 = graph.addVertex("0000", "main");
v1.setAttribute("VertexType", "Entry");
v1.setVertexType("Entry");
AttributedVertex v2 = graph.addVertex("0100", "Fun_One");
v2.setAttribute("VertexType", "Entry");
v2.setVertexType("Entry");
AttributedVertex v3 = graph.addVertex("0200", "Fun_Two");
v3.setAttribute("VertexType", "Entry");
v3.setVertexType("Entry");
AttributedEdge e1 = graph.addEdge(v1, v2);
e1.setAttribute("EdgeType", "Unconditional-Call");
e1.setEdgeType("Unconditional-Call");
AttributedEdge e2 = graph.addEdge(v1, v3);
e2.setAttribute("EdgeType", "Unconditional-Call");
e2.setEdgeType("Unconditional-Call");
display.setGraph(graph, "Program Graph", false, TaskMonitor.DUMMY);
waitForSwing();

View File

@ -536,7 +536,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
}
private AttributedGraph createGraph() {
AttributedGraph g = new AttributedGraph();
AttributedGraph g = new AttributedGraph("Test", new EmptyGraphType());
a = g.addVertex("A");
b = g.addVertex("B");
c = g.addVertex("C");

View File

@ -87,7 +87,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType());
}
@Test
@ -122,7 +122,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType());
}
@Test
@ -143,7 +143,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType());
}
@Test
@ -164,7 +164,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType());
}
@Test
@ -184,7 +184,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType());
}
@Test
@ -205,7 +205,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(v2);
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE));
assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType());
}
@Test

View File

@ -15,7 +15,8 @@
*/
package ghidra.graph;
import java.util.*;
import java.util.Map;
import java.util.Set;
import docking.action.DockingActionIf;
import docking.widgets.EventTrigger;
@ -24,8 +25,6 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class TestGraphDisplay implements GraphDisplay {
private Set<String> definedVertexAttributes = new HashSet<>();
private Set<String> definedEdgeAttributes = new HashSet<>();
private AttributedGraph graph;
private String title;
private GraphDisplayListener listener;
@ -62,23 +61,6 @@ public class TestGraphDisplay implements GraphDisplay {
// nothing
}
@Override
public void defineVertexAttribute(String name) {
definedVertexAttributes.add(name);
}
@Override
public void defineEdgeAttribute(String name) {
definedEdgeAttributes.add(name);
}
@Override
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
boolean monospace,
int maxLines) {
// nothing
}
@Override
public void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor)
@ -92,6 +74,12 @@ public class TestGraphDisplay implements GraphDisplay {
this.title = title;
}
@Override
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
boolean append, TaskMonitor monitor) throws CancelledException {
setGraph(graph, title, append, monitor);
}
@Override
public void clear() {
// nothing
@ -133,7 +121,7 @@ public class TestGraphDisplay 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));
}

View File

@ -23,8 +23,7 @@ import java.util.Set;
import org.junit.Test;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.graph.TestGraphDisplay;
import ghidra.graph.TestGraphService;
import ghidra.graph.*;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.program.util.ProgramLocation;
@ -46,7 +45,7 @@ public class BlockGraphEventTest extends AbstractBlockGraphTest {
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, false, false, false,
new BlockGraphTask(new BlockFlowGraphType(), false, false, false,
tool, null, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);

View File

@ -21,16 +21,13 @@ import java.util.Map;
import org.junit.Test;
import ghidra.graph.TestGraphDisplay;
import ghidra.graph.TestGraphService;
import ghidra.graph.*;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*;
import ghidra.util.task.TaskMonitor;
public class BlockGraphTaskTest extends AbstractBlockGraphTest {
private static final boolean SHOW_CODE = true;
private static final boolean DONT_SHOW_CODE = false;
@Test
public void testBlockGraph() throws Exception {
@ -39,8 +36,8 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false,
tool, null, null, model, graphService);
new BlockGraphTask(new BlockFlowGraphType(), false, false,
false, tool, null, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
@ -74,7 +71,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
assertNotNull(e4);
assertNotNull(e5);
Map<String, String> map = v1.getAttributeMap();
Map<String, String> map = v1.getAttributes();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
@ -83,14 +80,13 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
assertEquals("Body", v4.getAttribute("VertexType"));
assertEquals("Exit", v5.getAttribute("VertexType"));
map = e1.getAttributeMap();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
map = e1.getAttributes();
assertEquals(1, map.size());
assertTrue(map.containsKey("EdgeType"));
assertEquals("Fall-Through", e3.getAttribute("EdgeType"));
assertEquals("Fall-Through", e4.getAttribute("EdgeType"));
assertEquals("Conditional-Jump", e5.getAttribute("EdgeType"));
assertEquals("Fall Through", e3.getAttribute("EdgeType"));
assertEquals("Fall Through", e4.getAttribute("EdgeType"));
assertEquals("Conditional Jump", e5.getAttribute("EdgeType"));
}
@Test
@ -100,7 +96,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, SHOW_CODE, false, false,
new BlockGraphTask(new CodeFlowGraphType(), false, false, false,
tool, null, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
@ -135,7 +131,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
assertNotNull(e4);
assertNotNull(e5);
Map<String, String> map = v3.getAttributeMap();
Map<String, String> map = v3.getAttributes();
assertEquals(4, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
@ -153,7 +149,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, false, false, false,
new BlockGraphTask(new CallGraphType(), false, false, false,
tool, null, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
@ -174,7 +170,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
Map<String, String> map = v1.getAttributeMap();
Map<String, String> map = v1.getAttributes();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
@ -182,13 +178,12 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
assertEquals("Entry", v1.getAttribute("VertexType"));
assertEquals("Entry", v2.getAttribute("VertexType"));
map = e1.getAttributeMap();
map = e1.getAttributes();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertEquals(1, map.size());
assertTrue(map.containsKey("EdgeType"));
assertEquals("Unconditional-Call", e1.getAttribute("EdgeType"));
assertEquals("Unconditional Call", e1.getAttribute("EdgeType"));
}
@ -200,8 +195,8 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest {
TestGraphService graphService = new TestGraphService();
ProgramSelection sel = new ProgramSelection(addr(0x1002239), addr(0x1002247));
BlockGraphTask task =
new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false,
tool, sel, null, model, graphService);
new BlockGraphTask(new BlockFlowGraphType(), false, false,
false, tool, sel, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);

View File

@ -227,7 +227,7 @@ public class DataReferenceGraphTaskTest extends AbstractDataReferenceGraphTest {
assertNotNull(v1);
assertNotNull(v2);
assertNotNull(e1);
assertEquals("TriangleDown", v1.getAttribute("Icon"));
assertEquals("Initial Instruction", v1.getVertexType());
}
@Test