package rescuecore2.misc.gui;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.BorderFactory;
import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.JPopupMenu;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Color;
import java.awt.Shape;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.BasicStroke;
import java.awt.Rectangle;
import java.awt.Point;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.misc.geometry.Line2D;
import rescuecore2.misc.geometry.GeometryTools2D;
//import rescuecore2.log.Logger;
/**
A JFrame that can be used to debug geometric shape operations. When {@link #enable enabled} this frame will block whenever a show method is called until the user clicks on a button to continue. The "step" button will cause the show method to return and leave the frame visible and activated. The "continue" button will hide and {@link #deactivate} the frame so that further calls to show will return immediately.
*/
public class ShapeDebugFrame extends JFrame {
private static final int DISPLAY_WIDTH = 500;
private static final int DISPLAY_HEIGHT = 500;
private static final int LEGEND_WIDTH = 500;
private static final int LEGEND_HEIGHT = 500;
private static final double ZOOM_TO_OFFSET = 0.1;
private static final double ZOOM_TO_WIDTH_FACTOR = 1.2;
private JLabel title;
private JButton step;
private JButton cont;
private ShapeViewer viewer;
private ShapeInfoLegend legend;
private CyclicBarrier barrier;
private boolean enabled;
private Collection<? extends ShapeInfo> background;
private boolean backgroundEnabled;
private JPopupMenu menu;
private boolean autoZoom;
/**
Construct a new ShapeDebugFrame.
*/
public ShapeDebugFrame() {
barrier = new CyclicBarrier(2);
viewer = new ShapeViewer();
legend = new ShapeInfoLegend();
step = new JButton("Step");
cont = new JButton("Continue");
title = new JLabel();
add(title, BorderLayout.NORTH);
add(viewer, BorderLayout.CENTER);
JPanel buttons = new JPanel(new GridLayout(1, 2));
buttons.add(step);
buttons.add(cont);
add(buttons, BorderLayout.SOUTH);
add(legend, BorderLayout.EAST);
legend.setBorder(BorderFactory.createTitledBorder("Legend"));
viewer.setBorder(BorderFactory.createTitledBorder("Shapes"));
step.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
barrier.await();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException ex) {
// Ignore
}
catch (BrokenBarrierException ex) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
}
});
cont.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
deactivate();
barrier.await();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException ex) {
// Ignore
}
catch (BrokenBarrierException ex) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
}
});
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
try {
barrier.await();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException ex) {
// Ignore
}
catch (BrokenBarrierException ex) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
}
});
MouseAdapter m = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
menu.show(e.getComponent(), e.getPoint().x, e.getPoint().y);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
menu.show(e.getComponent(), e.getPoint().x, e.getPoint().y);
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
menu.show(e.getComponent(), e.getPoint().x, e.getPoint().y);
}
}
};
addMouseListener(m);
viewer.addMouseListener(m);
enabled = true;
clearBackground();
backgroundEnabled = true;
autoZoom = true;
pack();
menu = new JPopupMenu();
menu.add(new BackgroundAction());
}
/**
Set the "background" shapes. These will be drawn on every invocation of show.
@param back The new background shapes. This should not be null.
*/
public void setBackground(Collection<? extends ShapeInfo> back) {
background = back;
if (background == null) {
clearBackground();
}
}
/**
Clear the "background" shapes.
*/
public void clearBackground() {
background = new ArrayList<ShapeInfo>();
}
/**
Set whether the background is drawn or not.
@param b True if the background should be drawn, false otherwise.
*/
public void setBackgroundEnabled(boolean b) {
backgroundEnabled = b;
}
/**
Set whether autozoom is enabled.
@param b True if autozoom should be enabled, false otherwise.
*/
public void setAutozoomEnabled(boolean b) {
autoZoom = b;
}
/**
Show a set of ShapeInfo objects. If this frame is enabled then this method will block until the user clicks a button to continue.
@param description A description.
@param shapes A list of collections of ShapeInfo objects.
*/
public void show(String description, Collection<? extends ShapeInfo>... shapes) {
List<ShapeInfo> all = new ArrayList<ShapeInfo>();
for (Collection<? extends ShapeInfo> next : shapes) {
all.addAll(next);
}
show(description, all);
}
/**
Show a set of ShapeInfo objects. If this frame is enabled then this method will block until the user clicks a button to continue.
@param description A description.
@param shapes An array of ShapeInfo objects.
*/
public void show(String description, ShapeInfo... shapes) {
show(description, Arrays.asList(shapes));
}
/**
Show a set of ShapeInfo objects. If this frame is enabled then this method will block until the user clicks a button to continue.
@param description A description.
@param shapes A collection of ShapeInfo objects.
*/
public void show(final String description, final Collection<ShapeInfo> shapes) {
if (!enabled) {
return;
}
final List<ShapeInfo> allShapes = new ArrayList<ShapeInfo>(shapes);
setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (description == null) {
title.setText("");
}
else {
title.setText(description);
}
legend.setShapes(allShapes);
viewer.setShapes(allShapes);
if (autoZoom) {
viewer.zoomTo(shapes);
}
repaint();
}
});
try {
barrier.await();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException e) {
// Ignore
}
catch (BrokenBarrierException e) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
}
/**
Activate this frame. Future calls to show will block until the user clicks a button.
*/
public void activate() {
enabled = true;
}
/**
Deactivate and hides this frame. Future calls to show will return immediately.
*/
public void deactivate() {
enabled = false;
setVisible(false);
}
private Rectangle2D getBounds(Collection<? extends ShapeInfo>... shapes) {
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for (Collection<? extends ShapeInfo> c : shapes) {
if (c != null) {
for (ShapeInfo next : c) {
Shape bounds = next.getBoundsShape();
if (bounds != null) {
Rectangle2D rect = bounds.getBounds2D();
minX = Math.min(minX, rect.getMinX());
maxX = Math.max(maxX, rect.getMaxX());
minY = Math.min(minY, rect.getMinY());
maxY = Math.max(maxY, rect.getMaxY());
}
java.awt.geom.Point2D point = next.getBoundsPoint();
if (point != null) {
minX = Math.min(minX, point.getX());
maxX = Math.max(maxX, point.getX());
minY = Math.min(minY, point.getY());
maxY = Math.max(maxY, point.getY());
}
}
}
}
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
}
private class ShapeViewer extends JComponent {
private List<ShapeInfo> shapes;
private ScreenTransform transform;
private PanZoomListener panZoom;
private Map<Shape, ShapeInfo> drawnShapes;
/**
Create a ShapeViewer.
*/
public ShapeViewer() {
panZoom = new PanZoomListener(this);
drawnShapes = new HashMap<Shape, ShapeInfo>();
shapes = new ArrayList<ShapeInfo>();
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
Insets insets = getInsets();
Point p = new Point(e.getPoint());
p.translate(-insets.left, -insets.top);
List<ShapeInfo> s = getShapesAtPoint(p);
for (ShapeInfo next : s) {
System.out.println(next.getObject());
}
}
}
});
}
@Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
drawnShapes.clear();
if (shapes.isEmpty()) {
return;
}
Insets insets = getInsets();
int width = getWidth() - insets.left - insets.right;
int height = getHeight() - insets.top - insets.bottom;
transform.rescale(width, height);
// Logger.debug("View bounds: " + transform.getViewBounds());
for (ShapeInfo next : shapes) {
boolean visible = transform.isInView(next.getBoundsShape()) || transform.isInView(next.getBoundsPoint());
if (visible) {
Graphics g = graphics.create(insets.left, insets.top, width, height);
Shape shape = next.paint((Graphics2D)g, transform);
if (shape != null) {
drawnShapes.put(shape, next);
}
}
// else {
// Logger.debug("Pruned " + next);
// Logger.debug("Shape bounds: " + next.getBoundsShape());
// Logger.debug("Point bounds: " + next.getBoundsPoint());
// }
}
if (backgroundEnabled) {
for (ShapeInfo next : background) {
boolean visible = transform.isInView(next.getBoundsShape()) || transform.isInView(next.getBoundsPoint());
if (visible) {
Graphics g = graphics.create(insets.left, insets.top, width, height);
Shape shape = next.paint((Graphics2D)g, transform);
if (shape != null) {
drawnShapes.put(shape, next);
}
}
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(DISPLAY_WIDTH, DISPLAY_HEIGHT);
}
/**
Set the list of ShapeInfo objects to draw.
@param s The new list of ShapeInfo objects.
*/
@SuppressWarnings("unchecked")
public void setShapes(Collection<ShapeInfo> s) {
shapes.clear();
shapes.addAll(s);
Rectangle2D bounds = ShapeDebugFrame.this.getBounds(shapes, backgroundEnabled ? background : null);
transform = new ScreenTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
panZoom.setScreenTransform(transform);
repaint();
}
/**
Zoom to show a set of ShapeInfo objects.
@param zoom The set of objects to zoom to.
*/
@SuppressWarnings("unchecked")
public void zoomTo(Collection<ShapeInfo> zoom) {
Rectangle2D bounds = ShapeDebugFrame.this.getBounds(zoom);
// Increase the bounds by 10%
double newX = bounds.getMinX() - (bounds.getWidth() * ZOOM_TO_OFFSET);
double newY = bounds.getMinY() - (bounds.getHeight() * ZOOM_TO_OFFSET);
double newWidth = bounds.getWidth() * ZOOM_TO_WIDTH_FACTOR;
double newHeight = bounds.getHeight() * ZOOM_TO_WIDTH_FACTOR;
bounds.setRect(newX, newY, newWidth, newHeight);
transform.show(bounds);
repaint();
}
private List<ShapeInfo> getShapesAtPoint(Point p) {
List<ShapeInfo> result = new ArrayList<ShapeInfo>();
for (Map.Entry<Shape, ShapeInfo> next : drawnShapes.entrySet()) {
Shape shape = next.getKey();
if (shape.contains(p)) {
result.add(next.getValue());
}
}
return result;
}
}
/**
The legend for the debug frame.
*/
private class ShapeInfoLegend extends JComponent {
private static final int ROW_OFFSET = 5;
private static final int X_INDENT = 5;
private static final int ENTRY_WIDTH = 50;
private static final int ENTRY_HEIGHT = 9;
private List<ShapeInfo> shapes;
ShapeInfoLegend() {
shapes = new ArrayList<ShapeInfo>();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(LEGEND_WIDTH, LEGEND_HEIGHT);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (shapes.isEmpty()) {
return;
}
Set<String> seen = new HashSet<String>();
FontMetrics metrics = g.getFontMetrics();
int height = metrics.getHeight();
int y = getInsets().top;
int x = getInsets().left + X_INDENT;
if (backgroundEnabled) {
for (ShapeInfo next : background) {
String name = next.getName();
if (name == null || "".equals(name)) {
continue;
}
if (seen.contains(name)) {
continue;
}
seen.add(name);
next.paintLegend((Graphics2D)g.create(x, y + (height / 2) - (ENTRY_HEIGHT / 2), ENTRY_WIDTH, ENTRY_HEIGHT), ENTRY_WIDTH, ENTRY_HEIGHT);
g.setColor(Color.black);
g.drawString(next.getName(), x + ENTRY_WIDTH + X_INDENT, y + metrics.getAscent());
y += height + ROW_OFFSET;
}
}
for (ShapeInfo next : shapes) {
String name = next.getName();
if (name == null || "".equals(name)) {
continue;
}
if (seen.contains(name)) {
continue;
}
seen.add(name);
next.paintLegend((Graphics2D)g.create(x, y + (height / 2) - (ENTRY_HEIGHT / 2), ENTRY_WIDTH, ENTRY_HEIGHT), ENTRY_WIDTH, ENTRY_HEIGHT);
g.setColor(Color.black);
g.drawString(next.getName(), x + ENTRY_WIDTH + X_INDENT, y + metrics.getAscent());
y += height + ROW_OFFSET;
}
}
/**
Set the list of shapes.
@param s The new list of shapes.
*/
public void setShapes(Collection<ShapeInfo> s) {
shapes.clear();
shapes.addAll(s);
repaint();
}
}
/**
This class captures information about a shape that should be displayed on-screen.
*/
public abstract static class ShapeInfo {
/** The name of the shape. */
protected String name;
/** The object this shape represents. */
private Object object;
/**
Construct a new ShapeInfo object.
@param object The object this shape represents.
@param name The name of the shape.
*/
protected ShapeInfo(Object object, String name) {
this.object = object;
this.name = name;
}
/**
Paint this ShapeInfo on a Graphics2D object.
@param g The Graphics2D to draw on.
@param transform The current screen transform.
@return A shape for mouseover detection.
*/
public abstract Shape paint(Graphics2D g, ScreenTransform transform);
/**
Paint this ShapeInfo on a the legend.
@param g The Graphics2D to draw on.
@param width The available width.
@param height The available height.
*/
public abstract void paintLegend(Graphics2D g, int width, int height);
/**
Get the object this shape represents.
@return The object.
*/
public Object getObject() {
return object;
}
/**
Get the name of this shape info.
@return The name.
*/
public String getName() {
return name;
}
/**
Get the bounding shape of this shape.
@return The bounding shape or null if this shape represents a point.
*/
public abstract Shape getBoundsShape();
/**
Get the point representing this shape.
@return The shape point or null if this shape does not represent a point.
*/
public abstract java.awt.geom.Point2D getBoundsPoint();
}
/**
A ShapeInfo that encapsulates an awt Shape.
*/
public static class AWTShapeInfo extends ShapeInfo {
private Shape shape;
private boolean fill;
private Color colour;
private Rectangle2D bounds;
/**
Construct a new AWTShapeInfo object.
@param shape The shape to display.
@param name The name of the shape.
@param colour The colour of the shape.
@param fill Whether to fill the shape.
*/
public AWTShapeInfo(Shape shape, String name, Color colour, boolean fill) {
super(shape, name);
this.shape = shape;
this.fill = fill;
this.colour = colour;
if (shape != null) {
bounds = shape.getBounds2D();
}
}
@Override
public Shape paint(Graphics2D g, ScreenTransform transform) {
if (shape == null || (shape instanceof Area && ((Area)shape).isEmpty())) {
return null;
}
Path2D path = new Path2D.Double();
PathIterator pi = shape.getPathIterator(null);
// CHECKSTYLE:OFF:MagicNumber
double[] d = new double[6];
while (!pi.isDone()) {
int type = pi.currentSegment(d);
switch (type) {
case PathIterator.SEG_MOVETO:
path.moveTo(transform.xToScreen(d[0]), transform.yToScreen(d[1]));
break;
case PathIterator.SEG_LINETO:
path.lineTo(transform.xToScreen(d[0]), transform.yToScreen(d[1]));
break;
case PathIterator.SEG_CLOSE:
path.closePath();
break;
case PathIterator.SEG_QUADTO:
path.quadTo(transform.xToScreen(d[0]), transform.yToScreen(d[1]), transform.xToScreen(d[2]), transform.yToScreen(d[3]));
break;
case PathIterator.SEG_CUBICTO:
path.curveTo(transform.xToScreen(d[0]), transform.yToScreen(d[1]), transform.xToScreen(d[2]), transform.yToScreen(d[3]), transform.xToScreen(d[4]), transform.yToScreen(d[5]));
break;
default:
throw new RuntimeException("Unexpected PathIterator constant: " + type);
}
pi.next();
}
// CHECKSTYLE:ON:MagicNumber
g.setColor(colour);
if (fill) {
g.fill(path);
}
else {
g.draw(path);
}
return path.createTransformedShape(null);
}
@Override
public void paintLegend(Graphics2D g, int width, int height) {
if (shape == null) {
return;
}
g.setColor(colour);
if (fill) {
g.fillRect(0, 0, width, height);
}
else {
g.drawRect(0, 0, width - 1, height - 1);
}
}
@Override
public Rectangle2D getBoundsShape() {
return bounds;
}
@Override
public java.awt.geom.Point2D getBoundsPoint() {
return null;
}
}
/**
A ShapeInfo that encapsulates a Point2D.
*/
public static class Point2DShapeInfo extends ShapeInfo {
private static final int SIZE = 3;
private Point2D point;
private java.awt.geom.Point2D boundsPoint;
private boolean square;
private Color colour;
/**
Construct a new Point2DShapeInfo object.
@param point The point to display.
@param name The name of the point.
@param colour The colour of the point.
@param square Whether to draw as a square or a cross. If false then a cross will be drawn.
*/
public Point2DShapeInfo(Point2D point, String name, Color colour, boolean square) {
super(point, name);
this.point = point;
this.square = square;
this.colour = colour;
if (point != null) {
boundsPoint = new java.awt.geom.Point2D.Double(point.getX(), point.getY());
}
}
@Override
public Shape paint(Graphics2D g, ScreenTransform transform) {
if (point == null) {
return null;
}
int x = transform.xToScreen(point.getX());
int y = transform.yToScreen(point.getY());
g.setColor(colour);
if (square) {
g.fillRect(x - SIZE, y - SIZE, SIZE * 2, SIZE * 2);
}
else {
g.drawLine(x - SIZE, y - SIZE, x + SIZE, y + SIZE);
g.drawLine(x - SIZE, y + SIZE, x + SIZE, y - SIZE);
}
// Logger.debug("Painting point " + name + " (" + point + ") at " + x + ", " + y);
return new Rectangle(x - SIZE, y - SIZE, SIZE * 2, SIZE * 2);
}
@Override
public void paintLegend(Graphics2D g, int width, int height) {
if (point == null) {
return;
}
g.setColor(colour);
int x = (width / 2);
int y = (height / 2);
if (square) {
g.fillRect(x - SIZE, y - SIZE, SIZE * 2, SIZE * 2);
}
else {
g.drawLine(x - SIZE, y - SIZE, x + SIZE, y + SIZE);
g.drawLine(x - SIZE, y + SIZE, x + SIZE, y - SIZE);
}
}
@Override
public Shape getBoundsShape() {
return null;
}
@Override
public java.awt.geom.Point2D getBoundsPoint() {
return boundsPoint;
}
}
/**
A ShapeInfo that encapsulates a Line2D.
*/
public static class Line2DShapeInfo extends ShapeInfo {
private static final int SIZE = 2;
private static final BasicStroke THICK_STROKE = new BasicStroke(SIZE * 3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
private static final BasicStroke THIN_STROKE = new BasicStroke(SIZE, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
private Collection<Line2D> lines;
private Shape bounds;
private boolean arrow;
private boolean thick;
private Color colour;
/**
Construct a new Line2DShapeInfo object.
@param line The line to display.
@param name The name of the line.
@param colour The colour of the line.
@param thick Whether to draw the line with a thick stroke.
@param arrow Whether to draw an arrow showing the direction of the line.
*/
public Line2DShapeInfo(Line2D line, String name, Color colour, boolean thick, boolean arrow) {
this(Collections.singleton(line), name, colour, thick, arrow);
}
/**
Construct a new Line2DShapeInfo object.
@param lines The lines to display.
@param name The name of the line.
@param colour The colour of the line.
@param thick Whether to draw the line with a thick stroke.
@param arrow Whether to draw an arrow showing the direction of the line.
*/
public Line2DShapeInfo(Collection<Line2D> lines, String name, Color colour, boolean thick, boolean arrow) {
super(lines, name);
this.lines = lines;
this.arrow = arrow;
this.thick = thick;
this.colour = colour;
if (lines.isEmpty()) {
return;
}
if (lines.size() == 1) {
Line2D l = lines.iterator().next();
bounds = new java.awt.geom.Line2D.Double(l.getOrigin().getX(), l.getOrigin().getY(), l.getEndPoint().getX(), l.getEndPoint().getY());
}
else {
double xMin = Double.POSITIVE_INFINITY;
double yMin = Double.POSITIVE_INFINITY;
double xMax = Double.NEGATIVE_INFINITY;
double yMax = Double.NEGATIVE_INFINITY;
for (Line2D line : lines) {
xMin = Math.min(xMin, line.getOrigin().getX());
xMax = Math.max(xMax, line.getOrigin().getX());
xMin = Math.min(xMin, line.getEndPoint().getX());
xMax = Math.max(xMax, line.getEndPoint().getX());
yMin = Math.min(yMin, line.getOrigin().getY());
yMax = Math.max(yMax, line.getOrigin().getY());
yMin = Math.min(yMin, line.getEndPoint().getY());
yMax = Math.max(yMax, line.getEndPoint().getY());
}
double xRange = xMax - xMin;
double yRange = yMax - yMin;
bounds = new Rectangle2D.Double(xMin, yMin, xMax - xMin, yMax - yMin);
if (GeometryTools2D.nearlyZero(xRange) || GeometryTools2D.nearlyZero(yRange)) {
bounds = new java.awt.geom.Line2D.Double(xMin, yMin, xMax, yMax);
}
}
}
@Override
public Shape paint(Graphics2D g, ScreenTransform transform) {
if (lines.isEmpty()) {
return null;
}
if (thick) {
g.setStroke(THICK_STROKE);
}
else {
g.setStroke(THIN_STROKE);
}
g.setColor(colour);
Path2D result = new Path2D.Double();
for (Line2D line : lines) {
Point2D start = line.getOrigin();
Point2D end = line.getEndPoint();
int x1 = transform.xToScreen(start.getX());
int y1 = transform.yToScreen(start.getY());
int x2 = transform.xToScreen(end.getX());
int y2 = transform.yToScreen(end.getY());
g.drawLine(x1, y1, x2, y2);
if (arrow) {
DrawingTools.drawArrowHeads(x1, y1, x2, y2, g);
}
result.moveTo(x1, y1);
result.lineTo(x2, y2);
// Logger.debug("Painting line " + name + " (" + line + ") from " + x1 + ", " + y1 + " -> " + x2 + ", " + y2);
}
return g.getStroke().createStrokedShape(result);
}
@Override
public void paintLegend(Graphics2D g, int width, int height) {
if (thick) {
g.setStroke(THICK_STROKE);
}
else {
g.setStroke(THIN_STROKE);
}
g.setColor(colour);
g.drawLine(0, height / 2, width, height / 2);
if (arrow) {
DrawingTools.drawArrowHeads(0, height / 2, width, height / 2, g);
}
}
@Override
public Shape getBoundsShape() {
return bounds;
}
@Override
public java.awt.geom.Point2D getBoundsPoint() {
return null;
}
}
private class BackgroundAction extends AbstractAction {
public BackgroundAction() {
super(backgroundEnabled ? "Hide background" : "Show background");
putValue(Action.SELECTED_KEY, Boolean.valueOf(backgroundEnabled));
}
@Override
public void actionPerformed(ActionEvent e) {
boolean selected = ((Boolean)getValue(Action.SELECTED_KEY)).booleanValue();
setBackgroundEnabled(!selected);
putValue(Action.SELECTED_KEY, Boolean.valueOf(backgroundEnabled));
putValue(Action.NAME, backgroundEnabled ? "Hide background" : "Show background");
ShapeDebugFrame.this.repaint();
}
}
}