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

@ -32,8 +32,8 @@ import ghidra.util.exception.CancelledException;
public class SampleGraphPluginDependencyLayout
extends AbstractVisualGraphLayout<SampleVertex, SampleEdge> {
protected SampleGraphPluginDependencyLayout(SampleGraph graph) {
super(graph);
protected SampleGraphPluginDependencyLayout(SampleGraph graph, String name) {
super(graph, name);
}
@Override
@ -50,7 +50,7 @@ public class SampleGraphPluginDependencyLayout
}
SampleGraphPluginDependencyLayout newLayout =
new SampleGraphPluginDependencyLayout((SampleGraph) newGraph);
new SampleGraphPluginDependencyLayout((SampleGraph) newGraph, getLayoutName());
return newLayout;
}

View file

@ -30,20 +30,21 @@ import resources.ResourceManager;
public class SampleGraphPluginDependencyLayoutProvider
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");
@Override
public VisualGraphLayout<SampleVertex, SampleEdge> getLayout(SampleGraph g, TaskMonitor monitor)
throws CancelledException {
SampleGraphPluginDependencyLayout layout = new SampleGraphPluginDependencyLayout(g);
SampleGraphPluginDependencyLayout layout = new SampleGraphPluginDependencyLayout(g, NAME);
initVertexLocations(g, layout);
return layout;
}
@Override
public String getLayoutName() {
return "Plugin Dependency Layout";
return NAME;
}
// Note: each provider really should load its own icon so that the toolbar item can

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

View file

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

View file

@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.collections4.map.LazyMap;
import com.google.common.base.Function;
@ -49,24 +50,46 @@ import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
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
// TODO: should we allow grouping in this layout?
// TODO: entry not always at the top - winhello.exe 402c8c
/**
* A layout that uses the decompiler to show code nesting based upon conditional logic.
*
* <p>Edges returning to the default code flow are painted lighter to de-emphasize them. This
* could be made into an option.
*
* 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 {
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;
public DecompilerNestedLayout(FunctionGraph graph) {
this(graph, true);
public DecompilerNestedLayout(FunctionGraph graph, String name) {
this(graph, name, true);
}
private DecompilerNestedLayout(FunctionGraph graph, boolean initialize) {
super(graph);
private DecompilerNestedLayout(FunctionGraph graph, String name, boolean initialize) {
super(graph, name);
if (initialize) {
initialize();
}
@ -89,6 +112,10 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
return .3;
}
private DNLayoutOptions getLayoutOptions() {
return (DNLayoutOptions) options.getLayoutOptions(getLayoutName());
}
@Override
protected GridLocationMap<FGVertex, FGEdge> performInitialGridLayout(
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
protected Map<FGEdge, List<Point2D>> positionEdgeArticulationsInLayoutSpace(
VisualGraphVertexShapeTransformer<FGVertex> transformer,
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<>();
// 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!
//
@ -208,44 +245,34 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
FGVertex startVertex = e.getStart();
FGVertex endVertex = e.getEnd();
Vertex2d start = vertex2dFactory.get(startVertex);
Vertex2d end = vertex2dFactory.get(endVertex);
Address startAddress = startVertex.getVertexAddress();
Address endAddress = endVertex.getVertexAddress();
int result = startAddress.compareTo(endAddress);
int compareResult = startAddress.compareTo(endAddress);
boolean goingUp = compareResult > 0;
if (goingUp) {
DecompilerBlock block = blockGraphRoot.getBlock(endVertex);
DecompilerBlock loop = block.getParentLoop();
if (result > 0 && loop != null) {
// TODO better check for loops
routeLoopEdge(vertexLayoutLocations, layoutLocations, newEdgeArticulations, e,
startVertex, endVertex);
}
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?
//
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;
}
}
Column startCol = layoutLocations.col(startVertex);
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
// TODO make constant
// direction = 10;
}
else if (startCol.index > endCol.index) { // going backwards on the x-axis
direction = -direction;
}
int offsetFromVertex = isCondensedLayout()
? (int) (VERTEX_TO_EDGE_ARTICULATION_OFFSET * (1 - getCondenseFactor()))
: VERTEX_TO_EDGE_ARTICULATION_OFFSET;
if (startCol.index < endCol.index) { // going left or right
//
// Basic routing:
// -leave the bottom of the start vertex
@ -253,89 +280,418 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
// -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
// 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
// Thus, we 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
//
// 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.
//
double x1 = start.getX() + direction;
double y1 = start.getY(); // hidden
articulations.add(new Point2D.Double(x1, y1));
if (start.columnIndex < end.columnIndex) { // going to the right
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));
routeToTheRight(start, end, vertex2dFactory, articulations);
}
else if (startCol.index > endCol.index) { // flow return
e.setAlpha(.25);
else if (start.columnIndex > end.columnIndex) { // going to the left; flow return
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));
// 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 { // same column--nothing to route
// straight line, which is the default
e.setAlpha(.25);
else { // going down; no nesting; flow return
routeDownward(start, end, e, vertex2dFactory, articulations);
}
newEdgeArticulations.put(e, articulations);
}
}
vertex2dFactory.dispose();
return newEdgeArticulations;
}
private void routeLoopEdge(Map<FGVertex, Point2D> vertexLayoutLocations,
LayoutLocationMap<FGVertex, FGEdge> layoutLocations,
Map<FGEdge, List<Point2D>> newEdgeArticulations, FGEdge e, FGVertex startVertex,
FGVertex endVertex) {
private void routeToTheRightGoingUpwards(Vertex2d start, Vertex2d end,
Vertex2dFactory vertex2dFactory, List<Point2D> articulations) {
//
// 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
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
Column outermostCol = getOutermostCol(layoutLocations, vertices);
Column afterColumn = layoutLocations.nextColumn(outermostCol);
int halfWidth = loopEndColumn.getPaddedWidth(isCondensedLayout()) >> 1;
double x = loopEndColumn.x + halfWidth; // middle of the column
int halfWidth = afterColumn.getPaddedWidth(isCondensedLayout()) >> 1;
double x = afterColumn.x + halfWidth; // middle of the column
Point2D startVertexPoint = vertexLayoutLocations.get(startVertex);
Point2D startVertexPoint = start.center;
double y1 = startVertexPoint.getY();
Point2D first = new Point2D.Double(x, y1);
@ -344,12 +700,20 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
// loop second point - same y coord as destination;
// x is the col after the outermost dominated vertex
Point2D endVertexPoint = vertexLayoutLocations.get(endVertex);
Point2D endVertexPoint = end.center;
double y2 = endVertexPoint.getY();
Point2D second = new Point2D.Double(x, y2);
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,
@ -566,13 +930,130 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
@Override
protected AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedFGLayout(
FunctionGraph newGraph) {
return new DecompilerNestedLayout(newGraph, false);
return new DecompilerNestedLayout(newGraph, getLayoutName(), false);
}
//==================================================================================================
// 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 {
protected List<DecompilerBlock> allChildren = new ArrayList<>();
@ -666,7 +1147,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
@Override
String getName() {
return null;
// return "Block"; TODO could put in a 'debug name' method for debugging
}
@Override
@ -834,13 +1314,13 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
case LIST:
return new ListBlock(parent, block);
case CONDITION:
return new ConditionBlock(parent, block); // TODO - not sure
return new ConditionBlock(parent, block); // not sure
case PROPERIF:
return new IfBlock(parent, block);
case IFELSE:
return new IfElseBlock(parent, block);
case IFGOTO:
return new IfBlock(parent, block); // TODO - not sure
return new IfBlock(parent, block); // not sure
case WHILEDO:
return new WhileLoopBlock(parent, block);
case DOWHILE:
@ -874,7 +1354,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
@Override
String getName() {
return "Plain"; // TODO: maybe just null
return "Plain";
}
}
@ -902,7 +1382,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
@Override
String getName() {
return parent.getName();
// return "List";
}
}
@ -967,7 +1446,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
block.setCol(column);
}
doSetCol(col); // TODO does the non-copy block need a column??
doSetCol(col);
}
@Override

View file

@ -18,25 +18,33 @@ package ghidra.app.plugin.core.functiongraph.graph.layout;
import javax.swing.Icon;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.framework.options.Options;
import ghidra.util.task.TaskMonitor;
import resources.ResourceManager;
public class DecompilerNestedLayoutProvider implements FGLayoutProvider {
public class DecompilerNestedLayoutProvider extends FGLayoutProvider {
private static final Icon ICON =
ResourceManager.loadImage("images/function_graph_code_flow.png");
static final String LAYOUT_NAME = "Nested Code Layout";
@Override
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) {
DecompilerNestedLayout layout = new DecompilerNestedLayout(graph);
DecompilerNestedLayout layout = new DecompilerNestedLayout(graph, LAYOUT_NAME);
layout.setTaskMonitor(monitor);
return layout;
}
@Override
public FGLayoutOptions createLayoutOptions(Options options) {
DNLayoutOptions layoutOptions = new DNLayoutOptions();
layoutOptions.registerOptions(options);
return layoutOptions;
}
@Override
public String getLayoutName() {
// TODO better name?...or rename classes to match
return "Nested Code Layout";
return LAYOUT_NAME;
}
@Override

View file

@ -44,8 +44,8 @@ import ghidra.util.task.TaskMonitor;
*/
public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge> {
protected BowTieLayout(FunctionCallGraph graph) {
super(graph);
protected BowTieLayout(FunctionCallGraph graph, String name) {
super(graph, name);
}
@Override
@ -58,7 +58,7 @@ public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge>
getClass().getSimpleName());
}
BowTieLayout newLayout = new BowTieLayout((FunctionCallGraph) newGraph);
BowTieLayout newLayout = new BowTieLayout((FunctionCallGraph) newGraph, getLayoutName());
return newLayout;
}

View file

@ -38,7 +38,7 @@ public class BowTieLayoutProvider
public VisualGraphLayout<FcgVertex, FcgEdge> getLayout(FunctionCallGraph graph,
TaskMonitor monitor) throws CancelledException {
BowTieLayout layout = new BowTieLayout(graph);
BowTieLayout layout = new BowTieLayout(graph, NAME);
initVertexLocations(graph, layout);
return layout;
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,15 +15,17 @@
*/
package docking.menu;
import javax.swing.Icon;
import ghidra.util.HelpLocation;
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
* 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> {
@ -51,7 +52,7 @@ public class ActionState<T> {
return userData;
}
public void setHelpLocation( HelpLocation helpLocation ) {
public void setHelpLocation(HelpLocation helpLocation) {
this.helpLocation = helpLocation;
}
@ -60,27 +61,32 @@ public class ActionState<T> {
}
@Override
public boolean equals( Object other ) {
if ( other == null ) {
public boolean equals(Object other) {
if (other == null) {
return false;
}
Class<? extends Object> otherClass = other.getClass();
if ( !getClass().equals( otherClass ) ) {
if (!getClass().equals(otherClass)) {
return false;
}
ActionState<?> otherState = (ActionState<?>) other;
if ( !SystemUtilities.isEqual( userData, otherState.userData ) ) {
if (!SystemUtilities.isEqual(userData, otherState.userData)) {
return false;
}
return name.equals( otherState.name );
return name.equals(otherState.name);
}
@Override
public int hashCode() {
return name.hashCode() + ((userData == null) ? 0 : userData.hashCode());
}
@Override
public String toString() {
return name + ": " + userData;
}
}

View file

@ -76,11 +76,15 @@ public class VisualGraphEdgeSelectionGraphMousePlugin<V extends VisualVertex, E
private void pickAndShowVertex(V vertex, PickedState<V> pickedVertexState,
GraphViewer<V, E> viewer) {
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
updater.moveVertexToCenterWithAnimation(vertex, isBusy -> {
// pick the vertex after the animation has run
if (!isBusy) {
GPickedState<V> pickedStateWrapper = (GPickedState<V>) pickedVertexState;
pickedStateWrapper.pickToActivate(vertex);
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
updater.moveVertexToCenterWithAnimation(vertex);
}
});
}
@Override

View file

@ -33,7 +33,6 @@ import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
import ghidra.graph.viewer.renderer.VisualGraphRenderer;
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
import ghidra.util.datastruct.WeakDataStructureFactory;
@ -87,12 +86,22 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
new ArticulatedEdgeTransformer<>();
private ArticulatedEdgeRenderer<V, E> edgeRenderer = new ArticulatedEdgeRenderer<>();
protected TaskMonitor monitor = TaskMonitor.DUMMY;
protected String layoutName;
protected boolean layoutInitialized;
protected AbstractVisualGraphLayout(Graph<V, E> graph) {
protected TaskMonitor monitor = TaskMonitor.DUMMY;
protected AbstractVisualGraphLayout(Graph<V, E> graph, String layoutName) {
super(graph);
this.layoutName = layoutName;
}
/**
* Returns the name of this layout
* @return the name of this layout
*/
public String getLayoutName() {
return layoutName;
}
/**

View file

@ -99,6 +99,18 @@ public class LayoutLocationMap<V, E> {
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) {
Column column = columnsByIndex.get(index);
if (column == null) {
@ -342,4 +354,5 @@ public class LayoutLocationMap<V, E> {
offset += column.getPaddedWidth(isCondensed);
}
}
}

View file

@ -48,7 +48,6 @@ public interface LayoutProvider<V extends VisualVertex,
/**
* Returns a new instance of the layout that this class provides
*
* @param m the model object
* @param graph the graph
* @param monitor a task monitor
* @return the new layout

View file

@ -82,11 +82,11 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
// paint all the edges
// DEBUG code to show the edges *over* the vertices
// for (E e : layout.getGraph().getEdges()) {
//
// renderEdge(renderContext, layout, e);
// renderEdgeLabel(renderContext, layout, e);
// }
for (E e : layout.getGraph().getEdges()) {
renderEdge(renderContext, layout, e);
renderEdgeLabel(renderContext, layout, e);
}
paintLayoutGridCells(renderContext, layout);
}

View file

@ -273,7 +273,7 @@ public class TestGraphAlgorithmSteppingViewerPanel<V, E extends GEdge<V>> extend
AbstractVisualGraphLayout<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> {
protected TestGraphLayout(TestGraph graph) {
super(graph);
super(graph, "Test Layout");
}
@SuppressWarnings("unchecked")