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