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
;
33 * Create a new {@link MetaInfo} from a value (without children).
35 * For instance, you can call
36 * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>.
39 * the type of enum the value is
41 * the bundle this value belongs to
45 public MetaInfo(Class
<E
> type
, Bundle
<E
> bundle
, E id
) {
50 this.meta
= type
.getDeclaredField(id
.name()).getAnnotation(
52 } catch (NoSuchFieldException e
) {
53 } catch (SecurityException e
) {
56 // We consider that if a description bundle is used, everything is in it
58 String description
= null;
59 if (bundle
.getDescriptionBundle() != null) {
60 description
= bundle
.getDescriptionBundle().getString(id
);
61 if (description
!= null && description
.trim().isEmpty()) {
66 if (description
== null) {
67 description
= meta
.description();
68 if (description
== null) {
71 if (meta
.info() != null && !meta
.info().isEmpty()) {
72 if (!description
.isEmpty()) {
73 description
+= "\n\n";
75 description
+= meta
.info();
79 String name
= id
.toString();
80 if (name
.length() > 1) {
81 name
= name
.substring(0, 1) + name
.substring(1).toLowerCase();
82 name
= name
.replace("_", " ");
86 this.description
= description
;
92 * The name of this item, deduced from its ID.
94 * In other words, it is the ID but presented in a displayable form.
98 public String
getName() {
103 * The description of this item (information to present to the user).
105 * @return the description
107 public String
getDescription() {
112 * The format this item is supposed to follow
116 public Format
getFormat() {
117 return meta
.format();
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}
125 * @return the list of values
127 public String
[] getAllowedValues() {
132 * This item is a comma-separated list of values instead of a single value.
134 * The list items are separated by a comma, each surrounded by
135 * double-quotes, with backslashes and double-quotes escaped by a backslash.
137 * Example: <tt>"un", "deux"</tt>
139 * @return TRUE if it is
141 public boolean isArray() {
146 * This item is only used as a group, not as an option.
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.
152 * @return TRUE if it is a group
154 public boolean isGroup() {
159 * The value stored by this item, as a {@link String}.
163 public String
getString() {
168 * The default value of this item, as a {@link String}.
170 * @return the default value
172 public String
getDefaultString() {
177 * The value stored by this item, as a {@link Boolean}.
181 public Boolean
getBoolean() {
182 return BundleHelper
.parseBoolean(getString());
186 * The default value of this item, as a {@link Boolean}.
188 * @return the default value
190 public Boolean
getDefaultBoolean() {
191 return BundleHelper
.parseBoolean(getDefaultString());
195 * The value stored by this item, as a {@link Character}.
199 public Character
getCharacter() {
200 return BundleHelper
.parseCharacter(getString());
204 * The default value of this item, as a {@link Character}.
206 * @return the default value
208 public Character
getDefaultCharacter() {
209 return BundleHelper
.parseCharacter(getDefaultString());
213 * The value stored by this item, as an {@link Integer}.
217 public Integer
getInteger() {
218 return BundleHelper
.parseInteger(getString());
222 * The default value of this item, as an {@link Integer}.
224 * @return the default value
226 public Integer
getDefaultInteger() {
227 return BundleHelper
.parseInteger(getDefaultString());
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.
234 * The returned colour value is an ARGB value.
238 public Integer
getColor() {
239 return BundleHelper
.parseColor(getString());
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.
246 * The returned colour value is an ARGB value.
250 public Integer
getDefaultColor() {
251 return BundleHelper
.parseColor(getDefaultString());
255 * A {@link String} representation of the list of values.
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.
262 public List
<String
> getList() {
263 return BundleHelper
.parseList(getString());
267 * A {@link String} representation of the default list of values.
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.
274 public List
<String
> getDefaultList() {
275 return BundleHelper
.parseList(getDefaultString());
279 * The value stored by this item, as a {@link String}.
284 public void setString(String value
) {
289 * The value stored by this item, as a {@link Boolean}.
294 public void setBoolean(boolean value
) {
295 setString(BundleHelper
.fromBoolean(value
));
299 * The value stored by this item, as a {@link Character}.
304 public void setCharacter(char value
) {
305 setString(BundleHelper
.fromCharacter(value
));
309 * The value stored by this item, as an {@link Integer}.
314 public void setInteger(int value
) {
315 setString(BundleHelper
.fromInteger(value
));
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.
322 * The returned colour value is an ARGB value.
327 public void setColor(int value
) {
328 setString(BundleHelper
.fromColor(value
));
332 * A {@link String} representation of the default list of values.
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.
338 * the {@link String} representation
341 public void setList(List
<String
> value
) {
342 setString(BundleHelper
.fromList(value
));
346 * Reload the value from the {@link Bundle}, so the last value that was
347 * saved will be used.
349 public void reload() {
350 value
= bundle
.getString(id
);
351 for (Runnable listener
: reloadedListeners
) {
354 } catch (Exception e
) {
355 // TODO: error management?
362 * Add a listener that will be called <b>after</b> a reload operation.
364 * You could use it to refresh the UI for instance.
369 public void addReloadedListener(Runnable listener
) {
370 reloadedListeners
.add(listener
);
374 * Save the current value to the {@link Bundle}.
377 for (Runnable listener
: saveListeners
) {
380 } catch (Exception e
) {
381 // TODO: error management?
385 bundle
.setString(id
, value
);
389 * Add a listener that will be called <b>before</b> a save operation.
391 * You could use it to make some modification to the stored value before it
397 public void addSaveListener(Runnable listener
) {
398 saveListeners
.add(listener
);
402 * The sub-items if any (if no sub-items, will return an empty list).
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}.
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>
414 * @return the sub-items if any
416 public List
<MetaInfo
<E
>> getChildren() {
421 public Iterator
<MetaInfo
<E
>> iterator() {
422 return children
.iterator();
426 * Create a list of {@link MetaInfo}, one for each of the item in the given
430 * the type of {@link Bundle} to edit
432 * a class instance of the item type to work on
434 * the {@link Bundle} to sort through
438 static public <E
extends Enum
<E
>> List
<MetaInfo
<E
>> getItems(Class
<E
> type
,
440 List
<MetaInfo
<E
>> list
= new ArrayList
<MetaInfo
<E
>>();
441 List
<MetaInfo
<E
>> shadow
= new ArrayList
<MetaInfo
<E
>>();
442 for (E id
: type
.getEnumConstants()) {
443 MetaInfo
<E
> info
= new MetaInfo
<E
>(type
, bundle
, id
);
448 for (int i
= 0; i
< list
.size(); i
++) {
449 MetaInfo
<E
> info
= list
.get(i
);
451 MetaInfo
<E
> parent
= findParent(info
, shadow
);
452 if (parent
!= null) {
454 parent
.children
.add(info
);
462 * Find the longest parent of the given {@link MetaInfo}, which means:
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>
473 * the info to look for a parent for
475 * the list of potential parents
477 * @return the longest parent or NULL if no parent is found
479 static private <E
extends Enum
<E
>> MetaInfo
<E
> findParent(MetaInfo
<E
> info
,
480 List
<MetaInfo
<E
>> candidates
) {
481 String id
= info
.id
.toString();
482 MetaInfo
<E
> group
= null;
483 for (MetaInfo
<E
> pcandidate
: candidates
) {
484 if (pcandidate
.isGroup()) {
485 String candidateId
= pcandidate
.id
.toString();
486 if (!id
.equals(candidateId
) && id
.startsWith(candidateId
)) {
488 || group
.id
.toString().length() < candidateId