f7598f190c2f9a5d491fd2b56b9644e06c104d1f
1 package be
.nikiroo
.utils
.resources
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Iterator
;
7 import be
.nikiroo
.utils
.resources
.Meta
.Format
;
10 * A graphical item that reflect a configuration option from the given
16 * the type of {@link Bundle} to edit
18 public class MetaInfo
<E
extends Enum
<E
>> implements Iterable
<MetaInfo
<E
>> {
19 private final Bundle
<E
> bundle
;
23 private List
<MetaInfo
<E
>> children
= new ArrayList
<MetaInfo
<E
>>();
26 private List
<Runnable
> reloadedListeners
= new ArrayList
<Runnable
>();
27 private List
<Runnable
> saveListeners
= new ArrayList
<Runnable
>();
30 private String description
;
32 private boolean dirty
;
35 * Create a new {@link MetaInfo} from a value (without children).
37 * For instance, you can call
38 * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>.
41 * the type of enum the value is
43 * the bundle this value belongs to
47 public MetaInfo(Class
<E
> type
, Bundle
<E
> bundle
, E id
) {
52 this.meta
= type
.getDeclaredField(id
.name()).getAnnotation(
54 } catch (NoSuchFieldException e
) {
55 } catch (SecurityException e
) {
58 // We consider that if a description bundle is used, everything is in it
60 String description
= null;
61 if (bundle
.getDescriptionBundle() != null) {
62 description
= bundle
.getDescriptionBundle().getString(id
);
63 if (description
!= null && description
.trim().isEmpty()) {
67 if (description
== null) {
68 description
= meta
.description();
69 if (description
== null) {
74 String name
= idToName(id
, null);
76 // Special rules for groups:
78 String groupName
= description
.split("\n")[0];
79 description
= description
.substring(groupName
.length()).trim();
80 if (!groupName
.isEmpty()) {
85 if (meta
.def() != null && !meta
.def().isEmpty()) {
86 if (!description
.isEmpty()) {
87 description
+= "\n\n";
89 description
+= "(Default value: " + meta
.def() + ")";
93 this.description
= description
;
99 * For normal items, this is the name of this item, deduced from its ID (or
100 * in other words, it is the ID but presented in a displayable form).
102 * For group items, this is the first line of the description if it is not
103 * empty (else, it is the ID in the same way as normal items).
108 * @return the name, never NULL
110 public String
getName() {
115 * A description for this item: what it is or does, how to explain that item
116 * to the user including what can be used here (i.e., %s = file name, %d =
119 * For group, the first line ('\\n'-separated) will be used as a title while
120 * the rest will be the description.
122 * If a default value is known, it will be specified here, too.
126 * @return the description, not NULL
128 public String
getDescription() {
133 * The format this item is supposed to follow
137 public Format
getFormat() {
138 return meta
.format();
142 * The allowed list of values that a {@link Format#FIXED_LIST} item is
143 * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST}
144 * items. Also works for {@link Format#LOCALE}.
146 * Will always allow an empty string in addition to the rest.
148 * @return the list of values
150 public String
[] getAllowedValues() {
151 String
[] list
= meta
.list();
153 String
[] withEmpty
= new String
[list
.length
+ 1];
155 for (int i
= 0; i
< list
.length
; i
++) {
156 withEmpty
[i
+ 1] = list
[i
];
163 * Return all the languages known by the program for this bundle.
165 * This only works for {@link TransBundle}, and will return an empty list if
166 * this is not a {@link TransBundle}.
168 * @return the known language codes
170 public List
<String
> getKnownLanguages() {
171 if (bundle
instanceof TransBundle
) {
172 return ((TransBundle
<E
>) bundle
).getKnownLanguages();
175 return new ArrayList
<String
>();
179 * This item is a comma-separated list of values instead of a single value.
181 * The list items are separated by a comma, each surrounded by
182 * double-quotes, with backslashes and double-quotes escaped by a backslash.
184 * Example: <tt>"un", "deux"</tt>
186 * @return TRUE if it is
188 public boolean isArray() {
193 * A manual flag to specify if the data has been changed or not, which can
194 * be used by {@link MetaInfo#save(boolean)}.
196 * @return TRUE if it is dirty (if it has changed)
198 public boolean isDirty() {
203 * A manual flag to specify that the data has been changed, which can be
204 * used by {@link MetaInfo#save(boolean)}.
206 public void setDirty() {
211 * The number of items in this item if it {@link MetaInfo#isArray()}, or -1
214 * @param useDefaultIfEmpty
215 * check the size of the default list instead if the list is
218 * @return -1 or the number of items
220 public int getListSize(boolean useDefaultIfEmpty
) {
225 return BundleHelper
.getListSize(getString(-1, useDefaultIfEmpty
));
229 * This item is only used as a group, not as an option.
231 * For instance, you could have LANGUAGE_CODE as a group for which you won't
232 * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN
233 * inside for which the value must be set.
235 * @return TRUE if it is a group
237 public boolean isGroup() {
242 * The value stored by this item, as a {@link String}.
245 * the item number to get for an array of values, or -1 to get
246 * the whole value (has no effect if {@link MetaInfo#isArray()}
248 * @param useDefaultIfEmpty
249 * use the default value instead of NULL if the setting is not
254 public String
getString(int item
, boolean useDefaultIfEmpty
) {
255 if (isArray() && item
>= 0) {
256 List
<String
> values
= BundleHelper
.parseList(value
, -1);
257 if (values
!= null && item
< values
.size()) {
258 return values
.get(item
);
261 if (useDefaultIfEmpty
) {
262 return getDefaultString(item
);
268 if (value
== null && useDefaultIfEmpty
) {
269 return getDefaultString(item
);
276 * The default value of this item, as a {@link String}.
279 * the item number to get for an array of values, or -1 to get
280 * the whole value (has no effect if {@link MetaInfo#isArray()}
283 * @return the default value
285 public String
getDefaultString(int item
) {
286 if (isArray() && item
>= 0) {
287 List
<String
> values
= BundleHelper
.parseList(meta
.def(), item
);
288 if (values
!= null && item
< values
.size()) {
289 return values
.get(item
);
299 * The value stored by this item, as a {@link Boolean}.
302 * the item number to get for an array of values, or -1 to get
303 * the whole value (has no effect if {@link MetaInfo#isArray()}
305 * @param useDefaultIfEmpty
306 * use the default value instead of NULL if the setting is not
311 public Boolean
getBoolean(int item
, boolean useDefaultIfEmpty
) {
313 .parseBoolean(getString(item
, useDefaultIfEmpty
), -1);
317 * The default value of this item, as a {@link Boolean}.
320 * the item number to get for an array of values, or -1 to get
321 * the whole value (has no effect if {@link MetaInfo#isArray()}
324 * @return the default value
326 public Boolean
getDefaultBoolean(int item
) {
327 return BundleHelper
.parseBoolean(getDefaultString(item
), -1);
331 * The value stored by this item, as a {@link Character}.
334 * the item number to get for an array of values, or -1 to get
335 * the whole value (has no effect if {@link MetaInfo#isArray()}
337 * @param useDefaultIfEmpty
338 * use the default value instead of NULL if the setting is not
343 public Character
getCharacter(int item
, boolean useDefaultIfEmpty
) {
344 return BundleHelper
.parseCharacter(getString(item
, useDefaultIfEmpty
),
349 * The default value of this item, as a {@link Character}.
352 * the item number to get for an array of values, or -1 to get
353 * the whole value (has no effect if {@link MetaInfo#isArray()}
356 * @return the default value
358 public Character
getDefaultCharacter(int item
) {
359 return BundleHelper
.parseCharacter(getDefaultString(item
), -1);
363 * The value stored by this item, as an {@link Integer}.
366 * the item number to get for an array of values, or -1 to get
367 * the whole value (has no effect if {@link MetaInfo#isArray()}
369 * @param useDefaultIfEmpty
370 * use the default value instead of NULL if the setting is not
375 public Integer
getInteger(int item
, boolean useDefaultIfEmpty
) {
377 .parseInteger(getString(item
, useDefaultIfEmpty
), -1);
381 * The default value of this item, as an {@link Integer}.
384 * the item number to get for an array of values, or -1 to get
385 * the whole value (has no effect if {@link MetaInfo#isArray()}
388 * @return the default value
390 public Integer
getDefaultInteger(int item
) {
391 return BundleHelper
.parseInteger(getDefaultString(item
), -1);
395 * The value stored by this item, as a colour (represented here as an
396 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
398 * The returned colour value is an ARGB value.
401 * the item number to get for an array of values, or -1 to get
402 * the whole value (has no effect if {@link MetaInfo#isArray()}
404 * @param useDefaultIfEmpty
405 * use the default value instead of NULL if the setting is not
410 public Integer
getColor(int item
, boolean useDefaultIfEmpty
) {
411 return BundleHelper
.parseColor(getString(item
, useDefaultIfEmpty
), -1);
415 * The default value stored by this item, as a colour (represented here as
416 * an {@link Integer}) if it represents a colour, or NULL if it doesn't.
418 * The returned colour value is an ARGB value.
421 * the item number to get for an array of values, or -1 to get
422 * the whole value (has no effect if {@link MetaInfo#isArray()}
427 public Integer
getDefaultColor(int item
) {
428 return BundleHelper
.parseColor(getDefaultString(item
), -1);
432 * A {@link String} representation of the list of values.
434 * The list of values is comma-separated and each value is surrounded by
435 * double-quotes; backslashes and double-quotes are escaped by a backslash.
438 * the item number to get for an array of values, or -1 to get
439 * the whole value (has no effect if {@link MetaInfo#isArray()}
441 * @param useDefaultIfEmpty
442 * use the default value instead of NULL if the setting is not
447 public List
<String
> getList(int item
, boolean useDefaultIfEmpty
) {
448 return BundleHelper
.parseList(getString(item
, useDefaultIfEmpty
), -1);
452 * A {@link String} representation of the default list of values.
454 * The list of values is comma-separated and each value is surrounded by
455 * double-quotes; backslashes and double-quotes are escaped by a backslash.
458 * the item number to get for an array of values, or -1 to get
459 * the whole value (has no effect if {@link MetaInfo#isArray()}
464 public List
<String
> getDefaultList(int item
) {
465 return BundleHelper
.parseList(getDefaultString(item
), -1);
469 * The value stored by this item, as a {@link String}.
474 * the item number to set for an array of values, or -1 to set
475 * the whole value (has no effect if {@link MetaInfo#isArray()}
478 public void setString(String value
, int item
) {
479 if (isArray() && item
>= 0) {
480 this.value
= BundleHelper
.fromList(this.value
, value
, item
);
487 * The value stored by this item, as a {@link Boolean}.
492 * the item number to set for an array of values, or -1 to set
493 * the whole value (has no effect if {@link MetaInfo#isArray()}
496 public void setBoolean(boolean value
, int item
) {
497 setString(BundleHelper
.fromBoolean(value
), item
);
501 * The value stored by this item, as a {@link Character}.
506 * the item number to set for an array of values, or -1 to set
507 * the whole value (has no effect if {@link MetaInfo#isArray()}
510 public void setCharacter(char value
, int item
) {
511 setString(BundleHelper
.fromCharacter(value
), item
);
515 * The value stored by this item, as an {@link Integer}.
520 * the item number to set for an array of values, or -1 to set
521 * the whole value (has no effect if {@link MetaInfo#isArray()}
524 public void setInteger(int value
, int item
) {
525 setString(BundleHelper
.fromInteger(value
), item
);
529 * The value stored by this item, as a colour (represented here as an
530 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
532 * The colour value is an ARGB value.
537 * the item number to set for an array of values, or -1 to set
538 * the whole value (has no effect if {@link MetaInfo#isArray()}
541 public void setColor(int value
, int item
) {
542 setString(BundleHelper
.fromColor(value
), item
);
546 * A {@link String} representation of the default list of values.
548 * The list of values is comma-separated and each value is surrounded by
549 * double-quotes; backslashes and double-quotes are escaped by a backslash.
552 * the {@link String} representation
554 * the item number to set for an array of values, or -1 to set
555 * the whole value (has no effect if {@link MetaInfo#isArray()}
558 public void setList(List
<String
> value
, int item
) {
559 setString(BundleHelper
.fromList(value
), item
);
563 * Reload the value from the {@link Bundle}, so the last value that was
564 * saved will be used.
566 public void reload() {
567 if (bundle
.isSet(id
, false)) {
568 value
= bundle
.getString(id
);
573 // Copy the list so we can create new listener in a listener
574 for (Runnable listener
: new ArrayList
<Runnable
>(reloadedListeners
)) {
577 } catch (Exception e
) {
584 * Add a listener that will be called <b>after</b> a reload operation.
586 * You could use it to refresh the UI for instance.
591 public void addReloadedListener(Runnable listener
) {
592 reloadedListeners
.add(listener
);
596 * Save the current value to the {@link Bundle}.
598 * Note that listeners will be called <b>before</b> the dirty check and
599 * <b>before</b> saving the value.
602 * only save the data if the dirty flag is set (will reset the
605 public void save(boolean onlyIfDirty
) {
606 // Copy the list so we can create new listener in a listener
607 for (Runnable listener
: new ArrayList
<Runnable
>(saveListeners
)) {
610 } catch (Exception e
) {
615 if (!onlyIfDirty
|| isDirty()) {
616 bundle
.setString(id
, value
);
621 * Add a listener that will be called <b>before</b> a save operation.
623 * You could use it to make some modification to the stored value before it
629 public void addSaveListener(Runnable listener
) {
630 saveListeners
.add(listener
);
634 * The sub-items if any (if no sub-items, will return an empty list).
636 * Sub-items are declared when a {@link Meta} has an ID that starts with the
637 * ID of a {@link Meta#group()} {@link MetaInfo}.
641 * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li>
642 * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li>
643 * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li>
646 * @return the sub-items if any
648 public List
<MetaInfo
<E
>> getChildren() {
653 public Iterator
<MetaInfo
<E
>> iterator() {
654 return children
.iterator();
658 * Create a list of {@link MetaInfo}, one for each of the item in the given
662 * the type of {@link Bundle} to edit
664 * a class instance of the item type to work on
666 * the {@link Bundle} to sort through
670 static public <E
extends Enum
<E
>> List
<MetaInfo
<E
>> getItems(Class
<E
> type
,
672 List
<MetaInfo
<E
>> list
= new ArrayList
<MetaInfo
<E
>>();
673 List
<MetaInfo
<E
>> shadow
= new ArrayList
<MetaInfo
<E
>>();
674 for (E id
: type
.getEnumConstants()) {
675 MetaInfo
<E
> info
= new MetaInfo
<E
>(type
, bundle
, id
);
680 for (int i
= 0; i
< list
.size(); i
++) {
681 MetaInfo
<E
> info
= list
.get(i
);
683 MetaInfo
<E
> parent
= findParent(info
, shadow
);
684 if (parent
!= null) {
686 parent
.children
.add(info
);
687 info
.name
= idToName(info
.id
, parent
.id
);
695 * Find the longest parent of the given {@link MetaInfo}, which means:
697 * <li>the parent is a {@link Meta#group()}</li>
698 * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li>
699 * <li>there is no other parent sharing a substring for this
700 * {@link MetaInfo} with a longer Id</li>
706 * the info to look for a parent for
708 * the list of potential parents
710 * @return the longest parent or NULL if no parent is found
712 static private <E
extends Enum
<E
>> MetaInfo
<E
> findParent(MetaInfo
<E
> info
,
713 List
<MetaInfo
<E
>> candidates
) {
714 String id
= info
.id
.toString();
715 MetaInfo
<E
> group
= null;
716 for (MetaInfo
<E
> pcandidate
: candidates
) {
717 if (pcandidate
.isGroup()) {
718 String candidateId
= pcandidate
.id
.toString();
719 if (!id
.equals(candidateId
) && id
.startsWith(candidateId
)) {
721 || group
.id
.toString().length() < candidateId
732 static private <E
extends Enum
<E
>> String
idToName(E id
, E prefix
) {
733 String name
= id
.toString();
734 if (prefix
!= null && name
.startsWith(prefix
.toString())) {
735 name
= name
.substring(prefix
.toString().length());
738 if (name
.length() > 0) {
739 name
= name
.substring(0, 1).toUpperCase()
740 + name
.substring(1).toLowerCase();
743 name
= name
.replace("_", " ");