Commit | Line | Data |
---|---|---|
28a82bab NR |
1 | package be.nikiroo.utils.ui; |
2 | ||
3 | import java.util.ArrayList; | |
4 | import java.util.HashMap; | |
5 | import java.util.List; | |
6 | import java.util.Map; | |
7 | ||
8 | import be.nikiroo.utils.resources.Bundle; | |
9 | import 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 | */ | |
26 | public 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 | } |