X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=04bf76c04683291067ffb6671132e225ed7afbe7;hb=59654e2ab1f6d3314eff438bf9e30ed6f32e5e74;hp=95de8361f7880428e6cb3201fe48e58eea060dd4;hpb=0877d6f5485d3531b9fde6c264e5848630c80baf;p=fanfix.git diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 95de836..04bf76c 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -2,16 +2,19 @@ package be.nikiroo.utils.ui; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; import javax.swing.Icon; import javax.swing.ImageIcon; -import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; @@ -22,17 +25,23 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; +import javax.swing.JSpinner; import javax.swing.JTextField; -import javax.swing.plaf.basic.BasicArrowButton; +import be.nikiroo.utils.Image; import be.nikiroo.utils.StringUtils; import be.nikiroo.utils.StringUtils.Alignment; import be.nikiroo.utils.resources.Bundle; +import be.nikiroo.utils.resources.Meta.Format; import be.nikiroo.utils.resources.MetaInfo; /** * A graphical item that reflect a configuration option from the given * {@link Bundle}. + *

+ * This graphical item can be edited, and the result will be saved back into the + * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should + * you wish to, of course. * * @author niki * @@ -42,71 +51,132 @@ import be.nikiroo.utils.resources.MetaInfo; public class ConfigItem> extends JPanel { private static final long serialVersionUID = 1L; - public ConfigItem(MetaInfo info) { + private static int minimumHeight = -1; + + /** A small (?) blue in PNG, base64 encoded. */ + private static String infoImage64 = // + "" + + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" + + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G" + + "m1RDogYxq7WDDYMYTSajSG4n6YRYzSaSLibWbiaDIGwdiLIYDFKDNJEgKu969xi8UNHy7H7LPcN3" + + "v/Odcy+hG9oOIeIcBCJS9MAvlZtOMtHxsrFrJHGqe0RVGnHAHpcIbPlng8BS3HmKBJYzabGUzcrJ" + + "XK+ckIrqANYR2JEv2nYDEVck0WKGfHzyq82Go+btxoX3XAcAIqTj8wPqOH6mtMeM4bGCLhyfhTMA" + + "qlLhKHqujCfaweCAmV0p50dPzsNpEKpK01V/n55HIvTnfDC2odKlfeYadZN/T+AqDACUsnkhqaU1" + + "LRIVuX1x7ciuSWQxVIrunONrfq3dI6oh+T94Z8453vEem/HTqT8ZpFJ0qDXtGkPbAGAMeSRngQCA" + + "eUvgn195AwlZWyvjtQdhAAAAAElFTkSuQmCC"; + + /** The original value before current changes. */ + private Object orig; + + /** + * Create a new {@link ConfigItem} for the given {@link MetaInfo}. + * + * @param info + * the {@link MetaInfo} + * @param nhgap + * negative horisontal gap in pixel to use for the label, i.e., + * the step lock sized labels will start smaller by that amount + * (the use case would be to align controls that start at a + * different horisontal position) + */ + public ConfigItem(MetaInfo info, int nhgap) { this.setLayout(new BorderLayout()); - switch (info.getFormat()) { + // TODO: support arrays + Format fmt = info.getFormat(); + if (info.isArray()) { + fmt = Format.STRING; + } + + switch (fmt) { case BOOLEAN: - addBooleanField(info); + addBooleanField(info, nhgap); break; case COLOR: - addColorField(info); + addColorField(info, nhgap); break; case FILE: - addBrowseField(info, false); + addBrowseField(info, nhgap, false); break; case DIRECTORY: - addBrowseField(info, true); + addBrowseField(info, nhgap, true); break; case COMBO_LIST: - addComboboxField(info, true); + addComboboxField(info, nhgap, true); break; case FIXED_LIST: - addComboboxField(info, false); + addComboboxField(info, nhgap, false); break; case INT: - addIntField(info); + addIntField(info, nhgap); break; case PASSWORD: - addPasswordField(info); + addPasswordField(info, nhgap); break; case STRING: case LOCALE: // TODO? default: - addStringField(info); + addStringField(info, nhgap); break; } } - private void addStringField(final MetaInfo info) { + private void reload(Object value) { + // We consider "" and NULL to be equals + if ("".equals(value)) { + value = null; + } + orig = value; + } + + private boolean isChanged(Object newValue) { + // We consider "" and NULL to be equals + if ("".equals(newValue)) { + newValue = null; + } + + if (newValue == null) { + return orig != null; + } + + return !newValue.equals(orig); + } + + private void addStringField(final MetaInfo info, int nhgap) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getText()); + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addBooleanField(final MetaInfo info) { + private void addBooleanField(final MetaInfo info, int nhgap) { final JCheckBox field = new JCheckBox(); field.setToolTipText(info.getDescription()); - Boolean state = info.getBoolean(); - if (state == null) { - info.getDefaultBoolean(); - } + Boolean state = info.getBoolean(true); // Should not happen! if (state == null) { @@ -116,89 +186,114 @@ public class ConfigItem> extends JPanel { state = false; } + reload(state); field.setSelected(state); info.addReloadedListener(new Runnable() { @Override public void run() { - Boolean state = info.getBoolean(); - if (state == null) { - info.getDefaultBoolean(); - } + Boolean state = info.getBoolean(true); if (state == null) { state = false; } + reload(state); field.setSelected(state); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setBoolean(field.isSelected()); + boolean state = field.isSelected(); + if (isChanged(state)) { + info.setBoolean(state); + } } }); - field.setText(info.getName()); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addColorField(final MetaInfo info) { + private void addColorField(final MetaInfo info, int nhgap) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getText()); + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); JPanel pane = new JPanel(new BorderLayout()); final JButton colorWheel = new JButton(); - colorWheel.setIcon(getIcon(17, info.getColor())); + colorWheel.setIcon(getIcon(17, info.getColor(true))); colorWheel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Color initialColor = new Color(info.getColor(), true); + Integer icol = info.getColor(true); + if (icol == null) { + icol = new Color(255, 255, 255, 255).getRGB(); + } + Color initialColor = new Color(icol, true); Color newColor = JColorChooser.showDialog(ConfigItem.this, info.getName(), initialColor); if (newColor != null) { info.setColor(newColor.getRGB()); - field.setText(info.getString()); - colorWheel.setIcon(getIcon(17, info.getColor())); + field.setText(info.getString(false)); + colorWheel.setIcon(getIcon(17, info.getColor(true))); } } }); pane.add(colorWheel, BorderLayout.WEST); pane.add(field, BorderLayout.CENTER); this.add(pane, BorderLayout.CENTER); + + setPreferredSize(pane); } - private void addBrowseField(final MetaInfo info, final boolean dir) { + private void addBrowseField(final MetaInfo info, int nhgap, + final boolean dir) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getText()); + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } } }); @@ -213,141 +308,122 @@ public class ConfigItem> extends JPanel { if (chooser.showOpenDialog(ConfigItem.this) == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); if (file != null) { - info.setString(file.getAbsolutePath()); - field.setText(info.getString()); + String value = file.getAbsolutePath(); + if (isChanged(value)) { + info.setString(value); + } + field.setText(value); } } } }); JPanel pane = new JPanel(new BorderLayout()); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); pane.add(browseButton, BorderLayout.WEST); pane.add(field, BorderLayout.CENTER); this.add(pane, BorderLayout.CENTER); + + setPreferredSize(pane); } - private void addComboboxField(final MetaInfo info, boolean editable) { + private void addComboboxField(final MetaInfo info, int nhgap, + boolean editable) { // rawtypes for Java 1.6 (and 1.7 ?) support @SuppressWarnings({ "rawtypes", "unchecked" }) final JComboBox field = new JComboBox(info.getAllowedValues()); field.setEditable(editable); - field.setSelectedItem(info.getString()); + String value = info.getString(false); + reload(value); + field.setSelectedItem(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setSelectedItem(info.getString()); + String value = info.getString(false); + reload(value); + field.setSelectedItem(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getSelectedItem().toString()); + Object item = field.getSelectedItem(); + String value = item == null ? null : item.toString(); + if (isChanged(value)) { + info.setString(value); + } } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addPasswordField(final MetaInfo info) { + private void addPasswordField(final MetaInfo info, int nhgap) { final JPasswordField field = new JPasswordField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + String value = info.getString(false); + reload(value); + field.setText(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(new String(field.getPassword())); + String value = new String(field.getPassword()); + if (isChanged(value)) { + info.setString(value); + } } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addIntField(final MetaInfo info) { - final JTextField field = new JTextField(); + private void addIntField(final MetaInfo info, int nhgap) { + final JSpinner field = new JSpinner(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); - field.setInputVerifier(new InputVerifier() { - @Override - public boolean verify(JComponent input) { - String text = field.getText().trim(); - if (text.startsWith("-")) { - text = text.substring(1).trim(); - } - - return text.replaceAll("[0-9]", "").isEmpty(); - } - }); + int value = info.getInteger(true) == null ? 0 : info.getInteger(true); + reload(value); + field.setValue(value); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + int value = info.getInteger(true) == null ? 0 : info + .getInteger(true); + reload(value); + field.setValue(value); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getText()); - Integer value = info.getInteger(); - if (value == null) { - info.setString(""); - } else { + int value = field.getValue() == null ? 0 : (Integer) field + .getValue(); + if (isChanged(value)) { info.setInteger(value); } - field.setText(info.getString()); - } - }); - - JButton up = new BasicArrowButton(BasicArrowButton.NORTH); - JButton down = new BasicArrowButton(BasicArrowButton.SOUTH); - - up.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - int value = 0; - try { - value = Integer.parseInt(field.getText()); - } catch (NumberFormatException e) { - } - - field.setText(Integer.toString(value + 1)); - } - }); - - down.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - int value = 0; - try { - value = Integer.parseInt(field.getText()); - } catch (NumberFormatException e) { - } - - field.setText(Integer.toString(value - 1)); } }); - JPanel upDown = new JPanel(new BorderLayout()); - upDown.add(up, BorderLayout.NORTH); - upDown.add(down, BorderLayout.SOUTH); - - JPanel pane = new JPanel(new BorderLayout()); - pane.add(upDown, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); - this.add(label(info), BorderLayout.WEST); - this.add(pane, BorderLayout.CENTER); + setPreferredSize(field); } /** @@ -355,10 +431,15 @@ public class ConfigItem> extends JPanel { * * @param info * the {@link MetaInfo} for which we want to add a label + * @param nhgap + * negative horisontal gap in pixel to use for the label, i.e., + * the step lock sized labels will start smaller by that amount + * (the use case would be to align controls that start at a + * different horisontal position) * * @return the label */ - private JComponent label(final MetaInfo info) { + private JComponent label(final MetaInfo info, int nhgap) { final JLabel label = new JLabel(info.getName()); Dimension ps = label.getPreferredSize(); @@ -366,22 +447,23 @@ public class ConfigItem> extends JPanel { ps = label.getSize(); } + ps.height = Math.max(ps.height, getMinimumHeight()); + int w = ps.width; - int step = 80; - for (int i = 2 * step; i < 10 * step; i += step) { + int step = 150; + for (int i = 2 * step - nhgap; i < 10 * step; i += step) { if (w < i) { w = i; break; } } - // TODO: image - JButton help = new JButton("?"); - help.addActionListener(new ActionListener() { + final Runnable showInfo = new Runnable() { @Override - public void actionPerformed(ActionEvent e) { + public void run() { StringBuilder builder = new StringBuilder(); - String text = info.getDescription().replace("\\n", "\n"); + String text = (info.getDescription().replace("\\n", "\n")) + .trim(); for (String line : StringUtils.justifyText(text, 80, Alignment.LEFT)) { if (builder.length() > 0) { @@ -390,7 +472,30 @@ public class ConfigItem> extends JPanel { builder.append(line); } text = builder.toString(); - JOptionPane.showMessageDialog(ConfigItem.this, text); + JOptionPane.showMessageDialog(ConfigItem.this, text, + info.getName(), JOptionPane.INFORMATION_MESSAGE); + } + }; + + JLabel help = new JLabel(""); + help.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + try { + Image img = new Image(infoImage64); + try { + BufferedImage bImg = ImageUtilsAwt.fromImage(img); + help.setIcon(new ImageIcon(bImg)); + } finally { + img.close(); + } + } catch (IOException e) { + // This is an hard-coded image, should not happen + help.setText("?"); + } + + help.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + showInfo.run(); } }); @@ -398,13 +503,16 @@ public class ConfigItem> extends JPanel { pane2.add(help, BorderLayout.WEST); pane2.add(new JLabel(" "), BorderLayout.CENTER); - JPanel pane = new JPanel(new BorderLayout()); - pane.add(label, BorderLayout.WEST); - pane.add(pane2, BorderLayout.CENTER); + JPanel contentPane = new JPanel(new BorderLayout()); + contentPane.add(label, BorderLayout.WEST); + contentPane.add(pane2, BorderLayout.CENTER); ps.width = w + 30; // 30 for the (?) sign - pane.setSize(ps); - pane.setPreferredSize(ps); + contentPane.setSize(ps); + contentPane.setPreferredSize(ps); + + JPanel pane = new JPanel(new BorderLayout()); + pane.add(contentPane, BorderLayout.NORTH); return pane; } @@ -416,11 +524,17 @@ public class ConfigItem> extends JPanel { * @param size * the size of the badge * @param color - * the colour of the badge + * the colour of the badge, which can be NULL (will return + * transparent white) * * @return the badge */ - private Icon getIcon(int size, int color) { + private Icon getIcon(int size, Integer color) { + // Allow null values + if (color == null) { + color = new Color(255, 255, 255, 255).getRGB(); + } + Color c = new Color(color, true); int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3; Color border = (avg >= 128 ? Color.BLACK : Color.WHITE); @@ -440,4 +554,18 @@ public class ConfigItem> extends JPanel { return new ImageIcon(img); } + + private void setPreferredSize(JComponent field) { + int height = Math + .max(getMinimumHeight(), field.getMinimumSize().height); + setPreferredSize(new Dimension(200, height)); + } + + static private int getMinimumHeight() { + if (minimumHeight < 0) { + minimumHeight = new JTextField("Test").getMinimumSize().height; + } + + return minimumHeight; + } }