/* (@)SimpleVectorPaintTestGui.java */
/* Copyright 2010 Sebastian Haufe
* 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
[url]http://www.apache.org/licenses/LICENSE-2.0[/url]
* 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 com.ebenius;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import java.util.LinkedList;
import java.util.List;
import javax.swing.*;
/**
* Simple painting program.
*
* @author Sebastian Haufe
*/
public class SimpleVectorPaintTestGui {
static class PaintNode implements java.io.Serializable {
/** Serial version UID */
private static final long serialVersionUID = 1L;
private Point2D.Double location;
private boolean endOfPath = false;
/** Creates a new {@code PaintNode}. */
PaintNode(double x, double y, boolean endOfPath) {
location = new Point2D.Double(x, y);
this.endOfPath = endOfPath;
}
/** Creates a new {@code PaintNode}. */
PaintNode(Point2D location, boolean endOfPath) {
this(location.getX(), location.getY(), endOfPath);
}
Point2D.Double getLocation() {
return location;
}
/**
* Sets the location.
*
* @param location the location to set
*/
public void setLocation(Point2D.Double location) {
this.location.setLocation(location);
}
boolean isEndOfPath() {
return endOfPath;
}
}
static class PaintPane extends JComponent {
/** Serial version UID */
private static final long serialVersionUID = 1L;
private double dotSize = 4; // px
private double lockInRange = 10; // px
private AffineTransform userTransform = new AffineTransform();
private final List<PaintNode> paintNodes = new LinkedList<PaintNode>();
private final Arc2D.Double dot =
new Arc2D.Double(0, 0, dotSize, dotSize, 0, 360, Arc2D.PIE);
private final Line2D.Double line = new Line2D.Double(Double.NaN, 0, 0, 0);
private Stroke stroke = new BasicStroke(1);
private PaintNode draggedNode;
/** Creates a new {@code PaintPane}. */
PaintPane() {
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
setPreferredSize(new Dimension(400, 400));
}
@Override
protected void processMouseEvent(MouseEvent e) {
super.processMouseEvent(e);
final Point2D.Double location = new Point2D.Double();
try {
userTransform.inverseTransform(e.getPoint(), location);
if (e.isShiftDown()) { // move on SHIFT
draggedNode = null;
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
draggedNode = findNode(location);
} else if (e.isControlDown() // delete on MOUSE_CLICKED / CTRL+SHIFT
&& e.getID() == MouseEvent.MOUSE_CLICKED) {
paintNodes.remove(findNode(location));
}
} else if (e.isControlDown()) { // add end point on CTRL
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
paintNodes.add(new PaintNode(location, true));
}
} else if (e.isAltDown()) { // delete on ALT
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
// TODO
}
} else { // add start/intermediate otherwise
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
paintNodes.add(new PaintNode(location, false));
}
}
repaint();
} catch (NoninvertibleTransformException ex) {
System.err.println("Do not use noninvertible transforms!");
ex.printStackTrace();
}
}
@Override
protected void processMouseMotionEvent(MouseEvent e) {
final Point2D.Double location = new Point2D.Double();
try {
userTransform.inverseTransform(e.getPoint(), location);
if (e.isShiftDown() && // move on SHIFT
e.getID() == MouseEvent.MOUSE_DRAGGED) {
if (draggedNode != null) {
draggedNode.setLocation(location);
}
} else {
draggedNode = null;
}
repaint();
} catch (NoninvertibleTransformException ex) {
System.err.println("Do not use noninvertible transforms!");
ex.printStackTrace();
}
}
PaintNode findNode(Point2D.Double location) {
final Rectangle2D.Double bounds = new Rectangle2D.Double();
/* update dot size to 1 px in view space */
final double[] src = { 0, 0, this.lockInRange, this.lockInRange };
double lockInRange = 0.05;
try {
this.userTransform.inverseTransform(src, 0, src, 0, 2);
lockInRange =
Math.max(Math.abs(src[2] - src[0]), Math.abs(src[3] - src[1]));
} catch (NoninvertibleTransformException ex) {
System.err.println("Do not use noninvertible transforms!");
ex.printStackTrace();
}
bounds.x = location.x - lockInRange;
bounds.y = location.y - lockInRange;
bounds.width = bounds.height = lockInRange * 2;
for (PaintNode node : paintNodes) {
if (bounds.contains(node.getLocation())) {
return node;
}
}
return null;
}
void setDotSize(double dotSize) {
final double old = this.dotSize;
this.dotSize = dotSize;
refreshDotAndStroke();
firePropertyChange("dotSize", old, dotSize); //$NON-NLS-1$
repaint();
}
void setUserTransform(AffineTransform userTransform) {
final AffineTransform old = this.userTransform;
if (userTransform == null) {
userTransform = new AffineTransform();
}
this.userTransform = userTransform;
refreshDotAndStroke();
firePropertyChange("userTransform", old, userTransform); //$NON-NLS-1$
repaint();
}
private void refreshDotAndStroke() {
/* update dot size to 1 px in view space */
try {
double[] src = { 0, 0, dotSize, dotSize };
this.userTransform.inverseTransform(src, 0, src, 0, 2);
dot.width = Math.abs(src[2] - src[0]);
dot.height = Math.abs(src[3] - src[1]);
src = new double[] { 0, 0, 1, 1 };
this.userTransform.inverseTransform(src, 0, src, 0, 2);
stroke =
new BasicStroke((float) (Math.min(src[2] - src[0], src[3]
- src[1])));
} catch (NoninvertibleTransformException ex) {
System.err.println("Do not use noninvertible transforms!");
ex.printStackTrace();
dot.width = dot.height = 1;
stroke = new BasicStroke(1);
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics scratchGraphics = g.create();
try {
if (scratchGraphics instanceof Graphics2D) {
final Graphics2D g2d = (Graphics2D) scratchGraphics;
g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
g2d.transform(userTransform);
g2d.setStroke(stroke);
line.x1 = Double.NaN;
// lines first
for (PaintNode node : paintNodes) {
final Point2D.Double location = node.getLocation();
if (!Double.isNaN(line.x1)) {
line.x2 = location.x;
line.y2 = location.y;
g2d.setColor(Color.DARK_GRAY);
g2d.draw(line);
}
if (!node.isEndOfPath()) {
line.x1 = location.x;
line.y1 = location.y;
} else {
line.x1 = Double.NaN;
}
}
Color nextColor = Color.RED;
for (PaintNode node : paintNodes) {
final Point2D.Double location = node.getLocation();
dot.x = location.x - dot.width / 2.;
dot.y = location.y - dot.height / 2.;
if (node.isEndOfPath()) {
nextColor = Color.RED;
} else if (nextColor == Color.RED) {
nextColor = Color.GREEN;
} else {
nextColor = Color.BLACK;
}
g2d.setColor(nextColor);
g2d.fill(dot);
}
}
} finally {
scratchGraphics.dispose();
}
}
}
/** Creates the GUI. Call on EDT, only! */
static void createAndShowGui() {
final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
final PaintPane paintPane = new PaintPane();
paintPane.setUserTransform(AffineTransform.getScaleInstance(40, 40));
contentPane.add(new JScrollPane(paintPane));
final JFrame f = new JFrame("Test Frame: SimpleVectorPaintTestGui"); //$NON-NLS-1$
f.setContentPane(contentPane);
f.pack();
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);
}
/** @param args ignored */
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}