<commit_before>/*
 * $Id$
 *
 * Copyright (c) 2000-2003 by <NAME>
 *
 * 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.AlphaComposite;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

import VASSAL.build.GameModule;
import VASSAL.build.module.Map;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.Drawable;
import VASSAL.command.ChangeTracker;
import VASSAL.command.Command;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.HotKeyConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.PieceI18nData;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.imageop.GamePieceOp;
import VASSAL.tools.imageop.Op;
import VASSAL.tools.imageop.RotateScaleOp;

/**
 * A Decorator that rotates a GamePiece to an arbitrary angle
 */
public class FreeRotator extends Decorator
                         implements EditablePiece,
                                    MouseListener,
                                    MouseMotionListener,
                                    Drawable {
  public static final String ID = "rotate;";
  
  public static final String FACING = "_Facing";

  public static final String DEGREES = "_Degrees";

  public static final double PI_180 = Math.PI / 180.0;

  protected KeyCommand setAngleCommand;
  protected KeyCommand rotateCWCommand;
  protected KeyCommand rotateCCWCommand;
  protected KeyCommand[] commands;
  protected KeyStroke setAngleKey;
  protected String setAngleText = "Rotate";
  protected KeyStroke rotateCWKey;
  protected String rotateCWText = "Rotate CW";
  protected KeyStroke rotateCCWKey;
  protected String rotateCCWText = "Rotate CCW";
  protected String name = "Rotate";

  // for Random Rotate
  protected KeyCommand rotateRNDCommand;
  protected String rotateRNDText = "";
  protected KeyStroke rotateRNDKey;
  // END for Random Rotate

  protected boolean useUnrotatedShape;

  protected double[] validAngles = new double[] {0.0};
  protected int angleIndex = 0;

  @Deprecated protected java.util.Map<Double,Image> images =
    new HashMap<Double,Image>();
  protected java.util.Map<Double,Rectangle> bounds =
    new HashMap<Double,Rectangle>();
  @Deprecated protected PieceImage unrotated;
  protected GamePieceOp gpOp;
  protected java.util.Map<Double,RotateScaleOp> rotOp =
    new HashMap<Double,RotateScaleOp>();

  protected double tempAngle, startAngle;
  protected Point pivot;
  protected boolean drawGhost;

  public FreeRotator() {
    // modified for random rotation (added two ; )
    this(ID + "6;];[;Rotate CW;Rotate CCW;;;;", null);
  }

  public FreeRotator(String type, GamePiece inner) {
    mySetType(type);
    setInner(inner);
  }

  public String getName() {
    return piece.getName();
  }

  public void setInner(GamePiece p) {
    gpOp = p == null ? null : Op.piece(p);
    super.setInner(p);
  }

  private double centerX() {
    // The center is not on a vertex for pieces with odd widths.
    return (piece.boundingBox().width % 2) / 2.0;
  }

  private double centerY() {
    // The center is not on a vertex for pieces with odd heights.
    return (piece.boundingBox().height % 2) / 2.0;
  }

  public Rectangle boundingBox() {
    final Rectangle b = piece.boundingBox();
    final double angle = getAngle();

    if (angle == 0.0) {
      return b;
    }

    Rectangle r;
    if (gpOp.isChanged() || (r = bounds.get(angle)) == null) {

      r = AffineTransform.getRotateInstance(getAngleInRadians(),
                                            centerX(),
                                            centerY())
                         .createTransformedShape(b).getBounds();
      bounds.put(angle, r);
    }
    return new Rectangle(r);
  }

  public double getAngle() {
    return useUnrotatedShape ? 0.0 : validAngles[angleIndex];
  }
  
  public double getCumulativeAngle() {  
    double angle = getAngle();
    // Add cumulative angle of any other FreeRotator trait in this piece
    FreeRotator nextRotation = (FreeRotator) Decorator.getDecorator(getInner(), FreeRotator.class);
    if (nextRotation != null) {
      angle += nextRotation.getCumulativeAngle();
    }
    return angle;
  }
  
  public double getCumulativeAngleInRadians() {
    return -PI_180 * getCumulativeAngle();
  }

  public void setAngle(double angle) {
    if (validAngles.length == 1) {
      validAngles[angleIndex] = angle;
    }
    else {
      // Find nearest valid angle
      int newIndex = angleIndex;
      double minDist = Math.abs((validAngles[angleIndex] - angle + 360) % 360);
      for (int i = 0; i < validAngles.length; ++i) {
        if (minDist > Math.abs((validAngles[i] - angle + 360) % 360)) {
          newIndex = i;
          minDist = Math.abs((validAngles[i] - angle + 360) % 360);
        }
      }
      angleIndex = newIndex;
    }
  }

  /** @deprecated Use {@link boundingBox()} instead. */
  @Deprecated
  public Rectangle getRotatedBounds() {
    return boundingBox();
  }

  public Shape getShape() {
    final Shape s = piece.getShape();

    if (getAngle() == 0.0) {
      return s;
    }

    return AffineTransform.getRotateInstance(getAngleInRadians(),
                                             centerX(),
                                             centerY())
                          .createTransformedShape(s);
  }

  public double getAngleInRadians() {
    return -PI_180 * getAngle();
  }

  public void mySetType(String type) {
    type = type.substring(ID.length());
    final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, ';');
    validAngles = new double[Integer.parseInt(st.nextToken())];
    for (int i = 0; i < validAngles.length; ++i) {
      validAngles[i] = -i * (360.0 / validAngles.length);
    }
    if (validAngles.length == 1) {
      setAngleKey = st.nextKeyStroke(null);
      if (st.hasMoreTokens()) {
        setAngleText = st.nextToken();
      }
    }
    else {
      rotateCWKey = st.nextKeyStroke(null);
      rotateCCWKey = st.nextKeyStroke(null);
      rotateCWText = st.nextToken("");
      rotateCCWText = st.nextToken("");
    }
    // for random rotation
    rotateRNDKey = st.nextKeyStroke(null);
    rotateRNDText = st.nextToken("");
    // end for random rotation
    name = st.nextToken("");
    commands = null;
  }

  public void draw(final Graphics g,
                   final int x,
                   final int y,
                   final Component obs,
                   final double zoom) {
    if (getAngle() == 0.0) {
      piece.draw(g, x, y, obs, zoom);
    }
    else {
      final double angle = getAngle();
      RotateScaleOp op;

      if (gpOp.isChanged()) {
        gpOp = Op.piece(piece);
        op = Op.rotateScale(gpOp, angle, zoom);        <commit_msg>Bug Fix in FreeRotator:  Clear cache of all rotated images when piece state changes<commit_after>/*
 * $Id$
 *
 * Copyright (c) 2000-2003 by <NAME>
 *
 * 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.AlphaComposite;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

import VASSAL.build.GameModule;
import VASSAL.build.module.Map;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.Drawable;
import VASSAL.command.ChangeTracker;
import VASSAL.command.Command;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.HotKeyConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.PieceI18nData;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.imageop.GamePieceOp;
import VASSAL.tools.imageop.Op;
import VASSAL.tools.imageop.RotateScaleOp;

/**
 * A Decorator that rotates a GamePiece to an arbitrary angle
 */
public class FreeRotator extends Decorator
                         implements EditablePiece,
                                    MouseListener,
                                    MouseMotionListener,
                                    Drawable {
  public static final String ID = "rotate;";
  
  public static final String FACING = "_Facing";

  public static final String DEGREES = "_Degrees";

  public static final double PI_180 = Math.PI / 180.0;

  protected KeyCommand setAngleCommand;
  protected KeyCommand rotateCWCommand;
  protected KeyCommand rotateCCWCommand;
  protected KeyCommand[] commands;
  protected KeyStroke setAngleKey;
  protected String setAngleText = "Rotate";
  protected KeyStroke rotateCWKey;
  protected String rotateCWText = "Rotate CW";
  protected KeyStroke rotateCCWKey;
  protected String rotateCCWText = "Rotate CCW";
  protected String name = "Rotate";

  // for Random Rotate
  protected KeyCommand rotateRNDCommand;
  protected String rotateRNDText = "";
  protected KeyStroke rotateRNDKey;
  // END for Random Rotate

  protected boolean useUnrotatedShape;

  protected double[] validAngles = new double[] {0.0};
  protected int angleIndex = 0;

  @Deprecated protected java.util.Map<Double,Image> images =
    new HashMap<Double,Image>();
  protected java.util.Map<Double,Rectangle> bounds =
    new HashMap<Double,Rectangle>();
  @Deprecated protected PieceImage unrotated;
  protected GamePieceOp gpOp;
  protected java.util.Map<Double,RotateScaleOp> rotOp =
    new HashMap<Double,RotateScaleOp>();

  protected double tempAngle, startAngle;
  protected Point pivot;
  protected boolean drawGhost;

  public FreeRotator() {
    // modified for random rotation (added two ; )
    this(ID + "6;];[;Rotate CW;Rotate CCW;;;;", null);
  }

  public FreeRotator(String type, GamePiece inner) {
    mySetType(type);
    setInner(inner);
  }

  public String getName() {
    return piece.getName();
  }

  public void setInner(GamePiece p) {
    gpOp = p == null ? null : Op.piece(p);
    super.setInner(p);
  }

  private double centerX() {
    // The center is not on a vertex for pieces with odd widths.
    return (piece.boundingBox().width % 2) / 2.0;
  }

  private double centerY() {
    // The center is not on a vertex for pieces with odd heights.
    return (piece.boundingBox().height % 2) / 2.0;
  }

  public Rectangle boundingBox() {
    final Rectangle b = piece.boundingBox();
    final double angle = getAngle();

    if (angle == 0.0) {
      return b;
    }

    Rectangle r;
    if (gpOp.isChanged() || (r = bounds.get(angle)) == null) {

      r = AffineTransform.getRotateInstance(getAngleInRadians(),
                                            centerX(),
                                            centerY())
                         .createTransformedShape(b).getBounds();
      bounds.put(angle, r);
    }
    return new Rectangle(r);
  }

  public double getAngle() {
    return useUnrotatedShape ? 0.0 : validAngles[angleIndex];
  }
  
  public double getCumulativeAngle() {  
    double angle = getAngle();
    // Add cumulative angle of any other FreeRotator trait in this piece
    FreeRotator nextRotation = (FreeRotator) Decorator.getDecorator(getInner(), FreeRotator.class);
    if (nextRotation != null) {
      angle += nextRotation.getCumulativeAngle();
    }
    return angle;
  }
  
  public double getCumulativeAngleInRadians() {
    return -PI_180 * getCumulativeAngle();
  }

  public void setAngle(double angle) {
    if (validAngles.length == 1) {
      validAngles[angleIndex] = angle;
    }
    else {
      // Find nearest valid angle
      int newIndex = angleIndex;
      double minDist = Math.abs((validAngles[angleIndex] - angle + 360) % 360);
      for (int i = 0; i < validAngles.length; ++i) {
        if (minDist > Math.abs((validAngles[i] - angle + 360) % 360)) {
          newIndex = i;
          minDist = Math.abs((validAngles[i] - angle + 360) % 360);
        }
      }
      angleIndex = newIndex;
    }
  }

  /** @deprecated Use {@link boundingBox()} instead. */
  @Deprecated
  public Rectangle getRotatedBounds() {
    return boundingBox();
  }

  public Shape getShape() {
    final Shape s = piece.getShape();

    if (getAngle() == 0.0) {
      return s;
    }

    return AffineTransform.getRotateInstance(getAngleInRadians(),
                                             centerX(),
                                             centerY())
                          .createTransformedShape(s);
  }

  public double getAngleInRadians() {
    return -PI_180 * getAngle();
  }

  public void mySetType(String type) {
    type = type.substring(ID.length());
    final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, ';');
    validAngles = new double[Integer.parseInt(st.nextToken())];
    for (int i = 0; i < validAngles.length; ++i) {
      validAngles[i] = -i * (360.0 / validAngles.length);
    }
    if (validAngles.length == 1) {
      setAngleKey = st.nextKeyStroke(null);
      if (st.hasMoreTokens()) {
        setAngleText = st.nextToken();
      }
    }
    else {
      rotateCWKey = st.nextKeyStroke(null);
      rotateCCWKey = st.nextKeyStroke(null);
      rotateCWText = st.nextToken("");
      rotateCCWText = st.nextToken("");
    }
    // for random rotation
    rotateRNDKey = st.nextKeyStroke(null);
    rotateRNDText = st.nextToken("");
    // end for random rotation
    name = st.nextToken("");
    commands = null;
  }

  public void draw(final Graphics g,
                   final int x,
                   final int y,
                   final Component obs,
                   final double zoom) {
    if (getAngle() == 0.0) {
      piece.draw(g, x, y, obs, zoom);
    }
    else {
      final double angle = getAngle();
      RotateScaleOp op;

      if (gpOp.isChanged()) {
        gpOp = Op.piece(piece);
        rotOp.clear();
        op = Op.rotateScale(gpOp, angle, zoom);        
{'fraction_non_alphanumeric': 0.10313790765057641, 'fraction_numerical': 0.008754084211824179, 'mean_word_length': 2.8196373910995995, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4107056', 'n_tokens_mistral': 4997, 'n_tokens_neox': 4748, 'n_words': 1324}