14348b6dd4c1ceab382fdb6e634013e6592bb1fa
1 package be
.nikiroo
.utils
.ui
;
3 import java
.awt
.BorderLayout
;
5 import java
.awt
.Cursor
;
6 import java
.awt
.Dimension
;
7 import java
.awt
.Graphics2D
;
8 import java
.awt
.event
.ActionEvent
;
9 import java
.awt
.event
.ActionListener
;
10 import java
.awt
.event
.MouseAdapter
;
11 import java
.awt
.event
.MouseEvent
;
12 import java
.awt
.image
.BufferedImage
;
14 import java
.io
.IOException
;
16 import javax
.swing
.Icon
;
17 import javax
.swing
.ImageIcon
;
18 import javax
.swing
.JButton
;
19 import javax
.swing
.JCheckBox
;
20 import javax
.swing
.JColorChooser
;
21 import javax
.swing
.JComboBox
;
22 import javax
.swing
.JComponent
;
23 import javax
.swing
.JFileChooser
;
24 import javax
.swing
.JLabel
;
25 import javax
.swing
.JOptionPane
;
26 import javax
.swing
.JPanel
;
27 import javax
.swing
.JPasswordField
;
28 import javax
.swing
.JSpinner
;
29 import javax
.swing
.JTextField
;
31 import be
.nikiroo
.utils
.Image
;
32 import be
.nikiroo
.utils
.StringUtils
;
33 import be
.nikiroo
.utils
.StringUtils
.Alignment
;
34 import be
.nikiroo
.utils
.resources
.Bundle
;
35 import be
.nikiroo
.utils
.resources
.Meta
.Format
;
36 import be
.nikiroo
.utils
.resources
.MetaInfo
;
39 * A graphical item that reflect a configuration option from the given
42 * This graphical item can be edited, and the result will be saved back into the
43 * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should
44 * you wish to, of course.
49 * the type of {@link Bundle} to edit
51 public class ConfigItem
<E
extends Enum
<E
>> extends JPanel
{
52 private static final long serialVersionUID
= 1L;
54 /** A small (?) blue in PNG, base64 encoded. */
55 private static String infoImage64
= //
57 + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI"
58 + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G"
59 + "m1RDogYxq7WDDYMYTSajSG4n6YRYzSaSLibWbiaDIGwdiLIYDFKDNJEgKu969xi8UNHy7H7LPcN3"
60 + "v/Odcy+hG9oOIeIcBCJS9MAvlZtOMtHxsrFrJHGqe0RVGnHAHpcIbPlng8BS3HmKBJYzabGUzcrJ"
61 + "XK+ckIrqANYR2JEv2nYDEVck0WKGfHzyq82Go+btxoX3XAcAIqTj8wPqOH6mtMeM4bGCLhyfhTMA"
62 + "qlLhKHqujCfaweCAmV0p50dPzsNpEKpK01V/n55HIvTnfDC2odKlfeYadZN/T+AqDACUsnkhqaU1"
63 + "LRIVuX1x7ciuSWQxVIrunONrfq3dI6oh+T94Z8453vEem/HTqT8ZpFJ0qDXtGkPbAGAMeSRngQCA"
64 + "eUvgn195AwlZWyvjtQdhAAAAAElFTkSuQmCC";
66 /** The original value before current changes. */
70 * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
73 * the {@link MetaInfo}
75 * negative horisontal gap in pixel to use for the label, i.e.,
76 * the step lock sized labels will start smaller by that amount
77 * (the use case would be to align controls that start at a
78 * different horisontal position)
80 public ConfigItem(MetaInfo
<E
> info
, int nhgap
) {
81 this.setLayout(new BorderLayout());
83 // TODO: support arrays
84 Format fmt
= info
.getFormat();
91 addBooleanField(info
, nhgap
);
94 addColorField(info
, nhgap
);
97 addBrowseField(info
, nhgap
, false);
100 addBrowseField(info
, nhgap
, true);
103 addComboboxField(info
, nhgap
, true);
106 addComboboxField(info
, nhgap
, false);
109 addIntField(info
, nhgap
);
112 addPasswordField(info
, nhgap
);
115 case LOCALE
: // TODO?
117 addStringField(info
, nhgap
);
122 private void reload(Object value
) {
123 // We consider "" and NULL to be equals
124 if ("".equals(value
)) {
130 private boolean isChanged(Object newValue
) {
131 // We consider "" and NULL to be equals
132 if ("".equals(newValue
)) {
136 if (newValue
== null) {
140 return !newValue
.equals(orig
);
143 private void addStringField(final MetaInfo
<E
> info
, int nhgap
) {
144 final JTextField field
= new JTextField();
145 field
.setToolTipText(info
.getDescription());
146 String value
= info
.getString(false);
148 field
.setText(value
);
150 info
.addReloadedListener(new Runnable() {
153 String value
= info
.getString(false);
155 field
.setText(value
);
158 info
.addSaveListener(new Runnable() {
161 String value
= field
.getText();
162 if (isChanged(value
)) {
163 info
.setString(value
);
168 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
169 this.add(field
, BorderLayout
.CENTER
);
171 setPreferredSize(field
);
174 private void addBooleanField(final MetaInfo
<E
> info
, int nhgap
) {
175 final JCheckBox field
= new JCheckBox();
176 field
.setToolTipText(info
.getDescription());
177 Boolean state
= info
.getBoolean(true);
179 // Should not happen!
182 .println("No default value given for BOOLEAN parameter \""
183 + info
.getName() + "\", we consider it is FALSE");
188 field
.setSelected(state
);
190 info
.addReloadedListener(new Runnable() {
193 Boolean state
= info
.getBoolean(true);
199 field
.setSelected(state
);
202 info
.addSaveListener(new Runnable() {
205 boolean state
= field
.isSelected();
206 if (isChanged(state
)) {
207 info
.setBoolean(state
);
212 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
213 this.add(field
, BorderLayout
.CENTER
);
215 setPreferredSize(field
);
218 private void addColorField(final MetaInfo
<E
> info
, int nhgap
) {
219 final JTextField field
= new JTextField();
220 field
.setToolTipText(info
.getDescription());
221 String value
= info
.getString(false);
223 field
.setText(value
);
225 info
.addReloadedListener(new Runnable() {
228 String value
= info
.getString(false);
230 field
.setText(value
);
233 info
.addSaveListener(new Runnable() {
236 String value
= field
.getText();
237 if (isChanged(value
)) {
238 info
.setString(value
);
243 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
244 JPanel pane
= new JPanel(new BorderLayout());
246 final JButton colorWheel
= new JButton();
247 colorWheel
.setIcon(getIcon(17, info
.getColor(true)));
248 colorWheel
.addActionListener(new ActionListener() {
250 public void actionPerformed(ActionEvent e
) {
251 Integer icol
= info
.getColor(true);
253 icol
= new Color(255, 255, 255, 255).getRGB();
255 Color initialColor
= new Color(icol
, true);
256 Color newColor
= JColorChooser
.showDialog(ConfigItem
.this,
257 info
.getName(), initialColor
);
258 if (newColor
!= null) {
259 info
.setColor(newColor
.getRGB());
260 field
.setText(info
.getString(false));
261 colorWheel
.setIcon(getIcon(17, info
.getColor(true)));
265 pane
.add(colorWheel
, BorderLayout
.WEST
);
266 pane
.add(field
, BorderLayout
.CENTER
);
267 this.add(pane
, BorderLayout
.CENTER
);
269 setPreferredSize(pane
);
272 private void addBrowseField(final MetaInfo
<E
> info
, int nhgap
,
274 final JTextField field
= new JTextField();
275 field
.setToolTipText(info
.getDescription());
276 String value
= info
.getString(false);
278 field
.setText(value
);
280 info
.addReloadedListener(new Runnable() {
283 String value
= info
.getString(false);
285 field
.setText(value
);
288 info
.addSaveListener(new Runnable() {
291 String value
= field
.getText();
292 if (isChanged(value
)) {
293 info
.setString(value
);
298 JButton browseButton
= new JButton("...");
299 browseButton
.addActionListener(new ActionListener() {
301 public void actionPerformed(ActionEvent e
) {
302 JFileChooser chooser
= new JFileChooser();
303 chooser
.setCurrentDirectory(null);
304 chooser
.setFileSelectionMode(dir ? JFileChooser
.DIRECTORIES_ONLY
305 : JFileChooser
.FILES_ONLY
);
306 if (chooser
.showOpenDialog(ConfigItem
.this) == JFileChooser
.APPROVE_OPTION
) {
307 File file
= chooser
.getSelectedFile();
309 String value
= file
.getAbsolutePath();
310 if (isChanged(value
)) {
311 info
.setString(value
);
313 field
.setText(value
);
319 JPanel pane
= new JPanel(new BorderLayout());
320 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
321 pane
.add(browseButton
, BorderLayout
.WEST
);
322 pane
.add(field
, BorderLayout
.CENTER
);
323 this.add(pane
, BorderLayout
.CENTER
);
325 setPreferredSize(pane
);
328 private void addComboboxField(final MetaInfo
<E
> info
, int nhgap
,
330 // rawtypes for Java 1.6 (and 1.7 ?) support
331 @SuppressWarnings({ "rawtypes", "unchecked" })
332 final JComboBox field
= new JComboBox(info
.getAllowedValues());
333 field
.setEditable(editable
);
334 String value
= info
.getString(false);
336 field
.setSelectedItem(value
);
338 info
.addReloadedListener(new Runnable() {
341 String value
= info
.getString(false);
343 field
.setSelectedItem(value
);
346 info
.addSaveListener(new Runnable() {
349 String value
= field
.getSelectedItem().toString();
350 if (isChanged(value
)) {
351 info
.setString(value
);
356 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
357 this.add(field
, BorderLayout
.CENTER
);
359 setPreferredSize(field
);
362 private void addPasswordField(final MetaInfo
<E
> info
, int nhgap
) {
363 final JPasswordField field
= new JPasswordField();
364 field
.setToolTipText(info
.getDescription());
365 String value
= info
.getString(false);
367 field
.setText(value
);
369 info
.addReloadedListener(new Runnable() {
372 String value
= info
.getString(false);
374 field
.setText(value
);
377 info
.addSaveListener(new Runnable() {
380 String value
= new String(field
.getPassword());
381 if (isChanged(value
)) {
382 info
.setString(value
);
387 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
388 this.add(field
, BorderLayout
.CENTER
);
390 setPreferredSize(field
);
393 private void addIntField(final MetaInfo
<E
> info
, int nhgap
) {
394 final JSpinner field
= new JSpinner();
395 field
.setToolTipText(info
.getDescription());
396 int value
= info
.getInteger(true) == null ?
0 : info
.getInteger(true);
398 field
.setValue(value
);
400 info
.addReloadedListener(new Runnable() {
403 int value
= info
.getInteger(true) == null ?
0 : info
406 field
.setValue(value
);
409 info
.addSaveListener(new Runnable() {
412 int value
= field
.getValue() == null ?
0 : (Integer
) field
414 if (isChanged(value
)) {
415 info
.setInteger(value
);
420 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
421 this.add(field
, BorderLayout
.CENTER
);
423 setPreferredSize(field
);
427 * Create a label which width is constrained in lock steps.
430 * the {@link MetaInfo} for which we want to add a label
432 * negative horisontal gap in pixel to use for the label, i.e.,
433 * the step lock sized labels will start smaller by that amount
434 * (the use case would be to align controls that start at a
435 * different horisontal position)
439 private JComponent
label(final MetaInfo
<E
> info
, int nhgap
) {
440 final JLabel label
= new JLabel(info
.getName());
442 Dimension ps
= label
.getPreferredSize();
444 ps
= label
.getSize();
449 for (int i
= 2 * step
- nhgap
; i
< 10 * step
; i
+= step
) {
456 final Runnable showInfo
= new Runnable() {
459 StringBuilder builder
= new StringBuilder();
460 String text
= (info
.getDescription().replace("\\n", "\n"))
462 for (String line
: StringUtils
.justifyText(text
, 80,
464 if (builder
.length() > 0) {
465 builder
.append("\n");
467 builder
.append(line
);
469 text
= builder
.toString();
470 JOptionPane
.showMessageDialog(ConfigItem
.this, text
,
471 info
.getName(), JOptionPane
.INFORMATION_MESSAGE
);
475 JLabel help
= new JLabel("");
476 help
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
478 Image img
= new Image(infoImage64
);
480 BufferedImage bImg
= ImageUtilsAwt
.fromImage(img
);
481 help
.setIcon(new ImageIcon(bImg
));
485 } catch (IOException e
) {
486 // This is an hard-coded image, should not happen
490 help
.addMouseListener(new MouseAdapter() {
492 public void mouseClicked(MouseEvent e
) {
497 JPanel pane2
= new JPanel(new BorderLayout());
498 pane2
.add(help
, BorderLayout
.WEST
);
499 pane2
.add(new JLabel(" "), BorderLayout
.CENTER
);
501 JPanel pane
= new JPanel(new BorderLayout());
502 pane
.add(label
, BorderLayout
.WEST
);
503 pane
.add(pane2
, BorderLayout
.CENTER
);
505 ps
.width
= w
+ 30; // 30 for the (?) sign
507 pane
.setPreferredSize(ps
);
513 * Return an {@link Icon} to use as a colour badge for the colour field
517 * the size of the badge
519 * the colour of the badge, which can be NULL (will return
524 private Icon
getIcon(int size
, Integer color
) {
527 color
= new Color(255, 255, 255, 255).getRGB();
530 Color c
= new Color(color
, true);
531 int avg
= (c
.getRed() + c
.getGreen() + c
.getBlue()) / 3;
532 Color border
= (avg
>= 128 ? Color
.BLACK
: Color
.WHITE
);
534 BufferedImage img
= new BufferedImage(size
, size
,
535 BufferedImage
.TYPE_4BYTE_ABGR
);
537 Graphics2D g
= img
.createGraphics();
540 g
.fillRect(0, 0, img
.getWidth(), img
.getHeight());
542 g
.drawRect(0, 0, img
.getWidth() - 1, img
.getHeight() - 1);
547 return new ImageIcon(img
);
550 private void setPreferredSize(JComponent field
) {
551 JTextField a
= new JTextField("Test");
552 int height
= Math
.max(a
.getMinimumSize().height
,
553 field
.getMinimumSize().height
);
554 setPreferredSize(new Dimension(200, height
));