X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;fp=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=0000000000000000000000000000000000000000;hp=1f698860c4835caba63f4bc6ff1befe7fecf7e81;hb=46add0670fdee4bd936a13fe2448c5e20a7ffd0a;hpb=1b5197ed4ceec2025a9a40c417b37c646b756138 diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java deleted file mode 100644 index 1f69886..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ /dev/null @@ -1,764 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.Dimension; -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.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -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.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 - * - * @param - * the type of {@link Bundle} to edit - */ -public abstract class ConfigItem> extends JPanel { - private static final long serialVersionUID = 1L; - - private static int minimumHeight = -1; - - /** A small 16x16 "?" blue in PNG, base64 encoded. */ - private static String img64info = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" - + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G" - + "m1RDogYxq7WDDYMYTSajSG4n6YRYzSaSLibWbiaDIGwdiLIYDFKDNJEgKu969xi8UNHy7H7LPcN3" - + "v/Odcy+hG9oOIeIcBCJS9MAvlZtOMtHxsrFrJHGqe0RVGnHAHpcIbPlng8BS3HmKBJYzabGUzcrJ" - + "XK+ckIrqANYR2JEv2nYDEVck0WKGfHzyq82Go+btxoX3XAcAIqTj8wPqOH6mtMeM4bGCLhyfhTMA" - + "qlLhKHqujCfaweCAmV0p50dPzsNpEKpK01V/n55HIvTnfDC2odKlfeYadZN/T+AqDACUsnkhqaU1" - + "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 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 void init(int nhgap) { - if (info.isArray()) { - this.setLayout(new BorderLayout()); - add(label(nhgap), BorderLayout.WEST); - - 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 addItem(final int item) { - JPanel minusPanel = new JPanel(new BorderLayout()); - itemFields.put(item, minusPanel); - - JComponent field = createField(item); - - final JButton remove = new JButton(); - setImage(remove, img64remove, "-"); - - remove.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeItem(item); - } - }); - - minusPanel.add(field, BorderLayout.CENTER); - minusPanel.add(remove, BorderLayout.EAST); - - main.add(minusPanel); - main.revalidate(); - main.repaint(); - } - - 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); - } - - main.remove(itemFields.remove(last)); - main.revalidate(); - main.repaint(); - } - - /** - * 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(); - } - } - - /** - * 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); - - /** - * 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); - - /** - * 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); - - /** - * 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); - - /** - * 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); - - /** - * 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; - } - - for (int i = fields.size(); i <= item; i++) { - fields.add(null); - } - - fields.set(item, field); - } - - /** - * 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; - } - - if (item < fields.size()) { - return fields.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) - * - * @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); - } - - origs.set(item, value); - } - } - - /** - * 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); - } - } - - /** - * 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); - } - - /** - * 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)); - } - main.revalidate(); - main.repaint(); - for (int item = 0; item < info.getListSize(false); item++) { - reload(item); - } - } else { - reload(-1); - } - } - - /** - * 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); - } - - Object value = getFromInfo(item); - setToField(value, item); - setOrig(value == null ? "" : value, item); - } - - /** - * 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; - } - } - - 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); - } - } - } else { - if (getDirtyBit(-1)) { - Object value = getFromField(-1); - - info.setDirty(); - setToInfo(value, -1); - setOrig(value, -1); - } - } - } - - /** - * 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); - } - - return hasValueChanged(value, item); - } - - /** - * 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() { - reload(); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - save(); - } - }); - - int height = Math - .max(getMinimumHeight(), field.getMinimumSize().height); - field.setPreferredSize(new Dimension(200, height)); - - return field; - } - - /** - * Create a label which width is constrained in lock steps. - * - * @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 - */ - protected JComponent label(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)); - setImage(help, img64info, "?"); - - 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 new {@link ConfigItem} for the given {@link MetaInfo}. - * - * @param - * the type of {@link Bundle} to edit - * - * @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 new {@link ConfigItem} - */ - 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; - } - - configItem.init(nhgap); - return configItem; - } - - /** - * 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 { - 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); - } - } - - /** - * 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); - } - } - - static private int getMinimumHeight() { - if (minimumHeight < 0) { - minimumHeight = new JTextField("Test").getMinimumSize().height; - } - - return minimumHeight; - } -}