X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FConfigItem.java;h=c6b40753addabce25fc9d10c8b01c6ac426d7905;hb=ee7a6ccb60b905ad1363805495652e2da81ddeb7;hp=6afbb9457e69582ac760ceff6f7155a261c73cc0;hpb=424dcb0d7835d3be74134bfa7c4152e492c6f9ce;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 6afbb94..c6b4075 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -1,29 +1,39 @@ 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.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.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.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
*
@@ -33,174 +43,476 @@ import be.nikiroo.utils.resources.MetaInfo;
public class ConfigItem
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * 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
+ }
+
+ /**
+ * 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;
+ }
+
+ 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#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;
+ }
- this.add(label(info.getName()), BorderLayout.WEST);
- this.add(field, BorderLayout.CENTER);
+ if (item < fields.size()) {
+ return fields.get(item);
+ }
+
+ return null;
+ }
+
+ /**
+ * 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
+ *
+ * @return TRUE if it has
+ */
+ protected boolean hasValueChanged(Object value) {
+ // We consider "" and NULL to be equals
+ return !orig.equals(value == null ? "" : 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);
+
+ // We consider "" and NULL to be equals
+ orig = (value == null ? "" : value);
+ }
+
+ /**
+ * 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);
+ }
+
+ if (dirty) {
+ info.setDirty();
+ setToInfo(value, item);
+ orig = (value == null ? "" : 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() {
+ reload(item);
+ }
+ });
+ info.addSaveListener(new Runnable() {
+ @Override
+ public void run() {
+ save(item);
+ }
+ });
+
+ JComponent field = getField(item);
+ setPreferredSize(field);
+
+ return field;
+ }
+
/**
* Create a label which width is constrained in lock steps.
*
- * @param text
- * the text of the 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 JLabel label(String text) {
- final JLabel label = new JLabel(text);
+ 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 = 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;
}
}
- ps.width = w;
- label.setSize(ps);
- label.setPreferredSize(ps);
+ 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);
+ }
+ };
- return label;
- }
+ 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("?");
+ }
- /**
- * 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
- *
- * @return the badge
- */
- 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);
+ help.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ showInfo.run();
+ }
+ });
- BufferedImage img = new BufferedImage(size, size,
- BufferedImage.TYPE_4BYTE_ABGR);
+ JPanel pane2 = new JPanel(new BorderLayout());
+ pane2.add(help, BorderLayout.WEST);
+ pane2.add(new JLabel(" "), BorderLayout.CENTER);
- 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();
+ 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;
+ }
+
+ protected 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 new ImageIcon(img);
+ return minimumHeight;
}
}