ConfigItem add icon for "+"
[fanfix.git] / src / be / nikiroo / utils / resources / MetaInfo.java
CommitLineData
9e834013
NR
1package be.nikiroo.utils.resources;
2
3import java.util.ArrayList;
0877d6f5 4import java.util.Iterator;
9e834013
NR
5import java.util.List;
6
7import 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 18public 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
d5026c09
NR
32 private boolean dirty;
33
76b51de9
NR
34 /**
35 * Create a new {@link MetaInfo} from a value (without children).
36 * <p>
37 * For instance, you can call
38 * <tt>new MetaInfo(Config.class, configBundle, Config.MY_VALUE)</tt>.
39 *
40 * @param type
41 * the type of enum the value is
42 * @param bundle
43 * the bundle this value belongs to
44 * @param id
45 * the value itself
46 */
9e834013
NR
47 public MetaInfo(Class<E> type, Bundle<E> bundle, E id) {
48 this.bundle = bundle;
49 this.id = id;
50
51 try {
52 this.meta = type.getDeclaredField(id.name()).getAnnotation(
53 Meta.class);
54 } catch (NoSuchFieldException e) {
55 } catch (SecurityException e) {
56 }
57
58 // We consider that if a description bundle is used, everything is in it
59
60 String description = null;
61 if (bundle.getDescriptionBundle() != null) {
62 description = bundle.getDescriptionBundle().getString(id);
63 if (description != null && description.trim().isEmpty()) {
64 description = null;
65 }
66 }
9e834013
NR
67 if (description == null) {
68 description = meta.description();
0877d6f5
NR
69 if (description == null) {
70 description = "";
71 }
d18e136e
NR
72 }
73
74 String name = idToName(id, null);
75
76 // Special rules for groups:
77 if (meta.group()) {
78 String groupName = description.split("\n")[0];
79 description = description.substring(groupName.length()).trim();
80 if (!groupName.isEmpty()) {
81 name = groupName;
9e834013
NR
82 }
83 }
84
d18e136e
NR
85 if (meta.def() != null && !meta.def().isEmpty()) {
86 if (!description.isEmpty()) {
87 description += "\n\n";
88 }
89 description += "(Default value: " + meta.def() + ")";
9e834013
NR
90 }
91
92 this.name = name;
93 this.description = description;
94
95 reload();
96 }
97
98 /**
d18e136e
NR
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).
101 * <p>
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).
9e834013 104 * <p>
d18e136e 105 * Never NULL.
9e834013 106 *
d18e136e
NR
107 *
108 * @return the name, never NULL
9e834013
NR
109 */
110 public String getName() {
111 return name;
112 }
113
114 /**
d18e136e
NR
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 =
117 * file size...).
118 * <p>
119 * For group, the first line ('\\n'-separated) will be used as a title while
120 * the rest will be the description.
121 * <p>
122 * If a default value is known, it will be specified here, too.
123 * <p>
124 * Never NULL.
9e834013 125 *
d18e136e 126 * @return the description, not NULL
9e834013
NR
127 */
128 public String getDescription() {
129 return description;
130 }
131
76b51de9
NR
132 /**
133 * The format this item is supposed to follow
134 *
135 * @return the format
136 */
9e834013
NR
137 public Format getFormat() {
138 return meta.format();
139 }
140
76b51de9
NR
141 /**
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.
d18e136e
NR
145 * <p>
146 * Will always allow an empty string in addition to the rest.
76b51de9
NR
147 *
148 * @return the list of values
149 */
0877d6f5 150 public String[] getAllowedValues() {
d18e136e
NR
151 String[] list = meta.list();
152
153 String[] withEmpty = new String[list.length + 1];
154 withEmpty[0] = "";
155 for (int i = 0; i < list.length; i++) {
156 withEmpty[i + 1] = list[i];
157 }
158
159 return withEmpty;
0877d6f5
NR
160 }
161
76b51de9
NR
162 /**
163 * This item is a comma-separated list of values instead of a single value.
164 * <p>
165 * The list items are separated by a comma, each surrounded by
166 * double-quotes, with backslashes and double-quotes escaped by a backslash.
167 * <p>
168 * Example: <tt>"un", "deux"</tt>
169 *
170 * @return TRUE if it is
171 */
0877d6f5
NR
172 public boolean isArray() {
173 return meta.array();
174 }
175
d5026c09 176 /**
74d2a495
NR
177 * A manual flag to specify if the data has been changed or not, which can
178 * be used by {@link MetaInfo#save(boolean)}.
d5026c09
NR
179 *
180 * @return TRUE if it is dirty (if it has changed)
181 */
182 public boolean isDirty() {
183 return dirty;
184 }
185
186 /**
74d2a495
NR
187 * A manual flag to specify that the data has been changed, which can be
188 * used by {@link MetaInfo#save(boolean)}.
d5026c09
NR
189 */
190 public void setDirty() {
191 this.dirty = true;
192 }
193
194 /**
195 * The number of items in this item if it {@link MetaInfo#isArray()}, or -1
196 * if not.
197 *
198 * @param useDefaultIfEmpty
199 * check the size of the default list instead if the list is
200 * empty
201 *
202 * @return -1 or the number of items
203 */
204 public int getListSize(boolean useDefaultIfEmpty) {
205 if (!isArray()) {
206 return -1;
207 }
208
209 return BundleHelper.getListSize(getString(-1, useDefaultIfEmpty));
210 }
211
76b51de9
NR
212 /**
213 * This item is only used as a group, not as an option.
214 * <p>
215 * For instance, you could have LANGUAGE_CODE as a group for which you won't
216 * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN
217 * inside for which the value must be set.
218 *
219 * @return TRUE if it is a group
220 */
221 public boolean isGroup() {
222 return meta.group();
223 }
224
9e834013
NR
225 /**
226 * The value stored by this item, as a {@link String}.
227 *
d5026c09
NR
228 * @param item
229 * the item number to get for an array of values, or -1 to get
230 * the whole value (has no effect if {@link MetaInfo#isArray()}
231 * is FALSE)
d18e136e
NR
232 * @param useDefaultIfEmpty
233 * use the default value instead of NULL if the setting is not
234 * set
235 *
9e834013
NR
236 * @return the value
237 */
d5026c09
NR
238 public String getString(int item, boolean useDefaultIfEmpty) {
239 if (isArray() && item >= 0) {
240 List<String> values = BundleHelper.parseList(value, -1);
241 if (values != null && item < values.size()) {
242 return values.get(item);
243 }
244
245 if (useDefaultIfEmpty) {
246 return getDefaultString(item);
247 }
248
249 return null;
250 }
251
d18e136e 252 if (value == null && useDefaultIfEmpty) {
d5026c09 253 return getDefaultString(item);
d18e136e
NR
254 }
255
9e834013
NR
256 return value;
257 }
258
76b51de9
NR
259 /**
260 * The default value of this item, as a {@link String}.
261 *
d5026c09
NR
262 * @param item
263 * the item number to get for an array of values, or -1 to get
264 * the whole value (has no effect if {@link MetaInfo#isArray()}
265 * is FALSE)
266 *
76b51de9
NR
267 * @return the default value
268 */
d5026c09
NR
269 public String getDefaultString(int item) {
270 if (isArray() && item >= 0) {
271 List<String> values = BundleHelper.parseList(meta.def(), item);
272 if (values != null && item < values.size()) {
273 return values.get(item);
274 }
275
276 return null;
277 }
278
9e834013
NR
279 return meta.def();
280 }
281
76b51de9
NR
282 /**
283 * The value stored by this item, as a {@link Boolean}.
284 *
d5026c09
NR
285 * @param item
286 * the item number to get for an array of values, or -1 to get
287 * the whole value (has no effect if {@link MetaInfo#isArray()}
288 * is FALSE)
d18e136e
NR
289 * @param useDefaultIfEmpty
290 * use the default value instead of NULL if the setting is not
291 * set
292 *
76b51de9
NR
293 * @return the value
294 */
d5026c09
NR
295 public Boolean getBoolean(int item, boolean useDefaultIfEmpty) {
296 return BundleHelper
297 .parseBoolean(getString(item, useDefaultIfEmpty), -1);
9e834013
NR
298 }
299
76b51de9
NR
300 /**
301 * The default value of this item, as a {@link Boolean}.
302 *
d5026c09
NR
303 * @param item
304 * the item number to get for an array of values, or -1 to get
305 * the whole value (has no effect if {@link MetaInfo#isArray()}
306 * is FALSE)
307 *
76b51de9
NR
308 * @return the default value
309 */
d5026c09
NR
310 public Boolean getDefaultBoolean(int item) {
311 return BundleHelper.parseBoolean(getDefaultString(item), -1);
9e834013
NR
312 }
313
76b51de9
NR
314 /**
315 * The value stored by this item, as a {@link Character}.
316 *
d5026c09
NR
317 * @param item
318 * the item number to get for an array of values, or -1 to get
319 * the whole value (has no effect if {@link MetaInfo#isArray()}
320 * is FALSE)
d18e136e
NR
321 * @param useDefaultIfEmpty
322 * use the default value instead of NULL if the setting is not
323 * set
324 *
76b51de9
NR
325 * @return the value
326 */
d5026c09
NR
327 public Character getCharacter(int item, boolean useDefaultIfEmpty) {
328 return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty),
329 -1);
9e834013
NR
330 }
331
76b51de9
NR
332 /**
333 * The default value of this item, as a {@link Character}.
334 *
d5026c09
NR
335 * @param item
336 * the item number to get for an array of values, or -1 to get
337 * the whole value (has no effect if {@link MetaInfo#isArray()}
338 * is FALSE)
339 *
76b51de9
NR
340 * @return the default value
341 */
d5026c09
NR
342 public Character getDefaultCharacter(int item) {
343 return BundleHelper.parseCharacter(getDefaultString(item), -1);
9e834013
NR
344 }
345
76b51de9
NR
346 /**
347 * The value stored by this item, as an {@link Integer}.
348 *
d5026c09
NR
349 * @param item
350 * the item number to get for an array of values, or -1 to get
351 * the whole value (has no effect if {@link MetaInfo#isArray()}
352 * is FALSE)
d18e136e
NR
353 * @param useDefaultIfEmpty
354 * use the default value instead of NULL if the setting is not
355 * set
356 *
76b51de9
NR
357 * @return the value
358 */
d5026c09
NR
359 public Integer getInteger(int item, boolean useDefaultIfEmpty) {
360 return BundleHelper
361 .parseInteger(getString(item, useDefaultIfEmpty), -1);
9e834013
NR
362 }
363
76b51de9
NR
364 /**
365 * The default value of this item, as an {@link Integer}.
366 *
d5026c09
NR
367 * @param item
368 * the item number to get for an array of values, or -1 to get
369 * the whole value (has no effect if {@link MetaInfo#isArray()}
370 * is FALSE)
371 *
76b51de9
NR
372 * @return the default value
373 */
d5026c09
NR
374 public Integer getDefaultInteger(int item) {
375 return BundleHelper.parseInteger(getDefaultString(item), -1);
9e834013
NR
376 }
377
76b51de9
NR
378 /**
379 * The value stored by this item, as a colour (represented here as an
380 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
381 * <p>
382 * The returned colour value is an ARGB value.
383 *
d5026c09
NR
384 * @param item
385 * the item number to get for an array of values, or -1 to get
386 * the whole value (has no effect if {@link MetaInfo#isArray()}
387 * is FALSE)
d18e136e
NR
388 * @param useDefaultIfEmpty
389 * use the default value instead of NULL if the setting is not
390 * set
391 *
76b51de9
NR
392 * @return the value
393 */
d5026c09
NR
394 public Integer getColor(int item, boolean useDefaultIfEmpty) {
395 return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1);
9e834013
NR
396 }
397
76b51de9
NR
398 /**
399 * The default value stored by this item, as a colour (represented here as
400 * an {@link Integer}) if it represents a colour, or NULL if it doesn't.
401 * <p>
402 * The returned colour value is an ARGB value.
403 *
d5026c09
NR
404 * @param item
405 * the item number to get for an array of values, or -1 to get
406 * the whole value (has no effect if {@link MetaInfo#isArray()}
407 * is FALSE)
408 *
76b51de9
NR
409 * @return the value
410 */
d5026c09
NR
411 public Integer getDefaultColor(int item) {
412 return BundleHelper.parseColor(getDefaultString(item), -1);
9e834013
NR
413 }
414
76b51de9
NR
415 /**
416 * A {@link String} representation of the list of values.
417 * <p>
418 * The list of values is comma-separated and each value is surrounded by
419 * double-quotes; backslashes and double-quotes are escaped by a backslash.
420 *
d5026c09
NR
421 * @param item
422 * the item number to get for an array of values, or -1 to get
423 * the whole value (has no effect if {@link MetaInfo#isArray()}
424 * is FALSE)
d18e136e
NR
425 * @param useDefaultIfEmpty
426 * use the default value instead of NULL if the setting is not
427 * set
428 *
76b51de9
NR
429 * @return the value
430 */
d5026c09
NR
431 public List<String> getList(int item, boolean useDefaultIfEmpty) {
432 return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1);
9e834013
NR
433 }
434
76b51de9
NR
435 /**
436 * A {@link String} representation of the default list of values.
437 * <p>
438 * The list of values is comma-separated and each value is surrounded by
439 * double-quotes; backslashes and double-quotes are escaped by a backslash.
440 *
d5026c09
NR
441 * @param item
442 * the item number to get for an array of values, or -1 to get
443 * the whole value (has no effect if {@link MetaInfo#isArray()}
444 * is FALSE)
445 *
76b51de9
NR
446 * @return the value
447 */
d5026c09
NR
448 public List<String> getDefaultList(int item) {
449 return BundleHelper.parseList(getDefaultString(item), -1);
9e834013
NR
450 }
451
452 /**
453 * The value stored by this item, as a {@link String}.
454 *
455 * @param value
456 * the new value
d5026c09
NR
457 * @param item
458 * the item number to set for an array of values, or -1 to set
459 * the whole value (has no effect if {@link MetaInfo#isArray()}
460 * is FALSE)
9e834013 461 */
d5026c09
NR
462 public void setString(String value, int item) {
463 if (isArray() && item >= 0) {
464 List<String> values = BundleHelper.parseList(this.value, -1);
465 for (int i = values.size(); i <= item; i++) {
466 values.add(null);
467 }
468 values.set(item, value);
469 this.value = BundleHelper.fromList(values);
470 } else {
471 this.value = value;
472 }
9e834013
NR
473 }
474
76b51de9
NR
475 /**
476 * The value stored by this item, as a {@link Boolean}.
477 *
478 * @param value
479 * the new value
d5026c09
NR
480 * @param item
481 * the item number to set for an array of values, or -1 to set
482 * the whole value (has no effect if {@link MetaInfo#isArray()}
483 * is FALSE)
76b51de9 484 */
d5026c09
NR
485 public void setBoolean(boolean value, int item) {
486 setString(BundleHelper.fromBoolean(value), item);
9e834013
NR
487 }
488
76b51de9
NR
489 /**
490 * The value stored by this item, as a {@link Character}.
491 *
492 * @param value
493 * the new value
d5026c09
NR
494 * @param item
495 * the item number to set for an array of values, or -1 to set
496 * the whole value (has no effect if {@link MetaInfo#isArray()}
497 * is FALSE)
76b51de9 498 */
d5026c09
NR
499 public void setCharacter(char value, int item) {
500 setString(BundleHelper.fromCharacter(value), item);
9e834013
NR
501 }
502
76b51de9
NR
503 /**
504 * The value stored by this item, as an {@link Integer}.
505 *
506 * @param value
507 * the new value
d5026c09
NR
508 * @param item
509 * the item number to set for an array of values, or -1 to set
510 * the whole value (has no effect if {@link MetaInfo#isArray()}
511 * is FALSE)
76b51de9 512 */
d5026c09
NR
513 public void setInteger(int value, int item) {
514 setString(BundleHelper.fromInteger(value), item);
9e834013
NR
515 }
516
76b51de9
NR
517 /**
518 * The value stored by this item, as a colour (represented here as an
519 * {@link Integer}) if it represents a colour, or NULL if it doesn't.
520 * <p>
74d2a495 521 * The colour value is an ARGB value.
76b51de9
NR
522 *
523 * @param value
524 * the value
d5026c09
NR
525 * @param item
526 * the item number to set for an array of values, or -1 to set
527 * the whole value (has no effect if {@link MetaInfo#isArray()}
528 * is FALSE)
76b51de9 529 */
d5026c09
NR
530 public void setColor(int value, int item) {
531 setString(BundleHelper.fromColor(value), item);
9e834013
NR
532 }
533
76b51de9
NR
534 /**
535 * A {@link String} representation of the default list of values.
536 * <p>
537 * The list of values is comma-separated and each value is surrounded by
538 * double-quotes; backslashes and double-quotes are escaped by a backslash.
539 *
540 * @param value
541 * the {@link String} representation
d5026c09
NR
542 * @param item
543 * the item number to set for an array of values, or -1 to set
544 * the whole value (has no effect if {@link MetaInfo#isArray()}
545 * is FALSE)
76b51de9 546 */
d5026c09
NR
547 public void setList(List<String> value, int item) {
548 setString(BundleHelper.fromList(value), item);
9e834013
NR
549 }
550
551 /**
76b51de9
NR
552 * Reload the value from the {@link Bundle}, so the last value that was
553 * saved will be used.
9e834013
NR
554 */
555 public void reload() {
fde375c1
NR
556 if (bundle.isSet(id, false)) {
557 value = bundle.getString(id);
558 } else {
559 value = null;
560 }
561
8517b60c 562 for (Runnable listener : reloadedListeners) {
9e834013
NR
563 try {
564 listener.run();
565 } catch (Exception e) {
9e834013
NR
566 e.printStackTrace();
567 }
568 }
569 }
570
76b51de9
NR
571 /**
572 * Add a listener that will be called <b>after</b> a reload operation.
573 * <p>
574 * You could use it to refresh the UI for instance.
575 *
576 * @param listener
577 * the listener
578 */
8517b60c
NR
579 public void addReloadedListener(Runnable listener) {
580 reloadedListeners.add(listener);
9e834013
NR
581 }
582
583 /**
584 * Save the current value to the {@link Bundle}.
d5026c09
NR
585 * <p>
586 * Note that listeners will be called <b>before</b> the dirty check and
587 * <b>before</b> saving the value.
588 *
589 * @param onlyIfDirty
590 * only save the data if the dirty flag is set (will reset the
591 * dirty flag)
9e834013 592 */
d5026c09 593 public void save(boolean onlyIfDirty) {
8517b60c
NR
594 for (Runnable listener : saveListeners) {
595 try {
596 listener.run();
597 } catch (Exception e) {
8517b60c
NR
598 e.printStackTrace();
599 }
600 }
d5026c09
NR
601
602 if (!onlyIfDirty || isDirty()) {
603 bundle.setString(id, value);
604 }
9e834013
NR
605 }
606
76b51de9
NR
607 /**
608 * Add a listener that will be called <b>before</b> a save operation.
609 * <p>
610 * You could use it to make some modification to the stored value before it
611 * is saved.
612 *
613 * @param listener
614 * the listener
615 */
8517b60c
NR
616 public void addSaveListener(Runnable listener) {
617 saveListeners.add(listener);
618 }
619
76b51de9
NR
620 /**
621 * The sub-items if any (if no sub-items, will return an empty list).
622 * <p>
623 * Sub-items are declared when a {@link Meta} has an ID that starts with the
624 * ID of a {@link Meta#group()} {@link MetaInfo}.
625 * <p>
626 * For instance:
627 * <ul>
628 * <li>{@link Meta} <tt>MY_PREFIX</tt> is a {@link Meta#group()}</li>
629 * <li>{@link Meta} <tt>MY_PREFIX_DESCRIPTION</tt> is another {@link Meta}</li>
630 * <li><tt>MY_PREFIX_DESCRIPTION</tt> will be a child of <tt>MY_PREFIX</tt></li>
631 * </ul>
632 *
633 * @return the sub-items if any
634 */
635 public List<MetaInfo<E>> getChildren() {
636 return children;
637 }
638
0877d6f5
NR
639 @Override
640 public Iterator<MetaInfo<E>> iterator() {
641 return children.iterator();
642 }
643
9e834013
NR
644 /**
645 * Create a list of {@link MetaInfo}, one for each of the item in the given
646 * {@link Bundle}.
647 *
648 * @param <E>
649 * the type of {@link Bundle} to edit
650 * @param type
651 * a class instance of the item type to work on
652 * @param bundle
653 * the {@link Bundle} to sort through
654 *
655 * @return the list
656 */
657 static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type,
658 Bundle<E> bundle) {
659 List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>();
76b51de9 660 List<MetaInfo<E>> shadow = new ArrayList<MetaInfo<E>>();
9e834013 661 for (E id : type.getEnumConstants()) {
76b51de9
NR
662 MetaInfo<E> info = new MetaInfo<E>(type, bundle, id);
663 list.add(info);
664 shadow.add(info);
9e834013
NR
665 }
666
76b51de9
NR
667 for (int i = 0; i < list.size(); i++) {
668 MetaInfo<E> info = list.get(i);
8517b60c 669
76b51de9
NR
670 MetaInfo<E> parent = findParent(info, shadow);
671 if (parent != null) {
672 list.remove(i--);
673 parent.children.add(info);
d18e136e 674 info.name = idToName(info.id, parent.id);
8517b60c
NR
675 }
676 }
677
76b51de9 678 return list;
8517b60c
NR
679 }
680
76b51de9
NR
681 /**
682 * Find the longest parent of the given {@link MetaInfo}, which means:
683 * <ul>
684 * <li>the parent is a {@link Meta#group()}</li>
685 * <li>the parent Id is a substring of the Id of the given {@link MetaInfo}</li>
686 * <li>there is no other parent sharing a substring for this
687 * {@link MetaInfo} with a longer Id</li>
688 * </ul>
689 *
690 * @param <E>
691 * the kind of enum
692 * @param info
693 * the info to look for a parent for
694 * @param candidates
695 * the list of potential parents
696 *
697 * @return the longest parent or NULL if no parent is found
698 */
8517b60c 699 static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info,
76b51de9
NR
700 List<MetaInfo<E>> candidates) {
701 String id = info.id.toString();
8517b60c
NR
702 MetaInfo<E> group = null;
703 for (MetaInfo<E> pcandidate : candidates) {
76b51de9
NR
704 if (pcandidate.isGroup()) {
705 String candidateId = pcandidate.id.toString();
706 if (!id.equals(candidateId) && id.startsWith(candidateId)) {
707 if (group == null
708 || group.id.toString().length() < candidateId
709 .length()) {
710 group = pcandidate;
711 }
8517b60c
NR
712 }
713 }
714 }
715
716 return group;
717 }
d18e136e
NR
718
719 static private <E extends Enum<E>> String idToName(E id, E prefix) {
720 String name = id.toString();
721 if (prefix != null && name.startsWith(prefix.toString())) {
722 name = name.substring(prefix.toString().length());
723 }
724
725 if (name.length() > 0) {
726 name = name.substring(0, 1).toUpperCase()
727 + name.substring(1).toLowerCase();
728 }
729
730 name = name.replace("_", " ");
731
732 return name.trim();
733 }
9e834013 734}