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; } }