X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=04bf76c04683291067ffb6671132e225ed7afbe7;hb=59654e2ab1f6d3314eff438bf9e30ed6f32e5e74;hp=8545485d0dc2a98196796bb2ec305c502acb2755;hpb=db31c35860081535d6e7ddc83ab4af573bb0522e;p=fanfix.git diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 8545485..04bf76c 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -1,21 +1,47 @@ package be.nikiroo.utils.ui; import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; -import java.util.ArrayList; -import java.util.List; +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.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; 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.border.EmptyBorder; +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; +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 * @@ -24,92 +50,522 @@ import be.nikiroo.utils.resources.Meta; */ public class ConfigItem> extends JPanel { private static final long serialVersionUID = 1L; - private Class type; - private final Bundle bundle; - private final E id; - private Meta meta; - private String value; + private static int minimumHeight = -1; - private JTextField valueField; + /** 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"; - public ConfigItem(Class type, Bundle bundle, E id) { - this.type = type; - this.bundle = bundle; - this.id = id; + /** The original value before current changes. */ + private Object orig; - try { - this.meta = type.getDeclaredField(id.name()).getAnnotation( - Meta.class); - } catch (NoSuchFieldException e) { - } catch (SecurityException e) { + /** + * 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()); + + // TODO: support arrays + Format fmt = info.getFormat(); + if (info.isArray()) { + fmt = Format.STRING; } - this.setLayout(new BorderLayout()); - this.setBorder(new EmptyBorder(2, 10, 2, 10)); + switch (fmt) { + case BOOLEAN: + addBooleanField(info, nhgap); + break; + case COLOR: + addColorField(info, nhgap); + break; + case FILE: + addBrowseField(info, nhgap, false); + break; + case DIRECTORY: + addBrowseField(info, nhgap, true); + break; + case COMBO_LIST: + addComboboxField(info, nhgap, true); + break; + case FIXED_LIST: + addComboboxField(info, nhgap, false); + break; + case INT: + addIntField(info, nhgap); + break; + case PASSWORD: + addPasswordField(info, nhgap); + break; + case STRING: + case LOCALE: // TODO? + default: + addStringField(info, nhgap); + break; + } + } - String tooltip = null; - if (bundle.getDescriptionBundle() != null) { - tooltip = bundle.getDescriptionBundle().getString(id); - if (tooltip.trim().isEmpty()) { - tooltip = null; - } + private void reload(Object value) { + // We consider "" and NULL to be equals + if ("".equals(value)) { + value = null; } + orig = value; + } - String name = id.toString(); - if (name.length() > 1) { - name = name.substring(0, 1) + name.substring(1).toLowerCase(); - name = name.replace("_", " "); + private boolean isChanged(Object newValue) { + // We consider "" and NULL to be equals + if ("".equals(newValue)) { + newValue = null; } - JLabel nameLabel = new JLabel(name); - nameLabel.setToolTipText(tooltip); - nameLabel.setPreferredSize(new Dimension(400, 0)); - this.add(nameLabel, BorderLayout.WEST); + if (newValue == null) { + return orig != null; + } - valueField = new JTextField(); - valueField.setText(value); + return !newValue.equals(orig); + } - reload(); - this.add(valueField, BorderLayout.CENTER); + private void addStringField(final MetaInfo info, int nhgap) { + final JTextField field = new JTextField(); + field.setToolTipText(info.getDescription()); + String value = info.getString(false); + reload(value); + field.setText(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + String value = info.getString(false); + reload(value); + field.setText(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - /** - * Reload the value from the {@link Bundle}. - */ - public void reload() { - value = bundle.getString(id); - valueField.setText(value); + private void addBooleanField(final MetaInfo info, int nhgap) { + final JCheckBox field = new JCheckBox(); + field.setToolTipText(info.getDescription()); + Boolean state = info.getBoolean(true); + + // Should not happen! + if (state == null) { + System.err + .println("No default value given for BOOLEAN parameter \"" + + info.getName() + "\", we consider it is FALSE"); + state = false; + } + + reload(state); + field.setSelected(state); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + Boolean state = info.getBoolean(true); + if (state == null) { + state = false; + } + + reload(state); + field.setSelected(state); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + boolean state = field.isSelected(); + if (isChanged(state)) { + info.setBoolean(state); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); + } + + private void addColorField(final MetaInfo info, int nhgap) { + final JTextField field = new JTextField(); + field.setToolTipText(info.getDescription()); + String value = info.getString(false); + reload(value); + field.setText(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + String value = info.getString(false); + reload(value); + field.setText(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + JPanel pane = new JPanel(new BorderLayout()); + + final JButton colorWheel = new JButton(); + colorWheel.setIcon(getIcon(17, info.getColor(true))); + colorWheel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + 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(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, int nhgap, + final boolean dir) { + final JTextField field = new JTextField(); + field.setToolTipText(info.getDescription()); + String value = info.getString(false); + reload(value); + field.setText(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + String value = info.getString(false); + reload(value); + field.setText(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + String value = field.getText(); + if (isChanged(value)) { + info.setString(value); + } + } + }); + + JButton browseButton = new JButton("..."); + browseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(null); + chooser.setFileSelectionMode(dir ? JFileChooser.DIRECTORIES_ONLY + : JFileChooser.FILES_ONLY); + if (chooser.showOpenDialog(ConfigItem.this) == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + if (file != null) { + String value = file.getAbsolutePath(); + if (isChanged(value)) { + info.setString(value); + } + field.setText(value); + } + } + } + }); + + JPanel pane = new JPanel(new BorderLayout()); + 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, 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); + String value = info.getString(false); + reload(value); + field.setSelectedItem(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + String value = info.getString(false); + reload(value); + field.setSelectedItem(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + Object item = field.getSelectedItem(); + String value = item == null ? null : item.toString(); + if (isChanged(value)) { + info.setString(value); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); + } + + private void addPasswordField(final MetaInfo info, int nhgap) { + final JPasswordField field = new JPasswordField(); + field.setToolTipText(info.getDescription()); + String value = info.getString(false); + reload(value); + field.setText(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + String value = info.getString(false); + reload(value); + field.setText(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + String value = new String(field.getPassword()); + if (isChanged(value)) { + info.setString(value); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); + } + + private void addIntField(final MetaInfo info, int nhgap) { + final JSpinner field = new JSpinner(); + field.setToolTipText(info.getDescription()); + int value = info.getInteger(true) == null ? 0 : info.getInteger(true); + reload(value); + field.setValue(value); + + info.addReloadedListener(new Runnable() { + @Override + public void run() { + int value = info.getInteger(true) == null ? 0 : info + .getInteger(true); + reload(value); + field.setValue(value); + } + }); + info.addSaveListener(new Runnable() { + @Override + public void run() { + int value = field.getValue() == null ? 0 : (Integer) field + .getValue(); + if (isChanged(value)) { + info.setInteger(value); + } + } + }); + + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } /** - * Save the current value to the {@link Bundle}. + * Create a label which width is constrained in lock steps. + * + * @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 */ - public void save() { - value = valueField.getText(); - bundle.setString(id, value); + private JComponent label(final MetaInfo info, int nhgap) { + final JLabel label = new JLabel(info.getName()); + + Dimension ps = label.getPreferredSize(); + if (ps == null) { + ps = label.getSize(); + } + + ps.height = Math.max(ps.height, getMinimumHeight()); + + int w = ps.width; + int step = 150; + for (int i = 2 * step - nhgap; i < 10 * step; i += step) { + if (w < i) { + w = i; + break; + } + } + + final Runnable showInfo = new Runnable() { + @Override + public void run() { + StringBuilder builder = new StringBuilder(); + String text = (info.getDescription().replace("\\n", "\n")) + .trim(); + for (String line : StringUtils.justifyText(text, 80, + Alignment.LEFT)) { + if (builder.length() > 0) { + builder.append("\n"); + } + builder.append(line); + } + text = builder.toString(); + 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(); + } + }); + + JPanel pane2 = new JPanel(new BorderLayout()); + pane2.add(help, BorderLayout.WEST); + pane2.add(new JLabel(" "), 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 + contentPane.setSize(ps); + contentPane.setPreferredSize(ps); + + JPanel pane = new JPanel(new BorderLayout()); + pane.add(contentPane, BorderLayout.NORTH); + + return pane; } /** - * Create a list of {@link ConfigItem}, one for each of the item in the - * given {@link Bundle}. + * Return an {@link Icon} to use as a colour badge for the colour field + * controls. * - * @param - * the type of {@link Bundle} to edit - * @param type - * a class instance of the item type to work on - * @param bundle - * the {@link Bundle} to sort through + * @param size + * the size of the badge + * @param color + * the colour of the badge, which can be NULL (will return + * transparent white) * - * @return the list + * @return the badge */ - static public > List> getItems( - Class type, Bundle bundle) { - List> list = new ArrayList>(); - for (E id : type.getEnumConstants()) { - list.add(new ConfigItem(type, bundle, id)); + 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); + + BufferedImage img = new BufferedImage(size, size, + BufferedImage.TYPE_4BYTE_ABGR); + + Graphics2D g = img.createGraphics(); + try { + g.setColor(c); + g.fillRect(0, 0, img.getWidth(), img.getHeight()); + g.setColor(border); + g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1); + } finally { + g.dispose(); + } + + 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 list; + return minimumHeight; } }