ConfigItem: add all types except LOCALE (array still not working)
[nikiroo-utils.git] / src / be / nikiroo / utils / ui / ConfigItem.java
1 package be.nikiroo.utils.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.Graphics2D;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.image.BufferedImage;
10 import java.io.File;
11
12 import javax.swing.Icon;
13 import javax.swing.ImageIcon;
14 import javax.swing.InputVerifier;
15 import javax.swing.JButton;
16 import javax.swing.JCheckBox;
17 import javax.swing.JColorChooser;
18 import javax.swing.JComboBox;
19 import javax.swing.JComponent;
20 import javax.swing.JFileChooser;
21 import javax.swing.JLabel;
22 import javax.swing.JOptionPane;
23 import javax.swing.JPanel;
24 import javax.swing.JPasswordField;
25 import javax.swing.JTextField;
26 import javax.swing.plaf.basic.BasicArrowButton;
27
28 import be.nikiroo.utils.StringUtils;
29 import be.nikiroo.utils.StringUtils.Alignment;
30 import be.nikiroo.utils.resources.Bundle;
31 import be.nikiroo.utils.resources.MetaInfo;
32
33 /**
34 * A graphical item that reflect a configuration option from the given
35 * {@link Bundle}.
36 *
37 * @author niki
38 *
39 * @param <E>
40 * the type of {@link Bundle} to edit
41 */
42 public class ConfigItem<E extends Enum<E>> extends JPanel {
43 private static final long serialVersionUID = 1L;
44
45 public ConfigItem(MetaInfo<E> info) {
46 this.setLayout(new BorderLayout());
47
48 switch (info.getFormat()) {
49 case BOOLEAN:
50 addBooleanField(info);
51 break;
52 case COLOR:
53 addColorField(info);
54 break;
55 case FILE:
56 addBrowseField(info, false);
57 break;
58 case DIRECTORY:
59 addBrowseField(info, true);
60 break;
61 case COMBO_LIST:
62 addComboboxField(info, true);
63 break;
64 case FIXED_LIST:
65 addComboboxField(info, false);
66 break;
67 case INT:
68 addIntField(info);
69 break;
70 case PASSWORD:
71 addPasswordField(info);
72 break;
73 case STRING:
74 case LOCALE: // TODO?
75 default:
76 addStringField(info);
77 break;
78 }
79 }
80
81 private void addStringField(final MetaInfo<E> info) {
82 final JTextField field = new JTextField();
83 field.setToolTipText(info.getDescription());
84 field.setText(info.getString());
85
86 info.addReloadedListener(new Runnable() {
87 @Override
88 public void run() {
89 field.setText(info.getString());
90 }
91 });
92 info.addSaveListener(new Runnable() {
93 @Override
94 public void run() {
95 info.setString(field.getText());
96 }
97 });
98
99 this.add(label(info), BorderLayout.WEST);
100 this.add(field, BorderLayout.CENTER);
101 }
102
103 private void addBooleanField(final MetaInfo<E> info) {
104 final JCheckBox field = new JCheckBox();
105 field.setToolTipText(info.getDescription());
106 Boolean state = info.getBoolean();
107 if (state == null) {
108 info.getDefaultBoolean();
109 }
110
111 // Should not happen!
112 if (state == null) {
113 System.err
114 .println("No default value given for BOOLEAN parameter \""
115 + info.getName() + "\", we consider it is FALSE");
116 state = false;
117 }
118
119 field.setSelected(state);
120
121 info.addReloadedListener(new Runnable() {
122 @Override
123 public void run() {
124 Boolean state = info.getBoolean();
125 if (state == null) {
126 info.getDefaultBoolean();
127 }
128 if (state == null) {
129 state = false;
130 }
131
132 field.setSelected(state);
133 }
134 });
135 info.addSaveListener(new Runnable() {
136 @Override
137 public void run() {
138 info.setBoolean(field.isSelected());
139 }
140 });
141
142 field.setText(info.getName());
143 this.add(field, BorderLayout.CENTER);
144 }
145
146 private void addColorField(final MetaInfo<E> info) {
147 final JTextField field = new JTextField();
148 field.setToolTipText(info.getDescription());
149 field.setText(info.getString());
150
151 info.addReloadedListener(new Runnable() {
152 @Override
153 public void run() {
154 field.setText(info.getString());
155 }
156 });
157 info.addSaveListener(new Runnable() {
158 @Override
159 public void run() {
160 info.setString(field.getText());
161 }
162 });
163
164 this.add(label(info), BorderLayout.WEST);
165 JPanel pane = new JPanel(new BorderLayout());
166
167 final JButton colorWheel = new JButton();
168 colorWheel.setIcon(getIcon(17, info.getColor()));
169 colorWheel.addActionListener(new ActionListener() {
170 @Override
171 public void actionPerformed(ActionEvent e) {
172 Color initialColor = new Color(info.getColor(), true);
173 Color newColor = JColorChooser.showDialog(ConfigItem.this,
174 info.getName(), initialColor);
175 if (newColor != null) {
176 info.setColor(newColor.getRGB());
177 field.setText(info.getString());
178 colorWheel.setIcon(getIcon(17, info.getColor()));
179 }
180 }
181 });
182 pane.add(colorWheel, BorderLayout.WEST);
183 pane.add(field, BorderLayout.CENTER);
184 this.add(pane, BorderLayout.CENTER);
185 }
186
187 private void addBrowseField(final MetaInfo<E> info, final boolean dir) {
188 final JTextField field = new JTextField();
189 field.setToolTipText(info.getDescription());
190 field.setText(info.getString());
191
192 info.addReloadedListener(new Runnable() {
193 @Override
194 public void run() {
195 field.setText(info.getString());
196 }
197 });
198 info.addSaveListener(new Runnable() {
199 @Override
200 public void run() {
201 info.setString(field.getText());
202 }
203 });
204
205 JButton browseButton = new JButton("...");
206 browseButton.addActionListener(new ActionListener() {
207 @Override
208 public void actionPerformed(ActionEvent e) {
209 JFileChooser chooser = new JFileChooser();
210 chooser.setCurrentDirectory(null);
211 chooser.setFileSelectionMode(dir ? JFileChooser.DIRECTORIES_ONLY
212 : JFileChooser.FILES_ONLY);
213 if (chooser.showOpenDialog(ConfigItem.this) == JFileChooser.APPROVE_OPTION) {
214 File file = chooser.getSelectedFile();
215 if (file != null) {
216 info.setString(file.getAbsolutePath());
217 field.setText(info.getString());
218 }
219 }
220 }
221 });
222
223 JPanel pane = new JPanel(new BorderLayout());
224 this.add(label(info), BorderLayout.WEST);
225 pane.add(browseButton, BorderLayout.WEST);
226 pane.add(field, BorderLayout.CENTER);
227 this.add(pane, BorderLayout.CENTER);
228 }
229
230 private void addComboboxField(final MetaInfo<E> info, boolean editable) {
231 // rawtypes for Java 1.6 (and 1.7 ?) support
232 @SuppressWarnings({ "rawtypes", "unchecked" })
233 final JComboBox field = new JComboBox(info.getAllowedValues());
234 field.setEditable(editable);
235 field.setSelectedItem(info.getString());
236
237 info.addReloadedListener(new Runnable() {
238 @Override
239 public void run() {
240 field.setSelectedItem(info.getString());
241 }
242 });
243 info.addSaveListener(new Runnable() {
244 @Override
245 public void run() {
246 info.setString(field.getSelectedItem().toString());
247 }
248 });
249
250 this.add(label(info), BorderLayout.WEST);
251 this.add(field, BorderLayout.CENTER);
252 }
253
254 private void addPasswordField(final MetaInfo<E> info) {
255 final JPasswordField field = new JPasswordField();
256 field.setToolTipText(info.getDescription());
257 field.setText(info.getString());
258
259 info.addReloadedListener(new Runnable() {
260 @Override
261 public void run() {
262 field.setText(info.getString());
263 }
264 });
265 info.addSaveListener(new Runnable() {
266 @Override
267 public void run() {
268 info.setString(new String(field.getPassword()));
269 }
270 });
271
272 this.add(label(info), BorderLayout.WEST);
273 this.add(field, BorderLayout.CENTER);
274 }
275
276 private void addIntField(final MetaInfo<E> info) {
277 final JTextField field = new JTextField();
278 field.setToolTipText(info.getDescription());
279 field.setText(info.getString());
280 field.setInputVerifier(new InputVerifier() {
281 @Override
282 public boolean verify(JComponent input) {
283 String text = field.getText().trim();
284 if (text.startsWith("-")) {
285 text = text.substring(1).trim();
286 }
287
288 return text.replaceAll("[0-9]", "").isEmpty();
289 }
290 });
291
292 info.addReloadedListener(new Runnable() {
293 @Override
294 public void run() {
295 field.setText(info.getString());
296 }
297 });
298 info.addSaveListener(new Runnable() {
299 @Override
300 public void run() {
301 info.setString(field.getText());
302 Integer value = info.getInteger();
303 if (value == null) {
304 info.setString("");
305 } else {
306 info.setInteger(value);
307 }
308 field.setText(info.getString());
309 }
310 });
311
312 JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
313 JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
314
315 up.addActionListener(new ActionListener() {
316 @Override
317 public void actionPerformed(ActionEvent ae) {
318 int value = 0;
319 try {
320 value = Integer.parseInt(field.getText());
321 } catch (NumberFormatException e) {
322 }
323
324 field.setText(Integer.toString(value + 1));
325 }
326 });
327
328 down.addActionListener(new ActionListener() {
329 @Override
330 public void actionPerformed(ActionEvent ae) {
331 int value = 0;
332 try {
333 value = Integer.parseInt(field.getText());
334 } catch (NumberFormatException e) {
335 }
336
337 field.setText(Integer.toString(value - 1));
338 }
339 });
340
341 JPanel upDown = new JPanel(new BorderLayout());
342 upDown.add(up, BorderLayout.NORTH);
343 upDown.add(down, BorderLayout.SOUTH);
344
345 JPanel pane = new JPanel(new BorderLayout());
346 pane.add(upDown, BorderLayout.WEST);
347 pane.add(field, BorderLayout.CENTER);
348
349 this.add(label(info), BorderLayout.WEST);
350 this.add(pane, BorderLayout.CENTER);
351 }
352
353 /**
354 * Create a label which width is constrained in lock steps.
355 *
356 * @param info
357 * the {@link MetaInfo} for which we want to add a label
358 *
359 * @return the label
360 */
361 private JComponent label(final MetaInfo<E> info) {
362 final JLabel label = new JLabel(info.getName());
363
364 Dimension ps = label.getPreferredSize();
365 if (ps == null) {
366 ps = label.getSize();
367 }
368
369 int w = ps.width;
370 int step = 80;
371 for (int i = 2 * step; i < 10 * step; i += step) {
372 if (w < i) {
373 w = i;
374 break;
375 }
376 }
377
378 // TODO: image
379 JButton help = new JButton("?");
380 help.addActionListener(new ActionListener() {
381 @Override
382 public void actionPerformed(ActionEvent e) {
383 StringBuilder builder = new StringBuilder();
384 String text = info.getDescription().replace("\\n", "\n");
385 for (String line : StringUtils.justifyText(text, 80,
386 Alignment.LEFT)) {
387 if (builder.length() > 0) {
388 builder.append("\n");
389 }
390 builder.append(line);
391 }
392 text = builder.toString();
393 JOptionPane.showMessageDialog(ConfigItem.this, text);
394 }
395 });
396
397 JPanel pane2 = new JPanel(new BorderLayout());
398 pane2.add(help, BorderLayout.WEST);
399 pane2.add(new JLabel(" "), BorderLayout.CENTER);
400
401 JPanel pane = new JPanel(new BorderLayout());
402 pane.add(label, BorderLayout.WEST);
403 pane.add(pane2, BorderLayout.CENTER);
404
405 ps.width = w + 30; // 30 for the (?) sign
406 pane.setSize(ps);
407 pane.setPreferredSize(ps);
408
409 return pane;
410 }
411
412 /**
413 * Return an {@link Icon} to use as a colour badge for the colour field
414 * controls.
415 *
416 * @param size
417 * the size of the badge
418 * @param color
419 * the colour of the badge
420 *
421 * @return the badge
422 */
423 private Icon getIcon(int size, int color) {
424 Color c = new Color(color, true);
425 int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3;
426 Color border = (avg >= 128 ? Color.BLACK : Color.WHITE);
427
428 BufferedImage img = new BufferedImage(size, size,
429 BufferedImage.TYPE_4BYTE_ABGR);
430
431 Graphics2D g = img.createGraphics();
432 try {
433 g.setColor(c);
434 g.fillRect(0, 0, img.getWidth(), img.getHeight());
435 g.setColor(border);
436 g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1);
437 } finally {
438 g.dispose();
439 }
440
441 return new ImageIcon(img);
442 }
443 }