X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=1f698860c4835caba63f4bc6ff1befe7fecf7e81;hb=5584adbbbf5444c0039fed2b35dc7d5bb57b71b1;hp=f3b729dc63515cb0aaa21af4ac3a48293500463c;hpb=d18e136e69f03efe5fd6b8e6536cf8ad9033da1a;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index f3b729d..1f69886 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -1,38 +1,32 @@ 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import javax.swing.Icon; +import javax.swing.BoxLayout; 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 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; /** @@ -48,11 +42,13 @@ import be.nikiroo.utils.resources.MetaInfo; * @param * the type of {@link Bundle} to edit */ -public class ConfigItem> extends JPanel { +public abstract class ConfigItem> extends JPanel { private static final long serialVersionUID = 1L; - /** A small (?) blue in PNG, base64 encoded. */ - private static String infoImage64 = // + private static int minimumHeight = -1; + + /** A small 16x16 "?" blue in PNG, base64 encoded. */ + private static String img64info = // "" + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G" @@ -63,297 +59,514 @@ public class ConfigItem> extends JPanel { + "LRIVuX1x7ciuSWQxVIrunONrfq3dI6oh+T94Z8453vEem/HTqT8ZpFJ0qDXtGkPbAGAMeSRngQCA" + "eUvgn195AwlZWyvjtQdhAAAAAElFTkSuQmCC"; + /** A small 16x16 "+" image with colours */ + private static String img64add = // + "" + + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" + + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeES0QBFvvnAAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl" + + "YXRlZCB3aXRoIEdJTVBkLmUHAAACH0lEQVQ4y42Tz0sVURTHP+fMmC7CQMpH1EjgIimCsEVBEIg/" + + "qIbcBAW2Uai1m/oH2rlJXLQpeRJt2gQhTO0iTTKC1I2JBf5gKCJCRPvhPOed22LmvV70Fn7hwr3c" + + "+z3ne+73HCFHEClxaASRHgduA91AW369BkwDI3Foy0GkEofmACQnSxyaCyItAkMClMzYdeCAJgVP" + + "tJJrPA7tVoUjNZlngXMAiRmXClfoK/Tjq09x7T6LW+8RxOVJ5+LQzgSRojm5WCEDlMrQVbjIQNtN" + + "rh0d5FTzaTLBmWKgM4h0Ig4NzWseohYCJUuqx123Sx0MBpF2+MAdyWUnlqX4lf4bIDHjR+rwJJPR" + + "qNCgCjDsA10lM/oKIRcO9lByCYklnG/pqQa4euQ6J5tPoKI0yD6ef33Ku40Z80R7CSJNWyZxT+Ki" + + "2ytGP911hyZxQaRp1RtPPPYKD4+sGJwPrDUp7Q9Xxnj9fYrUUnaszEAwQHfrZQAerT/g7cYMiuCp" + + "z8LmLI0qBqz6wLQn2v5he57FrXkAtlPH2ZZOuskCzG2+4dnnx3iSuSgCKqLAlAIjmXPiVIRsgYjU" + + "usrfO0Gq7cA9jUNbBsZrmiQnac1e6n3FeBzakpf39OSBG9IPHAZwzlFoagVg5edHXn57wZed9dpA" + + "C3FoYRDpf8M0AQwKwu9yubxjeA7Y72ENqlp3mOqMcwcwDPQCx8gGchV4BYzGoS1V3gL8AVA5C5/0" + + "oRFoAAAAAElFTkSuQmCC"; + + /** A small 32x32 "-" image with colours */ + private static String img64remove = // + "" + + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" + + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeESw5X/JGsQAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl" + + "YXRlZCB3aXRoIEdJTVBkLmUHAAACKUlEQVQ4y5WTO2iTYRSG3+//v/+SJrG5SSABh1JQBHFJNUNR" + + "YodCLoMoTkK0YKhQtBmsl01wKVZRBwcrgosg3SwFW9Cippe0VmlpB6uYqYIaNSZtbv/lOKRx0iR9" + + "4YOzvOc8vOd8wLbG4nYGAKP9tshKr3Pq0zFXORt0UzbopvUeZ2ml1/niUcIWAYBzwwqr+xgAjCSt" + + "wpXjWzx105Ha+1XsMgT8U6IJfPAacyfO50OXJi3VwbtbxMbidtZ3tiClbzi/eAuCmxgai4AfNvNn" + + "KJn3X5xWKgwA0lHHYud3MdDUXMcmIOMx0oGJXJCN9tuiJ98p4//DbtTk2cFKhB/OSBcMgQHVMkir" + + "AqwJBhGYrIIkCQc2eJK3aewI9Crko2FIh0K1Jo0mcwmV6XFUlmfRXhK7eXuRKaRVIYdiUGKnW8Kn" + + "0ia0t6/hKHJVqCcLzncQgLhtIvBfbWbZZahq+cl96AuvQLre2Mw59NUlkCwjZ6USL0uYgSj26B/X" + + "oK+vtkYgMAhMRF4x5oWlPdod0UQtfUFo7YEBBKz59BEGAAtRx1xHVgzu5AYyHmMmMJHrZolhhU3t" + + "05XJe7s2PJuCq9k1MgKyNjOXiBf8kWW5JDy4XKHBl2ql6+pvX8ZjzDOqrcWsFQAAE/T3H3z2GG/6" + + "zhT8sfdKeehWkUQAeJ7WcH23xTz1uPBwf1hclA3mBZjPojFOIOSsVPpmN1OznfpA+Gn+2kCHqg/d" + + "LhIA/AFU5d0V6gTjtQAAAABJRU5ErkJggg=="; + + /** The original value before current changes. */ + private Object orig; + private List origs = new ArrayList(); + private List dirtyBits; + + /** The fields (one for non-array, a list for arrays). */ + private JComponent field; + private List fields = new ArrayList(); + + /** The fields to panel map to get the actual item added to 'main'. */ + private Map itemFields = new HashMap(); + + /** The main panel with all the fields in it. */ + private JPanel main; + + /** The {@link MetaInfo} linked to the field. */ + protected MetaInfo info; + /** * 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(); + public void init(int nhgap) { if (info.isArray()) { - fmt = Format.STRING; - } + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); - 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; + main = new JPanel(); + + main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); + int size = info.getListSize(false); + for (int i = 0; i < size; i++) { + addItem(i); + } + main.revalidate(); + main.repaint(); + + final JButton add = new JButton(); + setImage(add, img64add, "+"); + + add.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addItem(fields.size()); + main.revalidate(); + main.repaint(); + } + }); + + JPanel tmp = new JPanel(new BorderLayout()); + tmp.add(add, BorderLayout.WEST); + + JPanel mainPlus = new JPanel(new BorderLayout()); + mainPlus.add(main, BorderLayout.CENTER); + mainPlus.add(tmp, BorderLayout.SOUTH); + + add(mainPlus, BorderLayout.CENTER); + } else { + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); + + JComponent field = createField(-1); + add(field, BorderLayout.CENTER); } } - private void addStringField(final MetaInfo info, int nhgap) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - field.setText(info.getString(false)); + private void addItem(final int item) { + JPanel minusPanel = new JPanel(new BorderLayout()); + itemFields.put(item, minusPanel); - info.addReloadedListener(new Runnable() { - @Override - public void run() { - field.setText(info.getString(false)); - } - }); - info.addSaveListener(new Runnable() { + JComponent field = createField(item); + + final JButton remove = new JButton(); + setImage(remove, img64remove, "-"); + + remove.addActionListener(new ActionListener() { @Override - public void run() { - info.setString(field.getText()); + public void actionPerformed(ActionEvent e) { + removeItem(item); } }); - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + minusPanel.add(field, BorderLayout.CENTER); + minusPanel.add(remove, BorderLayout.EAST); - setPreferredSize(field); + main.add(minusPanel); + main.revalidate(); + main.repaint(); } - 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; + private void removeItem(int item) { + int last = itemFields.size() - 1; + + for (int i = item; i <= last; i++) { + Object value = null; + if (i < last) { + value = getFromField(i + 1); + } + setToField(value, i); + setToInfo(value, i); + setDirtyItem(i); } - field.setSelected(state); + main.remove(itemFields.remove(last)); + main.revalidate(); + main.repaint(); + } - info.addReloadedListener(new Runnable() { - @Override - public void run() { - Boolean state = info.getBoolean(true); - if (state == null) { - state = false; - } + /** + * Prepare a new {@link ConfigItem} instance, linked to the given + * {@link MetaInfo}. + * + * @param info + * the info + * @param autoDirtyHandling + * TRUE to automatically manage the setDirty/Save operations, + * FALSE if you want to do it yourself via + * {@link ConfigItem#setDirtyItem(int)} + */ + protected ConfigItem(MetaInfo info, boolean autoDirtyHandling) { + this.info = info; + if (!autoDirtyHandling) { + dirtyBits = new ArrayList(); + } + } - field.setSelected(state); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - info.setBoolean(field.isSelected()); - } - }); + /** + * Create an empty graphical component to be used later by + * {@link ConfigItem#createField(int)}. + *

+ * Note that {@link ConfigItem#reload(int)} will be called after it was + * created by {@link ConfigItem#createField(int)}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the graphical component + */ + abstract protected JComponent createEmptyField(int item); - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * Get the information from the {@link MetaInfo} in the subclass preferred + * format. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the information in the subclass preferred format + */ + abstract protected Object getFromInfo(int item); - setPreferredSize(field); - } + /** + * Set the value to the {@link MetaInfo}. + * + * @param value + * the value in the subclass preferred format + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + abstract protected void setToInfo(Object value, int item); - private void addColorField(final MetaInfo info, int nhgap) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - field.setText(info.getString(false)); + /** + * The value present in the given item's related field in the subclass + * preferred format. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the value present in the given item's related field in the + * subclass preferred format + */ + abstract protected Object getFromField(int item); - info.addReloadedListener(new Runnable() { - @Override - public void run() { - field.setText(info.getString(false)); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - info.setString(field.getText()); - } - }); + /** + * Set the value (in the subclass preferred format) into the field. + * + * @param value + * the value in the subclass preferred format + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + abstract protected void setToField(Object value, int item); - this.add(label(info, nhgap), BorderLayout.WEST); - JPanel pane = new JPanel(new BorderLayout()); + /** + * Create a new field for the given graphical component at the given index + * (note that the component is usually created by + * {@link ConfigItem#createEmptyField(int)}). + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param field + * the graphical component + */ + private void setField(int item, JComponent field) { + if (item < 0) { + this.field = field; + return; + } - final JButton colorWheel = new JButton(); - colorWheel.setIcon(getIcon(17, info.getColor(true))); - colorWheel.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Color initialColor = new Color(info.getColor(true), 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); + for (int i = fields.size(); i <= item; i++) { + fields.add(null); + } - setPreferredSize(pane); + fields.set(item, field); } - private void addBrowseField(final MetaInfo info, int nhgap, - final boolean dir) { - final JTextField field = new JTextField(); - field.setToolTipText(info.getDescription()); - field.setText(info.getString(false)); + /** + * Retrieve the associated graphical component that was created with + * {@link ConfigItem#createEmptyField(int)}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the graphical component + */ + protected JComponent getField(int item) { + if (item < 0) { + return field; + } - info.addReloadedListener(new Runnable() { - @Override - public void run() { - field.setText(info.getString(false)); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - info.setString(field.getText()); - } - }); + if (item < fields.size()) { + return fields.get(item); + } - 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) { - info.setString(file.getAbsolutePath()); - field.setText(info.getString(false)); - } - } + return null; + } + + /** + * The original value (before any changes to the {@link MetaInfo}) for this + * item. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the original value + */ + private Object getOrig(int item) { + if (item < 0) { + return orig; + } + + if (item < origs.size()) { + return origs.get(item); + } + + return null; + } + + /** + * The original value (before any changes to the {@link MetaInfo}) for this + * item. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * @param value + * the new original value + */ + private void setOrig(Object value, int item) { + if (item < 0) { + orig = value; + } else { + while (item >= origs.size()) { + origs.add(null); } - }); - 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); + origs.set(item, value); + } + } - setPreferredSize(pane); + /** + * Manually specify that the given item is "dirty" and thus should be saved + * when asked. + *

+ * Has no effect if the class is using automatic dirty handling (see + * {@link ConfigItem#ConfigItem(MetaInfo, boolean)}). + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + protected void setDirtyItem(int item) { + if (dirtyBits != null) { + dirtyBits.add(item); + } } - 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(false)); + /** + * Check if the value changed since the last load/save into the linked + * {@link MetaInfo}. + *

+ * Note that we consider NULL and an Empty {@link String} to be equals. + * + * @param value + * the value to test + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return TRUE if it has + */ + protected boolean hasValueChanged(Object value, int item) { + // We consider "" and NULL to be equals + Object orig = getOrig(item); + if (orig == null) { + orig = ""; + } + return !orig.equals(value == null ? "" : value); + } - info.addReloadedListener(new Runnable() { - @Override - public void run() { - field.setSelectedItem(info.getString(false)); + /** + * Reload the values to what they currently are in the {@link MetaInfo}. + */ + private void reload() { + if (info.isArray()) { + while (!itemFields.isEmpty()) { + main.remove(itemFields.remove(itemFields.size() - 1)); } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - info.setString(field.getSelectedItem().toString()); + main.revalidate(); + main.repaint(); + for (int item = 0; item < info.getListSize(false); item++) { + reload(item); } - }); + } else { + reload(-1); + } + } - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * Reload the values to what they currently are in the {@link MetaInfo}. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + */ + private void reload(int item) { + if (item >= 0 && !itemFields.containsKey(item)) { + addItem(item); + } - setPreferredSize(field); + Object value = getFromInfo(item); + setToField(value, item); + setOrig(value == null ? "" : value, item); } - private void addPasswordField(final MetaInfo info, int nhgap) { - final JPasswordField field = new JPasswordField(); - field.setToolTipText(info.getDescription()); - field.setText(info.getString(true)); + /** + * If the item has been modified, set the {@link MetaInfo} to dirty then + * modify it to, reflect the changes so it can be saved later. + *

+ * This method does not call {@link MetaInfo#save(boolean)}. + */ + private void save() { + if (info.isArray()) { + boolean dirty = itemFields.size() != info.getListSize(false); + for (int item = 0; item < itemFields.size(); item++) { + if (getDirtyBit(item)) { + dirty = true; + } + } - info.addReloadedListener(new Runnable() { - @Override - public void run() { - field.setText(info.getString(false)); + if (dirty) { + info.setDirty(); + info.setString(null, -1); + + for (int item = 0; item < itemFields.size(); item++) { + Object value = null; + if (getField(item) != null) { + value = getFromField(item); + if ("".equals(value)) { + value = null; + } + } + + setToInfo(value, item); + setOrig(value, item); + } } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - info.setString(new String(field.getPassword())); + } else { + if (getDirtyBit(-1)) { + Object value = getFromField(-1); + + info.setDirty(); + setToInfo(value, -1); + setOrig(value, -1); } - }); + } + } - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * Check if the item is dirty, and clear the dirty bit if set. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return TRUE if it was dirty, FALSE if not + */ + private boolean getDirtyBit(int item) { + if (dirtyBits != null) { + return dirtyBits.remove((Integer) item); + } + + Object value = null; + if (getField(item) != null) { + value = getFromField(item); + } - setPreferredSize(field); + return hasValueChanged(value, item); } - private void addIntField(final MetaInfo info, int nhgap) { - final JSpinner field = new JSpinner(); - field.setToolTipText(info.getDescription()); - field.setValue(info.getInteger(true) == null ? 0 : info - .getInteger(true)); + /** + * Create a new field for the given item. + * + * @param item + * the item number to get for an array of values, or -1 to get + * the whole value (has no effect if {@link MetaInfo#isArray()} + * is FALSE) + * + * @return the newly created field + */ + protected JComponent createField(final int item) { + JComponent field = createEmptyField(item); + setField(item, field); + reload(item); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setValue(info.getInteger(true) == null ? 0 : info - .getInteger(true)); + reload(); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setInteger((Integer) field.getValue()); - Integer value = info.getInteger(false); - if (value == null) { - field.setValue(0); - } + save(); } }); - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + int height = Math + .max(getMinimumHeight(), field.getMinimumSize().height); + field.setPreferredSize(new Dimension(200, height)); - setPreferredSize(field); + return field; } /** * 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 @@ -362,7 +575,7 @@ public class ConfigItem> extends JPanel { * * @return the label */ - private JComponent label(final MetaInfo info, int nhgap) { + protected JComponent label(int nhgap) { final JLabel label = new JLabel(info.getName()); Dimension ps = label.getPreferredSize(); @@ -370,6 +583,8 @@ public class ConfigItem> extends JPanel { 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) { @@ -400,18 +615,7 @@ public class ConfigItem> extends JPanel { 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("?"); - } + setImage(help, img64info, "?"); help.addMouseListener(new MouseAdapter() { @Override @@ -424,53 +628,137 @@ 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; } /** - * Return an {@link Icon} to use as a colour badge for the colour field - * controls. + * Create a new {@link ConfigItem} for the given {@link MetaInfo}. + * + * @param + * the type of {@link Bundle} to edit * - * @param size - * the size of the badge - * @param color - * the colour of the badge + * @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) * - * @return the badge + * @return the new {@link ConfigItem} */ - private Icon getIcon(int size, int color) { - Color c = new Color(color, true); - int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3; - Color border = (avg >= 128 ? Color.BLACK : Color.WHITE); + static public > ConfigItem createItem( + MetaInfo info, int nhgap) { + + ConfigItem configItem; + switch (info.getFormat()) { + case BOOLEAN: + configItem = new ConfigItemBoolean(info); + break; + case COLOR: + configItem = new ConfigItemColor(info); + break; + case FILE: + configItem = new ConfigItemBrowse(info, false); + break; + case DIRECTORY: + configItem = new ConfigItemBrowse(info, true); + break; + case COMBO_LIST: + configItem = new ConfigItemCombobox(info, true); + break; + case FIXED_LIST: + configItem = new ConfigItemCombobox(info, false); + break; + case INT: + configItem = new ConfigItemInteger(info); + break; + case PASSWORD: + configItem = new ConfigItemPassword(info); + break; + case LOCALE: + configItem = new ConfigItemLocale(info); + break; + case STRING: + default: + configItem = new ConfigItemString(info); + break; + } - BufferedImage img = new BufferedImage(size, size, - BufferedImage.TYPE_4BYTE_ABGR); + configItem.init(nhgap); + return configItem; + } - Graphics2D g = img.createGraphics(); + /** + * Set an image to the given {@link JButton}, with a fallback text if it + * fails. + * + * @param button + * the button to set + * @param image64 + * the image in BASE64 (should be PNG or similar) + * @param fallbackText + * text to use in case the image cannot be created + */ + static protected void setImage(JLabel button, String image64, + String fallbackText) { 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(); + Image img = new Image(image64); + try { + BufferedImage bImg = ImageUtilsAwt.fromImage(img); + button.setIcon(new ImageIcon(bImg)); + } finally { + img.close(); + } + } catch (IOException e) { + // This is an hard-coded image, should not happen + button.setText(fallbackText); } + } - return new ImageIcon(img); + /** + * Set an image to the given {@link JButton}, with a fallback text if it + * fails. + * + * @param button + * the button to set + * @param image64 + * the image in BASE64 (should be PNG or similar) + * @param fallbackText + * text to use in case the image cannot be created + */ + static protected void setImage(JButton button, String image64, + String fallbackText) { + try { + Image img = new Image(image64); + try { + BufferedImage bImg = ImageUtilsAwt.fromImage(img); + button.setIcon(new ImageIcon(bImg)); + } finally { + img.close(); + } + } catch (IOException e) { + // This is an hard-coded image, should not happen + button.setText(fallbackText); + } } - private void setPreferredSize(JComponent field) { - JTextField a = new JTextField("Test"); - int height = Math.max(a.getMinimumSize().height, - field.getMinimumSize().height); - setPreferredSize(new Dimension(200, height)); + static private int getMinimumHeight() { + if (minimumHeight < 0) { + minimumHeight = new JTextField("Test").getMinimumSize().height; + } + + return minimumHeight; } }