/* * 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; import java.awt.Color; import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.t3.model.Asset; import com.t3.model.AssetManager; import com.t3.model.Token; import com.t3.model.Zone; import com.t3.model.drawing.DrawablePaint; import com.t3.model.drawing.DrawableTexturePaint; import com.t3.util.StringUtil; public class T3Util { // private static Random random = new Random ( System.currentTimeMillis() ); private static Random random = new SecureRandom(); private static AtomicInteger nextTokenId = new AtomicInteger(1); /** * The map of color names to color values */ private static final Map<String, Color> COLOR_MAP = new TreeMap<String, Color>(); private static final Map<String, Color> COLOR_MAP_HTML = new HashMap<String, Color>(); /** * Set up the color map */ static { // Built-in Java colors that happen to match the values used by HTML... COLOR_MAP.put("black", Color.BLACK); COLOR_MAP.put("blue", Color.BLUE); COLOR_MAP.put("cyan", Color.CYAN); COLOR_MAP.put("gray", Color.GRAY); COLOR_MAP.put("magenta", Color.MAGENTA); COLOR_MAP.put("red", Color.RED); COLOR_MAP.put("white", Color.WHITE); COLOR_MAP.put("yellow", Color.YELLOW); // The built-in Java colors that DO NOT match the HTML colors... COLOR_MAP.put("darkgray", new Color(0xA9, 0xA9, 0xA9)); // Color.DARK_GRAY COLOR_MAP.put("green", new Color(0x00, 0x80, 0x00)); // Color.GREEN COLOR_MAP.put("lightgray", new Color(0xD3, 0xD3, 0xD3)); // Color.LIGHT_GRAY COLOR_MAP.put("orange", new Color(0xFF, 0xA5, 0x00)); // Color.ORANGE COLOR_MAP.put("pink", new Color(0xFF, 0xC0, 0xCB)); // Color.PINK // And the HTML colors that don't exist at all as built-in Java values... COLOR_MAP.put("aqua", new Color(0x00, 0xFF, 0xFF)); // same as Color.CYAN COLOR_MAP.put("fuchsia", new Color(0xFF, 0x00, 0xFF)); // same as Color.MAGENTA COLOR_MAP.put("lime", new Color(0xBF, 0xFF, 0x00)); COLOR_MAP.put("maroon", new Color(0x80, 0x00, 0x00)); COLOR_MAP.put("navy", new Color(0x00, 0x00, 0x80)); COLOR_MAP.put("olive", new Color(0x80, 0x80, 0x00)); COLOR_MAP.put("purple", new Color(0x80, 0x00, 0x80)); COLOR_MAP.put("silver", new Color(0xC0, 0xC0, 0xC0)); COLOR_MAP.put("teal", new Color(0x00, 0x80, 0x80)); // Additional Gray colors COLOR_MAP.put("gray25", new Color(0x3F, 0x3F, 0x3F)); COLOR_MAP.put("gray50", new Color(0x6F, 0x7F, 0x7F)); COLOR_MAP.put("gray75", new Color(0xBF, 0xBF, 0xBF)); // These are valid HTML colors. When getFontColor() is called, if one of these is // selected then the name is returned. When another value is selected, the Color // is converted to the '#112233f' notation and returned instead -- even if it's a name // in COLOR_MAP, above. String[] html = { "black", "white", "fuchsia", "aqua", "silver", "red", "lime", "blue", "yellow", "gray", "purple", "maroon", "navy", "olive", "green", "teal" }; for (int i = 0; i < html.length; i++) { Color c = COLOR_MAP.get(html[i]); assert c != null : "HTML color not in predefined list?"; COLOR_MAP_HTML.put(html[i], c); } } /** * Returns a random integer between 0 inclusive and max inclusive * @param max the maximum inclusive value * @return the random integer */ public static int getRandomNumber(int max) { return random.nextInt(1+max); } public static int getRandomNumber(int min, int max) { return random.nextInt(1+max-min)+min; } public static float getRandomRealNumber(float max) { return getRandomRealNumber(0, max); } public static float getRandomRealNumber(float min, float max) { return (float) ((max - min) * random.nextDouble()) + min; } private static final Pattern NAME_PATTERN = Pattern.compile("^(.*)\\s+(\\d+)\\s*$"); /** * Determine what the name of the new token should be. This method tries to choose a token name which is (a) unique * and (b) adheres to a numeric sequence. * * @param zone * the map that the token is being placed onto * @param token * the new token to be named * @return the new token's algorithmically generated name */ public static String nextTokenId(Zone zone, Token token, boolean force) { boolean isToken = token.isToken(); String baseName = token.getName(); String newName; Integer newNum = null; if (isToken && AppPreferences.getNewTokenNaming().equals(Token.NAME_USE_CREATURE)) { newName = "Creature"; } else if (!force) { return baseName; } else if (baseName == null) { int nextId = nextTokenId.getAndIncrement(); char ch = (char) ('a' + TabletopTool.getPlayerList().indexOf(TabletopTool.getPlayer())); return ch + Integer.toString(nextId); } else { baseName = baseName.trim(); Matcher m = NAME_PATTERN.matcher(baseName); if (m.find()) { newName = m.group(1); try { newNum = Integer.parseInt(m.group(2)); } catch (NumberFormatException nfe) { // This exception happens if the number is too big to fit inside an integer. // In this case, we use the original name as the filename and assign a new number as the suffix. newName = baseName; } } else { newName = baseName; } } boolean random = (isToken && AppPreferences.getDuplicateTokenNumber().equals(Token.NUM_RANDOM)); boolean addNumToGM = !AppPreferences.getTokenNumberDisplay().equals(Token.NUM_ON_NAME); boolean addNumToName = !AppPreferences.getTokenNumberDisplay().equals(Token.NUM_ON_GM); /* * If the token already has a number suffix, if the preferences indicate that token numbering should be random * and this token is on the Token layer, or if the token already exists somewhere on this map, then we need to * choose a new name. */ if (newNum != null || random || zone.getTokenByName(newName) != null) { // Figure out the proper number of digits and generate a random number. int maxNum = 99; if (random) { if (zone.getTokenCount() >= 89) maxNum = 999; if (zone.getTokenCount() >= 900) maxNum = 9999; newNum = getRandomNumber(10, maxNum); /* * If we're generating a random number suffix, check to see if the value we have is already taken and * pick a new one if so. The "Token Name" field is separate from the "GM Name" field. */ while (true) { boolean repeat = false; if (addNumToName) repeat = repeat || (zone.getTokenByName(newName + " " + newNum) != null); if (addNumToGM) repeat = repeat || (zone.getTokenByGMName(Integer.toString(newNum)) != null); if (!repeat) break; newNum = getRandomNumber(10, maxNum); } } else { newNum = zone.findFreeNumber(addNumToName ? newName : null, addNumToGM); } if (addNumToName) { newName += " "; newName += newNum; } if (addNumToGM) token.setGMName(Integer.toString(newNum)); } return newName; } public static boolean isDebugEnabled() { return System.getProperty("T3_DEV") != null; } public static boolean isValidColor(String name) { return COLOR_MAP.containsKey(name); } public static boolean isHtmlColor(String name) { return COLOR_MAP_HTML.containsKey(name); } /** * Returns a {@link Color} object if the parameter can be evaluated as a color. This includes a text search against * a list of known colors (case-insensitive; see {@link #COLOR_MAP}) and conversion of the string into a color using * {@link Color#decode(String)}. Invalid strings cause <code>COLOR_MAP.get("black")</code> to be returned. Calls * {@link #convertStringToColor(String)} if the parameter is not a recognized color name. * * @param name * a recognized color name or an integer color value in octal or hexadecimal form (such as * <code>#123</code>, <code>0x112233</code>, or <code>0X111222333</code>) * @return the corresponding Color object or {@link Color#BLACK} if not in a recognized format */ public static Color getColor(String name) { name = name.trim().toLowerCase(); Color c = COLOR_MAP.get(name); if (c != null) return c; c = convertStringToColor(name); return c; } /** * Converts the incoming string value to a Color object and stores <code>val</code> and the Color as a key/value * pair in a cache. The incoming string may start with a <code>#</code> to indicate a numeric color value in CSS * format. Any errors cause {@link #COLOR_MAP}<code>.get("black")</code> to be returned. * * @param val * color value to interpret * @return Color object */ private static Color convertStringToColor(String val) { Color c; if (StringUtil.isEmpty(val)) { c = COLOR_MAP.get("black"); } else { try { c = Color.decode(val); COLOR_MAP.put(val.toLowerCase(), c); } catch (NumberFormatException nfe) { c = COLOR_MAP.get("black"); } } return c; } public static Set<String> getColorNames() { return COLOR_MAP.keySet(); } public static void uploadTexture(DrawablePaint paint) { if (paint == null) { return; } if (paint instanceof DrawableTexturePaint) { Asset asset = ((DrawableTexturePaint) paint).getAsset(); uploadAsset(asset); } } public static void uploadAsset(Asset asset) { if (asset == null) { return; } if (!AssetManager.hasAsset(asset.getId())) { AssetManager.putAsset(asset); } if (!TabletopTool.isHostingServer() && !TabletopTool.getCampaign().containsAsset(asset.getId())) { TabletopTool.serverCommand().putAsset(asset); } } public static Random getRandom() { return random; } }