Commit | Line | Data |
---|---|---|
d350b96b NR |
1 | package be.nikiroo.utils.ui; |
2 | ||
3 | import java.awt.BorderLayout; | |
424dcb0d | 4 | import java.awt.Color; |
c637d2e0 | 5 | import java.awt.Dimension; |
424dcb0d NR |
6 | import java.awt.Graphics2D; |
7 | import java.awt.event.ActionEvent; | |
8 | import java.awt.event.ActionListener; | |
9 | import java.awt.image.BufferedImage; | |
0877d6f5 | 10 | import java.io.File; |
424dcb0d NR |
11 | |
12 | import javax.swing.Icon; | |
13 | import javax.swing.ImageIcon; | |
0877d6f5 | 14 | import javax.swing.InputVerifier; |
424dcb0d | 15 | import javax.swing.JButton; |
9e834013 | 16 | import javax.swing.JCheckBox; |
424dcb0d | 17 | import javax.swing.JColorChooser; |
0877d6f5 NR |
18 | import javax.swing.JComboBox; |
19 | import javax.swing.JComponent; | |
20 | import javax.swing.JFileChooser; | |
8517b60c | 21 | import javax.swing.JLabel; |
0877d6f5 | 22 | import javax.swing.JOptionPane; |
d350b96b | 23 | import javax.swing.JPanel; |
0877d6f5 | 24 | import javax.swing.JPasswordField; |
d350b96b | 25 | import javax.swing.JTextField; |
0877d6f5 | 26 | import javax.swing.plaf.basic.BasicArrowButton; |
d350b96b | 27 | |
0877d6f5 NR |
28 | import be.nikiroo.utils.StringUtils; |
29 | import be.nikiroo.utils.StringUtils.Alignment; | |
d350b96b | 30 | import be.nikiroo.utils.resources.Bundle; |
9e834013 | 31 | import be.nikiroo.utils.resources.MetaInfo; |
d350b96b NR |
32 | |
33 | /** | |
34 | * A graphical item that reflect a configuration option from the given | |
35 | * {@link Bundle}. | |
36 | * | |
37 | * @author niki | |
db31c358 | 38 | * |
d350b96b NR |
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; | |
db31c358 | 44 | |
0877d6f5 | 45 | public ConfigItem(MetaInfo<E> info) { |
d350b96b | 46 | this.setLayout(new BorderLayout()); |
d350b96b | 47 | |
0877d6f5 NR |
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 | } | |
d350b96b | 80 | |
0877d6f5 NR |
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()); | |
9e834013 | 96 | } |
0877d6f5 | 97 | }); |
d350b96b | 98 | |
0877d6f5 NR |
99 | this.add(label(info), BorderLayout.WEST); |
100 | this.add(field, BorderLayout.CENTER); | |
101 | } | |
9e834013 | 102 | |
0877d6f5 NR |
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 | } | |
8517b60c | 110 | |
0877d6f5 NR |
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(); | |
8517b60c | 127 | } |
0877d6f5 NR |
128 | if (state == null) { |
129 | state = false; | |
9e834013 | 130 | } |
0877d6f5 NR |
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()); | |
424dcb0d | 177 | field.setText(info.getString()); |
0877d6f5 | 178 | colorWheel.setIcon(getIcon(17, info.getColor())); |
424dcb0d | 179 | } |
0877d6f5 NR |
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()); | |
424dcb0d | 217 | field.setText(info.getString()); |
424dcb0d NR |
218 | } |
219 | } | |
0877d6f5 NR |
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(); | |
9e834013 | 286 | } |
0877d6f5 NR |
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); | |
8517b60c | 307 | } |
0877d6f5 NR |
308 | field.setText(info.getString()); |
309 | } | |
310 | }); | |
9e834013 | 311 | |
0877d6f5 NR |
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); | |
d350b96b | 351 | } |
c637d2e0 NR |
352 | |
353 | /** | |
354 | * Create a label which width is constrained in lock steps. | |
355 | * | |
0877d6f5 NR |
356 | * @param info |
357 | * the {@link MetaInfo} for which we want to add a label | |
c637d2e0 NR |
358 | * |
359 | * @return the label | |
360 | */ | |
0877d6f5 NR |
361 | private JComponent label(final MetaInfo<E> info) { |
362 | final JLabel label = new JLabel(info.getName()); | |
c637d2e0 NR |
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 | ||
0877d6f5 NR |
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); | |
c637d2e0 | 408 | |
0877d6f5 | 409 | return pane; |
c637d2e0 | 410 | } |
424dcb0d NR |
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 | } | |
d350b96b | 443 | } |