Improve ConfigItems and fix some related bugs
authorNiki Roo <niki@nikiroo.be>
Tue, 28 May 2019 21:13:17 +0000 (23:13 +0200)
committerNiki Roo <niki@nikiroo.be>
Tue, 28 May 2019 21:13:17 +0000 (23:13 +0200)
13 files changed:
src/be/nikiroo/utils/TraceHandler.java
src/be/nikiroo/utils/resources/Bundle.java
src/be/nikiroo/utils/resources/BundleHelper.java
src/be/nikiroo/utils/resources/MetaInfo.java
src/be/nikiroo/utils/ui/ConfigEditor.java
src/be/nikiroo/utils/ui/ConfigItem.java
src/be/nikiroo/utils/ui/ConfigItemBoolean.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemBrowse.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemColor.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemCombobox.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemInteger.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemPassword.java [new file with mode: 0644]
src/be/nikiroo/utils/ui/ConfigItemString.java [new file with mode: 0644]

index e1dc50fb1c7b1e0136cb68cbe56dcfe274f1378c..0a09712d5218013992493838c75ad9aa0ad92527 100644 (file)
@@ -95,7 +95,7 @@ public class TraceHandler {
                                System.err.print(StringUtils.fromTime(now) + ": ");
                                e.printStackTrace();
                        } else {
-                               error(e.getMessage());
+                               error(e.toString());
                        }
                }
        }
index 3506fdde070d82f0e24d803412a68e17523e0333..68c236fa108e7f54d842775d408607f7e2a7551c 100644 (file)
@@ -137,6 +137,26 @@ public class Bundle<E extends Enum<E>> {
         *         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}.
+        * <p>
+        * 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<E extends Enum<E>> {
                        }
                }
 
-               if (rep == null) {
-                       rep = def;
+               //TODO: is it ok? need to jDoc?
+               if (rep == null || rep.isEmpty()) {
+                       return def;
+               }
+
+               if (item >= 0) {
+                       List<String> 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<E extends Enum<E>> {
                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<String> 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<E extends Enum<E>> {
         *         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).
+        * <p>
+        * Will only accept suffixes that form an existing id.
+        * <p>
+        * 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).
+        * <p>
+        * Will only accept suffixes that form an existing id.
+        * <p>
+        * 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<E extends Enum<E>> {
         *            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).
+        * <p>
+        * 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<E extends Enum<E>> {
         * @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<E extends Enum<E>> {
         * @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}.
+        * <p>
+        * 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<E extends Enum<E>> {
         * 
         */
        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<E extends Enum<E>> {
         * @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<E extends Enum<E>> {
         * @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.
+        * <p>
+        * 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<E extends Enum<E>> {
         * 
         */
        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<E extends Enum<E>> {
         * @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<E extends Enum<E>> {
         * @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}.
+        * <p>
+        * 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<E extends Enum<E>> {
         * @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.
+        * <p>
+        * The returned value is an ARGB value.
+        * <p>
+        * 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.
+        * <p>
+        * The returned value is an ARGB value.
+        * <p>
+        * 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<E extends Enum<E>> {
         *            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<E extends Enum<E>> {
         *         not found or cannot be parsed as a list
         */
        public List<String> 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.
+        * <p>
+        * 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<String> getList(E id, List<String> def) {
+               List<String> 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.
+        * <p>
+        * 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<String> getList(E id, List<String> def, int item) {
+               String value = getString(id);
+               if (value != null) {
+                       return BundleHelper.parseList(value, item);
+               }
+
+               return def;
        }
 
        /**
@@ -411,7 +776,23 @@ public class Bundle<E extends Enum<E>> {
         *            the new list of values
         */
        public void setList(E id, List<String> 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<String> value, int item) {
+               setString(id, BundleHelper.fromList(value), item);
        }
 
        /**
@@ -627,7 +1008,7 @@ public class Bundle<E extends Enum<E>> {
                        }
 
                        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)");
                        }
                }
 
index 45f7cf4ccee250ec791009dcc983258db27bdedf..5d2638f865fb965df7b41629c8a03ea34fdec4ff 100644 (file)
@@ -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<String> list = parseList(raw, -1);
+               if (list == null) {
+                       return -1;
+               }
+
+               return list.size();
+       }
+
        /**
         * Return a {@link String} representation of the given list of values.
         * <p>
         * 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<String> parseList(String str) {
+       static public List<String> parseList(String str, int item) {
                if (str == null) {
                        return null;
                }
+
+               if (item >= 0) {
+                       str = getItem(str, item);
+               }
+
                List<String> list = new ArrayList<String>();
                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.
+        * <p>
+        * 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...).
         * <p>
         * 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).
         * <p>
         * 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 <tt>item</tt> if found,
+        *         NULL if not
+        */
+       static private String getItem(String value, int item) {
+               if (item >= 0) {
+                       value = null;
+                       List<String> values = parseList(value, -1);
+                       if (values != null && item < values.size()) {
+                               value = values.get(item);
+                       }
+               }
+
+               return value;
+       }
 }
index 9f86843226136fc071e38e36df5ea62b8bd7be9e..e7a8b7c5fe09b5f0a72e36a1161cfde4f2ecfe6f 100644 (file)
@@ -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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
        private String name;
        private String description;
 
+       private boolean dirty;
+
        /**
         * Create a new {@link MetaInfo} from a value (without children).
         * <p>
@@ -171,6 +174,42 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
                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.
         * <p>
@@ -187,15 +226,32 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
        /**
         * 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<String> 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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
        /**
         * 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<String> 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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * <p>
         * 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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * <p>
         * 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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 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<String> getList(boolean useDefaultIfEmpty) {
-               return BundleHelper.parseList(getString(useDefaultIfEmpty));
+       public List<String> getList(int item, boolean useDefaultIfEmpty) {
+               return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1);
        }
 
        /**
@@ -326,10 +439,15 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 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<String> getDefaultList() {
-               return BundleHelper.parseList(getDefaultString());
+       public List<String> getDefaultList(int item) {
+               return BundleHelper.parseList(getDefaultString(item), -1);
        }
 
        /**
@@ -337,9 +455,22 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<String> 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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
         * 
         * @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<String> value) {
-               setString(BundleHelper.fromList(value));
+       public void setList(List<String> value, int item) {
+               setString(BundleHelper.fromList(value), item);
        }
 
        /**
@@ -414,7 +564,6 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
                        try {
                                listener.run();
                        } catch (Exception e) {
-                               // TODO: error management?
                                e.printStackTrace();
                        }
                }
@@ -434,17 +583,26 @@ public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
 
        /**
         * Save the current value to the {@link Bundle}.
+        * <p>
+        * Note that listeners will be called <b>before</b> the dirty check and
+        * <b>before</b> 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);
+               }
        }
 
        /**
index 45a534ea0c1c576d02e76e71fc5d32cbe04ff270..2bc9dc9ff36652db8d4cd7dbbb3e1b1da2f9f2e8 100644 (file)
@@ -94,7 +94,7 @@ public class ConfigEditor<E extends Enum<E>> extends JPanel {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                for (MetaInfo<E> item : items) {
-                                       item.save();
+                                       item.save(true);
                                }
 
                                try {
index 04bf76c04683291067ffb6671132e225ed7afbe7..f974969335c2a5b2d725e5682cfe04e8e47420ad 100644 (file)
@@ -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<E extends Enum<E>> extends JPanel {
 
        /** The original value before current changes. */
        private Object orig;
+       private List<Integer> dirtyBits;
+
+       protected MetaInfo<E> info;
+
+       private JComponent field;
+       private List<JComponent> fields = new ArrayList<JComponent>();
 
        /**
         * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
@@ -80,357 +78,349 @@ public class ConfigItem<E extends Enum<E>> extends JPanel {
         *            different horisontal position)
         */
        public ConfigItem(MetaInfo<E> 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<E> configItem;
+               switch (info.getFormat()) {
                case BOOLEAN:
-                       addBooleanField(info, nhgap);
+                       configItem = new ConfigItemBoolean<E>(info);
                        break;
                case COLOR:
-                       addColorField(info, nhgap);
+                       configItem = new ConfigItemColor<E>(info);
                        break;
                case FILE:
-                       addBrowseField(info, nhgap, false);
+                       configItem = new ConfigItemBrowse<E>(info, false);
                        break;
                case DIRECTORY:
-                       addBrowseField(info, nhgap, true);
+                       configItem = new ConfigItemBrowse<E>(info, true);
                        break;
                case COMBO_LIST:
-                       addComboboxField(info, nhgap, true);
+                       configItem = new ConfigItemCombobox<E>(info, true);
                        break;
                case FIXED_LIST:
-                       addComboboxField(info, nhgap, false);
+                       configItem = new ConfigItemCombobox<E>(info, false);
                        break;
                case INT:
-                       addIntField(info, nhgap);
+                       configItem = new ConfigItemInteger<E>(info);
                        break;
                case PASSWORD:
-                       addPasswordField(info, nhgap);
+                       configItem = new ConfigItemPassword<E>(info);
                        break;
                case STRING:
                case LOCALE: // TODO?
                default:
-                       addStringField(info, nhgap);
+                       configItem = new ConfigItemString<E>(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<E> 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<E> 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<E> 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<E> info, boolean autoDirtyHandling) {
+               this.info = info;
+               if (!autoDirtyHandling) {
+                       dirtyBits = new ArrayList<Integer>();
                }
+       }
 
-               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)}.
+        * <p>
+        * 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<E> 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<E> 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<E> 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.
+        * <p>
+        * 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}.
+        * <p>
+        * 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<E> 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.
+        * <p>
+        * This method does <b>not</b> 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<E> 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<E extends Enum<E>> extends JPanel {
         * 
         * @return the label
         */
-       private JComponent label(final MetaInfo<E> 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<E extends Enum<E>> 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 (file)
index 0000000..be7ff20
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Create a new {@link ConfigItemBoolean} for the given {@link MetaInfo}.
+        * 
+        * @param info
+        *            the {@link MetaInfo}
+        */
+       public ConfigItemBoolean(MetaInfo<E> 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 (file)
index 0000000..10f5ccb
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       private boolean dir;
+       private Map<JComponent, JTextField> fields = new HashMap<JComponent, JTextField>();
+
+       /**
+        * 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<E> 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 (file)
index 0000000..cbb8084
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       private Map<JComponent, JTextField> fields = new HashMap<JComponent, JTextField>();
+
+       /**
+        * Create a new {@link ConfigItemColor} for the given {@link MetaInfo}.
+        * 
+        * @param info
+        *            the {@link MetaInfo}
+        */
+       public ConfigItemColor(MetaInfo<E> 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 (file)
index 0000000..db24589
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       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<E> 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 (file)
index 0000000..c110fae
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Create a new {@link ConfigItemInteger} for the given {@link MetaInfo}.
+        * 
+        * @param info
+        *            the {@link MetaInfo}
+        */
+       public ConfigItemInteger(MetaInfo<E> 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 (file)
index 0000000..381334e
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Create a new {@link ConfigItemPassword} for the given {@link MetaInfo}.
+        * 
+        * @param info
+        *            the {@link MetaInfo}
+        */
+       public ConfigItemPassword(MetaInfo<E> 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 (file)
index 0000000..5cf4101
--- /dev/null
@@ -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<E extends Enum<E>> extends ConfigItem<E> {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Create a new {@link ConfigItemString} for the given {@link MetaInfo}.
+        * 
+        * @param info
+        *            the {@link MetaInfo}
+        */
+       public ConfigItemString(MetaInfo<E> 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();
+       }
+}