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";
67 * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
70 * the {@link MetaInfo}
72 * negative horisontal gap in pixel to use for the label, i.e.,
73 * the step lock sized labels will start smaller by that amount
74 * (the use case would be to align controls that start at a
75 * different horisontal position)
77 public ConfigItem(MetaInfo
<E
> info
, int nhgap
) {
78 this.setLayout(new BorderLayout());
80 // TODO: support arrays
81 Format fmt
= info
.getFormat();
88 addBooleanField(info
, nhgap
);
91 addColorField(info
, nhgap
);
94 addBrowseField(info
, nhgap
, false);
97 addBrowseField(info
, nhgap
, true);
100 addComboboxField(info
, nhgap
, true);
103 addComboboxField(info
, nhgap
, false);
106 addIntField(info
, nhgap
);
109 addPasswordField(info
, nhgap
);
112 case LOCALE
: // TODO?
114 addStringField(info
, nhgap
);
119 private void addStringField(final MetaInfo
<E
> info
, int nhgap
) {
120 final JTextField field
= new JTextField();
121 field
.setToolTipText(info
.getDescription());
122 field
.setText(info
.getString(false));
124 info
.addReloadedListener(new Runnable() {
127 field
.setText(info
.getString(false));
130 info
.addSaveListener(new Runnable() {
133 info
.setString(field
.getText());
137 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
138 this.add(field
, BorderLayout
.CENTER
);
140 setPreferredSize(field
);
143 private void addBooleanField(final MetaInfo
<E
> info
, int nhgap
) {
144 final JCheckBox field
= new JCheckBox();
145 field
.setToolTipText(info
.getDescription());
146 Boolean state
= info
.getBoolean(true);
148 // Should not happen!
151 .println("No default value given for BOOLEAN parameter \""
152 + info
.getName() + "\", we consider it is FALSE");
156 field
.setSelected(state
);
158 info
.addReloadedListener(new Runnable() {
161 Boolean state
= info
.getBoolean(true);
166 field
.setSelected(state
);
169 info
.addSaveListener(new Runnable() {
172 info
.setBoolean(field
.isSelected());
176 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
177 this.add(field
, BorderLayout
.CENTER
);
179 setPreferredSize(field
);
182 private void addColorField(final MetaInfo
<E
> info
, int nhgap
) {
183 final JTextField field
= new JTextField();
184 field
.setToolTipText(info
.getDescription());
185 field
.setText(info
.getString(false));
187 info
.addReloadedListener(new Runnable() {
190 field
.setText(info
.getString(false));
193 info
.addSaveListener(new Runnable() {
196 info
.setString(field
.getText());
200 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
201 JPanel pane
= new JPanel(new BorderLayout());
203 final JButton colorWheel
= new JButton();
204 colorWheel
.setIcon(getIcon(17, info
.getColor(true)));
205 colorWheel
.addActionListener(new ActionListener() {
207 public void actionPerformed(ActionEvent e
) {
208 Color initialColor
= new Color(info
.getColor(true), true);
209 Color newColor
= JColorChooser
.showDialog(ConfigItem
.this,
210 info
.getName(), initialColor
);
211 if (newColor
!= null) {
212 info
.setColor(newColor
.getRGB());
213 field
.setText(info
.getString(false));
214 colorWheel
.setIcon(getIcon(17, info
.getColor(true)));
218 pane
.add(colorWheel
, BorderLayout
.WEST
);
219 pane
.add(field
, BorderLayout
.CENTER
);
220 this.add(pane
, BorderLayout
.CENTER
);
222 setPreferredSize(pane
);
225 private void addBrowseField(final MetaInfo
<E
> info
, int nhgap
,
227 final JTextField field
= new JTextField();
228 field
.setToolTipText(info
.getDescription());
229 field
.setText(info
.getString(false));
231 info
.addReloadedListener(new Runnable() {
234 field
.setText(info
.getString(false));
237 info
.addSaveListener(new Runnable() {
240 info
.setString(field
.getText());
244 JButton browseButton
= new JButton("...");
245 browseButton
.addActionListener(new ActionListener() {
247 public void actionPerformed(ActionEvent e
) {
248 JFileChooser chooser
= new JFileChooser();
249 chooser
.setCurrentDirectory(null);
250 chooser
.setFileSelectionMode(dir ? JFileChooser
.DIRECTORIES_ONLY
251 : JFileChooser
.FILES_ONLY
);
252 if (chooser
.showOpenDialog(ConfigItem
.this) == JFileChooser
.APPROVE_OPTION
) {
253 File file
= chooser
.getSelectedFile();
255 info
.setString(file
.getAbsolutePath());
256 field
.setText(info
.getString(false));
262 JPanel pane
= new JPanel(new BorderLayout());
263 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
264 pane
.add(browseButton
, BorderLayout
.WEST
);
265 pane
.add(field
, BorderLayout
.CENTER
);
266 this.add(pane
, BorderLayout
.CENTER
);
268 setPreferredSize(pane
);
271 private void addComboboxField(final MetaInfo
<E
> info
, int nhgap
,
273 // rawtypes for Java 1.6 (and 1.7 ?) support
274 @SuppressWarnings({ "rawtypes", "unchecked" })
275 final JComboBox field
= new JComboBox(info
.getAllowedValues());
276 field
.setEditable(editable
);
277 field
.setSelectedItem(info
.getString(false));
279 info
.addReloadedListener(new Runnable() {
282 field
.setSelectedItem(info
.getString(false));
285 info
.addSaveListener(new Runnable() {
288 info
.setString(field
.getSelectedItem().toString());
292 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
293 this.add(field
, BorderLayout
.CENTER
);
295 setPreferredSize(field
);
298 private void addPasswordField(final MetaInfo
<E
> info
, int nhgap
) {
299 final JPasswordField field
= new JPasswordField();
300 field
.setToolTipText(info
.getDescription());
301 field
.setText(info
.getString(true));
303 info
.addReloadedListener(new Runnable() {
306 field
.setText(info
.getString(false));
309 info
.addSaveListener(new Runnable() {
312 info
.setString(new String(field
.getPassword()));
316 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
317 this.add(field
, BorderLayout
.CENTER
);
319 setPreferredSize(field
);
322 private void addIntField(final MetaInfo
<E
> info
, int nhgap
) {
323 final JSpinner field
= new JSpinner();
324 field
.setToolTipText(info
.getDescription());
325 field
.setValue(info
.getInteger(true) == null ?
0 : info
328 info
.addReloadedListener(new Runnable() {
331 field
.setValue(info
.getInteger(true) == null ?
0 : info
335 info
.addSaveListener(new Runnable() {
338 info
.setInteger((Integer
) field
.getValue());
339 Integer value
= info
.getInteger(false);
346 this.add(label(info
, nhgap
), BorderLayout
.WEST
);
347 this.add(field
, BorderLayout
.CENTER
);
349 setPreferredSize(field
);
353 * Create a label which width is constrained in lock steps.
356 * the {@link MetaInfo} for which we want to add a label
358 * negative horisontal gap in pixel to use for the label, i.e.,
359 * the step lock sized labels will start smaller by that amount
360 * (the use case would be to align controls that start at a
361 * different horisontal position)
365 private JComponent
label(final MetaInfo
<E
> info
, int nhgap
) {
366 final JLabel label
= new JLabel(info
.getName());
368 Dimension ps
= label
.getPreferredSize();
370 ps
= label
.getSize();
375 for (int i
= 2 * step
- nhgap
; i
< 10 * step
; i
+= step
) {
382 final Runnable showInfo
= new Runnable() {
385 StringBuilder builder
= new StringBuilder();
386 String text
= (info
.getDescription().replace("\\n", "\n"))
388 for (String line
: StringUtils
.justifyText(text
, 80,
390 if (builder
.length() > 0) {
391 builder
.append("\n");
393 builder
.append(line
);
395 text
= builder
.toString();
396 JOptionPane
.showMessageDialog(ConfigItem
.this, text
,
397 info
.getName(), JOptionPane
.INFORMATION_MESSAGE
);
401 JLabel help
= new JLabel("");
402 help
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
404 Image img
= new Image(infoImage64
);
406 BufferedImage bImg
= ImageUtilsAwt
.fromImage(img
);
407 help
.setIcon(new ImageIcon(bImg
));
411 } catch (IOException e
) {
412 // This is an hard-coded image, should not happen
416 help
.addMouseListener(new MouseAdapter() {
418 public void mouseClicked(MouseEvent e
) {
423 JPanel pane2
= new JPanel(new BorderLayout());
424 pane2
.add(help
, BorderLayout
.WEST
);
425 pane2
.add(new JLabel(" "), BorderLayout
.CENTER
);
427 JPanel pane
= new JPanel(new BorderLayout());
428 pane
.add(label
, BorderLayout
.WEST
);
429 pane
.add(pane2
, BorderLayout
.CENTER
);
431 ps
.width
= w
+ 30; // 30 for the (?) sign
433 pane
.setPreferredSize(ps
);
439 * Return an {@link Icon} to use as a colour badge for the colour field
443 * the size of the badge
445 * the colour of the badge
449 private Icon
getIcon(int size
, int color
) {
450 Color c
= new Color(color
, true);
451 int avg
= (c
.getRed() + c
.getGreen() + c
.getBlue()) / 3;
452 Color border
= (avg
>= 128 ? Color
.BLACK
: Color
.WHITE
);
454 BufferedImage img
= new BufferedImage(size
, size
,
455 BufferedImage
.TYPE_4BYTE_ABGR
);
457 Graphics2D g
= img
.createGraphics();
460 g
.fillRect(0, 0, img
.getWidth(), img
.getHeight());
462 g
.drawRect(0, 0, img
.getWidth() - 1, img
.getHeight() - 1);
467 return new ImageIcon(img
);
470 private void setPreferredSize(JComponent field
) {
471 JTextField a
= new JTextField("Test");
472 int height
= Math
.max(a
.getMinimumSize().height
,
473 field
.getMinimumSize().height
);
474 setPreferredSize(new Dimension(200, height
));