Reputation: 35
How to drag free-form lines (paths) with AffineTransform?
I'm making a Paint-like application in Java and one of the requirements is being able to drag drawn free-form shapes to a new location on a JPanel used for drawing. I've been trying to get AffineTransform to do this for a day now and what it does at the moment is while the required line(which is stored as a Path2D) is selected and dragged it does move. However, once there is no more line selected, the line goes back to its original location. Also, when I select it again, it is immediately shown in that new location (if that makes any sense); this is probably to the coordinate system being translated, but I'm not sure... Any help will be highly appreciated! Perhaps there is an simpler way of doing this. PS I also noticed that when moving any of the drawn lines, except for the one drawn last, all of the lines drawn prior to the line, which is being moved, are moved together with it. Here's the code:
public class DrawPanel extends JPanel {
public double translateX=0;
public double translateY=0;
public int lastOffsetX;
public int lastOffsetY;
class Line {
public Point start;
public Point end;
public Color color;
public Path2D path;
}
ArrayList<Line> lines = new ArrayList<Line>();
ArrayList<Path2D> paths = new ArrayList<Path2D>();
boolean moveMode = false;
Path2D selectedLine;
int xDistance;
int yDistance;
public DrawPanel() {
setBackground(Color.WHITE);
setFocusable(true);
requestFocusInWindow();
this.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
//System.out.println(resizeMode);
if (moveMode) {
if (selectedLine!=null) {
int newX = e.getX() - lastOffsetX;
int newY = e.getY() - lastOffsetY;
lastOffsetX += newX;
lastOffsetY += newY;
translateX += newX;
translateY += newY;
repaint();
}
} else {
Path2D p = paths.get(paths.size() - 1);
p.lineTo(e.getX(), e.getY());
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
if (resizeMode) {
selectedLine = null;
for (Path2D l : paths) {
if (l.contains(e.getPoint())) {
selectedLine = l;
}
}
repaint();
}
}
});
this.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
if (!moveMode) {
Line l = new Line(e.getPoint());
l.path = new Path2D.Double();
l.path.moveTo(e.getX(), e.getY());
lines.add(l);
paths.add(l.path);
} else {
lastOffsetX = e.getX();
lastOffsetY = e.getY();
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
if (!resizeMode) {
if (selectedLine == null) {
Line l = lines.get(lines.size() - 1);
l.end = e.getPoint();
l.path.lineTo(e.getX(), e.getY());
Path2D p = paths.get(paths.size() - 1);
p.lineTo(e.getX(), e.getY());
repaint();
}
} else {
for (int j=0; j<paths.size();j++) {
if (selectedLine!=null && selectedLine.equals(paths.get(j))) {
paths.set(j, selectedLine);
}
}
repaint();
}
}
});
}
private void setKeyBindings() {
ActionMap actionMap = getActionMap();
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition );
String ctrl = "VK_CONTROL";
String ctrl_rel = "control_released";
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, KeyEvent.CTRL_DOWN_MASK, false), ctrl);
inputMap.put(KeyStroke.getKeyStroke(("released CONTROL")), ctrl_rel);
actionMap.put(ctrl, new KeyAction(ctrl));
actionMap.put(ctrl_rel, new KeyAction(ctrl_rel));
}
private class KeyAction extends AbstractAction {
public KeyAction(String actionCommand) {
putValue(ACTION_COMMAND_KEY, actionCommand);
}
@Override
public void actionPerformed(ActionEvent actionEvt) {
if(actionEvt.getActionCommand() == "VK_CONTROL") {
moveMode = true;
}
else if(actionEvt.getActionCommand() == "control_released") {
moveMode = false;
repaint();
}
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(10.0f));
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
if (moveMode) {
for (int j=0; j<paths.size();j++) {
Path2D path = paths.get(j);
if (selectedLine!=null && selectedLine.equals(path)) {
AffineTransform at = new AffineTransform();
at.translate(translateX, translateY);
g2d.transform(at);
g2d.setColor(Color.RED);
g2d.draw(path);
g2d.setColor(Color.BLACK);
continue;
}
g2d.draw(path);
g2d.setColor(Color.BLACK);
}
} else {
for (int i =0; i < paths.size();i++) {
Path2D path = paths.get(i);
g2d.draw(path); // outline
}
}
}
Edit to include resolution: So in the end what I did is I saved all of the path's coordinates (which I got from PathIterator) to and ArrayList, created a new empty path, added the previous path's coordinates from the ArrayList to the new path (via moveTo, lineTo) and appended the new path to the ArrayList of all drawn paths.
Upvotes: 1
Views: 808
Reputation: 205855
As you suspect, your AffineTransform
alters the graphics context's coordinate system for all subsequent drawing.
In the example cited here, each shape is an instance of the class Node
. Each Node
includes a selected
attribute, which allows the shape to be selected independently. The value determines the effect of updatePosition()
when called from mouseDragged()
. The implementation of updatePosition()
simply updates each selected node's coordinates, but you can also use the createTransformedShape()
of AffineTransform
.
Upvotes: 4