gt:       return ID + buffer.toString();
context: /*
   * $Id: NonRectangular.java 6014 2009-09-14 20:18:21Z uckelman $
   *
   * Copyright (c) 2003 by Rodney Kinney
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Library General Public
   * License (LGPL) as published by the Free Software Foundation.
   *
   * This library 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
   * Library General Public License for more details.
   *
   * You should have received a copy of the GNU Library General Public
   * License along with this library; if not, copies are available
   * at http://www.opensource.org.
   */
  package VASSAL.counters;
  
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Cursor;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.Image;
  import java.awt.Rectangle;
  import java.awt.Shape;
  import java.awt.Window;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Area;
  import java.awt.geom.GeneralPath;
  import java.awt.geom.PathIterator;
  import java.awt.image.BufferedImage;
  import java.util.HashMap;
  import java.util.StringTokenizer;
  import javax.swing.BoxLayout;
  import javax.swing.JPanel;
  import javax.swing.KeyStroke;
  import javax.swing.SwingUtilities;
  import javax.swing.border.TitledBorder;
  
  import VASSAL.build.module.documentation.HelpFile;
  import VASSAL.command.Command;
  import VASSAL.tools.image.ImageUtils;
  import VASSAL.tools.imageop.Op;
  
  /**
   * A trait for assigning an arbitrary shape to a {@link GamePiece}
   * 
   * @see GamePiece#getShape
   */
  public class NonRectangular extends Decorator implements EditablePiece {
    public static final String ID = "nonRect;";
    private static HashMap<String,Shape> shapeCache = new HashMap<String,Shape>();
  
    private String type;
    private Shape shape;
  
    public NonRectangular() {
      this(ID, null);
    }
  
    public NonRectangular(String type, GamePiece inner) {
      mySetType(type);
      setInner(inner);
    }
  
    public void mySetState(String newState) {
    }
  
    public String myGetState() {
      return "";
    }
  
    public String myGetType() {
      return type;
    }
  
    protected KeyCommand[] myGetKeyCommands() {
      return new KeyCommand[0];
    }
  
    public Command myKeyEvent(KeyStroke stroke) {
      return null;
    }
  
    public void draw(Graphics g, int x, int y, Component obs, double zoom) {
      piece.draw(g, x, y, obs, zoom);
    }
  
    public Rectangle boundingBox() {
      return piece.boundingBox();
    }
  
    public Shape getShape() {
      return shape != null ? shape : piece.getShape();
    }
  
    public String getName() {
      return piece.getName();
    }
  
    public String getDescription() {
      return "Non-Rectangular";
    }
  
    public void mySetType(String type) {
      this.type = type;
      final String shapeSpec = type.substring(ID.length());
      shape = buildPath(shapeSpec);
    }
  
    private Shape buildPath(String spec) {
      Shape sh = shapeCache.get(spec);
  
      if (sh == null) {
        final GeneralPath path = new GeneralPath();
        final StringTokenizer st = new StringTokenizer(spec, ",");
        if (st.hasMoreTokens()) {        
          while (st.hasMoreTokens()) {
            final String token = st.nextToken();
            switch (token.charAt(0)) {
            case 'c':
              path.closePath();
              break;
            case 'm':
              path.moveTo(Integer.parseInt(st.nextToken()),
                          Integer.parseInt(st.nextToken()));
              break;
            case 'l':
              path.lineTo(Integer.parseInt(st.nextToken()),
                          Integer.parseInt(st.nextToken()));
              break;
            }
          }
          sh = new Area(path);       
          shapeCache.put(spec, sh);
        }
      }
  
      return sh;
    }
  
    public HelpFile getHelpFile() {
      return null;
    }
  
    public PieceEditor getEditor() {
      return new Ed(this);
    }
  
    private class Ed implements PieceEditor {
      private Shape shape;
      private JPanel controls;
  
      private Ed(NonRectangular p) {
        shape = p.shape;
        controls = new JPanel();
        controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS));
  
        final JPanel shapePanel = new JPanel() {
          private static final long serialVersionUID = 1L;
  
          public void paint(Graphics g) {
            final Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.white);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            if (shape != null) {
              g2d.translate(getWidth() / 2, getHeight() / 2);
              g2d.setColor(Color.black);
              g2d.fill(shape);
            }
          }
  
          public Dimension getPreferredSize() {
            final Dimension d = shape == null
              ? new Dimension(60, 60) : shape.getBounds().getSize();
            d.width = Math.max(d.width, 60);
            d.height = Math.max(d.height, 60);
            return d;
          }
        };
        controls.add(shapePanel);
  
        final ImagePicker picker = new ImagePicker() {
          private static final long serialVersionUID = 1L;
  
          public void setImageName(String name) {
            super.setImageName(name);
  
            final Image img = Op.load(name).getImage();
            if (img != null) setShapeFromImage(img);
          }
        };
        picker.setBorder(new TitledBorder("Use image shape"));
        controls.add(picker);
      }
  
      public void setShapeFromImage(Image im) {
        controls.getTopLevelAncestor()
                .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
  
        final BufferedImage bi = ImageUtils.toBufferedImage(im);
        final int w = bi.getWidth();
        final int h = bi.getHeight();
        final int[] pixels = bi.getRGB(0, 0, w, h, new int[w*h], 0, w);
  
        final Area outline = new Area();
        for (int j = 0; j < h; ++j) {
          for (int i = 0; i < w; ++i) {
            if (((pixels[i + j*w] >>> 24) & 0xff) > 0) {
              outline.add(new Area(new Rectangle(i, j, 1, 1)));
            }
          }
        }
  
  // FIXME: should be 2.0 to avoid integer arithemtic?
        shape = AffineTransform.getTranslateInstance(-w / 2, -h / 2)
                               .createTransformedShape(outline);
  
        final  Window wd = SwingUtilities.getWindowAncestor(controls);
        if (wd != null) wd.pack();
  
        controls.getTopLevelAncestor().setCursor(null);
      }
  
      public Component getControls() {
        return controls;
      }
  
      public String getType() {
        final StringBuilder buffer = new StringBuilder();
        if (shape != null) {
          final PathIterator it = shape.getPathIterator(new AffineTransform());
          final float[] pts = new float[6];
          while (!it.isDone()) {
            switch (it.currentSegment(pts)) {
            case PathIterator.SEG_MOVETO:
              buffer.append('m')
                    .append(',')
                    .append(Math.round(pts[0]))
                    .append(',')
                    .append(Math.round(pts[1]));
              break;
            case PathIterator.SEG_LINETO:
            case PathIterator.SEG_CUBICTO:
            case PathIterator.SEG_QUADTO:
              buffer.append('l')
                    .append(',')
                    .append(Math.round(pts[0]))
                    .append(',')
                    .append(Math.round(pts[1]));
              break;
            case PathIterator.SEG_CLOSE:
              buffer.append('c');
              break;
            }
            it.next();
            if (!it.isDone()) {
              buffer.append(',');
            }
          }
        }
  