/* * Copyright 2010-2013 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.painter; import icy.canvas.IcyCanvas; import icy.canvas.IcyCanvas2D; import icy.common.EventHierarchicalChecker; import icy.file.xml.XMLPersistent; import icy.painter.OverlayEvent.OverlayEventType; import icy.painter.PainterEvent.PainterEventType; import icy.sequence.Sequence; import icy.type.point.Point5D; import icy.util.EventUtil; import icy.util.ShapeUtil; import icy.util.XMLUtil; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.EventListener; import org.w3c.dom.Node; import plugins.kernel.canvas.VtkCanvas; /** * @author Stephane */ public class Anchor2D extends Overlay implements XMLPersistent { /** * Interface to listen Anchor2D position change */ public static interface Anchor2DPositionListener extends EventListener { public void positionChanged(Anchor2D source); } /** * @deprecated Use {@link Anchor2DPositionListener} listener or {@link OverlayListener} */ @Deprecated public static interface Anchor2DListener extends PainterListener { public void positionChanged(Anchor2D source); } public static class Anchor2DEvent implements EventHierarchicalChecker { private final Anchor2D source; public Anchor2DEvent(Anchor2D source) { super(); this.source = source; } /** * @return the source */ public Anchor2D getSource() { return source; } @Override public boolean isEventRedundantWith(EventHierarchicalChecker event) { if (event instanceof Anchor2DEvent) return ((Anchor2DEvent) event).getSource() == source; return false; } } protected static final String ID_COLOR = "color"; protected static final String ID_SELECTEDCOLOR = "selected_color"; protected static final String ID_SELECTED = "selected"; protected static final String ID_POS_X = "pos_x"; protected static final String ID_POS_Y = "pos_y"; protected static final String ID_RAY = "ray"; protected static final String ID_VISIBLE = "visible"; protected static final int DEFAULT_RAY = 6; protected static final Color DEFAULT_NORMAL_COLOR = Color.YELLOW; protected static final Color DEFAULT_SELECTED_COLOR = Color.WHITE; /** * position (canvas) */ protected final Point2D.Double position; /** * radius */ protected int ray; /** * color */ protected Color color; /** * selection color */ protected Color selectedColor; /** * selection flag */ protected boolean selected; /** * flag that indicate if anchor is visible */ protected boolean visible; /** * internals */ protected final Ellipse2D ellipse; protected Point2D startDragMousePosition; protected Point2D startDragPainterPosition; public Anchor2D(double x, double y, int ray, Color color, Color selectedColor) { super("Anchor", OverlayPriority.SHAPE_NORMAL); position = new Point2D.Double(x, y); this.ray = ray; this.color = color; this.selectedColor = selectedColor; selected = false; visible = true; ellipse = new Ellipse2D.Double(); startDragMousePosition = null; startDragPainterPosition = null; } public Anchor2D(double x, double y, int ray, Color color) { this(x, y, ray, color, DEFAULT_SELECTED_COLOR); } /** * @param x * @param y * @param ray */ public Anchor2D(double x, double y, int ray) { this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); } /** * @param x * @param y * @param color */ public Anchor2D(double x, double y, Color color, Color selectedColor) { this(x, y, DEFAULT_RAY, color, selectedColor); } /** * @param x * @param y * @param color */ public Anchor2D(double x, double y, Color color) { this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); } /** * @param x * @param y */ public Anchor2D(double x, double y) { this(x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); } /** */ public Anchor2D() { this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); } /** * @deprecated Use {@link #Anchor2D(double, double, int, Color, Color)} instead. */ @Deprecated public Anchor2D(Point2D position, int ray, Color color, Color selectedColor) { this(position.getX(), position.getY(), ray, color, selectedColor); } /** * @deprecated Use {@link #Anchor2D(double, double, int, Color)} instead. */ @Deprecated public Anchor2D(Point2D position, int ray, Color color) { this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR); } /** * @deprecated Use {@link #Anchor2D(double, double, int)} instead. */ @Deprecated public Anchor2D(Point2D position, int ray) { this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); } /** * @deprecated Use {@link #Anchor2D(double, double, Color, Color)} instead. */ @Deprecated public Anchor2D(Point2D position, Color color, Color selectedColor) { this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor); } /** * @deprecated Use {@link #Anchor2D(double, double, Color)} instead. */ @Deprecated public Anchor2D(Point2D position, Color color) { this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); } /** * @deprecated Use {@link #Anchor2D(double, double)} instead. */ @Deprecated public Anchor2D(Point2D position) { this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, double x, double y, int ray, Color color, Color selectedColor) { this(x, y, ray, color, selectedColor); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position, int ray, Color color, Color selectedColor) { this(position.getX(), position.getY(), ray, color, selectedColor); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(Point2D, int, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position, int ray, Color color) { this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. */ @Deprecated public Anchor2D(Sequence sequence, double x, double y, int ray) { this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position, Color color) { this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position, int ray) { this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. */ @Deprecated public Anchor2D(Sequence sequence, double x, double y, Color color, Color selectedColor) { this(x, y, DEFAULT_RAY, color, selectedColor); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position, Color color, Color selectedColor) { this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead. */ @Deprecated public Anchor2D(Sequence sequence, double x, double y, Color color) { this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead. */ @Deprecated public Anchor2D(Sequence sequence, double x, double y) { this(sequence, x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead. */ @Deprecated public Anchor2D(Sequence sequence, Point2D position) { this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @deprecated Use {@link Anchor2D#Anchor2D()} instead. */ @Deprecated public Anchor2D(Sequence sequence) { this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); sequence.addOverlay(this); } /** * @return the x */ public double getX() { return position.x; } /** * @param x * the x to set */ public void setX(double x) { setPosition(x, position.y); } /** * @return the y */ public double getY() { return position.y; } /** * @param y * the y to set */ public void setY(double y) { setPosition(position.x, y); } public Point2D getPosition() { return new Point2D.Double(position.x, position.y); } public double getPositionX() { return position.x; } public double getPositionY() { return position.y; } public void moveTo(Point2D p) { setPosition(p.getX(), p.getY()); } public void moveTo(double x, double y) { setPosition(x, y); } public void setPosition(Point2D p) { setPosition(p.getX(), p.getY()); } public void setPosition(double x, double y) { if ((position.x != x) || (position.y != y)) { position.x = x; position.y = y; positionChanged(); painterChanged(); } } public void translate(double dx, double dy) { setPosition(position.x + dx, position.y + dy); } /** * @return the ray */ public int getRay() { return ray; } /** * @param value * the ray to set */ public void setRay(int value) { if (ray != value) { ray = value; painterChanged(); } } /** * @return the color */ public Color getColor() { return color; } /** * @param value * the color to set */ public void setColor(Color value) { if (color != value) { color = value; painterChanged(); } } /** * @return the selectedColor */ public Color getSelectedColor() { return selectedColor; } /** * @param value * the selectedColor to set */ public void setSelectedColor(Color value) { if (selectedColor != value) { selectedColor = value; painterChanged(); } } /** * @return the selected */ public boolean isSelected() { return selected; } /** * @param value * the selected to set */ public void setSelected(boolean value) { if (selected != value) { selected = value; // end drag if (!value) startDragMousePosition = null; painterChanged(); } } // /** // * @return the canRemove // */ // public boolean isCanRemove() // { // return canRemove; // } // // /** // * @param value // * the canRemove to set // */ // public void setCanRemove(boolean value) // { // canRemove = value; // } /** * @return the visible */ public boolean isVisible() { return visible; } /** * @param value * the visible to set */ public void setVisible(boolean value) { if (visible != value) { visible = value; painterChanged(); } } public boolean isOver(IcyCanvas canvas, Point2D p) { if (p == null) return false; return isOver(canvas, p.getX(), p.getY()); } public boolean isOver(IcyCanvas canvas, double x, double y) { updateEllipse(canvas); // fast contains test to start with if (ellipse.getBounds2D().contains(x, y)) return ellipse.contains(x, y); return false; } protected double getAdjRay(IcyCanvas canvas) { return Math.max(canvas.canvasToImageLogDeltaX(ray), canvas.canvasToImageLogDeltaY(ray)); } private void updateEllipse(IcyCanvas canvas) { final double adjRay = getAdjRay(canvas); ellipse.setFrame(position.x - adjRay, position.y - adjRay, adjRay * 2, adjRay * 2); } protected boolean updateDrag(InputEvent e, double x, double y) { // not dragging --> exit if (startDragMousePosition == null) return false; double dx = x - startDragMousePosition.getX(); double dy = y - startDragMousePosition.getY(); // shift action --> limit to one direction if (EventUtil.isShiftDown(e)) { // X drag if (Math.abs(dx) > Math.abs(dy)) dy = 0; // Y drag else dx = 0; } // set new position setPosition(new Point2D.Double(startDragPainterPosition.getX() + dx, startDragPainterPosition.getY() + dy)); return true; } protected boolean updateDrag(InputEvent e, Point2D pt) { return updateDrag(e, pt.getX(), pt.getY()); } /** * called when anchor position has changed */ protected void positionChanged() { updater.changed(new Anchor2DEvent(this)); } @SuppressWarnings("deprecation") @Override public void onChanged(EventHierarchicalChecker object) { if (object instanceof Anchor2DEvent) { firePositionChangedEvent(((Anchor2DEvent) object).getSource()); return; } // provide event backward compatibility if (object instanceof OverlayEvent) { final OverlayEvent event = (OverlayEvent) object; if (event.getType() == OverlayEventType.PAINTER_CHANGED) { final PainterEvent pe = new PainterEvent(this, PainterEventType.PAINTER_CHANGED); for (Anchor2DListener listener : listeners.getListeners(Anchor2DListener.class)) listener.painterChanged(pe); } } super.onChanged(object); } protected void firePositionChangedEvent(Anchor2D source) { for (Anchor2DPositionListener listener : listeners.getListeners(Anchor2DPositionListener.class)) listener.positionChanged(source); // backward compatibility for (Anchor2DListener listener : listeners.getListeners(Anchor2DListener.class)) listener.positionChanged(source); } public void addPositionListener(Anchor2DPositionListener listener) { listeners.add(Anchor2DPositionListener.class, listener); } public void removePositionListener(Anchor2DPositionListener listener) { listeners.remove(Anchor2DPositionListener.class, listener); } /** * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or * {@link #addOverlayListener(OverlayListener)} instead. */ @Deprecated public void addAnchorListener(Anchor2DListener listener) { listeners.add(Anchor2DListener.class, listener); } /** * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or * {@link #removeOverlayListener(OverlayListener)} instead. */ @Deprecated public void removeAnchorListener(Anchor2DListener listener) { listeners.remove(Anchor2DListener.class, listener); } /** * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or * {@link #addOverlayListener(OverlayListener)} instead. */ @Deprecated public void addListener(Anchor2DListener listener) { addAnchorListener(listener); } /** * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or * {@link #removeOverlayListener(OverlayListener)} instead. */ @Deprecated public void removeListener(Anchor2DListener listener) { removeAnchorListener(listener); } @Override public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof IcyCanvas2D) { updateEllipse(canvas); // trivial paint optimization final boolean shapeVisible = ShapeUtil.isVisible(g, ellipse); if (shapeVisible) { final Graphics2D g2 = (Graphics2D) g.create(); // draw content if (isSelected()) g2.setColor(getSelectedColor()); else g2.setColor(getColor()); // draw ellipse content g2.fill(ellipse); // draw black border g2.setStroke(new BasicStroke((float) (getAdjRay(canvas) / 8f))); g2.setColor(Color.black); g2.draw(ellipse); g2.dispose(); } } } /* * only for backward compatibility */ @Override public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // just for the shift key state change updateDrag(e, imagePoint); } /* * only for backward compatibility */ @Override public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // just for the shift key state change updateDrag(e, imagePoint); } /* * only for backward compatibility */ @Override public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { if (!isVisible()) return; if (e.isConsumed()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; if (EventUtil.isLeftMouseButton(e)) { // consume event to activate drag if (isSelected()) { startDragMousePosition = imagePoint; startDragPainterPosition = getPosition(); e.consume(); } } } /* * only for backward compatibility */ @Override public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { startDragMousePosition = null; } /* * only for backward compatibility */ @Override public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { if (!isVisible()) return; if (e.isConsumed()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; if (EventUtil.isLeftMouseButton(e)) { // if selected then move according to mouse position if (isSelected()) { // force start drag if not already the case if (startDragMousePosition == null) { startDragMousePosition = getPosition(); startDragPainterPosition = getPosition(); } updateDrag(e, imagePoint); e.consume(); } } } /* * only for backward compatibility */ @Override public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // already consumed, no selection possible if (e.isConsumed()) setSelected(false); else { final boolean overlapped = isOver(canvas, imagePoint.getX(), imagePoint.getY()); setSelected(overlapped); // so we can only have one selected at once if (overlapped) e.consume(); } } @Override public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // just for the shift key state change updateDrag(e, imagePoint.x, imagePoint.y); } @Override public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // just for the shift key state change updateDrag(e, imagePoint.x, imagePoint.y); } @Override public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { if (!isVisible()) return; if (e.isConsumed()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; if (EventUtil.isLeftMouseButton(e)) { // consume event to activate drag if (isSelected()) { startDragMousePosition = imagePoint.toPoint2D(); startDragPainterPosition = getPosition(); e.consume(); } } } @Override public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { startDragMousePosition = null; } @Override public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { if (!isVisible()) return; if (e.isConsumed()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; if (EventUtil.isLeftMouseButton(e)) { // if selected then move according to mouse position if (isSelected()) { // force start drag if not already the case if (startDragMousePosition == null) { startDragMousePosition = getPosition(); startDragPainterPosition = getPosition(); } updateDrag(e, imagePoint.x, imagePoint.y); e.consume(); } } } @Override public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { if (!isVisible()) return; // VtkCanvas not handled here if (canvas instanceof VtkCanvas) return; // no image position --> exit if (imagePoint == null) return; // already consumed, no selection possible if (e.isConsumed()) setSelected(false); else { final boolean overlapped = isOver(canvas, imagePoint.x, imagePoint.y); setSelected(overlapped); // so we can only have one selected at once if (overlapped) e.consume(); } } public boolean loadPositionFromXML(Node node) { if (node == null) return false; beginUpdate(); try { setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d)); setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d)); } finally { endUpdate(); } return true; } public boolean savePositionToXML(Node node) { if (node == null) return false; XMLUtil.setElementDoubleValue(node, ID_POS_X, getX()); XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY()); return true; } @Override public boolean loadFromXML(Node node) { if (node == null) return false; beginUpdate(); try { setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, DEFAULT_NORMAL_COLOR.getRGB()))); setSelectedColor(new Color(XMLUtil.getElementIntValue(node, ID_SELECTEDCOLOR, DEFAULT_SELECTED_COLOR.getRGB()))); setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d)); setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d)); setRay(XMLUtil.getElementIntValue(node, ID_RAY, DEFAULT_RAY)); setVisible(XMLUtil.getElementBooleanValue(node, ID_VISIBLE, true)); } finally { endUpdate(); } return true; } @Override public boolean saveToXML(Node node) { if (node == null) return false; XMLUtil.setElementIntValue(node, ID_COLOR, getColor().getRGB()); XMLUtil.setElementIntValue(node, ID_SELECTEDCOLOR, getSelectedColor().getRGB()); XMLUtil.setElementDoubleValue(node, ID_POS_X, getX()); XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY()); XMLUtil.setElementIntValue(node, ID_RAY, getRay()); XMLUtil.setElementBooleanValue(node, ID_VISIBLE, isVisible()); return true; } }