e7a8b7c5fe09b5f0a72e36a1161cfde4f2ecfe6f
1 package be
.nikiroo
.utils
.resources
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Iterator
;
7 import be
.nikiroo
.fanfix
.data
.MetaData
;
8 import be
.nikiroo
.utils
.resources
.Meta
.Format
;
11 * A graphical item that reflect a configuration option from the given
17 * the type of {@link Bundle} to edit
19 public class MetaInfo
<E
extends Enum
<E
>> implements Iterable
<MetaInfo
<E
>> {
20 private final Bundle
<E
> bundle
;
24 private List
<MetaInfo
<E
>> children
= new ArrayList
<MetaInfo
<E
>>();
27 private List
<Runnable
> reloadedListeners
= new ArrayList
<Runnable
>();
28 private List
<Runnable
> saveListeners
= new ArrayList
<Runnable
>();
31 private String description
;
33 private boolean dirty
;
36 * Create a new {@link MetaInfo} from a value (without children).
38 * For instance, you can call
39 * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>.
42 * the type of enum the value is
44 * the bundle this value belongs to
48 public MetaInfo(Class
<E
> type
, Bundle
<E
> bundle
, E id
) {
53 this.meta
= type
.getDeclaredField(id
.name()).getAnnotation(
55 } catch (NoSuchFieldException e
) {
56 } catch (SecurityException e
) {
59 // We consider that if a description bundle is used, everything is in it
61 String description
= null;
62 if (bundle
.getDescriptionBundle() != null) {
63 description
= bundle
.getDescriptionBundle().getString(id
);
64 if (description
!= null && description
.trim().isEmpty()) {
68 if (description
== null) {
69 description
= meta
.description();
70 if (description
== null) {
75 String name
= idToName(id
, null);
77 // Special rules for groups:
79 String groupName
= description
.split("\n")[0];
80 description
= description
.substring(groupName
.length()).trim();
81 if (!groupName
.isEmpty()) {
86 if (meta
.def() != null && !meta
.def().isEmpty()) {
87 if (!description
.isEmpty()) {
88 description
+= "\n\n";
90 description
+= "(Default value: " + meta
.def() + ")";
94 this.description
= description
;
100 * For normal items, this is the name of this item, deduced from its ID (or
101 * in other words, it is the ID but presented in a displayable form).
103 * For group items, this is the first line of the description if it is not
104 * empty (else, it is the ID in the same way as normal items).
109 * @return the name, never NULL
111 public String
getName() {
116 * A description for this item: what it is or does, how to explain that item
117 * to the user including what can be used here (i.e., %s = file name, %d =
120 * For group, the first line ('\\n'-separated) will be used as a title while
121 * the rest will be the description.
123 * If a default value is known, it will be specified here, too.
127 * @return the description, not NULL
129 public String
getDescription() {
134 * The format this item is supposed to follow
138 public Format
getFormat() {
139 return meta
.format();
143 * The allowed list of values that a {@link Format#FIXED_LIST} item is
144 * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST}
147 * Will always allow an empty string in addition to the rest.
149 * @return the list of values
151 public String
[] getAllowedValues() {
152 String
[] list
= meta
.list();
154 String
[] withEmpty
= new String
[list
.length
+ 1];
156 for (int i
= 0; i
< list
.length
; i
++) {
157 withEmpty
[i
+ 1] = list
[i
];
164 * This item is a comma-separated list of values instead of a single value.
166 * The list items are separated by a comma, each surrounded by
167 * double-quotes, with backslashes and double-quotes escaped by a backslash.
169 * Example: <tt>"un", "deux"</tt>
171 * @return TRUE if it is
173 public boolean isArray() {
178 * A manual flag to specify if the {@link MetaData} has been changed or not,
179 * which can be used by {@link MetaInfo#save(boolean)}.
181 * @return TRUE if it is dirty (if it has changed)
183 public boolean isDirty() {
188 * A manual flag to specify that the {@link MetaData} has been changed,
189 * which can be used by {@link MetaInfo#save(boolean)}.
191 public void setDirty() {
196 * The number of items in this item if it {@link MetaInfo#isArray()}, or -1
199 * @param useDefaultIfEmpty
200 * check the size of the default list instead if the list is
203 * @return -1 or the number of items
205 public int getListSize(boolean useDefaultIfEmpty
) {
210 return BundleHelper
.getListSize(getString(-1, useDefaultIfEmpty
));
214 * This item is only used as a group, not as an option.
216 * For instance, you could have LANGUAGE_CODE as a group for which you won't
217 * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN
218 * inside for which the value must be set.
220 * @return TRUE if it is a group
222 public boolean isGroup() {
227 * The value stored by this item, as a {@link String}.
230 * the item number to get for an array of values, or -1 to get
231 * the whole value (has no effect if {@link MetaInfo#isArray()}
233 * @param useDefaultIfEmpty
234 * use the default value instead of NULL if the setting is not
239 public String
getString(int item
, boolean useDefaultIfEmpty
) {
240 if (isArray() && item
>= 0) {
241 List
<String
> values
= BundleHelper
.parseList(value
, -1);
242 if (values
!= null && item
< values
.size()) {
243 return values
.get(item
);
246 if (useDefaultIfEmpty
) {
247 return getDefaultString(item
);
253 if (value
== null && useDefaultIfEmpty
) {
254 return getDefaultString(item
);
261 * The default value of this item, as a {@link String}.
264 * the item number to get for an array of values, or -1 to get
265 * the whole value (has no effect if {@link MetaInfo#isArray()}
268 * @return the default value
270 public String
getDefaultString(int item
) {
271 if (isArray() && item
>= 0) {
272 List
<String
> values
= BundleHelper
.parseList(meta
.def(), item
);
273 if (values
!= null && item
< values
.size()) {
274 return values
.get(item
);
284 * The value stored by this item, as a {@link Boolean}.
287 * the item number to get for an array of values, or -1 to get
288 * the whole value (has no effect if {@link MetaInfo#isArray()}
290 * @param useDefaultIfEmpty
291 * use the default value instead of NULL if the setting is not
296 public Boolean
getBoolean(int item
, boolean useDefaultIfEmpty
) {
298 .parseBoolean(getString(item
, useDefaultIfEmpty
), -1);
302 * The default value of this item, as a {@link Boolean}.
305 * the item number to get for an array of values, or -1 to get
306 * the whole value (has no effect if {@link MetaInfo#isArray()}
309 * @return the default value
311 public Boolean
getDefaultBoolean(int item
) {
312 return BundleHelper
.parseBoolean(getDefaultString(item
), -1);
316 * The value stored by this item, as a {@link Character}.
319 * the item number to get for an array of values, or -1 to get
320 * the whole value (has no effect if {@link MetaInfo#isArray()}
322 * @param useDefaultIfEmpty
323 * use the default value instead of NULL if the setting is not
328 public Character
getCharacter(int item
, boolean useDefaultIfEmpty
) {
329 return BundleHelper
.parseCharacter(getString(item
, useDefaultIfEmpty
),
334 * The default value of this item, as a {@link Character}.
337 * the item number to get for an array of values, or -1 to get
338 * the whole value (has no effect if {@link MetaInfo#isArray()}
341 * @return the default value
343 public Character
getDefaultCharacter(int item
) {
344 return BundleHelper
.parseCharacter(getDefaultString(item
), -1);
348 * The value stored by this item, as an {@link Integer}.
351 * the item number to get for an array of values, or -1 to get
352 * the whole value (has no effect if {@link MetaInfo#isArray()}
354 * @param useDefaultIfEmpty
355 * use the default value instead of NULL if the setting is not
360 public Integer
getInteger(int item
, boolean useDefaultIfEmpty
) {
362 .parseInteger(getString(item
, useDefaultIfEmpty
), -1);
366 * The default value of this item, as an {@link Integer}.
369 * the item number to get for an array of values, or -1 to get
370 * the whole value (has no effect if {@link MetaInfo#isArray()}
373 * @return the default value
375 public Integer
getDefaultInteger(int item
) {
376 return BundleHelper
.parseInteger(getDefaultString(item
), -1);
380 * The value stored by this item, as a colour (represented here as an
381 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
383 * The returned colour value is an ARGB value.
386 * the item number to get for an array of values, or -1 to get
387 * the whole value (has no effect if {@link MetaInfo#isArray()}
389 * @param useDefaultIfEmpty
390 * use the default value instead of NULL if the setting is not
395 public Integer
getColor(int item
, boolean useDefaultIfEmpty
) {
396 return BundleHelper
.parseColor(getString(item
, useDefaultIfEmpty
), -1);
400 * The default value stored by this item, as a colour (represented here as
401 * an {@link Integer}) if it represents a colour, or NULL if it doesn't.
403 * The returned colour value is an ARGB value.
406 * the item number to get for an array of values, or -1 to get
407 * the whole value (has no effect if {@link MetaInfo#isArray()}
412 public Integer
getDefaultColor(int item
) {
413 return BundleHelper
.parseColor(getDefaultString(item
), -1);
417 * A {@link String} representation of the list of values.
419 * The list of values is comma-separated and each value is surrounded by
420 * double-quotes; backslashes and double-quotes are escaped by a backslash.
423 * the item number to get for an array of values, or -1 to get
424 * the whole value (has no effect if {@link MetaInfo#isArray()}
426 * @param useDefaultIfEmpty
427 * use the default value instead of NULL if the setting is not
432 public List
<String
> getList(int item
, boolean useDefaultIfEmpty
) {
433 return BundleHelper
.parseList(getString(item
, useDefaultIfEmpty
), -1);
437 * A {@link String} representation of the default list of values.
439 * The list of values is comma-separated and each value is surrounded by
440 * double-quotes; backslashes and double-quotes are escaped by a backslash.
443 * the item number to get for an array of values, or -1 to get
444 * the whole value (has no effect if {@link MetaInfo#isArray()}
449 public List
<String
> getDefaultList(int item
) {
450 return BundleHelper
.parseList(getDefaultString(item
), -1);
454 * The value stored by this item, as a {@link String}.
459 * the item number to set for an array of values, or -1 to set
460 * the whole value (has no effect if {@link MetaInfo#isArray()}
463 public void setString(String value
, int item
) {
464 if (isArray() && item
>= 0) {
465 List
<String
> values
= BundleHelper
.parseList(this.value
, -1);
466 for (int i
= values
.size(); i
<= item
; i
++) {
469 values
.set(item
, value
);
470 this.value
= BundleHelper
.fromList(values
);
477 * The value stored by this item, as a {@link Boolean}.
482 * the item number to set for an array of values, or -1 to set
483 * the whole value (has no effect if {@link MetaInfo#isArray()}
486 public void setBoolean(boolean value
, int item
) {
487 setString(BundleHelper
.fromBoolean(value
), item
);
491 * The value stored by this item, as a {@link Character}.
496 * the item number to set for an array of values, or -1 to set
497 * the whole value (has no effect if {@link MetaInfo#isArray()}
500 public void setCharacter(char value
, int item
) {
501 setString(BundleHelper
.fromCharacter(value
), item
);
505 * The value stored by this item, as an {@link Integer}.
510 * the item number to set for an array of values, or -1 to set
511 * the whole value (has no effect if {@link MetaInfo#isArray()}
514 public void setInteger(int value
, int item
) {
515 setString(BundleHelper
.fromInteger(value
), item
);
519 * The value stored by this item, as a colour (represented here as an
520 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
522 * The returned colour value is an ARGB value.
527 * the item number to set for an array of values, or -1 to set
528 * the whole value (has no effect if {@link MetaInfo#isArray()}
531 public void setColor(int value
, int item
) {
532 setString(BundleHelper
.fromColor(value
), item
);
536 * A {@link String} representation of the default list of values.
538 * The list of values is comma-separated and each value is surrounded by
539 * double-quotes; backslashes and double-quotes are escaped by a backslash.
542 * the {@link String} representation
544 * the item number to set for an array of values, or -1 to set
545 * the whole value (has no effect if {@link MetaInfo#isArray()}
548 public void setList(List
<String
> value
, int item
) {
549 setString(BundleHelper
.fromList(value
), item
);
553 * Reload the value from the {@link Bundle}, so the last value that was
554 * saved will be used.
556 public void reload() {
557 if (bundle
.isSet(id
, false)) {
558 value
= bundle
.getString(id
);
563 for (Runnable listener
: reloadedListeners
) {
566 } catch (Exception e
) {
573 * Add a listener that will be called <b>after</b> a reload operation.
575 * You could use it to refresh the UI for instance.
580 public void addReloadedListener(Runnable listener
) {
581 reloadedListeners
.add(listener
);
585 * Save the current value to the {@link Bundle}.
587 * Note that listeners will be called <b>before</b> the dirty check and
588 * <b>before</b> saving the value.
591 * only save the data if the dirty flag is set (will reset the
594 public void save(boolean onlyIfDirty
) {
595 for (Runnable listener
: saveListeners
) {
598 } catch (Exception e
) {
603 if (!onlyIfDirty
|| isDirty()) {
604 bundle
.setString(id
, value
);
609 * Add a listener that will be called <b>before</b> a save operation.
611 * You could use it to make some modification to the stored value before it
617 public void addSaveListener(Runnable listener
) {
618 saveListeners
.add(listener
);
622 * The sub-items if any (if no sub-items, will return an empty list).
624 * Sub-items are declared when a {@link Meta} has an ID that starts with the
625 * ID of a {@link Meta#group()} {@link MetaInfo}.
629 * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li>
630 * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li>
631 * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li>
634 * @return the sub-items if any
636 public List
<MetaInfo
<E
>> getChildren() {
641 public Iterator
<MetaInfo
<E
>> iterator() {
642 return children
.iterator();
646 * Create a list of {@link MetaInfo}, one for each of the item in the given
650 * the type of {@link Bundle} to edit
652 * a class instance of the item type to work on
654 * the {@link Bundle} to sort through
658 static public <E
extends Enum
<E
>> List
<MetaInfo
<E
>> getItems(Class
<E
> type
,
660 List
<MetaInfo
<E
>> list
= new ArrayList
<MetaInfo
<E
>>();
661 List
<MetaInfo
<E
>> shadow
= new ArrayList
<MetaInfo
<E
>>();
662 for (E id
: type
.getEnumConstants()) {
663 MetaInfo
<E
> info
= new MetaInfo
<E
>(type
, bundle
, id
);
668 for (int i
= 0; i
< list
.size(); i
++) {
669 MetaInfo
<E
> info
= list
.get(i
);
671 MetaInfo
<E
> parent
= findParent(info
, shadow
);
672 if (parent
!= null) {
674 parent
.children
.add(info
);
675 info
.name
= idToName(info
.id
, parent
.id
);
683 * Find the longest parent of the given {@link MetaInfo}, which means:
685 * <li>the parent is a {@link Meta#group()}</li>
686 * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li>
687 * <li>there is no other parent sharing a substring for this
688 * {@link MetaInfo} with a longer Id</li>
694 * the info to look for a parent for
696 * the list of potential parents
698 * @return the longest parent or NULL if no parent is found
700 static private <E
extends Enum
<E
>> MetaInfo
<E
> findParent(MetaInfo
<E
> info
,
701 List
<MetaInfo
<E
>> candidates
) {
702 String id
= info
.id
.toString();
703 MetaInfo
<E
> group
= null;
704 for (MetaInfo
<E
> pcandidate
: candidates
) {
705 if (pcandidate
.isGroup()) {
706 String candidateId
= pcandidate
.id
.toString();
707 if (!id
.equals(candidateId
) && id
.startsWith(candidateId
)) {
709 || group
.id
.toString().length() < candidateId
720 static private <E
extends Enum
<E
>> String
idToName(E id
, E prefix
) {
721 String name
= id
.toString();
722 if (prefix
!= null && name
.startsWith(prefix
.toString())) {
723 name
= name
.substring(prefix
.toString().length());
726 if (name
.length() > 0) {
727 name
= name
.substring(0, 1).toUpperCase()
728 + name
.substring(1).toLowerCase();
731 name
= name
.replace("_", " ");