X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fresources%2FMetaInfo.java;h=70c6c43181bbff8f8885c1eca15a792eef03eac1;hp=d95e98c986a0e53266bbd203f95efa90066a7580;hb=bc4db23ff5c21d7d4716f9b7f97de73b15a6fc21;hpb=0877d6f5485d3531b9fde6c264e5848630c80baf diff --git a/src/be/nikiroo/utils/resources/MetaInfo.java b/src/be/nikiroo/utils/resources/MetaInfo.java index d95e98c..70c6c43 100644 --- a/src/be/nikiroo/utils/resources/MetaInfo.java +++ b/src/be/nikiroo/utils/resources/MetaInfo.java @@ -3,9 +3,6 @@ package be.nikiroo.utils.resources; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; import be.nikiroo.utils.resources.Meta.Format; @@ -30,8 +27,24 @@ public class MetaInfo> implements Iterable> { private List saveListeners = new ArrayList(); private String name; + private boolean hidden; private String description; + private boolean dirty; + + /** + * Create a new {@link MetaInfo} from a value (without children). + *

+ * For instance, you can call + * new MetaInfo(Config.class, configBundle, Config.MY_VALUE). + * + * @param type + * the type of enum the value is + * @param bundle + * the bundle this value belongs to + * @param id + * the value itself + */ public MetaInfo(Class type, Bundle bundle, E id) { this.bundle = bundle; this.id = id; @@ -52,117 +65,416 @@ public class MetaInfo> implements Iterable> { description = null; } } - if (description == null) { description = meta.description(); if (description == null) { description = ""; } - if (meta.info() != null && !meta.info().isEmpty()) { - if (!description.isEmpty()) { - description += "\n\n"; - } - description += meta.info(); + } + + String name = idToName(id, null); + + // Special rules for groups: + if (meta.group()) { + String groupName = description.split("\n")[0]; + description = description.substring(groupName.length()).trim(); + if (!groupName.isEmpty()) { + name = groupName; } } - String name = id.toString(); - if (name.length() > 1) { - name = name.substring(0, 1) + name.substring(1).toLowerCase(); - name = name.replace("_", " "); + if (meta.def() != null && !meta.def().isEmpty()) { + if (!description.isEmpty()) { + description += "\n\n"; + } + description += "(Default value: " + meta.def() + ")"; } this.name = name; + this.hidden = meta.hidden(); this.description = description; reload(); } /** - * THe name of this item, deduced from its ID. + * For normal items, this is the name of this item, deduced from its ID (or + * in other words, it is the ID but presented in a displayable form). *

- * In other words, it is the ID but presented in a displayable form. + * For group items, this is the first line of the description if it is not + * empty (else, it is the ID in the same way as normal items). + *

+ * Never NULL. + * * - * @return the name + * @return the name, never NULL */ public String getName() { return name; } + + /** + * This item should be hidden from the user (she will still be able to + * modify it if she opens the file manually). + * + * @return TRUE if it should stay hidden + */ + public boolean isHidden() { + return hidden; + } /** - * The description of this item (information to present to the user). + * A description for this item: what it is or does, how to explain that item + * to the user including what can be used here (i.e., %s = file name, %d = + * file size...). + *

+ * For group, the first line ('\\n'-separated) will be used as a title while + * the rest will be the description. + *

+ * If a default value is known, it will be specified here, too. + *

+ * Never NULL. * - * @return the description + * @return the description, not NULL */ public String getDescription() { return description; } + /** + * The format this item is supposed to follow + * + * @return the format + */ public Format getFormat() { return meta.format(); } - // for ComboBox, this is mostly a suggestion + /** + * The allowed list of values that a {@link Format#FIXED_LIST} item is + * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} + * items. Also works for {@link Format#LOCALE}. + *

+ * Will always allow an empty string in addition to the rest. + * + * @return the list of values + */ public String[] getAllowedValues() { - return meta.list(); + String[] list = meta.list(); + + String[] withEmpty = new String[list.length + 1]; + withEmpty[0] = ""; + for (int i = 0; i < list.length; i++) { + withEmpty[i + 1] = list[i]; + } + + return withEmpty; + } + + /** + * Return all the languages known by the program for this bundle. + *

+ * This only works for {@link TransBundle}, and will return an empty list if + * this is not a {@link TransBundle}. + * + * @return the known language codes + */ + public List getKnownLanguages() { + if (bundle instanceof TransBundle) { + return ((TransBundle) bundle).getKnownLanguages(); + } + + return new ArrayList(); } - // TODO: use it! + /** + * This item is a comma-separated list of values instead of a single value. + *

+ * The list items are separated by a comma, each surrounded by + * double-quotes, with backslashes and double-quotes escaped by a backslash. + *

+ * Example: "un", "deux" + * + * @return TRUE if it is + */ public boolean isArray() { return meta.array(); } + /** + * A manual flag to specify if the data has been changed or not, which can + * be used by {@link MetaInfo#save(boolean)}. + * + * @return TRUE if it is dirty (if it has changed) + */ + public boolean isDirty() { + return dirty; + } + + /** + * A manual flag to specify that the data has been changed, which can be + * used by {@link MetaInfo#save(boolean)}. + */ + public void setDirty() { + this.dirty = true; + } + + /** + * The number of items in this item if it {@link MetaInfo#isArray()}, or -1 + * if not. + * + * @param useDefaultIfEmpty + * check the size of the default list instead if the list is + * empty + * + * @return -1 or the number of items + */ + public int getListSize(boolean useDefaultIfEmpty) { + if (!isArray()) { + return -1; + } + + return BundleHelper.getListSize(getString(-1, useDefaultIfEmpty)); + } + + /** + * This item is only used as a group, not as an option. + *

+ * For instance, you could have LANGUAGE_CODE as a group for which you won't + * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN + * inside for which the value must be set. + * + * @return TRUE if it is a group + */ + public boolean isGroup() { + return meta.group(); + } + /** * The value stored by this item, as a {@link String}. * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public String getString() { + public String getString(int item, boolean useDefaultIfEmpty) { + if (isArray() && item >= 0) { + List values = BundleHelper.parseList(value, -1); + if (values != null && item < values.size()) { + return values.get(item); + } + + if (useDefaultIfEmpty) { + return getDefaultString(item); + } + + return null; + } + + if (value == null && useDefaultIfEmpty) { + return getDefaultString(item); + } + return value; } - public String getDefaultString() { + /** + * The default value of this item, as a {@link String}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the default value + */ + public String getDefaultString(int item) { + if (isArray() && item >= 0) { + List values = BundleHelper.parseList(meta.def(), item); + if (values != null && item < values.size()) { + return values.get(item); + } + + return null; + } + return meta.def(); } - public Boolean getBoolean() { - return BundleHelper.parseBoolean(getString()); + /** + * The value stored by this item, as a {@link Boolean}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * + * @return the value + */ + public Boolean getBoolean(int item, boolean useDefaultIfEmpty) { + return BundleHelper + .parseBoolean(getString(item, useDefaultIfEmpty), -1); } - public Boolean getDefaultBoolean() { - return BundleHelper.parseBoolean(getDefaultString()); + /** + * The default value of this item, as a {@link Boolean}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the default value + */ + public Boolean getDefaultBoolean(int item) { + return BundleHelper.parseBoolean(getDefaultString(item), -1); } - public Character getCharacter() { - return BundleHelper.parseCharacter(getString()); + /** + * The value stored by this item, as a {@link Character}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * + * @return the value + */ + public Character getCharacter(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty), + -1); } - public Character getDefaultCharacter() { - return BundleHelper.parseCharacter(getDefaultString()); + /** + * The default value of this item, as a {@link Character}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the default value + */ + public Character getDefaultCharacter(int item) { + return BundleHelper.parseCharacter(getDefaultString(item), -1); } - public Integer getInteger() { - return BundleHelper.parseInteger(getString()); + /** + * The value stored by this item, as an {@link Integer}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * + * @return the value + */ + public Integer getInteger(int item, boolean useDefaultIfEmpty) { + return BundleHelper + .parseInteger(getString(item, useDefaultIfEmpty), -1); } - public Integer getDefaultInteger() { - return BundleHelper.parseInteger(getDefaultString()); + /** + * The default value of this item, as an {@link Integer}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the default value + */ + public Integer getDefaultInteger(int item) { + return BundleHelper.parseInteger(getDefaultString(item), -1); } - public Integer getColor() { - return BundleHelper.parseColor(getString()); + /** + * The value stored by this item, as a colour (represented here as an + * {@link Integer}) if it represents a colour, or NULL if it doesn't. + *

+ * The returned colour value is an ARGB value. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * + * @return the value + */ + public Integer getColor(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1); } - public Integer getDefaultColor() { - return BundleHelper.parseColor(getDefaultString()); + /** + * The default value stored by this item, as a colour (represented here as + * an {@link Integer}) if it represents a colour, or NULL if it doesn't. + *

+ * The returned colour value is an ARGB value. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the value + */ + public Integer getDefaultColor(int item) { + return BundleHelper.parseColor(getDefaultString(item), -1); } - public List getList() { - return BundleHelper.parseList(getString()); + /** + * A {@link String} representation of the list of values. + *

+ * The list of values is comma-separated and each value is surrounded by + * double-quotes; backslashes and double-quotes are escaped by a backslash. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * + * @return the value + */ + public List getList(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1); } - public List getDefaultList() { - return BundleHelper.parseList(getDefaultString()); + /** + * A {@link String} representation of the default list of values. + *

+ * The list of values is comma-separated and each value is surrounded by + * double-quotes; backslashes and double-quotes are escaped by a backslash. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the value + */ + public List getDefaultList(int item) { + return BundleHelper.parseList(getDefaultString(item), -1); } /** @@ -170,80 +482,199 @@ public class MetaInfo> implements Iterable> { * * @param value * the new value + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) */ - public void setString(String value) { - this.value = value; + public void setString(String value, int item) { + if (isArray() && item >= 0) { + this.value = BundleHelper.fromList(this.value, value, item); + } else { + this.value = value; + } } - public void setBoolean(boolean value) { - setString(BundleHelper.fromBoolean(value)); + /** + * The value stored by this item, as a {@link Boolean}. + * + * @param value + * the new value + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + public void setBoolean(boolean value, int item) { + setString(BundleHelper.fromBoolean(value), item); } - public void setCharacter(char value) { - setString(BundleHelper.fromCharacter(value)); + /** + * The value stored by this item, as a {@link Character}. + * + * @param value + * the new value + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + public void setCharacter(char value, int item) { + setString(BundleHelper.fromCharacter(value), item); } - public void setInteger(int value) { - setString(BundleHelper.fromInteger(value)); + /** + * The value stored by this item, as an {@link Integer}. + * + * @param value + * the new value + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + public void setInteger(int value, int item) { + setString(BundleHelper.fromInteger(value), item); } - public void setColor(int value) { - setString(BundleHelper.fromColor(value)); + /** + * The value stored by this item, as a colour (represented here as an + * {@link Integer}) if it represents a colour, or NULL if it doesn't. + *

+ * The colour value is an ARGB value. + * + * @param value + * the value + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + public void setColor(int value, int item) { + setString(BundleHelper.fromColor(value), item); } - public void setList(List value) { - setString(BundleHelper.fromList(value)); + /** + * A {@link String} representation of the default list of values. + *

+ * The list of values is comma-separated and each value is surrounded by + * double-quotes; backslashes and double-quotes are escaped by a backslash. + * + * @param value + * the {@link String} representation + * @param item + * the item number to set for an array of values, or -1 to set + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + public void setList(List value, int item) { + setString(BundleHelper.fromList(value), item); } /** - * Reload the value from the {@link Bundle}. + * Reload the value from the {@link Bundle}, so the last value that was + * saved will be used. */ public void reload() { - value = bundle.getString(id); - for (Runnable listener : reloadedListeners) { + if (bundle.isSet(id, false)) { + value = bundle.getString(id); + } else { + value = null; + } + + // Copy the list so we can create new listener in a listener + for (Runnable listener : new ArrayList(reloadedListeners)) { try { listener.run(); } catch (Exception e) { - // TODO: error management? e.printStackTrace(); } } } - // listeners will be called AFTER reload + /** + * Add a listener that will be called after a reload operation. + *

+ * You could use it to refresh the UI for instance. + * + * @param listener + * the listener + */ public void addReloadedListener(Runnable listener) { reloadedListeners.add(listener); } /** * Save the current value to the {@link Bundle}. + *

+ * Note that listeners will be called before the dirty check and + * before saving the value. + * + * @param onlyIfDirty + * only save the data if the dirty flag is set (will reset the + * dirty flag) */ - public void save() { - for (Runnable listener : saveListeners) { + public void save(boolean onlyIfDirty) { + // Copy the list so we can create new listener in a listener + for (Runnable listener : new ArrayList(saveListeners)) { try { listener.run(); } catch (Exception e) { - // TODO: error management? e.printStackTrace(); } } - bundle.setString(id, value); + + if (!onlyIfDirty || isDirty()) { + bundle.setString(id, value); + } } - // listeners will be called BEFORE save + /** + * Add a listener that will be called before a save operation. + *

+ * You could use it to make some modification to the stored value before it + * is saved. + * + * @param listener + * the listener + */ public void addSaveListener(Runnable listener) { saveListeners.add(listener); } + /** + * The sub-items if any (if no sub-items, will return an empty list). + *

+ * Sub-items are declared when a {@link Meta} has an ID that starts with the + * ID of a {@link Meta#group()} {@link MetaInfo}. + *

+ * For instance: + *

    + *
  • {@link Meta} MY_PREFIX is a {@link Meta#group()}
  • + *
  • {@link Meta} MY_PREFIX_DESCRIPTION is another {@link Meta}
  • + *
  • MY_PREFIX_DESCRIPTION will be a child of MY_PREFIX
  • + *
+ * + * @return the sub-items if any + */ + public List> getChildren() { + return children; + } + + /** + * The number of sub-items, if any. + * + * @return the number or 0 + */ + public int size() { + return children.size(); + } + @Override public Iterator> iterator() { return children.iterator(); } - public List> getChildren() { - return children; - } - /** * Create a list of {@link MetaInfo}, one for each of the item in the given * {@link Bundle}. @@ -260,59 +691,80 @@ public class MetaInfo> implements Iterable> { static public > List> getItems(Class type, Bundle bundle) { List> list = new ArrayList>(); + List> shadow = new ArrayList>(); for (E id : type.getEnumConstants()) { - list.add(new MetaInfo(type, bundle, id)); - } - - return list; - } - - // TODO: multiple levels? - static public > Map, List>> getGroupedItems( - Class type, Bundle bundle) { - Map, List>> map = new TreeMap, List>>(); - Map, List>> map1 = new TreeMap, List>>(); - - List> ungrouped = new ArrayList>(); - for (MetaInfo info : getItems(type, bundle)) { - if (info.meta.group()) { - List> list = new ArrayList>(); - map.put(info, list); - map1.put(info, list); - } else { - ungrouped.add(info); + MetaInfo info = new MetaInfo(type, bundle, id); + if (!info.hidden) { + list.add(info); + shadow.add(info); } } - for (int i = 0; i < ungrouped.size(); i++) { - MetaInfo info = ungrouped.get(i); - MetaInfo group = findParent(info, map.keySet()); - if (group != null) { - map.get(group).add(info); - ungrouped.remove(i--); - } - } + for (int i = 0; i < list.size(); i++) { + MetaInfo info = list.get(i); - if (ungrouped.size() > 0) { - map.put(null, ungrouped); + MetaInfo parent = findParent(info, shadow); + if (parent != null) { + list.remove(i--); + parent.children.add(info); + info.name = idToName(info.id, parent.id); + } } - return map; + return list; } + /** + * Find the longest parent of the given {@link MetaInfo}, which means: + *
    + *
  • the parent is a {@link Meta#group()}
  • + *
  • the parent Id is a substring of the Id of the given {@link MetaInfo}
  • + *
  • there is no other parent sharing a substring for this + * {@link MetaInfo} with a longer Id
  • + *
+ * + * @param + * the kind of enum + * @param info + * the info to look for a parent for + * @param candidates + * the list of potential parents + * + * @return the longest parent or NULL if no parent is found + */ static private > MetaInfo findParent(MetaInfo info, - Set> candidates) { + List> candidates) { + String id = info.id.toString(); MetaInfo group = null; for (MetaInfo pcandidate : candidates) { - if (info.id.toString().startsWith(pcandidate.id.toString())) { - if (group == null - || group.id.toString().length() < pcandidate.id - .toString().length()) { - group = pcandidate; + if (pcandidate.isGroup()) { + String candidateId = pcandidate.id.toString(); + if (!id.equals(candidateId) && id.startsWith(candidateId)) { + if (group == null + || group.id.toString().length() < candidateId + .length()) { + group = pcandidate; + } } } } return group; } + + static private > String idToName(E id, E prefix) { + String name = id.toString(); + if (prefix != null && name.startsWith(prefix.toString())) { + name = name.substring(prefix.toString().length()); + } + + if (name.length() > 0) { + name = name.substring(0, 1).toUpperCase() + + name.substring(1).toLowerCase(); + } + + name = name.replace("_", " "); + + return name.trim(); + } }