Commit | Line | Data |
---|---|---|
9e834013 NR |
1 | package be.nikiroo.utils.resources; |
2 | ||
3 | import java.util.ArrayList; | |
0877d6f5 | 4 | import java.util.Iterator; |
9e834013 NR |
5 | import java.util.List; |
6 | ||
7 | import be.nikiroo.utils.resources.Meta.Format; | |
8 | ||
9 | /** | |
10 | * A graphical item that reflect a configuration option from the given | |
11 | * {@link Bundle}. | |
12 | * | |
13 | * @author niki | |
14 | * | |
15 | * @param <E> | |
16 | * the type of {@link Bundle} to edit | |
17 | */ | |
0877d6f5 | 18 | public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> { |
9e834013 NR |
19 | private final Bundle<E> bundle; |
20 | private final E id; | |
21 | ||
22 | private Meta meta; | |
0877d6f5 | 23 | private List<MetaInfo<E>> children = new ArrayList<MetaInfo<E>>(); |
9e834013 NR |
24 | |
25 | private String value; | |
8517b60c NR |
26 | private List<Runnable> reloadedListeners = new ArrayList<Runnable>(); |
27 | private List<Runnable> saveListeners = new ArrayList<Runnable>(); | |
9e834013 NR |
28 | |
29 | private String name; | |
ee020e75 | 30 | private boolean hidden; |
9e834013 NR |
31 | private String description; |
32 | ||
d5026c09 NR |
33 | private boolean dirty; |
34 | ||
76b51de9 NR |
35 | /** |
36 | * Create a new {@link MetaInfo} from a value (without children). | |
37 | * <p> | |
38 | * For instance, you can call | |
39 | * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>. | |
40 | * | |
41 | * @param type | |
42 | * the type of enum the value is | |
43 | * @param bundle | |
44 | * the bundle this value belongs to | |
45 | * @param id | |
46 | * the value itself | |
47 | */ | |
9e834013 NR |
48 | public MetaInfo(Class<E> type, Bundle<E> bundle, E id) { |
49 | this.bundle = bundle; | |
50 | this.id = id; | |
51 | ||
52 | try { | |
53 | this.meta = type.getDeclaredField(id.name()).getAnnotation( | |
54 | Meta.class); | |
55 | } catch (NoSuchFieldException e) { | |
56 | } catch (SecurityException e) { | |
57 | } | |
58 | ||
59 | // We consider that if a description bundle is used, everything is in it | |
60 | ||
61 | String description = null; | |
62 | if (bundle.getDescriptionBundle() != null) { | |
63 | description = bundle.getDescriptionBundle().getString(id); | |
64 | if (description != null && description.trim().isEmpty()) { | |
65 | description = null; | |
66 | } | |
67 | } | |
9e834013 NR |
68 | if (description == null) { |
69 | description = meta.description(); | |
0877d6f5 NR |
70 | if (description == null) { |
71 | description = ""; | |
72 | } | |
d18e136e NR |
73 | } |
74 | ||
75 | String name = idToName(id, null); | |
76 | ||
77 | // Special rules for groups: | |
78 | if (meta.group()) { | |
79 | String groupName = description.split("\n")[0]; | |
80 | description = description.substring(groupName.length()).trim(); | |
81 | if (!groupName.isEmpty()) { | |
82 | name = groupName; | |
9e834013 NR |
83 | } |
84 | } | |
85 | ||
d18e136e NR |
86 | if (meta.def() != null && !meta.def().isEmpty()) { |
87 | if (!description.isEmpty()) { | |
88 | description += "\n\n"; | |
89 | } | |
90 | description += "(Default value: " + meta.def() + ")"; | |
9e834013 NR |
91 | } |
92 | ||
93 | this.name = name; | |
ee020e75 | 94 | this.hidden = meta.hidden(); |
9e834013 NR |
95 | this.description = description; |
96 | ||
97 | reload(); | |
98 | } | |
99 | ||
100 | /** | |
d18e136e NR |
101 | * For normal items, this is the name of this item, deduced from its ID (or |
102 | * in other words, it is the ID but presented in a displayable form). | |
103 | * <p> | |
104 | * For group items, this is the first line of the description if it is not | |
105 | * empty (else, it is the ID in the same way as normal items). | |
9e834013 | 106 | * <p> |
d18e136e | 107 | * Never NULL. |
9e834013 | 108 | * |
d18e136e NR |
109 | * |
110 | * @return the name, never NULL | |
9e834013 NR |
111 | */ |
112 | public String getName() { | |
113 | return name; | |
114 | } | |
ee020e75 NR |
115 | |
116 | /** | |
117 | * This item should be hidden from the user (she will still be able to | |
118 | * modify it if she opens the file manually). | |
119 | * | |
120 | * @return TRUE if it should stay hidden | |
121 | */ | |
122 | public boolean isHidden() { | |
123 | return hidden; | |
124 | } | |
9e834013 NR |
125 | |
126 | /** | |
d18e136e NR |
127 | * A description for this item: what it is or does, how to explain that item |
128 | * to the user including what can be used here (i.e., %s = file name, %d = | |
129 | * file size...). | |
130 | * <p> | |
131 | * For group, the first line ('\\n'-separated) will be used as a title while | |
132 | * the rest will be the description. | |
133 | * <p> | |
134 | * If a default value is known, it will be specified here, too. | |
135 | * <p> | |
136 | * Never NULL. | |
9e834013 | 137 | * |
d18e136e | 138 | * @return the description, not NULL |
9e834013 NR |
139 | */ |
140 | public String getDescription() { | |
141 | return description; | |
142 | } | |
143 | ||
76b51de9 NR |
144 | /** |
145 | * The format this item is supposed to follow | |
146 | * | |
147 | * @return the format | |
148 | */ | |
9e834013 NR |
149 | public Format getFormat() { |
150 | return meta.format(); | |
151 | } | |
152 | ||
76b51de9 NR |
153 | /** |
154 | * The allowed list of values that a {@link Format#FIXED_LIST} item is | |
155 | * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} | |
0f7de31e | 156 | * items. Also works for {@link Format#LOCALE}. |
d18e136e NR |
157 | * <p> |
158 | * Will always allow an empty string in addition to the rest. | |
76b51de9 NR |
159 | * |
160 | * @return the list of values | |
161 | */ | |
0877d6f5 | 162 | public String[] getAllowedValues() { |
d18e136e NR |
163 | String[] list = meta.list(); |
164 | ||
165 | String[] withEmpty = new String[list.length + 1]; | |
166 | withEmpty[0] = ""; | |
167 | for (int i = 0; i < list.length; i++) { | |
168 | withEmpty[i + 1] = list[i]; | |
169 | } | |
170 | ||
171 | return withEmpty; | |
0877d6f5 NR |
172 | } |
173 | ||
0f7de31e NR |
174 | /** |
175 | * Return all the languages known by the program for this bundle. | |
176 | * <p> | |
177 | * This only works for {@link TransBundle}, and will return an empty list if | |
178 | * this is not a {@link TransBundle}. | |
179 | * | |
180 | * @return the known language codes | |
181 | */ | |
182 | public List<String> getKnownLanguages() { | |
183 | if (bundle instanceof TransBundle) { | |
184 | return ((TransBundle<E>) bundle).getKnownLanguages(); | |
185 | } | |
186 | ||
187 | return new ArrayList<String>(); | |
188 | } | |
189 | ||
76b51de9 NR |
190 | /** |
191 | * This item is a comma-separated list of values instead of a single value. | |
192 | * <p> | |
193 | * The list items are separated by a comma, each surrounded by | |
194 | * double-quotes, with backslashes and double-quotes escaped by a backslash. | |
195 | * <p> | |
196 | * Example: <tt>"un", "deux"</tt> | |
197 | * | |
198 | * @return TRUE if it is | |
199 | */ | |
0877d6f5 NR |
200 | public boolean isArray() { |
201 | return meta.array(); | |
202 | } | |
203 | ||
d5026c09 | 204 | /** |
74d2a495 NR |
205 | * A manual flag to specify if the data has been changed or not, which can |
206 | * be used by {@link MetaInfo#save(boolean)}. | |
d5026c09 NR |
207 | * |
208 | * @return TRUE if it is dirty (if it has changed) | |
209 | */ | |
210 | public boolean isDirty() { | |
211 | return dirty; | |
212 | } | |
213 | ||
214 | /** | |
74d2a495 NR |
215 | * A manual flag to specify that the data has been changed, which can be |
216 | * used by {@link MetaInfo#save(boolean)}. | |
d5026c09 NR |
217 | */ |
218 | public void setDirty() { | |
219 | this.dirty = true; | |
220 | } | |
221 | ||
222 | /** | |
223 | * The number of items in this item if it {@link MetaInfo#isArray()}, or -1 | |
224 | * if not. | |
225 | * | |
226 | * @param useDefaultIfEmpty | |
227 | * check the size of the default list instead if the list is | |
228 | * empty | |
229 | * | |
230 | * @return -1 or the number of items | |
231 | */ | |
232 | public int getListSize(boolean useDefaultIfEmpty) { | |
233 | if (!isArray()) { | |
234 | return -1; | |
235 | } | |
236 | ||
237 | return BundleHelper.getListSize(getString(-1, useDefaultIfEmpty)); | |
238 | } | |
239 | ||
76b51de9 NR |
240 | /** |
241 | * This item is only used as a group, not as an option. | |
242 | * <p> | |
243 | * For instance, you could have LANGUAGE_CODE as a group for which you won't | |
244 | * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN | |
245 | * inside for which the value must be set. | |
246 | * | |
247 | * @return TRUE if it is a group | |
248 | */ | |
249 | public boolean isGroup() { | |
250 | return meta.group(); | |
251 | } | |
252 | ||
9e834013 NR |
253 | /** |
254 | * The value stored by this item, as a {@link String}. | |
255 | * | |
d5026c09 NR |
256 | * @param item |
257 | * the item number to get for an array of values, or -1 to get | |
258 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
259 | * is FALSE) | |
d18e136e NR |
260 | * @param useDefaultIfEmpty |
261 | * use the default value instead of NULL if the setting is not | |
262 | * set | |
263 | * | |
9e834013 NR |
264 | * @return the value |
265 | */ | |
d5026c09 NR |
266 | public String getString(int item, boolean useDefaultIfEmpty) { |
267 | if (isArray() && item >= 0) { | |
268 | List<String> values = BundleHelper.parseList(value, -1); | |
269 | if (values != null && item < values.size()) { | |
270 | return values.get(item); | |
271 | } | |
272 | ||
273 | if (useDefaultIfEmpty) { | |
274 | return getDefaultString(item); | |
275 | } | |
276 | ||
277 | return null; | |
278 | } | |
279 | ||
d18e136e | 280 | if (value == null && useDefaultIfEmpty) { |
d5026c09 | 281 | return getDefaultString(item); |
d18e136e NR |
282 | } |
283 | ||
9e834013 NR |
284 | return value; |
285 | } | |
286 | ||
76b51de9 NR |
287 | /** |
288 | * The default value of this item, as a {@link String}. | |
289 | * | |
d5026c09 NR |
290 | * @param item |
291 | * the item number to get for an array of values, or -1 to get | |
292 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
293 | * is FALSE) | |
294 | * | |
76b51de9 NR |
295 | * @return the default value |
296 | */ | |
d5026c09 NR |
297 | public String getDefaultString(int item) { |
298 | if (isArray() && item >= 0) { | |
299 | List<String> values = BundleHelper.parseList(meta.def(), item); | |
300 | if (values != null && item < values.size()) { | |
301 | return values.get(item); | |
302 | } | |
303 | ||
304 | return null; | |
305 | } | |
306 | ||
9e834013 NR |
307 | return meta.def(); |
308 | } | |
309 | ||
76b51de9 NR |
310 | /** |
311 | * The value stored by this item, as a {@link Boolean}. | |
312 | * | |
d5026c09 NR |
313 | * @param item |
314 | * the item number to get for an array of values, or -1 to get | |
315 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
316 | * is FALSE) | |
d18e136e NR |
317 | * @param useDefaultIfEmpty |
318 | * use the default value instead of NULL if the setting is not | |
319 | * set | |
320 | * | |
76b51de9 NR |
321 | * @return the value |
322 | */ | |
d5026c09 NR |
323 | public Boolean getBoolean(int item, boolean useDefaultIfEmpty) { |
324 | return BundleHelper | |
325 | .parseBoolean(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
326 | } |
327 | ||
76b51de9 NR |
328 | /** |
329 | * The default value of this item, as a {@link Boolean}. | |
330 | * | |
d5026c09 NR |
331 | * @param item |
332 | * the item number to get for an array of values, or -1 to get | |
333 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
334 | * is FALSE) | |
335 | * | |
76b51de9 NR |
336 | * @return the default value |
337 | */ | |
d5026c09 NR |
338 | public Boolean getDefaultBoolean(int item) { |
339 | return BundleHelper.parseBoolean(getDefaultString(item), -1); | |
9e834013 NR |
340 | } |
341 | ||
76b51de9 NR |
342 | /** |
343 | * The value stored by this item, as a {@link Character}. | |
344 | * | |
d5026c09 NR |
345 | * @param item |
346 | * the item number to get for an array of values, or -1 to get | |
347 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
348 | * is FALSE) | |
d18e136e NR |
349 | * @param useDefaultIfEmpty |
350 | * use the default value instead of NULL if the setting is not | |
351 | * set | |
352 | * | |
76b51de9 NR |
353 | * @return the value |
354 | */ | |
d5026c09 NR |
355 | public Character getCharacter(int item, boolean useDefaultIfEmpty) { |
356 | return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty), | |
357 | -1); | |
9e834013 NR |
358 | } |
359 | ||
76b51de9 NR |
360 | /** |
361 | * The default value of this item, as a {@link Character}. | |
362 | * | |
d5026c09 NR |
363 | * @param item |
364 | * the item number to get for an array of values, or -1 to get | |
365 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
366 | * is FALSE) | |
367 | * | |
76b51de9 NR |
368 | * @return the default value |
369 | */ | |
d5026c09 NR |
370 | public Character getDefaultCharacter(int item) { |
371 | return BundleHelper.parseCharacter(getDefaultString(item), -1); | |
9e834013 NR |
372 | } |
373 | ||
76b51de9 NR |
374 | /** |
375 | * The value stored by this item, as an {@link Integer}. | |
376 | * | |
d5026c09 NR |
377 | * @param item |
378 | * the item number to get for an array of values, or -1 to get | |
379 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
380 | * is FALSE) | |
d18e136e NR |
381 | * @param useDefaultIfEmpty |
382 | * use the default value instead of NULL if the setting is not | |
383 | * set | |
384 | * | |
76b51de9 NR |
385 | * @return the value |
386 | */ | |
d5026c09 NR |
387 | public Integer getInteger(int item, boolean useDefaultIfEmpty) { |
388 | return BundleHelper | |
389 | .parseInteger(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
390 | } |
391 | ||
76b51de9 NR |
392 | /** |
393 | * The default value of this item, as an {@link Integer}. | |
394 | * | |
d5026c09 NR |
395 | * @param item |
396 | * the item number to get for an array of values, or -1 to get | |
397 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
398 | * is FALSE) | |
399 | * | |
76b51de9 NR |
400 | * @return the default value |
401 | */ | |
d5026c09 NR |
402 | public Integer getDefaultInteger(int item) { |
403 | return BundleHelper.parseInteger(getDefaultString(item), -1); | |
9e834013 NR |
404 | } |
405 | ||
76b51de9 NR |
406 | /** |
407 | * The value stored by this item, as a colour (represented here as an | |
408 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
409 | * <p> | |
410 | * The returned colour value is an ARGB value. | |
411 | * | |
d5026c09 NR |
412 | * @param item |
413 | * the item number to get for an array of values, or -1 to get | |
414 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
415 | * is FALSE) | |
d18e136e NR |
416 | * @param useDefaultIfEmpty |
417 | * use the default value instead of NULL if the setting is not | |
418 | * set | |
419 | * | |
76b51de9 NR |
420 | * @return the value |
421 | */ | |
d5026c09 NR |
422 | public Integer getColor(int item, boolean useDefaultIfEmpty) { |
423 | return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
424 | } |
425 | ||
76b51de9 NR |
426 | /** |
427 | * The default value stored by this item, as a colour (represented here as | |
428 | * an {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
429 | * <p> | |
430 | * The returned colour value is an ARGB value. | |
431 | * | |
d5026c09 NR |
432 | * @param item |
433 | * the item number to get for an array of values, or -1 to get | |
434 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
435 | * is FALSE) | |
436 | * | |
76b51de9 NR |
437 | * @return the value |
438 | */ | |
d5026c09 NR |
439 | public Integer getDefaultColor(int item) { |
440 | return BundleHelper.parseColor(getDefaultString(item), -1); | |
9e834013 NR |
441 | } |
442 | ||
76b51de9 NR |
443 | /** |
444 | * A {@link String} representation of the list of values. | |
445 | * <p> | |
446 | * The list of values is comma-separated and each value is surrounded by | |
447 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
448 | * | |
d5026c09 NR |
449 | * @param item |
450 | * the item number to get for an array of values, or -1 to get | |
451 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
452 | * is FALSE) | |
d18e136e NR |
453 | * @param useDefaultIfEmpty |
454 | * use the default value instead of NULL if the setting is not | |
455 | * set | |
456 | * | |
76b51de9 NR |
457 | * @return the value |
458 | */ | |
d5026c09 NR |
459 | public List<String> getList(int item, boolean useDefaultIfEmpty) { |
460 | return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1); | |
9e834013 NR |
461 | } |
462 | ||
76b51de9 NR |
463 | /** |
464 | * A {@link String} representation of the default list of values. | |
465 | * <p> | |
466 | * The list of values is comma-separated and each value is surrounded by | |
467 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
468 | * | |
d5026c09 NR |
469 | * @param item |
470 | * the item number to get for an array of values, or -1 to get | |
471 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
472 | * is FALSE) | |
473 | * | |
76b51de9 NR |
474 | * @return the value |
475 | */ | |
d5026c09 NR |
476 | public List<String> getDefaultList(int item) { |
477 | return BundleHelper.parseList(getDefaultString(item), -1); | |
9e834013 NR |
478 | } |
479 | ||
480 | /** | |
481 | * The value stored by this item, as a {@link String}. | |
482 | * | |
483 | * @param value | |
484 | * the new value | |
d5026c09 NR |
485 | * @param item |
486 | * the item number to set for an array of values, or -1 to set | |
487 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
488 | * is FALSE) | |
9e834013 | 489 | */ |
d5026c09 NR |
490 | public void setString(String value, int item) { |
491 | if (isArray() && item >= 0) { | |
856f5898 | 492 | this.value = BundleHelper.fromList(this.value, value, item); |
d5026c09 NR |
493 | } else { |
494 | this.value = value; | |
495 | } | |
9e834013 NR |
496 | } |
497 | ||
76b51de9 NR |
498 | /** |
499 | * The value stored by this item, as a {@link Boolean}. | |
500 | * | |
501 | * @param value | |
502 | * the new value | |
d5026c09 NR |
503 | * @param item |
504 | * the item number to set for an array of values, or -1 to set | |
505 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
506 | * is FALSE) | |
76b51de9 | 507 | */ |
d5026c09 NR |
508 | public void setBoolean(boolean value, int item) { |
509 | setString(BundleHelper.fromBoolean(value), item); | |
9e834013 NR |
510 | } |
511 | ||
76b51de9 NR |
512 | /** |
513 | * The value stored by this item, as a {@link Character}. | |
514 | * | |
515 | * @param value | |
516 | * the new value | |
d5026c09 NR |
517 | * @param item |
518 | * the item number to set for an array of values, or -1 to set | |
519 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
520 | * is FALSE) | |
76b51de9 | 521 | */ |
d5026c09 NR |
522 | public void setCharacter(char value, int item) { |
523 | setString(BundleHelper.fromCharacter(value), item); | |
9e834013 NR |
524 | } |
525 | ||
76b51de9 NR |
526 | /** |
527 | * The value stored by this item, as an {@link Integer}. | |
528 | * | |
529 | * @param value | |
530 | * the new value | |
d5026c09 NR |
531 | * @param item |
532 | * the item number to set for an array of values, or -1 to set | |
533 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
534 | * is FALSE) | |
76b51de9 | 535 | */ |
d5026c09 NR |
536 | public void setInteger(int value, int item) { |
537 | setString(BundleHelper.fromInteger(value), item); | |
9e834013 NR |
538 | } |
539 | ||
76b51de9 NR |
540 | /** |
541 | * The value stored by this item, as a colour (represented here as an | |
542 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
543 | * <p> | |
74d2a495 | 544 | * The colour value is an ARGB value. |
76b51de9 NR |
545 | * |
546 | * @param value | |
547 | * the value | |
d5026c09 NR |
548 | * @param item |
549 | * the item number to set for an array of values, or -1 to set | |
550 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
551 | * is FALSE) | |
76b51de9 | 552 | */ |
d5026c09 NR |
553 | public void setColor(int value, int item) { |
554 | setString(BundleHelper.fromColor(value), item); | |
9e834013 NR |
555 | } |
556 | ||
76b51de9 NR |
557 | /** |
558 | * A {@link String} representation of the default list of values. | |
559 | * <p> | |
560 | * The list of values is comma-separated and each value is surrounded by | |
561 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
562 | * | |
563 | * @param value | |
564 | * the {@link String} representation | |
d5026c09 NR |
565 | * @param item |
566 | * the item number to set for an array of values, or -1 to set | |
567 | * the whole value (has no effect if {@link MetaInfo#isArray()} | |
568 | * is FALSE) | |
76b51de9 | 569 | */ |
d5026c09 NR |
570 | public void setList(List<String> value, int item) { |
571 | setString(BundleHelper.fromList(value), item); | |
9e834013 NR |
572 | } |
573 | ||
574 | /** | |
76b51de9 NR |
575 | * Reload the value from the {@link Bundle}, so the last value that was |
576 | * saved will be used. | |
9e834013 NR |
577 | */ |
578 | public void reload() { | |
fde375c1 NR |
579 | if (bundle.isSet(id, false)) { |
580 | value = bundle.getString(id); | |
581 | } else { | |
582 | value = null; | |
583 | } | |
584 | ||
856f5898 NR |
585 | // Copy the list so we can create new listener in a listener |
586 | for (Runnable listener : new ArrayList<Runnable>(reloadedListeners)) { | |
9e834013 NR |
587 | try { |
588 | listener.run(); | |
589 | } catch (Exception e) { | |
9e834013 NR |
590 | e.printStackTrace(); |
591 | } | |
592 | } | |
593 | } | |
594 | ||
76b51de9 NR |
595 | /** |
596 | * Add a listener that will be called <b>after</b> a reload operation. | |
597 | * <p> | |
598 | * You could use it to refresh the UI for instance. | |
599 | * | |
600 | * @param listener | |
601 | * the listener | |
602 | */ | |
8517b60c NR |
603 | public void addReloadedListener(Runnable listener) { |
604 | reloadedListeners.add(listener); | |
9e834013 NR |
605 | } |
606 | ||
607 | /** | |
608 | * Save the current value to the {@link Bundle}. | |
d5026c09 NR |
609 | * <p> |
610 | * Note that listeners will be called <b>before</b> the dirty check and | |
611 | * <b>before</b> saving the value. | |
612 | * | |
613 | * @param onlyIfDirty | |
614 | * only save the data if the dirty flag is set (will reset the | |
615 | * dirty flag) | |
9e834013 | 616 | */ |
d5026c09 | 617 | public void save(boolean onlyIfDirty) { |
856f5898 NR |
618 | // Copy the list so we can create new listener in a listener |
619 | for (Runnable listener : new ArrayList<Runnable>(saveListeners)) { | |
8517b60c NR |
620 | try { |
621 | listener.run(); | |
622 | } catch (Exception e) { | |
8517b60c NR |
623 | e.printStackTrace(); |
624 | } | |
625 | } | |
d5026c09 NR |
626 | |
627 | if (!onlyIfDirty || isDirty()) { | |
628 | bundle.setString(id, value); | |
629 | } | |
9e834013 NR |
630 | } |
631 | ||
76b51de9 NR |
632 | /** |
633 | * Add a listener that will be called <b>before</b> a save operation. | |
634 | * <p> | |
635 | * You could use it to make some modification to the stored value before it | |
636 | * is saved. | |
637 | * | |
638 | * @param listener | |
639 | * the listener | |
640 | */ | |
8517b60c NR |
641 | public void addSaveListener(Runnable listener) { |
642 | saveListeners.add(listener); | |
643 | } | |
644 | ||
76b51de9 NR |
645 | /** |
646 | * The sub-items if any (if no sub-items, will return an empty list). | |
647 | * <p> | |
648 | * Sub-items are declared when a {@link Meta} has an ID that starts with the | |
649 | * ID of a {@link Meta#group()} {@link MetaInfo}. | |
650 | * <p> | |
651 | * For instance: | |
652 | * <ul> | |
653 | * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li> | |
654 | * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li> | |
655 | * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li> | |
656 | * </ul> | |
657 | * | |
658 | * @return the sub-items if any | |
659 | */ | |
660 | public List<MetaInfo<E>> getChildren() { | |
661 | return children; | |
662 | } | |
663 | ||
06359ad5 NR |
664 | /** |
665 | * The number of sub-items, if any. | |
666 | * | |
667 | * @return the number or 0 | |
668 | */ | |
669 | public int size() { | |
670 | return children.size(); | |
671 | } | |
672 | ||
0877d6f5 NR |
673 | @Override |
674 | public Iterator<MetaInfo<E>> iterator() { | |
675 | return children.iterator(); | |
676 | } | |
677 | ||
9e834013 NR |
678 | /** |
679 | * Create a list of {@link MetaInfo}, one for each of the item in the given | |
680 | * {@link Bundle}. | |
681 | * | |
682 | * @param <E> | |
683 | * the type of {@link Bundle} to edit | |
684 | * @param type | |
685 | * a class instance of the item type to work on | |
686 | * @param bundle | |
687 | * the {@link Bundle} to sort through | |
688 | * | |
689 | * @return the list | |
690 | */ | |
691 | static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type, | |
692 | Bundle<E> bundle) { | |
693 | List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>(); | |
76b51de9 | 694 | List<MetaInfo<E>> shadow = new ArrayList<MetaInfo<E>>(); |
9e834013 | 695 | for (E id : type.getEnumConstants()) { |
76b51de9 | 696 | MetaInfo<E> info = new MetaInfo<E>(type, bundle, id); |
ee020e75 NR |
697 | if (!info.hidden) { |
698 | list.add(info); | |
699 | shadow.add(info); | |
700 | } | |
9e834013 NR |
701 | } |
702 | ||
76b51de9 NR |
703 | for (int i = 0; i < list.size(); i++) { |
704 | MetaInfo<E> info = list.get(i); | |
8517b60c | 705 | |
76b51de9 NR |
706 | MetaInfo<E> parent = findParent(info, shadow); |
707 | if (parent != null) { | |
708 | list.remove(i--); | |
709 | parent.children.add(info); | |
d18e136e | 710 | info.name = idToName(info.id, parent.id); |
8517b60c NR |
711 | } |
712 | } | |
713 | ||
76b51de9 | 714 | return list; |
8517b60c NR |
715 | } |
716 | ||
76b51de9 NR |
717 | /** |
718 | * Find the longest parent of the given {@link MetaInfo}, which means: | |
719 | * <ul> | |
720 | * <li>the parent is a {@link Meta#group()}</li> | |
721 | * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li> | |
722 | * <li>there is no other parent sharing a substring for this | |
723 | * {@link MetaInfo} with a longer Id</li> | |
724 | * </ul> | |
725 | * | |
726 | * @param <E> | |
727 | * the kind of enum | |
728 | * @param info | |
729 | * the info to look for a parent for | |
730 | * @param candidates | |
731 | * the list of potential parents | |
732 | * | |
733 | * @return the longest parent or NULL if no parent is found | |
734 | */ | |
8517b60c | 735 | static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info, |
76b51de9 NR |
736 | List<MetaInfo<E>> candidates) { |
737 | String id = info.id.toString(); | |
8517b60c NR |
738 | MetaInfo<E> group = null; |
739 | for (MetaInfo<E> pcandidate : candidates) { | |
76b51de9 NR |
740 | if (pcandidate.isGroup()) { |
741 | String candidateId = pcandidate.id.toString(); | |
742 | if (!id.equals(candidateId) && id.startsWith(candidateId)) { | |
743 | if (group == null | |
744 | || group.id.toString().length() < candidateId | |
745 | .length()) { | |
746 | group = pcandidate; | |
747 | } | |
8517b60c NR |
748 | } |
749 | } | |
750 | } | |
751 | ||
752 | return group; | |
753 | } | |
d18e136e NR |
754 | |
755 | static private <E extends Enum<E>> String idToName(E id, E prefix) { | |
756 | String name = id.toString(); | |
757 | if (prefix != null && name.startsWith(prefix.toString())) { | |
758 | name = name.substring(prefix.toString().length()); | |
759 | } | |
760 | ||
761 | if (name.length() > 0) { | |
762 | name = name.substring(0, 1).toUpperCase() | |
763 | + name.substring(1).toLowerCase(); | |
764 | } | |
765 | ||
766 | name = name.replace("_", " "); | |
767 | ||
768 | return name.trim(); | |
769 | } | |
9e834013 | 770 | } |