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; | |
30 | private String description; | |
31 | ||
76b51de9 NR |
32 | /** |
33 | * Create a new {@link MetaInfo} from a value (without children). | |
34 | * <p> | |
35 | * For instance, you can call | |
36 | * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>. | |
37 | * | |
38 | * @param type | |
39 | * the type of enum the value is | |
40 | * @param bundle | |
41 | * the bundle this value belongs to | |
42 | * @param id | |
43 | * the value itself | |
44 | */ | |
9e834013 NR |
45 | public MetaInfo(Class<E> type, Bundle<E> bundle, E id) { |
46 | this.bundle = bundle; | |
47 | this.id = id; | |
48 | ||
49 | try { | |
50 | this.meta = type.getDeclaredField(id.name()).getAnnotation( | |
51 | Meta.class); | |
52 | } catch (NoSuchFieldException e) { | |
53 | } catch (SecurityException e) { | |
54 | } | |
55 | ||
56 | // We consider that if a description bundle is used, everything is in it | |
57 | ||
58 | String description = null; | |
59 | if (bundle.getDescriptionBundle() != null) { | |
60 | description = bundle.getDescriptionBundle().getString(id); | |
61 | if (description != null && description.trim().isEmpty()) { | |
62 | description = null; | |
63 | } | |
64 | } | |
65 | ||
66 | if (description == null) { | |
67 | description = meta.description(); | |
0877d6f5 NR |
68 | if (description == null) { |
69 | description = ""; | |
70 | } | |
9e834013 | 71 | if (meta.info() != null && !meta.info().isEmpty()) { |
0877d6f5 NR |
72 | if (!description.isEmpty()) { |
73 | description += "\n\n"; | |
74 | } | |
75 | description += meta.info(); | |
9e834013 NR |
76 | } |
77 | } | |
78 | ||
79 | String name = id.toString(); | |
80 | if (name.length() > 1) { | |
81 | name = name.substring(0, 1) + name.substring(1).toLowerCase(); | |
82 | name = name.replace("_", " "); | |
83 | } | |
84 | ||
85 | this.name = name; | |
86 | this.description = description; | |
87 | ||
88 | reload(); | |
89 | } | |
90 | ||
91 | /** | |
76b51de9 | 92 | * The name of this item, deduced from its ID. |
9e834013 NR |
93 | * <p> |
94 | * In other words, it is the ID but presented in a displayable form. | |
95 | * | |
96 | * @return the name | |
97 | */ | |
98 | public String getName() { | |
99 | return name; | |
100 | } | |
101 | ||
102 | /** | |
103 | * The description of this item (information to present to the user). | |
104 | * | |
105 | * @return the description | |
106 | */ | |
107 | public String getDescription() { | |
108 | return description; | |
109 | } | |
110 | ||
76b51de9 NR |
111 | /** |
112 | * The format this item is supposed to follow | |
113 | * | |
114 | * @return the format | |
115 | */ | |
9e834013 NR |
116 | public Format getFormat() { |
117 | return meta.format(); | |
118 | } | |
119 | ||
76b51de9 NR |
120 | /** |
121 | * The allowed list of values that a {@link Format#FIXED_LIST} item is | |
122 | * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} | |
123 | * items. | |
124 | * | |
125 | * @return the list of values | |
126 | */ | |
0877d6f5 NR |
127 | public String[] getAllowedValues() { |
128 | return meta.list(); | |
129 | } | |
130 | ||
76b51de9 NR |
131 | /** |
132 | * This item is a comma-separated list of values instead of a single value. | |
133 | * <p> | |
134 | * The list items are separated by a comma, each surrounded by | |
135 | * double-quotes, with backslashes and double-quotes escaped by a backslash. | |
136 | * <p> | |
137 | * Example: <tt>"un", "deux"</tt> | |
138 | * | |
139 | * @return TRUE if it is | |
140 | */ | |
0877d6f5 NR |
141 | public boolean isArray() { |
142 | return meta.array(); | |
143 | } | |
144 | ||
76b51de9 NR |
145 | /** |
146 | * This item is only used as a group, not as an option. | |
147 | * <p> | |
148 | * For instance, you could have LANGUAGE_CODE as a group for which you won't | |
149 | * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN | |
150 | * inside for which the value must be set. | |
151 | * | |
152 | * @return TRUE if it is a group | |
153 | */ | |
154 | public boolean isGroup() { | |
155 | return meta.group(); | |
156 | } | |
157 | ||
9e834013 NR |
158 | /** |
159 | * The value stored by this item, as a {@link String}. | |
160 | * | |
161 | * @return the value | |
162 | */ | |
163 | public String getString() { | |
164 | return value; | |
165 | } | |
166 | ||
76b51de9 NR |
167 | /** |
168 | * The default value of this item, as a {@link String}. | |
169 | * | |
170 | * @return the default value | |
171 | */ | |
9e834013 NR |
172 | public String getDefaultString() { |
173 | return meta.def(); | |
174 | } | |
175 | ||
76b51de9 NR |
176 | /** |
177 | * The value stored by this item, as a {@link Boolean}. | |
178 | * | |
179 | * @return the value | |
180 | */ | |
9e834013 NR |
181 | public Boolean getBoolean() { |
182 | return BundleHelper.parseBoolean(getString()); | |
183 | } | |
184 | ||
76b51de9 NR |
185 | /** |
186 | * The default value of this item, as a {@link Boolean}. | |
187 | * | |
188 | * @return the default value | |
189 | */ | |
9e834013 NR |
190 | public Boolean getDefaultBoolean() { |
191 | return BundleHelper.parseBoolean(getDefaultString()); | |
192 | } | |
193 | ||
76b51de9 NR |
194 | /** |
195 | * The value stored by this item, as a {@link Character}. | |
196 | * | |
197 | * @return the value | |
198 | */ | |
9e834013 NR |
199 | public Character getCharacter() { |
200 | return BundleHelper.parseCharacter(getString()); | |
201 | } | |
202 | ||
76b51de9 NR |
203 | /** |
204 | * The default value of this item, as a {@link Character}. | |
205 | * | |
206 | * @return the default value | |
207 | */ | |
9e834013 NR |
208 | public Character getDefaultCharacter() { |
209 | return BundleHelper.parseCharacter(getDefaultString()); | |
210 | } | |
211 | ||
76b51de9 NR |
212 | /** |
213 | * The value stored by this item, as an {@link Integer}. | |
214 | * | |
215 | * @return the value | |
216 | */ | |
9e834013 NR |
217 | public Integer getInteger() { |
218 | return BundleHelper.parseInteger(getString()); | |
219 | } | |
220 | ||
76b51de9 NR |
221 | /** |
222 | * The default value of this item, as an {@link Integer}. | |
223 | * | |
224 | * @return the default value | |
225 | */ | |
9e834013 NR |
226 | public Integer getDefaultInteger() { |
227 | return BundleHelper.parseInteger(getDefaultString()); | |
228 | } | |
229 | ||
76b51de9 NR |
230 | /** |
231 | * The value stored by this item, as a colour (represented here as an | |
232 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
233 | * <p> | |
234 | * The returned colour value is an ARGB value. | |
235 | * | |
236 | * @return the value | |
237 | */ | |
9e834013 NR |
238 | public Integer getColor() { |
239 | return BundleHelper.parseColor(getString()); | |
240 | } | |
241 | ||
76b51de9 NR |
242 | /** |
243 | * The default value stored by this item, as a colour (represented here as | |
244 | * an {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
245 | * <p> | |
246 | * The returned colour value is an ARGB value. | |
247 | * | |
248 | * @return the value | |
249 | */ | |
9e834013 NR |
250 | public Integer getDefaultColor() { |
251 | return BundleHelper.parseColor(getDefaultString()); | |
252 | } | |
253 | ||
76b51de9 NR |
254 | /** |
255 | * A {@link String} representation of the list of values. | |
256 | * <p> | |
257 | * The list of values is comma-separated and each value is surrounded by | |
258 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
259 | * | |
260 | * @return the value | |
261 | */ | |
9e834013 NR |
262 | public List<String> getList() { |
263 | return BundleHelper.parseList(getString()); | |
264 | } | |
265 | ||
76b51de9 NR |
266 | /** |
267 | * A {@link String} representation of the default list of values. | |
268 | * <p> | |
269 | * The list of values is comma-separated and each value is surrounded by | |
270 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
271 | * | |
272 | * @return the value | |
273 | */ | |
9e834013 NR |
274 | public List<String> getDefaultList() { |
275 | return BundleHelper.parseList(getDefaultString()); | |
276 | } | |
277 | ||
278 | /** | |
279 | * The value stored by this item, as a {@link String}. | |
280 | * | |
281 | * @param value | |
282 | * the new value | |
283 | */ | |
284 | public void setString(String value) { | |
285 | this.value = value; | |
286 | } | |
287 | ||
76b51de9 NR |
288 | /** |
289 | * The value stored by this item, as a {@link Boolean}. | |
290 | * | |
291 | * @param value | |
292 | * the new value | |
293 | */ | |
9e834013 NR |
294 | public void setBoolean(boolean value) { |
295 | setString(BundleHelper.fromBoolean(value)); | |
296 | } | |
297 | ||
76b51de9 NR |
298 | /** |
299 | * The value stored by this item, as a {@link Character}. | |
300 | * | |
301 | * @param value | |
302 | * the new value | |
303 | */ | |
9e834013 NR |
304 | public void setCharacter(char value) { |
305 | setString(BundleHelper.fromCharacter(value)); | |
306 | } | |
307 | ||
76b51de9 NR |
308 | /** |
309 | * The value stored by this item, as an {@link Integer}. | |
310 | * | |
311 | * @param value | |
312 | * the new value | |
313 | */ | |
9e834013 NR |
314 | public void setInteger(int value) { |
315 | setString(BundleHelper.fromInteger(value)); | |
316 | } | |
317 | ||
76b51de9 NR |
318 | /** |
319 | * The value stored by this item, as a colour (represented here as an | |
320 | * {@link Integer}) if it represents a colour, or NULL if it doesn't. | |
321 | * <p> | |
322 | * The returned colour value is an ARGB value. | |
323 | * | |
324 | * @param value | |
325 | * the value | |
326 | */ | |
9e834013 NR |
327 | public void setColor(int value) { |
328 | setString(BundleHelper.fromColor(value)); | |
329 | } | |
330 | ||
76b51de9 NR |
331 | /** |
332 | * A {@link String} representation of the default list of values. | |
333 | * <p> | |
334 | * The list of values is comma-separated and each value is surrounded by | |
335 | * double-quotes; backslashes and double-quotes are escaped by a backslash. | |
336 | * | |
337 | * @param value | |
338 | * the {@link String} representation | |
339 | * | |
340 | */ | |
9e834013 NR |
341 | public void setList(List<String> value) { |
342 | setString(BundleHelper.fromList(value)); | |
343 | } | |
344 | ||
345 | /** | |
76b51de9 NR |
346 | * Reload the value from the {@link Bundle}, so the last value that was |
347 | * saved will be used. | |
9e834013 NR |
348 | */ |
349 | public void reload() { | |
350 | value = bundle.getString(id); | |
8517b60c | 351 | for (Runnable listener : reloadedListeners) { |
9e834013 NR |
352 | try { |
353 | listener.run(); | |
354 | } catch (Exception e) { | |
355 | // TODO: error management? | |
356 | e.printStackTrace(); | |
357 | } | |
358 | } | |
359 | } | |
360 | ||
76b51de9 NR |
361 | /** |
362 | * Add a listener that will be called <b>after</b> a reload operation. | |
363 | * <p> | |
364 | * You could use it to refresh the UI for instance. | |
365 | * | |
366 | * @param listener | |
367 | * the listener | |
368 | */ | |
8517b60c NR |
369 | public void addReloadedListener(Runnable listener) { |
370 | reloadedListeners.add(listener); | |
9e834013 NR |
371 | } |
372 | ||
373 | /** | |
374 | * Save the current value to the {@link Bundle}. | |
375 | */ | |
376 | public void save() { | |
8517b60c NR |
377 | for (Runnable listener : saveListeners) { |
378 | try { | |
379 | listener.run(); | |
380 | } catch (Exception e) { | |
381 | // TODO: error management? | |
382 | e.printStackTrace(); | |
383 | } | |
384 | } | |
9e834013 NR |
385 | bundle.setString(id, value); |
386 | } | |
387 | ||
76b51de9 NR |
388 | /** |
389 | * Add a listener that will be called <b>before</b> a save operation. | |
390 | * <p> | |
391 | * You could use it to make some modification to the stored value before it | |
392 | * is saved. | |
393 | * | |
394 | * @param listener | |
395 | * the listener | |
396 | */ | |
8517b60c NR |
397 | public void addSaveListener(Runnable listener) { |
398 | saveListeners.add(listener); | |
399 | } | |
400 | ||
76b51de9 NR |
401 | /** |
402 | * The sub-items if any (if no sub-items, will return an empty list). | |
403 | * <p> | |
404 | * Sub-items are declared when a {@link Meta} has an ID that starts with the | |
405 | * ID of a {@link Meta#group()} {@link MetaInfo}. | |
406 | * <p> | |
407 | * For instance: | |
408 | * <ul> | |
409 | * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li> | |
410 | * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li> | |
411 | * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li> | |
412 | * </ul> | |
413 | * | |
414 | * @return the sub-items if any | |
415 | */ | |
416 | public List<MetaInfo<E>> getChildren() { | |
417 | return children; | |
418 | } | |
419 | ||
0877d6f5 NR |
420 | @Override |
421 | public Iterator<MetaInfo<E>> iterator() { | |
422 | return children.iterator(); | |
423 | } | |
424 | ||
9e834013 NR |
425 | /** |
426 | * Create a list of {@link MetaInfo}, one for each of the item in the given | |
427 | * {@link Bundle}. | |
428 | * | |
429 | * @param <E> | |
430 | * the type of {@link Bundle} to edit | |
431 | * @param type | |
432 | * a class instance of the item type to work on | |
433 | * @param bundle | |
434 | * the {@link Bundle} to sort through | |
435 | * | |
436 | * @return the list | |
437 | */ | |
438 | static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type, | |
439 | Bundle<E> bundle) { | |
440 | List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>(); | |
76b51de9 | 441 | List<MetaInfo<E>> shadow = new ArrayList<MetaInfo<E>>(); |
9e834013 | 442 | for (E id : type.getEnumConstants()) { |
76b51de9 NR |
443 | MetaInfo<E> info = new MetaInfo<E>(type, bundle, id); |
444 | list.add(info); | |
445 | shadow.add(info); | |
9e834013 NR |
446 | } |
447 | ||
76b51de9 NR |
448 | for (int i = 0; i < list.size(); i++) { |
449 | MetaInfo<E> info = list.get(i); | |
8517b60c | 450 | |
76b51de9 NR |
451 | MetaInfo<E> parent = findParent(info, shadow); |
452 | if (parent != null) { | |
453 | list.remove(i--); | |
454 | parent.children.add(info); | |
8517b60c NR |
455 | } |
456 | } | |
457 | ||
76b51de9 | 458 | return list; |
8517b60c NR |
459 | } |
460 | ||
76b51de9 NR |
461 | /** |
462 | * Find the longest parent of the given {@link MetaInfo}, which means: | |
463 | * <ul> | |
464 | * <li>the parent is a {@link Meta#group()}</li> | |
465 | * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li> | |
466 | * <li>there is no other parent sharing a substring for this | |
467 | * {@link MetaInfo} with a longer Id</li> | |
468 | * </ul> | |
469 | * | |
470 | * @param <E> | |
471 | * the kind of enum | |
472 | * @param info | |
473 | * the info to look for a parent for | |
474 | * @param candidates | |
475 | * the list of potential parents | |
476 | * | |
477 | * @return the longest parent or NULL if no parent is found | |
478 | */ | |
8517b60c | 479 | static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info, |
76b51de9 NR |
480 | List<MetaInfo<E>> candidates) { |
481 | String id = info.id.toString(); | |
8517b60c NR |
482 | MetaInfo<E> group = null; |
483 | for (MetaInfo<E> pcandidate : candidates) { | |
76b51de9 NR |
484 | if (pcandidate.isGroup()) { |
485 | String candidateId = pcandidate.id.toString(); | |
486 | if (!id.equals(candidateId) && id.startsWith(candidateId)) { | |
487 | if (group == null | |
488 | || group.id.toString().length() < candidateId | |
489 | .length()) { | |
490 | group = pcandidate; | |
491 | } | |
8517b60c NR |
492 | } |
493 | } | |
494 | } | |
495 | ||
496 | return group; | |
497 | } | |
9e834013 | 498 | } |