Bundle: fix memory leak at init/reset
[fanfix.git] / ui / ConfigItemBase.java
CommitLineData
aa9c3626
NR
1package be.nikiroo.utils.ui;
2
3import java.util.ArrayList;
4import java.util.HashMap;
5import java.util.List;
6import java.util.Map;
7
8import be.nikiroo.utils.resources.Bundle;
9import be.nikiroo.utils.resources.MetaInfo;
10
11/**
12 * A graphical item that reflect a configuration option from the given
13 * {@link Bundle}.
14 * <p>
15 * This graphical item can be edited, and the result will be saved back into the
16 * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should
17 * you wish to, of course.
18 *
19 * @author niki
20 *
21 * @param <T>
22 * the graphical base type to use (i.e., T or TWidget)
23 * @param <E>
24 * the type of {@link Bundle} to edit
25 */
26public abstract class ConfigItemBase<T, E extends Enum<E>> {
27 /** The original value before current changes. */
28 private Object orig;
29 private List<Object> origs = new ArrayList<Object>();
30 private List<Integer> dirtyBits;
31
32 /** The fields (one for non-array, a list for arrays). */
33 private T field;
34 private List<T> fields = new ArrayList<T>();
35
36 /** The fields to panel map to get the actual item added to 'main'. */
37 private Map<Integer, T> itemFields = new HashMap<Integer, T>();
38
39 /** The {@link MetaInfo} linked to the field. */
40 private MetaInfo<E> info;
41
42 /** The {@link MetaInfo} linked to the field. */
43 public MetaInfo<E> getInfo() {
44 return info;
45 }
46
47 /**
48 * The number of fields, for arrays.
49 *
50 * @return
51 */
52 public int getFieldsSize() {
53 return fields.size();
54 }
55
56 /**
57 * The number of fields to panel map to get the actual item added to 'main'.
58 */
59 public int getItemFieldsSize() {
60 return itemFields.size();
61 }
62
63 /**
64 * Add a new item in an array-value {@link MetaInfo}.
65 *
66 * @param item
67 * the index of the new item
68 * @param panel
69 * a linked T, if we want to link it into the itemFields (can be
70 * NULL) -- that way, we can get it back later on
71 * {@link ConfigItemBase#removeItem(int)}
72 *
73 * @return the newly created graphical field
74 */
75 public T addItem(final int item, T panel) {
76 if (panel != null) {
77 itemFields.put(item, panel);
78 }
79 return createField(item);
80 }
81
82 /**
83 * The counter-part to {@link ConfigItemBase#addItem(int, Object)}, to
84 * remove a specific item of an array-values {@link MetaInfo}; all the
85 * remaining items will be shifted as required (so, always the last
86 * graphical object will be removed).
87 *
88 * @param item
89 * the index of the item to remove
90 *
91 * @return the linked graphical T to remove if any (always the latest
92 * graphical object if any)
93 */
94 protected T removeItem(int item) {
95 int last = itemFields.size() - 1;
96
97 for (int i = item; i <= last; i++) {
98 Object value = null;
99 if (i < last) {
100 value = getFromField(i + 1);
101 }
102 setToField(value, i);
103 setToInfo(value, i);
104 setDirtyItem(i);
105 }
106
107 return itemFields.remove(last);
108 }
109
110 /**
111 * Prepare a new {@link ConfigItemBase} instance, linked to the given
112 * {@link MetaInfo}.
113 *
114 * @param info
115 * the info
116 * @param autoDirtyHandling
117 * TRUE to automatically manage the setDirty/Save operations,
118 * FALSE if you want to do it yourself via
119 * {@link ConfigItemBase#setDirtyItem(int)}
120 */
121 protected ConfigItemBase(MetaInfo<E> info, boolean autoDirtyHandling) {
122 this.info = info;
123 if (!autoDirtyHandling) {
124 dirtyBits = new ArrayList<Integer>();
125 }
126 }
127
128 /**
129 * Create an empty graphical component to be used later by
130 * {@link ConfigItemBase#createField(int)}.
131 * <p>
132 * Note that {@link ConfigItemBase#reload(int)} will be called after it was
133 * created by {@link ConfigItemBase#createField(int)}.
134 *
135 * @param item
136 * the item number to get for an array of values, or -1 to get
137 * the whole value (has no effect if {@link MetaInfo#isArray()}
138 * is FALSE)
139 *
140 * @return the graphical component
141 */
142 abstract protected T createEmptyField(int item);
143
144 /**
145 * Get the information from the {@link MetaInfo} in the subclass preferred
146 * format.
147 *
148 * @param item
149 * the item number to get for an array of values, or -1 to get
150 * the whole value (has no effect if {@link MetaInfo#isArray()}
151 * is FALSE)
152 *
153 * @return the information in the subclass preferred format
154 */
155 abstract protected Object getFromInfo(int item);
156
157 /**
158 * Set the value to the {@link MetaInfo}.
159 *
160 * @param value
161 * the value in the subclass preferred format
162 * @param item
163 * the item number to get for an array of values, or -1 to get
164 * the whole value (has no effect if {@link MetaInfo#isArray()}
165 * is FALSE)
166 */
167 abstract protected void setToInfo(Object value, int item);
168
169 /**
170 * The value present in the given item's related field in the subclass
171 * preferred format.
172 *
173 * @param item
174 * the item number to get for an array of values, or -1 to get
175 * the whole value (has no effect if {@link MetaInfo#isArray()}
176 * is FALSE)
177 *
178 * @return the value present in the given item's related field in the
179 * subclass preferred format
180 */
181 abstract protected Object getFromField(int item);
182
183 /**
184 * Set the value (in the subclass preferred format) into the field.
185 *
186 * @param value
187 * the value in the subclass preferred format
188 * @param item
189 * the item number to get for an array of values, or -1 to get
190 * the whole value (has no effect if {@link MetaInfo#isArray()}
191 * is FALSE)
192 */
193 abstract protected void setToField(Object value, int item);
194
195 /**
196 * Create a new field for the given graphical component at the given index
197 * (note that the component is usually created by
198 * {@link ConfigItemBase#createEmptyField(int)}).
199 *
200 * @param item
201 * the item number to get for an array of values, or -1 to get
202 * the whole value (has no effect if {@link MetaInfo#isArray()}
203 * is FALSE)
204 * @param field
205 * the graphical component
206 */
207 private void setField(int item, T field) {
208 if (item < 0) {
209 this.field = field;
210 return;
211 }
212
213 for (int i = fields.size(); i <= item; i++) {
214 fields.add(null);
215 }
216
217 fields.set(item, field);
218 }
219
220 /**
221 * Retrieve the associated graphical component that was created with
222 * {@link ConfigItemBase#createEmptyField(int)}.
223 *
224 * @param item
225 * the item number to get for an array of values, or -1 to get
226 * the whole value (has no effect if {@link MetaInfo#isArray()}
227 * is FALSE)
228 *
229 * @return the graphical component
230 */
231 public T getField(int item) {
232 if (item < 0) {
233 return field;
234 }
235
236 if (item < fields.size()) {
237 return fields.get(item);
238 }
239
240 return null;
241 }
242
243 /**
244 * The original value (before any changes to the {@link MetaInfo}) for this
245 * item.
246 *
247 * @param item
248 * the item number to get for an array of values, or -1 to get
249 * the whole value (has no effect if {@link MetaInfo#isArray()}
250 * is FALSE)
251 *
252 * @return the original value
253 */
254 private Object getOrig(int item) {
255 if (item < 0) {
256 return orig;
257 }
258
259 if (item < origs.size()) {
260 return origs.get(item);
261 }
262
263 return null;
264 }
265
266 /**
267 * The original value (before any changes to the {@link MetaInfo}) for this
268 * item.
269 *
270 * @param item
271 * the item number to get for an array of values, or -1 to get
272 * the whole value (has no effect if {@link MetaInfo#isArray()}
273 * is FALSE)
274 * @param value
275 * the new original value
276 */
277 private void setOrig(Object value, int item) {
278 if (item < 0) {
279 orig = value;
280 } else {
281 while (item >= origs.size()) {
282 origs.add(null);
283 }
284
285 origs.set(item, value);
286 }
287 }
288
289 /**
290 * Manually specify that the given item is "dirty" and thus should be saved
291 * when asked.
292 * <p>
293 * Has no effect if the class is using automatic dirty handling (see
294 * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}).
295 *
296 * @param item
297 * the item number to get for an array of values, or -1 to get
298 * the whole value (has no effect if {@link MetaInfo#isArray()}
299 * is FALSE)
300 */
301 public void setDirtyItem(int item) {
302 if (dirtyBits != null) {
303 dirtyBits.add(item);
304 }
305 }
306
307 /**
308 * Check if the value changed since the last load/save into the linked
309 * {@link MetaInfo}.
310 * <p>
311 * Note that we consider NULL and an Empty {@link String} to be equals.
312 *
313 * @param value
314 * the value to test
315 * @param item
316 * the item number to get for an array of values, or -1 to get
317 * the whole value (has no effect if {@link MetaInfo#isArray()}
318 * is FALSE)
319 *
320 * @return TRUE if it has
321 */
322 public boolean hasValueChanged(Object value, int item) {
323 // We consider "" and NULL to be equals
324 Object orig = getOrig(item);
325 if (orig == null) {
326 orig = "";
327 }
328 return !orig.equals(value == null ? "" : value);
329 }
330
331 /**
332 * Reload the values to what they currently are in the {@link MetaInfo}.
333 *
334 * @return for arrays, the list of graphical T objects we don't need any
335 * more (never NULL, but can be empty)
336 */
337 public List<T> reload() {
338 List<T> removed = new ArrayList<T>();
339 if (info.isArray()) {
340 while (!itemFields.isEmpty()) {
341 removed.add(itemFields.remove(itemFields.size() - 1));
342 }
343 for (int item = 0; item < info.getListSize(false); item++) {
344 reload(item);
345 }
346 } else {
347 reload(-1);
348 }
349
350 return removed;
351 }
352
353 /**
354 * Reload the values to what they currently are in the {@link MetaInfo}.
355 *
356 * @param item
357 * the item number to get for an array of values, or -1 to get
358 * the whole value (has no effect if {@link MetaInfo#isArray()}
359 * is FALSE)
360 */
361 private void reload(int item) {
362 if (item >= 0 && !itemFields.containsKey(item)) {
363 addItem(item, null);
364 }
365
366 Object value = getFromInfo(item);
367 setToField(value, item);
368 setOrig(value == null ? "" : value, item);
369 }
370
371 /**
372 * If the item has been modified, set the {@link MetaInfo} to dirty then
373 * modify it to, reflect the changes so it can be saved later.
374 * <p>
375 * This method does <b>not</b> call {@link MetaInfo#save(boolean)}.
376 */
377 private void save() {
378 if (info.isArray()) {
379 boolean dirty = itemFields.size() != info.getListSize(false);
380 for (int item = 0; item < itemFields.size(); item++) {
381 if (getDirtyBit(item)) {
382 dirty = true;
383 }
384 }
385
386 if (dirty) {
387 info.setDirty();
388 info.setString(null, -1);
389
390 for (int item = 0; item < itemFields.size(); item++) {
391 Object value = null;
392 if (getField(item) != null) {
393 value = getFromField(item);
394 if ("".equals(value)) {
395 value = null;
396 }
397 }
398
399 setToInfo(value, item);
400 setOrig(value, item);
401 }
402 }
403 } else {
404 if (getDirtyBit(-1)) {
405 Object value = getFromField(-1);
406
407 info.setDirty();
408 setToInfo(value, -1);
409 setOrig(value, -1);
410 }
411 }
412 }
413
414 /**
415 * Check if the item is dirty, and clear the dirty bit if set.
416 *
417 * @param item
418 * the item number to get for an array of values, or -1 to get
419 * the whole value (has no effect if {@link MetaInfo#isArray()}
420 * is FALSE)
421 *
422 * @return TRUE if it was dirty, FALSE if not
423 */
424 private boolean getDirtyBit(int item) {
425 if (dirtyBits != null) {
426 return dirtyBits.remove((Integer) item);
427 }
428
429 Object value = null;
430 if (getField(item) != null) {
431 value = getFromField(item);
432 }
433
434 return hasValueChanged(value, item);
435 }
436
437 /**
438 * Create a new field for the given item.
439 *
440 * @param item
441 * the item number to get for an array of values, or -1 to get
442 * the whole value (has no effect if {@link MetaInfo#isArray()}
443 * is FALSE)
444 *
445 * @return the newly created field
446 */
447 public T createField(final int item) {
448 T field = createEmptyField(item);
449 setField(item, field);
450 reload(item);
451
452 info.addReloadedListener(new Runnable() {
453 @Override
454 public void run() {
455 reload();
456 }
457 });
458 info.addSaveListener(new Runnable() {
459 @Override
460 public void run() {
461 save();
462 }
463 });
464
465 return field;
466 }
467}