mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
Merge remote-tracking branch 'origin/GP-5717-dragonmacher-vertex-shapes'
This commit is contained in:
commit
88f77e5cd1
9 changed files with 882 additions and 466 deletions
|
@ -26,6 +26,8 @@ color.fg.dialog.equates.suggestion = color.palette.hint
|
|||
color.fg.consoletextpane = color.fg
|
||||
color.fg.consoletextpane.error = color.fg.error
|
||||
|
||||
color.bg.graph.vertex.function = MediumAquamarine
|
||||
|
||||
color.fg.infopanel.version = color.fg
|
||||
|
||||
color.bg.interpreterconsole = color.bg
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* ###
|
||||
* 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.base.graph;
|
||||
|
||||
import java.awt.Shape;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import ghidra.graph.viewer.vertex.AbstractVisualVertex;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
|
||||
/**
|
||||
* A vertex that is a circle shape with a label below the circle to show the given text.
|
||||
*/
|
||||
public class CircleWithLabelVertex extends AbstractVisualVertex implements VertexShapeProvider {
|
||||
|
||||
protected CircleWithLabelVertexShapeProvider shapeProvider;
|
||||
|
||||
public CircleWithLabelVertex(String label) {
|
||||
this.shapeProvider = new CircleWithLabelVertexShapeProvider(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return shapeProvider.getComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return shapeProvider.getCompactShape();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getFullShape() {
|
||||
return shapeProvider.getFullShape();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return shapeProvider.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return shapeProvider.getName();// + " @ " + level; // + " (" + System.identityHashCode(this) + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
/* ###
|
||||
* 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.base.graph;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Ellipse2D.Double;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import docking.widgets.EmptyBorderButton;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class CircleWithLabelVertexShapeProvider implements VertexShapeProvider {
|
||||
|
||||
//@formatter:off
|
||||
public static final Color DEFAULT_VERTEX_SHAPE_COLOR = new GColor("color.bg.graph.vertex.function");
|
||||
//@formatter:on
|
||||
|
||||
protected static final Icon EXPAND_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.EXPAND_ALL_ICON, 10, 10);
|
||||
protected static final Icon COLLAPSE_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.COLLAPSE_ALL_ICON, 10, 10);
|
||||
|
||||
// higher numbered layers go on top
|
||||
protected static final Integer VERTEX_SHAPE_LAYER = 100;
|
||||
protected static final Integer TOGGLE_BUTTON_LAYER = 200;
|
||||
protected static final Integer LABEL_LAYER = 300;
|
||||
|
||||
protected static final int GAP = 2;
|
||||
protected static final int VERTEX_SHAPE_SIZE = 50;
|
||||
|
||||
// Note: This should be made into an option
|
||||
// based upon the default function name, plus some extra
|
||||
protected static final int MAX_NAME_LENGTH = 30;
|
||||
|
||||
protected JLayeredPane layeredPane;
|
||||
protected JButton toggleInsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
protected JButton toggleOutsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
protected JLabel nameLabel = new GDLabel();
|
||||
protected JLabel vertexImageLabel = new GDLabel();
|
||||
|
||||
protected Double vertexShape;
|
||||
protected Double compactShape;
|
||||
protected Shape fullShape;
|
||||
|
||||
protected boolean incomingExpanded;
|
||||
protected boolean outgoingExpanded;
|
||||
|
||||
// set this to true to see borders around the components of this vertex
|
||||
protected boolean useDebugBorders = false;
|
||||
|
||||
private String fullLabelText;
|
||||
|
||||
public CircleWithLabelVertexShapeProvider(String label) {
|
||||
this.fullLabelText = label;
|
||||
buildUi();
|
||||
}
|
||||
|
||||
public CircleWithLabelVertexShapeProvider(String label,
|
||||
VertexExpansionListener expansionListener) {
|
||||
this.fullLabelText = label;
|
||||
buildUi();
|
||||
}
|
||||
|
||||
protected void buildUi() {
|
||||
|
||||
String name = generateLabelText();
|
||||
nameLabel.setText(name);
|
||||
buildVertexShape();
|
||||
|
||||
// calculate the needed size
|
||||
layeredPane = new JLayeredPane();
|
||||
Border border = createDebugBorder(new LineBorder(Palette.GOLD, 1));
|
||||
layeredPane.setBorder(border);
|
||||
|
||||
updateLayeredPaneSize();
|
||||
|
||||
// layout the components
|
||||
addVertexShape();
|
||||
addToggleButtons();
|
||||
addNameLabel();
|
||||
|
||||
buildFullShape();
|
||||
}
|
||||
|
||||
protected String generateLabelText() {
|
||||
return fullLabelText;
|
||||
}
|
||||
|
||||
private Border createDebugBorder(Border border) {
|
||||
if (useDebugBorders) {
|
||||
return border;
|
||||
}
|
||||
return BorderFactory.createEmptyBorder();
|
||||
}
|
||||
|
||||
private void buildFullShape() {
|
||||
|
||||
// Note: this method assumes all bounds have been set
|
||||
Area parent = new Area();
|
||||
|
||||
Area v = new Area(vertexShape);
|
||||
Area name = new Area(nameLabel.getBounds());
|
||||
parent.add(v);
|
||||
parent.add(name);
|
||||
|
||||
// for now, the buttons only appear on hover, but if we want to avoid clipping when
|
||||
// painting, we need to account for them in the shape's overall bounds
|
||||
Area in = new Area(toggleInsButton.getBounds());
|
||||
Area out = new Area(toggleOutsButton.getBounds());
|
||||
parent.add(in);
|
||||
parent.add(out);
|
||||
|
||||
fullShape = parent;
|
||||
}
|
||||
|
||||
private void updateLayeredPaneSize() {
|
||||
|
||||
//
|
||||
// The overall component size is the total width and height of all components, with any
|
||||
// spacing between them.
|
||||
//
|
||||
|
||||
Dimension shapeSize = vertexImageLabel.getPreferredSize();
|
||||
Dimension nameLabelSize = nameLabel.getPreferredSize();
|
||||
int height = shapeSize.height + GAP + nameLabelSize.height;
|
||||
|
||||
Dimension insSize = toggleInsButton.getPreferredSize();
|
||||
Dimension outsSize = toggleOutsButton.getPreferredSize();
|
||||
int buttonWidth = Math.max(insSize.width, outsSize.width);
|
||||
int offset = buttonWidth / 3; // overlap the vertex shape
|
||||
|
||||
int width = offset + shapeSize.width;
|
||||
width = Math.max(width, nameLabelSize.width);
|
||||
|
||||
layeredPane.setPreferredSize(new Dimension(width, height));
|
||||
}
|
||||
|
||||
private void buildVertexShape() {
|
||||
int w = VERTEX_SHAPE_SIZE;
|
||||
int h = VERTEX_SHAPE_SIZE;
|
||||
Double circle = new Ellipse2D.Double(0, 0, w, h);
|
||||
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2.setPaint(getVertexShapePaint());
|
||||
|
||||
g2.fill(circle);
|
||||
|
||||
g2.dispose();
|
||||
|
||||
vertexShape = circle;
|
||||
compactShape = (Double) vertexShape.clone();
|
||||
vertexImageLabel.setIcon(new ImageIcon(image));
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Palette.PINK, 1));
|
||||
vertexImageLabel.setBorder(border);
|
||||
}
|
||||
|
||||
protected Paint getVertexShapePaint() {
|
||||
return getDefaultVertexShapeColor();
|
||||
}
|
||||
|
||||
protected Color getDefaultVertexShapeColor() {
|
||||
return DEFAULT_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
private void addVertexShape() {
|
||||
|
||||
Dimension parentSize = layeredPane.getPreferredSize();
|
||||
Dimension size = vertexImageLabel.getPreferredSize();
|
||||
|
||||
// centered
|
||||
int x = (parentSize.width / 2) - (size.width / 2);
|
||||
int y = 0;
|
||||
|
||||
vertexImageLabel.setBounds(x, y, size.width, size.height);
|
||||
Dimension shapeSize = vertexShape.getBounds().getSize();
|
||||
|
||||
// setFrame() will make sure the shape's x,y values are where they need to be
|
||||
// for the later 'full shape' creation
|
||||
vertexShape.setFrame(x, y, shapeSize.width, shapeSize.height);
|
||||
layeredPane.add(vertexImageLabel, VERTEX_SHAPE_LAYER);
|
||||
}
|
||||
|
||||
private void addNameLabel() {
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Palette.GREEN, 1));
|
||||
nameLabel.setBorder(border);
|
||||
|
||||
// assume the vertex label has been bounded
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = nameLabel.getPreferredSize();
|
||||
|
||||
// bottom, centered under the shape
|
||||
int x = (parentBounds.x + (parentBounds.width / 2)) - (size.width / 2);
|
||||
int y = parentBounds.y + parentBounds.height + GAP;
|
||||
nameLabel.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(nameLabel, LABEL_LAYER);
|
||||
}
|
||||
|
||||
private void addToggleButtons() {
|
||||
|
||||
// hide the button background
|
||||
toggleInsButton.setBackground(Palette.NO_COLOR);
|
||||
toggleOutsButton.setBackground(Palette.NO_COLOR);
|
||||
|
||||
// This is needed for Flat Dark theme to work correctly, due to the fact that it wants to
|
||||
// paint its parent background when the button is opaque. The parent background will get
|
||||
// painted over any items that lie between the button and the parent.
|
||||
toggleInsButton.setOpaque(false);
|
||||
toggleOutsButton.setOpaque(false);
|
||||
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = toggleInsButton.getPreferredSize();
|
||||
|
||||
// upper toggle; upper-left
|
||||
int x = parentBounds.x - (size.width / 3);
|
||||
int y = 0;
|
||||
toggleInsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleInsButton, TOGGLE_BUTTON_LAYER);
|
||||
|
||||
// lower toggle; lower-left, lined-up with the vertex shape
|
||||
size = toggleOutsButton.getPreferredSize();
|
||||
Dimension vertexSize = parentBounds.getSize();
|
||||
y = vertexSize.height - size.height;
|
||||
toggleOutsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleOutsButton, TOGGLE_BUTTON_LAYER);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return fullLabelText;
|
||||
}
|
||||
|
||||
public JButton getIncomingToggleButton() {
|
||||
return toggleInsButton;
|
||||
}
|
||||
|
||||
public JButton getOutgoingToggleButton() {
|
||||
return toggleOutsButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public void setIncomingExpanded(boolean setExpanded) {
|
||||
this.incomingExpanded = setExpanded;
|
||||
toggleInsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " incoming edges");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public boolean isIncomingExpanded() {
|
||||
return incomingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public void setOutgoingExpanded(boolean setExpanded) {
|
||||
this.outgoingExpanded = setExpanded;
|
||||
toggleOutsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " outgoing edges");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public boolean isOutgoingExpanded() {
|
||||
return outgoingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex is fully expanded in its current direction
|
||||
*
|
||||
* @return whether this vertex is fully expanded in its current direction
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
return isIncomingExpanded() && isOutgoingExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this node can be expanded
|
||||
* @return true if this node can be expanded
|
||||
*/
|
||||
public boolean canExpand() {
|
||||
return false; // subclasses can override
|
||||
}
|
||||
|
||||
protected boolean hasIncomingEdges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean hasOutgoingEdges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void setTogglesVisible(boolean visible) {
|
||||
toggleInsButton.setVisible(visible);
|
||||
toggleOutsButton.setVisible(visible);
|
||||
}
|
||||
|
||||
public JComponent getComponent() {
|
||||
return layeredPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return compactShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getFullShape() {
|
||||
return fullShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();// + " @ " + level; // + " (" + System.identityHashCode(this) + ')';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* ###
|
||||
* 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.base.graph;
|
||||
|
||||
import ghidra.graph.viewer.VisualVertex;
|
||||
|
||||
/**
|
||||
* A listener to know when a vertex has been told to expand
|
||||
*/
|
||||
public interface VertexExpansionListener {
|
||||
|
||||
/**
|
||||
* Show or hide those vertices that are on incoming edges to v
|
||||
*
|
||||
* @param v the vertex
|
||||
*/
|
||||
public void toggleIncomingVertices(VisualVertex v);
|
||||
|
||||
/**
|
||||
* Show or hide those vertices that are on outgoing edges to v
|
||||
*
|
||||
* @param v the vertex
|
||||
*/
|
||||
public void toggleOutgoingVertices(VisualVertex v);
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
[Defaults]
|
||||
|
||||
color.bg.plugin.fcg.vertex.default = MediumAquamarine // TODO
|
||||
color.bg.plugin.fcg.vertex.toobig = color.palette.lightgray
|
||||
|
||||
color.bg.plugin.fcg.edge.primary.direct = darkseagreen // TODO
|
||||
color.bg.plugin.fcg.edge.primary.direct = darkseagreen
|
||||
color.bg.plugin.fcg.edge.primary.direct.selected = color.palette.lightgreen
|
||||
color.bg.plugin.fcg.edge.primary.indirect = color.palette.lavender
|
||||
color.bg.plugin.fcg.edge.primary.indirect.selected = color.palette.silver
|
||||
|
|
|
@ -15,88 +15,26 @@
|
|||
*/
|
||||
package functioncalls.graph;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Ellipse2D.Double;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.JButton;
|
||||
|
||||
import docking.widgets.EmptyBorderButton;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import functioncalls.plugin.FcgOptions;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import generic.theme.Gui;
|
||||
import ghidra.graph.viewer.vertex.AbstractVisualVertex;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
import ghidra.base.graph.CircleWithLabelVertex;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.util.StringUtilities;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A {@link FunctionCallGraph} vertex
|
||||
*/
|
||||
public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvider {
|
||||
|
||||
//@formatter:off
|
||||
public static final Color DEFAULT_VERTEX_SHAPE_COLOR = new GColor("color.bg.plugin.fcg.vertex.default");
|
||||
private static final Color TOO_BIG_VERTEX_SHAPE_COLOR = new GColor("color.bg.plugin.fcg.vertex.toobig");
|
||||
//@formatter:on
|
||||
|
||||
public static final Icon NOT_ALLOWED_ICON = Icons.ERROR_ICON;
|
||||
private static final Icon EXPAND_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.EXPAND_ALL_ICON, 10, 10);
|
||||
private static final Icon COLLAPSE_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.COLLAPSE_ALL_ICON, 10, 10);
|
||||
|
||||
// higher numbered layers go on top
|
||||
private static final Integer VERTEX_SHAPE_LAYER = 100;
|
||||
private static final Integer TOGGLE_BUTTON_LAYER = 200;
|
||||
private static final Integer LABEL_LAYER = 300;
|
||||
|
||||
private static final int GAP = 2;
|
||||
private static final int VERTEX_SHAPE_SIZE = 50;
|
||||
|
||||
// TODO to be made an option in an upcoming ticket
|
||||
// based upon the default function name, plus some extra
|
||||
private static final int MAX_NAME_LENGTH = 30;
|
||||
public class FcgVertex extends CircleWithLabelVertex {
|
||||
|
||||
private Function function;
|
||||
|
||||
private JLayeredPane layeredPane;
|
||||
private JButton toggleInsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
private JButton toggleOutsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
private JLabel nameLabel = new GDLabel();
|
||||
private JLabel vertexImageLabel = new GDLabel();
|
||||
|
||||
private Double vertexShape;
|
||||
private Double compactShape;
|
||||
private Shape fullShape;
|
||||
|
||||
// these values are set after construction from external sources
|
||||
private boolean hasIncomingReferences;
|
||||
private boolean hasOutgoingReferences;
|
||||
private boolean tooManyIncomingReferences;
|
||||
private boolean tooManyOutgoingReferences;
|
||||
private boolean incomingExpanded;
|
||||
private boolean outgoingExpanded;
|
||||
|
||||
// set this to true to see borders around the components of this vertex
|
||||
private boolean useDebugBorders = false;
|
||||
|
||||
private Paint inPaint;
|
||||
private Paint outPaint;
|
||||
|
||||
private FcgLevel level;
|
||||
private FcgOptions options;
|
||||
|
||||
private FcgVertexShapeProvider fcgShapeProvider;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -107,252 +45,14 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
*/
|
||||
public FcgVertex(Function function, FcgLevel level,
|
||||
FcgVertexExpansionListener expansionListener, FcgOptions options) {
|
||||
super(function.getName());
|
||||
this.function = function;
|
||||
this.level = level;
|
||||
this.options = options;
|
||||
Objects.requireNonNull(expansionListener);
|
||||
|
||||
toggleInsButton.addActionListener(e -> {
|
||||
if (tooManyIncomingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleIncomingVertices(FcgVertex.this);
|
||||
});
|
||||
fcgShapeProvider = new FcgVertexShapeProvider(this, expansionListener);
|
||||
shapeProvider = fcgShapeProvider;
|
||||
|
||||
toggleOutsButton.addActionListener(e -> {
|
||||
if (tooManyOutgoingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleOutgoingVertices(FcgVertex.this);
|
||||
});
|
||||
|
||||
buildUi();
|
||||
|
||||
setTogglesVisible(false);
|
||||
}
|
||||
|
||||
private void createPaints() {
|
||||
|
||||
Color vertexShapeColor = getVertexShapeColor();
|
||||
|
||||
Color lightColor = vertexShapeColor;
|
||||
Color darkColor = Gui.darker(vertexShapeColor);
|
||||
Color darkestColor = Gui.darker(darkColor);
|
||||
int offset = 5 * level.getDistance();
|
||||
int half = VERTEX_SHAPE_SIZE / 2;
|
||||
int start = 0;
|
||||
int end = half + offset;
|
||||
|
||||
// paint top-down: dark to light for incoming; light to dark for outgoing
|
||||
inPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .2f, 1f }, new Color[] { darkestColor, darkColor, lightColor });
|
||||
|
||||
start = half - offset; // (offset + 10);
|
||||
end = VERTEX_SHAPE_SIZE;
|
||||
outPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .8f, 1f }, new Color[] { lightColor, darkColor, darkestColor });
|
||||
}
|
||||
|
||||
private void buildUi() {
|
||||
|
||||
createPaints();
|
||||
|
||||
// init the components
|
||||
boolean truncate = options.useTruncatedFunctionNames();
|
||||
String name = getName();
|
||||
if (truncate) {
|
||||
name = StringUtilities.trimMiddle(getName(), MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
nameLabel.setText(name);
|
||||
buildVertexShape();
|
||||
|
||||
// calculate the needed size
|
||||
layeredPane = new JLayeredPane();
|
||||
Border border = createDebugBorder(new LineBorder(Palette.GOLD, 1));
|
||||
layeredPane.setBorder(border);
|
||||
|
||||
updateLayeredPaneSize();
|
||||
|
||||
// layout the components
|
||||
addVertexShape();
|
||||
addToggleButtons();
|
||||
addNameLabel();
|
||||
|
||||
buildFullShape();
|
||||
}
|
||||
|
||||
private Border createDebugBorder(Border border) {
|
||||
if (useDebugBorders) {
|
||||
return border;
|
||||
}
|
||||
return BorderFactory.createEmptyBorder();
|
||||
}
|
||||
|
||||
private void buildFullShape() {
|
||||
|
||||
// Note: this method assumes all bounds have been set
|
||||
Area parent = new Area();
|
||||
|
||||
Area v = new Area(vertexShape);
|
||||
Area name = new Area(nameLabel.getBounds());
|
||||
parent.add(v);
|
||||
parent.add(name);
|
||||
|
||||
// for now, the buttons only appear on hover, but if we want to avoid clipping when
|
||||
// painting, we need to account for them in the shape's overall bounds
|
||||
Area in = new Area(toggleInsButton.getBounds());
|
||||
Area out = new Area(toggleOutsButton.getBounds());
|
||||
parent.add(in);
|
||||
parent.add(out);
|
||||
|
||||
fullShape = parent;
|
||||
}
|
||||
|
||||
private void updateLayeredPaneSize() {
|
||||
|
||||
//
|
||||
// The overall component size is the total width and height of all components, with any
|
||||
// spacing between them.
|
||||
//
|
||||
|
||||
Dimension shapeSize = vertexImageLabel.getPreferredSize();
|
||||
Dimension nameLabelSize = nameLabel.getPreferredSize();
|
||||
int height = shapeSize.height + GAP + nameLabelSize.height;
|
||||
|
||||
Dimension insSize = toggleInsButton.getPreferredSize();
|
||||
Dimension outsSize = toggleOutsButton.getPreferredSize();
|
||||
int buttonWidth = Math.max(insSize.width, outsSize.width);
|
||||
int offset = buttonWidth / 3; // overlap the vertex shape
|
||||
|
||||
int width = offset + shapeSize.width;
|
||||
width = Math.max(width, nameLabelSize.width);
|
||||
|
||||
layeredPane.setPreferredSize(new Dimension(width, height));
|
||||
}
|
||||
|
||||
private void buildVertexShape() {
|
||||
int w = VERTEX_SHAPE_SIZE;
|
||||
int h = VERTEX_SHAPE_SIZE;
|
||||
Double circle = new Ellipse2D.Double(0, 0, w, h);
|
||||
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
g2.setColor(getVertexShapeColor());
|
||||
}
|
||||
else if (direction.isIn()) {
|
||||
g2.setPaint(inPaint);
|
||||
}
|
||||
else {
|
||||
g2.setPaint(outPaint);
|
||||
}
|
||||
|
||||
g2.fill(circle);
|
||||
|
||||
g2.dispose();
|
||||
|
||||
vertexShape = circle;
|
||||
compactShape = (Double) vertexShape.clone();
|
||||
vertexImageLabel.setIcon(new ImageIcon(image));
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Palette.PINK, 1));
|
||||
vertexImageLabel.setBorder(border);
|
||||
}
|
||||
|
||||
private Color getVertexShapeColor() {
|
||||
|
||||
if (isInDirection() && tooManyIncomingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
if (isOutDirection() && tooManyOutgoingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
return DEFAULT_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
private boolean isInDirection() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isIn = direction.isIn() || direction.isSource();
|
||||
return isIn;
|
||||
}
|
||||
|
||||
private boolean isOutDirection() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isOut = direction.isOut() || direction.isSource();
|
||||
return isOut;
|
||||
}
|
||||
|
||||
private void addVertexShape() {
|
||||
|
||||
Dimension parentSize = layeredPane.getPreferredSize();
|
||||
Dimension size = vertexImageLabel.getPreferredSize();
|
||||
|
||||
// centered
|
||||
int x = (parentSize.width / 2) - (size.width / 2);
|
||||
int y = 0;
|
||||
|
||||
vertexImageLabel.setBounds(x, y, size.width, size.height);
|
||||
Dimension shapeSize = vertexShape.getBounds().getSize();
|
||||
|
||||
// setFrame() will make sure the shape's x,y values are where they need to be
|
||||
// for the later 'full shape' creation
|
||||
vertexShape.setFrame(x, y, shapeSize.width, shapeSize.height);
|
||||
layeredPane.add(vertexImageLabel, VERTEX_SHAPE_LAYER);
|
||||
}
|
||||
|
||||
private void addNameLabel() {
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Palette.GREEN, 1));
|
||||
nameLabel.setBorder(border);
|
||||
|
||||
// assume the vertex label has been bounded
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = nameLabel.getPreferredSize();
|
||||
|
||||
// bottom, centered under the shape
|
||||
int x = (parentBounds.x + (parentBounds.width / 2)) - (size.width / 2);
|
||||
int y = parentBounds.y + parentBounds.height + GAP;
|
||||
nameLabel.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(nameLabel, LABEL_LAYER);
|
||||
}
|
||||
|
||||
private void addToggleButtons() {
|
||||
|
||||
// hide the button background
|
||||
toggleInsButton.setBackground(Palette.NO_COLOR);
|
||||
toggleOutsButton.setBackground(Palette.NO_COLOR);
|
||||
|
||||
// This is needed for Flat Dark theme to work correctly, due to the fact that it wants to
|
||||
// paint its parent background when the button is opaque. The parent background will get
|
||||
// painted over any items that lie between the button and the parent.
|
||||
toggleInsButton.setOpaque(false);
|
||||
toggleOutsButton.setOpaque(false);
|
||||
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = toggleInsButton.getPreferredSize();
|
||||
|
||||
// upper toggle; upper-left
|
||||
int x = parentBounds.x - (size.width / 3);
|
||||
int y = 0;
|
||||
toggleInsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleInsButton, TOGGLE_BUTTON_LAYER);
|
||||
|
||||
// lower toggle; lower-left, lined-up with the vertex shape
|
||||
size = toggleOutsButton.getPreferredSize();
|
||||
Dimension vertexSize = parentBounds.getSize();
|
||||
y = vertexSize.height - size.height;
|
||||
toggleOutsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleOutsButton, TOGGLE_BUTTON_LAYER);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return function.getName();
|
||||
}
|
||||
|
||||
public Function getFunction() {
|
||||
|
@ -363,6 +63,10 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
return function.getEntryPoint();
|
||||
}
|
||||
|
||||
public FcgOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public FcgLevel getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
@ -375,105 +79,35 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
return level.getDirection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHovered(boolean hovered) {
|
||||
super.setHovered(hovered);
|
||||
fcgShapeProvider.setTogglesVisible(hovered);
|
||||
}
|
||||
|
||||
public JButton getIncomingToggleButton() {
|
||||
return toggleInsButton;
|
||||
return shapeProvider.getIncomingToggleButton();
|
||||
}
|
||||
|
||||
public JButton getOutgoingToggleButton() {
|
||||
return toggleOutsButton;
|
||||
return shapeProvider.getOutgoingToggleButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the incoming direction
|
||||
* Sets whether this vertex has any incoming references
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the incoming direction
|
||||
* @param hasIncoming true if this vertex has any incoming references
|
||||
*/
|
||||
public void setIncomingExpanded(boolean setExpanded) {
|
||||
|
||||
validateIncomingExpandedState(setExpanded);
|
||||
|
||||
this.incomingExpanded = setExpanded;
|
||||
toggleInsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " incoming edges");
|
||||
}
|
||||
|
||||
private void validateOutgoingExpandedState(boolean isExpanding) {
|
||||
if (isExpanding) {
|
||||
if (!canExpandOutgoingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isOutgoingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIncomingExpandedState(boolean expanding) {
|
||||
|
||||
if (expanding) {
|
||||
if (!canExpandIncomingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isIncomingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
public void setHasIncomingReferences(boolean hasIncoming) {
|
||||
fcgShapeProvider.setHasIncomingReferences(hasIncoming);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the incoming direction
|
||||
* Sets whether this vertex has any outgoing references
|
||||
* @param hasOutgoing true if this vertex has any outgoing references
|
||||
*/
|
||||
public boolean isIncomingExpanded() {
|
||||
return incomingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public void setOutgoingExpanded(boolean setExpanded) {
|
||||
|
||||
validateOutgoingExpandedState(setExpanded);
|
||||
|
||||
this.outgoingExpanded = setExpanded;
|
||||
toggleOutsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " outgoing edges");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public boolean isOutgoingExpanded() {
|
||||
return outgoingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex is fully expanded in its current direction
|
||||
*
|
||||
* @return whether this vertex is fully expanded in its current direction
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return isIncomingExpanded() && isOutgoingExpanded();
|
||||
}
|
||||
if (direction.isIn()) {
|
||||
return isIncomingExpanded();
|
||||
}
|
||||
return isOutgoingExpanded();
|
||||
public void setHasOutgoingReferences(boolean hasOutgoing) {
|
||||
fcgShapeProvider.setHasOutgoingReferences(hasOutgoing);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -484,10 +118,7 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyIncomingReferences(boolean tooMany) {
|
||||
this.tooManyIncomingReferences = tooMany;
|
||||
toggleInsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleInsButton.setToolTipText("Too many incoming references to show");
|
||||
buildUi();
|
||||
fcgShapeProvider.setTooManyIncomingReferences(tooMany);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -498,10 +129,7 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyOutgoingReferences(boolean tooMany) {
|
||||
this.tooManyOutgoingReferences = tooMany;
|
||||
toggleOutsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleOutsButton.setToolTipText("Too many outgoing references to show");
|
||||
buildUi();
|
||||
fcgShapeProvider.setTooManyOutgoingReferences(tooMany);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -512,7 +140,7 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyIncomingReferences() {
|
||||
return tooManyIncomingReferences;
|
||||
return fcgShapeProvider.hasTooManyIncomingReferences();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -523,7 +151,34 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyOutgoingReferences() {
|
||||
return tooManyOutgoingReferences;
|
||||
return fcgShapeProvider.hasTooManyOutgoingReferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public boolean isIncomingExpanded() {
|
||||
return fcgShapeProvider.isIncomingExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public boolean isOutgoingExpanded() {
|
||||
return fcgShapeProvider.isOutgoingExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex is fully expanded in its current direction
|
||||
*
|
||||
* @return whether this vertex is fully expanded in its current direction
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
return fcgShapeProvider.isExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -533,76 +188,33 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
* @return true if this vertex can be expanded
|
||||
*/
|
||||
public boolean canExpand() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return canExpandIncomingReferences() || canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
if (direction.isIn()) {
|
||||
return canExpandIncomingReferences();
|
||||
}
|
||||
|
||||
return canExpandOutgoingReferences();
|
||||
return fcgShapeProvider.canExpand();
|
||||
}
|
||||
|
||||
public boolean canExpandIncomingReferences() {
|
||||
return hasIncomingReferences && !tooManyIncomingReferences && !incomingExpanded;
|
||||
return fcgShapeProvider.canExpandIncomingReferences();
|
||||
}
|
||||
|
||||
public boolean canExpandOutgoingReferences() {
|
||||
return hasOutgoingReferences && !tooManyOutgoingReferences && !outgoingExpanded;
|
||||
return fcgShapeProvider.canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any incoming references
|
||||
* Sets to true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @param hasIncoming true if this vertex has any incoming references
|
||||
* @param setExpanded true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public void setHasIncomingReferences(boolean hasIncoming) {
|
||||
this.hasIncomingReferences = hasIncoming;
|
||||
public void setIncomingExpanded(boolean setExpanded) {
|
||||
fcgShapeProvider.setIncomingExpanded(setExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any outgoing references
|
||||
* Sets to true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @param hasOutgoing true if this vertex has any outgoing references
|
||||
* @param setExpanded true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
|
||||
public void setHasOutgoingReferences(boolean hasOutgoing) {
|
||||
this.hasOutgoingReferences = hasOutgoing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHovered(boolean hovered) {
|
||||
super.setHovered(hovered);
|
||||
|
||||
setTogglesVisible(hovered);
|
||||
}
|
||||
|
||||
private void setTogglesVisible(boolean visible) {
|
||||
|
||||
boolean isIn = isInDirection();
|
||||
boolean turnOn = isIn && hasIncomingReferences && visible;
|
||||
toggleInsButton.setVisible(turnOn);
|
||||
|
||||
boolean isOut = isOutDirection();
|
||||
turnOn = isOut && hasOutgoingReferences && visible;
|
||||
toggleOutsButton.setVisible(turnOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return layeredPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return compactShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getFullShape() {
|
||||
return fullShape;
|
||||
public void setOutgoingExpanded(boolean setExpanded) {
|
||||
fcgShapeProvider.setOutgoingExpanded(setExpanded);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -633,6 +245,7 @@ public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvid
|
|||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// nothing to do
|
||||
this.function = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,338 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import functioncalls.plugin.FcgOptions;
|
||||
import functioncalls.plugin.FunctionCallGraphPlugin;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.Gui;
|
||||
import ghidra.base.graph.CircleWithLabelVertexShapeProvider;
|
||||
import ghidra.util.StringUtilities;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* A vertex shape provider for the {@link FunctionCallGraphPlugin}.
|
||||
*/
|
||||
public class FcgVertexShapeProvider extends CircleWithLabelVertexShapeProvider {
|
||||
|
||||
//@formatter:off
|
||||
private static final Color TOO_BIG_VERTEX_SHAPE_COLOR = new GColor("color.bg.plugin.fcg.vertex.toobig");
|
||||
//@formatter:on
|
||||
|
||||
public static final Icon NOT_ALLOWED_ICON = Icons.ERROR_ICON;
|
||||
|
||||
private FcgVertex vertex;
|
||||
|
||||
private Paint inPaint;
|
||||
private Paint outPaint;
|
||||
|
||||
// these values are set after construction from external sources
|
||||
private boolean hasIncomingReferences;
|
||||
private boolean hasOutgoingReferences;
|
||||
private boolean tooManyIncomingReferences;
|
||||
private boolean tooManyOutgoingReferences;
|
||||
|
||||
public FcgVertexShapeProvider(FcgVertex vertex,
|
||||
FcgVertexExpansionListener expansionListener) {
|
||||
super(vertex.getName());
|
||||
this.vertex = vertex;
|
||||
|
||||
Objects.requireNonNull(expansionListener);
|
||||
|
||||
toggleInsButton.addActionListener(e -> {
|
||||
if (tooManyIncomingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleIncomingVertices(vertex);
|
||||
});
|
||||
|
||||
toggleOutsButton.addActionListener(e -> {
|
||||
if (tooManyOutgoingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleOutgoingVertices(vertex);
|
||||
});
|
||||
|
||||
buildUi();
|
||||
setTogglesVisible(false);
|
||||
}
|
||||
|
||||
private void createPaints() {
|
||||
|
||||
Color vertexShapeColor = getDefaultVertexShapeColor();
|
||||
|
||||
Color lightColor = vertexShapeColor;
|
||||
Color darkColor = Gui.darker(vertexShapeColor);
|
||||
Color darkestColor = Gui.darker(darkColor);
|
||||
FcgLevel level = vertex.getLevel();
|
||||
int offset = 5 * level.getDistance();
|
||||
int half = VERTEX_SHAPE_SIZE / 2;
|
||||
int start = 0;
|
||||
int end = half + offset;
|
||||
|
||||
// paint top-down: dark to light for incoming; light to dark for outgoing
|
||||
inPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .2f, 1f }, new Color[] { darkestColor, darkColor, lightColor });
|
||||
|
||||
start = half - offset; // (offset + 10);
|
||||
end = VERTEX_SHAPE_SIZE;
|
||||
outPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .8f, 1f }, new Color[] { lightColor, darkColor, darkestColor });
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildUi() {
|
||||
if (vertex == null) {
|
||||
return; // still being constructed
|
||||
}
|
||||
createPaints();
|
||||
super.buildUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String generateLabelText() {
|
||||
FcgOptions options = vertex.getOptions();
|
||||
boolean optionsUseTruncatedName = options.useTruncatedFunctionNames();
|
||||
String name = getName();
|
||||
if (optionsUseTruncatedName) {
|
||||
name = StringUtilities.trimMiddle(getName(), MAX_NAME_LENGTH);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Paint getVertexShapePaint() {
|
||||
FcgLevel level = vertex.getLevel();
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return getDefaultVertexShapeColor();
|
||||
}
|
||||
else if (direction.isIn()) {
|
||||
return inPaint;
|
||||
}
|
||||
else {
|
||||
return outPaint;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Color getDefaultVertexShapeColor() {
|
||||
|
||||
if (isInDirection() && tooManyIncomingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
if (isOutDirection() && tooManyOutgoingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
return DEFAULT_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
private boolean isInDirection() {
|
||||
FcgLevel level = vertex.getLevel();
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isIn = direction.isIn() || direction.isSource();
|
||||
return isIn;
|
||||
}
|
||||
|
||||
private boolean isOutDirection() {
|
||||
FcgLevel level = vertex.getLevel();
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isOut = direction.isOut() || direction.isSource();
|
||||
return isOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the incoming direction
|
||||
* @param setExpanded true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
@Override
|
||||
public void setIncomingExpanded(boolean setExpanded) {
|
||||
validateIncomingExpandedState(setExpanded);
|
||||
super.setIncomingExpanded(setExpanded);
|
||||
}
|
||||
|
||||
private void validateOutgoingExpandedState(boolean isExpanding) {
|
||||
if (isExpanding) {
|
||||
if (!canExpandOutgoingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isOutgoingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIncomingExpandedState(boolean expanding) {
|
||||
|
||||
if (expanding) {
|
||||
if (!canExpandIncomingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isIncomingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the outgoing direction
|
||||
* @param setExpanded true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
@Override
|
||||
public void setOutgoingExpanded(boolean setExpanded) {
|
||||
validateOutgoingExpandedState(setExpanded);
|
||||
super.setOutgoingExpanded(setExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex is fully expanded in its current direction
|
||||
* @return whether this vertex is fully expanded in its current direction
|
||||
*/
|
||||
@Override
|
||||
public boolean isExpanded() {
|
||||
FcgLevel level = vertex.getLevel();
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return isIncomingExpanded() && isOutgoingExpanded();
|
||||
}
|
||||
if (direction.isIn()) {
|
||||
return isIncomingExpanded();
|
||||
}
|
||||
return isOutgoingExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has too many incoming references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyIncomingReferences(boolean tooMany) {
|
||||
this.tooManyIncomingReferences = tooMany;
|
||||
toggleInsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleInsButton.setToolTipText("Too many incoming references to show");
|
||||
buildUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has too many outgoing references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyOutgoingReferences(boolean tooMany) {
|
||||
this.tooManyOutgoingReferences = tooMany;
|
||||
toggleOutsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleOutsButton.setToolTipText("Too many outgoing references to show");
|
||||
buildUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex has too many incoming references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyIncomingReferences() {
|
||||
return tooManyIncomingReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex has too many outgoing references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyOutgoingReferences() {
|
||||
return tooManyOutgoingReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex can expand itself in its current direction, or in either
|
||||
* direction if this is a source vertex
|
||||
*
|
||||
* @return true if this vertex can be expanded
|
||||
*/
|
||||
@Override
|
||||
public boolean canExpand() {
|
||||
FcgLevel level = vertex.getLevel();
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return canExpandIncomingReferences() || canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
if (direction.isIn()) {
|
||||
return canExpandIncomingReferences();
|
||||
}
|
||||
|
||||
return canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
public boolean canExpandIncomingReferences() {
|
||||
return hasIncomingReferences && !tooManyIncomingReferences && !incomingExpanded;
|
||||
}
|
||||
|
||||
public boolean canExpandOutgoingReferences() {
|
||||
return hasOutgoingReferences && !tooManyOutgoingReferences && !outgoingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any incoming references
|
||||
*
|
||||
* @param hasIncoming true if this vertex has any incoming references
|
||||
*/
|
||||
public void setHasIncomingReferences(boolean hasIncoming) {
|
||||
this.hasIncomingReferences = hasIncoming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any outgoing references
|
||||
* @param hasOutgoing true if this vertex has any outgoing references
|
||||
*/
|
||||
public void setHasOutgoingReferences(boolean hasOutgoing) {
|
||||
this.hasOutgoingReferences = hasOutgoing;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTogglesVisible(boolean visible) {
|
||||
|
||||
boolean isIn = isInDirection();
|
||||
boolean turnOn = isIn && hasIncomingReferences && visible;
|
||||
toggleInsButton.setVisible(turnOn);
|
||||
|
||||
boolean isOut = isOutDirection();
|
||||
turnOn = isOut && hasOutgoingReferences && visible;
|
||||
toggleOutsButton.setVisible(turnOn);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ import functioncalls.graph.renderer.FcgEdgePaintTransformer;
|
|||
import functioncalls.graph.renderer.FcgVertexPaintTransformer;
|
||||
import functioncalls.plugin.FunctionCallGraphPlugin;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.base.graph.CircleWithLabelVertexShapeProvider;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.graph.viewer.edge.VisualEdgeRenderer;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
|
@ -33,7 +34,8 @@ import ghidra.graph.viewer.vertex.VisualVertexRenderer;
|
|||
public class FcgComponent extends GraphComponent<FcgVertex, FcgEdge, FunctionCallGraph> {
|
||||
|
||||
private FcgVertexPaintTransformer vertexPaintTransformer =
|
||||
new FcgVertexPaintTransformer(FcgVertex.DEFAULT_VERTEX_SHAPE_COLOR);
|
||||
new FcgVertexPaintTransformer(
|
||||
CircleWithLabelVertexShapeProvider.DEFAULT_VERTEX_SHAPE_COLOR);
|
||||
|
||||
private FcgEdgePaintTransformer edgePaintTransformer =
|
||||
new FcgEdgePaintTransformer(new GColor("color.bg.plugin.fcg.edge.primary.direct"),
|
||||
|
|
|
@ -37,6 +37,7 @@ public abstract class AbstractVisualVertex implements VisualVertex {
|
|||
this.focused = focused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocused() {
|
||||
return focused;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue