forgotten file
authorNiki Roo <niki@nikiroo.be>
Mon, 6 Apr 2020 10:09:26 +0000 (12:09 +0200)
committerNiki Roo <niki@nikiroo.be>
Mon, 6 Apr 2020 10:09:26 +0000 (12:09 +0200)
src/be/nikiroo/utils/ui/ConfigItemBase.java [new file with mode: 0644]

diff --git a/src/be/nikiroo/utils/ui/ConfigItemBase.java b/src/be/nikiroo/utils/ui/ConfigItemBase.java
new file mode 100644 (file)
index 0000000..21b5755
--- /dev/null
@@ -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}.
+ * <p>
+ * 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 <T>
+ *            the graphical base type to use (i.e., T or TWidget)
+ * @param <E>
+ *            the type of {@link Bundle} to edit
+ */
+public abstract class ConfigItemBase<T, E extends Enum<E>> {
+       /** The original value before current changes. */
+       private Object orig;
+       private List<Object> origs = new ArrayList<Object>();
+       private List<Integer> dirtyBits;
+
+       /** The fields (one for non-array, a list for arrays). */
+       private T field;
+       private List<T> fields = new ArrayList<T>();
+
+       /** The fields to panel map to get the actual item added to 'main'. */
+       private Map<Integer, T> itemFields = new HashMap<Integer, T>();
+
+       /** The {@link MetaInfo} linked to the field. */
+       private MetaInfo<E> info;
+
+       /** The {@link MetaInfo} linked to the field. */
+       public MetaInfo<E> 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<E> info, boolean autoDirtyHandling) {
+               this.info = info;
+               if (!autoDirtyHandling) {
+                       dirtyBits = new ArrayList<Integer>();
+               }
+       }
+
+       /**
+        * Create an empty graphical component to be used later by
+        * {@link ConfigItemBase#createField(int)}.
+        * <p>
+        * 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.
+        * <p>
+        * 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}.
+        * <p>
+        * 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<T> reload() {
+               List<T> removed = new ArrayList<T>();
+               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.
+        * <p>
+        * This method does <b>not</b> 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;
+       }
+}