/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui.token; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.apache.log4j.Logger; import com.t3.guid.GUID; import com.t3.model.Token; /** * This supports token states flowing from one box to the next when multiple states are set on the same token. * * @author Jay */ public class TokenOverlayFlow { private static final Logger log = Logger.getLogger(TokenOverlayFlow.class); /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ /** * The number of cells in the X & Y directions on the token. */ private final int gridSize; /** * Offsets for the placement of each state in percentage of the token size. They are calculated from the grid size. */ private final double[] offsets; /** * The size of a cell in percentage of the token size. Calculated from the grid size. */ private final double size; /** * This map contains the list of states for each token in the order they are drawn. It's done this way so that the * states don't jump around as they are added or removed. */ private final Map<GUID, List<String>> savedStates = new HashMap<GUID, List<String>>(); /*--------------------------------------------------------------------------------------------- * Class Variables *-------------------------------------------------------------------------------------------*/ /** * Flows are shared by multiple token overlay types. This map contains all of the available flow instances. */ private static Map<Integer, TokenOverlayFlow> instances = new HashMap<Integer, TokenOverlayFlow>(); /*--------------------------------------------------------------------------------------------- * Constructor *-------------------------------------------------------------------------------------------*/ /** * Internal constructor to make sure only one of each grid size is created. * * @param aGrid * The size of the grid placed over the token. */ private TokenOverlayFlow(int aGrid) { gridSize = aGrid; size = 1.0D / gridSize; offsets = new double[gridSize]; for (int i = 0; i < offsets.length; i++) offsets[i] = i * size; } /*--------------------------------------------------------------------------------------------- * Instance Methods *-------------------------------------------------------------------------------------------*/ /** * Calculate the bounds to paint the passed state. It takes into account states that have already been set w/o * changing the order. It also removes any unused states. * * @param bounds * The token's bounds. All states are drawn inside this area. * @param token * Rendering the states for this token. * @param state * The state being rendered. * @return The bounds used to paint the state. */ public Rectangle2D getStateBounds2D(Rectangle bounds, Token token, String state) { // Find the list of states already drawn on the token List<String> states = savedStates.get(token.getId()); if (states == null) { states = new LinkedList<String>(); savedStates.put(token.getId(), states); } // endif // Find the state in the list, make sure that all the states before it still exist. ListIterator<String> i = states.listIterator(); boolean found = false; while (i.hasNext()) { String savedState = i.next(); if (!found && savedState.equals(state)) { found = true; } else { //FIXME if something redery is weird look here^^ /*Object stateValue = token.getState(savedState); if (stateValue == null) { i.remove(); } else if (stateValue instanceof Boolean) { Boolean b = (Boolean) stateValue; if (b.booleanValue() == false) i.remove(); } else if (stateValue instanceof BigDecimal) { BigDecimal bd = (BigDecimal) stateValue; if (bd.compareTo(BigDecimal.ZERO) == 0) i.remove(); }*/ if(!token.hasState(savedState)) i.remove(); } } // Find the index of the state, then convert it into row & column int index = states.size(); if (found) { index = states.indexOf(state); } else { states.add(state); } // endif if (index >= gridSize * gridSize) { log.warn("Overlapping states in grid size " + gridSize + " at " + index + " on token " + token.getName()); index = index % (gridSize * gridSize); } // endif int row = gridSize - 1 - (index / gridSize); // Start at bottom int col = gridSize - 1 - (index % gridSize); // Start at right // Build the rectangle from the passed bounds return new Rectangle2D.Double(offsets[col] * bounds.width + bounds.x, offsets[row] * bounds.height + bounds.y, size * bounds.width, size * bounds.height); } /** * Calculate the bounds to paint the passed state. It takes into account states that have already been set w/o * changing the order. It also removes any unused states. * * @param bounds * The token's bounds. All states are drawn inside this area. * @param token * Rendering the states for this token. * @param state * The state being rendered. * @return The bounds used to paint the state. */ public Rectangle getStateBounds(Rectangle bounds, Token token, String state) { Rectangle2D r = getStateBounds2D(bounds, token, state); return new Rectangle((int) Math.round(r.getX()), (int) Math.round(r.getY()), (int) Math.round(r.getWidth()), (int) Math.round(r.getHeight())); } /*--------------------------------------------------------------------------------------------- * Class Methods *-------------------------------------------------------------------------------------------*/ /** * Get the one and only instance of an overlay flow for a particular grid size. * * @param grid * The size of the grid placement for the placement of states. * @return The flow for the passed grid size */ public static TokenOverlayFlow getInstance(int grid) { Integer key = Integer.valueOf(grid); TokenOverlayFlow instance = instances.get(key); if (instance == null) { instance = new TokenOverlayFlow(grid); instances.put(key, instance); } // endif return instance; } }