From: Niki Roo Date: Tue, 19 Nov 2019 12:37:21 +0000 (+0100) Subject: Merge branch 'subtree' X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=commitdiff_plain;h=d80040bdb4bacb152c67eb40141f9e1dc1aa3c8b;hp=75efdb7e651001aa1141c3080a80dd92544cc08f Merge branch 'subtree' --- diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 1f69886..3ae029e 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -9,10 +9,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.swing.BoxLayout; import javax.swing.ImageIcon; @@ -93,23 +90,86 @@ public abstract class ConfigItem> extends JPanel { + "zhT8sfdKeehWkUQAeJ7WcH23xTz1uPBwf1hclA3mBZjPojFOIOSsVPpmN1OznfpA+Gn+2kCHqg/d" + "LhIA/AFU5d0V6gTjtQAAAABJRU5ErkJggg=="; - /** The original value before current changes. */ - private Object orig; - private List origs = new ArrayList(); - private List dirtyBits; - - /** The fields (one for non-array, a list for arrays). */ - private JComponent field; - private List fields = new ArrayList(); - - /** The fields to panel map to get the actual item added to 'main'. */ - private Map itemFields = new HashMap(); + /** The code base */ + private final ConfigItemBase base; /** The main panel with all the fields in it. */ private JPanel main; - /** The {@link MetaInfo} linked to the field. */ - protected MetaInfo info; + /** + * Prepare a new {@link ConfigItem} instance, linked to the given + * {@link MetaInfo}. + * + * @param info + * the info + * @param autoDirtyHandling + * TRUE to automatically manage the setDirty/Save operations, + * FALSE if you want to do it yourself via + * {@link ConfigItem#setDirtyItem(int)} + */ + protected ConfigItem(MetaInfo info, boolean autoDirtyHandling) { + base = new ConfigItemBase(info, autoDirtyHandling) { + @Override + protected JComponent createEmptyField(int item) { + return ConfigItem.this.createEmptyField(item); + } + + @Override + protected Object getFromInfo(int item) { + return ConfigItem.this.getFromInfo(item); + } + + @Override + protected void setToInfo(Object value, int item) { + ConfigItem.this.setToInfo(value, item); + } + + @Override + protected Object getFromField(int item) { + return ConfigItem.this.getFromField(item); + } + + @Override + protected void setToField(Object value, int item) { + ConfigItem.this.setToField(value, item); + } + + @Override + public JComponent createField(int item) { + JComponent field = super.createField(item); + + int height = Math.max(getMinimumHeight(), + field.getMinimumSize().height); + field.setPreferredSize(new Dimension(200, height)); + + return field; + } + + @Override + public List reload() { + List removed = base.reload(); + if (!removed.isEmpty()) { + for (JComponent c : removed) { + main.remove(c); + } + main.revalidate(); + main.repaint(); + } + + return removed; + } + + @Override + protected JComponent removeItem(int item) { + JComponent removed = super.removeItem(item); + main.remove(removed); + main.revalidate(); + main.repaint(); + + return removed; + } + }; + } /** * Create a new {@link ConfigItem} for the given {@link MetaInfo}. @@ -121,16 +181,16 @@ public abstract class ConfigItem> extends JPanel { * different horisontal position) */ public void init(int nhgap) { - if (info.isArray()) { + if (getInfo().isArray()) { this.setLayout(new BorderLayout()); add(label(nhgap), BorderLayout.WEST); main = new JPanel(); main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); - int size = info.getListSize(false); + int size = getInfo().getListSize(false); for (int i = 0; i < size; i++) { - addItem(i); + addItemWithMinusPanel(i); } main.revalidate(); main.repaint(); @@ -141,7 +201,7 @@ public abstract class ConfigItem> extends JPanel { add.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - addItem(fields.size()); + addItemWithMinusPanel(base.getFieldsSize()); main.revalidate(); main.repaint(); } @@ -159,16 +219,74 @@ public abstract class ConfigItem> extends JPanel { this.setLayout(new BorderLayout()); add(label(nhgap), BorderLayout.WEST); - JComponent field = createField(-1); + JComponent field = base.createField(-1); add(field, BorderLayout.CENTER); } } - private void addItem(final int item) { - JPanel minusPanel = new JPanel(new BorderLayout()); - itemFields.put(item, minusPanel); + /** The {@link MetaInfo} linked to the field. */ + public MetaInfo getInfo() { + return base.getInfo(); + } + + /** + * Retrieve the associated graphical component that was created with + * {@link ConfigItemBase#createEmptyField(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) { + return base.getField(item); + } + + /** + * Manually specify that the given item is "dirty" and thus should be saved + * when asked. + *

+ * Has no effect if the class is using automatic dirty handling (see + * {@link ConfigItemBase#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) { + base.setDirtyItem(item); + } + + /** + * Check if the value changed since the last load/save into the linked + * {@link MetaInfo}. + *

+ * Note that we consider NULL and an Empty {@link String} to be equals. + * + * @param value + * the value to test + * @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 TRUE if it has + */ + protected boolean hasValueChanged(Object value, int item) { + return base.hasValueChanged(value, item); + } + + private void addItemWithMinusPanel(int item) { + JPanel minusPanel = createMinusPanel(item); + JComponent field = base.addItem(item, minusPanel); + minusPanel.add(field, BorderLayout.CENTER); + } - JComponent field = createField(item); + private JPanel createMinusPanel(final int item) { + JPanel minusPanel = new JPanel(new BorderLayout()); final JButton remove = new JButton(); setImage(remove, img64remove, "-"); @@ -176,52 +294,17 @@ public abstract class ConfigItem> extends JPanel { remove.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - removeItem(item); + base.removeItem(item); } }); - minusPanel.add(field, BorderLayout.CENTER); minusPanel.add(remove, BorderLayout.EAST); main.add(minusPanel); main.revalidate(); main.repaint(); - } - - private void removeItem(int item) { - int last = itemFields.size() - 1; - - for (int i = item; i <= last; i++) { - Object value = null; - if (i < last) { - value = getFromField(i + 1); - } - setToField(value, i); - setToInfo(value, i); - setDirtyItem(i); - } - - main.remove(itemFields.remove(last)); - main.revalidate(); - main.repaint(); - } - /** - * Prepare a new {@link ConfigItem} instance, linked to the given - * {@link MetaInfo}. - * - * @param info - * the info - * @param autoDirtyHandling - * TRUE to automatically manage the setDirty/Save operations, - * FALSE if you want to do it yourself via - * {@link ConfigItem#setDirtyItem(int)} - */ - protected ConfigItem(MetaInfo info, boolean autoDirtyHandling) { - this.info = info; - if (!autoDirtyHandling) { - dirtyBits = new ArrayList(); - } + return minusPanel; } /** @@ -291,279 +374,6 @@ public abstract class ConfigItem> extends JPanel { */ abstract protected void setToField(Object value, int item); - /** - * Create a new field for the given graphical component at the given index - * (note that the component is usually created by - * {@link ConfigItem#createEmptyField(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; - } - - for (int i = fields.size(); i <= item; i++) { - fields.add(null); - } - - fields.set(item, field); - } - - /** - * Retrieve the associated graphical component that was created with - * {@link ConfigItem#createEmptyField(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; - } - - if (item < fields.size()) { - return fields.get(item); - } - - return null; - } - - /** - * The original value (before any changes to the {@link MetaInfo}) for this - * item. - * - * @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 original value - */ - private Object getOrig(int item) { - if (item < 0) { - return orig; - } - - if (item < origs.size()) { - return origs.get(item); - } - - return null; - } - - /** - * The original value (before any changes to the {@link MetaInfo}) for this - * item. - * - * @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 value - * the new original value - */ - private void setOrig(Object value, int item) { - if (item < 0) { - orig = value; - } else { - while (item >= origs.size()) { - origs.add(null); - } - - origs.set(item, value); - } - } - - /** - * Manually specify that the given item is "dirty" and thus should be saved - * when asked. - *

- * Has no effect if the class is using automatic dirty handling (see - * {@link ConfigItem#ConfigItem(MetaInfo, boolean)}). - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - protected void setDirtyItem(int item) { - if (dirtyBits != null) { - dirtyBits.add(item); - } - } - - /** - * Check if the value changed since the last load/save into the linked - * {@link MetaInfo}. - *

- * Note that we consider NULL and an Empty {@link String} to be equals. - * - * @param value - * the value to test - * @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 TRUE if it has - */ - protected boolean hasValueChanged(Object value, int item) { - // We consider "" and NULL to be equals - Object orig = getOrig(item); - if (orig == null) { - orig = ""; - } - return !orig.equals(value == null ? "" : value); - } - - /** - * Reload the values to what they currently are in the {@link MetaInfo}. - */ - private void reload() { - if (info.isArray()) { - while (!itemFields.isEmpty()) { - main.remove(itemFields.remove(itemFields.size() - 1)); - } - main.revalidate(); - main.repaint(); - for (int item = 0; item < info.getListSize(false); item++) { - reload(item); - } - } else { - reload(-1); - } - } - - /** - * 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) - */ - private void reload(int item) { - if (item >= 0 && !itemFields.containsKey(item)) { - addItem(item); - } - - Object value = getFromInfo(item); - setToField(value, item); - setOrig(value == null ? "" : value, item); - } - - /** - * If the item has been modified, set the {@link MetaInfo} to dirty then - * modify it to, reflect the changes so it can be saved later. - *

- * This method does not call {@link MetaInfo#save(boolean)}. - */ - private void save() { - if (info.isArray()) { - boolean dirty = itemFields.size() != info.getListSize(false); - for (int item = 0; item < itemFields.size(); item++) { - if (getDirtyBit(item)) { - dirty = true; - } - } - - if (dirty) { - info.setDirty(); - info.setString(null, -1); - - for (int item = 0; item < itemFields.size(); item++) { - Object value = null; - if (getField(item) != null) { - value = getFromField(item); - if ("".equals(value)) { - value = null; - } - } - - setToInfo(value, item); - setOrig(value, item); - } - } - } else { - if (getDirtyBit(-1)) { - Object value = getFromField(-1); - - info.setDirty(); - setToInfo(value, -1); - setOrig(value, -1); - } - } - } - - /** - * Check if the item is dirty, and clear the dirty bit if set. - * - * @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 TRUE if it was dirty, FALSE if not - */ - private boolean getDirtyBit(int item) { - if (dirtyBits != null) { - return dirtyBits.remove((Integer) item); - } - - Object value = null; - if (getField(item) != null) { - value = getFromField(item); - } - - return hasValueChanged(value, item); - } - - /** - * Create a new field for the given item. - * - * @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 newly created field - */ - protected JComponent createField(final int item) { - JComponent field = createEmptyField(item); - setField(item, field); - reload(item); - - info.addReloadedListener(new Runnable() { - @Override - public void run() { - reload(); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - save(); - } - }); - - int height = Math - .max(getMinimumHeight(), field.getMinimumSize().height); - field.setPreferredSize(new Dimension(200, height)); - - return field; - } - /** * Create a label which width is constrained in lock steps. * @@ -576,7 +386,7 @@ public abstract class ConfigItem> extends JPanel { * @return the label */ protected JComponent label(int nhgap) { - final JLabel label = new JLabel(info.getName()); + final JLabel label = new JLabel(getInfo().getName()); Dimension ps = label.getPreferredSize(); if (ps == null) { @@ -598,7 +408,7 @@ public abstract class ConfigItem> extends JPanel { @Override public void run() { StringBuilder builder = new StringBuilder(); - String text = (info.getDescription().replace("\\n", "\n")) + String text = (getInfo().getDescription().replace("\\n", "\n")) .trim(); for (String line : StringUtils.justifyText(text, 80, Alignment.LEFT)) { @@ -608,8 +418,8 @@ public abstract class ConfigItem> extends JPanel { builder.append(line); } text = builder.toString(); - JOptionPane.showMessageDialog(ConfigItem.this, text, - info.getName(), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(ConfigItem.this, text, getInfo() + .getName(), JOptionPane.INFORMATION_MESSAGE); } }; diff --git a/src/be/nikiroo/utils/ui/ConfigItemBoolean.java b/src/be/nikiroo/utils/ui/ConfigItemBoolean.java index 255ec13..de89f68 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemBoolean.java +++ b/src/be/nikiroo/utils/ui/ConfigItemBoolean.java @@ -30,7 +30,7 @@ class ConfigItemBoolean> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getBoolean(item, true); + return getInfo().getBoolean(item, true); } @Override @@ -49,7 +49,7 @@ class ConfigItemBoolean> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setBoolean((Boolean) value, item); + getInfo().setBoolean((Boolean) value, item); } @Override @@ -58,7 +58,8 @@ class ConfigItemBoolean> extends ConfigItem { if (getFromInfo(item) == null) { System.err .println("No default value given for BOOLEAN parameter \"" - + info.getName() + "\", we consider it is FALSE"); + + getInfo().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 index 6c8af99..9a54e52 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemBrowse.java +++ b/src/be/nikiroo/utils/ui/ConfigItemBrowse.java @@ -48,7 +48,7 @@ class ConfigItemBrowse> extends ConfigItem { @Override protected Object getFromInfo(int item) { - String path = info.getString(item, false); + String path = getInfo().getString(item, false); if (path != null && !path.isEmpty()) { return new File(path); } @@ -66,7 +66,7 @@ class ConfigItemBrowse> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setString(((File) value).getPath(), item); + getInfo().setString(((File) value).getPath(), item); } @Override diff --git a/src/be/nikiroo/utils/ui/ConfigItemColor.java b/src/be/nikiroo/utils/ui/ConfigItemColor.java index 500efff..951ff45 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemColor.java +++ b/src/be/nikiroo/utils/ui/ConfigItemColor.java @@ -49,7 +49,7 @@ class ConfigItemColor> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getString(item, true); + return getInfo().getString(item, true); } @Override @@ -67,7 +67,7 @@ class ConfigItemColor> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setString((String) value, item); + getInfo().setString((String) value, item); } /** @@ -81,7 +81,7 @@ class ConfigItemColor> extends ConfigItem { * @return a colour */ private int getFromInfoColor(int item) { - Integer color = info.getColor(item, true); + Integer color = getInfo().getColor(item, true); if (color == null) { return new Color(255, 255, 255, 255).getRGB(); } @@ -102,11 +102,12 @@ class ConfigItemColor> extends ConfigItem { int icol = getFromInfoColor(item); Color initialColor = new Color(icol, true); Color newColor = JColorChooser.showDialog(ConfigItemColor.this, - info.getName(), initialColor); + getInfo().getName(), initialColor); if (newColor != null) { - info.setColor(newColor.getRGB(), item); - field.setText(info.getString(item, false)); - colorWheel.setIcon(getIcon(17, info.getColor(item, true))); + getInfo().setColor(newColor.getRGB(), item); + field.setText(getInfo().getString(item, false)); + colorWheel.setIcon(getIcon(17, + getInfo().getColor(item, true))); } } }); @@ -114,7 +115,7 @@ class ConfigItemColor> extends ConfigItem { field.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { - info.setString(field.getText() + e.getKeyChar(), item); + getInfo().setString(field.getText() + e.getKeyChar(), item); int color = getFromInfoColor(item); colorWheel.setIcon(getIcon(17, color)); } diff --git a/src/be/nikiroo/utils/ui/ConfigItemCombobox.java b/src/be/nikiroo/utils/ui/ConfigItemCombobox.java index b77e0a8..07a6115 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemCombobox.java +++ b/src/be/nikiroo/utils/ui/ConfigItemCombobox.java @@ -39,7 +39,7 @@ class ConfigItemCombobox> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getString(item, false); + return getInfo().getString(item, false); } @Override @@ -54,7 +54,7 @@ class ConfigItemCombobox> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setString((String) value, item); + getInfo().setString((String) value, item); } // rawtypes for Java 1.6 (and 1.7 ?) support diff --git a/src/be/nikiroo/utils/ui/ConfigItemInteger.java b/src/be/nikiroo/utils/ui/ConfigItemInteger.java index 9b838a5..10c5d9d 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemInteger.java +++ b/src/be/nikiroo/utils/ui/ConfigItemInteger.java @@ -30,7 +30,7 @@ class ConfigItemInteger> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getInteger(item, true); + return getInfo().getInteger(item, true); } @Override @@ -43,7 +43,7 @@ class ConfigItemInteger> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setInteger((Integer) value, item); + getInfo().setInteger((Integer) value, item); } @Override diff --git a/src/be/nikiroo/utils/ui/ConfigItemPassword.java b/src/be/nikiroo/utils/ui/ConfigItemPassword.java index 348b78f..e8ad2f2 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemPassword.java +++ b/src/be/nikiroo/utils/ui/ConfigItemPassword.java @@ -61,7 +61,7 @@ class ConfigItemPassword> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getString(item, false); + return getInfo().getString(item, false); } @Override @@ -74,7 +74,7 @@ class ConfigItemPassword> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setString((String) value, item); + getInfo().setString((String) value, item); } @Override diff --git a/src/be/nikiroo/utils/ui/ConfigItemString.java b/src/be/nikiroo/utils/ui/ConfigItemString.java index 99a8cc3..46333c0 100644 --- a/src/be/nikiroo/utils/ui/ConfigItemString.java +++ b/src/be/nikiroo/utils/ui/ConfigItemString.java @@ -30,7 +30,7 @@ class ConfigItemString> extends ConfigItem { @Override protected Object getFromInfo(int item) { - return info.getString(item, false); + return getInfo().getString(item, false); } @Override @@ -43,7 +43,7 @@ class ConfigItemString> extends ConfigItem { @Override protected void setToInfo(Object value, int item) { - info.setString((String) value, item); + getInfo().setString((String) value, item); } @Override diff --git a/ui/ConfigItemBase.java b/ui/ConfigItemBase.java new file mode 100644 index 0000000..21b5755 --- /dev/null +++ b/ui/ConfigItemBase.java @@ -0,0 +1,467 @@ +package be.nikiroo.utils.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import be.nikiroo.utils.resources.Bundle; +import be.nikiroo.utils.resources.MetaInfo; + +/** + * A graphical item that reflect a configuration option from the given + * {@link Bundle}. + *

+ * This graphical item can be edited, and the result will be saved back into the + * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should + * you wish to, of course. + * + * @author niki + * + * @param + * the graphical base type to use (i.e., T or TWidget) + * @param + * the type of {@link Bundle} to edit + */ +public abstract class ConfigItemBase> { + /** The original value before current changes. */ + private Object orig; + private List origs = new ArrayList(); + private List dirtyBits; + + /** The fields (one for non-array, a list for arrays). */ + private T field; + private List fields = new ArrayList(); + + /** The fields to panel map to get the actual item added to 'main'. */ + private Map itemFields = new HashMap(); + + /** The {@link MetaInfo} linked to the field. */ + private MetaInfo info; + + /** The {@link MetaInfo} linked to the field. */ + public MetaInfo getInfo() { + return info; + } + + /** + * The number of fields, for arrays. + * + * @return + */ + public int getFieldsSize() { + return fields.size(); + } + + /** + * The number of fields to panel map to get the actual item added to 'main'. + */ + public int getItemFieldsSize() { + return itemFields.size(); + } + + /** + * Add a new item in an array-value {@link MetaInfo}. + * + * @param item + * the index of the new item + * @param panel + * a linked T, if we want to link it into the itemFields (can be + * NULL) -- that way, we can get it back later on + * {@link ConfigItemBase#removeItem(int)} + * + * @return the newly created graphical field + */ + public T addItem(final int item, T panel) { + if (panel != null) { + itemFields.put(item, panel); + } + return createField(item); + } + + /** + * The counter-part to {@link ConfigItemBase#addItem(int, Object)}, to + * remove a specific item of an array-values {@link MetaInfo}; all the + * remaining items will be shifted as required (so, always the last + * graphical object will be removed). + * + * @param item + * the index of the item to remove + * + * @return the linked graphical T to remove if any (always the latest + * graphical object if any) + */ + protected T removeItem(int item) { + int last = itemFields.size() - 1; + + for (int i = item; i <= last; i++) { + Object value = null; + if (i < last) { + value = getFromField(i + 1); + } + setToField(value, i); + setToInfo(value, i); + setDirtyItem(i); + } + + return itemFields.remove(last); + } + + /** + * Prepare a new {@link ConfigItemBase} 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 ConfigItemBase#setDirtyItem(int)} + */ + protected ConfigItemBase(MetaInfo info, boolean autoDirtyHandling) { + this.info = info; + if (!autoDirtyHandling) { + dirtyBits = new ArrayList(); + } + } + + /** + * Create an empty graphical component to be used later by + * {@link ConfigItemBase#createField(int)}. + *

+ * Note that {@link ConfigItemBase#reload(int)} will be called after it was + * created by {@link ConfigItemBase#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 + */ + abstract protected T createEmptyField(int item); + + /** + * 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 + */ + abstract protected Object getFromInfo(int item); + + /** + * 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) + */ + abstract protected void setToInfo(Object value, int item); + + /** + * The value present in the given item's related field 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 value present in the given item's related field in the + * subclass preferred format + */ + abstract protected Object getFromField(int item); + + /** + * 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) + */ + abstract protected void setToField(Object value, int item); + + /** + * Create a new field for the given graphical component at the given index + * (note that the component is usually created by + * {@link ConfigItemBase#createEmptyField(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, T field) { + if (item < 0) { + this.field = field; + return; + } + + for (int i = fields.size(); i <= item; i++) { + fields.add(null); + } + + fields.set(item, field); + } + + /** + * Retrieve the associated graphical component that was created with + * {@link ConfigItemBase#createEmptyField(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 + */ + public T getField(int item) { + if (item < 0) { + return field; + } + + if (item < fields.size()) { + return fields.get(item); + } + + return null; + } + + /** + * The original value (before any changes to the {@link MetaInfo}) for this + * item. + * + * @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 original value + */ + private Object getOrig(int item) { + if (item < 0) { + return orig; + } + + if (item < origs.size()) { + return origs.get(item); + } + + return null; + } + + /** + * The original value (before any changes to the {@link MetaInfo}) for this + * item. + * + * @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 value + * the new original value + */ + private void setOrig(Object value, int item) { + if (item < 0) { + orig = value; + } else { + while (item >= origs.size()) { + origs.add(null); + } + + origs.set(item, value); + } + } + + /** + * Manually specify that the given item is "dirty" and thus should be saved + * when asked. + *

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

+ * Note that we consider NULL and an Empty {@link String} to be equals. + * + * @param value + * the value to test + * @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 TRUE if it has + */ + public boolean hasValueChanged(Object value, int item) { + // We consider "" and NULL to be equals + Object orig = getOrig(item); + if (orig == null) { + orig = ""; + } + return !orig.equals(value == null ? "" : value); + } + + /** + * Reload the values to what they currently are in the {@link MetaInfo}. + * + * @return for arrays, the list of graphical T objects we don't need any + * more (never NULL, but can be empty) + */ + public List reload() { + List removed = new ArrayList(); + if (info.isArray()) { + while (!itemFields.isEmpty()) { + removed.add(itemFields.remove(itemFields.size() - 1)); + } + for (int item = 0; item < info.getListSize(false); item++) { + reload(item); + } + } else { + reload(-1); + } + + return removed; + } + + /** + * 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) + */ + private void reload(int item) { + if (item >= 0 && !itemFields.containsKey(item)) { + addItem(item, null); + } + + Object value = getFromInfo(item); + setToField(value, item); + setOrig(value == null ? "" : value, item); + } + + /** + * If the item has been modified, set the {@link MetaInfo} to dirty then + * modify it to, reflect the changes so it can be saved later. + *

+ * This method does not call {@link MetaInfo#save(boolean)}. + */ + private void save() { + if (info.isArray()) { + boolean dirty = itemFields.size() != info.getListSize(false); + for (int item = 0; item < itemFields.size(); item++) { + if (getDirtyBit(item)) { + dirty = true; + } + } + + if (dirty) { + info.setDirty(); + info.setString(null, -1); + + for (int item = 0; item < itemFields.size(); item++) { + Object value = null; + if (getField(item) != null) { + value = getFromField(item); + if ("".equals(value)) { + value = null; + } + } + + setToInfo(value, item); + setOrig(value, item); + } + } + } else { + if (getDirtyBit(-1)) { + Object value = getFromField(-1); + + info.setDirty(); + setToInfo(value, -1); + setOrig(value, -1); + } + } + } + + /** + * Check if the item is dirty, and clear the dirty bit if set. + * + * @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 TRUE if it was dirty, FALSE if not + */ + private boolean getDirtyBit(int item) { + if (dirtyBits != null) { + return dirtyBits.remove((Integer) item); + } + + Object value = null; + if (getField(item) != null) { + value = getFromField(item); + } + + return hasValueChanged(value, item); + } + + /** + * Create a new field for the given item. + * + * @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 newly created field + */ + public T createField(final int item) { + T field = createEmptyField(item); + setField(item, field); + reload(item); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + reload(); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + save(); + } + }); + + return field; + } +}