mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GT-3019 - Function Graph - Added option for edge routing around vertices
This commit is contained in:
parent
ac98e609d7
commit
a04185e942
24 changed files with 933 additions and 259 deletions
|
@ -32,8 +32,8 @@ import ghidra.util.exception.CancelledException;
|
||||||
public class SampleGraphPluginDependencyLayout
|
public class SampleGraphPluginDependencyLayout
|
||||||
extends AbstractVisualGraphLayout<SampleVertex, SampleEdge> {
|
extends AbstractVisualGraphLayout<SampleVertex, SampleEdge> {
|
||||||
|
|
||||||
protected SampleGraphPluginDependencyLayout(SampleGraph graph) {
|
protected SampleGraphPluginDependencyLayout(SampleGraph graph, String name) {
|
||||||
super(graph);
|
super(graph, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -50,7 +50,7 @@ public class SampleGraphPluginDependencyLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleGraphPluginDependencyLayout newLayout =
|
SampleGraphPluginDependencyLayout newLayout =
|
||||||
new SampleGraphPluginDependencyLayout((SampleGraph) newGraph);
|
new SampleGraphPluginDependencyLayout((SampleGraph) newGraph, getLayoutName());
|
||||||
return newLayout;
|
return newLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,20 +30,21 @@ import resources.ResourceManager;
|
||||||
public class SampleGraphPluginDependencyLayoutProvider
|
public class SampleGraphPluginDependencyLayoutProvider
|
||||||
extends AbstractLayoutProvider<SampleVertex, SampleEdge, SampleGraph> {
|
extends AbstractLayoutProvider<SampleVertex, SampleEdge, SampleGraph> {
|
||||||
|
|
||||||
|
private static final String NAME = "Plugin Dependency Layout";
|
||||||
private static final Icon DEFAULT_ICON = ResourceManager.loadImage("images/color_swatch.png");
|
private static final Icon DEFAULT_ICON = ResourceManager.loadImage("images/color_swatch.png");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VisualGraphLayout<SampleVertex, SampleEdge> getLayout(SampleGraph g, TaskMonitor monitor)
|
public VisualGraphLayout<SampleVertex, SampleEdge> getLayout(SampleGraph g, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
|
||||||
SampleGraphPluginDependencyLayout layout = new SampleGraphPluginDependencyLayout(g);
|
SampleGraphPluginDependencyLayout layout = new SampleGraphPluginDependencyLayout(g, NAME);
|
||||||
initVertexLocations(g, layout);
|
initVertexLocations(g, layout);
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLayoutName() {
|
public String getLayoutName() {
|
||||||
return "Plugin Dependency Layout";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: each provider really should load its own icon so that the toolbar item can
|
// Note: each provider really should load its own icon so that the toolbar item can
|
||||||
|
|
|
@ -48,7 +48,6 @@ import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.AssertException;
|
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
|
@ -72,7 +71,6 @@ class FGActionManager {
|
||||||
private MultiStateDockingAction<EdgeDisplayType> vertexHoverModeAction;
|
private MultiStateDockingAction<EdgeDisplayType> vertexHoverModeAction;
|
||||||
private MultiStateDockingAction<EdgeDisplayType> vertexFocusModeAction;
|
private MultiStateDockingAction<EdgeDisplayType> vertexFocusModeAction;
|
||||||
|
|
||||||
private FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder();
|
|
||||||
private MultiStateDockingAction<Class<? extends FGLayoutProvider>> layoutAction;
|
private MultiStateDockingAction<Class<? extends FGLayoutProvider>> layoutAction;
|
||||||
|
|
||||||
FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) {
|
FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) {
|
||||||
|
@ -857,8 +855,7 @@ class FGActionManager {
|
||||||
HelpLocation layoutHelpLocation =
|
HelpLocation layoutHelpLocation =
|
||||||
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
||||||
|
|
||||||
layoutAction = new MultiStateDockingAction<Class<? extends FGLayoutProvider>>(
|
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) {
|
||||||
"Relayout Graph", plugin.getName()) {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doActionPerformed(ActionContext context) {
|
protected void doActionPerformed(ActionContext context) {
|
||||||
|
@ -901,20 +898,12 @@ class FGActionManager {
|
||||||
|
|
||||||
private List<ActionState<Class<? extends FGLayoutProvider>>> loadActionStatesForLayoutProviders() {
|
private List<ActionState<Class<? extends FGLayoutProvider>>> loadActionStatesForLayoutProviders() {
|
||||||
|
|
||||||
Set<FGLayoutProvider> instances = layoutFinder.findLayouts();
|
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders();
|
||||||
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<ActionState<Class<? extends FGLayoutProvider>>> list = new ArrayList<>();
|
List<ActionState<Class<? extends FGLayoutProvider>>> list = new ArrayList<>();
|
||||||
HelpLocation layoutHelpLocation =
|
HelpLocation layoutHelpLocation =
|
||||||
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
||||||
for (FGLayoutProvider layout : layoutInstances) {
|
for (FGLayoutProvider layout : layoutInstances) {
|
||||||
|
|
||||||
ActionState<Class<? extends FGLayoutProvider>> layoutState = new ActionState<>(
|
ActionState<Class<? extends FGLayoutProvider>> layoutState = new ActionState<>(
|
||||||
layout.getLayoutName(), layout.getActionIcon(), layout.getClass());
|
layout.getLayoutName(), layout.getActionIcon(), layout.getClass());
|
||||||
layoutState.setHelpLocation(layoutHelpLocation);
|
layoutState.setHelpLocation(layoutHelpLocation);
|
||||||
|
@ -996,7 +985,7 @@ class FGActionManager {
|
||||||
offState.setHelpLocation(pathHelpLocation);
|
offState.setHelpLocation(pathHelpLocation);
|
||||||
|
|
||||||
vertexHoverModeAction =
|
vertexHoverModeAction =
|
||||||
new MultiStateDockingAction<EdgeDisplayType>("Block Hover Mode", plugin.getName()) {
|
new MultiStateDockingAction<>("Block Hover Mode", plugin.getName()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
|
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
|
||||||
|
@ -1065,7 +1054,7 @@ class FGActionManager {
|
||||||
offState.setHelpLocation(pathHelpLocation);
|
offState.setHelpLocation(pathHelpLocation);
|
||||||
|
|
||||||
vertexFocusModeAction =
|
vertexFocusModeAction =
|
||||||
new MultiStateDockingAction<EdgeDisplayType>("Block Focus Mode", plugin.getName()) {
|
new MultiStateDockingAction<>("Block Focus Mode", plugin.getName()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
|
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
|
||||||
|
@ -1204,10 +1193,6 @@ class FGActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLayoutFinder(FGLayoutFinder layoutFinder) {
|
|
||||||
this.layoutFinder = layoutFinder;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEdgeFocusMode(EdgeDisplayType edgeDisplayType) {
|
void setEdgeFocusMode(EdgeDisplayType edgeDisplayType) {
|
||||||
vertexFocusModeAction.setCurrentActionStateByUserData(edgeDisplayType);
|
vertexFocusModeAction.setCurrentActionStateByUserData(edgeDisplayType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ import ghidra.app.events.*;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.plugin.core.colorizer.ColorizingService;
|
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.plugin.core.functiongraph.mvc.FunctionGraphOptions;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
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.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
import ghidra.util.exception.AssertException;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
@ -78,6 +81,7 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
|
||||||
private FunctionGraphOptions functionGraphOptions = new FunctionGraphOptions();
|
private FunctionGraphOptions functionGraphOptions = new FunctionGraphOptions();
|
||||||
|
|
||||||
private FGColorProvider colorProvider;
|
private FGColorProvider colorProvider;
|
||||||
|
private List<FGLayoutProvider> layoutProviders;
|
||||||
|
|
||||||
public FunctionGraphPlugin(PluginTool tool) {
|
public FunctionGraphPlugin(PluginTool tool) {
|
||||||
super(tool, true, true, true);
|
super(tool, true, true, true);
|
||||||
|
@ -88,6 +92,9 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
|
layoutProviders = loadLayoutProviders();
|
||||||
|
|
||||||
createNewProvider();
|
createNewProvider();
|
||||||
initializeOptions();
|
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() {
|
private void initializeOptions() {
|
||||||
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
|
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
|
||||||
options.addOptionsChangeListener(this);
|
options.addOptionsChangeListener(this);
|
||||||
functionGraphOptions.initializeOptions(this, options);
|
functionGraphOptions.registerOptions(options);
|
||||||
functionGraphOptions.loadOptions(this, 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
|
@Override
|
||||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||||
Object newValue) {
|
Object newValue) {
|
||||||
functionGraphOptions.loadOptions(this, options);
|
functionGraphOptions.loadOptions(options);
|
||||||
connectedProvider.getComponent().repaint();
|
connectedProvider.getComponent().repaint();
|
||||||
for (FGProvider provider : disconnectedProviders) {
|
for (FGProvider provider : disconnectedProviders) {
|
||||||
provider.getComponent().repaint();
|
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() {
|
public FGColorProvider getColorProvider() {
|
||||||
return colorProvider;
|
return colorProvider;
|
||||||
}
|
}
|
||||||
|
@ -399,4 +425,7 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
|
||||||
return functionGraphOptions;
|
return functionGraphOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FGLayoutProvider> getLayoutProviders() {
|
||||||
|
return Collections.unmodifiableList(layoutProviders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ public abstract class AbstractFGLayout extends AbstractVisualGraphLayout<FGVerte
|
||||||
protected Function function;
|
protected Function function;
|
||||||
protected FunctionGraphOptions options;
|
protected FunctionGraphOptions options;
|
||||||
|
|
||||||
protected AbstractFGLayout(FunctionGraph graph) {
|
protected AbstractFGLayout(FunctionGraph graph, String layoutName) {
|
||||||
super(graph);
|
super(graph, layoutName);
|
||||||
this.function = graph.getFunction();
|
this.function = graph.getFunction();
|
||||||
this.options = graph.getOptions();
|
this.options = graph.getOptions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,10 @@ import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class EmptyLayout extends AbstractVisualGraphLayout<FGVertex, FGEdge> implements FGLayout {
|
public class EmptyLayout extends AbstractVisualGraphLayout<FGVertex, FGEdge> implements FGLayout {
|
||||||
|
|
||||||
|
private static final String NAME = "Empty Layout";
|
||||||
|
|
||||||
public EmptyLayout(FunctionGraph graph) {
|
public EmptyLayout(FunctionGraph graph) {
|
||||||
super(graph);
|
super(graph, NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,7 +19,7 @@ import javax.swing.Icon;
|
||||||
|
|
||||||
import resources.ResourceManager;
|
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");
|
private static final Icon ICON = ResourceManager.loadImage("images/package_development.png");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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.FGEdge;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
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.FGVertex;
|
||||||
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.graph.viewer.layout.LayoutProvider;
|
import ghidra.graph.viewer.layout.LayoutProvider;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
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
|
// Suppressing warning on the return type; we know our class is the right type
|
||||||
@Override
|
@Override
|
||||||
public default FGLayout getLayout(FunctionGraph graph, TaskMonitor monitor)
|
public FGLayout getLayout(FunctionGraph graph, TaskMonitor monitor) throws CancelledException {
|
||||||
throws CancelledException {
|
|
||||||
|
|
||||||
return getFGLayout(graph, monitor);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,20 @@
|
||||||
package ghidra.app.plugin.core.functiongraph.mvc;
|
package ghidra.app.plugin.core.functiongraph.mvc;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
|
||||||
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.graph.viewer.options.*;
|
import ghidra.graph.viewer.options.*;
|
||||||
import ghidra.program.model.symbol.FlowType;
|
import ghidra.program.model.symbol.FlowType;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
public class FunctionGraphOptions extends VisualGraphOptions {
|
public class FunctionGraphOptions extends VisualGraphOptions {
|
||||||
|
|
||||||
|
protected static final String OWNER = FunctionGraphPlugin.class.getSimpleName();
|
||||||
|
|
||||||
private static final String EDGE_FALLTHROUGH_HIGHLIGHT_COLOR_KEY =
|
private static final String EDGE_FALLTHROUGH_HIGHLIGHT_COLOR_KEY =
|
||||||
"Edge Color - Fallthrough Highlight";
|
"Edge Color - Fallthrough Highlight";
|
||||||
private static final String EDGE_UNCONDITIONAL_JUMP_HIGHLIGHT_COLOR_KEY =
|
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_KEY = "Default Group Color";
|
||||||
private static final String DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION =
|
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 =
|
private static final String UPDATE_GROUP_AND_UNGROUP_COLORS =
|
||||||
"Update Vertex Colors When Grouping";
|
"Update Vertex Colors When Grouping";
|
||||||
|
@ -74,6 +79,8 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
||||||
|
|
||||||
protected RelayoutOption relayoutOption = RelayoutOption.NEVER;
|
protected RelayoutOption relayoutOption = RelayoutOption.NEVER;
|
||||||
|
|
||||||
|
private Map<String, FGLayoutOptions> layoutOptionsByName = new HashMap<>();
|
||||||
|
|
||||||
public Color getDefaultGroupBackgroundColor() {
|
public Color getDefaultGroupBackgroundColor() {
|
||||||
return defaultGroupBackgroundColor;
|
return defaultGroupBackgroundColor;
|
||||||
}
|
}
|
||||||
|
@ -110,8 +117,9 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
||||||
return relayoutOption;
|
return relayoutOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeOptions(Plugin plugin, ToolOptions options) {
|
public void registerOptions(Options options) {
|
||||||
HelpLocation help = new HelpLocation(plugin.getName(), "Options");
|
|
||||||
|
HelpLocation help = new HelpLocation(OWNER, "Options");
|
||||||
options.setOptionsHelpLocation(help);
|
options.setOptionsHelpLocation(help);
|
||||||
|
|
||||||
options.registerOption(RELAYOUT_OPTIONS_KEY, RelayoutOption.VERTEX_GROUPING_CHANGES, 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);
|
USE_MOUSE_RELATIVE_ZOOM_DESCRIPTION);
|
||||||
|
|
||||||
options.registerOption(USE_CONDENSED_LAYOUT, useCondensedLayout(),
|
options.registerOption(USE_CONDENSED_LAYOUT, useCondensedLayout(),
|
||||||
new HelpLocation(plugin.getName(), "Layout_Compressing"),
|
new HelpLocation(OWNER, "Layout_Compressing"), USE_CONDENSED_LAYOUT_DESCRIPTION);
|
||||||
USE_CONDENSED_LAYOUT_DESCRIPTION);
|
|
||||||
|
|
||||||
options.registerOption(VIEW_RESTORE_OPTIONS_KEY, ViewRestoreOption.START_FULLY_ZOOMED_OUT,
|
options.registerOption(VIEW_RESTORE_OPTIONS_KEY, ViewRestoreOption.START_FULLY_ZOOMED_OUT,
|
||||||
help, VIEW_RESTORE_OPTIONS_DESCRIPTION);
|
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 =
|
conditionalJumpEdgeColor =
|
||||||
options.getColor(EDGE_COLOR_CONDITIONAL_JUMP_KEY, conditionalJumpEdgeColor);
|
options.getColor(EDGE_COLOR_CONDITIONAL_JUMP_KEY, conditionalJumpEdgeColor);
|
||||||
|
|
||||||
|
@ -198,6 +205,14 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
||||||
|
|
||||||
updateGroupColorsAutomatically =
|
updateGroupColorsAutomatically =
|
||||||
options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS, 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) {
|
public Color getColor(FlowType flowType) {
|
||||||
|
@ -227,4 +242,12 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
||||||
|
|
||||||
return Color.BLACK;
|
return Color.BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FGLayoutOptions getLayoutOptions(String layoutName) {
|
||||||
|
return layoutOptionsByName.get(layoutName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLayoutOptions(String layoutName, FGLayoutOptions options) {
|
||||||
|
layoutOptionsByName.put(layoutName, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,6 @@ import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.*;
|
import ghidra.app.plugin.core.functiongraph.graph.*;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
|
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.graph.vertex.*;
|
||||||
import ghidra.app.plugin.core.functiongraph.mvc.*;
|
import ghidra.app.plugin.core.functiongraph.mvc.*;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
@ -75,7 +74,6 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.test.*;
|
import ghidra.test.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.task.RunManager;
|
import ghidra.util.task.RunManager;
|
||||||
import util.CollectionUtils;
|
|
||||||
|
|
||||||
public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest {
|
public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
|
@ -558,9 +556,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
|
||||||
|
|
||||||
graphProvider = waitForComponentProvider(FGProvider.class);
|
graphProvider = waitForComponentProvider(FGProvider.class);
|
||||||
assertNotNull("Graph not shown", graphProvider);
|
assertNotNull("Graph not shown", graphProvider);
|
||||||
|
|
||||||
FGActionManager actionManager = graphProvider.getActionManager();
|
|
||||||
actionManager.setLayoutFinder(() -> CollectionUtils.asSet(new TestFGLayoutProvider()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() {
|
protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import javax.swing.Icon;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
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.FGVertex;
|
||||||
|
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
|
||||||
import ghidra.graph.VisualGraph;
|
import ghidra.graph.VisualGraph;
|
||||||
import ghidra.graph.viewer.layout.*;
|
import ghidra.graph.viewer.layout.*;
|
||||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||||
|
@ -37,13 +38,14 @@ import resources.Icons;
|
||||||
/**
|
/**
|
||||||
* A simple layout that is used during testing
|
* 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;
|
private static final int VERTEX_TO_EDGE_ARTICULATION_OFFSET = 20;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLayoutName() {
|
public String getLayoutName() {
|
||||||
return "Test Layout";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -65,7 +67,7 @@ public class TestFGLayoutProvider implements FGLayoutProvider {
|
||||||
private class TestFGLayout extends AbstractFGLayout {
|
private class TestFGLayout extends AbstractFGLayout {
|
||||||
|
|
||||||
protected TestFGLayout(FunctionGraph graph) {
|
protected TestFGLayout(FunctionGraph graph) {
|
||||||
super(graph);
|
super(graph, NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -224,7 +226,9 @@ public class TestFGLayoutProvider implements FGLayoutProvider {
|
||||||
treeify(g, left, nodesByVertices);
|
treeify(g, left, nodesByVertices);
|
||||||
break;
|
break;
|
||||||
default:
|
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) {
|
Node(FGVertex v) {
|
||||||
this.v = v;
|
this.v = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
//@formatter:off
|
||||||
|
return "{\n" +
|
||||||
|
"\tv: " + v + ",\n" +
|
||||||
|
"\tleft: " + left + ",\n" +
|
||||||
|
"\tright: " + right + "\n" +
|
||||||
|
"}";
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* ###
|
||||||
|
* 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.framework.options.Options;
|
||||||
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for the {@link DecompilerNestedLayout}
|
||||||
|
*/
|
||||||
|
public class DNLayoutOptions implements FGLayoutOptions {
|
||||||
|
|
||||||
|
private static final String USE_EDGE_ROUTING_AROUND_VERTICES_KEY =
|
||||||
|
"Route Edges Around Vertices";
|
||||||
|
private static final String USE_EDGE_ROUTING_AROUND_VERTICES_DESCRIPTION = "Signals that " +
|
||||||
|
"edges should be routed around any intersecting vertex. When toggled off, edges will " +
|
||||||
|
"pass through any intersecting vertices.";
|
||||||
|
|
||||||
|
private boolean useEdgeRoutingAroundVertices;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOptions(Options options) {
|
||||||
|
|
||||||
|
// TODO layout-specific help
|
||||||
|
HelpLocation help = new HelpLocation(OWNER, "Options");
|
||||||
|
|
||||||
|
options.registerOption(USE_EDGE_ROUTING_AROUND_VERTICES_KEY, useEdgeRoutingAroundVertices,
|
||||||
|
help, USE_EDGE_ROUTING_AROUND_VERTICES_DESCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadOptions(Options options) {
|
||||||
|
useEdgeRoutingAroundVertices =
|
||||||
|
options.getBoolean(USE_EDGE_ROUTING_AROUND_VERTICES_KEY, useEdgeRoutingAroundVertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useEdgeRoutingAroundVertices() {
|
||||||
|
return useEdgeRoutingAroundVertices;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.apache.commons.collections4.BidiMap;
|
import org.apache.commons.collections4.BidiMap;
|
||||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||||
|
import org.apache.commons.collections4.map.LazyMap;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
|
||||||
|
@ -49,24 +50,46 @@ import ghidra.util.exception.AssertException;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
// TODO paint fallthrough differently for all, or just for those returning to the baseline
|
/**
|
||||||
// TODO: edges for loops could stand out more...maybe not needed with better routing or background painting
|
* A layout that uses the decompiler to show code nesting based upon conditional logic.
|
||||||
|
*
|
||||||
// TODO: should we allow grouping in this layout?
|
* <p>Edges returning to the default code flow are painted lighter to de-emphasize them. This
|
||||||
|
* could be made into an option.
|
||||||
// TODO: entry not always at the top - winhello.exe 402c8c
|
*
|
||||||
|
* TODO ideas:
|
||||||
|
* -paint fallthrough differently for all, or just for those returning to the baseline
|
||||||
|
* -using 'edge routing mode' that avoids vertices can be tweaked so that all returning edges also
|
||||||
|
* will not overlap (do this by keeping some sort of mapping for a given edge's column); when
|
||||||
|
* not routing edges around vertices, the edges intentionally overlap, to reduce noise
|
||||||
|
*/
|
||||||
public class DecompilerNestedLayout extends AbstractFGLayout {
|
public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
|
|
||||||
private static final int VERTEX_TO_EDGE_ARTICULATION_OFFSET = 20;
|
/** Amount of visual buffer between edges and other things used to show separation */
|
||||||
|
private static final int EDGE_SPACING = 5;
|
||||||
|
|
||||||
|
/** The space between an articulation point and its vertex */
|
||||||
|
private static final int VERTEX_TO_EDGE_ARTICULATION_PADDING = 20;
|
||||||
|
|
||||||
|
private static final int VERTEX_TO_EDGE_AVOIDANCE_PADDING =
|
||||||
|
VERTEX_TO_EDGE_ARTICULATION_PADDING - EDGE_SPACING;
|
||||||
|
|
||||||
|
/** Multiplier used to grow spacing as distance between two edge endpoints grows */
|
||||||
|
private static final int EDGE_ENDPOINT_DISTANCE_MULTIPLIER = 20;
|
||||||
|
|
||||||
|
/** Amount to keep an edge away from the bounding box of a vertex */
|
||||||
|
private static final int VERTEX_BORDER_THICKNESS = EDGE_SPACING;
|
||||||
|
|
||||||
|
/** An amount by which edges entering a vertex from the left are offset to avoid overlapping */
|
||||||
|
private static final int EDGE_OFFSET_INCOMING_FROM_LEFT = EDGE_SPACING;
|
||||||
|
|
||||||
private DecompilerBlockGraph blockGraphRoot;
|
private DecompilerBlockGraph blockGraphRoot;
|
||||||
|
|
||||||
public DecompilerNestedLayout(FunctionGraph graph) {
|
public DecompilerNestedLayout(FunctionGraph graph, String name) {
|
||||||
this(graph, true);
|
this(graph, name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecompilerNestedLayout(FunctionGraph graph, boolean initialize) {
|
private DecompilerNestedLayout(FunctionGraph graph, String name, boolean initialize) {
|
||||||
super(graph);
|
super(graph, name);
|
||||||
if (initialize) {
|
if (initialize) {
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
@ -89,6 +112,10 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
return .3;
|
return .3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DNLayoutOptions getLayoutOptions() {
|
||||||
|
return (DNLayoutOptions) options.getLayoutOptions(getLayoutName());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GridLocationMap<FGVertex, FGEdge> performInitialGridLayout(
|
protected GridLocationMap<FGVertex, FGEdge> performInitialGridLayout(
|
||||||
VisualGraph<FGVertex, FGEdge> jungGraph) throws CancelledException {
|
VisualGraph<FGVertex, FGEdge> jungGraph) throws CancelledException {
|
||||||
|
@ -190,15 +217,25 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO The 'vertexLayoutLocations' is too close to 'layoutLocations'...rename/refactor
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<FGEdge, List<Point2D>> positionEdgeArticulationsInLayoutSpace(
|
protected Map<FGEdge, List<Point2D>> positionEdgeArticulationsInLayoutSpace(
|
||||||
VisualGraphVertexShapeTransformer<FGVertex> transformer,
|
VisualGraphVertexShapeTransformer<FGVertex> transformer,
|
||||||
Map<FGVertex, Point2D> vertexLayoutLocations, Collection<FGEdge> edges,
|
Map<FGVertex, Point2D> vertexLayoutLocations, Collection<FGEdge> edges,
|
||||||
LayoutLocationMap<FGVertex, FGEdge> layoutLocations) throws CancelledException {
|
LayoutLocationMap<FGVertex, FGEdge> layoutToGridMap) throws CancelledException {
|
||||||
|
|
||||||
Map<FGEdge, List<Point2D>> newEdgeArticulations = new HashMap<>();
|
Map<FGEdge, List<Point2D>> newEdgeArticulations = new HashMap<>();
|
||||||
|
|
||||||
|
// Condensing Note: we have guilty knowledge that our parent class my condense the
|
||||||
|
// vertices and edges towards the center of the graph after we calculate positions.
|
||||||
|
// To prevent the edges from moving to far behind the vertices, we will compensate a
|
||||||
|
// bit for that effect using this offset value. The getEdgeOffset() method below is
|
||||||
|
// updated for the condense factor.
|
||||||
|
int edgeOffset = isCondensedLayout()
|
||||||
|
? (int) (VERTEX_TO_EDGE_ARTICULATION_PADDING * (1 - getCondenseFactor()))
|
||||||
|
: VERTEX_TO_EDGE_ARTICULATION_PADDING;
|
||||||
|
Vertex2dFactory vertex2dFactory =
|
||||||
|
new Vertex2dFactory(transformer, vertexLayoutLocations, layoutToGridMap, edgeOffset);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Route our edges!
|
// Route our edges!
|
||||||
//
|
//
|
||||||
|
@ -208,134 +245,453 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
FGVertex startVertex = e.getStart();
|
FGVertex startVertex = e.getStart();
|
||||||
FGVertex endVertex = e.getEnd();
|
FGVertex endVertex = e.getEnd();
|
||||||
|
|
||||||
|
Vertex2d start = vertex2dFactory.get(startVertex);
|
||||||
|
Vertex2d end = vertex2dFactory.get(endVertex);
|
||||||
|
|
||||||
Address startAddress = startVertex.getVertexAddress();
|
Address startAddress = startVertex.getVertexAddress();
|
||||||
Address endAddress = endVertex.getVertexAddress();
|
Address endAddress = endVertex.getVertexAddress();
|
||||||
int result = startAddress.compareTo(endAddress);
|
int compareResult = startAddress.compareTo(endAddress);
|
||||||
DecompilerBlock block = blockGraphRoot.getBlock(endVertex);
|
boolean goingUp = compareResult > 0;
|
||||||
DecompilerBlock loop = block.getParentLoop();
|
|
||||||
|
|
||||||
if (result > 0 && loop != null) {
|
if (goingUp) {
|
||||||
// TODO better check for loops
|
|
||||||
routeLoopEdge(vertexLayoutLocations, layoutLocations, newEdgeArticulations, e,
|
DecompilerBlock block = blockGraphRoot.getBlock(endVertex);
|
||||||
startVertex, endVertex);
|
DecompilerBlock loop = block.getParentLoop();
|
||||||
|
|
||||||
|
if (loop != null) {
|
||||||
|
Set<FGVertex> vertices = loop.getVertices();
|
||||||
|
|
||||||
|
Column outermostCol = getOutermostCol(layoutToGridMap, vertices);
|
||||||
|
Column loopEndColumn = layoutToGridMap.nextColumn(outermostCol);
|
||||||
|
List<Point2D> articulations = routeLoopEdge(start, end, loopEndColumn);
|
||||||
|
newEdgeArticulations.put(e, articulations);
|
||||||
|
|
||||||
|
vertex2dFactory.dispose();
|
||||||
|
return newEdgeArticulations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
//
|
|
||||||
// TODO For now I will use the layout positions to determine edge type (nested v.
|
|
||||||
// fallthrough). It would be nicer if I had this information defined somewhere
|
|
||||||
// -->Maybe positioning is simple enough?
|
|
||||||
//
|
|
||||||
|
|
||||||
Column startCol = layoutLocations.col(startVertex);
|
List<Point2D> articulations = new ArrayList<>();
|
||||||
Column endCol = layoutLocations.col(endVertex);
|
|
||||||
Point2D start = vertexLayoutLocations.get(startVertex);
|
|
||||||
Point2D end = vertexLayoutLocations.get(endVertex);
|
|
||||||
List<Point2D> articulations = new ArrayList<>();
|
|
||||||
|
|
||||||
int direction = 20;
|
//
|
||||||
if (startCol.index < endCol.index) { // going forward on the x-axis
|
// Basic routing:
|
||||||
// TODO make constant
|
// -leave the bottom of the start vertex
|
||||||
// direction = 10;
|
// -first bend at some constant offset
|
||||||
}
|
// -move to right or left, to above the end vertex
|
||||||
else if (startCol.index > endCol.index) { // going backwards on the x-axis
|
// -second bend above the end vertex at previous constant offset
|
||||||
direction = -direction;
|
//
|
||||||
}
|
// Edges start/end on the vertex center. If we offset them to avoid
|
||||||
|
// overlapping, then they produce angles when only using two articulations.
|
||||||
|
// Thus, we create articulations that are behind the vertices to remove
|
||||||
|
// the angles. This points will not be seen.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Complex routing:
|
||||||
|
// -this mode will route edges around vertices
|
||||||
|
//
|
||||||
|
// One goal for complex edge routing is to prevent overlapping (simple edge routing
|
||||||
|
// prefers overlapping to reduce lines). To prevent overlapping we will use different
|
||||||
|
// offset x and y values, depending upon the start and end vertex row and column
|
||||||
|
// locations. Specifically, for a given edge direction there will be a bias:
|
||||||
|
// -Edge to the right - leave from the right; arrive to the left
|
||||||
|
// -Edge to the left - leave from the left; arrive to the right
|
||||||
|
// -Edge straight down - go straight down
|
||||||
|
//
|
||||||
|
// For each of the above offsets, there will be an amplifier based upon row/column
|
||||||
|
// distance from start to end vertex. This has the effect that larger vertex
|
||||||
|
// distances will have a larger offset/spacing.
|
||||||
|
//
|
||||||
|
|
||||||
int offsetFromVertex = isCondensedLayout()
|
if (start.columnIndex < end.columnIndex) { // going to the right
|
||||||
? (int) (VERTEX_TO_EDGE_ARTICULATION_OFFSET * (1 - getCondenseFactor()))
|
|
||||||
: VERTEX_TO_EDGE_ARTICULATION_OFFSET;
|
|
||||||
|
|
||||||
if (startCol.index < endCol.index) { // going left or right
|
routeToTheRight(start, end, vertex2dFactory, articulations);
|
||||||
//
|
|
||||||
// Basic routing:
|
|
||||||
// -leave the bottom of the start vertex
|
|
||||||
// -first bend at some constant offset
|
|
||||||
// -move to right or left, to above the end vertex
|
|
||||||
// -second bend above the end vertex at previous constant offset
|
|
||||||
//
|
|
||||||
// Advanced considerations:
|
|
||||||
// -Remove angles from vertex points:
|
|
||||||
// -->Edges start/end on the vertex center. If we offset them to avoid
|
|
||||||
// overlapping, then they produce angles when only using two articulations.
|
|
||||||
// Thus, we will create articulations that are behind the vertices to remove
|
|
||||||
// the angles. This points will not be seen.
|
|
||||||
//
|
|
||||||
Shape shape = transformer.apply(startVertex);
|
|
||||||
Rectangle bounds = shape.getBounds();
|
|
||||||
double vertexBottom = start.getY() + (bounds.height >> 1); // location is centered
|
|
||||||
|
|
||||||
double x1 = start.getX() + direction;
|
|
||||||
double y1 = start.getY(); // hidden
|
|
||||||
articulations.add(new Point2D.Double(x1, y1));
|
|
||||||
|
|
||||||
double x2 = x1;
|
|
||||||
double y2 = vertexBottom + offsetFromVertex;
|
|
||||||
y2 = end.getY();
|
|
||||||
articulations.add(new Point2D.Double(x2, y2));
|
|
||||||
|
|
||||||
double x3 = end.getX() + (-direction);
|
|
||||||
double y3 = y2;
|
|
||||||
articulations.add(new Point2D.Double(x3, y3));
|
|
||||||
|
|
||||||
// double x4 = x3;
|
|
||||||
// double y4 = end.getY(); // hidden
|
|
||||||
// articulations.add(new Point2D.Double(x4, y4));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (startCol.index > endCol.index) { // flow return
|
|
||||||
e.setAlpha(.25);
|
|
||||||
|
|
||||||
Shape shape = transformer.apply(startVertex);
|
|
||||||
Rectangle bounds = shape.getBounds();
|
|
||||||
double vertexBottom = start.getY() + (bounds.height >> 1); // location is centered
|
|
||||||
|
|
||||||
double x1 = start.getX() + (direction);
|
|
||||||
double y1 = start.getY(); // hidden
|
|
||||||
articulations.add(new Point2D.Double(x1, y1));
|
|
||||||
|
|
||||||
double x2 = x1;
|
|
||||||
double y2 = vertexBottom + offsetFromVertex;
|
|
||||||
articulations.add(new Point2D.Double(x2, y2));
|
|
||||||
|
|
||||||
double x3 = end.getX() + (-direction);
|
|
||||||
double y3 = y2;
|
|
||||||
articulations.add(new Point2D.Double(x3, y3));
|
|
||||||
|
|
||||||
double x4 = x3;
|
|
||||||
double y4 = end.getY(); // hidden
|
|
||||||
articulations.add(new Point2D.Double(x4, y4));
|
|
||||||
}
|
|
||||||
|
|
||||||
else { // same column--nothing to route
|
|
||||||
// straight line, which is the default
|
|
||||||
e.setAlpha(.25);
|
|
||||||
}
|
|
||||||
newEdgeArticulations.put(e, articulations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (start.columnIndex > end.columnIndex) { // going to the left; flow return
|
||||||
|
|
||||||
|
// check for the up or down direction
|
||||||
|
if (start.rowIndex < end.rowIndex) { // down
|
||||||
|
routeToTheLeft(start, end, e, vertex2dFactory, articulations);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
routeToTheRightGoingUpwards(start, end, vertex2dFactory, articulations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else { // going down; no nesting; flow return
|
||||||
|
|
||||||
|
routeDownward(start, end, e, vertex2dFactory, articulations);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEdgeArticulations.put(e, articulations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vertex2dFactory.dispose();
|
||||||
return newEdgeArticulations;
|
return newEdgeArticulations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void routeLoopEdge(Map<FGVertex, Point2D> vertexLayoutLocations,
|
private void routeToTheRightGoingUpwards(Vertex2d start, Vertex2d end,
|
||||||
LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations) {
|
||||||
Map<FGEdge, List<Point2D>> newEdgeArticulations, FGEdge e, FGVertex startVertex,
|
|
||||||
FGVertex endVertex) {
|
//
|
||||||
|
// For routing to the right and back up we will leave the start vertex from the right side
|
||||||
|
// and enter the end vertex on the right side. As the vertices get further apart, we will
|
||||||
|
// space them further in towards the center.
|
||||||
|
//
|
||||||
|
|
||||||
|
int delta = start.rowIndex - end.rowIndex;
|
||||||
|
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
|
||||||
|
// Condensing Note: we have guilty knowledge that our parent class my condense the
|
||||||
|
// vertices and edges towards the center of the graph after we calculate positions.
|
||||||
|
// To prevent the edges from moving to far behind the vertices, we will compensate a
|
||||||
|
// bit for that effect using this offset value. The getEdgeOffset() method is
|
||||||
|
// updated for the condense factor.
|
||||||
|
int exaggerationFactor = 1;
|
||||||
|
if (isCondensedLayout()) {
|
||||||
|
exaggerationFactor = 2; // determined by trial-and-error; can be made into an option
|
||||||
|
}
|
||||||
|
distanceSpacing *= exaggerationFactor;
|
||||||
|
|
||||||
|
double x1 = start.getX();
|
||||||
|
double y1 = start.getTop() + VERTEX_BORDER_THICKNESS;
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
y1 += distanceSpacing;
|
||||||
|
|
||||||
|
// restrict y from moving past the center
|
||||||
|
double startCenterY = start.getY() - VERTEX_BORDER_THICKNESS;
|
||||||
|
y1 = Math.min(y1, startCenterY);
|
||||||
|
articulations.add(new Point2D.Double(x1, y1)); // point is hidden behind the vertex
|
||||||
|
|
||||||
|
// Use the spacing to move the y value towards the top of the vertex. Just like with
|
||||||
|
// the x value, restrict the y to the range between the edge and the center.
|
||||||
|
double startRightX = start.getRight();
|
||||||
|
double x2 = startRightX + VERTEX_BORDER_THICKNESS; // start at the end
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
x2 += distanceSpacing;
|
||||||
|
|
||||||
|
double y2 = y1;
|
||||||
|
articulations.add(new Point2D.Double(x2, y2));
|
||||||
|
|
||||||
|
double x3 = x2;
|
||||||
|
double y3 = end.getBottom() - VERTEX_BORDER_THICKNESS;
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
y3 -= distanceSpacing;
|
||||||
|
|
||||||
|
// restrict from moving back past the center
|
||||||
|
double endYLimit = end.getY() + VERTEX_BORDER_THICKNESS;
|
||||||
|
y3 = Math.max(y3, endYLimit);
|
||||||
|
articulations.add(new Point2D.Double(x3, y3));
|
||||||
|
|
||||||
|
Column column = vertex2dFactory.getColumn(x1);
|
||||||
|
routeAroundColumnVertices(start, end, column.index, vertex2dFactory, articulations, x3);
|
||||||
|
|
||||||
|
double x4 = end.getX();
|
||||||
|
double y4 = y3;
|
||||||
|
articulations.add(new Point2D.Double(x4, y4)); // point is hidden behind the vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeDownward(Vertex2d start, Vertex2d end, FGEdge e,
|
||||||
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations) {
|
||||||
|
|
||||||
|
lighten(e);
|
||||||
|
|
||||||
|
int delta = end.rowIndex - start.rowIndex;
|
||||||
|
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
|
||||||
|
double x1 = start.getX() - distanceSpacing; // update for extra spacing
|
||||||
|
double y1 = start.getY(); // hidden
|
||||||
|
articulations.add(new Point2D.Double(x1, y1));
|
||||||
|
|
||||||
|
double x2 = x1; // same distance over
|
||||||
|
double y2 = end.getY();
|
||||||
|
articulations.add(new Point2D.Double(x2, y2));
|
||||||
|
|
||||||
|
double x3 = end.getX() + (-distanceSpacing);
|
||||||
|
double y3 = y2;
|
||||||
|
|
||||||
|
routeAroundColumnVertices(start, end, vertex2dFactory, articulations, x3);
|
||||||
|
|
||||||
|
articulations.add(new Point2D.Double(x3, y3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeToTheLeft(Vertex2d start, Vertex2d end, FGEdge e,
|
||||||
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations) {
|
||||||
|
|
||||||
|
lighten(e);
|
||||||
|
|
||||||
|
//
|
||||||
|
// For routing to the left we will leave the start vertex from just left of center and
|
||||||
|
// enter the end vertex on the top, towards the right. As the vertices get further apart,
|
||||||
|
// we will space them further in towards the center of the end vertex. This will keep
|
||||||
|
// edges with close endpoints from intersecting edges with distant endpoints.
|
||||||
|
//
|
||||||
|
|
||||||
|
int delta = end.rowIndex - start.rowIndex;
|
||||||
|
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
|
||||||
|
double x1 = start.getX() - VERTEX_BORDER_THICKNESS; // start at the center
|
||||||
|
|
||||||
|
// spacing moves closer to left edge as the distance grows
|
||||||
|
x1 -= distanceSpacing;
|
||||||
|
|
||||||
|
// restrict from moving backwards past the edge
|
||||||
|
double startXLimit = start.getLeft() + VERTEX_BORDER_THICKNESS;
|
||||||
|
x1 = Math.max(x1, startXLimit);
|
||||||
|
|
||||||
|
// restrict x from moving past the end vertex x value to force the edge to enter
|
||||||
|
// from the side
|
||||||
|
double endRightX = end.getRight() - VERTEX_BORDER_THICKNESS;
|
||||||
|
x1 = Math.max(x1, endRightX);
|
||||||
|
|
||||||
|
double y1 = start.getY();
|
||||||
|
articulations.add(new Point2D.Double(x1, y1)); // point is hidden behind the vertex
|
||||||
|
|
||||||
|
double x2 = x1;
|
||||||
|
double y2 = start.getBottom() + start.getEdgeOffset();
|
||||||
|
articulations.add(new Point2D.Double(x2, y2)); // out of the bottom of the vertex
|
||||||
|
|
||||||
|
// Use the spacing to move the end x value towards the center of the vertex
|
||||||
|
double x3 = endRightX - VERTEX_BORDER_THICKNESS; // start at the end
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
x3 -= distanceSpacing;
|
||||||
|
|
||||||
|
// restrict x from moving past the end vertex center x
|
||||||
|
int edgeOffset = 0;
|
||||||
|
if (getLayoutOptions().useEdgeRoutingAroundVertices()) {
|
||||||
|
// for now, only offset edge lines when we are performing complex routing
|
||||||
|
edgeOffset = EDGE_OFFSET_INCOMING_FROM_LEFT;
|
||||||
|
}
|
||||||
|
double endXLimit = end.getX() + VERTEX_BORDER_THICKNESS + edgeOffset;
|
||||||
|
x3 = Math.max(x3, endXLimit);
|
||||||
|
double y3 = y2;
|
||||||
|
articulations.add(new Point2D.Double(x3, y3)); // into the top of the end vertex
|
||||||
|
|
||||||
|
routeAroundColumnVertices(start, end, vertex2dFactory, articulations, x3);
|
||||||
|
|
||||||
|
double x4 = x3;
|
||||||
|
double y4 = end.getY();
|
||||||
|
articulations.add(new Point2D.Double(x4, y4)); // point is hidden behind the vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeToTheRight(Vertex2d start, Vertex2d end, Vertex2dFactory vertex2dFactory,
|
||||||
|
List<Point2D> articulations) {
|
||||||
|
|
||||||
|
//
|
||||||
|
// For routing to the right we will leave the start vertex from the right side and
|
||||||
|
// enter the end vertex on the left side. As the vertices get further apart, we will
|
||||||
|
// space them further in towards the center. This will keep edges with close endpoints
|
||||||
|
// from intersecting edges with distant endpoints.
|
||||||
|
//
|
||||||
|
|
||||||
|
int delta = end.rowIndex - start.rowIndex;
|
||||||
|
int distanceSpacing = delta * EDGE_ENDPOINT_DISTANCE_MULTIPLIER;
|
||||||
|
|
||||||
|
double startRightX = start.getRight();
|
||||||
|
double x1 = startRightX - VERTEX_BORDER_THICKNESS; // start at the end
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
x1 -= distanceSpacing;
|
||||||
|
|
||||||
|
// restrict x from moving past the end vertex x value to force the edge to enter
|
||||||
|
// from the side
|
||||||
|
double endLeftX = end.getLeft() - end.getEdgeOffset();
|
||||||
|
x1 = Math.min(x1, endLeftX);
|
||||||
|
|
||||||
|
// restrict from moving backwards past the center
|
||||||
|
double startXLimit = start.getX() + VERTEX_BORDER_THICKNESS;
|
||||||
|
x1 = Math.max(x1, startXLimit);
|
||||||
|
|
||||||
|
double y1 = start.getY();
|
||||||
|
articulations.add(new Point2D.Double(x1, y1)); // point is hidden behind the vertex
|
||||||
|
|
||||||
|
// Use the spacing to move the y value towards the top of the vertex. Just like with
|
||||||
|
// the x value, restrict the y to the range between the edge and the center.
|
||||||
|
double x2 = x1;
|
||||||
|
double y2 = end.getTop() + VERTEX_BORDER_THICKNESS;
|
||||||
|
|
||||||
|
// spacing moves closer to center as the distance grows
|
||||||
|
y2 += distanceSpacing;
|
||||||
|
|
||||||
|
// restrict from moving forwards past the center
|
||||||
|
double endYLimit = end.getY() - VERTEX_BORDER_THICKNESS;
|
||||||
|
y2 = Math.min(y2, endYLimit);
|
||||||
|
articulations.add(new Point2D.Double(x2, y2));
|
||||||
|
|
||||||
|
// have not yet seen an example of vertex/edge clipping when routing to the right
|
||||||
|
// routeAroundColumnVertices(start, end, vertex2dFactory, articulations, x2);
|
||||||
|
|
||||||
|
double x3 = end.getX();
|
||||||
|
double y3 = y2;
|
||||||
|
articulations.add(new Point2D.Double(x3, y3)); // point is hidden behind the vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeAroundColumnVertices(Vertex2d start, Vertex2d end,
|
||||||
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations, double edgeX) {
|
||||||
|
|
||||||
|
int column = end.columnIndex;
|
||||||
|
routeAroundColumnVertices(start, end, column, vertex2dFactory, articulations, edgeX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeAroundColumnVertices(Vertex2d start, Vertex2d end, int column,
|
||||||
|
Vertex2dFactory vertex2dFactory, List<Point2D> articulations, double edgeX) {
|
||||||
|
|
||||||
|
if (!getLayoutOptions().useEdgeRoutingAroundVertices()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startRow = start.rowIndex;
|
||||||
|
int endRow = end.rowIndex;
|
||||||
|
|
||||||
|
if (startRow > endRow) { // going upwards
|
||||||
|
endRow = start.rowIndex;
|
||||||
|
startRow = end.rowIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = startRow + 1; row < endRow; row++) {
|
||||||
|
|
||||||
|
// assume any other vertex in our column can clip (it will not clip when
|
||||||
|
// the 'spacing' above pushes the edge away from this column, like for
|
||||||
|
// large row delta values)
|
||||||
|
Vertex2d otherVertex = vertex2dFactory.get(row, column);
|
||||||
|
if (otherVertex == null) {
|
||||||
|
continue; // no vertex in this cell
|
||||||
|
}
|
||||||
|
|
||||||
|
int delta = endRow - startRow;
|
||||||
|
int padding = VERTEX_TO_EDGE_AVOIDANCE_PADDING;
|
||||||
|
int distanceSpacing = padding + delta; // adding the delta makes overlap less likely
|
||||||
|
|
||||||
|
// Condensing Note: we have guilty knowledge that our parent class my condense the
|
||||||
|
// vertices and edges towards the center of the graph after we calculate positions.
|
||||||
|
// To prevent the edges from moving to far behind the vertices, we will compensate a
|
||||||
|
// bit for that effect using this offset value. The getEdgeOffset() method is
|
||||||
|
// updated for the condense factor.
|
||||||
|
int vertexToEdgeOffset = otherVertex.getEdgeOffset();
|
||||||
|
int exaggerationFactor = 1;
|
||||||
|
if (isCondensedLayout()) {
|
||||||
|
exaggerationFactor = 4; // determined by trial-and-error; can be made into an option
|
||||||
|
}
|
||||||
|
|
||||||
|
double centerX = otherVertex.getX();
|
||||||
|
boolean isLeft = edgeX < centerX;
|
||||||
|
|
||||||
|
// no need to check the 'y' value, as the end vertex is below this one
|
||||||
|
|
||||||
|
if (isLeft) {
|
||||||
|
if (edgeX >= otherVertex.getLeft()) {
|
||||||
|
// less than center; after the start of the vertex
|
||||||
|
|
||||||
|
/*
|
||||||
|
Must route around this vertex - new points:
|
||||||
|
-p1 - just above the intersection point
|
||||||
|
-p2 - just past the left edge
|
||||||
|
-p3 - just past the bottom of the vertex
|
||||||
|
-p4 - back at the original x value
|
||||||
|
|
||||||
|
|
|
||||||
|
.___|
|
||||||
|
| .-----.
|
||||||
|
| | |
|
||||||
|
| '-----'
|
||||||
|
'---.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// p1 - same x; y just above vertex
|
||||||
|
double x = edgeX;
|
||||||
|
double y = otherVertex.getTop() - vertexToEdgeOffset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// maybe merge points if they are too close together
|
||||||
|
if (articulations.size() > 2) {
|
||||||
|
Point2D previousArticulation = articulations.get(articulations.size() - 2);
|
||||||
|
int closenessHeight = 50;
|
||||||
|
double previousY = previousArticulation.getY();
|
||||||
|
if (y - previousY < closenessHeight) {
|
||||||
|
// remove our first articulation and the one before that so that
|
||||||
|
// the get merged into on line on the outside of the vertex
|
||||||
|
articulations.remove(articulations.size() - 1);
|
||||||
|
articulations.remove(articulations.size() - 1);
|
||||||
|
Point2D newPrevious = articulations.get(articulations.size() - 1);
|
||||||
|
y = newPrevious.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// p2 - move over; same y
|
||||||
|
int offset = Math.max(vertexToEdgeOffset, distanceSpacing);
|
||||||
|
offset *= exaggerationFactor;
|
||||||
|
x = otherVertex.getLeft() - offset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// p3 - same x; move y below the vertex
|
||||||
|
y = otherVertex.getBottom() + vertexToEdgeOffset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// p4 - move over back to our original x; same y
|
||||||
|
x = edgeX;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // right
|
||||||
|
|
||||||
|
double otherEnd = otherVertex.getRight();
|
||||||
|
if (edgeX < otherEnd) {
|
||||||
|
// greater than center; before the end of the vertex
|
||||||
|
// (see not above for routing info)
|
||||||
|
|
||||||
|
// p1 - same x; y just above vertex
|
||||||
|
double x = edgeX;
|
||||||
|
double y = otherVertex.getTop() - vertexToEdgeOffset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// maybe merge points if they are too close together
|
||||||
|
if (articulations.size() > 2) {
|
||||||
|
Point2D previousArticulation = articulations.get(articulations.size() - 2);
|
||||||
|
int closenessHeight = 50;
|
||||||
|
double previousY = previousArticulation.getY();
|
||||||
|
if (y - previousY < closenessHeight) {
|
||||||
|
// remove our first articulation and the one before that so that
|
||||||
|
// the get merged into on line on the outside of the vertex
|
||||||
|
articulations.remove(articulations.size() - 1);
|
||||||
|
articulations.remove(articulations.size() - 1);
|
||||||
|
Point2D newPrevious = articulations.get(articulations.size() - 1);
|
||||||
|
y = newPrevious.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// p2 - move over; same y
|
||||||
|
int offset = Math.max(vertexToEdgeOffset, distanceSpacing);
|
||||||
|
offset *= exaggerationFactor;
|
||||||
|
x = otherEnd + offset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// p3 - same x; move y below the vertex
|
||||||
|
y = otherVertex.getBottom() + vertexToEdgeOffset;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
|
||||||
|
// p4 - move over back to our original x; same y
|
||||||
|
x = edgeX;
|
||||||
|
articulations.add(new Point2D.Double(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Point2D> routeLoopEdge(Vertex2d start, Vertex2d end, Column loopEndColumn) {
|
||||||
|
|
||||||
// going backwards
|
// going backwards
|
||||||
List<Point2D> articulations = new ArrayList<>();
|
List<Point2D> articulations = new ArrayList<>();
|
||||||
|
|
||||||
DecompilerBlock block = blockGraphRoot.getBlock(endVertex);
|
|
||||||
DecompilerBlock loop = block.getParentLoop();
|
|
||||||
Set<FGVertex> vertices = loop.getVertices();
|
|
||||||
|
|
||||||
// loop first point - same y coord as the vertex; x is the middle of the next col
|
// loop first point - same y coord as the vertex; x is the middle of the next col
|
||||||
Column outermostCol = getOutermostCol(layoutLocations, vertices);
|
int halfWidth = loopEndColumn.getPaddedWidth(isCondensedLayout()) >> 1;
|
||||||
Column afterColumn = layoutLocations.nextColumn(outermostCol);
|
double x = loopEndColumn.x + halfWidth; // middle of the column
|
||||||
|
|
||||||
int halfWidth = afterColumn.getPaddedWidth(isCondensedLayout()) >> 1;
|
Point2D startVertexPoint = start.center;
|
||||||
double x = afterColumn.x + halfWidth; // middle of the column
|
|
||||||
|
|
||||||
Point2D startVertexPoint = vertexLayoutLocations.get(startVertex);
|
|
||||||
|
|
||||||
double y1 = startVertexPoint.getY();
|
double y1 = startVertexPoint.getY();
|
||||||
Point2D first = new Point2D.Double(x, y1);
|
Point2D first = new Point2D.Double(x, y1);
|
||||||
|
@ -344,12 +700,20 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
// loop second point - same y coord as destination;
|
// loop second point - same y coord as destination;
|
||||||
// x is the col after the outermost dominated vertex
|
// x is the col after the outermost dominated vertex
|
||||||
|
|
||||||
Point2D endVertexPoint = vertexLayoutLocations.get(endVertex);
|
Point2D endVertexPoint = end.center;
|
||||||
double y2 = endVertexPoint.getY();
|
double y2 = endVertexPoint.getY();
|
||||||
Point2D second = new Point2D.Double(x, y2);
|
Point2D second = new Point2D.Double(x, y2);
|
||||||
articulations.add(second);
|
articulations.add(second);
|
||||||
|
|
||||||
newEdgeArticulations.put(e, articulations);
|
return articulations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lighten(FGEdge e) {
|
||||||
|
|
||||||
|
// assumption: edges that move to the left in this layout are return flows that happen
|
||||||
|
// after the code block has been executed. We dim those a bit so that they
|
||||||
|
// produce less clutter.
|
||||||
|
e.setAlpha(.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Column getOutermostCol(LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
|
private Column getOutermostCol(LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
|
||||||
|
@ -566,13 +930,130 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
@Override
|
@Override
|
||||||
protected AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedFGLayout(
|
protected AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedFGLayout(
|
||||||
FunctionGraph newGraph) {
|
FunctionGraph newGraph) {
|
||||||
return new DecompilerNestedLayout(newGraph, false);
|
return new DecompilerNestedLayout(newGraph, getLayoutName(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
private class Vertex2dFactory {
|
||||||
|
|
||||||
|
private VisualGraphVertexShapeTransformer<FGVertex> vertexShaper;
|
||||||
|
private Map<FGVertex, Point2D> vertexLayoutLocations;
|
||||||
|
private LayoutLocationMap<FGVertex, FGEdge> layoutToGridMap;
|
||||||
|
private int edgeOffset;
|
||||||
|
private Map<FGVertex, Vertex2d> cache =
|
||||||
|
LazyMap.lazyMap(new HashMap<>(), v -> new Vertex2d(v, vertexShaper,
|
||||||
|
vertexLayoutLocations, layoutToGridMap, getEdgeOffset()));
|
||||||
|
|
||||||
|
Vertex2dFactory(VisualGraphVertexShapeTransformer<FGVertex> transformer,
|
||||||
|
Map<FGVertex, Point2D> vertexLayoutLocations,
|
||||||
|
LayoutLocationMap<FGVertex, FGEdge> layoutToGridMap, int edgeOffset) {
|
||||||
|
this.vertexShaper = transformer;
|
||||||
|
this.vertexLayoutLocations = vertexLayoutLocations;
|
||||||
|
this.layoutToGridMap = layoutToGridMap;
|
||||||
|
this.edgeOffset = edgeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Column getColumn(double x) {
|
||||||
|
return layoutToGridMap.getColumnContaining((int) x);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEdgeOffset() {
|
||||||
|
return edgeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vertex2d get(FGVertex v) {
|
||||||
|
return cache.get(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vertex2d get(int rowIndex, int columnIndex) {
|
||||||
|
|
||||||
|
Row<FGVertex> row = layoutToGridMap.row(rowIndex);
|
||||||
|
FGVertex v = row.getVertex(columnIndex);
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return get(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents 2D information about the contained vertex, such as location,
|
||||||
|
* bounds, row and column of the layout grid.
|
||||||
|
*/
|
||||||
|
private class Vertex2d {
|
||||||
|
|
||||||
|
private FGVertex v;
|
||||||
|
private Row<FGVertex> row;
|
||||||
|
private Column column;
|
||||||
|
private int rowIndex;
|
||||||
|
private int columnIndex;
|
||||||
|
private Point2D center; // center point of vertex shape
|
||||||
|
private Shape shape;
|
||||||
|
private Rectangle bounds; // centered over the 'location'
|
||||||
|
private int edgeOffset;
|
||||||
|
|
||||||
|
Vertex2d(FGVertex v, VisualGraphVertexShapeTransformer<FGVertex> transformer,
|
||||||
|
Map<FGVertex, Point2D> vertexLayoutLocations,
|
||||||
|
LayoutLocationMap<FGVertex, FGEdge> layoutLocations, int edgeOffset) {
|
||||||
|
|
||||||
|
this.v = v;
|
||||||
|
this.row = layoutLocations.row(v);
|
||||||
|
this.rowIndex = row.index;
|
||||||
|
this.column = layoutLocations.col(v);
|
||||||
|
this.columnIndex = column.index;
|
||||||
|
this.center = vertexLayoutLocations.get(v);
|
||||||
|
this.shape = transformer.apply(v);
|
||||||
|
this.bounds = shape.getBounds();
|
||||||
|
this.edgeOffset = edgeOffset;
|
||||||
|
|
||||||
|
// center bounds over location (this is how the graph gets painted)
|
||||||
|
double cornerX = center.getX() + bounds.getWidth() / 2;
|
||||||
|
double cornerY = center.getY() + bounds.getHeight() / 2;
|
||||||
|
Point2D corner = new Point2D.Double(cornerX, cornerY);
|
||||||
|
bounds.setFrameFromCenter(center, corner);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getY() {
|
||||||
|
return center.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getX() {
|
||||||
|
return center.getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLeft() {
|
||||||
|
return center.getX() - (bounds.width >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getRight() {
|
||||||
|
return center.getX() + (bounds.width >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getBottom() {
|
||||||
|
return center.getY() + (bounds.height >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getTop() {
|
||||||
|
return center.getY() - (bounds.height >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getEdgeOffset() {
|
||||||
|
return edgeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class DecompilerBlockGraph extends DecompilerBlock {
|
private class DecompilerBlockGraph extends DecompilerBlock {
|
||||||
|
|
||||||
protected List<DecompilerBlock> allChildren = new ArrayList<>();
|
protected List<DecompilerBlock> allChildren = new ArrayList<>();
|
||||||
|
@ -666,7 +1147,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
@Override
|
@Override
|
||||||
String getName() {
|
String getName() {
|
||||||
return null;
|
return null;
|
||||||
// return "Block"; TODO could put in a 'debug name' method for debugging
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -834,13 +1314,13 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
case LIST:
|
case LIST:
|
||||||
return new ListBlock(parent, block);
|
return new ListBlock(parent, block);
|
||||||
case CONDITION:
|
case CONDITION:
|
||||||
return new ConditionBlock(parent, block); // TODO - not sure
|
return new ConditionBlock(parent, block); // not sure
|
||||||
case PROPERIF:
|
case PROPERIF:
|
||||||
return new IfBlock(parent, block);
|
return new IfBlock(parent, block);
|
||||||
case IFELSE:
|
case IFELSE:
|
||||||
return new IfElseBlock(parent, block);
|
return new IfElseBlock(parent, block);
|
||||||
case IFGOTO:
|
case IFGOTO:
|
||||||
return new IfBlock(parent, block); // TODO - not sure
|
return new IfBlock(parent, block); // not sure
|
||||||
case WHILEDO:
|
case WHILEDO:
|
||||||
return new WhileLoopBlock(parent, block);
|
return new WhileLoopBlock(parent, block);
|
||||||
case DOWHILE:
|
case DOWHILE:
|
||||||
|
@ -874,7 +1354,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getName() {
|
String getName() {
|
||||||
return "Plain"; // TODO: maybe just null
|
return "Plain";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -902,7 +1382,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
@Override
|
@Override
|
||||||
String getName() {
|
String getName() {
|
||||||
return parent.getName();
|
return parent.getName();
|
||||||
// return "List";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,7 +1446,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
||||||
block.setCol(column);
|
block.setCol(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
doSetCol(col); // TODO does the non-copy block need a column??
|
doSetCol(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,25 +18,33 @@ package ghidra.app.plugin.core.functiongraph.graph.layout;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
||||||
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
public class DecompilerNestedLayoutProvider implements FGLayoutProvider {
|
public class DecompilerNestedLayoutProvider extends FGLayoutProvider {
|
||||||
|
|
||||||
private static final Icon ICON =
|
private static final Icon ICON =
|
||||||
ResourceManager.loadImage("images/function_graph_code_flow.png");
|
ResourceManager.loadImage("images/function_graph_code_flow.png");
|
||||||
|
static final String LAYOUT_NAME = "Nested Code Layout";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) {
|
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) {
|
||||||
DecompilerNestedLayout layout = new DecompilerNestedLayout(graph);
|
DecompilerNestedLayout layout = new DecompilerNestedLayout(graph, LAYOUT_NAME);
|
||||||
layout.setTaskMonitor(monitor);
|
layout.setTaskMonitor(monitor);
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FGLayoutOptions createLayoutOptions(Options options) {
|
||||||
|
DNLayoutOptions layoutOptions = new DNLayoutOptions();
|
||||||
|
layoutOptions.registerOptions(options);
|
||||||
|
return layoutOptions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLayoutName() {
|
public String getLayoutName() {
|
||||||
// TODO better name?...or rename classes to match
|
return LAYOUT_NAME;
|
||||||
return "Nested Code Layout";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -44,8 +44,8 @@ import ghidra.util.task.TaskMonitor;
|
||||||
*/
|
*/
|
||||||
public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge> {
|
public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge> {
|
||||||
|
|
||||||
protected BowTieLayout(FunctionCallGraph graph) {
|
protected BowTieLayout(FunctionCallGraph graph, String name) {
|
||||||
super(graph);
|
super(graph, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,7 +58,7 @@ public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge>
|
||||||
getClass().getSimpleName());
|
getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
BowTieLayout newLayout = new BowTieLayout((FunctionCallGraph) newGraph);
|
BowTieLayout newLayout = new BowTieLayout((FunctionCallGraph) newGraph, getLayoutName());
|
||||||
return newLayout;
|
return newLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class BowTieLayoutProvider
|
||||||
public VisualGraphLayout<FcgVertex, FcgEdge> getLayout(FunctionCallGraph graph,
|
public VisualGraphLayout<FcgVertex, FcgEdge> getLayout(FunctionCallGraph graph,
|
||||||
TaskMonitor monitor) throws CancelledException {
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
BowTieLayout layout = new BowTieLayout(graph);
|
BowTieLayout layout = new BowTieLayout(graph, NAME);
|
||||||
initVertexLocations(graph, layout);
|
initVertexLocations(graph, layout);
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,15 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package docking.menu;
|
package docking.menu;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: this class overrides the {@ #equals(Object)} method} and relies upon the <tt>equals</tt>
|
* Note: this class overrides the <tt>equals(Object)</tt> and relies upon the <tt>equals</tt>
|
||||||
* method of the <tt>userData</tt> object. Thus, if it is important that equals work for you in
|
* method of the <tt>userData</tt> object. Thus, if it is important that equals work for you in
|
||||||
* the non-standard identity way, then you must override <tt>equals</tt> in your user data obejcts.
|
* the non-standard identity way, then you must override <tt>equals</tt> in your user data objects.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the action state
|
||||||
*/
|
*/
|
||||||
public class ActionState<T> {
|
public class ActionState<T> {
|
||||||
|
|
||||||
|
@ -51,36 +52,41 @@ public class ActionState<T> {
|
||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHelpLocation( HelpLocation helpLocation ) {
|
public void setHelpLocation(HelpLocation helpLocation) {
|
||||||
this.helpLocation = helpLocation;
|
this.helpLocation = helpLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HelpLocation getHelpLocation() {
|
public HelpLocation getHelpLocation() {
|
||||||
return helpLocation;
|
return helpLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals( Object other ) {
|
public boolean equals(Object other) {
|
||||||
if ( other == null ) {
|
if (other == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends Object> otherClass = other.getClass();
|
Class<? extends Object> otherClass = other.getClass();
|
||||||
if ( !getClass().equals( otherClass ) ) {
|
if (!getClass().equals(otherClass)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionState<?> otherState = (ActionState<?>) other;
|
ActionState<?> otherState = (ActionState<?>) other;
|
||||||
|
|
||||||
if ( !SystemUtilities.isEqual( userData, otherState.userData ) ) {
|
if (!SystemUtilities.isEqual(userData, otherState.userData)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return name.equals( otherState.name );
|
return name.equals(otherState.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return name.hashCode() + ((userData == null) ? 0 : userData.hashCode());
|
return name.hashCode() + ((userData == null) ? 0 : userData.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + ": " + userData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,11 +76,15 @@ public class VisualGraphEdgeSelectionGraphMousePlugin<V extends VisualVertex, E
|
||||||
private void pickAndShowVertex(V vertex, PickedState<V> pickedVertexState,
|
private void pickAndShowVertex(V vertex, PickedState<V> pickedVertexState,
|
||||||
GraphViewer<V, E> viewer) {
|
GraphViewer<V, E> viewer) {
|
||||||
|
|
||||||
GPickedState<V> pickedStateWrapper = (GPickedState<V>) pickedVertexState;
|
|
||||||
pickedStateWrapper.pickToActivate(vertex);
|
|
||||||
|
|
||||||
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
|
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
|
||||||
updater.moveVertexToCenterWithAnimation(vertex);
|
updater.moveVertexToCenterWithAnimation(vertex, isBusy -> {
|
||||||
|
|
||||||
|
// pick the vertex after the animation has run
|
||||||
|
if (!isBusy) {
|
||||||
|
GPickedState<V> pickedStateWrapper = (GPickedState<V>) pickedVertexState;
|
||||||
|
pickedStateWrapper.pickToActivate(vertex);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,7 +33,6 @@ import ghidra.graph.VisualGraph;
|
||||||
import ghidra.graph.viewer.*;
|
import ghidra.graph.viewer.*;
|
||||||
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
|
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
|
||||||
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
|
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
|
||||||
import ghidra.graph.viewer.renderer.VisualGraphRenderer;
|
|
||||||
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
|
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
|
||||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||||
|
@ -87,12 +86,22 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
||||||
new ArticulatedEdgeTransformer<>();
|
new ArticulatedEdgeTransformer<>();
|
||||||
private ArticulatedEdgeRenderer<V, E> edgeRenderer = new ArticulatedEdgeRenderer<>();
|
private ArticulatedEdgeRenderer<V, E> edgeRenderer = new ArticulatedEdgeRenderer<>();
|
||||||
|
|
||||||
protected TaskMonitor monitor = TaskMonitor.DUMMY;
|
protected String layoutName;
|
||||||
|
|
||||||
protected boolean layoutInitialized;
|
protected boolean layoutInitialized;
|
||||||
|
|
||||||
protected AbstractVisualGraphLayout(Graph<V, E> graph) {
|
protected TaskMonitor monitor = TaskMonitor.DUMMY;
|
||||||
|
|
||||||
|
protected AbstractVisualGraphLayout(Graph<V, E> graph, String layoutName) {
|
||||||
super(graph);
|
super(graph);
|
||||||
|
this.layoutName = layoutName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this layout
|
||||||
|
* @return the name of this layout
|
||||||
|
*/
|
||||||
|
public String getLayoutName() {
|
||||||
|
return layoutName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -99,6 +99,18 @@ public class LayoutLocationMap<V, E> {
|
||||||
return doGetColumn(gridX);
|
return doGetColumn(gridX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Column getColumnContaining(int x) {
|
||||||
|
Column column = null;
|
||||||
|
Collection<Column> values = columnsByIndex.values();
|
||||||
|
for (Column nextColumn : values) {
|
||||||
|
column = nextColumn;
|
||||||
|
if (x < column.x) {
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
private Column doGetColumn(int index) {
|
private Column doGetColumn(int index) {
|
||||||
Column column = columnsByIndex.get(index);
|
Column column = columnsByIndex.get(index);
|
||||||
if (column == null) {
|
if (column == null) {
|
||||||
|
@ -342,4 +354,5 @@ public class LayoutLocationMap<V, E> {
|
||||||
offset += column.getPaddedWidth(isCondensed);
|
offset += column.getPaddedWidth(isCondensed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ public interface LayoutProvider<V extends VisualVertex,
|
||||||
/**
|
/**
|
||||||
* Returns a new instance of the layout that this class provides
|
* Returns a new instance of the layout that this class provides
|
||||||
*
|
*
|
||||||
* @param m the model object
|
|
||||||
* @param graph the graph
|
* @param graph the graph
|
||||||
* @param monitor a task monitor
|
* @param monitor a task monitor
|
||||||
* @return the new layout
|
* @return the new layout
|
||||||
|
|
|
@ -82,11 +82,11 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
|
||||||
|
|
||||||
// paint all the edges
|
// paint all the edges
|
||||||
// DEBUG code to show the edges *over* the vertices
|
// DEBUG code to show the edges *over* the vertices
|
||||||
// for (E e : layout.getGraph().getEdges()) {
|
for (E e : layout.getGraph().getEdges()) {
|
||||||
//
|
|
||||||
// renderEdge(renderContext, layout, e);
|
renderEdge(renderContext, layout, e);
|
||||||
// renderEdgeLabel(renderContext, layout, e);
|
renderEdgeLabel(renderContext, layout, e);
|
||||||
// }
|
}
|
||||||
|
|
||||||
paintLayoutGridCells(renderContext, layout);
|
paintLayoutGridCells(renderContext, layout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,7 +273,7 @@ public class TestGraphAlgorithmSteppingViewerPanel<V, E extends GEdge<V>> extend
|
||||||
AbstractVisualGraphLayout<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> {
|
AbstractVisualGraphLayout<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> {
|
||||||
|
|
||||||
protected TestGraphLayout(TestGraph graph) {
|
protected TestGraphLayout(TestGraph graph) {
|
||||||
super(graph);
|
super(graph, "Test Layout");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue