/*
 * File    : PolygonDrawPanel.java
 * Created : 17-may-2002 10:20
 * By      : allastar
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 (see the LICENSE file).
 */

package edu.xtec.jclic.shapers;

import edu.xtec.util.StrUtils;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;

/**
 * @author allastar
 * @author Francesc Busquets (fbusquets@xtec.cat)
 * @version 13.09.17
 */
public class PolygonDrawPanel implements java.awt.event.MouseMotionListener, java.awt.event.MouseListener {

  // List containing the polygon currently being modified
  private List<EditableShape> vShapes;
  private static List<EditableShape> vCopied;
  private List<EditableShape> vRedrawingLines, vRedrawingLinesBeforeModify, vShapeBeforeModify;
  private List<PointListener> vPointListeners;
  private double iniX, iniY, finX, finY, lastFinX, lastFinY;
  private Point2D iniPoint = null, lastPoint = null;
  private boolean bSelectedPoint = false;
  private double zoomX = 0, zoomY = 0, zoomH = -1, zoomW = -1;
  private boolean creatingRect = false, creatingEllipse = false, creatingPolygon = false, bRedrawingLines = false,
      bSelectingArea = false;
  private boolean bResizing = false;
  private int resizingDirection = NO_RESIZING;
  private boolean bSpecialLine = false;
  private boolean bMoving = false;
  private EditableShape specialLine = null;
  private int drawingMode = SELECTING;
  static double defaultSensibility = 1.5;
  private AffineTransform at;
  private List<Rectangle> vDrawnBorders = null;
  private Shape current = null;

  private JComponent container;

  private short INITIAL = 0;
  private short END = 1;

  public static final int SELECTING = 1;
  public static final int MOVING = 2;
  public static final int NEW_POINT = 4;
  public static final int DRAWING_RECT = 5;
  public static final int DRAWING_ELLIPSE = 6;
  public static final int DRAWING_POLYGON = 7;
  public static final int ZOOM = 12;

  public static final int NO_RESIZING = -1;
  public static final int EAST = 0;
  public static final int SOUTH = 1;
  public static final int SOUTH_EAST = 2;

  private int backgroundComposite = 0;

  protected HolesEditorPanel hep;
  protected boolean canResize;
  protected Rectangle lastPreviewArea;

  static Cursor[] cursors = null;
  public static final int PEN_CURSOR = 0;
  public static final int CIRCLE_CURSOR = 1;

  protected Shape esborram = null;

  /** Creates new PolygonDrawPanel */
  public PolygonDrawPanel(int width, int height, HolesEditorPanel hep, boolean canResize) {
    this.hep = hep;
    this.canResize = canResize;
    vShapes = new ArrayList<EditableShape>();
    if (vCopied == null)
      vCopied = new ArrayList<EditableShape>();

    vRedrawingLines = new ArrayList<EditableShape>();
    vRedrawingLinesBeforeModify = new ArrayList<EditableShape>();
    vPointListeners = new ArrayList<PointListener>();
    at = new AffineTransform();
    initDrawnBorders();

    if (cursors == null) {
      cursors = new Cursor[2];
      Toolkit tk = Toolkit.getDefaultToolkit();
      cursors[PEN_CURSOR] = tk.createCustomCursor(
          edu.xtec.util.ResourceManager.getImageIcon("cursors/llapis.gif").getImage(), new Point(12, 24), "pen");
      cursors[CIRCLE_CURSOR] = tk.createCustomCursor(
          edu.xtec.util.ResourceManager.getImageIcon("cursors/cercle.gif").getImage(), new Point(16, 16), "circle");
    }
    hep.addKeyListener(new PolygonDrawPanel.KeyHandler());
  }

  public void setDrawingMode(int drawingMode) {
    if (this.drawingMode != drawingMode) {
      this.drawingMode = drawingMode;
      if (creatingPolygon)
        joinPolygon();
      if (drawingMode != NEW_POINT && drawingMode != SELECTING) {
        endPolygon();
      }
      hep.repaint(0);
    }
  }

  public int getVisibleWidth() {
    return hep.getPreviewPanel().getWidth();
  }

  public int getVisibleHeight() {
    return hep.getPreviewPanel().getHeight();
  }

  public void initDrawnBorders() {
    if (vDrawnBorders != null)
      vDrawnBorders.clear();
    else
      vDrawnBorders = new ArrayList<Rectangle>();
    for (int i = 0; i < hep.getNumShapes(); i++) {
      if (i != hep.currentShape) {
        Shape s = hep.getHoles().getShape(i, hep.previewArea);
        if (s != null)
          vDrawnBorders.addAll(getBorders(s));
      }
    }
  }

  private List<Rectangle> getBorders(Shape s) {
    // Utility function that returns the points that define the "segments" of the
    // polygon 's'.
    int xIni = 0;
    int yIni = 0;

    if (s == null)
      return null;
    List<Rectangle> vPoints = new ArrayList<Rectangle>();
    double x, y;
    if (s instanceof GeneralPath) {
      GeneralPath gp = (GeneralPath) s;
      PathIterator it = gp.getPathIterator(new AffineTransform());
      double[] coords = new double[6];
      while (!it.isDone()) {
        int type = it.currentSegment(coords);
        switch (type) {
        case PathIterator.SEG_MOVETO:
          x = coords[0];
          y = coords[1];
          vPoints.add(new Rectangle((int) (x + xIni) - (EditableShapeConstants.selectLength / 2),
              (int) (y + yIni) - (EditableShapeConstants.selectLength / 2), EditableShapeConstants.selectLength,
              EditableShapeConstants.selectLength));
          break;
        case PathIterator.SEG_LINETO:
          x = coords[0];
          y = coords[1];
          vPoints.add(new Rectangle((int) (x + xIni) - (EditableShapeConstants.selectLength / 2),
              (int) (y + yIni) - (EditableShapeConstants.selectLength / 2), EditableShapeConstants.selectLength,
              EditableShapeConstants.selectLength));
          break;
        case PathIterator.SEG_CUBICTO:
          x = coords[4];
          y = coords[5];
          vPoints.add(new Rectangle((int) (x + xIni) - (EditableShapeConstants.selectLength / 2),
              (int) (y + yIni) - (EditableShapeConstants.selectLength / 2), EditableShapeConstants.selectLength,
              EditableShapeConstants.selectLength));
          break;
        case PathIterator.SEG_QUADTO:
          x = coords[2];
          y = coords[3];
          vPoints.add(new Rectangle((int) (x + xIni) - (EditableShapeConstants.selectLength / 2),
              (int) (y + yIni) - (EditableShapeConstants.selectLength / 2), EditableShapeConstants.selectLength,
              EditableShapeConstants.selectLength));
          break;
        case PathIterator.SEG_CLOSE:
          break;
        default:
        }
        it.next();
      }
    }
    return vPoints;
  }

  public void paint(java.awt.Graphics2D g) {

    Graphics2D g2d = (Graphics2D) g;

    if (EditableShapeConstants.showDrawnPoints)
      paintDrawnBorders(g);

    for (EditableShape esh : vShapes) {
      if (bSpecialLine && esh == specialLine)
        esh.paintWithColor(g, drawingMode, EditableShapeConstants.CUT_COLOR);
      else
        esh.paintWithColor(g, drawingMode, EditableShapeConstants.ACTIVE_COLOR);
    }
    if (bMoving)
      paintMoved(g);
    if (creatingRect) {
      g.setColor(EditableShapeConstants.selectedColor);
      EditableRectangle rect = new EditableRectangle((int) iniX, (int) iniY, (int) (finX - iniX), (int) (finY - iniY));
      rect.paintWithColor(g, drawingMode, EditableShapeConstants.selectedColor);
    }
    if (creatingEllipse) {
      g.setColor(EditableShapeConstants.selectedColor);
      EditableEllipse2D ellipse = new EditableEllipse2D((int) iniX, (int) iniY, (int) (finX - iniX),
          (int) (finY - iniY));
      ellipse.paintWithColor(g, drawingMode, EditableShapeConstants.selectedColor);
    }
    if (creatingPolygon) {
      if (lastPoint != null) {
        EditableLine2D el = new EditableLine2D(lastPoint.getX(), lastPoint.getY(), finX, finY);
        el.paintWithColor(g, drawingMode, EditableShapeConstants.selectedColor);
      }
    }
  }

  public void drawGrid(java.awt.Graphics g, int gridWidth) {
    if (gridWidth <= 1)
      return;
    int width = (int) (hep.previewArea.getWidth());
    int height = (int) (hep.previewArea.getHeight());
    g.setColor(EditableShapeConstants.gridColor);
    for (double i = hep.previewArea.x; i <= hep.previewArea.x + width; i += (gridWidth * hep.xFactor)) {
      // from 0 in order to avoid changes in the location of the grid when zoomed
      // vertical
      g.drawLine((int) i, hep.previewArea.y, (int) i, (int) (hep.previewArea.y + height));
    }
    for (double i = hep.previewArea.y; i <= hep.previewArea.y + height; i += (gridWidth * hep.yFactor)) {
      // horitzontal
      g.drawLine(hep.previewArea.x, (int) i, (int) (hep.previewArea.x + width), (int) i);
    }
  }

  protected void paintDrawnBorders(java.awt.Graphics2D g) {
    for (Rectangle r : vDrawnBorders) {
      double x = r.getX();
      double y = r.getY();
      double w = r.getWidth();
      double h = r.getHeight();
      x = x + (w / 4);
      y = y + (h / 4);
      w = w / 2;
      h = h / 2;
      g.setColor(EditableShapeConstants.DRAWN_BORDER_COLOR);
      g.fillRect((int) x, (int) y, (int) w, (int) h);
    }
  }

  private void paintMoved(java.awt.Graphics2D g) {
    for (EditableShape esh : vCopied) {
      EditableShape copied = (EditableShape) esh.clone();
      copied.transform(AffineTransform.getTranslateInstance(finX - iniX, finY - iniY));
      copied.paintWithColor(g, drawingMode, EditableShapeConstants.movingColor);
    }
  }

  public void updateView() {
    List v = getGeneralPath();
    if (lastPreviewArea == null)
      lastPreviewArea = hep.previewArea;
    if (v.size() > 0) {
      move(hep.previewArea.x - lastPreviewArea.x, hep.previewArea.y - lastPreviewArea.y, false, false);
    }
    try {
      lastPreviewArea = (Rectangle) (hep.previewArea.clone());
    } catch (Exception e) {
      System.err.println("Error updating view:\n" + e);
    }
  }

  public void setShapeData(ShapeData sd, double x, double y, double scaleX, double scaleY) {
    // x,y indicate the current position
    clean();
    current = (sd != null) ? sd.getShape(hep.previewArea) : null;
    double firstX = -1, firstY = -1;
    if (sd != null && sd.primitiveType >= 0 && sd.primitivePoints != null && sd.primitivePoints.length > 3) {
      EditableShape es;
      double xTr = (sd.primitivePoints[0] * hep.previewArea.getWidth()) + hep.previewArea.getX();
      double yTr = (sd.primitivePoints[1] * hep.previewArea.getHeight()) + hep.previewArea.getY();
      double wSc = sd.primitivePoints[2] * hep.previewArea.getWidth();
      double hSc = sd.primitivePoints[3] * hep.previewArea.getHeight();
      switch (sd.primitiveType) {
      case ShapeData.RECTANGLE:
        es = new EditableRectangle((int) xTr, (int) yTr, (int) wSc, (int) hSc);
        vShapes.add(es);
        break;
      case ShapeData.ELLIPSE:
        es = new EditableEllipse2D((int) xTr, (int) yTr, (int) wSc, (int) hSc);
        vShapes.add(es);
        break;
      }
    } else if (sd != null) {
      Shape s = sd.getShape(hep.previewArea);
      if (s instanceof GeneralPath) {
        GeneralPath gp = (GeneralPath) s;
        PathIterator it = gp.getPathIterator(new AffineTransform());
        double[] coords = new double[6];
        while (!it.isDone()) {
          int type = it.currentSegment(coords);
          switch (type) {
          case PathIterator.SEG_MOVETO:
            x = coords[0];
            y = coords[1];
            if (firstX == -1) {
              // Too close
              firstX = x;
              firstY = y;
            }
            break;
          case PathIterator.SEG_LINETO:
            vShapes.add(new EditableLine2D(x, y, coords[0], coords[1]));
            x = coords[0];
            y = coords[1];
            break;
          case PathIterator.SEG_CUBICTO:
            vShapes
                .add(new EditableCubicCurve2D(x, y, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]));
            x = coords[4];
            y = coords[5];
            break;
          case PathIterator.SEG_QUADTO:
            vShapes.add(new EditableQuadCurve2D(x, y, coords[0], coords[1], coords[2], coords[3]));
            x = coords[2];
            y = coords[3];
            break;
          case PathIterator.SEG_CLOSE:
            if (firstX != -1 && (x != firstX || y != firstY)) {
              vShapes.add(new EditableLine2D(x, y, firstX, firstY));
              x = firstX;
              y = firstY;
            }
            break;
          default:
            break;
          }
          it.next();
        }
        if (firstX != -1 && (x != firstX || y != firstY)) {
          // That's to be sure that the shape is always closed
          vShapes.add(new EditableLine2D(x, y, firstX, firstY));
        }
      }
    }
    removeDrawnBorders(sd);
  }

  public void clean() {
    vShapes = new ArrayList<EditableShape>();
    vRedrawingLines = new ArrayList<EditableShape>();
    vRedrawingLinesBeforeModify = new ArrayList<EditableShape>();
  }

  public boolean selectDrawnShape(Point2D p) {
    endPolygon();
    for (int i = 0; i < hep.getNumShapes(); i++) {
      Shape s = hep.getHoles().getShape(i, hep.previewArea);
      if (s.contains(p) && hep.currentShape != i) {
        hep.setCurrentShape(i);
        setShapeData(hep.getHoles().getShapeData(i), 0, 0, 1, 1);
        return true;
      }
    }
    hep.setCurrentShape(hep.getHoles().getNumCells() + 1);
    clean();
    return false;
  }

  public void selectShape(int iIndex) {
    if (iIndex < 0)
      return;
    ShapeData sd = hep.getHoles().getShapeData(iIndex);
    if (sd != null) {
      setShapeData(sd, 0, 0, 1, 1);
    }
  }

  private EditableShape aproximationToLine(double x, double y) {
    return aproximationToLine(x, y, null);
  }

  private EditableShape aproximationToLine(double x, double y, List<EditableShape> vRedrawingLines) {
    // returns an EditableShape when there is a corner at (x,y) that not belongs to
    // the lines in the Rectangle List vRedrawingLines. Otherwise returns null
    if (vRedrawingLines != null) {
      for (EditableShape esh : vRedrawingLines) {
        if (!vRedrawingLines.contains(esh)) {
          if (esh.hasClickedBorder((int) x, (int) y, false)) {
            return esh;
          }
        }
      }
    }
    return null;
  }

  private Point2D getTransformedPoint(Point2D p, boolean mustBeOnGrid) {
    // mustBeOnGrid is used to discard the approach
    Point2D mousePoint = new Point2D.Double(p.getX(), p.getY());
    if (EditableShapeConstants.gridWidth != -1 && EditableShapeConstants.pointsOnGrid && mustBeOnGrid)
      moveToGrid(mousePoint);
    return mousePoint;
  }

  private void moveToGrid(Point2D p) {
    // Moves the supplied point to the closest grid point
    int x = (int) p.getX();
    int y = (int) p.getY();

    x -= hep.previewArea.getX();
    y -= hep.previewArea.getY();

    double wd = EditableShapeConstants.gridWidth * hep.xFactor;
    int w = (int) wd;
    if (w == -1)
      return;

    int xLeft = (int) (((int) (x / wd)) * wd);
    if ((x - xLeft) < (w / 2))
      x = xLeft;
    else
      x = (int) (((int) ((x + w - 1) / wd)) * wd);

    int yUp = (int) (((int) (y / wd)) * wd);
    if ((y - yUp) < (w / 2))
      y = yUp;
    else
      y = (int) (((int) ((y + w - 1) / wd)) * wd);
    x += hep.previewArea.getX();
    y += hep.previewArea.getY();
    p.setLocation(x, y);
  }

  public Point2D aproximationToDrawnBorder(double x, double y) {
    // Returns true if a rectangle in vDrawnBorders contains the point (x,y)
    for (Rectangle2D r : vDrawnBorders) {
      if (r.contains(x, y))
        return new Point2D.Double(r.getX() + (r.getWidth() / 2), r.getY() + (r.getHeight() / 2));
    }
    return null;
  }

  protected void redrawingLines(double x, double y) {
    // moves all the selected shapes containing (x,y) in one of its corners
    for (EditableShape esh : vRedrawingLines)
      esh.changeBorder(x, y);
  }

  private void cleanZoom() {
    zoomX = 0;
    zoomY = 0;
    zoomW = -1;
    zoomH = -1;
    at = new AffineTransform();
    cancelCurrentOperations();
    bSelectingArea = false;
    hep.repaint(0);
  }

  public void cancelCurrentOperations() {
    creatingRect = false;
    creatingEllipse = false;
  }

  public void cut(double x, double y) {
    copy(false);
    clean();
    bMoving = true;
    iniX = x;
    iniY = y;
    finX = iniX;
    finY = iniY;
  }

  public void cut() {
    cut(-1, -1);
  }

  public void copy(boolean needSelected) {
    vCopied.clear();
    for (EditableShape esh : vShapes) {
      esh = (EditableShape) esh.clone();
      if (!needSelected || esh.isSelected()) {
        vCopied.add(esh);
      }
    }
  }

  public void paste() {
    bMoving = true;
    iniX = -1;
    iniY = -1;
    finX = -1;
    finY = -1;
    paste(5, 5);
  }

  public void paste(double x, double y) {
    List<EditableShape> newCopied = new ArrayList<EditableShape>();
    deSelectAll();
    for (EditableShape esh : vCopied) {
      EditableShape copied = (EditableShape) esh.clone();
      copied.transform(AffineTransform.getTranslateInstance(x, y));
      copied.setSelected(true);
      vShapes.add(copied);
      newCopied.add(copied);
      // to avoid overlap of shapes when the user makes "paste" two times
    }
    vCopied = newCopied;
  }

  public void deSelectAll() {
    for (EditableShape esh : vShapes) {
      esh.setSelected(false);
    }
    bSelectedPoint = false;
    hep.repaint(0);
  }

  private EditableShape nearestLine(double x, double y) {
    EditableShape nearest = null;
    double distance = 0;
    double currentDistance;
    for (EditableShape esh : vShapes) {
      currentDistance = esh.distanceTo(x, y);
      if (nearest == null || (currentDistance < distance)) {
        distance = currentDistance;
        nearest = esh;
      }
    }
    return nearest;
  }

  private void clicatISeleccionada(int x, int y, boolean needSelected) {
    // With needSelected=false it's not necessary to have selected a shape in order
    // to drag it leaves in vRedrawingLines the selected lines with one point near
    // the supplied co-ordinates (x,y)
    Point2D redrawingPoint = null;
    vRedrawingLines.clear();
    for (EditableShape esh : vShapes) {
      if ((!needSelected || esh.isSelected()) && esh.hasClickedBorder(x, y, needSelected)) {
        Point2D p = esh.getNearestBorder(x, y);
        if (redrawingPoint == null || redrawingPoint.equals(p)) {
          redrawingPoint = p;
          vRedrawingLines.add(esh);
        }
      }
    }
    vRedrawingLinesBeforeModify = cloneVector(vRedrawingLines);
  }

  private List<EditableShape> cloneVector(List<EditableShape> v) {
    List<EditableShape> vClone = new ArrayList<EditableShape>();
    if (v != null) {
      for (EditableShape es : v) {
        vClone.add((EditableShape) es.clone());
      }
    }
    return vClone;
  }

  private void divideShape(EditableShape specialLine, double x, double y) {
    if (specialLine != null) {
      EditableShape[] shapes = specialLine.divide(x, y);
      if (shapes != null) {
        // -> The two following lines are necessary in order to grant the connection of
        // the resulting shape
        // Dividing two times in the same point can create an independent line out of
        // the shape
        List<EditableShape> vCheckPoint = new ArrayList<EditableShape>();
        vCheckPoint.addAll(vShapes);
        vShapes.remove(specialLine);
        for (int i = 0; i < shapes.length; i++) {
          if (shapes[i] != null)
            vShapes.add(shapes[i]);
        }
        boolean bValidate = validateShape();
        if (!bValidate)
          vShapes = vCheckPoint;
      }
      hep.updateView();
    }
    hep.repaint(0);
  }

  private boolean validateShape() {
    return (getGeneralPath().size() == 1);
  }

  public List<GeneralPath> getGeneralPath() {
    List<GeneralPath> vGpaths = new ArrayList<GeneralPath>();
    GeneralPath currentPolygon = new GeneralPath();
    List<EditableShape> shapes = new ArrayList<EditableShape>();
    shapes.addAll(vShapes);
    if (!(shapes.size() > 0))
      return vGpaths;
    EditableShape esh = shapes.get(0);
    shapes.remove(esh);
    currentPolygon.append(esh, true);
    short notUsedPoint = END;
    // indicates the side of the last shape non-adjacent to anyone
    while (shapes.size() > 0) {
      EditableShape shape = getAdjacent(shapes, esh, notUsedPoint);
      if (shape != null) {
        currentPolygon.append(shape, true);
        notUsedPoint = getNotUsed(esh, shape);
        // returns the point of the shape non-adjacent to "current"
        shapes.remove(shape);
        esh = shape;
      } else {
        vGpaths.add(currentPolygon);
        currentPolygon = new GeneralPath();
        notUsedPoint = END;
        esh = (EditableShape) shapes.get(0);
        shapes.remove(esh);
        currentPolygon.append(esh, true);
      }
    }
    vGpaths.add(currentPolygon);
    return vGpaths;
  }

  private short getNotUsed(EditableShape current, EditableShape shape) {
    // returns the point of the shape non-adjacent to "current"
    if (shape.getInitialPoint().equals(current.getInitialPoint())
        || shape.getInitialPoint().equals(current.getEndPoint()))
      return END;
    else
      return INITIAL;
  }

  private EditableShape getAdjacent(List<EditableShape> shapes, EditableShape sh, short notUsedPoint) {
    Point2D p;
    if (notUsedPoint == INITIAL)
      p = sh.getInitialPoint();
    else
      p = sh.getEndPoint();
    for (EditableShape shape : shapes) {
      if (shape.isAdjacentTo(p))
        return shape;
    }
    return null;
  }

  public boolean hasSelectedDrawnShape(Point2D p) {
    for (int i = 0; i < hep.getNumShapes(); i++) {
      Shape s = hep.getHoles().getShape(i, hep.previewArea);
      if (s.contains(p)) {
        return true;
      }
    }
    return false;
  }

  private double distanceToNearest(double x, double y) {
    EditableShape nearest = nearestLine(x, y);
    if (nearest != null)
      return nearest.distanceTo(x, y);
    else
      return -1;
  }

  public void deleteSelected(boolean isCut) {
    if (hasSelectedPoint()) {
      joinAdjacentsToSelectedPoint();
      bSelectedPoint = false;
    } else {
      List<EditableShape> vShapesCopy = new ArrayList<EditableShape>();
      boolean allSelected = true, noneSelected = true;
      vShapesCopy.addAll(vShapes);
      for (EditableShape esh : vShapesCopy) {
        if (!esh.isSelected())
          allSelected = false;
        else {
          noneSelected = false;
          if (isCut || vShapes.size() >= 4) {
            // Avoid to delete objects when there are only 3 or less elements, unless
            // is a "cut"
            vShapes.remove(esh);
            if (!isCut)
              joinAdjacentsTo(esh, vShapes);
          }
        }
      }
      // allSelected indicates if all the object was selected
      if (allSelected || noneSelected) {
        vShapes.clear();
        this.current = null;
        hep.getHoles().removeShape(hep.currentShape);
        hep.setCurrentShape(hep.getHoles().getNumCells());
      }
    }
  }

  private void joinAdjacentsTo(EditableShape current, List<EditableShape> vShapes) {
    // All the shapes in vShapes will converge in one of the "current" points.
    EditableShape s1 = getAdjacent(vShapes, current, INITIAL);
    if (s1 != null) {
      // Always
      s1.hasClickedBorder(current.getInitialPoint().getX(), current.getInitialPoint().getY(), false);
      // hasClickedBorder marks the shape corner nearest to the supplied point.
      // Calling changeBorder this corner will be modified to the new point.
      s1.changeBorder(current.getEndPoint().getX(), current.getEndPoint().getY());
    }
  }

  private void joinAdjacentsToSelectedPoint() {

    if (vShapes.size() != 1 && vShapes.size() <= 3)
      return;

    EditableShape other = null;
    int count = 0;
    for (EditableShape esh : vShapes) {
      if (esh.hasSelectedBorder()) {
        if (esh instanceof EditableRectangle) {
          Point2D p = esh.getNotSelectedBorder();
          convertToSimpleShapes();
          selectBorder(p.getX(), p.getY());
          joinAdjacentsToSelectedPoint();
          break;
        } else {
          count++;
          if (count == 1)
            other = esh;
          else if (other != null) {
            Point2D p1 = esh.getNotSelectedBorder();
            Point2D p2 = other.getNotSelectedBorder();
            vShapes.add(new EditableLine2D(p1, p2));
            vShapes.remove(esh);
            vShapes.remove(other);
          }
        }
      }
    }
    hep.repaint(0);
  }

  private void setEndToVector(double finX, double finY, List<EditableShape> vRedrawingLines) {
    // approach of all the lines of vRedrawingLines to the point finX, finY
    for (EditableShape esh : vRedrawingLines)
      esh.aproximateNearestBorder(finX, finY);
  }

  public boolean hasSelectedPoint() {
    return bSelectedPoint;
  }

  public List<EditableShape> getSelectedShapes() {
    List<EditableShape> v = new ArrayList<EditableShape>();
    for (EditableShape esh : vShapes) {
      if (esh.isSelected())
        v.add(esh);
    }
    return v;
  }

  public int getNumShapes() {
    return vShapes.size();
  }

  public void deleteCurrent() {
    clean();
    current = null;
  }

  public ShapeData getShapeData() {
    ShapeData sd = null;
    AffineTransform aft = AffineTransform.getScaleInstance((1 / hep.previewArea.getWidth()),
        (1 / hep.previewArea.getHeight()));
    aft.concatenate(AffineTransform.getTranslateInstance(-hep.previewArea.x, -hep.previewArea.y));
    if (getNumShapes() == 1) {
      // Is a rectangle or a ellipse
      EditableShape es = (EditableShape) vShapes.get(0).clone();
      es.transform(aft);
      Shape s;
      if (es instanceof EditableEllipse2D)
        s = ((EditableEllipse2D) es).getEllipse();
      else
        s = es;
      sd = ShapeData.getShapeData(s, null, false);
    } else {
      List<GeneralPath> v = getGeneralPath();
      // Get only the first polygon found (should be unique)
      if (v.size() > 0) {
        GeneralPath gp = (v.get(0));
        Shape s = gp.createTransformedShape(aft);
        sd = ShapeData.getShapeData(s, null);
      }
    }
    return sd;
  }

  public void endPolygon() {
    endPolygon(false, true);
  }

  public void endPolygon(boolean changeShape, boolean updateList) {
    endPolygon(changeShape, updateList, -1);
  }

  public void endPolygon(boolean changeShape, boolean updateList, int iNextShape) {
    ShapeData sd = getShapeData();
    addCurrentDrawnBorders(sd);
    endPolygon(sd, changeShape, updateList, iNextShape);
    if (sd != null)
      clean();
    bSelectedPoint = false;
  }

  private void addCurrentDrawnBorders(ShapeData sd) {
    if (sd != null && hep != null) {
      Shape s = sd.getShape(hep.previewArea);
      vDrawnBorders.addAll(getBorders(s));
    }
  }

  private void removeDrawnBorders(ShapeData sd) {
    Shape s = sd.getShape(hep.previewArea);
    vDrawnBorders.removeAll(getBorders(s));
    // removeAll removes all the instances of the elements passed over the
    // shapedata (only one instance is needed)
  }

  public void endPolygon(ShapeData sd, boolean changeShape, boolean updateList, int iNextShape) {
    // Save the created/modified polygon. changeShape indicates if we "come" from a
    // tab key.
    if (sd != null) {

      addCurrentDrawnBorders(sd);

      if (hep.currentShape < hep.getHoles().getNumCells()) {
        // hep.currentShape has been modified
        hep.getHoles().modifyShape(hep.currentShape, sd);
        hep.updateView();
      } else {
        // A comment has been created
        sd.comment = StrUtils.secureString(sd.comment, "" + hep.currentShape);
        hep.getHoles().addShape(sd);
        hep.updateList();
        hep.updateView();
      }
    }
    int iCurrentShape = hep.currentShape + 1;
    if (changeShape) {
      if (iNextShape >= 0)
        iCurrentShape = iNextShape;
      else
        iCurrentShape = iCurrentShape % hep.getHoles().getNumCells();
    } else
      iCurrentShape = hep.getHoles().getNumCells();
    // Next one will be new
    if (hep.currentShape != iCurrentShape)
      hep.setCurrentShape(iCurrentShape);
  }

  private void aplicateTransformation(AffineTransform aTransf, boolean needSelected) {
    for (EditableShape esh : vShapes) {
      if (!needSelected || esh.isSelected()) {
        esh.transform(aTransf);
      }
    }
  }

  public void move(int xInc, int yInc, boolean needSelected, boolean moveAll) {
    // moveAll indicates if we want to move also the inactive objects
    AffineTransform aTransf = AffineTransform.getTranslateInstance(xInc, yInc);
    aplicateTransformation(aTransf, needSelected);
    hep.repaint(0);
  }

  public void scale(double xInc, double yInc, boolean needSelected, boolean scaleAll) {
    Point2D center = getCenter(scaleAll);
    AffineTransform aTransf = AffineTransform.getTranslateInstance(center.getX(), center.getY());
    aTransf.concatenate(AffineTransform.getScaleInstance(xInc, yInc));
    aTransf.concatenate(AffineTransform.getTranslateInstance(-center.getX(), -center.getY()));
    aplicateTransformation(aTransf, needSelected);
    hep.repaint(0);
  }

  public void rotate(double theta, boolean needSelected, boolean rotateAll) {
    convertToSimpleShapes();
    // If it is a triangle, it will be necessary to convert it to lines in
    // order to rotate the shape
    Point2D center = getCenter(rotateAll);
    AffineTransform aTransf = AffineTransform.getRotateInstance(theta, center.getX(), center.getY());
    aplicateTransformation(aTransf, needSelected);
    hep.repaint(0);
  }

  private Point2D getCenter(boolean cellCenter) {
    // Returns the central point of the edited shape when cellCenter==false,
    // otherwise, returns the center of the cell
    if (!cellCenter) {
      GeneralPath gp = new GeneralPath();
      for (EditableShape esh : vShapes)
        gp.append(esh, false);
      Rectangle2D r = gp.getBounds();
      // to calculate the central point
      return new Point2D.Double(r.getCenterX(), r.getCenterY());
    } else
      return new Point2D.Double(hep.getPreviewPanel().getX(), hep.getPreviewPanel().getY());
  }

  private void convertToSimpleShapes() {
    // If the edited shape is a rectangle or a ellipse, transform it to a set of
    // segments or cubic lines
    for (EditableShape esh : vShapes) {
      if (esh instanceof EditableRectangle) {
        // Rectangular shapes must be converted to simple shapes in order to
        // rotate it
        vShapes.remove(esh);
        EditableShape[] lines = ((EditableRectangle) esh).divide(-1, -1, false);
        // Do no add any point
        for (int i = 0; i < lines.length; i++)
          if (lines[i] != null)
            vShapes.add(lines[i]);
      }
    }
  }

  private EditableShape getSelectedShape(boolean hasToBeALine) {
    // Returns the selected line when there is only one
    EditableShape selected = null;
    int i = 0;
    for (EditableShape esh : vShapes) {
      if (esh.isSelected()) {
        if (!hasToBeALine || esh instanceof EditableLine2D) {
          selected = esh;
          i++;
        } else {
          i = 2;
          // Do nothing
        }
      }
      if (i >= 2)
        break;
    }
    if (i == 1)
      return selected;
    else
      return null;
  }

  public void convertToBezier() {
    EditableShape selected = getSelectedShape(false);
    if (selected != null) {
      double x1 = selected.getInitialPoint().getX();
      double y1 = selected.getInitialPoint().getY();
      double x2 = selected.getEndPoint().getX();
      double y2 = selected.getEndPoint().getY();
      double ctrl1x = x1 + ((x2 - x1) / 3);
      double ctrl2x = x1 + (2 * ((x2 - x1) / 3));
      double ctrl1y = y1 + ((y2 - y1) / 3);
      double ctrl2y = y1 + (2 * ((y2 - y1) / 3));
      EditableCubicCurve2D bez = new EditableCubicCurve2D(x1, y1, ctrl1x, ctrl1y, ctrl2x, ctrl2y, x2, y2);
      bez.setSelected(true);
      vShapes.remove(selected);
      vShapes.add(bez);
    }
  }

  public void convertToQuad() {
    EditableShape selected = getSelectedShape(false);
    if (selected != null) {
      double x1 = selected.getInitialPoint().getX();
      double y1 = selected.getInitialPoint().getY();
      double x2 = selected.getEndPoint().getX();
      double y2 = selected.getEndPoint().getY();
      double ctrlx = x1 + ((x2 - x1) / 2);
      double ctrly = y1 + ((y2 - y1) / 2);
      EditableQuadCurve2D quad = new EditableQuadCurve2D(x1, y1, ctrlx, ctrly, x2, y2);
      quad.setSelected(true);
      vShapes.remove(selected);
      vShapes.add(quad);
    }
  }

  public void convertToLine() {
    EditableShape selected = getSelectedShape(false);
    if (selected != null && !(selected instanceof EditableRectangle)) {
      EditableLine2D line = new EditableLine2D(selected.getInitialPoint(), selected.getEndPoint());
      line.setSelected(true);
      vShapes.remove(selected);
      vShapes.add(line);
    } else if (selected != null && selected instanceof EditableRectangle) {
      // Convert a rectangle into four lines
      vShapes.remove(selected);
      EditableShape[] lines = ((EditableRectangle) selected).divide(-1, -1, false);
      // Do not add any point
      for (int i = 0; i < lines.length; i++)
        if (lines[i] != null)
          vShapes.add(lines[i]);
    }
  }

  public void notifyShapeChanged() {
    for (PointListener pl : vPointListeners) {
      pl.shapeChanged();
    }
  }

  public void addPointListener(PointListener listener) {
    vPointListeners.add(listener);
  }

  public void undoLastMove(List<EditableShape> vRedrawingLines, List<EditableShape> vRedrawingLinesBeforeModify) {
    vShapes.removeAll(vRedrawingLines);
    vShapes.addAll(vRedrawingLinesBeforeModify);
    vRedrawingLines.clear();
  }

  private boolean isIntoArea(List<EditableShape> vShapes, boolean move) {
    boolean isInto = true;
    Rectangle2D r = new Rectangle2D.Double(hep.previewArea.getX() - 1, hep.previewArea.getY() - 1,
        hep.previewArea.getWidth() + 2, hep.previewArea.getHeight() + 2);
    Point2D[] borders;
    for (EditableShape esh : vShapes) {
      if (!isInto)
        break;
      EditableShape es;
      if (!move)
        es = esh;
      else {
        es = (EditableShape) esh.clone();
        es.transform(AffineTransform.getTranslateInstance(finX - iniX, finY - iniY));
      }

      borders = es.getBorders();
      if (borders == null)
        continue;
      for (int j = 0; j < borders.length && isInto; j++)
        isInto = r.contains(borders[j]);
    }
    return isInto;
  }

  private void joinPolygon() {
    if (vShapes.size() >= 2) {
      vShapes.add(new EditableLine2D(lastPoint.getX(), lastPoint.getY(), iniPoint.getX(), iniPoint.getY()));
    } else
      vShapes.clear();
    creatingPolygon = false;
    lastPoint = null;
    iniPoint = null;
    if (bSelectedPoint)
      deselectBorder();
    bSelectedPoint = false;
    hep.setDrawingMode(SELECTING);
  }

  public void mouseDragged(java.awt.event.MouseEvent mouseEvent) {
    if ((mouseEvent.getModifiers() & java.awt.event.MouseEvent.BUTTON1_MASK) == 0)
      return;
    // Button 1 was not pressed
    Point2D mousePoint = getTransformedPoint(mouseEvent.getPoint(), true);

    if (bMoving)
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));

    if (mousePoint.getX() < hep.previewArea.x || mousePoint.getY() < hep.previewArea.y
        || mousePoint.getX() > hep.previewArea.x + hep.previewArea.getWidth()
        || mousePoint.getY() > hep.previewArea.y + hep.previewArea.getHeight()) {
      return;
    }

    vShapeBeforeModify = (drawingMode == SELECTING && !bMoving) ? cloneVector(vRedrawingLines) : cloneVector(vCopied);
    EditableShape near = null;

    Point2D nearDrawn = aproximationToDrawnBorder(mouseEvent.getX(), mouseEvent.getY());
    if (nearDrawn != null && EditableShapeConstants.pointsOnGrid) {
      finX = nearDrawn.getX();
      finY = nearDrawn.getY();
    } else {
      finX = mousePoint.getX();
      finY = mousePoint.getY();
    }

    if (creatingRect || creatingEllipse) {
      near = aproximationToLine(finX, finY);
      hep.getPreviewPanel().repaint(0);
    } else if (bRedrawingLines) {
      redrawingLines(finX, finY);
      near = aproximationToLine(finX, finY, vRedrawingLines);
      // we are over a shape corner not selected near (at less)...
      hep.repaint(0);
    } else if (bMoving || esInterior(finX, finY)) {
      near = nearestLine(finX, finY);
      if (near != null) {
        double d = near.distanceTo(finX, finY);
        if (!bMoving && d > (EditableShapeConstants.selectLength / 2))
          cut(finX, finY);
      }
      hep.repaint(0);
    }
    if (creatingRect || creatingEllipse || bRedrawingLines) {
      if ((near != null || nearDrawn != null) && EditableShapeConstants.pointsOnGrid)
        hep.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      else
        hep.setCursor(cursors[PEN_CURSOR]);
    }
    boolean b = isIntoArea((drawingMode == SELECTING && !bMoving) ? vRedrawingLines : vCopied, (bMoving));
    if (!b) {
      if (drawingMode == SELECTING && !bMoving) {
        undoLastMove(vRedrawingLines, vShapeBeforeModify);
        finX = lastFinX;
        finY = lastFinY;
      }
      if (bMoving) {
        finX = lastFinX;
        finY = lastFinY;
      }
    } else {
      lastFinX = finX;
      lastFinY = finY;
    }

    if (bResizing)
      setResizingCursor(resizingDirection);
  }

  protected boolean esCantonada(double x, double y) {
    EditableShape near = aproximationToLine(x, y);
    return (near != null || aproximationToDrawnBorder(x, y) != null);
  }

  protected boolean esSobreFigura(double x, double y) {
    int minimumDistance = Math.max(Math.max(2, EditableShapeConstants.selectLength / 2),
        EditableShapeConstants.gridWidth);
    double dist = distanceToNearest(x, y);
    return (dist >= 0 && dist < minimumDistance);
  }

  protected boolean esInterior(double x, double y) {
    return (current != null && current.contains(x, y));
  }

  public void mouseMoved(java.awt.event.MouseEvent mouseEvent) {

    double x, y;
    Point2D mousePoint = mouseEvent.getPoint();

    boolean esCantonada = esCantonada(mouseEvent.getPoint().getX(), mouseEvent.getPoint().getY());
    if (drawingMode == NEW_POINT || (esCantonada && EditableShapeConstants.pointsOnGrid)) {
      x = mousePoint.getX();
      y = mousePoint.getY();
    } else {
      x = mousePoint.getX();
      y = mousePoint.getY();
    }

    if (x < hep.previewArea.x || y < hep.previewArea.y || x > hep.previewArea.x + hep.previewArea.getWidth()
        || y > hep.previewArea.y + hep.previewArea.getHeight())
      return;
    if (drawingMode != NEW_POINT) {
      if (esCantonada && (!creatingPolygon || EditableShapeConstants.pointsOnGrid))
        hep.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      else if (creatingPolygon)
        hep.setCursor(cursors[PEN_CURSOR]);
      else if (!bMoving && esSobreFigura(x, y))
        hep.setCursor(cursors[CIRCLE_CURSOR]);
      else if (esInterior(x, y))
        hep.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
      else
        hep.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    if (drawingMode == NEW_POINT) {
      if (esSobreFigura(x, y)) {
        bSpecialLine = true;
        // temporally paint it in another color
        specialLine = nearestLine(x, y);
        hep.setCursor(cursors[PEN_CURSOR]);
        hep.repaint(0);
      } else {
        boolean willRepaint = bSpecialLine;
        bSpecialLine = false;
        hep.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        if (willRepaint)
          hep.repaint(0);
      }
    }
    if (bMoving) {
      if (iniX == -1) {
        deleteSelected(true);
        iniX = x;
        iniY = y;
      }
      finX = x;
      finY = y;
      hep.repaint(0);
    }
    if (creatingPolygon) {
      finX = x;
      finY = y;
      hep.repaint(0);
    }
    if (canResize) {
      if (!bResizing) {
        int resizing = getResizing(mousePoint);
        if (resizing != NO_RESIZING)
          setResizingCursor(resizing);
      } else
        setResizingCursor(resizingDirection);
    }
  }

  protected int getResizing(Point2D mousePoint) {
    if (!canResize)
      return NO_RESIZING;
    ShapeData sd = hep.getHoles().getEnclosingShapeData();
    Rectangle r = hep.getPreviewArea();
    double width = r.getWidth();
    double height = r.getHeight();
    AffineTransform aft = AffineTransform.getTranslateInstance(-hep.previewArea.x, -hep.previewArea.y);
    aft.transform(mousePoint, mousePoint);
    if (mousePoint.getX() == (width - 1) && mousePoint.getY() == (height - 1))
      return SOUTH_EAST;
    else if (mousePoint.getX() == (width - 1))
      return EAST;
    else if (mousePoint.getY() == (height - 1))
      return SOUTH;
    else
      return NO_RESIZING;
  }

  protected void setResizingCursor(int resizing) {
    if (resizing == EAST)
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR), false);
    else if (resizing == SOUTH)
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR), false);
    else if (resizing == SOUTH_EAST)
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR), false);
  }

  protected void selectBorder(double x, double y) {
    for (EditableShape s : vShapes)
      s.selectBorder(x, y);
  }

  protected void deselectBorder() {
    for (EditableShape s : vShapes)
      s.deselectBorder();
  }

  protected boolean removeNullLines(List<EditableShape> vRedrawingLines) {
    // Removes from vRedrawingLines all the lines having the two points in
    // (approximately) the same co-ordinates
    boolean canRemove = false;
    for (EditableShape s : vRedrawingLines) {
      if (canRemove)
        break;
      if (s instanceof EditableLine2D) {
        Point2D[] p = s.getBorders();
        if (p.length > 1) {
          Rectangle r = new Rectangle((int) (p[0].getX()) - (EditableShapeConstants.selectLength / 2),
              (int) (p[0].getY()) - (EditableShapeConstants.selectLength / 2), EditableShapeConstants.selectLength,
              EditableShapeConstants.selectLength);
          if (r.contains(p[1].getX(), p[1].getY())) {
            // This line is prescindible
            if (vShapes.size() >= 4) {
              // Do not delete any element when there are only 3 or les (except if it's a cut)
              canRemove = true;
              vShapes.remove(s);
              joinAdjacentsTo(s, vShapes);
            }
          }
        }
      }
    }
    return canRemove;
  }

  public void mouseClicked(java.awt.event.MouseEvent mouseEvent) {
    Point2D mousePoint = mouseEvent.getPoint();
    boolean bSobreFigura = esSobreFigura(mousePoint.getX(), mousePoint.getY());
    if (drawingMode != NEW_POINT && drawingMode != DRAWING_POLYGON && !bSobreFigura && selectDrawnShape(mousePoint)
        && !creatingPolygon) {
      notifyShapeChanged();
      hep.repaint(0);
    } else if (drawingMode != NEW_POINT && bSobreFigura && !creatingPolygon) {
      EditableShape line = nearestLine(mousePoint.getX(), mousePoint.getY());
      if (line != null) {
        // The caller wants to select a fragment of the polygon
        if (esCantonada(mousePoint.getX(), mousePoint.getY())) {
          Point2D p = line.getNearestBorder(mousePoint.getX(), mousePoint.getY());
          deSelectAll();
          bSelectedPoint = true;
          selectBorder(p.getX(), p.getY());
          hep.repaint(0);
        } else {
          if (bSelectedPoint)
            deselectBorder();
          bSelectedPoint = false;
          if (line.isSelected())
            line.setSelected(false);
          else {
            if ((mouseEvent.getModifiers() & java.awt.event.MouseEvent.SHIFT_MASK) == 0)
              deSelectAll();
            line.setSelected(true);
          }
          notifyShapeChanged();
          hep.repaint(0);
        }
      }
    }
    if (creatingPolygon) {
      if (mouseEvent.getClickCount() == 2)
        joinPolygon();
      else {
        mousePoint = mouseEvent.getPoint();
        EditableShape near = aproximationToLine(mousePoint.getX(), mousePoint.getY(), vRedrawingLines);
        Point2D nearDrawn = null;
        Point2D nearDrawnOther = aproximationToDrawnBorder(mousePoint.getX(), mousePoint.getY());
        if (near != null) {
          // if the ending point is near the mouse, approximate to it
          nearDrawn = near.getNearestBorder(mousePoint.getX(), mousePoint.getY());
        }

        if (nearDrawnOther != null && EditableShapeConstants.pointsOnGrid) {
          finX = nearDrawnOther.getX();
          finY = nearDrawnOther.getY();
        } else {
          mousePoint = getTransformedPoint(mouseEvent.getPoint(), true);
          finX = mousePoint.getX();
          finY = mousePoint.getY();
        }

        if (lastPoint != null) {
          // isn't the first point
          if (nearDrawn != null && iniPoint.getX() == nearDrawn.getX() && iniPoint.getY() == nearDrawn.getY()) {
            // Has clicked over the starting point of the polygon
            if (vShapes.size() >= 2)
              joinPolygon();
          } else {
            if (nearDrawn == null) {
              // Points cannot be repeated
              vShapes.add(new EditableLine2D(lastPoint.getX(), lastPoint.getY(), finX, finY));
              lastPoint = new Point2D.Double(finX, finY);
            }
          }
        } else {
          // it's the first point
          iniPoint = new Point2D.Double(finX, finY);
          lastPoint = iniPoint;
        }
      }
    }
  }

  public void mouseEntered(java.awt.event.MouseEvent mouseEvent) {
  }

  public void mouseExited(java.awt.event.MouseEvent mouseEvent) {
  }

  public void mousePressed(java.awt.event.MouseEvent mouseEvent) {
    Point2D mousePoint = getTransformedPoint(mouseEvent.getPoint(), drawingMode != SELECTING);
    // when selecting, the point doesn't must be in the grid
    int x = (int) mousePoint.getX();
    int y = (int) mousePoint.getY();

    if (canResize) {
      int resizing = getResizing(mousePoint);
      if (resizing != NO_RESIZING) {
        if (drawingMode != SELECTING)
          hep.setDrawingMode(SELECTING);
        bResizing = true;
        resizingDirection = resizing;
      }
    }

    if (x < hep.previewArea.x || y < hep.previewArea.y || x > hep.previewArea.x + hep.previewArea.getWidth()
        || y > hep.previewArea.y + hep.previewArea.getHeight())
      return;
    if (bMoving) {
      // CTRL+X has been pressed while moving a shape
      paste(finX - iniX, finY - iniY);
      bMoving = false;
    }
    iniX = x;
    iniY = y;

    if (drawingMode == SELECTING && !bMoving) {
      clicatISeleccionada(x, y, false);
      // false: drag is possible despite of having the shape selected or unselected
      if (vRedrawingLines.size() > 0)
        bRedrawingLines = true;
      // Redraw lines when clicking on a corner
    } else if ((drawingMode == DRAWING_RECT || drawingMode == DRAWING_ELLIPSE || drawingMode == DRAWING_POLYGON)
        && !hasSelectedDrawnShape(mouseEvent.getPoint())) {
      if (drawingMode == DRAWING_RECT)
        creatingRect = true;
      else if (drawingMode == DRAWING_ELLIPSE)
        creatingEllipse = true;
      else
        creatingPolygon = true;
      EditableShape near = aproximationToLine(x, y);
      Point2D pNear = aproximationToDrawnBorder(x, y);
      if (near != null) {
        pNear = near.getNearestBorder(x, y);
      }
      if (pNear != null) {
        iniX = pNear.getX();
        iniY = pNear.getY();
      } else {
        iniX = x;
        iniY = y;
      }
      finX = iniX;
      finY = iniY;
      hep.repaint(0);
    } else if (drawingMode == NEW_POINT) {
      EditableShape lineToDivide = specialLine;
      EditableShape near = aproximationToLine(x, y, null);
      Point2D nearDrawn = null;
      boolean isSelect = false;
      if (near != null)
        nearDrawn = near.getNearestBorder(x, y);
      if (drawingMode == NEW_POINT
          && (lineToDivide != null && bSpecialLine && (nearDrawn == null || lineToDivide instanceof EditableEllipse2D
              || lineToDivide instanceof EditableCubicCurve2D || lineToDivide instanceof EditableQuadCurve2D))) {
        // A point is added only when the current point is not over another one
        divideShape(lineToDivide, x, y);
      } else {
        isSelect = selectDrawnShape(mouseEvent.getPoint());
      }
      if (!isSelect) {
        // A point has been added, or a click has been done out of any shape
        hep.setDrawingMode(SELECTING);
      }
    }
  }

  public void mouseReleased(java.awt.event.MouseEvent mouseEvent) {
    if ((mouseEvent.getModifiers() & java.awt.event.MouseEvent.BUTTON1_MASK) == 0) {
      // The button 1 was not pressed
      return;
    }
    if (bMoving) {
      paste(finX - iniX, finY - iniY);
      bMoving = false;
      deSelectAll();
    }

    Point2D mousePoint = mouseEvent.getPoint();

    EditableShape near = aproximationToLine(mousePoint.getX(), mousePoint.getY(), vRedrawingLines);
    Point2D nearDrawnPropi = null;
    Point2D nearDrawn = aproximationToDrawnBorder(mousePoint.getX(), mousePoint.getY());
    // Corner of a non-active polygon
    if (near != null || nearDrawn != null)
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    else
      hep.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    if (near != null) {
      // if there is any nearest point, approximate the ending point to it
      nearDrawnPropi = near.getNearestBorder(mousePoint.getX(), mousePoint.getY());
      // Corner of the active polygon
    }

    mousePoint = getTransformedPoint(mouseEvent.getPoint(), true);
    if (!(mousePoint.getX() < hep.previewArea.x || mousePoint.getY() < hep.previewArea.y
        || mousePoint.getX() > hep.previewArea.x + hep.previewArea.getWidth()
        || mousePoint.getY() > hep.previewArea.y + hep.previewArea.getHeight())) {
      if (nearDrawn != null && EditableShapeConstants.pointsOnGrid) {
        // Only when approaching
        finX = nearDrawn.getX();
        finY = nearDrawn.getY();
      } else {
        finX = mousePoint.getX();
        // This point is maintained as long as the pointer remains into the drawing area
        finY = mousePoint.getY();
      }
    }

    if ((drawingMode == SELECTING && !bMoving && nearDrawnPropi != null)
        || (!creatingPolygon && vShapes.size() > 1 && !validateShape())) {
      boolean canRemove = removeNullLines(vRedrawingLines);
      if (!canRemove) {
        undoLastMove(vRedrawingLines, vRedrawingLinesBeforeModify);
        finX = iniX;
        finY = iniY;
      }
    } else if (bRedrawingLines) {
      setEndToVector(finX, finY, vRedrawingLines);
      bRedrawingLines = false;
      vRedrawingLines.clear();
      vRedrawingLinesBeforeModify.clear();
    }
    if (creatingRect) {
      creatingRect = false;
      vShapes.add(new EditableRectangle((int) iniX, (int) iniY, (int) (finX - iniX), (int) (finY - iniY)));
      if (hep.currentShape >= hep.getHoles().getNumCells()) {
        // Reserve space for the new rectangle when confirmed(in order to give it a
        // name)
        ShapeData sd = new ShapeData();
        sd.comment = "" + hep.currentShape;
        hep.getHoles().addShape(sd);
        hep.updateList();
      }
    }
    if (creatingEllipse) {
      creatingEllipse = false;
      vShapes.add(new EditableEllipse2D((int) iniX, (int) iniY, (int) (finX - iniX), (int) (finY - iniY)));
      if (hep.currentShape >= hep.getHoles().getNumCells()) {
        // Reserve space for the new rectangle when confirmed(in order to give it a
        // name)
        ShapeData sd = new ShapeData();
        sd.comment = "" + hep.currentShape;
        hep.getHoles().addShape(sd);
        hep.updateList();
      }
    }

    if (bResizing) {
      if (resizingDirection != NO_RESIZING) {
        double x = mousePoint.getX();
        double y = mousePoint.getY();
        double xInc = x - iniX;
        double yInc = y - iniY;
        if (resizingDirection == EAST)
          yInc = 0;
        else if (resizingDirection == SOUTH)
          xInc = 0;
        hep.incDrawingArea(xInc, yInc);
      }
      bResizing = false;
    }

    ShapeData sd = getShapeData();
    current = (sd != null) ? sd.getShape(hep.previewArea) : null;
    // Update the modifications to the shape
    bSelectingArea = false;
    hep.repaint(0);
    if (!creatingPolygon)
      notifyShapeChanged();
  }

  public class KeyHandler extends KeyAdapter {
    @Override
    public void keyPressed(KeyEvent e) {
      if (e.getKeyCode() == KeyEvent.VK_DELETE) {
        deleteSelected(false);
        hep.shapeChanged();
      }
    }
  }

  private double viewIniX = -1;
  private double viewIniY = -1;
}
