GT-3019 - Function Graph - Added option for edge routing around vertices

This commit is contained in:
dragonmacher 2019-07-25 18:40:21 -04:00
parent ac98e609d7
commit a04185e942
24 changed files with 933 additions and 259 deletions

View file

@ -48,7 +48,6 @@ import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import resources.Icons;
import resources.ResourceManager;
@ -72,7 +71,6 @@ class FGActionManager {
private MultiStateDockingAction<EdgeDisplayType> vertexHoverModeAction;
private MultiStateDockingAction<EdgeDisplayType> vertexFocusModeAction;
private FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder();
private MultiStateDockingAction<Class<? extends FGLayoutProvider>> layoutAction;
FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) {
@ -857,8 +855,7 @@ class FGActionManager {
HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
layoutAction = new MultiStateDockingAction<Class<? extends FGLayoutProvider>>(
"Relayout Graph", plugin.getName()) {
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) {
@Override
protected void doActionPerformed(ActionContext context) {
@ -901,20 +898,12 @@ class FGActionManager {
private List<ActionState<Class<? extends FGLayoutProvider>>> loadActionStatesForLayoutProviders() {
Set<FGLayoutProvider> instances = layoutFinder.findLayouts();
if (instances.isEmpty()) {
throw new AssertException("Could not find any layout providers. You project may not " +
"be configured properly.");
}
List<FGLayoutProvider> layoutInstances = new ArrayList<>(instances);
Collections.sort(layoutInstances,
(o1, o2) -> -o1.getPriorityLevel() + o2.getPriorityLevel());
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders();
List<ActionState<Class<? extends FGLayoutProvider>>> list = new ArrayList<>();
HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
for (FGLayoutProvider layout : layoutInstances) {
ActionState<Class<? extends FGLayoutProvider>> layoutState = new ActionState<>(
layout.getLayoutName(), layout.getActionIcon(), layout.getClass());
layoutState.setHelpLocation(layoutHelpLocation);
@ -996,7 +985,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation);
vertexHoverModeAction =
new MultiStateDockingAction<EdgeDisplayType>("Block Hover Mode", plugin.getName()) {
new MultiStateDockingAction<>("Block Hover Mode", plugin.getName()) {
@Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1065,7 +1054,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation);
vertexFocusModeAction =
new MultiStateDockingAction<EdgeDisplayType>("Block Focus Mode", plugin.getName()) {
new MultiStateDockingAction<>("Block Focus Mode", plugin.getName()) {
@Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1204,10 +1193,6 @@ class FGActionManager {
}
}
void setLayoutFinder(FGLayoutFinder layoutFinder) {
this.layoutFinder = layoutFinder;
}
void setEdgeFocusMode(EdgeDisplayType edgeDisplayType) {
vertexFocusModeAction.setCurrentActionStateByUserData(edgeDisplayType);
}

View file

@ -27,6 +27,8 @@ import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager;
@ -40,6 +42,7 @@ import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.exception.AssertException;
import resources.ResourceManager;
//@formatter:off
@ -78,6 +81,7 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
private FunctionGraphOptions functionGraphOptions = new FunctionGraphOptions();
private FGColorProvider colorProvider;
private List<FGLayoutProvider> layoutProviders;
public FunctionGraphPlugin(PluginTool tool) {
super(tool, true, true, true);
@ -88,6 +92,9 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
@Override
protected void init() {
super.init();
layoutProviders = loadLayoutProviders();
createNewProvider();
initializeOptions();
@ -125,17 +132,44 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
}
}
private List<FGLayoutProvider> loadLayoutProviders() {
FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder();
Set<FGLayoutProvider> instances = layoutFinder.findLayouts();
if (instances.isEmpty()) {
throw new AssertException("Could not find any layout providers. You project may not " +
"be configured properly.");
}
List<FGLayoutProvider> layouts = new ArrayList<>(instances);
Collections.sort(layouts, (o1, o2) -> -o1.getPriorityLevel() + o2.getPriorityLevel());
return layouts;
}
private void initializeOptions() {
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
options.addOptionsChangeListener(this);
functionGraphOptions.initializeOptions(this, options);
functionGraphOptions.loadOptions(this, options);
functionGraphOptions.registerOptions(options);
functionGraphOptions.loadOptions(options);
for (FGLayoutProvider layoutProvider : layoutProviders) {
String layoutName = layoutProvider.getLayoutName();
Options layoutToolOptions = options.getOptions(layoutName);
FGLayoutOptions layoutOptions = layoutProvider.createLayoutOptions(layoutToolOptions);
if (layoutOptions == null) {
continue; // many layouts do not have options
}
layoutOptions.registerOptions(layoutToolOptions);
layoutOptions.loadOptions(layoutToolOptions);
functionGraphOptions.setLayoutOptions(layoutName, layoutOptions);
}
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
functionGraphOptions.loadOptions(this, options);
functionGraphOptions.loadOptions(options);
connectedProvider.getComponent().repaint();
for (FGProvider provider : disconnectedProviders) {
provider.getComponent().repaint();
@ -383,14 +417,6 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
}
}
@Override
public void dataStateRestoreCompleted() {
super.dataStateRestoreCompleted();
// ProgramLocation location = ProgramLocation.getLocation(
// currentProgram, dataSaveState, null );
}
public FGColorProvider getColorProvider() {
return colorProvider;
}
@ -399,4 +425,7 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
return functionGraphOptions;
}
public List<FGLayoutProvider> getLayoutProviders() {
return Collections.unmodifiableList(layoutProviders);
}
}

View file

@ -37,8 +37,8 @@ public abstract class AbstractFGLayout extends AbstractVisualGraphLayout<FGVerte
protected Function function;
protected FunctionGraphOptions options;
protected AbstractFGLayout(FunctionGraph graph) {
super(graph);
protected AbstractFGLayout(FunctionGraph graph, String layoutName) {
super(graph, layoutName);
this.function = graph.getFunction();
this.options = graph.getOptions();
}

View file

@ -34,8 +34,10 @@ import ghidra.util.task.TaskMonitor;
public class EmptyLayout extends AbstractVisualGraphLayout<FGVertex, FGEdge> implements FGLayout {
private static final String NAME = "Empty Layout";
public EmptyLayout(FunctionGraph graph) {
super(graph);
super(graph, NAME);
}
@Override

View file

@ -19,7 +19,7 @@ import javax.swing.Icon;
import resources.ResourceManager;
public abstract class ExperimentalLayoutProvider implements FGLayoutProvider {
public abstract class ExperimentalLayoutProvider extends FGLayoutProvider {
private static final Icon ICON = ResourceManager.loadImage("images/package_development.png");

View file

@ -0,0 +1,43 @@
/* ###
* 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.functiongraph.graph.layout;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.framework.options.Options;
/**
* An interface for {@link FGLayout} options
*/
public interface FGLayoutOptions {
public static final String OWNER = FunctionGraphPlugin.class.getSimpleName();
/**
* Called during setup for this class to register its options with the given {@link Options}
* object
*
* @param options the tool options
*/
public void registerOptions(Options options);
/**
* Called when the given {@link Options} object has changed. This class will update its
* options with the values from the given options object.
*
* @param options the tool options
*/
public void loadOptions(Options options);
}

View file

@ -18,20 +18,30 @@ package ghidra.app.plugin.core.functiongraph.graph.layout;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.framework.options.Options;
import ghidra.graph.viewer.layout.LayoutProvider;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public interface FGLayoutProvider extends LayoutProvider<FGVertex, FGEdge, FunctionGraph> {
public abstract class FGLayoutProvider implements LayoutProvider<FGVertex, FGEdge, FunctionGraph> {
public abstract FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor)
throws CancelledException;
// Suppressing warning on the return type; we know our class is the right type
@Override
public default FGLayout getLayout(FunctionGraph graph, TaskMonitor monitor)
throws CancelledException {
public FGLayout getLayout(FunctionGraph graph, TaskMonitor monitor) throws CancelledException {
return getFGLayout(graph, monitor);
}
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) throws CancelledException;
/**
* Creates an options object for layouts created by this provider. Returns null if there
* are not options for layouts created by this provider.
*
* @param options the tool options into which layout options should be registered
* @return the new options; null if there are no options
*/
public FGLayoutOptions createLayoutOptions(Options options) {
return null;
}
}

View file

@ -16,15 +16,20 @@
package ghidra.app.plugin.core.functiongraph.mvc;
import java.awt.Color;
import java.util.*;
import java.util.Map.Entry;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.Plugin;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
import ghidra.framework.options.Options;
import ghidra.graph.viewer.options.*;
import ghidra.program.model.symbol.FlowType;
import ghidra.util.HelpLocation;
public class FunctionGraphOptions extends VisualGraphOptions {
protected static final String OWNER = FunctionGraphPlugin.class.getSimpleName();
private static final String EDGE_FALLTHROUGH_HIGHLIGHT_COLOR_KEY =
"Edge Color - Fallthrough Highlight";
private static final String EDGE_UNCONDITIONAL_JUMP_HIGHLIGHT_COLOR_KEY =
@ -48,7 +53,7 @@ public class FunctionGraphOptions extends VisualGraphOptions {
private static final String DEFAULT_GROUP_BACKGROUND_COLOR_KEY = "Default Group Color";
private static final String DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION =
"The default " + "background color applied to newly created group vertices";
"The default background color applied to newly created group vertices";
private static final String UPDATE_GROUP_AND_UNGROUP_COLORS =
"Update Vertex Colors When Grouping";
@ -74,6 +79,8 @@ public class FunctionGraphOptions extends VisualGraphOptions {
protected RelayoutOption relayoutOption = RelayoutOption.NEVER;
private Map<String, FGLayoutOptions> layoutOptionsByName = new HashMap<>();
public Color getDefaultGroupBackgroundColor() {
return defaultGroupBackgroundColor;
}
@ -110,8 +117,9 @@ public class FunctionGraphOptions extends VisualGraphOptions {
return relayoutOption;
}
public void initializeOptions(Plugin plugin, ToolOptions options) {
HelpLocation help = new HelpLocation(plugin.getName(), "Options");
public void registerOptions(Options options) {
HelpLocation help = new HelpLocation(OWNER, "Options");
options.setOptionsHelpLocation(help);
options.registerOption(RELAYOUT_OPTIONS_KEY, RelayoutOption.VERTEX_GROUPING_CHANGES, help,
@ -124,8 +132,7 @@ public class FunctionGraphOptions extends VisualGraphOptions {
USE_MOUSE_RELATIVE_ZOOM_DESCRIPTION);
options.registerOption(USE_CONDENSED_LAYOUT, useCondensedLayout(),
new HelpLocation(plugin.getName(), "Layout_Compressing"),
USE_CONDENSED_LAYOUT_DESCRIPTION);
new HelpLocation(OWNER, "Layout_Compressing"), USE_CONDENSED_LAYOUT_DESCRIPTION);
options.registerOption(VIEW_RESTORE_OPTIONS_KEY, ViewRestoreOption.START_FULLY_ZOOMED_OUT,
help, VIEW_RESTORE_OPTIONS_DESCRIPTION);
@ -161,7 +168,7 @@ public class FunctionGraphOptions extends VisualGraphOptions {
}
public void loadOptions(Plugin plugin, ToolOptions options) {
public void loadOptions(Options options) {
conditionalJumpEdgeColor =
options.getColor(EDGE_COLOR_CONDITIONAL_JUMP_KEY, conditionalJumpEdgeColor);
@ -198,6 +205,14 @@ public class FunctionGraphOptions extends VisualGraphOptions {
updateGroupColorsAutomatically =
options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically);
Set<Entry<String, FGLayoutOptions>> entries = layoutOptionsByName.entrySet();
for (Entry<String, FGLayoutOptions> entry : entries) {
String layoutName = entry.getKey();
FGLayoutOptions layoutOptions = entry.getValue();
Options layoutToolOptions = options.getOptions(layoutName);
layoutOptions.loadOptions(layoutToolOptions);
}
}
public Color getColor(FlowType flowType) {
@ -227,4 +242,12 @@ public class FunctionGraphOptions extends VisualGraphOptions {
return Color.BLACK;
}
public FGLayoutOptions getLayoutOptions(String layoutName) {
return layoutOptionsByName.get(layoutName);
}
public void setLayoutOptions(String layoutName, FGLayoutOptions options) {
layoutOptionsByName.put(layoutName, options);
}
}

View file

@ -53,7 +53,6 @@ import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.functiongraph.graph.*;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.layout.TestFGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.services.*;
@ -75,7 +74,6 @@ import ghidra.program.util.ProgramSelection;
import ghidra.test.*;
import ghidra.util.Msg;
import ghidra.util.task.RunManager;
import util.CollectionUtils;
public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest {
@ -558,9 +556,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
graphProvider = waitForComponentProvider(FGProvider.class);
assertNotNull("Graph not shown", graphProvider);
FGActionManager actionManager = graphProvider.getActionManager();
actionManager.setLayoutFinder(() -> CollectionUtils.asSet(new TestFGLayoutProvider()));
}
protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() {

View file

@ -25,6 +25,7 @@ import javax.swing.Icon;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.layout.*;
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
@ -37,13 +38,14 @@ import resources.Icons;
/**
* A simple layout that is used during testing
*/
public class TestFGLayoutProvider implements FGLayoutProvider {
public class TestFGLayoutProvider extends FGLayoutProvider {
private static final String NAME = "Test Layout";
private static final int VERTEX_TO_EDGE_ARTICULATION_OFFSET = 20;
@Override
public String getLayoutName() {
return "Test Layout";
return NAME;
}
@Override
@ -65,7 +67,7 @@ public class TestFGLayoutProvider implements FGLayoutProvider {
private class TestFGLayout extends AbstractFGLayout {
protected TestFGLayout(FunctionGraph graph) {
super(graph);
super(graph, NAME);
}
@Override
@ -224,7 +226,9 @@ public class TestFGLayoutProvider implements FGLayoutProvider {
treeify(g, left, nodesByVertices);
break;
default:
Msg.debug(this, "\n\n\tMore than 2 edges????: " + parent);
if (!(parent.v instanceof GroupedFunctionGraphVertex)) {
Msg.debug(this, "\n\n\tMore than 2 edges????: " + parent);
}
}
}
@ -357,5 +361,16 @@ public class TestFGLayoutProvider implements FGLayoutProvider {
Node(FGVertex v) {
this.v = v;
}
@Override
public String toString() {
//@formatter:off
return "{\n" +
"\tv: " + v + ",\n" +
"\tleft: " + left + ",\n" +
"\tright: " + right + "\n" +
"}";
//@formatter:on
}
}
}