X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=f974969335c2a5b2d725e5682cfe04e8e47420ad;hb=d5026c096121da14c20d69893520594a36d088bb;hp=04bf76c04683291067ffb6671132e225ed7afbe7;hpb=59654e2ab1f6d3314eff438bf9e30ed6f32e5e74;p=fanfix.git diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 04bf76c..f974969 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -1,38 +1,30 @@ 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.List; -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; /** @@ -67,6 +59,12 @@ public class ConfigItem> extends JPanel { /** The original value before current changes. */ private Object orig; + private List dirtyBits; + + protected MetaInfo info; + + private JComponent field; + private List fields = new ArrayList(); /** * Create a new {@link ConfigItem} for the given {@link MetaInfo}. @@ -80,357 +78,349 @@ public class ConfigItem> extends JPanel { * 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(info, true); - switch (fmt) { + ConfigItem configItem; + switch (info.getFormat()) { case BOOLEAN: - addBooleanField(info, nhgap); + configItem = new ConfigItemBoolean(info); break; case COLOR: - addColorField(info, nhgap); + configItem = new ConfigItemColor(info); break; case FILE: - addBrowseField(info, nhgap, false); + configItem = new ConfigItemBrowse(info, false); break; case DIRECTORY: - addBrowseField(info, nhgap, true); + configItem = new ConfigItemBrowse(info, true); break; case COMBO_LIST: - addComboboxField(info, nhgap, true); + configItem = new ConfigItemCombobox(info, true); break; case FIXED_LIST: - addComboboxField(info, nhgap, false); + configItem = new ConfigItemCombobox(info, false); break; case INT: - addIntField(info, nhgap); + configItem = new ConfigItemInteger(info); break; case PASSWORD: - addPasswordField(info, nhgap); + configItem = new ConfigItemPassword(info); break; case STRING: case LOCALE: // TODO? default: - addStringField(info, nhgap); + configItem = new ConfigItemString(info); break; } - } - private void reload(Object value) { - // We consider "" and NULL to be equals - if ("".equals(value)) { - value = null; - } - orig = value; - } + if (info.isArray()) { + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); + + final JPanel main = new JPanel(); + main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); + int size = info.getListSize(false); + for (int i = 0; i < size; i++) { + JComponent field = configItem.createComponent(i); + main.add(field); + } - private boolean isChanged(Object newValue) { - // We consider "" and NULL to be equals - if ("".equals(newValue)) { - newValue = null; - } + // TODO: image + final JButton add = new JButton("+"); + final ConfigItem fconfigItem = configItem; + add.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JComponent field = fconfigItem + .createComponent(fconfigItem.info + .getListSize(false)); + main.add(field); + + // TODO this doesn't woooooorkk + add.invalidate(); + field.invalidate(); + main.invalidate(); + ConfigItem.this.repaint(); + ConfigItem.this.validate(); + ConfigItem.this.repaint(); + } + }); - if (newValue == null) { - return orig != null; - } + JPanel tmp = new JPanel(new BorderLayout()); + tmp.add(add, BorderLayout.WEST); - return !newValue.equals(orig); - } + JPanel mainPlus = new JPanel(new BorderLayout()); + mainPlus.add(main, BorderLayout.CENTER); + mainPlus.add(tmp, BorderLayout.SOUTH); - 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); + add(mainPlus, BorderLayout.CENTER); + } else { + this.setLayout(new BorderLayout()); + add(label(nhgap), BorderLayout.WEST); - 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); + JComponent field = configItem.createComponent(-1); + add(field, BorderLayout.CENTER); + } } - 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; + /** + * 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(); } + } - 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); - } - } - }); + /** + * Create an empty graphical component to be used later by + * {@link ConfigItem#getField(int)}. + *

+ * Note that {@link ConfigItem#reload(int)} will be called after it was + * created. + * + * @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 createField(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the sublasses + return null; + } - 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 + */ + protected Object getFromInfo(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + return null; + } - 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) + */ + protected void setToInfo(@SuppressWarnings("unused") Object value, + @SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses } - 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); + /** + * @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 + */ + protected Object getFromField(@SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + return null; + } - 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); - } - } - }); + /** + * 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) + */ + protected void setToField(@SuppressWarnings("unused") Object value, + @SuppressWarnings("unused") int item) { + // Not used by the main class, only the subclasses + } - 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#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) + * @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) { - 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); + 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()); - 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); - } - } - } - }); + /** + * Retrieve the associated graphical component that was created with + * {@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 + */ + protected JComponent getField(int item) { + if (item < 0) { + return field; + } - 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); + if (item < fields.size()) { + return fields.get(item); + } - setPreferredSize(pane); + return null; } - 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); + /** + * 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); + } + } - setPreferredSize(field); + /** + * 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 + * + * @return TRUE if it has + */ + protected boolean hasValueChanged(Object value) { + // We consider "" and NULL to be equals + return !orig.equals(value == null ? "" : value); } - 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); + /** + * 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) + */ + protected void reload(int item) { + Object value = getFromInfo(item); + setToField(value, item); - 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); - } - } - }); + // We consider "" and NULL to be equals + orig = (value == null ? "" : value); + } - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); + /** + * 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)}. + * + * @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 save(int item) { + Object value = getFromField(item); + + boolean dirty = false; + if (dirtyBits != null) { + dirty = dirtyBits.remove((Integer) item); + } else { + // We consider "" and NULL to be equals + dirty = hasValueChanged(value); + } - setPreferredSize(field); + if (dirty) { + info.setDirty(); + setToInfo(value, item); + orig = (value == null ? "" : value); + } } - 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); + /** + * + * @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 addTo + * @param nhgap + */ + protected JComponent createComponent(final int item) { + setField(item, createField(item)); + reload(item); info.addReloadedListener(new Runnable() { @Override public void run() { - int value = info.getInteger(true) == null ? 0 : info - .getInteger(true); - reload(value); - field.setValue(value); + reload(item); } }); info.addSaveListener(new Runnable() { @Override public void run() { - int value = field.getValue() == null ? 0 : (Integer) field - .getValue(); - if (isChanged(value)) { - info.setInteger(value); - } + save(item); } }); - this.add(label(info, nhgap), BorderLayout.WEST); - this.add(field, BorderLayout.CENTER); - + JComponent field = getField(item); 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 @@ -439,7 +429,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(); @@ -517,45 +507,7 @@ public class ConfigItem> extends JPanel { return pane; } - /** - * Return an {@link Icon} to use as a colour badge for the colour field - * controls. - * - * @param size - * the size of the badge - * @param color - * the colour of the badge, which can be NULL (will return - * transparent white) - * - * @return the badge - */ - 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) { + protected void setPreferredSize(JComponent field) { int height = Math .max(getMinimumHeight(), field.getMinimumSize().height); setPreferredSize(new Dimension(200, height));