9f86843226136fc071e38e36df5ea62b8bd7be9e
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()) {
65 if (description
== null) {
66 description
= meta
.description();
67 if (description
== null) {
72 String name
= idToName(id
, null);
74 // Special rules for groups:
76 String groupName
= description
.split("\n")[0];
77 description
= description
.substring(groupName
.length()).trim();
78 if (!groupName
.isEmpty()) {
83 if (meta
.def() != null && !meta
.def().isEmpty()) {
84 if (!description
.isEmpty()) {
85 description
+= "\n\n";
87 description
+= "(Default value: " + meta
.def() + ")";
91 this.description
= description
;
97 * For normal items, this is the name of this item, deduced from its ID (or
98 * in other words, it is the ID but presented in a displayable form).
100 * For group items, this is the first line of the description if it is not
101 * empty (else, it is the ID in the same way as normal items).
106 * @return the name, never NULL
108 public String
getName() {
113 * A description for this item: what it is or does, how to explain that item
114 * to the user including what can be used here (i.e., %s = file name, %d =
117 * For group, the first line ('\\n'-separated) will be used as a title while
118 * the rest will be the description.
120 * If a default value is known, it will be specified here, too.
124 * @return the description, not NULL
126 public String
getDescription() {
131 * The format this item is supposed to follow
135 public Format
getFormat() {
136 return meta
.format();
140 * The allowed list of values that a {@link Format#FIXED_LIST} item is
141 * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST}
144 * Will always allow an empty string in addition to the rest.
146 * @return the list of values
148 public String
[] getAllowedValues() {
149 String
[] list
= meta
.list();
151 String
[] withEmpty
= new String
[list
.length
+ 1];
153 for (int i
= 0; i
< list
.length
; i
++) {
154 withEmpty
[i
+ 1] = list
[i
];
161 * This item is a comma-separated list of values instead of a single value.
163 * The list items are separated by a comma, each surrounded by
164 * double-quotes, with backslashes and double-quotes escaped by a backslash.
166 * Example: <tt>"un", "deux"</tt>
168 * @return TRUE if it is
170 public boolean isArray() {
175 * This item is only used as a group, not as an option.
177 * For instance, you could have LANGUAGE_CODE as a group for which you won't
178 * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN
179 * inside for which the value must be set.
181 * @return TRUE if it is a group
183 public boolean isGroup() {
188 * The value stored by this item, as a {@link String}.
190 * @param useDefaultIfEmpty
191 * use the default value instead of NULL if the setting is not
196 public String
getString(boolean useDefaultIfEmpty
) {
197 if (value
== null && useDefaultIfEmpty
) {
198 return getDefaultString();
205 * The default value of this item, as a {@link String}.
207 * @return the default value
209 public String
getDefaultString() {
214 * The value stored by this item, as a {@link Boolean}.
216 * @param useDefaultIfEmpty
217 * use the default value instead of NULL if the setting is not
222 public Boolean
getBoolean(boolean useDefaultIfEmpty
) {
223 return BundleHelper
.parseBoolean(getString(useDefaultIfEmpty
));
227 * The default value of this item, as a {@link Boolean}.
229 * @return the default value
231 public Boolean
getDefaultBoolean() {
232 return BundleHelper
.parseBoolean(getDefaultString());
236 * The value stored by this item, as a {@link Character}.
238 * @param useDefaultIfEmpty
239 * use the default value instead of NULL if the setting is not
244 public Character
getCharacter(boolean useDefaultIfEmpty
) {
245 return BundleHelper
.parseCharacter(getString(useDefaultIfEmpty
));
249 * The default value of this item, as a {@link Character}.
251 * @return the default value
253 public Character
getDefaultCharacter() {
254 return BundleHelper
.parseCharacter(getDefaultString());
258 * The value stored by this item, as an {@link Integer}.
260 * @param useDefaultIfEmpty
261 * use the default value instead of NULL if the setting is not
266 public Integer
getInteger(boolean useDefaultIfEmpty
) {
267 return BundleHelper
.parseInteger(getString(useDefaultIfEmpty
));
271 * The default value of this item, as an {@link Integer}.
273 * @return the default value
275 public Integer
getDefaultInteger() {
276 return BundleHelper
.parseInteger(getDefaultString());
280 * The value stored by this item, as a colour (represented here as an
281 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
283 * The returned colour value is an ARGB value.
285 * @param useDefaultIfEmpty
286 * use the default value instead of NULL if the setting is not
291 public Integer
getColor(boolean useDefaultIfEmpty
) {
292 return BundleHelper
.parseColor(getString(useDefaultIfEmpty
));
296 * The default value stored by this item, as a colour (represented here as
297 * an {@link Integer}) if it represents a colour, or NULL if it doesn't.
299 * The returned colour value is an ARGB value.
303 public Integer
getDefaultColor() {
304 return BundleHelper
.parseColor(getDefaultString());
308 * A {@link String} representation of the list of values.
310 * The list of values is comma-separated and each value is surrounded by
311 * double-quotes; backslashes and double-quotes are escaped by a backslash.
313 * @param useDefaultIfEmpty
314 * use the default value instead of NULL if the setting is not
319 public List
<String
> getList(boolean useDefaultIfEmpty
) {
320 return BundleHelper
.parseList(getString(useDefaultIfEmpty
));
324 * A {@link String} representation of the default list of values.
326 * The list of values is comma-separated and each value is surrounded by
327 * double-quotes; backslashes and double-quotes are escaped by a backslash.
331 public List
<String
> getDefaultList() {
332 return BundleHelper
.parseList(getDefaultString());
336 * The value stored by this item, as a {@link String}.
341 public void setString(String value
) {
346 * The value stored by this item, as a {@link Boolean}.
351 public void setBoolean(boolean value
) {
352 setString(BundleHelper
.fromBoolean(value
));
356 * The value stored by this item, as a {@link Character}.
361 public void setCharacter(char value
) {
362 setString(BundleHelper
.fromCharacter(value
));
366 * The value stored by this item, as an {@link Integer}.
371 public void setInteger(int value
) {
372 setString(BundleHelper
.fromInteger(value
));
376 * The value stored by this item, as a colour (represented here as an
377 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
379 * The returned colour value is an ARGB value.
384 public void setColor(int value
) {
385 setString(BundleHelper
.fromColor(value
));
389 * A {@link String} representation of the default list of values.
391 * The list of values is comma-separated and each value is surrounded by
392 * double-quotes; backslashes and double-quotes are escaped by a backslash.
395 * the {@link String} representation
398 public void setList(List
<String
> value
) {
399 setString(BundleHelper
.fromList(value
));
403 * Reload the value from the {@link Bundle}, so the last value that was
404 * saved will be used.
406 public void reload() {
407 if (bundle
.isSet(id
, false)) {
408 value
= bundle
.getString(id
);
413 for (Runnable listener
: reloadedListeners
) {
416 } catch (Exception e
) {
417 // TODO: error management?
424 * Add a listener that will be called <b>after</b> a reload operation.
426 * You could use it to refresh the UI for instance.
431 public void addReloadedListener(Runnable listener
) {
432 reloadedListeners
.add(listener
);
436 * Save the current value to the {@link Bundle}.
439 for (Runnable listener
: saveListeners
) {
442 } catch (Exception e
) {
443 // TODO: error management?
447 bundle
.setString(id
, value
);
451 * Add a listener that will be called <b>before</b> a save operation.
453 * You could use it to make some modification to the stored value before it
459 public void addSaveListener(Runnable listener
) {
460 saveListeners
.add(listener
);
464 * The sub-items if any (if no sub-items, will return an empty list).
466 * Sub-items are declared when a {@link Meta} has an ID that starts with the
467 * ID of a {@link Meta#group()} {@link MetaInfo}.
471 * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li>
472 * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li>
473 * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li>
476 * @return the sub-items if any
478 public List
<MetaInfo
<E
>> getChildren() {
483 public Iterator
<MetaInfo
<E
>> iterator() {
484 return children
.iterator();
488 * Create a list of {@link MetaInfo}, one for each of the item in the given
492 * the type of {@link Bundle} to edit
494 * a class instance of the item type to work on
496 * the {@link Bundle} to sort through
500 static public <E
extends Enum
<E
>> List
<MetaInfo
<E
>> getItems(Class
<E
> type
,
502 List
<MetaInfo
<E
>> list
= new ArrayList
<MetaInfo
<E
>>();
503 List
<MetaInfo
<E
>> shadow
= new ArrayList
<MetaInfo
<E
>>();
504 for (E id
: type
.getEnumConstants()) {
505 MetaInfo
<E
> info
= new MetaInfo
<E
>(type
, bundle
, id
);
510 for (int i
= 0; i
< list
.size(); i
++) {
511 MetaInfo
<E
> info
= list
.get(i
);
513 MetaInfo
<E
> parent
= findParent(info
, shadow
);
514 if (parent
!= null) {
516 parent
.children
.add(info
);
517 info
.name
= idToName(info
.id
, parent
.id
);
525 * Find the longest parent of the given {@link MetaInfo}, which means:
527 * <li>the parent is a {@link Meta#group()}</li>
528 * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li>
529 * <li>there is no other parent sharing a substring for this
530 * {@link MetaInfo} with a longer Id</li>
536 * the info to look for a parent for
538 * the list of potential parents
540 * @return the longest parent or NULL if no parent is found
542 static private <E
extends Enum
<E
>> MetaInfo
<E
> findParent(MetaInfo
<E
> info
,
543 List
<MetaInfo
<E
>> candidates
) {
544 String id
= info
.id
.toString();
545 MetaInfo
<E
> group
= null;
546 for (MetaInfo
<E
> pcandidate
: candidates
) {
547 if (pcandidate
.isGroup()) {
548 String candidateId
= pcandidate
.id
.toString();
549 if (!id
.equals(candidateId
) && id
.startsWith(candidateId
)) {
551 || group
.id
.toString().length() < candidateId
562 static private <E
extends Enum
<E
>> String
idToName(E id
, E prefix
) {
563 String name
= id
.toString();
564 if (prefix
!= null && name
.startsWith(prefix
.toString())) {
565 name
= name
.substring(prefix
.toString().length());
568 if (name
.length() > 0) {
569 name
= name
.substring(0, 1).toUpperCase()
570 + name
.substring(1).toLowerCase();
573 name
= name
.replace("_", " ");