@Override
  public void draw(
      Graphics2D g2d,
      ComponentState componentState,
      boolean outlineMode,
      Project project,
      IDrawingObserver drawingObserver) {
    Shape clip = g2d.getClip();
    if (checkPointsClipped(clip)
        && !clip.contains(firstPoint.x, secondPoint.y)
        && !clip.contains(secondPoint.x, firstPoint.y)) {
      return;
    }
    super.draw(g2d, componentState, outlineMode, project, drawingObserver);
    if (componentState != ComponentState.DRAGGING) {
      if (alpha < MAX_ALPHA) {
        g2d.setComposite(
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
      }
      Point p = new Point(firstPoint);
      int diameter = getClosestOdd((int) PAD_SIZE.convertToPixels());
      int holeDiameter = getClosestOdd((int) HOLE_SIZE.convertToPixels());
      int spacing = (int) this.spacing.convertToPixels();

      while (p.y < secondPoint.y - spacing) {
        p.x = firstPoint.x;
        p.y += spacing;
        while (p.x < secondPoint.x - spacing - diameter) {
          p.x += spacing;
          g2d.setColor(padColor);
          g2d.fillOval(p.x - diameter / 2, p.y - diameter / 2, diameter, diameter);
          g2d.setColor(padColor.darker());
          g2d.drawOval(p.x - diameter / 2, p.y - diameter / 2, diameter, diameter);
          g2d.setColor(Constants.CANVAS_COLOR);
          g2d.fillOval(p.x - holeDiameter / 2, p.y - holeDiameter / 2, holeDiameter, holeDiameter);
          g2d.setColor(padColor.darker());
          g2d.drawOval(p.x - holeDiameter / 2, p.y - holeDiameter / 2, holeDiameter, holeDiameter);
        }
      }
      super.drawCoordinates(g2d, spacing);
    }
  }
  @Override
  public void draw(
      Graphics2D g2d,
      ComponentState componentState,
      boolean outlineMode,
      Project project,
      IDrawingObserver drawingObserver) {
    if (checkPointsClipped(g2d.getClip())) {
      return;
    }
    int pinSize = (int) PIN_SIZE.convertToPixels() / 2 * 2;
    Area mainArea = getBody();
    Composite oldComposite = g2d.getComposite();
    if (alpha < MAX_ALPHA) {
      g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
    }
    g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
    g2d.fill(mainArea);
    g2d.setComposite(oldComposite);
    Color finalBorderColor;
    Theme theme =
        (Theme)
            ConfigurationManager.getInstance()
                .readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
    if (outlineMode) {
      finalBorderColor =
          componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
              ? SELECTION_COLOR
              : theme.getOutlineColor();
    } else {
      finalBorderColor =
          componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
              ? SELECTION_COLOR
              : borderColor;
    }
    g2d.setColor(finalBorderColor);
    g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
    g2d.draw(mainArea);

    for (Point point : controlPoints) {
      if (!outlineMode) {
        g2d.setColor(PIN_COLOR);
        g2d.fillOval(point.x - pinSize / 2, point.y - pinSize / 2, pinSize, pinSize);
      }
      g2d.setColor(outlineMode ? theme.getOutlineColor() : PIN_BORDER_COLOR);
      g2d.drawOval(point.x - pinSize / 2, point.y - pinSize / 2, pinSize, pinSize);
    }

    // Draw label.
    g2d.setFont(LABEL_FONT);
    Color finalLabelColor;
    if (outlineMode) {
      finalLabelColor =
          componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
              ? LABEL_COLOR_SELECTED
              : theme.getOutlineColor();
    } else {
      finalLabelColor =
          componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
              ? LABEL_COLOR_SELECTED
              : getLabelColor();
    }
    g2d.setColor(finalLabelColor);
    String label = (getDisplay() == Display.NAME) ? getName() : getValue();
    FontMetrics fontMetrics = g2d.getFontMetrics(g2d.getFont());
    Rectangle2D rect = fontMetrics.getStringBounds(label, g2d);
    int textHeight = (int) (rect.getHeight());
    int textWidth = (int) (rect.getWidth());
    // Center text horizontally and vertically
    Rectangle bounds = mainArea.getBounds();
    int x = bounds.x + (bounds.width - textWidth) / 2;
    int y = bounds.y + (bounds.height - textHeight) / 2 + fontMetrics.getAscent();
    g2d.drawString(label, x, y);
  }
  public Area getBody() {
    if (body == null) {
      int x = (controlPoints[0].x + controlPoints[1].x + controlPoints[2].x) / 3;
      int y = (controlPoints[0].y + controlPoints[1].y + controlPoints[2].y) / 3;
      int bodyDiameter = getClosestOdd(BODY_DIAMETER.convertToPixels());
      int bodyLength = getClosestOdd(BODY_LENGTH.convertToPixels());
      int edgeRadius = (int) EDGE_RADIUS.convertToPixels();

      if (folded) {
        switch (orientation) {
          case DEFAULT:
            body =
                new Area(
                    new RoundRectangle2D.Double(
                        x - bodyLength,
                        y - bodyDiameter / 2,
                        bodyLength,
                        bodyDiameter,
                        edgeRadius,
                        edgeRadius));
            body.add(
                new Area(
                    new Rectangle2D.Double(
                        x - bodyLength / 2, y - bodyDiameter / 2, bodyLength / 2, bodyDiameter)));
            break;
          case _90:
            body =
                new Area(
                    new RoundRectangle2D.Double(
                        x - bodyDiameter / 2,
                        y - bodyLength,
                        bodyDiameter,
                        bodyLength,
                        edgeRadius,
                        edgeRadius));
            body.add(
                new Area(
                    new Rectangle2D.Double(
                        x - bodyDiameter / 2, y - bodyLength / 2, bodyDiameter, bodyLength / 2)));
            break;
          case _180:
            body =
                new Area(
                    new RoundRectangle2D.Double(
                        x, y - bodyDiameter / 2, bodyLength, bodyDiameter, edgeRadius, edgeRadius));
            body.add(
                new Area(
                    new Rectangle2D.Double(x, y - bodyDiameter / 2, bodyLength / 2, bodyDiameter)));
            break;
          case _270:
            body =
                new Area(
                    new RoundRectangle2D.Double(
                        x - bodyDiameter / 2, y, bodyDiameter, bodyLength, edgeRadius, edgeRadius));
            body.add(
                new Area(
                    new Rectangle2D.Double(x - bodyDiameter / 2, y, bodyDiameter, bodyLength / 2)));
            break;
          default:
            throw new RuntimeException("Unexpected orientation: " + orientation);
        }
      } else {
        body =
            new Area(
                new Ellipse2D.Double(
                    x - bodyDiameter / 2, y - bodyDiameter / 2, bodyDiameter, bodyDiameter));
      }
    }
    return body;
  }
 public Area[] getBody() {
   if (body == null) {
     body = new Area[2];
     int x = controlPoints[0].x;
     int y = controlPoints[0].y;
     int thickness = getClosestOdd(THICKNESS.convertToPixels());
     int width;
     int height;
     int pinSpacing = (int) this.pinSpacing.convertToPixels();
     Area indentation = null;
     int indentationSize = getClosestOdd(INDENT_SIZE.convertToPixels());
     switch (orientation) {
       case DEFAULT:
         width = thickness;
         height = pinCount.getValue() * pinSpacing;
         x -= thickness / 2;
         y -= pinSpacing / 2;
         indentation =
             new Area(
                 new Ellipse2D.Double(
                     x + width / 2 - indentationSize / 2,
                     y - indentationSize / 2,
                     indentationSize,
                     indentationSize));
         break;
       case _90:
         width = pinCount.getValue() * pinSpacing;
         height = thickness;
         x -= (pinSpacing / 2) + width - pinSpacing;
         y -= thickness / 2;
         indentation =
             new Area(
                 new Ellipse2D.Double(
                     x + width - indentationSize / 2,
                     y + height / 2 - indentationSize / 2,
                     indentationSize,
                     indentationSize));
         break;
       case _180:
         width = thickness;
         height = pinCount.getValue() * pinSpacing;
         x -= thickness / 2;
         y -= (pinSpacing / 2) + height - pinSpacing;
         indentation =
             new Area(
                 new Ellipse2D.Double(
                     x + width / 2 - indentationSize / 2,
                     y + height - indentationSize / 2,
                     indentationSize,
                     indentationSize));
         break;
       case _270:
         width = pinCount.getValue() * pinSpacing;
         height = thickness;
         x -= pinSpacing / 2;
         y -= thickness / 2;
         indentation =
             new Area(
                 new Ellipse2D.Double(
                     x - indentationSize / 2,
                     y + height / 2 - indentationSize / 2,
                     indentationSize,
                     indentationSize));
         break;
       default:
         throw new RuntimeException("Unexpected orientation: " + orientation);
     }
     body[0] =
         new Area(new RoundRectangle2D.Double(x, y, width, height, EDGE_RADIUS, EDGE_RADIUS));
     body[1] = indentation;
     if (indentation != null) {
       indentation.intersect(body[0]);
     }
   }
   return body;
 }