/** @param mouseLoc the mouse location, relative to the panel (pre-transform) */
        private Selection getSelection(ShapeCreationPanel scp, Point2D mouseLoc) {
          AffineTransform tx = scp.getTransform();

          CubicPath[] paths = getCubicPaths(scp);
          Integer shapeIntersection = null;
          int r = scp.getHandleSize() / 2;
          Active active = scp.getHandlesActive();
          for (int shapeIndex = paths.length - 1; shapeIndex >= 0; shapeIndex--) {
            if (active.supports(scp, shapeIndex)) {
              CubicPath path = paths[shapeIndex];
              for (int nodeIndex = 0; nodeIndex < path.getNodeCount(); nodeIndex++) {
                Point2D p2 = path.getPrevControlForNode(nodeIndex, null);
                if (p2 != null) {
                  tx.transform(p2, p2);
                  if (hit(scp, mouseLoc, p2)) {
                    return new Selection(shapeIndex, nodeIndex, Handle.PREVIOUS_CONTROL);
                  }
                }
                p2 = path.getNode(nodeIndex, null);
                if (p2 != null) {
                  tx.transform(p2, p2);
                  if (hit(scp, mouseLoc, p2)) {
                    return new Selection(shapeIndex, nodeIndex, Handle.PRIMARY);
                  }
                }
                p2 = path.getNextControlForNode(nodeIndex, null);
                if (p2 != null) {
                  tx.transform(p2, p2);
                  if (hit(scp, mouseLoc, p2)) {
                    return new Selection(shapeIndex, nodeIndex, Handle.NEXT_CONTROL);
                  }
                }
              }
              if (shapeIntersection == null) {
                Shape outline =
                    new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)
                        .createStrokedShape(path);
                if (outline.intersects(mouseLoc.getX() - r, mouseLoc.getY() - r, 2 * r, 2 * r)) {
                  shapeIntersection = shapeIndex;
                }
              }
            }
          }

          return getSelectedShape(scp, mouseLoc);
        }
        @Override
        public void mousePressed(MouseEvent e) {
          ShapeCreationPanel scp = (ShapeCreationPanel) e.getComponent();
          scp.requestFocus();
          boolean isCreating = ShapeCreationPanel.MODE_CREATE.equals(scp.getMode());
          if (e.getClickCount() > 1 && isCreating) {
            scp.setMode(ShapeCreationPanel.MODE_DEFAULT);
            return;
          }

          lastUntransformedX = e.getX();
          lastUntransformedY = e.getY();
          Point2D p = new Point2D.Double(e.getX(), e.getY());
          try {
            scp.getTransform().createInverse().transform(p, p);
          } catch (NoninvertibleTransformException e2) {
            throw new RuntimeException(e2);
          }
          float x = (float) p.getX();
          float y = (float) p.getY();

          clickX = x;
          clickY = y;

          if (isCreating) {
            int selectedShape = scp.getSelectionModel().getSelection().getShapeIndex();
            if (selectedShape == -1) {
              GeneralPath path = new GeneralPath();
              path.moveTo(x, y);
              int newIndex = scp.getDataModel().addShape(path);
              scp.getSelectionModel().select(newIndex, 0, Handle.NEXT_CONTROL);
            } else {
              /**
               * We didn't give a next control point to the *last* node yet. Now we do. If the last
               * node in a path has a next control point: that path will be closed.
               */
              CubicPath[] paths = getCubicPaths(scp);
              CubicPath path = paths[selectedShape];
              int i = path.getNodeCount() - 1;
              Point2D lastPoint = path.getNode(i, null);
              Point2D lastPointCtrlPoint = path.getPrevControlForNode(i, null);
              if (lastPointCtrlPoint == null) lastPointCtrlPoint = lastPoint;

              double dx = lastPoint.getX() - lastPointCtrlPoint.getX();
              double dy = lastPoint.getY() - lastPointCtrlPoint.getY();
              path.setNextControlForNode(
                  i, new Point2D.Double(lastPoint.getX() + dx, lastPoint.getY() + dy));

              path.lineTo(x, y);

              changingDataModel.add(Thread.currentThread());
              try {
                scp.getDataModel().setShape(selectedShape, path);
              } finally {
                changingDataModel.remove(Thread.currentThread());
              }
            }
          } else {
            Selection selection = getSelection(scp, e.getPoint());
            scp.getSelectionModel().select(selection);
          }
        }
  @Override
  protected void paintControls(Graphics2D g, ShapeCreationPanel scp) {
    g = (Graphics2D) g.create();

    Rectangle2D r = new Rectangle2D.Float();
    Ellipse2D e = new Ellipse2D.Float();
    Line2D line = new Line2D.Float();
    double z = ((double) scp.getHandleSize()) / 2.0;

    AffineTransform tx = scp.getTransform();

    try {
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g.setColor(Color.black);
      g.setStroke(new BasicStroke(1));

      CubicPath[] paths = getCubicPaths(scp);
      Selection selection = scp.getSelectionModel().getSelection();
      Selection indication = scp.getSelectionModel().getIndication();
      for (int shapeIndex = 0; shapeIndex < paths.length; shapeIndex++) {
        if (scp.getHandlesActive().supports(scp, shapeIndex)) {
          CubicPath path = paths[shapeIndex];

          if (path != null && path.isEmpty() == false) {
            for (int nodeIndex = 0; nodeIndex < path.getNodeCount(); nodeIndex++) {
              Point2D nodePoint = path.getNode(nodeIndex, null);
              nodePoint = tx.transform(nodePoint, null);

              Point2D p = path.getPrevControlForNode(nodeIndex, null);

              if (p != null) {
                p = tx.transform(p, null);

                g.setColor(Color.lightGray);
                line.setLine(nodePoint, p);
                g.draw(line);

                e.setFrame(p.getX() - z, p.getY() - z, 2 * z, 2 * z);

                if (selection.getShapeIndex() == shapeIndex
                    && selection.getNodeIndex() == nodeIndex
                    && Handle.PREVIOUS_CONTROL.equals(selection.getHandle())) {
                  g.setColor(Color.black);
                } else if (indication.getShapeIndex() == shapeIndex
                    && indication.getNodeIndex() == nodeIndex
                    && Handle.PREVIOUS_CONTROL.equals(indication.getHandle())) {
                  g.setColor(Color.gray);
                } else {
                  g.setColor(Color.white);
                }
                g.fill(e);
                g.setColor(Color.black);
                g.draw(e);
              }

              p = path.getNextControlForNode(nodeIndex, null);
              if (p != null) {
                p = tx.transform(p, null);

                g.setColor(Color.lightGray);
                line.setLine(nodePoint, p);
                g.draw(line);

                e.setFrame(p.getX() - z, p.getY() - z, 2 * z, 2 * z);

                if (selection.getShapeIndex() == shapeIndex
                    && selection.getNodeIndex() == nodeIndex
                    && Handle.NEXT_CONTROL.equals(selection.getHandle())) {
                  g.setColor(Color.black);
                } else if (indication.getShapeIndex() == shapeIndex
                    && indication.getNodeIndex() == nodeIndex
                    && Handle.NEXT_CONTROL.equals(indication.getHandle())) {
                } else {
                  g.setColor(Color.white);
                }
                g.fill(e);
                g.setColor(Color.black);
                g.draw(e);
              }

              r.setFrame(nodePoint.getX() - z, nodePoint.getY() - z, 2 * z, 2 * z);

              if (selection.getShapeIndex() == shapeIndex
                  && selection.getNodeIndex() == nodeIndex
                  && Handle.PRIMARY.equals(selection.getHandle())) {
                g.setColor(Color.black);
              } else if (indication.getShapeIndex() == shapeIndex
                  && indication.getNodeIndex() == nodeIndex
                  && Handle.PRIMARY.equals(indication.getHandle())) {
                g.setColor(Color.gray);
              } else {
                g.setColor(Color.white);
              }
              g.fill(r);
              g.setColor(Color.black);
              g.draw(r);
            }
          }
        }
      }
    } finally {
      g.dispose();
    }
  }