ConfigItem: add all types except LOCALE (array still not working)
[fanfix.git] / src / be / nikiroo / utils / resources / MetaInfo.java
1 package be.nikiroo.utils.resources;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.TreeMap;
9
10 import be.nikiroo.utils.resources.Meta.Format;
11
12 /**
13 * A graphical item that reflect a configuration option from the given
14 * {@link Bundle}.
15 *
16 * @author niki
17 *
18 * @param <E>
19 * the type of {@link Bundle} to edit
20 */
21 public class MetaInfo<E extends Enum<E>> implements Iterable<MetaInfo<E>> {
22 private final Bundle<E> bundle;
23 private final E id;
24
25 private Meta meta;
26 private List<MetaInfo<E>> children = new ArrayList<MetaInfo<E>>();
27
28 private String value;
29 private List<Runnable> reloadedListeners = new ArrayList<Runnable>();
30 private List<Runnable> saveListeners = new ArrayList<Runnable>();
31
32 private String name;
33 private String description;
34
35 public MetaInfo(Class<E> type, Bundle<E> bundle, E id) {
36 this.bundle = bundle;
37 this.id = id;
38
39 try {
40 this.meta = type.getDeclaredField(id.name()).getAnnotation(
41 Meta.class);
42 } catch (NoSuchFieldException e) {
43 } catch (SecurityException e) {
44 }
45
46 // We consider that if a description bundle is used, everything is in it
47
48 String description = null;
49 if (bundle.getDescriptionBundle() != null) {
50 description = bundle.getDescriptionBundle().getString(id);
51 if (description != null && description.trim().isEmpty()) {
52 description = null;
53 }
54 }
55
56 if (description == null) {
57 description = meta.description();
58 if (description == null) {
59 description = "";
60 }
61 if (meta.info() != null && !meta.info().isEmpty()) {
62 if (!description.isEmpty()) {
63 description += "\n\n";
64 }
65 description += meta.info();
66 }
67 }
68
69 String name = id.toString();
70 if (name.length() > 1) {
71 name = name.substring(0, 1) + name.substring(1).toLowerCase();
72 name = name.replace("_", " ");
73 }
74
75 this.name = name;
76 this.description = description;
77
78 reload();
79 }
80
81 /**
82 * THe name of this item, deduced from its ID.
83 * <p>
84 * In other words, it is the ID but presented in a displayable form.
85 *
86 * @return the name
87 */
88 public String getName() {
89 return name;
90 }
91
92 /**
93 * The description of this item (information to present to the user).
94 *
95 * @return the description
96 */
97 public String getDescription() {
98 return description;
99 }
100
101 public Format getFormat() {
102 return meta.format();
103 }
104
105 // for ComboBox, this is mostly a suggestion
106 public String[] getAllowedValues() {
107 return meta.list();
108 }
109
110 // TODO: use it!
111 public boolean isArray() {
112 return meta.array();
113 }
114
115 /**
116 * The value stored by this item, as a {@link String}.
117 *
118 * @return the value
119 */
120 public String getString() {
121 return value;
122 }
123
124 public String getDefaultString() {
125 return meta.def();
126 }
127
128 public Boolean getBoolean() {
129 return BundleHelper.parseBoolean(getString());
130 }
131
132 public Boolean getDefaultBoolean() {
133 return BundleHelper.parseBoolean(getDefaultString());
134 }
135
136 public Character getCharacter() {
137 return BundleHelper.parseCharacter(getString());
138 }
139
140 public Character getDefaultCharacter() {
141 return BundleHelper.parseCharacter(getDefaultString());
142 }
143
144 public Integer getInteger() {
145 return BundleHelper.parseInteger(getString());
146 }
147
148 public Integer getDefaultInteger() {
149 return BundleHelper.parseInteger(getDefaultString());
150 }
151
152 public Integer getColor() {
153 return BundleHelper.parseColor(getString());
154 }
155
156 public Integer getDefaultColor() {
157 return BundleHelper.parseColor(getDefaultString());
158 }
159
160 public List<String> getList() {
161 return BundleHelper.parseList(getString());
162 }
163
164 public List<String> getDefaultList() {
165 return BundleHelper.parseList(getDefaultString());
166 }
167
168 /**
169 * The value stored by this item, as a {@link String}.
170 *
171 * @param value
172 * the new value
173 */
174 public void setString(String value) {
175 this.value = value;
176 }
177
178 public void setBoolean(boolean value) {
179 setString(BundleHelper.fromBoolean(value));
180 }
181
182 public void setCharacter(char value) {
183 setString(BundleHelper.fromCharacter(value));
184 }
185
186 public void setInteger(int value) {
187 setString(BundleHelper.fromInteger(value));
188 }
189
190 public void setColor(int value) {
191 setString(BundleHelper.fromColor(value));
192 }
193
194 public void setList(List<String> value) {
195 setString(BundleHelper.fromList(value));
196 }
197
198 /**
199 * Reload the value from the {@link Bundle}.
200 */
201 public void reload() {
202 value = bundle.getString(id);
203 for (Runnable listener : reloadedListeners) {
204 try {
205 listener.run();
206 } catch (Exception e) {
207 // TODO: error management?
208 e.printStackTrace();
209 }
210 }
211 }
212
213 // listeners will be called AFTER reload
214 public void addReloadedListener(Runnable listener) {
215 reloadedListeners.add(listener);
216 }
217
218 /**
219 * Save the current value to the {@link Bundle}.
220 */
221 public void save() {
222 for (Runnable listener : saveListeners) {
223 try {
224 listener.run();
225 } catch (Exception e) {
226 // TODO: error management?
227 e.printStackTrace();
228 }
229 }
230 bundle.setString(id, value);
231 }
232
233 // listeners will be called BEFORE save
234 public void addSaveListener(Runnable listener) {
235 saveListeners.add(listener);
236 }
237
238 @Override
239 public Iterator<MetaInfo<E>> iterator() {
240 return children.iterator();
241 }
242
243 public List<MetaInfo<E>> getChildren() {
244 return children;
245 }
246
247 /**
248 * Create a list of {@link MetaInfo}, one for each of the item in the given
249 * {@link Bundle}.
250 *
251 * @param <E>
252 * the type of {@link Bundle} to edit
253 * @param type
254 * a class instance of the item type to work on
255 * @param bundle
256 * the {@link Bundle} to sort through
257 *
258 * @return the list
259 */
260 static public <E extends Enum<E>> List<MetaInfo<E>> getItems(Class<E> type,
261 Bundle<E> bundle) {
262 List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>();
263 for (E id : type.getEnumConstants()) {
264 list.add(new MetaInfo<E>(type, bundle, id));
265 }
266
267 return list;
268 }
269
270 // TODO: multiple levels?
271 static public <E extends Enum<E>> Map<MetaInfo<E>, List<MetaInfo<E>>> getGroupedItems(
272 Class<E> type, Bundle<E> bundle) {
273 Map<MetaInfo<E>, List<MetaInfo<E>>> map = new TreeMap<MetaInfo<E>, List<MetaInfo<E>>>();
274 Map<MetaInfo<E>, List<MetaInfo<E>>> map1 = new TreeMap<MetaInfo<E>, List<MetaInfo<E>>>();
275
276 List<MetaInfo<E>> ungrouped = new ArrayList<MetaInfo<E>>();
277 for (MetaInfo<E> info : getItems(type, bundle)) {
278 if (info.meta.group()) {
279 List<MetaInfo<E>> list = new ArrayList<MetaInfo<E>>();
280 map.put(info, list);
281 map1.put(info, list);
282 } else {
283 ungrouped.add(info);
284 }
285 }
286
287 for (int i = 0; i < ungrouped.size(); i++) {
288 MetaInfo<E> info = ungrouped.get(i);
289 MetaInfo<E> group = findParent(info, map.keySet());
290 if (group != null) {
291 map.get(group).add(info);
292 ungrouped.remove(i--);
293 }
294 }
295
296 if (ungrouped.size() > 0) {
297 map.put(null, ungrouped);
298 }
299
300 return map;
301 }
302
303 static private <E extends Enum<E>> MetaInfo<E> findParent(MetaInfo<E> info,
304 Set<MetaInfo<E>> candidates) {
305 MetaInfo<E> group = null;
306 for (MetaInfo<E> pcandidate : candidates) {
307 if (info.id.toString().startsWith(pcandidate.id.toString())) {
308 if (group == null
309 || group.id.toString().length() < pcandidate.id
310 .toString().length()) {
311 group = pcandidate;
312 }
313 }
314 }
315
316 return group;
317 }
318 }