From d5026c096121da14c20d69893520594a36d088bb Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 28 May 2019 23:13:17 +0200 Subject: [PATCH] Improve ConfigItems and fix some related bugs --- src/be/nikiroo/utils/TraceHandler.java | 2 +- src/be/nikiroo/utils/resources/Bundle.java | 433 ++++++++++++- .../nikiroo/utils/resources/BundleHelper.java | 144 +++-- src/be/nikiroo/utils/resources/MetaInfo.java | 240 +++++-- src/be/nikiroo/utils/ui/ConfigEditor.java | 2 +- src/be/nikiroo/utils/ui/ConfigItem.java | 612 ++++++++---------- .../nikiroo/utils/ui/ConfigItemBoolean.java | 66 ++ src/be/nikiroo/utils/ui/ConfigItemBrowse.java | 116 ++++ src/be/nikiroo/utils/ui/ConfigItemColor.java | 141 ++++ .../nikiroo/utils/ui/ConfigItemCombobox.java | 66 ++ .../nikiroo/utils/ui/ConfigItemInteger.java | 53 ++ .../nikiroo/utils/ui/ConfigItemPassword.java | 53 ++ src/be/nikiroo/utils/ui/ConfigItemString.java | 53 ++ 13 files changed, 1545 insertions(+), 436 deletions(-) create mode 100644 src/be/nikiroo/utils/ui/ConfigItemBoolean.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemBrowse.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemColor.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemCombobox.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemInteger.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemPassword.java create mode 100644 src/be/nikiroo/utils/ui/ConfigItemString.java diff --git a/src/be/nikiroo/utils/TraceHandler.java b/src/be/nikiroo/utils/TraceHandler.java index e1dc50f..0a09712 100644 --- a/src/be/nikiroo/utils/TraceHandler.java +++ b/src/be/nikiroo/utils/TraceHandler.java @@ -95,7 +95,7 @@ public class TraceHandler { System.err.print(StringUtils.fromTime(now) + ": "); e.printStackTrace(); } else { - error(e.getMessage()); + error(e.toString()); } } } diff --git a/src/be/nikiroo/utils/resources/Bundle.java b/src/be/nikiroo/utils/resources/Bundle.java index 3506fdd..68c236f 100644 --- a/src/be/nikiroo/utils/resources/Bundle.java +++ b/src/be/nikiroo/utils/resources/Bundle.java @@ -137,6 +137,26 @@ public class Bundle> { * resource file) */ public String getString(E id, String def) { + return getString(id, def, -1); + } + + /** + * Return the value associated to the given id as a {@link String}. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param def + * the default value when it is not present in the config file + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + * @return the associated value, or NULL if not found (not present in the + * resource file) + */ + public String getString(E id, String def, int item) { String rep = getString(id.name(), null); if (rep == null) { try { @@ -148,8 +168,18 @@ public class Bundle> { } } - if (rep == null) { - rep = def; + //TODO: is it ok? need to jDoc? + if (rep == null || rep.isEmpty()) { + return def; + } + + if (item >= 0) { + List values = BundleHelper.parseList(rep, item); + if (values != null && item < values.size()) { + return values.get(item); + } + + return null; } return rep; @@ -168,6 +198,31 @@ public class Bundle> { setString(id.name(), value); } + /** + * Set the value associated to the given id as a {@link String}. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setString(E id, String value, int item) { + if (item < 0) { + setString(id.name(), value); + } else { + List values = getList(id); + for (int i = values.size(); i < item; i++) { + values.add(null); + } + values.set(item, value); + setString(id.name(), BundleHelper.fromList(values)); + } + } + /** * Return the value associated to the given id as a {@link String} suffixed * with the runtime value "_suffix" (that is, "_" and suffix). @@ -185,14 +240,63 @@ public class Bundle> { * resource file) */ public String getStringX(E id, String suffix) { + return getStringX(id, suffix, null, -1); + } + + /** + * Return the value associated to the given id as a {@link String} suffixed + * with the runtime value "_suffix" (that is, "_" and suffix). + *

+ * Will only accept suffixes that form an existing id. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param suffix + * the runtime suffix + * @param def + * the default value when it is not present in the config file + * + * @return the associated value, or NULL if not found (not present in the + * resource file) + */ + public String getStringX(E id, String suffix, String def) { + return getStringX(id, suffix, def, -1); + } + + /** + * Return the value associated to the given id as a {@link String} suffixed + * with the runtime value "_suffix" (that is, "_" and suffix). + *

+ * Will only accept suffixes that form an existing id. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param suffix + * the runtime suffix + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * @param def + * the default value when it is not present in the config file + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + * @return the associated value, or NULL if not found (not present in the + * resource file) + */ + public String getStringX(E id, String suffix, String def, int item) { String key = id.name() + (suffix == null ? "" : "_" + suffix.toUpperCase()); try { id = Enum.valueOf(type, key); - return getString(id); + return getString(id, def, item); } catch (IllegalArgumentException e) { - } return null; @@ -212,14 +316,33 @@ public class Bundle> { * the value */ public void setStringX(E id, String suffix, String value) { + setStringX(id, suffix, value, -1); + } + + /** + * Set the value associated to the given id as a {@link String} suffixed + * with the runtime value "_suffix" (that is, "_" and suffix). + *

+ * Will only accept suffixes that form an existing id. + * + * @param id + * the id of the value to set + * @param suffix + * the runtime suffix + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + */ + public void setStringX(E id, String suffix, String value, int item) { String key = id.name() + (suffix == null ? "" : "_" + suffix.toUpperCase()); try { id = Enum.valueOf(type, key); - setString(id, value); + setString(id, value, item); } catch (IllegalArgumentException e) { - } } @@ -234,8 +357,7 @@ public class Bundle> { * @return the associated value */ public Boolean getBoolean(E id) { - String str = getString(id); - return BundleHelper.parseBoolean(str); + return BundleHelper.parseBoolean(getString(id), -1); } /** @@ -252,9 +374,35 @@ public class Bundle> { * @return the associated value */ public boolean getBoolean(E id, boolean def) { - Boolean b = getBoolean(id); - if (b != null) - return b; + Boolean value = getBoolean(id); + if (value != null) { + return value; + } + + return def; + } + + /** + * Return the value associated to the given id as a {@link Boolean}. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param def + * the default value when it is not present in the config file or + * if it is not a boolean value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + * @return the associated value + */ + public Boolean getBoolean(E id, boolean def, int item) { + String value = getString(id); + if (value != null) { + return BundleHelper.parseBoolean(value, item); + } return def; } @@ -269,7 +417,23 @@ public class Bundle> { * */ public void setBoolean(E id, boolean value) { - setString(id.name(), BundleHelper.fromBoolean(value)); + setBoolean(id, value, -1); + } + + /** + * Set the value associated to the given id as a {@link Boolean}. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setBoolean(E id, boolean value, int item) { + setString(id, BundleHelper.fromBoolean(value), item); } /** @@ -283,7 +447,12 @@ public class Bundle> { * @return the associated value */ public Integer getInteger(E id) { - return BundleHelper.parseInteger(getString(id)); + String value = getString(id); + if (value != null) { + return BundleHelper.parseInteger(value, -1); + } + + return null; } /** @@ -300,9 +469,35 @@ public class Bundle> { * @return the associated value */ public int getInteger(E id, int def) { - Integer i = getInteger(id); - if (i != null) - return i; + Integer value = getInteger(id); + if (value != null) { + return value; + } + + return def; + } + + /** + * Return the value associated to the given id as an int. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param def + * the default value when it is not present in the config file or + * if it is not a int value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + * @return the associated value + */ + public Integer getInteger(E id, int def, int item) { + String value = getString(id); + if (value != null) { + return BundleHelper.parseInteger(value, item); + } return def; } @@ -317,7 +512,23 @@ public class Bundle> { * */ public void setInteger(E id, int value) { - setString(id.name(), BundleHelper.fromInteger(value)); + setInteger(id, value, -1); + } + + /** + * Set the value associated to the given id as a {@link Integer}. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setInteger(E id, int value, int item) { + setString(id, BundleHelper.fromInteger(value), item); } /** @@ -331,7 +542,7 @@ public class Bundle> { * @return the associated value */ public Character getCharacter(E id) { - return BundleHelper.parseCharacter(getString(id)); + return BundleHelper.parseCharacter(getString(id), -1); } /** @@ -348,13 +559,65 @@ public class Bundle> { * @return the associated value */ public char getCharacter(E id, char def) { - Character car = getCharacter(id); - if (car != null) - return car; + Character value = getCharacter(id); + if (value != null) { + return value; + } + + return def; + } + + /** + * Return the value associated to the given id as a {@link Character}. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * @param def + * the default value when it is not present in the config file or + * if it is not a char value + * + * @return the associated value + */ + public Character getCharacter(E id, char def, int item) { + String value = getString(id); + if (value != null) { + return BundleHelper.parseCharacter(value, item); + } return def; } + /** + * Set the value associated to the given id as a {@link Character}. + * + * @param id + * the id of the value to set + * @param value + * the value + * + */ + public void setCharacter(E id, char value) { + setCharacter(id, value, -1); + } + + /** + * Set the value associated to the given id as a {@link Character}. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setCharacter(E id, char value, int item) { + setString(id, BundleHelper.fromCharacter(value), item); + } + /** * Return the value associated to the given id as a colour if it is found * and can be parsed. @@ -369,7 +632,51 @@ public class Bundle> { * @return the associated value */ public Integer getColor(E id) { - return BundleHelper.parseColor(getString(id)); + return BundleHelper.parseColor(getString(id), -1); + } + + /** + * Return the value associated to the given id as a colour if it is found + * and can be parsed. + *

+ * The returned value is an ARGB value. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * + * @return the associated value + */ + public int getColor(E id, int def) { + Integer value = getColor(id); + if (value != null) { + return value; + } + + return def; + } + + /** + * Return the value associated to the given id as a colour if it is found + * and can be parsed. + *

+ * The returned value is an ARGB value. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * + * @return the associated value + */ + public Integer getColor(E id, int def, int item) { + String value = getString(id); + if (value != null) { + return BundleHelper.parseColor(value, item); + } + + return def; } /** @@ -383,7 +690,23 @@ public class Bundle> { * the new colour */ public void setColor(E id, Integer color) { - setString(id, BundleHelper.fromColor(color)); + setColor(id, color, -1); + } + + /** + * Set the value associated to the given id as a Color. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setColor(E id, int value, int item) { + setString(id, BundleHelper.fromColor(value), item); } /** @@ -399,7 +722,49 @@ public class Bundle> { * not found or cannot be parsed as a list */ public List getList(E id) { - return BundleHelper.parseList(getString(id)); + return BundleHelper.parseList(getString(id), -1); + } + + /** + * Return the value associated to the given id as a list of values if it is + * found and can be parsed. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * + * @return the associated list, empty if the value is empty, NULL if it is + * not found or cannot be parsed as a list + */ + public List getList(E id, List def) { + List value = getList(id); + if (value != null) { + return value; + } + + return def; + } + + /** + * Return the value associated to the given id as a list of values if it is + * found and can be parsed. + *

+ * If no value is associated, take the default one if any. + * + * @param id + * the id of the value to get + * + * @return the associated list, empty if the value is empty, NULL if it is + * not found or cannot be parsed as a list + */ + public List getList(E id, List def, int item) { + String value = getString(id); + if (value != null) { + return BundleHelper.parseList(value, item); + } + + return def; } /** @@ -411,7 +776,23 @@ public class Bundle> { * the new list of values */ public void setList(E id, List list) { - setString(id, BundleHelper.fromList(list)); + setList(id, list, -1); + } + + /** + * Set the value associated to the given id as a {@link List}. + * + * @param id + * the id of the value to set + * @param value + * the value + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays + * + */ + public void setList(E id, List value, int item) { + setString(id, BundleHelper.fromList(value), item); } /** @@ -627,7 +1008,7 @@ public class Bundle> { } if (array) { - builder.append("\n# (This item accepts a list of escaped comma-separated values)"); + builder.append("\n# (This item accepts a list of ^escaped comma-separated values)"); } } diff --git a/src/be/nikiroo/utils/resources/BundleHelper.java b/src/be/nikiroo/utils/resources/BundleHelper.java index 45f7cf4..5d2638f 100644 --- a/src/be/nikiroo/utils/resources/BundleHelper.java +++ b/src/be/nikiroo/utils/resources/BundleHelper.java @@ -18,20 +18,25 @@ class BundleHelper { * * @param str * the input {@link String} + * @param item + * the item number to use for an array of values, or -1 for + * non-arrays * * @return the converted {@link Boolean} or NULL */ - static public Boolean parseBoolean(String str) { - if (str != null && str.length() > 0) { - if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("on") - || str.equalsIgnoreCase("yes")) - return true; - if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("off") - || str.equalsIgnoreCase("no")) - return false; - + static public Boolean parseBoolean(String str, int item) { + str = getItem(str, item); + if (str == null) { + return null; } + if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("on") + || str.equalsIgnoreCase("yes")) + return true; + if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("off") + || str.equalsIgnoreCase("no")) + return false; + return null; } @@ -55,10 +60,18 @@ class BundleHelper { * * @param str * the input {@link String} + * @param item + * the item number to use for an array of values, or -1 for + * non-arrays * * @return the converted {@link Integer} or NULL */ - static public Integer parseInteger(String str) { + static public Integer parseInteger(String str, int item) { + str = getItem(str, item); + if (str == null) { + return null; + } + try { return Integer.parseInt(str); } catch (Exception e) { @@ -79,18 +92,6 @@ class BundleHelper { return Integer.toString(value); } - /** - * Return a {@link String} representation of the given {@link Integer}. - * - * @param value - * the input value - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromBoolean(int value) { - return Integer.toString(value); - } - /** * Convert the given {@link String} into a {@link Character} if it * represents a {@link Character}, or NULL if it doesn't. @@ -101,10 +102,18 @@ class BundleHelper { * * @param str * the input {@link String} + * @param item + * the item number to use for an array of values, or -1 for + * non-arrays * * @return the converted {@link Character} or NULL */ - static public Character parseCharacter(String str) { + static public Character parseCharacter(String str, int item) { + str = getItem(str, item); + if (str == null) { + return null; + } + String s = str.trim(); if (s.length() == 1) { return s.charAt(0); @@ -133,10 +142,18 @@ class BundleHelper { * * @param str * the input {@link String} + * @param item + * the item number to use for an array of values, or -1 for + * non-arrays * * @return the converted colour as an {@link Integer} value or NULL */ - static Integer parseColor(String str) { + static Integer parseColor(String str, int item) { + str = getItem(str, item); + if (str == null) { + return null; + } + Integer rep = null; str = str.trim(); @@ -244,20 +261,46 @@ class BundleHelper { return "#" + rs + gs + bs + as; } + /** + * The size of this raw list. + * + * @param raw + * the raw list + * + * @return its size if it is a list, -1 if not + */ + static public int getListSize(String raw) { + List list = parseList(raw, -1); + if (list == null) { + return -1; + } + + return list.size(); + } + /** * Return a {@link String} representation of the given 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. + * double-quotes; caret (^) and double-quotes (") are escaped by a caret. * * @param str * the input value + * @param item + * the item number to use for an array of values, or -1 for + * non-arrays + * * @return the raw {@link String} value that correspond to it */ - static public List parseList(String str) { + static public List parseList(String str, int item) { if (str == null) { return null; } + + if (item >= 0) { + str = getItem(str, item); + } + List list = new ArrayList(); try { boolean inQuote = false; @@ -283,7 +326,7 @@ class BundleHelper { inQuote = !inQuote; break; - case '\\': + case '^': // We don't process it here builder.append(car); prevIsBackSlash = true; @@ -327,6 +370,8 @@ class BundleHelper { /** * Return a {@link String} representation of the given list of values. + *

+ * NULL will be assimilated to an empty {@link String}. * * @param list * the input value @@ -339,14 +384,14 @@ class BundleHelper { if (builder.length() > 0) { builder.append(", "); } - builder.append(escape(item)); + builder.append(escape(item == null ? "" : item)); } return builder.toString(); } /** - * Escape the given value for list formating (no \\, no \n). + * Escape the given value for list formating (no carets, no NEWLINES...). *

* You can unescape it with {@link BundleHelper#unescape(String)} * @@ -358,16 +403,16 @@ class BundleHelper { */ static public String escape(String value) { return '"' + value// - .replace("\\", "\\\\") // - .replace("\"", "\\\"") // - .replace("\n", "\\\n") // - .replace("\r", "\\\r") // + .replace("^", "^^") // + .replace("\"", "^\"") // + .replace("\n", "^\n") // + .replace("\r", "^\r") // + '"'; } /** - * Unescape the given value for list formating (change \\n into \n and so - * on). + * Unescape the given value for list formating (change ^n into NEWLINE and + * so on). *

* You can escape it with {@link BundleHelper#escape(String)} * @@ -400,12 +445,13 @@ class BundleHelper { case 'R': builder.append('\r'); break; - default: // includes \ and " + default: // includes ^ and " builder.append(car); break; } + prevIsBackslash = false; } else { - if (car == '\\') { + if (car == '^') { prevIsBackslash = true; } else { builder.append(car); @@ -420,4 +466,28 @@ class BundleHelper { return builder.toString(); } + + /** + * Retrieve the specific item in the given value, assuming it is an array. + * + * @param value + * the value to look into + * @param item + * the item number to get for an array of values, or -1 for + * non-arrays (in that case, simply return the value as-is) + * + * @return the value as-is for non arrays, the item item if found, + * NULL if not + */ + static private String getItem(String value, int item) { + if (item >= 0) { + value = null; + List values = parseList(value, -1); + if (values != null && item < values.size()) { + value = values.get(item); + } + } + + return value; + } } diff --git a/src/be/nikiroo/utils/resources/MetaInfo.java b/src/be/nikiroo/utils/resources/MetaInfo.java index 9f86843..e7a8b7c 100644 --- a/src/be/nikiroo/utils/resources/MetaInfo.java +++ b/src/be/nikiroo/utils/resources/MetaInfo.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.utils.resources.Meta.Format; /** @@ -29,6 +30,8 @@ public class MetaInfo> implements Iterable> { private String name; private String description; + private boolean dirty; + /** * Create a new {@link MetaInfo} from a value (without children). *

@@ -171,6 +174,42 @@ public class MetaInfo> implements Iterable> { return meta.array(); } + /** + * A manual flag to specify if the {@link MetaData} 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 {@link MetaData} 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. *

@@ -187,15 +226,32 @@ public class MetaInfo> implements Iterable> { /** * 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(boolean useDefaultIfEmpty) { + 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(); + return getDefaultString(item); } return value; @@ -204,76 +260,120 @@ public class MetaInfo> implements Iterable> { /** * 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() { + 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(); } /** * 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(boolean useDefaultIfEmpty) { - return BundleHelper.parseBoolean(getString(useDefaultIfEmpty)); + public Boolean getBoolean(int item, boolean useDefaultIfEmpty) { + return BundleHelper + .parseBoolean(getString(item, useDefaultIfEmpty), -1); } /** * 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() { - return BundleHelper.parseBoolean(getDefaultString()); + public Boolean getDefaultBoolean(int item) { + return BundleHelper.parseBoolean(getDefaultString(item), -1); } /** * 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(boolean useDefaultIfEmpty) { - return BundleHelper.parseCharacter(getString(useDefaultIfEmpty)); + public Character getCharacter(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty), + -1); } /** * 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() { - return BundleHelper.parseCharacter(getDefaultString()); + public Character getDefaultCharacter(int item) { + return BundleHelper.parseCharacter(getDefaultString(item), -1); } /** * 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(boolean useDefaultIfEmpty) { - return BundleHelper.parseInteger(getString(useDefaultIfEmpty)); + public Integer getInteger(int item, boolean useDefaultIfEmpty) { + return BundleHelper + .parseInteger(getString(item, useDefaultIfEmpty), -1); } /** * 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() { - return BundleHelper.parseInteger(getDefaultString()); + public Integer getDefaultInteger(int item) { + return BundleHelper.parseInteger(getDefaultString(item), -1); } /** @@ -282,14 +382,18 @@ public class MetaInfo> implements Iterable> { *

* 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(boolean useDefaultIfEmpty) { - return BundleHelper.parseColor(getString(useDefaultIfEmpty)); + public Integer getColor(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1); } /** @@ -298,10 +402,15 @@ public class MetaInfo> implements Iterable> { *

* 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() { - return BundleHelper.parseColor(getDefaultString()); + public Integer getDefaultColor(int item) { + return BundleHelper.parseColor(getDefaultString(item), -1); } /** @@ -310,14 +419,18 @@ public class MetaInfo> implements Iterable> { * 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(boolean useDefaultIfEmpty) { - return BundleHelper.parseList(getString(useDefaultIfEmpty)); + public List getList(int item, boolean useDefaultIfEmpty) { + return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1); } /** @@ -326,10 +439,15 @@ public class MetaInfo> implements Iterable> { * 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() { - return BundleHelper.parseList(getDefaultString()); + public List getDefaultList(int item) { + return BundleHelper.parseList(getDefaultString(item), -1); } /** @@ -337,9 +455,22 @@ public class MetaInfo> implements Iterable> { * * @param value * the new value - */ - public void setString(String value) { - this.value = 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, int item) { + if (isArray() && item >= 0) { + List values = BundleHelper.parseList(this.value, -1); + for (int i = values.size(); i <= item; i++) { + values.add(null); + } + values.set(item, value); + this.value = BundleHelper.fromList(values); + } else { + this.value = value; + } } /** @@ -347,9 +478,13 @@ 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 setBoolean(boolean value) { - setString(BundleHelper.fromBoolean(value)); + public void setBoolean(boolean value, int item) { + setString(BundleHelper.fromBoolean(value), item); } /** @@ -357,9 +492,13 @@ 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 setCharacter(char value) { - setString(BundleHelper.fromCharacter(value)); + public void setCharacter(char value, int item) { + setString(BundleHelper.fromCharacter(value), item); } /** @@ -367,9 +506,13 @@ 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 setInteger(int value) { - setString(BundleHelper.fromInteger(value)); + public void setInteger(int value, int item) { + setString(BundleHelper.fromInteger(value), item); } /** @@ -380,9 +523,13 @@ public class MetaInfo> implements Iterable> { * * @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) { - setString(BundleHelper.fromColor(value)); + public void setColor(int value, int item) { + setString(BundleHelper.fromColor(value), item); } /** @@ -393,10 +540,13 @@ public class MetaInfo> implements Iterable> { * * @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) { - setString(BundleHelper.fromList(value)); + public void setList(List value, int item) { + setString(BundleHelper.fromList(value), item); } /** @@ -414,7 +564,6 @@ public class MetaInfo> implements Iterable> { try { listener.run(); } catch (Exception e) { - // TODO: error management? e.printStackTrace(); } } @@ -434,17 +583,26 @@ public class MetaInfo> implements Iterable> { /** * 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() { + public void save(boolean onlyIfDirty) { for (Runnable listener : saveListeners) { try { listener.run(); } catch (Exception e) { - // TODO: error management? e.printStackTrace(); } } - bundle.setString(id, value); + + if (!onlyIfDirty || isDirty()) { + bundle.setString(id, value); + } } /** diff --git a/src/be/nikiroo/utils/ui/ConfigEditor.java b/src/be/nikiroo/utils/ui/ConfigEditor.java index 45a534e..2bc9dc9 100644 --- a/src/be/nikiroo/utils/ui/ConfigEditor.java +++ b/src/be/nikiroo/utils/ui/ConfigEditor.java @@ -94,7 +94,7 @@ public class ConfigEditor> extends JPanel { @Override public void actionPerformed(ActionEvent e) { for (MetaInfo item : items) { - item.save(); + item.save(true); } try { diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 04bf76c..f974969 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -1,38 +1,30 @@ package be.nikiroo.utils.ui; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; -import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; -import javax.swing.Icon; +import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JSpinner; import javax.swing.JTextField; import be.nikiroo.utils.Image; import be.nikiroo.utils.StringUtils; import be.nikiroo.utils.StringUtils.Alignment; import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.Meta.Format; import be.nikiroo.utils.resources.MetaInfo; /** @@ -67,6 +59,12 @@ public class ConfigItem> extends JPanel { /** The original value before current changes. */ private Object orig; + private List dirtyBits; + + protected MetaInfo info; + + private JComponent field; + private List fields = new ArrayList(); /** * Create a new {@link ConfigItem} for the given {@link MetaInfo}. @@ -80,357 +78,349 @@ public class ConfigItem> extends JPanel { * different horisontal position) */ public ConfigItem(MetaInfo info, int nhgap) { - this.setLayout(new BorderLayout()); - - // TODO: support arrays - Format fmt = info.getFormat(); - if (info.isArray()) { - fmt = Format.STRING; - } + this(info, true); - switch (fmt) { + ConfigItem configItem; + switch (info.getFormat()) { case BOOLEAN: - addBooleanField(info, nhgap); + configItem = new ConfigItemBoolean(info); break; case COLOR: - addColorField(info, nhgap); + configItem = new ConfigItemColor(info); break; case FILE: - addBrowseField(info, nhgap, false); + configItem = new ConfigItemBrowse(info, false); break; case DIRECTORY: - addBrowseField(info, nhgap, true); + configItem = new ConfigItemBrowse(info, true); break; case COMBO_LIST: - addComboboxField(info, nhgap, true); + configItem = new ConfigItemCombobox(info, true); break; case FIXED_LIST: - addComboboxField(info, nhgap, false); + configItem = new ConfigItemCombobox(info, false); break; case INT: - addIntField(info, nhgap); + configItem = new ConfigItemInteger(info); break; case PASSWORD: - addPasswordField(info, nhgap); + configItem = new ConfigItemPassword(info); break; case STRING: case LOCALE: // TODO? default: - addStringField(info, nhgap); + configItem = new ConfigItemString(info); break; } - } - private void reload(Object value) { - // We consider "" and NULL to be equals - if ("".equals(value)) { - value = null; - } - orig = value; - } + if (info.isArray()) { + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); + + final JPanel main = new JPanel(); + main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); + int size = info.getListSize(false); + for (int i = 0; i < size; i++) { + JComponent field = configItem.createComponent(i); + main.add(field); + } - private boolean isChanged(Object newValue) { - // We consider "" and NULL to be equals - if ("".equals(newValue)) { - newValue = null; - } + // TODO: image + final JButton add = new JButton("+"); + final ConfigItem fconfigItem = configItem; + add.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JComponent field = fconfigItem + .createComponent(fconfigItem.info + .getListSize(false)); + main.add(field); + + // TODO this doesn't woooooorkk + add.invalidate(); + field.invalidate(); + main.invalidate(); + ConfigItem.this.repaint(); + ConfigItem.this.validate(); + ConfigItem.this.repaint(); + } + }); - if (newValue == null) { - return orig != null; - } + JPanel tmp = new JPanel(new BorderLayout()); + tmp.add(add, BorderLayout.WEST); - return !newValue.equals(orig); - } + JPanel mainPlus = new JPanel(new BorderLayout()); + mainPlus.add(main, BorderLayout.CENTER); + mainPlus.add(tmp, BorderLayout.SOUTH); - private void addStringField(final MetaInfo info, int nhgap) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - String value = info.getString(false); - reload(value); - field.setText(value); + add(mainPlus, BorderLayout.CENTER); + } else { + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); - info.addReloadedListener(new Runnable() { - @Override - public void run() { - String value = info.getString(false); - reload(value); - field.setText(value); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - String value = field.getText(); - if (isChanged(value)) { - info.setString(value); - } - } - }); - - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); - - setPreferredSize(field); + JComponent field = configItem.createComponent(-1); + add(field, BorderLayout.CENTER); + } } - private void addBooleanField(final MetaInfo info, int nhgap) { - final JCheckBox field = new JCheckBox(); - field.setToolTipText(info.getDescription()); - Boolean state = info.getBoolean(true); - - // Should not happen! - if (state == null) { - System.err - .println("No default value given for BOOLEAN parameter \"" - + info.getName() + "\", we consider it is FALSE"); - state = false; + /** + * Prepare a new {@link ConfigItem} instance, linked to the given + * {@link MetaInfo}. + * + * @param info + * the info + * @param autoDirtyHandling + * TRUE to automatically manage the setDirty/Save operations, + * FALSE if you want to do it yourself via + * {@link ConfigItem#setDirtyItem(int)} + */ + protected ConfigItem(MetaInfo info, boolean autoDirtyHandling) { + this.info = info; + if (!autoDirtyHandling) { + dirtyBits = new ArrayList(); } + } - reload(state); - field.setSelected(state); - - info.addReloadedListener(new Runnable() { - @Override - public void run() { - Boolean state = info.getBoolean(true); - if (state == null) { - state = false; - } - - reload(state); - field.setSelected(state); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - boolean state = field.isSelected(); - if (isChanged(state)) { - info.setBoolean(state); - } - } - }); + /** + * Create an empty graphical component to be used later by + * {@link ConfigItem#getField(int)}. + *

+ * Note that {@link ConfigItem#reload(int)} will be called after it was + * created. + * + * @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 graphical component + */ + protected JComponent createField(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the sublasses + return null; + } - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * Get the information from the {@link MetaInfo} in the subclass preferred + * format. + * + * @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 information in the subclass preferred format + */ + protected Object getFromInfo(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + return null; + } - setPreferredSize(field); + /** + * Set the value to the {@link MetaInfo}. + * + * @param value + * the value in the subclass preferred format + * @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) + */ + protected void setToInfo(@SuppressWarnings("unused") Object value, + @SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses } - private void addColorField(final MetaInfo info, int nhgap) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - String value = info.getString(false); - reload(value); - field.setText(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 + */ + protected Object getFromField(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + return null; + } - info.addReloadedListener(new Runnable() { - @Override - public void run() { - String value = info.getString(false); - reload(value); - field.setText(value); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - String value = field.getText(); - if (isChanged(value)) { - info.setString(value); - } - } - }); + /** + * Set the value (in the subclass preferred format) into the field. + * + * @param value + * the value in the subclass preferred format + * @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) + */ + protected void setToField(@SuppressWarnings("unused") Object value, + @SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + } - this.add(label(info, nhgap), BorderLayout.WEST); - JPanel pane = new JPanel(new BorderLayout()); + /** + * Create a new field for the given graphical component at the given index + * (note that the component is usually created by + * {@link ConfigItem#createField(int)}). + * + * @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 field + * the graphical component + */ + private void setField(int item, JComponent field) { + if (item < 0) { + this.field = field; + return; + } - final JButton colorWheel = new JButton(); - colorWheel.setIcon(getIcon(17, info.getColor(true))); - colorWheel.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Integer icol = info.getColor(true); - if (icol == null) { - icol = new Color(255, 255, 255, 255).getRGB(); - } - Color initialColor = new Color(icol, true); - Color newColor = JColorChooser.showDialog(ConfigItem.this, - info.getName(), initialColor); - if (newColor != null) { - info.setColor(newColor.getRGB()); - field.setText(info.getString(false)); - colorWheel.setIcon(getIcon(17, info.getColor(true))); - } - } - }); - pane.add(colorWheel, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); - this.add(pane, BorderLayout.CENTER); + for (int i = fields.size(); i <= item; i++) { + fields.add(null); + } - setPreferredSize(pane); + fields.set(item, field); } - private void addBrowseField(final MetaInfo info, int nhgap, - final boolean dir) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - String value = info.getString(false); - reload(value); - field.setText(value); - - info.addReloadedListener(new Runnable() { - @Override - public void run() { - String value = info.getString(false); - reload(value); - field.setText(value); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - String value = field.getText(); - if (isChanged(value)) { - info.setString(value); - } - } - }); - - JButton browseButton = new JButton("..."); - browseButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(null); - chooser.setFileSelectionMode(dir ? JFileChooser.DIRECTORIES_ONLY - : JFileChooser.FILES_ONLY); - if (chooser.showOpenDialog(ConfigItem.this) == JFileChooser.APPROVE_OPTION) { - File file = chooser.getSelectedFile(); - if (file != null) { - String value = file.getAbsolutePath(); - if (isChanged(value)) { - info.setString(value); - } - field.setText(value); - } - } - } - }); + /** + * Retrieve the associated graphical component that was created with + * {@link ConfigItem#createField(int)}. + * + * @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 graphical component + */ + protected JComponent getField(int item) { + if (item < 0) { + return field; + } - JPanel pane = new JPanel(new BorderLayout()); - this.add(label(info, nhgap), BorderLayout.WEST); - pane.add(browseButton, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); - this.add(pane, BorderLayout.CENTER); + if (item < fields.size()) { + return fields.get(item); + } - setPreferredSize(pane); + return null; } - private void addComboboxField(final MetaInfo info, int nhgap, - boolean editable) { - // rawtypes for Java 1.6 (and 1.7 ?) support - @SuppressWarnings({ "rawtypes", "unchecked" }) - final JComboBox field = new JComboBox(info.getAllowedValues()); - field.setEditable(editable); - String value = info.getString(false); - reload(value); - field.setSelectedItem(value); - - info.addReloadedListener(new Runnable() { - @Override - public void run() { - String value = info.getString(false); - reload(value); - field.setSelectedItem(value); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - Object item = field.getSelectedItem(); - String value = item == null ? null : item.toString(); - if (isChanged(value)) { - info.setString(value); - } - } - }); - - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * Manually specify that the given item is "dirty" and thus should be saved + * when asked. + *

+ * Has no effect if the class is using automatic dirty handling (see + * {@link ConfigItem#ConfigItem(MetaInfo, 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) + */ + protected void setDirtyItem(int item) { + if (dirtyBits != null) { + dirtyBits.add(item); + } + } - setPreferredSize(field); + /** + * Check if the value changed since the last load/save into the linked + * {@link MetaInfo}. + *

+ * Note that we consider NULL and an Empty {@link String} to be equals. + * + * @param value + * the value to test + * + * @return TRUE if it has + */ + protected boolean hasValueChanged(Object value) { + // We consider "" and NULL to be equals + return !orig.equals(value == null ? "" : value); } - private void addPasswordField(final MetaInfo info, int nhgap) { - final JPasswordField field = new JPasswordField(); - field.setToolTipText(info.getDescription()); - String value = info.getString(false); - reload(value); - field.setText(value); + /** + * Reload the values to what they currently are in the {@link MetaInfo}. + * + * @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) + */ + protected void reload(int item) { + Object value = getFromInfo(item); + setToField(value, item); - info.addReloadedListener(new Runnable() { - @Override - public void run() { - String value = info.getString(false); - reload(value); - field.setText(value); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - String value = new String(field.getPassword()); - if (isChanged(value)) { - info.setString(value); - } - } - }); + // We consider "" and NULL to be equals + orig = (value == null ? "" : value); + } - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * If the item has been modified, set the {@link MetaInfo} to dirty then + * modify it to, reflect the changes so it can be saved later. + *

+ * This method does not call {@link MetaInfo#save(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) + */ + protected void save(int item) { + Object value = getFromField(item); + + boolean dirty = false; + if (dirtyBits != null) { + dirty = dirtyBits.remove((Integer) item); + } else { + // We consider "" and NULL to be equals + dirty = hasValueChanged(value); + } - setPreferredSize(field); + if (dirty) { + info.setDirty(); + setToInfo(value, item); + orig = (value == null ? "" : value); + } } - private void addIntField(final MetaInfo info, int nhgap) { - final JSpinner field = new JSpinner(); - field.setToolTipText(info.getDescription()); - int value = info.getInteger(true) == null ? 0 : info.getInteger(true); - reload(value); - field.setValue(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 addTo + * @param nhgap + */ + protected JComponent createComponent(final int item) { + setField(item, createField(item)); + reload(item); info.addReloadedListener(new Runnable() { @Override public void run() { - int value = info.getInteger(true) == null ? 0 : info - .getInteger(true); - reload(value); - field.setValue(value); + reload(item); } }); info.addSaveListener(new Runnable() { @Override public void run() { - int value = field.getValue() == null ? 0 : (Integer) field - .getValue(); - if (isChanged(value)) { - info.setInteger(value); - } + save(item); } }); - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); - + JComponent field = getField(item); setPreferredSize(field); + + return field; } /** * Create a label which width is constrained in lock steps. * - * @param info - * the {@link MetaInfo} for which we want to add a label * @param nhgap * negative horisontal gap in pixel to use for the label, i.e., * the step lock sized labels will start smaller by that amount @@ -439,7 +429,7 @@ public class ConfigItem> extends JPanel { * * @return the label */ - private JComponent label(final MetaInfo info, int nhgap) { + protected JComponent label(int nhgap) { final JLabel label = new JLabel(info.getName()); Dimension ps = label.getPreferredSize(); @@ -517,45 +507,7 @@ public class ConfigItem> extends JPanel { return pane; } - /** - * Return an {@link Icon} to use as a colour badge for the colour field - * controls. - * - * @param size - * the size of the badge - * @param color - * the colour of the badge, which can be NULL (will return - * transparent white) - * - * @return the badge - */ - private Icon getIcon(int size, Integer color) { - // Allow null values - if (color == null) { - color = new Color(255, 255, 255, 255).getRGB(); - } - - Color c = new Color(color, true); - int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3; - Color border = (avg >= 128 ? Color.BLACK : Color.WHITE); - - BufferedImage img = new BufferedImage(size, size, - BufferedImage.TYPE_4BYTE_ABGR); - - Graphics2D g = img.createGraphics(); - try { - g.setColor(c); - g.fillRect(0, 0, img.getWidth(), img.getHeight()); - g.setColor(border); - g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1); - } finally { - g.dispose(); - } - - return new ImageIcon(img); - } - - private void setPreferredSize(JComponent field) { + protected void setPreferredSize(JComponent field) { int height = Math .max(getMinimumHeight(), field.getMinimumSize().height); setPreferredSize(new Dimension(200, height)); diff --git a/src/be/nikiroo/utils/ui/ConfigItemBoolean.java b/src/be/nikiroo/utils/ui/ConfigItemBoolean.java new file mode 100644 index 0000000..be7ff20 --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemBoolean.java @@ -0,0 +1,66 @@ +package be.nikiroo.utils.ui; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemBoolean> extends ConfigItem { + private static final long serialVersionUID = 1L; + + /** + * Create a new {@link ConfigItemBoolean} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + */ + public ConfigItemBoolean(MetaInfo info) { + super(info, true); + } + + @Override + protected Object getFromField(int item) { + JCheckBox field = (JCheckBox) getField(item); + if (field != null) { + return field.isSelected(); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getBoolean(item, false); + } + + @Override + protected void setToField(Object value, int item) { + JCheckBox field = (JCheckBox) getField(item); + if (field != null) { + // Should not happen if config enum is correct + // (but this is not enforced) + if (value == null) { + value = false; + } + + field.setSelected((Boolean) value); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setBoolean((Boolean) value, item); + } + + @Override + protected JComponent createField(int item) { + // Should not happen! + if (getFromInfo(item) == null) { + System.err + .println("No default value given for BOOLEAN parameter \"" + + info.getName() + "\", we consider it is FALSE"); + } + + return new JCheckBox(); + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemBrowse.java b/src/be/nikiroo/utils/ui/ConfigItemBrowse.java new file mode 100644 index 0000000..10f5ccb --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemBrowse.java @@ -0,0 +1,116 @@ +package be.nikiroo.utils.ui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemBrowse> extends ConfigItem { + private static final long serialVersionUID = 1L; + + private boolean dir; + private Map fields = new HashMap(); + + /** + * Create a new {@link ConfigItemBrowse} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + * @param dir + * TRUE for directory browsing, FALSE for file browsing + */ + public ConfigItemBrowse(MetaInfo info, boolean dir) { + super(info, false); + this.dir = dir; + } + + @Override + protected Object getFromField(int item) { + JTextField field = fields.get(getField(item)); + if (field != null) { + return new File(field.getText()); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + String path = info.getString(item, false); + if (path != null && !path.isEmpty()) { + return new File(path); + } + + return null; + } + + @Override + protected void setToField(Object value, int item) { + JTextField field = fields.get(getField(item)); + if (field != null) { + field.setText(value == null ? "" : ((File) value).getPath()); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setString(((File) value).getPath(), item); + } + + @Override + protected JComponent createField(final int item) { + final JPanel pane = new JPanel(new BorderLayout()); + final JTextField field = new JTextField(); + field.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + File file = null; + if (!field.getText().isEmpty()) { + file = new File(field.getText()); + } + + if (hasValueChanged(file)) { + setDirtyItem(item); + } + } + }); + + final JButton browseButton = new JButton("..."); + browseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory((File) getFromInfo(item)); + chooser.setFileSelectionMode(dir ? JFileChooser.DIRECTORIES_ONLY + : JFileChooser.FILES_ONLY); + if (chooser.showOpenDialog(ConfigItemBrowse.this) == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + if (file != null) { + setToField(file, item); + if (hasValueChanged(file)) { + setDirtyItem(item); + } + } + } + } + }); + + pane.add(browseButton, BorderLayout.WEST); + pane.add(field, BorderLayout.CENTER); + + fields.put(pane, field); + return pane; + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemColor.java b/src/be/nikiroo/utils/ui/ConfigItemColor.java new file mode 100644 index 0000000..cbb8084 --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemColor.java @@ -0,0 +1,141 @@ +package be.nikiroo.utils.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JColorChooser; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemColor> extends ConfigItem { + private static final long serialVersionUID = 1L; + + private Map fields = new HashMap(); + + /** + * Create a new {@link ConfigItemColor} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + */ + public ConfigItemColor(MetaInfo info) { + super(info, true); + } + + @Override + protected Object getFromField(int item) { + JTextField field = fields.get(getField(item)); + if (field != null) { + return field.getText(); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getString(item, false); + } + + @Override + protected void setToField(Object value, int item) { + JTextField field = fields.get(getField(item)); + if (field != null) { + field.setText(value == null ? "" : value.toString()); + } + // TODO: change color too + } + + @Override + protected void setToInfo(Object value, int item) { + info.setString((String) value, item); + } + + private int getFromInfoColor(int item) { + Integer color = info.getColor(item, true); + if (color == null) { + return new Color(255, 255, 255, 255).getRGB(); + } + + return color; + } + + @Override + protected JComponent createField(final int item) { + final JPanel pane = new JPanel(new BorderLayout()); + final JTextField field = new JTextField(); + + final JButton colorWheel = new JButton(); + colorWheel.setIcon(getIcon(17, getFromInfoColor(item))); + colorWheel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int icol = getFromInfoColor(item); + Color initialColor = new Color(icol, true); + Color newColor = JColorChooser.showDialog(ConfigItemColor.this, + info.getName(), initialColor); + if (newColor != null) { + info.setColor(newColor.getRGB(), item); + field.setText(info.getString(item, false)); + colorWheel.setIcon(getIcon(17, info.getColor(item, true))); + } + } + }); + + pane.add(colorWheel, BorderLayout.WEST); + pane.add(field, BorderLayout.CENTER); + + fields.put(pane, field); + return pane; + } + + /** + * Return an {@link Icon} to use as a colour badge for the colour field + * controls. + * + * @param size + * the size of the badge + * @param color + * the colour of the badge, which can be NULL (will return + * transparent white) + * + * @return the badge + */ + static private Icon getIcon(int size, Integer color) { + // Allow null values + if (color == null) { + color = new Color(255, 255, 255, 255).getRGB(); + } + + Color c = new Color(color, true); + int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3; + Color border = (avg >= 128 ? Color.BLACK : Color.WHITE); + + BufferedImage img = new BufferedImage(size, size, + BufferedImage.TYPE_4BYTE_ABGR); + + Graphics2D g = img.createGraphics(); + try { + g.setColor(c); + g.fillRect(0, 0, img.getWidth(), img.getHeight()); + g.setColor(border); + g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1); + } finally { + g.dispose(); + } + + return new ImageIcon(img); + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemCombobox.java b/src/be/nikiroo/utils/ui/ConfigItemCombobox.java new file mode 100644 index 0000000..db24589 --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemCombobox.java @@ -0,0 +1,66 @@ +package be.nikiroo.utils.ui; + +import javax.swing.JComboBox; +import javax.swing.JComponent; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemCombobox> extends ConfigItem { + private static final long serialVersionUID = 1L; + + private boolean editable; + + /** + * Create a new {@link ConfigItemCombobox} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + * @param editable + * allows the user to type in another value not in the list + */ + public ConfigItemCombobox(MetaInfo info, boolean editable) { + super(info, true); + this.editable = editable; + } + + @Override + protected Object getFromField(int item) { + // rawtypes for Java 1.6 (and 1.7 ?) support + @SuppressWarnings("rawtypes") + JComboBox field = (JComboBox) getField(item); + if (field != null) { + return field.getSelectedItem(); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getString(item, false); + } + + @Override + protected void setToField(Object value, int item) { + // rawtypes for Java 1.6 (and 1.7 ?) support + @SuppressWarnings("rawtypes") + JComboBox field = (JComboBox) getField(item); + if (field != null) { + field.setSelectedItem(value); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setString((String) value, item); + } + + // rawtypes for Java 1.6 (and 1.7 ?) support + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected JComponent createField(int item) { + JComboBox field = new JComboBox(info.getAllowedValues()); + field.setEditable(editable); + return field; + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemInteger.java b/src/be/nikiroo/utils/ui/ConfigItemInteger.java new file mode 100644 index 0000000..c110fae --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemInteger.java @@ -0,0 +1,53 @@ +package be.nikiroo.utils.ui; + +import javax.swing.JComponent; +import javax.swing.JSpinner; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemInteger> extends ConfigItem { + private static final long serialVersionUID = 1L; + + /** + * Create a new {@link ConfigItemInteger} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + */ + public ConfigItemInteger(MetaInfo info) { + super(info, true); + } + + @Override + protected Object getFromField(int item) { + JSpinner field = (JSpinner) getField(item); + if (field != null) { + return field.getValue(); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getInteger(item, false); + } + + @Override + protected void setToField(Object value, int item) { + JSpinner field = (JSpinner) getField(item); + if (field != null) { + field.setValue(value == null ? 0 : (Integer) value); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setInteger((Integer) value, item); + } + + @Override + protected JComponent createField(int item) { + return new JSpinner(); + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemPassword.java b/src/be/nikiroo/utils/ui/ConfigItemPassword.java new file mode 100644 index 0000000..381334e --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemPassword.java @@ -0,0 +1,53 @@ +package be.nikiroo.utils.ui; + +import javax.swing.JComponent; +import javax.swing.JPasswordField; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemPassword> extends ConfigItem { + private static final long serialVersionUID = 1L; + + /** + * Create a new {@link ConfigItemPassword} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + */ + public ConfigItemPassword(MetaInfo info) { + super(info, true); + } + + @Override + protected Object getFromField(int item) { + JPasswordField field = (JPasswordField) getField(item); + if (field != null) { + return new String(field.getPassword()); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getString(item, false); + } + + @Override + protected void setToField(Object value, int item) { + JPasswordField field = (JPasswordField) getField(item); + if (field != null) { + field.setText(value == null ? "" : value.toString()); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setString((String) value, item); + } + + @Override + protected JComponent createField(int item) { + return new JPasswordField(); + } +} diff --git a/src/be/nikiroo/utils/ui/ConfigItemString.java b/src/be/nikiroo/utils/ui/ConfigItemString.java new file mode 100644 index 0000000..5cf4101 --- /dev/null +++ b/src/be/nikiroo/utils/ui/ConfigItemString.java @@ -0,0 +1,53 @@ +package be.nikiroo.utils.ui; + +import javax.swing.JComponent; +import javax.swing.JTextField; + +import be.nikiroo.utils.resources.MetaInfo; + +public class ConfigItemString> extends ConfigItem { + private static final long serialVersionUID = 1L; + + /** + * Create a new {@link ConfigItemString} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + */ + public ConfigItemString(MetaInfo info) { + super(info, true); + } + + @Override + protected Object getFromField(int item) { + JTextField field = (JTextField) getField(item); + if (field != null) { + return field.getText(); + } + + return null; + } + + @Override + protected Object getFromInfo(int item) { + return info.getString(item, false); + } + + @Override + protected void setToField(Object value, int item) { + JTextField field = (JTextField) getField(item); + if (field != null) { + field.setText(value == null ? "" : value.toString()); + } + } + + @Override + protected void setToInfo(Object value, int item) { + info.setString((String) value, item); + } + + @Override + protected JComponent createField(int item) { + return new JTextField(); + } +} -- 2.27.0