/* * $Id$ * * Copyright (c) 2000-2006 by Rodney Kinney, Brent Easton * * 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.i18n; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import VASSAL.build.AbstractConfigurable; import VASSAL.build.AutoConfigurable; import VASSAL.build.Configurable; import VASSAL.counters.BasicPiece; import VASSAL.counters.Decorator; import VASSAL.counters.GamePiece; import VASSAL.counters.PlaceMarker; /** * Object encapsulating the internationalization information for a component. * The majority of translatable components subclass AbstractConfigurable, * but some extend JFrame or JDialog and implement Configurable or * AutoConfigurable. * * AbstractConfigurable components are almost completely handled within the * AbstractConfigurable base class. AutoConfigurable/Configurable components * must call a different constructor and supply additional information. * * @author Brent Easton */ public class ComponentI18nData { protected String prefix; protected Translatable parent; protected Configurable myComponent; protected Map<String, Property> translatableProperties = new TreeMap<String, Property>(); protected Map<String, Property> allProperties = new TreeMap<String, Property>(); protected List<Translatable> children = new ArrayList<Translatable>(); /** * Build from an AbstractConfigurable. The parent will be set from * AbstractConfigurable.add(). untranslatedValues will be filled in as * attributes are translated. * * @param c * AbstractConfigurable component * @param prefix * I18n Prefix */ public ComponentI18nData(AbstractConfigurable c, String prefix) { init(c, prefix, c.getAttributeNames(), c.getAttributeTypes(), c.getAttributeDescriptions()); } public ComponentI18nData(AbstractConfigurable c, String prefix, ArrayList<String> names, ArrayList<Class<?>> types, ArrayList<String> descriptions) { init(c, prefix, names.toArray(new String[0]), types.toArray(new Class<?>[0]), descriptions.toArray(new String[0])); } /** * Build from an AutoConfigurable * * @param c * AutoConfgurable component * @param prefix * I18n prefix */ public ComponentI18nData(AutoConfigurable c, String prefix) { this.prefix = prefix; parent = null; init(c, prefix, c.getAttributeNames(), c.getAttributeTypes(), c.getAttributeDescriptions()); } protected void init(Configurable c, String pfx, String[] names, Class<?>[] types, String[] descriptions) { boolean[] translatable = new boolean[types.length]; for (int i = 0; i < types.length; i++) { translatable[i] = types[i] != null && (types[i].equals(String.class) || TranslatableConfigurerFactory.class.isAssignableFrom(types[i])); } init(c, pfx, names, descriptions, translatable); } protected void init(Configurable c, String pfx, String[] names, String[] descriptions, boolean[] translatable) { prefix = pfx; myComponent = c; for (Configurable child : myComponent.getConfigureComponents()) { children.add(child); } for (int i = 0; i < translatable.length; i++) { Property p = new Property(names[i], descriptions[i]); allProperties.put(names[i], p); if (translatable[i]) { translatableProperties.put(names[i], p); } } } /** * Build from a Configurable. Configurable does not support * getAttributeNames() getAttributeTypes() or getAttributeValueString(), * so more information must be supplied. * * @param c * Component * @param prefix * I18n prefix * @param parent * parent translatable * @param names * Array of attribute names * @param translatable * Array of Attribute translatable status */ public ComponentI18nData(Configurable c, String prefix, Translatable parent, String[] names, boolean[] translatable, String[] descriptions) { myComponent = c; this.prefix = prefix; this.parent = parent; init(c, prefix, names, descriptions, translatable); } public ComponentI18nData(Configurable c, String prefix, Translatable parent) { this(c, prefix, parent, new String[0], new boolean[0], new String[0]); } public ComponentI18nData(Configurable c, String prefix) { this(c, prefix, (Translatable) null); } /** * Special build for PrototypeDefinition and PieceSlot */ public ComponentI18nData(Configurable c, GamePiece piece) { myComponent = c; this.prefix = TranslatablePiece.PREFIX; parent = null; for (GamePiece p = piece; p != null;) { if (p instanceof TranslatablePiece) { PieceI18nData pieceData = ((TranslatablePiece) p).getI18nData(); for (PieceI18nData.Property prop : pieceData.getProperties()) { Property property = new Property(prop.getName(), prop.getDescription()); translatableProperties.put(prop.getName(), property); allProperties.put(prop.getName(),property); } } if (p instanceof PlaceMarker) { if (((PlaceMarker)p).isMarkerStandalone()) { children.add(new TranslatableMarker((PlaceMarker) p)); } } if (p instanceof BasicPiece) { p = null; } else { p = ((Decorator) p).getInner(); } } } /** * Return a unique Key prefix identifying this component */ public String getPrefix() { return prefix; } public void setPrefix(String p) { prefix = p; } /** * Return a unique key prefix including a full path of parent prefixes. All Translatable Pieces share a common prefix. * * @return */ public String getFullPrefix() { if (TranslatablePiece.PREFIX.equals(prefix)) { return prefix; } String fullPrefix = getOwningComponent() == null ? "" : getOwningComponent().getI18nData() //$NON-NLS-1$ .getFullPrefix(); if (fullPrefix.length() > 0 && prefix.length() > 0) { fullPrefix += "."; //$NON-NLS-1$ } return fullPrefix + prefix; } /** * Return a list of all of the translatable Keys for attributes of this Translatable item. */ public Collection<String> getAttributeKeys() { return translatableProperties.keySet(); } /** * Set the owning Translatable of this component */ public void setOwningComponent(Translatable t) { parent = t; } /** * Return the owning Translatable of this component */ public Translatable getOwningComponent() { return parent; } /** * Is the specified attribute allowed to be translated? * * @param attr * Attribute name * @return is translatable */ public boolean isAttributeTranslatable(String attr) { return translatableProperties.containsKey(attr); } /** * Return true if this component has any translatable attributes, or if any of its children are translatable * * @return component translatable status */ public boolean isTranslatable() { if (translatableProperties.size() > 0) { return true; } for (Translatable child : children) { if (child.getI18nData().isTranslatable()) { return true; } } return false; } /** * Force a specified attribute to be translatable/not translatable * * @param attribute * Attribute name * @param set * translatable status */ public void setAttributeTranslatable(String attribute, boolean set) { if (set) { translatableProperties.put(attribute, allProperties.get(attribute)); } else { translatableProperties.remove(attribute); } } /** * Convenience method to force all attributes to be not translatable */ public void setAllAttributesUntranslatable() { translatableProperties.clear(); } /** * Apply a translatation to the specified attribute. Record the untranslated value in the untranslatedValues array and * set the new value into the real attribute * * @param attr * Attribute name * @param value * Translated value */ public void applyTranslation(String attr, String value) { Property p = translatableProperties.get(attr); if (attr != null) { p.setUntranslatedValue(myComponent.getAttributeValueString(attr)); myComponent.setAttribute(attr, value); } } /** * Return description for named Attribute * * @param attr * @return description */ public String getAttributeDescription(String attr) { return allProperties.get(attr).getDescription(); } /** * Return the pre-translation value stored in this Object. * * @param attr * Attribute Name * @return untranslated value */ public String getLocalUntranslatedValue(String attr) { String val; Property p = allProperties.get(attr); if (p == null || p.getUntranslatedValue() == null) { val = myComponent.getAttributeValueString(attr); } else { val = p.getUntranslatedValue(); } return val; } /** * Set an untranslatedValue for the specified attribute. Used by components that do not subclass AbstractConfigurable * * @param attr * Attribute name * @param value * untranslated value */ public void setUntranslatedValue(String attr, String value) { allProperties.get(attr).setUntranslatedValue(value); } /* * Return a translation of an attribute */ public String getTranslatedValue(String attr, Translation translation) { String fullKey = getFullPrefix() + attr; return translation.translate(fullKey); } /** * Return all child Translatable components of this component * * @return Child translatables */ public List<Translatable> getChildren() { return Collections.unmodifiableList(children); } /** * Return true if this component or any of its children have at least one translatable attribute with a non-null value * that does not have a translation in the supplied translation. * * @param t * Translation * @return true if translation of this component is not complete */ public boolean hasUntranslatedAttributes(Translation t) { if (t == null) { return false; } /* * Check attributes of this component first */ for (Property p : translatableProperties.values()) { String currentValue = myComponent.getAttributeValueString(p.getName()); if (currentValue != null && currentValue.length() > 0) { String translation = getTranslatedValue(p.getName(), t); if (translation == null || translation.length() == 0) { return true; } } } /* * Check Children */ for (Translatable child : children) { if (child.getI18nData().hasUntranslatedAttributes(t)) { return true; } } /* * Nothing left untranslated! */ return false; } /** An attribute of a Configurable component that can be translated into another language */ public static class Property { private String name; private String description; private String untranslatedValue; public Property(String name, String description) { super(); this.name = name; this.description = description; this.untranslatedValue = name; } public String getDescription() { return description; } public String getName() { return name; } public String getUntranslatedValue() { return untranslatedValue; } public void setUntranslatedValue(String untranslatedValue) { this.untranslatedValue = untranslatedValue; } } }